all files / dui/src/ init.js

10.12% Statements 17/168
2.88% Branches 3/104
15.38% Functions 2/13
10.12% Lines 17/168
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
/**
 * DOM脚手架
 *
 * @file 用于批量初始化页面中DOM
 * @author Brian Li (lbxxlht@163.com)
 */
var define = typeof define === 'function' && define.amd ? define : function (factory) {
    typeof module === 'object' ? (module.exports = factory(require)) : '';
};
 
define(function (require) {
 
    var util = require('./core/util');
    var factory = require('./factory');
 
    // //////////以下为业务方法/////////////
 
    /**
     *  批量创建接口
     *     1 此接口只对dom中的子节点进行一次创建。创建过程中存在innerHTML操作,新产生的节点不进入创建队列。
     *     2 若节点中出现控件标记嵌套的情况,即带有data-ui-type标记的节点内部包含了带有data-ui-type标记的子节点,则创建
     *  结果视父级控件标记而定:
     *      2.1 如果父级控件自身没有模板(没有模板的控件只增加class和event,不修改innerHTML),子级控件会被创建,其可用
     *  性与父控件无关;
     *      2.2 如果父级控件自身存在模板,子控件会被创建成功,但原DOM的父子关系会被破坏,子控件随init返回值带出,可以在
     *  其他DOM中appendChild子控件的container,这样就可以继续使用了。
     *
     * @param {?(HtmlElement | string)} dom 待分析的DOM容器或字符串,在Browser端为根DOM容器,在NodeJS中为html字符串
     * @param {?Object} params 创建时的参数hash
     * @return {Object | string} 组件对象hash或含有控件一切标记和内容的html字符串
     */
    function init(dom, params) {
        // ////////解析html字符串并初始化控件,返回含有控件信息的html字符串//////// //
        if (util.inNode() || typeof dom === 'string') {
            return parseHtml(dom, params);
        }
        // ////////解析DOM并初始化控件,返回控件对象集合//////// //
        dom = dom || document.body;
        params = params || {};
        var result = {};
        var originalDOM = dom.getElementsByTagName('*');
        var elements = [];
        // 缓存所有DOM
        for (var n = 0; n < originalDOM.length; n++) {
            elements.push(originalDOM[n]);
        }
        if (elements.length === 0) {
            return result;
        }
        // 解析所有DOM
        for (var i = 0; i < elements.length; i++) {
            var element = elements[i];
            // 从DOM标签中导出初始化数据
            var paramFromHtml = getParamFromHtml(element);
            if (!paramFromHtml.hasOwnProperty('type') || typeof factory[paramFromHtml.type] !== 'function') {
                continue;
            }
            // 从传入的params导出初始化数据
            var paramFromCode = params[paramFromHtml.id] || {};
            // 合并控件初始化数据
            var param = mergeParam(paramFromCode, paramFromHtml);
            param.container = element;
            // 创建控件实例
            result[param.id] = new factory[param.type](param);
        }
        return result;
    }
 
    /**
     * 分析html
     * 分析传入的html结构,初始化其中包含的控件,将控件中涉及的class、innerHTML拼入原html字符串,将拼接好的html串返回。
     * 此方法一般用于Node环境
     *
     * @param {string} html 原始html字符串
     * @param {params} params 创建时的参数hash
     * @return {string} 含有控件一切标记和内容的html字符串
     */
    function parseHtml(html, params) {
        if (html.length === 0) {
            return '';
        }
        var picker = new util.Picker(html);
        for (var i = 0; i < picker._treesource.length; i++) {
            var item = picker._treesource[i];
            if (item == null || item._pos === 'end') {
                continue;
            }
            var paramFromHtml = getParamFromObject(item);
            if (!paramFromHtml.hasOwnProperty('type') || typeof factory[paramFromHtml.type] !== 'function') {
                continue;
            }
            var paramFromCode = params[paramFromHtml.id] || {};
            var param = mergeParam(paramFromCode, paramFromHtml);
            delete param.container;
            var dom = new factory[param.type](param);
            var classes = dom._temp.className.split(' ');
            item._suff.push(dom.html);
            item._removeInner = dom.html.length > 0;
            item['data-rendered'] = 'rendered';
            item.class = (item.class instanceof Array) ? item.class.concat(classes) : classes;
        }
        return picker.export().join('');
    }
 
    /**
     * 从虚拟DOM中解析出控件初始化需要的参数,作用跟getParamFromHtml相同
     *
     * @param {Object} dom 待分析的虚拟dom对象
     * @return {Object} 用户传入的数据集
     */
    function getParamFromObject(dom) {
        var result = {};
        // data-ui
        if (dom.hasOwnProperty('data-ui')) {
            merge(result, string2object(dom['data-ui']));
        }
        // data-plugin
        if (dom.hasOwnProperty('data-plugin')) {
            var plugin = plugin2array(dom['data-plugin']);
            if (plugin.length > 0) {
                result.plugin = plugin;
            }
        }
        // other
        for (var key in dom) {
            if (
                key.indexOf('_') === 0 || key === 'data-ui' || key === 'data-plugin'
                || (key.indexOf('data-ui') !== 0 && key.indexOf('data-plugin') !== 0)
            ) {
                continue;
            }
            // data-ui-***
            if (key.indexOf('data-ui') === 0) {
                var newkey = key.slice(8, key.length).replace(/\-(\w)/g, replacerAllUpperCase);
                result[newkey] = dom[key];
                continue;
            }
            // data-plugin-***
            var value = dom[key];
            key = key.slice(12, key.length)
                .replace(/\-(\w)/g, replacerAllUpperCase)// 转驼峰
                .replace(/(\w)/, replacerUpperCase);// 首字母大写
            try {
                value = JSON.parse(value.replace(/'/g, '"'));
                result.plugin = result.plugin || [];
                result.plugin.push({type: key, param: value});
            }
            catch (e) {
                /* eslint-disable */
                console.error(e);
                /* eslint-enable */
            }
        }
        // return
        if (!result.hasOwnProperty('id')) {
            result.id = util.uuid();
        }
        if (result.hasOwnProperty('type')) {
            result.type = result.type.replace(/(\w)/, replacerUpperCase);
        }
        return result;
    }
 
    /**
     * 获取dom用户从dom标签中传入的输入
     * 1.用于ui主体初始化的参数都从data-ui-***或data-ui中灌入
     * 2.用于ui插件初始化的参数都从data-plugin-***或data-plugin中灌入
     * 3.data-ui的优先级比data-ui-***的优先级低,如:
     *  data-ui="Type:Abc"和data-ui-type="AAA"同时出现,则最终解析出的type为'AAA'
     *
     * @param {HtmlElement} dom 待分析的dom对象
     * @return {Object} 用户传入的数据集
     */
    function getParamFromHtml(dom) {
        var dataset = util.getDataset(dom);
        var result = {};
        // data-ui
        if (dataset.hasOwnProperty('ui')) {
            merge(result, string2object(dataset.ui));
        }
        // data-plugin
        if (dataset.hasOwnProperty('plugin')) {
            var plugin = plugin2array(dataset.plugin);
            if (plugin.length > 0) {
                result.plugin = plugin;
            }
        }
        // other
        for (var key in dataset) {
            if (!dataset.hasOwnProperty(key)
                || key === 'ui'
                || key === 'plugin'
                || (key.indexOf('ui') !== 0 && key.indexOf('plugin') !== 0)
            ) {
                continue;
            }
            // data-ui-***
            if (key.indexOf('ui') === 0) {
                var newkey = key.slice(2, key.length).replace(/^[A-Z]/, replacerLowerCase);
                result[newkey] = dataset[key];
                continue;
            }
            // data-plugin-***
            var value = dataset[key];
            key = key.slice(6, key.length);
            try {
                value = JSON.parse(value.replace(/'/g, '"'));
                result.plugin = result.plugin || [];
                result.plugin.push({type: key, param: value});
            }
            catch (e) {
                /* eslint-disable */
                console.error(e);
                /* eslint-enable */
            }
        }
        // return
        if (!result.hasOwnProperty('id')) {
            result.id = util.uuid();
        }
        if (result.hasOwnProperty('type')) {
            result.type = result.type.replace(/(\w)/, replacerUpperCase);
        }
        return result;
    }
 
    /**
     *  将源对象合并到目标对象中,返回目标对象
     *
     *  优先级:
     *  1.source中的键不覆盖dest中的同名键,出现这种情况时丢弃source中的键值,即dest的普通键优先级更高;
     *  2.plugin键的优先级:
     *      2.1 plugin的顺序:source.plugin在前,dest.plugin在后,即source.plugin的顺序优先级高;
     *      2.2 若source.plugin中的类型在dest.plugin中存在,那么source中的类型将被替换,即dest的类型优先级更高;
     *      2.3 若source.plugin中存在类型相同的两个plugin,则后一个不会被初始化,即source.plugin中靠前的类型优先级更高;
     *      2.4 若dest.plugin中存在类型相同的两个plugin,则后一个会覆盖前一个,即在dest.plugin中靠后的类型优先级更高;
     *  3.关于source.plugin的顺序,这个取决于浏览器的解析,跟html书写的顺序没有关系,例如IE就和标准浏览器的解析顺序不一
     *  致。因此,html标签里的插件不应当存在相互的依赖关系,否则可能导致初始化不成功。
     *
     * @param {Object} dest 主对象,由JS代码导入
     * @param {Object} source 从对象,由HTML中导入
     * @return {Object} 合并结果
     */
    function mergeParam(dest, source) {
        dest.plugin = dest.plugin || [];
        for (var key in source) {
            if (dest.hasOwnProperty(key) || key === 'plugin') {
                continue;
            }
            dest[key] = source[key];
        }
        if (source.hasOwnProperty('plugin') && source.plugin.length > 0) {
            var exist = {};
            var sourcePlugins = [];
            for (i = 0; i < source.plugin.length; i++) {
                var plugin = source.plugin[i];
                if (typeof factory[plugin.type] !== 'function' || exist.hasOwnProperty(plugin.type)) {
                    continue;
                }
                try {
                    plugin = new factory[plugin.type](plugin.param);
                    sourcePlugins.push(plugin);
                    exist[plugin.name] = sourcePlugins.length - 1;
                }
                catch (e) {
                    /* eslint-disable */
                    console.error(e);
                    /* eslint-enable */
                }
            }
            if (dest.plugin instanceof Array) {
                for (var i = 0; i < dest.plugin.length; i++) {
                    var plugin = dest.plugin[i];
                    if (exist.hasOwnProperty(plugin.name)) {
                        sourcePlugins[exist[plugin.name]] = plugin;
                    }
                    else {
                        sourcePlugins.push(plugin);
                        exist[plugin.name] = sourcePlugins.length - 1;
                    }
                }
            }
            if (sourcePlugins.length === 0) {
                delete dest.plugin;
            }
            else {
                dest.plugin = sourcePlugins;
            }
        }
        return dest;
    }
 
    /**
     * 将传入的plugin字符串转换成有序的plugin对象组
     * 串的一级格式与string2object相同,但每个key所对应的value都是JSONstring
     * 如将"A:{'a':1,'b':1};B:{'c':1,d:2}",转换成:
     *  [
     *      A: {a: 1, b: 1},
     *      B: {c: 1, d: 2}
     *  ]
     *
     * <Plugin>
     * <Plugin>.type {string} 插件名称
     * <Plugin>.param {Object} 用于初始化插件的参数对象
     *
     * @param {string} e 待转换串
     * @return {Array.<Plugin>} 转换后的对象序列,
     */
    function plugin2array(e) {
        var result = [];
        var arr = e.replace(/\n|\r| /g, '').split(';');
        for (var i = 0; i < arr.length; i++) {
            var str = arr[i];
            var p = str.indexOf(':');
            if (p < 0) {
                continue;
            }
            var key = str.slice(0, p);
            var value = str.slice(p + 1, str.length).replace(/'/g, '"');
            try {
                value = JSON.parse(value);
                result.push({type: key, param: value});
            }
            catch (err) {
                /* eslint-disable */
                console.error(e);
                /* eslint-enable */
            }
        }
        return result;
    }
 
    /**
     * 将输入串转成对象
     * 'a:1;b:2' -> {a:1, b:2}
     * 该方法不做key的大小写转换和value的类型转换,所有value都是字符串
     *
     * @param {string} e 待转换串
     * @return {Object} 转换后对象
     */
    function string2object(e) {
        var result = {};
        var arr = e.replace(/\n|\r/g, '').split(';');
        for (var i = 0; i < arr.length; i++) {
            var item = arr[i].split(':');
            if (item.length !== 2) {
                continue;
            }
            result[item[0]] = item[1];
        }
        return result;
    }
 
    /**
     * 合并对象,使用了对象引用,无返回值
     *
     * @param {Object} obj1 母对象
     * @param {Object} obj2 待合并到母对象的对象
     * @param {?Object} conf 合并配置选项
     */
    function merge(obj1, obj2, conf) {
        for (var key in obj2) {
            if (!obj2.hasOwnProperty(key)) {
                continue;
            }
            obj1[key] = obj2[key];
        }
    }
 
    /**
     * 小写字母转大写
     */
    function replacerUpperCase(a) {
        return a.toUpperCase();
    }
 
    /**
     * 大写字母转小写
     *
     * @param {string} a 待转字符(串)
     * @return {string} 转换后结果
     */
    function replacerLowerCase(a) {
        return a.toLocaleLowerCase();
    }
 
    /**
     * 所有字母变大写
     *
     * @param {Array} all 所有字符
     * @param {string} letter 当前字符
     * @return {string} 当前字符的大写字符
     */
    function replacerAllUpperCase(all, letter) {
        return letter.toUpperCase();
    }
 
    // ////////////////返回接口//////////////////
    return init;
});