litejs
Version:
Single-page application framework
628 lines (557 loc) • 15.4 kB
JavaScript
!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)