digi
Version:
1,218 lines (1,053 loc) • 33.2 kB
JavaScript
;
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;