Source: Page.js

/**
 * @file 页面
 * @author Leon(leon@outlook.com)
 *
 * @requires underscore
 * @requires react
 */

var u = require('underscore');
var React = require('react');

var ContextProvider = require('./component/ConextProvider');
var Context = require('./Context');
var componseReducer = require('./util/composeReducer');
var invariant = require('./util/invariant');

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

/**
 * 页面
 *
 * @constructor
 * @param {*} initialState 初始数据状态
 */
function Page(initialState) {

    this.context = new Context(
        initialState,
        componseReducer(this.reducer)
    );

}

/** @lends Page.prototype */
var PagePrototype = {

    /**
     * 初始化
     *
     * 此方法只会被调用一次
     *
     * 处理请求的过程中,在页面实例化后,会被调用到此方法
     *
     * 此方法会派发一个init动作,并附带有getInitialState方法所返回的数据
     *
     * init动作提供给页面的初始数据剪裁的时机
     *
     * 但是只有在execute的情况下才会被调用,
     * 我们在bootstrap时传入的initialState是已经是剪裁好的数据
     * 也就是在server端预渲染后向client端同步数据状态的场景
     *
     *
     * @TODO 通过page的stage来保证此动作只能触发一次
     *
     * @public
     * @return {module:Page}
     */
    init: function () {

        this.dispatch({
            type: 'init',
            data: this.getState()
        });

        return this;

    },

    /**
     * 使用当前上下文中的数据,创建一个可提渲染使用的react元素
     *
     * @public
     *
     * @return {ReactElement}
     */
    createElement: function () {

        var view = this.view;

        return React.createElement(
            ContextProvider,
            {
                ei: this.context
            },
            function () {
                return React.createElement(view);
            }
        );

    },

    /**
     * 使用当前的上下文中的数据,将页面渲染到指定的元素
     *
     * 只能在客户端使用
     *
     * @public
     *
     * @param {Element} target DOM元素
     *
     * @return {module:Page}
     */
    render: function (target) {

        React.render(this.createElement(), target);

        return this;
    },

    /**
     * 使用当前的上下文中的数据,将页面渲染成字符串
     *
     * @public
     *
     * @return {string}
     */
    renderToString: function () {
        return React.renderToString(this.createElement());
    },

    /**
     * 返回当前上下文中的所有数据
     *
     * 此方法用于将服务器端的页面数据,同步到客户端上
     *
     * @public
     *
     * @return {*}
     */
    getState: function () {
        return this.context.getState();
    },

    /**
     * 派发一个动作,激活相应的数据剪切和视图更新
     *
     * @public
     *
     * @method module:Page#dispatch
     *
     * @param {(Object | Function)} action 动作
     *
     * @return {Object}
     *
     * @fires module:events~page-dispatch
     */
    dispatch: function (action) {

        /**
         * @event module:events~page-dispatch
         * @param {(Object | Function)} action 动作
         */
        events.emit('page-dispatch', action);

        this.context.dispatch(action);

        return action;

    },

    /**
     * 获取页面初始数据
     *
     * 页面在启动时,一般都会需要通过操作资源来获取数据作为初始数据
     *
     * 并且,这个过程一般还需要使用当前`请求`来完成决策
     *
     * 在app中接收到请求后会加载路由中指定的Page模块,将其实例化后,执行此方法
     *
     * @todo page需要有一个状态标识,new / inited / rendered / disposed
     *
     * @public
     *
     * @param {Object} request 请求
     *
     * @return {Promise}
     */
    getInitialState: function (request) {
        return {};
    },

    /**
     * 销毁页面
     *
     * @return {module:Page}
     */
    dispose: function () {

        /**
         * @event module:event~page-dispose
         */
        events.emit('page-dispose');

        // @TODO 补充销毁时的必要处理

        return this;
    }

};

/**
 * 生成Page子类
 *
 * @param {!Object} proto 扩展Page的配置
 * @return {Function}
 */
Page.extend = function (proto) {

    invariant(proto, 'create Page need options');

    invariant(proto.reducer, 'Pager must have a reducer');

    invariant(proto.view, 'Pager must have a view');

    function SubPage(initialState) {
        Page.call(this, initialState);
    }

    u.extend(SubPage.prototype, PagePrototype, proto);

    return SubPage;

};

module.exports = Page;