UNPKG

digi

Version:
1,218 lines (1,053 loc) 33.2 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } var getToStringTag = function getToStringTag(value) { return toString.call(value); }; var tags = { stringTag: getToStringTag(''), objectTag: getToStringTag({}), arrayTag: getToStringTag([]), numberTag: getToStringTag(1), functionTag: getToStringTag(toString), // booleanTag: getToStringTag(true), undefinedTag: getToStringTag(undefined), nullTag: getToStringTag(null) // regExpTag: getToStringTag(new RegExp()) }; var objectTag = tags.objectTag, arrayTag = tags.arrayTag; /** * 克隆对象或数组 * @function * @param {Array|Object} value - 源数据 * @returns {Array|Object} - 返回新的数据 * @example * import { cloneDeep } from 'digi' * * const obj = { a: 123 } * const newObj = cloneDeep(obj) * * console.log(obj === newObj) * // => false * console.log(obj.a === newObj.a) * // => true */ var cloneDeep = function cloneDeep(value) { var tag = getToStringTag(value); if (!(tag === arrayTag || tag === objectTag)) { return value; } var rValue; if (tag === objectTag) { rValue = Object.assign({}, value); } else { rValue = _toConsumableArray(value); } for (var key in rValue) { rValue[key] = cloneDeep(rValue[key]); } return rValue; }; var arrayEach = function arrayEach(array, callBack) { for (var i = 0; i < array.length; i++) { if (callBack(array[i], i) === false) break; } }; var objectEach = function objectEach(object, callBack) { for (var key in object) { if (Object.prototype.hasOwnProperty.call(object, key)) { if (callBack(object[key], key) === false) break; } } }; /** * 遍历对象或数组,每遍历一个值调用callBack(value, key|index),callBack 返回 false 可提前结束遍历。 * @function * @param {Array|Object} data 要遍历的对象或数组 * @param {Function} callBack 每遍历一个值调用的函数 * @example * import { forEach } from 'digi' * * var obj = { a: 1, b: 2 } * forEach(obj, (value, key) => console.log(value, key)) * // => a, 1 * // => b, 2 * * forEach(obj, (value, key) => { * console.log(value, key) * return false // 提前结束遍历 * }) * // => a, 1 */ var forEach = function forEach(data, callBack) { if (getToStringTag(data) === arrayTag) { arrayEach(data, callBack); } else { objectEach(data, callBack); } }; var getTypeofTag = function getTypeofTag(value) { return _typeof(value); }; var tofTags = { tofObjectTag: getTypeofTag({}) }; var fun = {}; var addFun = function addFun(tag, key, getTag) { var newKey = 'is' + key[0].toUpperCase() + key.replace(/^.|Tag$/g, ''); fun[newKey] = function (value) { return getTag(value) === tag; }; // function name Object.defineProperty(fun[newKey], 'name', { value: newKey }); }; objectEach(tags, function (tag, key) { return addFun(tag, key, getToStringTag); }); objectEach(tofTags, function (tag, key) { return addFun(tag, key, getTypeofTag); }); var isString = fun.isString, isObject = fun.isObject, isTofObject = fun.isTofObject, isFunction = fun.isFunction, isArray = fun.isArray, isNumber = fun.isNumber, isUndefined = fun.isUndefined, isNull = fun.isNull; // [ = \u005b ; ] = \u005d // 匹配中括号 var bracketRE = /[\u005b\u005d]/g; // 匹配多个点 var pointsRE = /\.{2,}/g; // 匹配前后点 var beforeAndAfterRE = /^\.|\.$/g; /** * 接连路径<br> * @function * @param {Array} paths - 多个路径 * @returns {String} - 返回连接成的新路径; 数组和对象的路径都用“.”分隔: 1、paths.join('.'),2、中括号替换为点,3、多点替换为一个点,4、去掉前后点 * @example * import { pathJoin } from 'digi' * * console.log(pathJoin(a, '[1].b')) * // => a.1.b */ var pathJoin = function pathJoin() { for (var _len = arguments.length, paths = new Array(_len), _key = 0; _key < _len; _key++) { paths[_key] = arguments[_key]; } return paths.join('.').replace(bracketRE, '.').replace(pointsRE, '.').replace(beforeAndAfterRE, ''); }; /** * 将路径转成数组 * @function * @param {String} paths - 字符串路径 * @returns {Array} - 数组路径 * @example * import { pathSplit } from 'digi' * * console.log(pathSplit('a[1]')) * // => ['a', '1'] */ var pathSplit = function pathSplit(path) { return path.replace(bracketRE, '.').replace(pointsRE, '.').replace(beforeAndAfterRE, '').split('.'); }; /** * 选择对象中的一些属性组成新的对象 * @function * @param {Object} object - 源对象 * @param {String|Array} paths - 要选择的属性路径 * @returns {Object} - 返回新的对象 * @example * import { pick } from 'digi' * * var obj = { a: 1, b: 2, c: 3 } * pick(obj, 'a') * // => { a: 1} * * pick(obj, ['a', 'b']) * // => { a: 1, b: 3 } */ var pick = function pick(object, paths) { if (isString(paths)) { paths = [paths]; } var newObj = {}; forEach(paths, function (path) { if (Object.prototype.hasOwnProperty.call(object, path)) { newObj[path] = object[path]; } }); return newObj; }; /** * 设置对象或数组属性值 * @function * @param {Object|Array} data - 将要被改变属性的数据源 * @param {String|Array} paths - 属性路径 * @param {Any} value - 属性值 * @example * import { set } from 'digi' * * const obj = {} * set(obj, 'a.b.c', 123) * console.log(obj) * // => { a: { b: { c: 123 } } } * * set(obj, ['a', 'b', 'c'], 321) * console.log(obj) * // => { a: { b: { c: 321 } } } */ var set = function set(data, paths, value) { if (isString(paths)) { paths = pathSplit(paths); } var obj = data; var lastPath = paths.pop(); forEach(paths, function (path) { if (isUndefined(obj[path])) { obj[path] = {}; } obj = obj[path]; }); obj[lastPath] = value; }; var emptyObject = JSON.stringify({}); /** * 检查数组或对象是否为空 * @function * @param {any} value - 要检查的值 * @example * import { isEmpty } from 'digi' * * console.log(isEmpty({})) * // => true * console.log(isEmpty([])) * // => true * console.log(isEmpty('123')) * // => true */ var isEmpty = function isEmpty(value) { if (isArray(value) && value.length > 0) { return false; } else if (isObject(value) && emptyObject !== JSON.stringify(value)) { return false; } return true; }; /** * 各种实用工具 */ var utils = /*#__PURE__*/Object.freeze({ __proto__: null, cloneDeep: cloneDeep, forEach: forEach, isString: isString, isObject: isObject, isTofObject: isTofObject, isFunction: isFunction, isArray: isArray, isNumber: isNumber, isUndefined: isUndefined, isNull: isNull, pathJoin: pathJoin, pathSplit: pathSplit, pick: pick, set: set, isEmpty: isEmpty }); var version = "1.0.13"; /** * 存储插件handlerFun * @private * @property {string} [key] - key为元素属性名,plugins[key] = handler */ var plugins = {}; /** * 添加单个插件 * @private * @function * @param {Array|Object} plugin - plugin = { property: '元素属性名', handler: (元素, 元素属性值) => {} };<br> * handler在创建元素时抓捕到对应属性被触发, 并plugin.options = Object.assign(plugin.options, options) * @param {Object|Undefined} options - 插件自定义配置 */ var addPlugin = function addPlugin(plugin, options) { if (isArray(plugin)) { addPlugin(plugin[0], plugin[1]); } else if (!isObject(plugin)) { window.console.error('plugins Error: ', plugin); window.console.log("View document: https://digi1874.github.io/digi-doc/".concat(version, "/global.html#plugins")); } else { // 存储插件handlerFun plugins[plugin.property] = plugin.handler; // 修改可配置 plugin.options && options && Object.assign(plugin.options, options); } }; /** * 添加插件 * @function * @name plugins * @param {Array} plugins - 值 = [plugin1, ..., pluginN]; <br> * plugin = { property: '元素属性名', handler: (元素, 元素属性值) => {} };<br> * 或 plugin = [{ property: '元素属性名', handler: (元素, 元素属性值) => {} }, options]; options = {...}<br> * handler在创建元素时抓捕到对应属性被触发, 并plugin.options = Object.assign(plugin.options, options) * @example * import digi, { plugins } from 'digi' * import refs, { allotId } from 'digi-refs' * console.log(refs) * // {property: "ref", handler: ƒ, allotId: ƒ} * * // 添加插件: plugins([refs]) 或 digi.plugins([refs]) * plugins([refs]) * * // 分配标记id * const textRefId = allotId() * * // 添加元素 * digi({ ref: textRefId, text: 'hello world' }) * * console.log(refs[textRefId].outerHTML) * // => <div>hello world</div> */ var addPlugins = function addPlugins(plugins) { return forEach(plugins, function (item) { return addPlugin(item); }); }; /** * 存储变化数据和调用方法 * watchs.key = [handler1, ..., handlerN]; handler = (newVal, oldVal) { ... } * @private */ var watchs = {}; /** * 添加监听,添加时handler会被调用一次 * @private * @function * @param {String} path - 生成渲染模板中的监听对象路径({{监听对象路径|过滤器id}}) * @param {Function|Undefined} handler - handler = (newVal, oldVal) { ... }; watchs[path]初始化时可为空 */ var addWatch = function addWatch(path, handler) { var isRun = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (!watchs[path]) { watchs[path] = []; Object.defineProperties(watchs[path], { newVal: { writable: true }, oldVal: { writable: true } }); } if (handler && watchs[path].indexOf(handler) === -1) { watchs[path].push(handler); // 添加时handler会被调用一次 isRun && handler(watchs[path].newVal, watchs[path].oldVal); } }; /** * 删除监听 * @private * @function * @param {String} path - 添加监听{@link addWatch}时的path * @param {Function} handler - 添加监听{@link addWatch}时的handler */ var removeWatch = function removeWatch(path, handler) { var index = watchs[path].indexOf(handler); index !== -1 && watchs[path].splice(index, 1); }; /** * 触发监听 * @private * @function * @param {String} path - 添加监听{@link addWatch}时的path * @param {Any} newVal - 新值 * @param {Any} oldVal - 旧值 */ var triggerWatch = function triggerWatch(path, newVal, oldVal) { !watchs[path] && addWatch(path); newVal = cloneDeep(newVal); oldVal = cloneDeep(oldVal); watchs[path].newVal = newVal; watchs[path].oldVal = oldVal; // 对象类型不同 if (isTofObject(oldVal) && toString.call(newVal) !== toString.call(oldVal)) { forEach(oldVal, function (val, k) { return triggerWatch(pathJoin(path, k), undefined, val); }); } // 调用监听 forEach(watchs[path], function (handler) { return handler(newVal, oldVal); }); }; /** * 存储过滤器数据 * filters[addFilter.id] = [filter1, ..., filterN]; filter = [handleFun, arg1, ..., arg2] * @private */ var filters = {}; /** * 添加过滤器数据 * @private * @function * @param {Array} args - args = [filter1, ..., filterN]; filter = [handleFun, arg1, ..., arg2] || handleFun * @returns {Number} - 返回id值 */ var addFilter = function addFilter(args) { var item = []; forEach(args, function (filter) { return isArray(filter) ? item.push(filter) : item.push([filter]); }); filters[++addFilter.id] = item; return addFilter.id; }; /** * 累计id * @private * @property {Number} addFilter.id 最后一个id值 */ Object.defineProperty(addFilter, 'id', { value: 0, writable: true }); /** * 获取过滤器 * @private * @param {Number} id - {@link addFilter}返回的id * @returns {Array} - 返回[[filter, arg1, ..., argN], ... ] */ var getFilter = function getFilter(id) { return filters[id]; }; /** * 删除过滤器 * @private * @function * @param {Number} id - {@link addFilter}返回的id * @returns {Array} - 成功返回[[filter, arg1, ..., argN], ... ],否则返回undefined */ var removeFilter = function removeFilter(id) { var filter = filters[id]; delete filters[id]; return filter; }; /** * 恢复过滤器 * @private * @function * @param {Number} id - {@link removeFilter}删除过滤器的id * @param {Array} filter - {@link removeFilter}删除过滤器返回的数据 */ var restoreFilter = function restoreFilter(id, filter) { return filters[id] = filter; }; /** * 生成渲染模板并存储过滤器 * @private * @function * @param {String} path - 对象属性路径 * @param {Array} filters - 过滤器 filters = [filter1, ..., filterN]; filter = fun || [fun, arg1, ..., argN] * @returns {String} - 返回模板 */ var createTemplates = function createTemplates(path) { for (var _len = arguments.length, filters = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { filters[_key - 1] = arguments[_key]; } if (filters.length === 0) { // 无过滤器,返回`{{监听对象路径}}` return "{{".concat(path, "}}"); } var filterId = addFilter(filters); // 有过滤器,返回`{{监听对象路径|过滤器id}}` return "{{".concat(path, "|").concat(filterId, "}}"); }; /** * 设置Proxy * @private * @param {Object|Array} target - 对象类型值,target = {} || [] * @param {Object|Array} path - 对象路径,用于组装监听路径和触发监听 * @returns {Proxy} - 返回代理对象 */ var setProxy = function setProxy(target, path) { var handler = { get: function get(target, prop) { return target[prop]; }, set: function set(target, prop, newVal) { var oldVal = target[prop]; // 无数据变更 if (JSON.stringify(newVal) === JSON.stringify(oldVal)) { return true; } var watchPath = pathJoin(path, prop); // 触发watch triggerWatch(watchPath, newVal, oldVal); // 对象处理 if (isObject(newVal)) { if (!isObject(oldVal)) { target[prop] = setProxy({}, watchPath); } forEach(target[prop], function (val, k) { if (!Object.prototype.hasOwnProperty.call(newVal, k)) { target[prop][k] = undefined; delete target[prop][k]; } }); forEach(newVal, function (val, k) { return target[prop][k] = val; }); } // 数组处理 else if (isArray(newVal)) { if (!isArray(oldVal)) { target[prop] = setProxy([], watchPath); } for (var i = newVal.length; i < target[prop].length; i++) { target[prop][i] = undefined; delete target[prop][i]; } forEach(newVal, function (val, k) { return target[prop][k] = val; }); } // 其它类型 else { target[prop] = newVal; } return true; } }; return new Proxy(target, handler); }; /** * 创建可监听对象 * @function * @param {Object} data - 源对象 * @param {Object} watch - watch = { path1: fun1, ..., pathN: funN };<br> * path = 源对象路径; <br> * fun = (newVal, [oldVal]) => {}; * @returns {Object} - 返回可监听对象 * @example * import digi, { createData } from 'digi' * import refs, { allotId } from 'digi-refs' * * digi.plugins([refs]) * * // 创建监听数据 * const data = createData({ a: 123 }, { watch: { * a: (newVal, oldVal) => { * console.log(`watch a => newVal: ${ newVal }, oldVal: ${ oldVal }`) * } * }}) * * // 分配标记id * const textRefId = allotId() * * // 添加元素 * digi({ ref: textRefId, text: data.$tp('a') }) * * console.log(refs[textRefId].outerHTML) * // => <div>123</div> * * data.a = 321 * // => watch a => newVal: 321, oldVal: 123 * * console.log(refs[textRefId].outerHTML) * // => <div>321</div> */ var createData = function createData(data) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, watch = _ref.watch; // 记录惟一值 var id = ++createData.id; // 可监听对象 var newData = setProxy({}, id); forEach(data, function (value, key) { return newData[key] = value; }); // 添加监听 forEach(watch, function (handler, path) { return addWatch(pathJoin(id, path), handler, false); }); // 生成渲染模板 Object.defineProperty(newData, '$tp', { value: function value(path) { for (var _len = arguments.length, filters = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { filters[_key - 1] = arguments[_key]; } return createTemplates.apply(void 0, [pathJoin(id, path)].concat(filters)); } }); return newData; }; /** * 累计id * @private * @property {Number} createData.id 最后一个id值 */ Object.defineProperty(createData, 'id', { value: 0, writable: true }); /** * 渲染模板,调回渲染后的值 * @private * @param {String} template - 模板 * @param {Object} tpData - 模板数据,tpData = { val: 匹配模板最新值, tp: { [路径|id]: { RE: 匹配模板正则, filterId: 过滤器id }, ... }}; * @param {Function} callBack - 回调渲染后的值,callBack(newVal) */ var updated = function updated(template, tpData, callBack) { // 属性值 var newVal = template; forEach(tpData, function (item) { forEach(item.tp, function (tp) { var val = item.val; if (tp.filterId) { forEach(getFilter(tp.filterId), function (args) { // 数组第1个是过滤器,后面的是参数 args = _toConsumableArray(args); var filter = args[0]; args[0] = val; val = filter.apply(void 0, _toConsumableArray(args)); }); } var valStr = val; if (isTofObject(valStr)) { // 转 typeOf object 为字符串 valStr = JSON.stringify(valStr); } else if (isUndefined(valStr) || isNull(valStr)) { // undefined 和 Null 转成空字符串 valStr = ''; } else if (Object.is(valStr, NaN)) { valStr = 0; } // 正则替换 newVal = newVal.replace(tp.RE, valStr); if (valStr !== '' && newVal === valStr + '') { newVal = val; } }); }); // 回调值 callBack(newVal); }; /** * 处理监听 * @private * @param {Object} element - 元素 * @param {string} path - 元素的属性名 * @param {String} template - 属性值模板 * @param {Object} tpData - 模板匹配数据 * @param {Function} callBack - 回调渲染后的值和路径,callBack(newVal, path) */ var handlerWatch = function handlerWatch(element, path, template, tpData, callBack) { // 存储监听方法,用于元素移除时清除监听方法 var watchFuns = {}; // 存储过滤器id,用于元素移除时清除过滤器 var filtersIds = []; // 存储移除时的过滤器,用于恢复 var filters = {}; forEach(tpData, function (item, tpPath) { // 存储监听方法 watchFuns[tpPath] = function (val) { item.val = val; // 值变更,回调渲染后的值和路径 updated(template, tpData, function (newVal) { return callBack(newVal, path); }); }; // 存储过滤器 forEach(item.tp, function (tp) { return tp.filterId && filtersIds.push(tp.filterId); }); }); // 移除监听 var $removeWatch = element.$removeWatch; element.$removeWatch = function () { forEach(watchFuns, function (fun, tpPath) { return removeWatch(tpPath, fun); }); $removeWatch && $removeWatch(); }; // 恢复监听 var $addWatch = element.$addWatch; element.$addWatch = function () { forEach(watchFuns, function (fun, tpPath) { return addWatch(tpPath, fun); }); $addWatch && $addWatch(); }; // 移除过滤器 var $removeFilter = element.$removeFilter; element.$removeFilter = function () { forEach(filtersIds, function (id) { return filters[id] = removeFilter(id) || filters[id]; }); $removeFilter && $removeFilter(); }; // 恢复过滤器 var $restoreFilter = element.$restoreFilter; element.$restoreFilter = function () { forEach(filters, function (filter, id) { return restoreFilter(id, filter); }); $restoreFilter && $restoreFilter(); }; }; var tpRE = /{{(([\s\S]+?)(?:\|([0-9]+))?)}}/g; /** * 设置元素属性,如有模板会监听模板 * @private * @function * @param {Object} element - 元素 * @param {String} path - 元素的属性或路径 * @param {Any} value - 属性值或模板 * @param {Function} callBack - 回调值和路径,callBack(newVal, path) */ var update = function update(element, path, value, callBack) { if (isObject(value) || isArray(value)) { // 不能用element[path],其可能是空或不是对象,所以组装路径 forEach(value, function (val, k) { return update(element, pathJoin(path, k), val, callBack); }); return; } // 回调路径和值 callBack(value, path); // 模板数据,tpData = { val: 匹配模板最新值, tp: { [路径|id]: { RE: 匹配模板正则, filterId: 过滤器id }}}; var tpData = {}; // 暂存匹配数据 var tp = ''; do { // tp = tpRE.exec(value); if (tp !== null) { // 路径|id var key = tp[1]; // 路径 var tpPath = tp[2]; if (!tpData[tpPath]) { tpData[tpPath] = { val: '', tp: {} }; } if (!tpData[tpPath].tp[key]) { // 模板转正则 tpData[tpPath].tp[key] = { RE: RegExp(tp[0].replace('|', '\\|'), 'g'), filterId: tp[3] }; } } } while (tp !== null); // 处理监听 !isEmpty(tpData) && handlerWatch(element, path, value, tpData, callBack); }; /** * 更新属性值 * @private * @function * @param {Any} value - 属性值 * @param {String} key - 属性名 * @param {String} path - 路径 * @param {Any} newVal - 路径对应的更新值 */ var updateValue = function updateValue(value, key, path, newVal) { if (isTofObject(value) && key !== path) { set(value, path.replace(key + '.', ''), newVal); return value; } else { return newVal; } }; /** * 处理移除元素 * @private * @param {Object} element - 元素 */ var handlerRemove = function handlerRemove(element) { // 移除监听 element.$removeWatch && element.$removeWatch(); // 移除过滤器 element.$removeFilter && element.$removeFilter(); // 继续处理子元素 forEach(element.childNodes, function (e) { return e.$isUpdate = false; }); }; /** * 处理恢复元素 * @private * @param {Object} element - 元素 */ var handlerRestore = function handlerRestore(element) { // 优先恢复过滤器 element.$restoreFilter && element.$restoreFilter(); // 恢复监听 element.$addWatch && element.$addWatch(); // 继续处理子元素 forEach(element.childNodes, function (el) { return el.$isUpdate = true; }); }; /** * 给元素添加$isUpdate属性,true更新,false不是更新 * @private * @param {Object} element - 元素 */ var defineIsUpdate = function defineIsUpdate(element) { var isUpdate = false; Object.defineProperty(element, '$isUpdate', { get: function get() { return isUpdate; }, set: function set(newVal) { if (newVal !== isUpdate) { if (newVal && element.parentNode && element.parentNode.$isUpdate) { isUpdate = newVal; handlerRestore(element); } else if (!newVal) { isUpdate = newVal; handlerRemove(element); } } } }); }; /** * 创建文本节点 * @function * @param {String} text - 节点的文本 * @returns {Object} - 返回文本节点 * @example * import { createTextNode, createData } from 'digi' * * const data = createData({ a: 123 }) * const textNode = createTextNode(data.$tp('a')) * * // 没有添加到页面的元素不更新 * console.log(textNode.nodeValue) * // => {{1.a}} * * // 添加到页面,自动更新 * document.body.appendChild(textNode) * console.log(textNode.nodeValue) * // => 123 * * // 可自动更新 * data.a = 321 * console.log(textNode.nodeValue) * // => 321 */ var createTextNode = function createTextNode(text) { var textNode = document.createTextNode(text); update(textNode, 'nodeValue', text, function (newValue) { return textNode.nodeValue = newValue; }); defineIsUpdate(textNode); return textNode; }; var textKeyRE = /^text(Content)*[0-9]*$/; // 匹配为子元素的属性名 var childKeyRE = /^child[0-9]*$/; /** * 创建元素 * @function * @param {String|Object|Undefined} data - data = tagName || { ...元素属性 };<br> * 扩展元素属性:{ child: 子元素, text: 文本节点 };<br> * child = data 或 [data1, ..., dataN]; <br> * 子元素的属性名为 'child' 或 'child' + 数字 { child: 子元素, child0: 子元素, child1: 子元素, ... }; <br> * 文本节点的属性名为 'text' 或 'text' + 数字 { text: '内容', text0: '内容', text1: '内容', ... }; <br> * 注意:值为字符串会调用setAttribute,例外['value', 'className'].indexOf(key) === -1,例外后继会添加 * @param {String|Undefined} ptn - 父元素的tagName,父元素的tagName为svg时必需传 * @returns {Object} - 返回元素 * @example * import { createElement } from 'digi' * * let el = createElement() * console.log(el.outerHTML) * // => '<div></div>' * * el = createElement('a') * console.log(el.outerHTML) * // => '<a></a>' * * el = createElement({ tagName: 'a' }) * console.log(el.outerHTML) * // => '<a></a>' * * // 子元素1 * el = createElement({ child: 'a' }) * console.log(el.outerHTML) * // => '<div><a></a></div>' * * // 子元素2 * el = createElement({ child: { tagName: 'a' } }) * console.log(el.outerHTML) * // => '<div><a></a></div>' * * // 子元素3 * el = createElement({ child: [{ tagName: 'a' }, 'p'] }) * console.log(el.outerHTML) * // => '<div><a></a><p></p></div>' * * // 文本节点 * el = createElement({ text: '123', text2: 'aa' }) * console.log(el.outerHTML) * // => '<div>123aa</div>' */ var createElement = function createElement(data, ptn) { if (isString(data) || isUndefined(data)) { data = { tagName: data }; } if (!isObject(data)) { window.console.error('createElement Error: ', data); window.console.log("View document: https://digi1874.github.io/digi-doc/".concat(version, "/global.html#digi")); return; } // 创建element,无tagName时默认为'div' ptn = ptn === 'svg' ? 'svg' : data.tagName === 'svg' ? 'svg' : ''; var element = ptn === 'svg' ? document.createElementNS('http://www.w3.org/2000/svg', data.tagName) : document.createElement(data.tagName || 'div'); data = cloneDeep(data); delete data.tagName; forEach(data, function (value, key) { if (childKeyRE.test(key)) { // 子元素 if (isArray(value)) { forEach(value, function (val) { var el = createElement(val, ptn); el && element.appendChild(el); }); } else { var el = createElement(value, ptn); el && element.appendChild(el); } } else if (textKeyRE.test(key)) { // 文本 var text = createTextNode(value); text && element.appendChild(text); } else { update(element, key, value, function (newVal, path) { value = updateValue(value, key, path, newVal); if (Object.prototype.hasOwnProperty.call(plugins, key)) { // 调用插件 plugins[key](element, value, path, newVal); } else if (['value', 'className'].indexOf(key) === -1 && isString(value)) { element.setAttribute(key, value); } else { set(element, path, newVal); } }); } }); defineIsUpdate(element); return element; }; /** * 监听document.body.childNode变化 * @private * @function */ var observe = function observe() { var observer = new MutationObserver(function (mutationsList) { forEach(mutationsList, function (mutation) { forEach(mutation.removedNodes, function (node) { node.$isUpdate = false; }); forEach(mutation.addedNodes, function (node) { node.$isUpdate = true; }); }); }); observer.observe(document.body, { childList: true, subtree: true }); }; /** * 把data转成元素添加为element的子元素 * @param {Array|Object|String} data - data = tagName || { ...element } || [..., tagName, ..., { ...element }, ...];<br> * 扩展元素属性:{ child: 子元素, text: 文本节点 },<br> * child = [data1, ..., dataN] 或 data <br> * 子元素的属性名为 'child' 或 'child' + 数字 { child: 子元素, child0: 子元素, child1: 子元素, ... }; <br> * 文本节点的属性名为 'text' 或 'text' + 数字 { text: '内容', text0: '内容', text1: '内容', ... } <br> * 参数详情查看{@link createElement} * @param {Object|Undefined} element - 元素,默认为#app元素或document.body,把data转成元素添加为其子元素 * @example * import digi, { createData } from 'digi' * * // 创建监听数据 * const data = createData({ a: 123 }) * * // 添加元素 * digi({ text: data.$tp('a') }) * * console.log(document.body.lastChild.outerHTML) * // => <div>123</div> * * data.a = 321 * // => watch a => newVal: 321, oldVal: 123 * * console.log(document.body.lastChild.outerHTML) * // => <div>321</div> */ var digi = function digi(data) { var element = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : digi.el; if (isArray(data)) { forEach(data, function (val) { var el = createElement(val); el && element.appendChild(el); }); } else { var el = createElement(data); el && element.appendChild(el); } }; Object.defineProperties(digi, { el: { value: document.getElementById('app') || document.body }, createElement: { value: createElement }, createTextNode: { value: createTextNode }, utils: { value: {} }, createData: { value: createData }, plugins: { value: addPlugins } }); forEach(utils, function (value, key) { Object.defineProperty(digi.utils, key, { value: value, enumerable: true }); }); digi.el.$isUpdate = true; document.body.$isUpdate = true; // 监听document.body.childNode变化 observe(); exports.cloneDeep = cloneDeep; exports.createData = createData; exports.createElement = createElement; exports.createTextNode = createTextNode; exports.default = digi; exports.forEach = forEach; exports.isArray = isArray; exports.isEmpty = isEmpty; exports.isFunction = isFunction; exports.isNull = isNull; exports.isNumber = isNumber; exports.isObject = isObject; exports.isString = isString; exports.isTofObject = isTofObject; exports.isUndefined = isUndefined; exports.pathJoin = pathJoin; exports.pathSplit = pathSplit; exports.pick = pick; exports.plugins = addPlugins; exports.set = set;