API Docs for: 0.2.2
Show:

File: src\R.App.js

module.exports = function(R) {
    var co = require("co");
    var React = R.React;
    var _ = require("lodash");
    var assert = require("assert");
    var path = require("path");

    /**
    * <p>Simply create an App class with specifics</p>
    * <p>Provides methods in order to render the specified App server-side and client-side</p>
    * <ul>
    * <li> App.createApp => initializes methods of an application according to the specifications provided </li>
    * <li> App.renderToStringInServer => compute all React Components with data and render the corresponding HTML for the requesting client </li>
    * <li> App.renderIntoDocumentInClient => compute all React Components client-side and establishes a connection via socket in order to make data subscriptions</li>
    * <li> App.createPlugin => initiliaziation method of a plugin for the application </li>
    * </ul>
    * @class R.App
    */
    var App = {
        /**
        * <p> Initializes the application according to the specifications provided </p>
        * @method createApp
        * @param {object} specs All the specifications of the App
        * @return {AppInstance} AppInstance The instance of the created App
        */
        createApp: function createApp(specs) {
            R.Debug.dev(function() {
                assert(_.isPlainObject(specs), "R.App.createApp(...).specs: expecting Object.");
                assert(specs.fluxClass && _.isFunction(specs.fluxClass), "R.App.createApp(...).specs.fluxClass: expecting Function.");
                assert(specs.rootClass && _.isFunction(specs.rootClass), "R.App.createApp(...).specs.rootClass: expecting Function.");
                assert(specs.bootstrapTemplateVarsInServer && _.isFunction(specs.bootstrapTemplateVarsInServer, "R.App.createApp(...).specs.bootstrapTemplateVarsInServer: expecting Function."));
            });

            var AppInstance = function AppInstance() {
                _.extend(this, {
                    _fluxClass: specs.fluxClass,
                    _rootClass: specs.rootClass,
                    _template: specs.template || App.defaultTemplate,
                    _bootstrapTemplateVarsInServer: specs.bootstrapTemplateVarsInServer,
                    _vars: specs.vars || {},
                    _plugins: specs.plugins || {},
                    _templateLibs: _.extend(specs.templateLibs || {}, {
                        _: _,
                    }),
                });
                _.extend(this, specs);
                _.each(specs, R.scope(function(val, attr) {
                    if(_.isFunction(val)) {
                        this[attr] = R.scope(val, this);
                    }
                }, this));
            };
            _.extend(AppInstance.prototype, R.App._AppInstancePrototype);
            return AppInstance;
        },
        _AppInstancePrototype: {
            _fluxClass: null,
            _rootClass: null,
            _template: null,
            _bootstrapTemplateVarsInServer: null,
            _vars: null,
            _templateLibs: null,
            _plugins: null,
            /**
            * <p>Compute all React Components with data server-side and render the corresponding HTML for the requesting client</p>
            * @method renderToStringInServer
            * @param {object} req The classical request object
            * @return {object} template : the computed HTML template with data for the requesting client
            */
            renderToStringInServer: function* renderToStringInServer(req) {
                R.Debug.dev(function() {
                    assert(R.isServer(), "R.App.AppInstance.renderAppToStringInServer(...): should be in server.");
                });
                //Generate a guid
                var guid = R.guid();
                //Flux is the class that will allow each component to retrieve data
                var flux = new this._fluxClass();
                //Register store (R.Store) : UplinkServer and Memory
                //Initializes flux and UplinkServer in order to be able to fetch data from uplink-server
                yield flux.bootstrapInServer(req, req.headers, guid);
                //Initializes plugin and fill all corresponding data for store : Memory
                _.each(this._plugins, function(Plugin, name) {
                    var plugin = new Plugin();
                    R.Debug.dev(function() {
                        assert(plugin.installInServer && _.isFunction(plugin.installInServer), "R.App.renderToStringInServer(...).plugins[...].installInServer: expecting Function. ('" + name + "')");
                    });
                    plugin.installInServer(flux, req);
                });
                var rootProps = { flux: flux };
                R.Debug.dev(R.scope(function() {
                    _.extend(rootProps, { __ReactOnRailsApp: this });
                }, this));

                //Create the React instance of root component with flux
                var surrogateRootComponent = new this._rootClass.__ReactOnRailsSurrogate({}, rootProps);

                if(!surrogateRootComponent.componentWillMount) {
                    R.Debug.dev(function() {
                        console.error("Root component doesn't have componentWillMount. Maybe you forgot R.Root.Mixin? ('" + surrogateRootComponent.displayName + "')");
                    });
                }
                surrogateRootComponent.componentWillMount();

                //Fetching root component and childs in order to retrieve all data
                //Fill all data for store : Uplink
                yield surrogateRootComponent.prefetchFluxStores();
                surrogateRootComponent.componentWillUnmount();

                var factoryRootComponent = React.createFactory(this._rootClass);
                var rootComponent = factoryRootComponent(rootProps);
                flux.startInjectingFromStores();
                /*
                * Render root component server-side, for each components :
                * 1. getInitialState : return prefetched stored data and fill the component's state
                * 2. componentWillMount : simple initialization 
                * 3. Render : compute DOM with the component's state
                */
                var rootHtml = React.renderToString(rootComponent);
                flux.stopInjectingFromStores();

                //Serializes flux in order to provides all prefetched stored data to the client
                var serializedFlux = flux.serialize();
                flux.destroy();
                return this._template(_.extend({}, yield this._bootstrapTemplateVarsInServer(req), this._vars, {
                    rootHtml: rootHtml,
                    serializedFlux: serializedFlux,
                    serializedHeaders: R.Base64.encode(JSON.stringify(req.headers)),
                    guid: guid,
                }), this._templateLibs);
            },
            /**
            * <p>Setting all the data for each React Component and Render it into the client. <br />
            * Connecting to the uplink-server via in order to enable the establishment of subsriptions for each React Component</p>
            * @method renderIntoDocumentInClient
            * @param {object} window The classical window object
            */
            renderIntoDocumentInClient: function* renderIntoDocumentInClient(window) {
                R.Debug.dev(function() {
                    assert(R.isClient(), "R.App.AppInstance.renderAppIntoDocumentInClient(...): should be in client.");
                    assert(_.has(window, "__ReactOnRails") && _.isPlainObject(window.__ReactOnRails), "R.App.AppInstance.renderIntoDocumentInClient(...).__ReactOnRails: expecting Object.");
                    assert(_.has(window.__ReactOnRails, "guid") && _.isString(window.__ReactOnRails.guid), "R.App.AppInstance.renderIntoDocumentInClient(...).__ReactOnRails.guid: expecting String.");
                    assert(_.has(window.__ReactOnRails, "serializedFlux") && _.isString(window.__ReactOnRails.serializedFlux), "R.App.AppInstance.renderIntoDocumentInClient(...).__ReactOnRails.serializedFlux: expecting String.");
                    assert(_.has(window.__ReactOnRails, "serializedHeaders") && _.isString(window.__ReactOnRails.serializedHeaders), "R.App.AppInstance.renderIntoDocumentInClient(...).__ReactOnRails.headers: expecting String.");
                });
                var flux = new this._fluxClass();
                R.Debug.dev(function() {
                    window.__ReactOnRails.flux = flux;
                });
                var headers = JSON.parse(R.Base64.decode(window.__ReactOnRails.serializedHeaders));
                var guid = window.__ReactOnRails.guid;
                //Register store (R.Store) : UplinkServer and Memory
                //Initialize flux and UplinkServer in order to be able to fetch data from uplink-server and connect to it via socket
                yield flux.bootstrapInClient(window, headers, guid);
                //Unserialize flux in order to fill all data in store
                flux.unserialize(window.__ReactOnRails.serializedFlux);
                _.each(this._plugins, function(Plugin, name) {
                    var plugin = new Plugin();
                    R.Debug.dev(function() {
                        assert(plugin.installInClient && _.isFunction(plugin.installInClient), "R.App.renderToStringInServer(...).plugins[...].installInClient: expecting Function. ('" + name + "')");
                    });
                    plugin.installInClient(flux, window);
                });
                var rootProps = { flux: flux };
                R.Debug.dev(R.scope(function() {
                    _.extend(rootProps, { __ReactOnRailsApp: this });
                }, this));
                var factoryRootComponent = React.createFactory(this._rootClass);
                var rootComponent = factoryRootComponent(rootProps);
                R.Debug.dev(function() {
                    window.__ReactOnRails.rootComponent = rootComponent;
                });
                flux.startInjectingFromStores();
                /*
                * Render root component client-side, for each components:
                * 1. getInitialState : return store data computed server-side with R.Flux.prefetchFluxStores
                * 2. componentWillMount : initialization 
                * 3. Render : compute DOM with store data computed server-side with R.Flux.prefetchFluxStores
                * Root Component already has this server-rendered markup, 
                * React will preserve it and only attach event handlers.
                * 4. Finally componentDidMount (subscribe and fetching data) then rerendering with new potential computed data
                */
                React.render(rootComponent, window.document.getElementById("ReactOnRails-App-Root"));
                flux.stopInjectingFromStores();
            },
        },
        /**
        * <p>Initiliaziation method of a plugin for the application</p>
        * @method createPlugin
        * @param {object} specs The specified specs provided by the plugin
        * @return {object} PluginInstance The instance of the created plugin
        */
        createPlugin: function createPlugin(specs) {
            R.Debug.dev(function() {
                assert(specs && _.isPlainObject(specs), "R.App.createPlugin(...).specs: expecting Object.");
                assert(specs.displayName && _.isString(specs.displayName), "R.App.createPlugin(...).specs.displayName: expecting String.");
                assert(specs.installInServer && _.isFunction(specs.installInServer), "R.App.createPlugin(...).specs.installInServer: expecting Function.");
                assert(specs.installInClient && _.isFunction(specs.installInClient), "R.App.createPlugin(...).specs.installInClient: expecting Function.");
            });

            var PluginInstance = function PluginInstance() {
                this.displayName = specs.displayName;
                _.each(specs, R.scope(function(val, attr) {
                    if(_.isFunction(val)) {
                        this[attr] = R.scope(val, this);
                    }
                }, this));
            };

            _.extend(PluginInstance.prototype, specs, App._PluginInstancePrototype);

            return PluginInstance;
        },
        _PluginInstancePrototype: {
            displayName: null,
            installInClient: null,
            installInServer: null,
        },
    };

    if(R.isServer()) {
        var fs = require("fs");
        var _defaultTemplate = _.template(fs.readFileSync(path.join(__dirname, "..", "src", "R.App.defaultTemplate.tpl")));
        App.defaultTemplate = function defaultTemplate(vars, libs) {
            return _defaultTemplate({ vars: vars, libs: libs });
        };
    }
    else {
        App.defaultTemplate = function defaultTemplate(vars, libs) {
            throw new Error("R.App.AppInstance.defaultTemplate(...): should not be called in the client.");
        };
    }

    return App;
};