core/widget.js

define('widget', ['engine', 'underscore', 'zepto'], function (xjs, _, $) {
    var declare = xjs.declare;

    /**
     * @fileOverview 这是Page的基类,所有Page的默认事件和流程都是在这里被定义<br>
     * 所有Page类通过[xjs.declare]{@link xjs.declare}申明,并将[widget]{@link widget}作为`parents`参数传入,用以继承默认事件流程。<br>
     * 最后使用[xjs.createView]{@link xjs.createView}实例化Page类
     *
     * 所有基类的函数都可以被重写(除了`init`),重写后的事件可以调用`this.super()`方法执行父类被覆盖的函数<br>
     * widget的流程行顺序按照以下:<br>
     * `init—>render—>request—>syncGetData—>buildRender—>startup—>onExit`
     * @mixin widget
     */
    return declare('ui.Widget', {
        /**
         * Page类的初始化函数,同时控制渲染事件的执行流程,此方法不可以被重写。
         * @memberOf widget
         * @function init
         * @param dom 根Dom节点,用于插入模板
         */
        init: function (dom) {
            this.domNode = this.domNode || (this.$domNode = $(dom)).get(0);

            this.render();

            $.when(this.syncGetData()).done(function () {
                this.buildRender();
                if (!this.finalStep) {
                    xjs.triggerAnnounceEvent('widgetReady', this.routeEventName);
                } else {
                    xjs.triggerAnnounceEvent('allWidgetReady', this.routeEventName);
                }
                /**
                 * 当模板和数据都被渲染后就会调用startup事件,Page里的Dom节点操作以及业务逻辑都应该在这里实现。
                 * @memberOf widget
                 * @function startup
                 */
                this.startup && this.startup();
            }.bind(this));
            return this;
        },
        render: function () {
            /**
             * 定义Page的标题
             *
             * @type {string}
             * @memberOf widget
             * @name title
             */
            document.title = this.title || document.title;
            /**
             * 实例化后的Page类id,此id是唯一的,可以用于`xjs.byId`获取到`This`对象
             *
             * @type {string}
             * @memberOf widget
             * @name id
             */
            this.id = this.domNode.id;
        },
        syncGetData: function () {
            /**
             * request函数用于设置需要预先请求的数据队列,所有队列请求成功后才会执行后面的流程。
             * 对zepto的ajax模块进行了二次封装,所有参数和$.ajax一致。ajax返回的数据将会根据app的名字挂载到this.data下
             * @memberOf widget
             * @function request
             * @example
             * request: function() {
             *     return {
             *         app: 'name', //在request之后的函数中可以用this.data.name获取到数据
             *         url: 'example.do',
             *         data: {}
             *     }
             * }
             * //或者
             * request: function() {
             *     return [{
             *         app: 'app1', //在request之后的函数中可以用this.data.app1获取到数据
             *         url: 'example.do',
             *         data: {}
             *     }, {
             *         app: 'app2',
             *         url: 'test.do'
             *     }]
             * }
             */
            var o = this.request ? this.request() : 0, dtd = $.Deferred();
            if (!o) return dtd.resolve();
            if (o.then) {
                o.done(xjs.hitch(this, waitRequest));
                return dtd.promise();
            }
            xjs.hitch(this, waitRequest)(o);
            function waitRequest(param) {
                var param = param instanceof Array ? param : [param], i, name, count = 0;
                this.data = this.data || {};
                for (i = 0; i < param.length; i++) {
                    name = param[i].app;
                    if (!param[i].hasOwnProperty('showShadow')) param[i].showShadow = true;
                    delete param[i].app;
                    xjs.load(param[i]).then(
                        xjs.hitch(this, function (reslute, key) {
                            this.data[key] = reslute;
                            count += 1;
                            if (count == param.length) dtd.resolve();
                        }, name)
                    );
                }
            }

            return dtd.promise();
        },
        /**
         * 模板渲染流程,将会把this对象作为数据采集对象传入模板。并扫描模板里的自定义锚点后映射到this对象上<br>
         * [data-xjs-element] 将挂载到this对象上,并通过$ + name 用以区分普通dom对象和jquery对象
         * @example
         * <div data-xjs-element="divNode"></div>
         * //this.divNode 获取原始dom对象
         * //this.$divNode 获取jquery对象
         * @memberOf widget
         * @function buildRender
         * @see {widget#request}
         */
        buildRender: function () {
            /**
             * Page类的CSS Class Name
             *
             * @type {string}
             * @memberOf widget
             * @name baseClass
             */
            this.$domNode.addClass(this.baseClass);
            /**
             * 传入模板字符串,基于`underscore`的模板引擎渲染HTML
             *
             * @type {string}
             * @memberOf widget
             * @name templateString
             */
            if (this.templateString) {
                this.domNode.innerHTML = _.template(this.templateString)(this);
            }
            __createNode.call(this) && __createEvent.call(this);
        },
        /**
         * Page的退出事件,在路由切换被触发时调用,如果有添加事件监听需要自行注销,应该写在这个事件里,
         * 如果你复写了这个函数,别忘了在function末尾调用this._super()
         * @memberOf widget
         * @function onExit
         */
        onExit: function () {
            this.$domNode.off().remove();
        }
    });
    function __createNode() {
        var doms, dom, parents, n, i;
        doms = this.domNode.querySelectorAll('[data-xjs-element]');
        doms = Array.prototype.slice.call(doms);
        for (i = 0; i < doms.length; i++) {
            dom = $(doms[i]);
            parents = dom.parents('[data-xjs-mixin]');
            if (parents.length && parents[0] != this.domNode) break;
            n = dom.data('xjs-element');
            this[n] = ( this['$' + n] = dom ).get(0);
        }
        return true;
    }

    function __createEvent() {
        var doms, dom, parents, n, i;
        doms = this.domNode.querySelectorAll('[data-xjs-event]');
        doms = Array.prototype.slice.call(doms);
        if (this.$domNode.data('xjs-event')) doms.push(this.domNode);
        for (i = 0; i < doms.length; i++) {
            var fns = {}, f, j;
            dom = $(doms[i]);
            parents = dom.parents('[data-xjs-mixin]');

            if (parents.length && parents[0] != this.domNode)
                break;

            n = dom.data('xjs-event');
            f = n.replace(/\s/g, "").split(';').slice(0, -1);
            for (j = 0; j < f.length; j++) {
                var event = f[j].split(':');
                dom.on(event[0], xjs.hitch(this, this[event[1]]));
            }
        }
        return true;
    }
});