Skip to content

Commit

Permalink
Add so much documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Dressman committed Mar 11, 2015
1 parent e66b277 commit b813eef
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 78 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ npm install
gulp
```

Visit [http://localhost:9312](http://localhost:9312)

## Libraries
- [fluxible](http://fluxible.io) - pluggable application container to facilitate an isomorphic React+Flux architecture. Developed by Yahoo. Makes use of plugins wrapping smaller core libraries:
- [dispatchr](https://github.com/yahoo/dispatchr) - isolates dispatcher and stores per request
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
"dependencies": {
"body-parser": "^1.10.0",
"browserify": "^9.0.3",
"debug": "^2.1.1",
"express": "^4.9.5",
"flux-router-component": "^0.5.8",
"fluxible": "^0.2.1",
Expand Down
92 changes: 60 additions & 32 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,109 @@

require('node-jsx').install({extension: '.jsx', harmony: true}); // parses JSX and enables ES6/7 transpiling

var express = require('express');
var bodyParser = require('body-parser');
var app = require('../src/app'); // Fluxible app
var path = require('path'); // path util
var React = require('react');
var express = require('express');
var bodyParser = require('body-parser');
var path = require('path');
var serialize = require('serialize-javascript');
var debug = require('debug')('fluxMiddleware');
var React = require('react');
var app = require('../src/app'); // Fluxible app
var navigateAction = require('flux-router-component').navigateAction;
var HtmlComponent = React.createFactory(require('../src/components/html-component.jsx'));

// Make our node process bulletproof
process.on('uncaughtException', function(err) {
console.error(err.stack);
});

// initialize our server
var server = express();
server.set('state namespace', 'App');

// serve our 'build' dir assets under '/v2/assets'
server.use('/v2/assets', express.static(path.join(__dirname, '..', 'dist')));

// automatically parse any encoding of JSON
server.use(bodyParser.json());
server.use('/v2/assets', express.static(path.join(__dirname, '..', 'dist')));

// Get access to the fetchr plugin instance
// Get access to the app's fetchr plugin instance
var fetchrPlugin = app.getPlugin('FetchrPlugin');

// Register our REST services
// Register our REST services with fetchr
fetchrPlugin.registerService(require('./services/project-service'));

// Set up the fetchr middleware
// Set up the fetchr server middleware
server.use(fetchrPlugin.getXhrPath(), fetchrPlugin.getMiddleware());

// attach our flux middleware (this is our actual app)
/*
* The is the entrypoint into the Flux flow
*/
server.use(function(req, res) {

/*
* Create a request-scoped context to isolate data per request
*/
var context = app.createContext({
req: req, // The fetchr plugin depends on this
xhrContext: { // Used as query params for all XHR calls
lang: 'en-US', // make sure XHR calls receive the same lang as the initial request
_csrf: 'a3fc2d' // CSRF token to validate on the server using your favorite library
req: req, // pass the request object to fetchr
xhrContext: { // query params for all fetchr XHR calls
lang: 'en-US',
_csrf: 'a3fc2d'
}
});

/*
* Fluxible has context interfaces for Action Creators, Components,
* and Stores which provide access to Flux methods needed by each.
*
* Action Context provides dispatch, executeAction, and getStore
*/
var actionContext = context.getActionContext();

debug('Executing navigate action');
/*
* navigateAction takes req.url and matches a route from src/routes.js,
* executing the route object's action, if it exists. After the route's
* action, we continue in this function's callback.
*/
actionContext.executeAction(navigateAction, {
url: req.url
}, function (err) {

if (err) {
res.status(500);
res.send("five hundo. sad panda.");
return;
}

debug('Exposing context state to client as window.App');
/*
* Exposing your app's server-rendered state so React can re-initialize
* client-side on top of the existing DOM.
*
* Dispatchr provides dehydrate/rehydrayte functions that will serialize
* data from all registered stores.
*
* We create a string variable to pass into the HtmlComponent below,
* which will be rendered as JavaScript to create an App variable on
* the global window object.
*/
var exposed = 'window.App=' + serialize(app.dehydrate(context)) + ';';

debug('Rendering Application component into html');
// Application's root component defined in app.js
var Component = app.getComponent();

/*
* Render our React application to basic HTML. This function adds
* data-reactid attributes to each DOM node for the client to reconcile.
*
* Component Context provides access to getStore and executeAction and
* is shared with all child components using React's built-in context
* thanks to FluxibleMixin.
*
* The markup prop will contain the output from React rendering our
* root app component.
*
*/
var html = React.renderToStaticMarkup(HtmlComponent({
context: context.getComponentContext(),
state: exposed,
markup: React.renderToString(Component({context:context.getComponentContext()}))
markup: React.renderToString(
Component({ context: context.getComponentContext() })
)
}));

debug('Sending markup');
// Render!
res.send(html);
});
});

// ok, ready: run the server
// ok, run the server
var port = 9312;
server.listen(port, function() {
var env = process.env.NODE_ENV || 'development';
Expand Down
26 changes: 19 additions & 7 deletions server/services/project-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@

var serverData = require('./data/project-data');

/**
* Returns project data
/*
* Fetchr provides an abstraction which allows you to fetch data using the same
* syntax on both server and client. For example, you can make a request using
* node's request module on the server and use the same service service on the
* client without having to write a separate AJAX request to the same endpoint.
*
* @param {Object} config (optional)
* @param {function} callback async callback
*
* @return {Object} list of projects
* This example service demonstrates asynchronous service by reading and
* writing to an array in a setTimeout call.
*/
module.exports = {

// Actions use this name to call fetchr services
name: 'projectService',

/*
* Fetchr requires CRUD interface names
*/
read: function(req, resource, params, config, callback) {
setTimeout(function () {
callback(null, JSON.parse(JSON.stringify(serverData)));
Expand All @@ -28,5 +34,11 @@ module.exports = {
callback(null, serverData);
}, 10);
}

/*
* Exercise!
* - Add Delete project feature
*
* update: function(req, resource, params, body, config, callback) {},
* delete: function(req, resource, params, config, callback) {}
*/
};
9 changes: 9 additions & 0 deletions src/actions/create-project.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

module.exports = function (context, payload, done) {

/*
* Calls the service's create function and passes in data. This service
* returns all projects including the one newly added.
*/
context.service.create('projectService', payload, {}, function(err, projects) {
if (err) {
console.error(err);
done(err);
return;
}

/*
* Dispatches the same event as the server's load action,
* passing along an updated list of projects to all stores registered
* to handle this event.
*/
context.dispatch('RECEIVE_PROJECTS_SUCCESS', projects);
done();
});
Expand Down
23 changes: 17 additions & 6 deletions src/actions/load-home-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@

var RSVP = require('rsvp');

/**
* executes the navigation to the home page
*
* @param {Context} context
* @param {Object} payload: the route object
* @param {Fn} done
/*
* Action executed on the server before route loads. All server functionality,
* data fetching, etc. needed for initial page load happens here.
*/
module.exports = function (context, payload, done) {

// Create RSVP promise to fetch asynchronous data from the server
var getProjects = new RSVP.Promise(function (resolve, reject) {

/*
* Call projectService's read() with fetchr and handle the response
* in callback. If the call succeeded, resolve the promise passing in
* the server payload. Otherwise, reject the promise with the error.
*/
context.service.read('projectService', {}, {}, function(err, results) {
if (err) { reject(err); }
resolve(results);
});

});

getProjects.then(function(projects) {
/*
* Flux magic!
*
* Dispatch a named event to all stores registered to handle this
* specific event, passing along the action's payload.
*/
context.dispatch('RECEIVE_PROJECTS_SUCCESS', projects);
done();
}).catch(function (err) {
Expand Down
24 changes: 12 additions & 12 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
'use strict';
var React = require('react');
var FluxibleApp = require('fluxible');
var Fluxible = require('fluxible');
var fetchrPlugin = require('fluxible-plugin-fetchr');
var routrPlugin = require('fluxible-plugin-routr');

/*
* This is our porch global flux app. It registers all of our pages, stores,
* and handles the basic routing and page loading.
*/
var app = new FluxibleApp({
* Common application setup code.
*
* - Create new Fluxible app instance
* - Define root application component
* - Install plugins
* - Register stores
*/

var app = new Fluxible({
component: React.createFactory(require('./pages/homepage/home-page'))
});

app.plug(fetchrPlugin({
xhrPath: '/napi/'
}));
app.plug(fetchrPlugin());

app.plug(routrPlugin({
routes: require('./routes')
}));
app.plug(routrPlugin({ routes: require('./routes') }));

// Register required stores
app.registerStore(require('../src/stores/project-store'));

module.exports = app;
33 changes: 26 additions & 7 deletions src/client.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
'use strict';

var app = require('./app');

var React = require('react');
var dehydratedState = window.App; // sent from the server
window.React = React; // for chrome dev tool support
window.React = React; // for Chrome DevTools support

/*
* Grab dehydrated application state from all stores.
* Sent from the server
*/
var dehydratedState = window.App;

/*
* Re-initialize application state and provides the request's
* context object to the callback
*/
app.rehydrate(dehydratedState, function (err, context) {
if (err) {
throw err;
}
window.context = context;

window.context = context;
var mountNode = document.getElementById('app');
var Component = app.getComponent();

React.render(app.getComponent()({context: context.getComponentContext()}), mountNode, function () {
console.log('React client-side rendered.');
});
/*
* React will "render" the application component at the mountNode and
* compare the results with the existing server-rendered DOM.
* If everything matches (!!), React will mount itself on top and attach
* client-side event handlers.
*/
React.render(
Component({context: context.getComponentContext()}),
mountNode,
function () {
console.log('React client-side rendered.');
}
);
});
12 changes: 5 additions & 7 deletions src/components/html-component.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
"use strict";

var React = require('react/addons');
var FluxibleMixin = require('fluxible').Mixin;
var React = require('react');
var FluxibleMixin = require('fluxible').Mixin;

var Html = React.createClass({

mixins: [ FluxibleMixin ],

getInitialState: function () {
return {};
},

render: function () {

return (
Expand All @@ -26,12 +22,14 @@ var Html = React.createClass({
</head>

<body>
{/* Inject root application component */}
<div id="app" dangerouslySetInnerHTML={{__html: this.props.markup}}></div>
</body>

{/* dehydrated json state of all the stores */}
{/* Exposes dehydrated json state of all the stores as window.App */}
<script dangerouslySetInnerHTML={{__html: this.props.state}}></script>

{/* Generated JavaScript from gulp browserify. Loads client.js */}
<script src="/v2/assets/bundle.js"></script>
</html>
);
Expand Down
Loading

0 comments on commit b813eef

Please sign in to comment.