Component.js 12.7 KB
define([
    'juicer',
    'mUtil',
    'i18n',
    'zepto'
], function (juicer, mUtil) {
    'use strict';

    var isString = mUtil.isString,
        isElement = mUtil.isElement,
        isObject = mUtil.isObject,
        isArray = mUtil.isArray;
    var comStatus = {
        UNLOAD: 1,
        LOAD: 2,
        DATALOAD: 3
    };

    var F = function (opts) {
        opts = opts || {};

        var evt = {  // 事件handlers
            handlers: {},
            once: {}
        };
        var always = function () { return true; };

        // 自定义绑定事件 非dom事件
        this.$on = function (evtName, handler) {
            evt.handlers[evtName] = handler;
        };
        // 只执行一次的事件
        this.$once = function (evtName, handler) {
            evt.once[evtName] = handler;
        };
        // 取消绑定
        this.$off = function (evtName) {
            if(evt.once[evtName]) {
                delete evt.once[evtName];
            } else if(evt.handlers[evtName]) {
                delete evt.handlers[evtName];
            } else if(!evtName) {
                evt.handlers = {};
                evt.once = {};
            }
        };
        // 触发自定义绑定事件 
        this.$emit = function (evtName) {
            var args = [].slice.call(arguments, 1);

            if(evt.once[evtName]) {
                evt.once[evtName].apply(this, args);
                delete evt.once[evtName];
            } else if(evt.handlers[evtName]) {
                evt.handlers[evtName].apply(this, args);
            }
        };

        //初始化生命周期方法
        this.beforeMount = always;
        this.mounted = always;

        // dataModel 转化为 viewModel
        this.transferToVM = always;

        // 组件模板和css
        this.tpl = "";
        this.css = "";
        this.components = {};

        this.status = comStatus.UNLOAD;
        this.id = F.util.getId(opts.el);
        this.pageid = opts.pageid || mUtil.getCurrentPageId();
        this.$container = $(opts.container || "body");

        this.updateStatus = function (status) {
            this.status = status;
        }

        // 组件render
        this.render = function () {
            var that = this,
                util = F.util;

            // 组件类型
            opts.type = opts.type || that.type;
            // url: 后端请求获取dataModel的方式
            // option: 前端直接调用组件所传option,即viewModel
            return $.when(util.initData(opts.url || opts.option || {}), util.getTpl(opts.type, this.tpl), util.getCss(this.css))
                .then(function (result) {
                    var $el = that.$container.find("[id='" + that.id + "']");
                    var pageid = that.pageid;

                    if(opts.url || opts.needTransfer) {
                        that.transferToVM(result);
                    } else {
                        that.viewModel = $.extend(true, that.viewModel, result);
                    }

                    that.viewModel = util.formatVM(that.viewModel, pageid, function (key) {
                        var keysOfSkipedVarParse = that.keysOfSkipedVarParse || [];
                        
                        return !!~keysOfSkipedVarParse.indexOf(key);
                    });
                    that.id = result.id || that.id;

                    if(pageid) {
                        F.instances[pageid] = F.instances[pageid] || {};
                        F.instances[pageid][that.id] = that;
                    } else {
                        F.instances[that.id] = that;
                    }

                    that.components = util.map(that.components, function (com) {
                        com.props = com.props || {};
                        com.uid = com.props.uid = "_" + mUtil.UUID();
                        com.content = juicer(com.tmpl, com.props);
                        return com;
                    });

                    var thrunk = that.beforeMount();

                    //beforeMount()可能返回值:undefined,true,Deferred
                    thrunk = typeof(thrunk) == 'object' ? thrunk : $.Deferred().resolve();

                    return thrunk.then(function(){
                        // 扩展viewModel
                        $.extend(true, that.viewModel, { id: that.id, compType: opts.type });
                        that.$el = util.render($el, $.extend(true, {}, that.viewModel, that.components));
                        that.$comp = that.$el.children(".wev-comp-" + that.type);
                        that.components = util.map(that.components, function (com) {
                            com.$el = that.$el.find("#" + com.uid);
                            return com;
                        });

                        Object.keys(that.components).forEach(function (k) {
                            that.components[k].mounted();
                        });

                        return that.mounted();
                    });
                });
        };

    };

    F.tpls = {}; // 缓存组件的模板

    F.instances = {}; //缓存插件

    F.util = {
        getId: function (el) {
            if(isString(el)) return el;
            if(isElement(el)) return el.id;

            return el;
        },
        getTpl: function (type, modName) {
            var deferred = $.Deferred();

            if(type in F.tpls) return deferred.resolve();
            if(!modName) return (F.tpls[type] = juicer(""));

            require([modName], function (template) {
                F.tpls[type] = juicer(mUtil.replaceI18n(template));
                deferred.resolve();
            });

            return deferred;
        },
        getCss: function (modName) {
            var deferred = $.Deferred();

            if(!modName) return deferred.resolve();

            if(isString(modName)) {
                modName = ['css!' + modName];
            } else if(isArray(modName)) {
                modName = modName.map(function (name) {
                    return 'css!' + name;
                });
            }

            require(modName, function () {
                deferred.resolve();
            });

            return deferred;
        },
        render: function (el, vm) {
            var tpl = F.tpls[vm.compType],
                html = tpl.render(vm);

            return $(el).html(html);
        },
        initData: function (data, fn) {
            var deferred = $.Deferred();

            if(isObject(data)) return deferred.resolve(data);

            mUtil.getJSON(data, function (result) {
                deferred.resolve(result.data);
            }, function (e) {
                var mec_id = data.split("mec_id=")[1];

                mUtil.console.error("无法获取到组件id为 " + mec_id + " 的相关数据");
            });

            return deferred;
        },
        map: function (o, map) {
            var n = {};

            Object.keys(o).map(function (k) {
                n[k] = map(o[k]);
            });
            return n;
        },
        formatVM: function (vm, pageid, skip) {
            var t = this;
            var datasets = this.getDataSet(pageid);
            var languageId = mUtil.getUserLanguage();
            var needParseVar = mUtil.containsVarParser(JSON.stringify(vm));

            vm = mUtil.replaceJSON(vm, function (str) {
                str = mUtil.replaceVariables(str, pageid);//替换页面变量和系统变量
                str = t.replaceMutilLanguage(str, languageId);

                if(datasets.length) {
                    datasets.forEach(function (dataset) {
                        str = dataset.replace(str);
                    });
                }
                needParseVar && (str = mUtil.replaceVarParser(str));
                return str;
            }, skip);
            vm.needParseVar = needParseVar;

            return vm;
        },
        replaceMutilLanguage: function (str, languageId) { //替换字符串里的包含的多语言值,如果不包含,返回原字符串
            var t = this;
            (str.match(/~`~`(.|\n)+?`~`~/g) || []).map(function (val) {
                str = str.replace(val, t.replaceStandMutilLanguage(val, languageId));
            });
            return str;
        },
        replaceStandMutilLanguage: function (mval, languageId) { //替换标准多语言值
            var symbol = "`~`";
            var values = mval.replace(/^~`~`|`~`~$/g, "").split(symbol);
            if(values.length == 1 && values[0] === mval) return mval;
            var firstMutilValue = '';
            for(var i = 0; i < values.length; i++) {
                var val = values[i];
                var langid = val.replace(/([\d]{1,2}) [\s\S]*/, "$1");
                val = val.replace(/[\d]{1,2} /, "").trim();
                if(val && !firstMutilValue) {
                    firstMutilValue = val;
                }
                if(val && languageId == langid) {
                    mval = val;
                }
            }
            return mval.indexOf('~') >= 0 ? firstMutilValue : mval;
        },
        getDataSet: function (pageid) {
            var coms = F.instances[pageid];

            if(!coms) return [];

            return Object.keys(coms).filter(function (comId) {
                var com = coms[comId];
                return com.type === "DataSet";
            }).map(function (comId) {
                return coms[comId];
            });
        }
    };

    return {
        super: function (component, opts) {
            F.call(component, opts);
        },
        init: function (Component) {
            // 寄生组合继承
            function Super() { }

            Super.prototype = F.prototype; // 只保留F.prototype的公有方法和属性
            Component.prototype = new Super();

            return Component;
        },
        getInstance: function (id, pageid) {
            pageid = pageid || mUtil.getCurrentPageId();

            if(!pageid) return F.instances[id];

            return F.instances[pageid][id];
        },
        getPageComs: function (pageid) {
            if (!pageid) return [];

            var comsMap = F.instances[pageid] || {};

            return Object.keys(comsMap).map(function (comId) {
                return comsMap[comId];
            });
        },
        load: function (components, onload, onComLoad) {
            var index = -1;
            var order = {};
            var len = components.length;

            components.forEach(function (com) {
                if(!com.type) return;

                var priority = order[com.priority] || [];

                com.index = priority.push(com) - 1;
                com.ready = $.Deferred();
                com.render = $.Deferred();
                order[com.priority] = priority;

                require([com.type], function (Component) {
                    var component = new Component(com);

                    component.$once("loaded", function (index) {
                        // 组件加载完成后 触发的回调
                        component.updateStatus(comStatus.LOAD);

                        if(mUtil.isFunction(onComLoad)) {
                            setTimeout(function () {
                                onComLoad(component, index);
                            }, 0);
                        }

                        if(len - 1 !== index) return;

                        // 所有组件加载完成 触发后的回调
                        mUtil.isFunction(onload) && setTimeout(onload, 0);
                    });

                    com.ready.then(function () {
                        return component.render().then(function () {
                            component.$emit("loaded", ++index);
                            component.$el.data("loaded", true);
                            com.render.resolve();
                        });
                    });
                });
            });

            Object.keys(order).sort(function (a, b) { return a - b }).reduce(function (prev, now) {
                prev = prev || $.Deferred().resolve();

                return prev.then(function () {
                    var deferreds = (order[now] || []).map(function (c) {
                        c.ready.resolve();
                        return c.render;
                    });
                    return $.when.apply($.when, deferreds);
                });
            }, "");
        },
        preload: function (components) {
            components.forEach(function (com) {
                require([com.type]);
            });
        },
        replaceMutilLanguage: function (content, languageid) {
            return F.util.replaceMutilLanguage(content, languageid);
        },
        render: function (el, vm){
            return F.util.render(el, vm);
        },
        status: comStatus
    };
});