UNPKG

litejs

Version:

Single-page application framework

628 lines (557 loc) 15.4 kB
!function(exports, Object) { "use strict"; var arrProto = Array.prototype , getFns = Object.create(null) , setFns = Object.create(null) , filterFns = Object.create(null) , escRe = /['\n\r\u2028\u2029]|\\(?!x2e)/g , pathRe = /(^$|.+?)(?:\[([^\]]*)\]|\{([^}]*)})?(\.(?=[^.])|$)/g , reEscRe = /[.+^=:${}()|\/\\]/g , globRe = /[?*]|\[\w+\]/ , globReplace = /\?|(?=\*)/g , globGroup = /\[!(?=.*\])/g , primitiveRe = /^(-?(\d*\.)?\d+|true|false|null)$/ , valRe = /("|')(\\?.)*?\1|(\w*)\{(("|')(?:\\?.)*?\5|\w*\{(?:("|')(?:\\?.)*?\6|[^}])*?\}|.)*?\}|(@?)[^,]+/g , filterRe = /(!?)(\$?)((?:[-+:.\/\w]+|\[[^\]]+\]|\{[^}]+}|\\x2e)+)(\[]|\{}|)(?:(!(?=\1)==?|(?=\1)[<>=]=?)((?:("|')(?:\\?.)*?\7|\w*\{(?:("|')(?:\\?.)*?\8|\w*\{(?:("|')(?:\\?.)*?\9|[^}])*?\}|.)*?\}|[^|&()])*))?(?=[)|&]|$)|((&|\|)\11*|([()])|.)/g , onlyFilterRe = RegExp("^(?:([<>=])\\d+|" + filterRe.source.slice(0, -10) + "))+$") , cleanRe = /(\(o=d\)&&(?!.*o=o).*)\(o=d\)&&/g , nameRe = / (\w+)/ , fns = { "==": "e", "===": "s", ">": "g", ">=": "ge", "<": "l", "<=": "le" } , equal = Fn("a->b->a==b") , strictEqual = Fn("a->b->a===b") , greater = Fn("a->b->a<b") , greaterEqual = Fn("a->b->a<=b") , less = Fn("a->b->a>b") , lessEqual = Fn("a->b->a>=b") , reMatch = Fn("a->b->typeof b==='string'&&a.test(b)") , tmpDate = new Date() , isArray = Array.isArray exports.Item = Item exports.List = List Item.filterFn = filterFn filterFn.re = filterRe Item.copy = copy Item.extend = List.extend = extend Item.cache = {} List.cache = {} Item.get = function(obj, pointer) { return pathFn(pointer)(obj) } Item.get.str = pathStr Item.set = function(obj, pointer, value) { return pathFn(pointer, true)(obj, value) } function escFn(str) { return escape(str).replace(/%u/g, "\\u").replace(/%/g, "\\x") } function extend(a, b, c, d, e) { // IE8: var a=function L(){a!==L} var fn = this , str = fn.toString() , clone = Function(str + ";return " + str.match(nameRe)[1])() , _super = fn.prototype clone.prototype = Object.assign( isArray(_super) ? [] : Object.create(null), _super, a, b, c, d, e, {_super: _super, constructor: clone} ) clone.extend = extend clone.cache = clone.prototype.cache || fn.cache return clone } /* istanbul ignore next */ function Item(attrs, opts) { var item = this if (attrs instanceof Item) { return attrs } if (!(item instanceof Item)) { return Item.cache[attrs.id] || (Item.cache[attrs.id] = new Item(attrs, opts)) } item.data = attrs item.lists = [] if (item.init) { item.init(attrs, opts) } item.set(attrs) } Item.prototype = { constructor: Item, set: function(key, val, opts) { var item = this , changed = [] , previous = {} , data = key if (typeof key !== "object") { pathFn(key, true)(data = {}, val) } else { opts = val } if (item.validate(data, opts)) { JSON.mergePatch(item.data, data, changed, previous) } if (changed.length) { item.emit("change", changed, item, opts, previous) changed.each(function(pointer, idx) { item.emit("change:" + pointer, item.get(pointer), item, opts, previous[pointer]) }) } return changed }, get: function(name, fallback) { var val = pathFn(name)(this.data) return val == void 0 ? fallback : val }, validate: returnTrue, destroy: function(next) { var item = this , arr = item.lists.slice() , len = arr.length for (; len--; ) { arr[len].remove(item) } item.emit("destroy") item.off() item.constructor.cache[item.get("id")] = null //delete item.constructor.cache[item.get("id")] }, matches: function(expr) { return filterFn(expr)(this.data) }, each: function(path, fn, scope) { Object.each(this.get(path), fn, scope || this) return this }, then: _then, toJSON: function(attrs) { return isArray(attrs) ? copy({}, this.data, attrs) : this.data } } Event.asEmitter(Item.prototype) /* istanbul ignore next */ function List(name, opts) { var list = this if (!(list instanceof List)) { return List.cache[name] || (List.cache[name] = new List(name, opts)) } list.name = name if (list.init) { list.init(name, opts) } list.on("newListener", function(type, fn, scope, origin) { if (type.split(":")[0] === "change") { list.eachLive(add, remove) list.on("removeListener", off) } function add(item) { item.on(type, fn, scope, origin) } function remove(item) { item.off(type, fn, scope, origin) } function off(_type, _fn, _scope, _origin) { if (_type === type && _fn == fn && _scope == scope && _origin == origin) { list.each(remove).off("add", add).off("remove", remove) list.off("removeListener", off) } } }) } Object.assign(List.prototype = [], Event.Emitter.prototype, { item: Item, wait: Fn.hold, syncMethods: [ "add", "emit", "filterFn", "merge", "item", "paged", "pluck", "remove", "removeAll", "sortFn" ], add: function(item) { var pos , list = this if (item) { if (item.constructor === Object) item = list.item(item) if (item.lists.indexOf(list) == -1) { pos = indexFor(list, item, list.sortFn) if (list.filterFn(item, pos)) { item.lists.push(list) list.splice(pos , 0, item) list.emit("add", item, pos, true) } } } return list }, filterFn: returnTrue, remove: function(item) { var list = this if (item && item.lists && item.lists.remove(list) > -1) { list.emit("remove", item, arrProto.remove.call(list, item), false) } return list }, removeAll: function(){ for (var list = this, len = list.length; len--; ) { list.remove(list[len]) } return list }, each: function(fn, scope) { arrProto.each.call(this, fn, scope || this) return this }, eachLive: function(start, stop, scope) { this .on("add", start, scope) .on("remove", stop, scope) .slice(0) .each(start, scope) return this }, get: function(id, next, scope) { var list = this , item = list.item.cache[id] if (item && item.lists.indexOf(list) != -1) { return next ? (next.call(scope || list, null, item), list) : item } next && next.call(scope || list, Error("Item[#" + id + "] not found in " + list)) }, at: function(index, next, scope) { var list = this , item = list[index < 0 ? list.length + index : index] , err = item ? null : Error("Item not found at " + index + " in " + list) return next ? (next.call(scope || list, err, item), list) : item }, then: _then, pluck: function(field, next, scope) { var list = this , arr = arrProto.map.call(list, function(item) { return item.get(field) }) return next ? (next.call(scope || list, arr), list) : arr }, set: function(key, val) { this.each(function(item) { item.set(key, val) }) return this }, merge: function(mergeList) { var list = this , lists = list.lists || (list.lists = []) if (mergeList && lists.indexOf(mergeList) == -1) { lists.push(mergeList) mergeList .eachLive(list.add, _remove, list) .on("change", _change, list) .then(list.wait()) } return list }, unmerge: function(mergedList) { var list = this , lists = list.lists , idx = lists && lists.indexOf(mergedList) if (lists && mergedList && idx != -1) { lists.splice(idx, 1) mergedList .each(_remove, list) .off("add", list.add, list) .off("remove", _remove, list) .off("change", _change, list) } return list }, paged: function(num) { var list = this , view = new List() , start = 0 , end = num view.filterFn = function(item, pos) { pos = list.indexOf(item) return pos >= start && pos < end } view.pages = new List() view.setPage = function(newPage) { var lastPage = 0 | (list.length / num) if (newPage > lastPage) { newPage = lastPage } view.page = newPage start = num * newPage end = start + num view.reFilter() } view.merge(list) return view }, reFilter: function() { var list = this , lists = list.lists || (list.lists = []) lists.each(function(parent) { parent.each(function(item) { _change.call(list, null, item) }) }) }, reSort: function(fn) { var item , list = this , len = list.length , removed = [] if (typeof fn === "string") { fn = [fn] } if (isArray(fn)) { fn = Fn("a b->" + fn.map(function(key) { return "(A=a.get('" + key + "'))<(B=b.get('" + key + "'))?-1:A>B?1:0" }).join("||")) } fn = list.sortFn = fn || list.sortFn for (; len > 1; ) { item = list[--len - 1] if (fn(item, list[len]) > 0) { removed.push(item) list.remove(item) } } removed.each(list.add, list) }, toJSON: function() { return this.slice(0) } }) function _remove(item) { if (item.lists.every(notIn, this.lists)) { this.remove(item) } } function _change(changed, item) { var list = this , matches = list.filterFn(item) , inList = item.lists.indexOf(list) > -1 if (matches && !inList) { list.add(item) } if (!matches && inList) { list.remove(item) } } function pathStr(str, set) { return ( str.charAt(0) === "/" ? str.slice(1).replace(/\./g, "\\x2e").replace(/\//g, ".").replace(/~1/g, "/").replace(/~0/g, "~") : str ) .replace(escRe, escFn) .replace(pathRe, set === true ? pathSet : pathGet) } function pathGet(str, path, arr, obj, dot) { var v = dot ? "(o=" : "(c=" , sub = arr || obj if (sub && !(sub = onlyFilterRe.exec(sub))) throw Error("Invalid GET filter") return ( sub ? pathGet(0, path, 0, 0, 1) + (arr ? "i" : "j") + "(o)&&" + v + ( sub[1] ? (arr ? "o" : "Object.keys(o)") + ".length" + (sub[1] == "=" ? "=" : "") + sub[0] : +arr == arr ? "o[" + (arr < 0 ? "o.length" + arr : arr) + "]" : (arr ? "I" : "J") + "(o,f('" + sub[0] + "'))" ) + ")" : v + "o['" + path + ( arr === "" ? "'])&&i(c)&&c" : obj === "" ? "'])&&j(c)&&c" : "'])" ) ) + (dot ? "&&" : "") } function pathSet(str, path, arr, obj, dot) { var op = "o['" + path + "']" , out = "" , sub = arr || obj if (sub) { out = "(o="+(arr?"i":"j")+"(o['" + path + "'])?o['" + path + "']:(o['" + path + "']="+(arr?"[]":"{}")+"))&&" if (arr === "-") { op = "o[o.length]" } else if (+arr == arr) { op = "o[" + (arr < 0 ? "o.length" + arr : arr) + "]" } else { if (!onlyFilterRe.test(arr)) throw Error("Invalid SET filter") op = "o[t]" out += "(t="+(arr?"I":"J")+"(o,f('" + sub + "'),1))!=null&&" } } return out + (dot ? "(o=typeof " + op + "==='object'&&" + op + "||(" + op + "={}))&&" : "((c=" + op + "),(" + op + "=v),c)" ) } function pathFn(str, set) { var map = set === true ? setFns : getFns return ( map[str] || (map[str] = Function("i,j,I,J,f", "return function(d,v){var c,o,t;return (o=d)&&" + pathStr(str, set) + "||c}")( isArray, isObject, inArray, inObject, filterFn )) ) } function copy(a, b, attrs) { var len = b && attrs && attrs.length return len ? ( copy[len] || (copy[len] = Function("a,b,k,g", "var v,u;return " + Object.keys(attrs).map(copyFnI) + ",a")) )(a, b, attrs, pathFn) : a } function copyFnI(i) { return "(v=g(k[" + i + "])(b))!==u&&g(k[" + i + "],true)(a,v)" } function returnTrue() { return true } function _then(next, scope) { if (typeof next === "function") { next.call(scope || this) } return this } function notIn(v) { return this.indexOf(v) == -1 } function indexFor(arr, el, compFn) { var o , i = 0 , l = arr.length // Fast lookup for last position if (compFn && l && compFn(el, arr[l-1]) < 1) { // Otherwise binary search for (; i < l; ) { compFn(el, arr[o=(i+l)>>1]) < 0 ? l=o : i=o+1 } } return l } function filterFn(str, prefix, opts, getter, tmp) { var optimized , arr = [] , key = (prefix || "") + filterStr(str, opts, arr, getter) , fn = filterFns[key] if (!fn) { for (optimized = key; optimized != (optimized = optimized.replace(cleanRe, "$1")); ); fn = filterFns[key] = Function( "a,i,j,I,J,f,p,e,s,g,ge,l,le,m,t", "return function(d){var o;return " + optimized + "}" ) } return fn( arr, isArray, isObject, inArray, inObject, filterFn, pathFn, equal, strictEqual, greater, greaterEqual, less, lessEqual, reMatch, tmp ) } filterFn.str = filterStr filterFn.valRe = valRe // Date{day=1,2} // sliceable[start:stop:step] // Geo{distance=200km&lat=40&lon=-70} // ?pos=Geo{distance=200km&lat=@lat&lon=@lon} // [lon, lat] in The GeoJSON Format RFC 7946 // IP{net=127.0.0.1/30} // var re = /^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(?:\.(?=.)|$)){4}$/ // ["1.2.3.4", "127.0.0.1", "192.175.255.254."].map(re.test, re) filterFn.date = function(str) { return filterFn(str, "(t.setTime(+d)>=0)&&", null, dateGetter, tmpDate) } function dateGetter(name) { return "(t.get" + Date.fnMap[name] + ")" } function filterStr(qs, opts, arr, getter) { return qs.replace(filterRe, worker).replace(/^[1&]&+|&+1?$/g, "") || "1" function worker(all, not, isOption, attr, isArray, op, val, q1, q2, q3, ext, ok2, ok1) { if (ext) { if (!ok2 && !ok1) { throw new Error("Invalid filter: " + qs) } return ok2 ? ok2 + ok2 : ok1 } if (isOption) { if (opts) opts[attr] = val return "1" } var idd, m, v, isRe , a = [] , pre = "(o=d)&&" attr = (getter || pathStr)(attr) if (m = attr.match(/\(c=(.*?)\)$/)) { if (m.index) pre += attr.slice(0, m.index) attr = m[1] } if (op == "!=" || op == "!==") { not = "!" op = op.slice(1) } if (isArray) { pre += not + (isArray === "[]" ? "i(" : "j(") + attr + ")" } if (!op) { return isArray === "" ? pre + not + attr : pre } if (op == "=" || op == "==") op += "=" if (val === "") val="''" for (; m = valRe.exec(val); ) { isRe = 0 idd = arr.indexOf(v = m[1] || m[4] ? m[0].slice(m[3] ? m[3].length + 1 : 1, -1) : m[7] ? m[0].slice(1) : primitiveRe.test(m[0]) ? JSON.parse(m[0]) : (isRe = globRe.test(m[0])) ? RegExp( "^" + m[0] .replace(reEscRe, "\\$&") .replace(globReplace, ".") .replace(globGroup, "[^") + "$", op === "==" ? "i" : "" ) : m[0] ) v = "a[" + (idd > -1 ? idd : arr.push(v) - 1) + "]" idd = ( m[3] ? "f." + m[3].toLowerCase() : m[4] ? "f" : isRe ? "m" : fns[op] ) + "(" + v a.push( isArray ? (isArray === "[]" ? "I(" : "J(") + attr + "," + idd + "))" : isRe ? "typeof " + attr + "==='string'&&" + v + ".test(" + attr + ")" : m[3] || m[4] ? idd + ")(" + attr + ")" : m[7] ? attr + "!==void 0&&" + attr + op + "p(" + v + ")(o)" : attr + op + v ) } return pre + ( isArray ? (not ? "||" : "&&") : "" ) + not + "(" + a.join("||") + ")" } } function isObject(obj) { return obj != null && obj.constructor === Object } function inArray(a, fn, idx) { for (var i = -1, len = a.length; ++i < len; ) { if (fn(a[i])) return idx == null ? a[i] : i } return idx != null && len } function inObject(o, fn, idx) { for (var key in o) { if (fn(o[key])) return idx == null ? o[key] : key } return null } }(this, Object)