UNPKG

todomvc

Version:

> Helping you select an MV\* framework

1,737 lines (1,409 loc) 118 kB
/* * Kendo UI Web v2013.3.1119 (http://kendoui.com) * Copyright 2013 Telerik AD. All rights reserved. * * Kendo UI Web commercial licenses may be obtained at * https://www.kendoui.com/purchase/license-agreement/kendo-ui-web-commercial.aspx * If you do not own a commercial license, this file shall be governed by the * GNU General Public License (GPL) version 3. * For GPL requirements, please review: http://www.gnu.org/copyleft/gpl.html */ kendo_module({ id: "data", name: "Data source", category: "framework", description: "Powerful component for using local and remote data.Fully supports CRUD, Sorting, Paging, Filtering, Grouping, and Aggregates.", depends: [ "core" ], features: [ { id: "data-odata", name: "OData", description: "Support for accessing Open Data Protocol (OData) services.", depends: [ "data.odata" ] }, { id: "data-XML", name: "XML", description: "Support for binding to XML.", depends: [ "data.xml" ] } ] }); (function($, undefined) { var extend = $.extend, proxy = $.proxy, isPlainObject = $.isPlainObject, isEmptyObject = $.isEmptyObject, isArray = $.isArray, grep = $.grep, ajax = $.ajax, map, each = $.each, noop = $.noop, kendo = window.kendo, isFunction = kendo.isFunction, Observable = kendo.Observable, Class = kendo.Class, STRING = "string", FUNCTION = "function", CREATE = "create", READ = "read", UPDATE = "update", DESTROY = "destroy", CHANGE = "change", SYNC = "sync", GET = "get", ERROR = "error", REQUESTSTART = "requestStart", PROGRESS = "progress", REQUESTEND = "requestEnd", crud = [CREATE, READ, UPDATE, DESTROY], identity = function(o) { return o; }, getter = kendo.getter, stringify = kendo.stringify, math = Math, push = [].push, join = [].join, pop = [].pop, splice = [].splice, shift = [].shift, slice = [].slice, unshift = [].unshift, toString = {}.toString, stableSort = kendo.support.stableSort, dateRegExp = /^\/Date\((.*?)\)\/$/, newLineRegExp = /(\r+|\n+)/g, quoteRegExp = /(?=['\\])/g; var ObservableArray = Observable.extend({ init: function(array, type) { var that = this; that.type = type || ObservableObject; Observable.fn.init.call(that); that.length = array.length; that.wrapAll(array, that); }, toJSON: function() { var idx, length = this.length, value, json = new Array(length); for (idx = 0; idx < length; idx++){ value = this[idx]; if (value instanceof ObservableObject) { value = value.toJSON(); } json[idx] = value; } return json; }, parent: noop, wrapAll: function(source, target) { var that = this, idx, length, parent = function() { return that; }; target = target || []; for (idx = 0, length = source.length; idx < length; idx++) { target[idx] = that.wrap(source[idx], parent); } return target; }, wrap: function(object, parent) { var that = this, observable; if (object !== null && toString.call(object) === "[object Object]") { observable = object instanceof that.type || object instanceof Model; if (!observable) { object = object instanceof ObservableObject ? object.toJSON() : object; object = new that.type(object); } object.parent = parent; object.bind(CHANGE, function(e) { that.trigger(CHANGE, { field: e.field, node: e.node, index: e.index, items: e.items || [this], action: e.node ? (e.action || "itemchange") : "itemchange" }); }); } return object; }, push: function() { var index = this.length, items = this.wrapAll(arguments), result; result = push.apply(this, items); this.trigger(CHANGE, { action: "add", index: index, items: items }); return result; }, slice: slice, join: join, pop: function() { var length = this.length, result = pop.apply(this); if (length) { this.trigger(CHANGE, { action: "remove", index: length - 1, items:[result] }); } return result; }, splice: function(index, howMany, item) { var items = this.wrapAll(slice.call(arguments, 2)), result, i, len; result = splice.apply(this, [index, howMany].concat(items)); if (result.length) { this.trigger(CHANGE, { action: "remove", index: index, items: result }); for (i = 0, len = result.length; i < len; i++) { if (result[i].children) { result[i].unbind(CHANGE); } } } if (item) { this.trigger(CHANGE, { action: "add", index: index, items: items }); } return result; }, shift: function() { var length = this.length, result = shift.apply(this); if (length) { this.trigger(CHANGE, { action: "remove", index: 0, items:[result] }); } return result; }, unshift: function() { var items = this.wrapAll(arguments), result; result = unshift.apply(this, items); this.trigger(CHANGE, { action: "add", index: 0, items: items }); return result; }, indexOf: function(item) { var that = this, idx, length; for (idx = 0, length = that.length; idx < length; idx++) { if (that[idx] === item) { return idx; } } return -1; }, forEach: function(callback) { var idx = 0, length = this.length; for (; idx < length; idx++) { callback(this[idx], idx, this); } }, map: function(callback) { var idx = 0, result = [], length = this.length; for (; idx < length; idx++) { result[idx] = callback(this[idx], idx, this); } return result; }, filter: function(callback) { var idx = 0, result = [], item, length = this.length; for (; idx < length; idx++) { item = this[idx]; if (callback(item, idx, this)) { result[result.length] = item; } } return result; }, find: function(callback) { var idx = 0, item, length = this.length; for (; idx < length; idx++) { item = this[idx]; if (callback(item, idx, this)) { return item; } } }, every: function(callback) { var idx = 0, item, length = this.length; for (; idx < length; idx++) { item = this[idx]; if (!callback(item, idx, this)) { return false; } } return true; }, some: function(callback) { var idx = 0, item, length = this.length; for (; idx < length; idx++) { item = this[idx]; if (callback(item, idx, this)) { return true; } } return false; }, // non-standard collection methods remove: function(item) { this.splice(this.indexOf(item), 1); }, empty: function() { this.splice(0, this.length); } }); function eventHandler(context, type, field, prefix) { return function(e) { var event = {}, key; for (key in e) { event[key] = e[key]; } if (prefix) { event.field = field + "." + e.field; } else { event.field = field; } if (type == CHANGE && context._notifyChange) { context._notifyChange(event); } context.trigger(type, event); }; } var ObservableObject = Observable.extend({ init: function(value) { var that = this, member, field, parent = function() { return that; }; Observable.fn.init.call(this); for (field in value) { member = value[field]; if (field.charAt(0) != "_") { member = that.wrap(member, field, parent); } that[field] = member; } that.uid = kendo.guid(); }, shouldSerialize: function(field) { return this.hasOwnProperty(field) && field !== "_events" && typeof this[field] !== FUNCTION && field !== "uid"; }, forEach: function(f) { for (var i in this) { if (this.shouldSerialize(i)) { f(this[i], i); } } }, toJSON: function() { var result = {}, value, field; for (field in this) { if (this.shouldSerialize(field)) { value = this[field]; if (value instanceof ObservableObject || value instanceof ObservableArray) { value = value.toJSON(); } result[field] = value; } } return result; }, get: function(field) { var that = this, result; that.trigger(GET, { field: field }); if (field === "this") { result = that; } else { result = kendo.getter(field, true)(that); } return result; }, _set: function(field, value) { var that = this; var composite = field.indexOf(".") >= 0; if (composite) { var paths = field.split("."), path = ""; while (paths.length > 1) { path += paths.shift(); var obj = kendo.getter(path, true)(that); if (obj instanceof ObservableObject) { obj.set(paths.join("."), value); return composite; } path += "."; } } kendo.setter(field)(that, value); return composite; }, set: function(field, value) { var that = this, current = kendo.getter(field, true)(that); if (current !== value) { if (!that.trigger("set", { field: field, value: value })) { if (!that._set(field, that.wrap(value, field, function() { return that; })) || field.indexOf("(") >= 0 || field.indexOf("[") >= 0) { that.trigger(CHANGE, { field: field }); } } } }, parent: noop, wrap: function(object, field, parent) { var that = this, type = toString.call(object); if (object != null && (type === "[object Object]" || type === "[object Array]")) { var isObservableArray = object instanceof ObservableArray; var isDataSource = object instanceof DataSource; if (type === "[object Object]" && !isDataSource && !isObservableArray) { if (!(object instanceof ObservableObject)) { object = new ObservableObject(object); } if (object.parent() != parent()) { object.bind(GET, eventHandler(that, GET, field, true)); object.bind(CHANGE, eventHandler(that, CHANGE, field, true)); } } else if (type === "[object Array]" || isObservableArray || isDataSource) { if (!isObservableArray && !isDataSource) { object = new ObservableArray(object); } if (object.parent() != parent()) { object.bind(CHANGE, eventHandler(that, CHANGE, field, false)); } } object.parent = parent; } return object; } }); function equal(x, y) { if (x === y) { return true; } var xtype = $.type(x), ytype = $.type(y), field; if (xtype !== ytype) { return false; } if (xtype === "date") { return x.getTime() === y.getTime(); } if (xtype !== "object" && xtype !== "array") { return false; } for (field in x) { if (!equal(x[field], y[field])) { return false; } } return true; } var parsers = { "number": function(value) { return kendo.parseFloat(value); }, "date": function(value) { return kendo.parseDate(value); }, "boolean": function(value) { if (typeof value === STRING) { return value.toLowerCase() === "true"; } return value != null ? !!value : value; }, "string": function(value) { return value != null ? (value + "") : value; }, "default": function(value) { return value; } }; var defaultValues = { "string": "", "number": 0, "date": new Date(), "boolean": false, "default": "" }; function getFieldByName(obj, name) { var field, fieldName; for (fieldName in obj) { field = obj[fieldName]; if (isPlainObject(field) && field.field && field.field === name) { return field; } else if (field === name) { return field; } } return null; } var Model = ObservableObject.extend({ init: function(data) { var that = this; if (!data || $.isEmptyObject(data)) { data = $.extend({}, that.defaults, data); } ObservableObject.fn.init.call(that, data); that.dirty = false; if (that.idField) { that.id = that.get(that.idField); if (that.id === undefined) { that.id = that._defaultId; } } }, shouldSerialize: function(field) { return ObservableObject.fn.shouldSerialize.call(this, field) && field !== "uid" && !(this.idField !== "id" && field === "id") && field !== "dirty" && field !== "_accessors"; }, _parse: function(field, value) { var that = this, fieldName = field, fields = (that.fields || {}), parse; field = fields[field]; if (!field) { field = getFieldByName(fields, fieldName); } if (field) { parse = field.parse; if (!parse && field.type) { parse = parsers[field.type.toLowerCase()]; } } return parse ? parse(value) : value; }, _notifyChange: function(e) { var action = e.action; if (action == "add" || action == "remove") { this.dirty = true; } }, editable: function(field) { field = (this.fields || {})[field]; return field ? field.editable !== false : true; }, set: function(field, value, initiator) { var that = this; if (that.editable(field)) { value = that._parse(field, value); if (!equal(value, that.get(field))) { that.dirty = true; ObservableObject.fn.set.call(that, field, value, initiator); } } }, accept: function(data) { var that = this, parent = function() { return that; }, field; for (field in data) { var value = data[field]; if (field.charAt(0) != "_") { value = that.wrap(data[field], field, parent); } that._set(field, value); } if (that.idField) { that.id = that.get(that.idField); } that.dirty = false; }, isNew: function() { return this.id === this._defaultId; } }); Model.define = function(base, options) { if (options === undefined) { options = base; base = Model; } var model, proto = extend({ defaults: {} }, options), name, field, type, value, idx, length, fields = {}, originalName, id = proto.id; if (id) { proto.idField = id; } if (proto.id) { delete proto.id; } if (id) { proto.defaults[id] = proto._defaultId = ""; } if (toString.call(proto.fields) === "[object Array]") { for (idx = 0, length = proto.fields.length; idx < length; idx++) { field = proto.fields[idx]; if (typeof field === STRING) { fields[field] = {}; } else if (field.field) { fields[field.field] = field; } } proto.fields = fields; } for (name in proto.fields) { field = proto.fields[name]; type = field.type || "default"; value = null; originalName = name; name = typeof (field.field) === STRING ? field.field : name; if (!field.nullable) { value = proto.defaults[originalName !== name ? originalName : name] = field.defaultValue !== undefined ? field.defaultValue : defaultValues[type.toLowerCase()]; } if (options.id === name) { proto._defaultId = value; } proto.defaults[originalName !== name ? originalName : name] = value; field.parse = field.parse || parsers[type]; } model = base.extend(proto); model.define = function(options) { return Model.define(model, options); }; if (proto.fields) { model.fields = proto.fields; model.idField = proto.idField; } return model; }; var Comparer = { selector: function(field) { return isFunction(field) ? field : getter(field); }, compare: function(field) { var selector = this.selector(field); return function (a, b) { a = selector(a); b = selector(b); if (a == null && b == null) { return 0; } if (a == null) { return -1; } if (b == null) { return 1; } if (a.localeCompare) { return a.localeCompare(b); } return a > b ? 1 : (a < b ? -1 : 0); }; }, create: function(sort) { var compare = sort.compare || this.compare(sort.field); if (sort.dir == "desc") { return function(a, b) { return compare(b, a, true); }; } return compare; }, combine: function(comparers) { return function(a, b) { var result = comparers[0](a, b), idx, length; for (idx = 1, length = comparers.length; idx < length; idx ++) { result = result || comparers[idx](a, b); } return result; }; } }; var StableComparer = extend({}, Comparer, { asc: function(field) { var selector = this.selector(field); return function (a, b) { var valueA = selector(a); var valueB = selector(b); if (valueA && valueA.getTime && valueB && valueB.getTime) { valueA = valueA.getTime(); valueB = valueB.getTime(); } if (valueA === valueB) { return a.__position - b.__position; } if (valueA == null) { return -1; } if (valueB == null) { return 1; } if (valueA.localeCompare) { return valueA.localeCompare(valueB); } return valueA > valueB ? 1 : -1; }; }, desc: function(field) { var selector = this.selector(field); return function (a, b) { var valueA = selector(a); var valueB = selector(b); if (valueA && valueA.getTime && valueB && valueB.getTime) { valueA = valueA.getTime(); valueB = valueB.getTime(); } if (valueA === valueB) { return a.__position - b.__position; } if (valueA == null) { return 1; } if (valueB == null) { return -1; } if (valueB.localeCompare) { return valueB.localeCompare(valueA); } return valueA < valueB ? 1 : -1; }; }, create: function(sort) { return this[sort.dir](sort.field); } }); map = function (array, callback) { var idx, length = array.length, result = new Array(length); for (idx = 0; idx < length; idx++) { result[idx] = callback(array[idx], idx, array); } return result; }; var operators = (function(){ function quote(value) { return value.replace(quoteRegExp, "\\").replace(newLineRegExp, ""); } function operator(op, a, b, ignore) { var date; if (b != null) { if (typeof b === STRING) { b = quote(b); date = dateRegExp.exec(b); if (date) { b = new Date(+date[1]); } else if (ignore) { b = "'" + b.toLowerCase() + "'"; a = "(" + a + " || '').toLowerCase()"; } else { b = "'" + b + "'"; } } if (b.getTime) { //b looks like a Date a = "(" + a + "?" + a + ".getTime():" + a + ")"; b = b.getTime(); } } return a + " " + op + " " + b; } return { eq: function(a, b, ignore) { return operator("==", a, b, ignore); }, neq: function(a, b, ignore) { return operator("!=", a, b, ignore); }, gt: function(a, b, ignore) { return operator(">", a, b, ignore); }, gte: function(a, b, ignore) { return operator(">=", a, b, ignore); }, lt: function(a, b, ignore) { return operator("<", a, b, ignore); }, lte: function(a, b, ignore) { return operator("<=", a, b, ignore); }, startswith: function(a, b, ignore) { if (ignore) { a = "(" + a + " || '').toLowerCase()"; if (b) { b = b.toLowerCase(); } } if (b) { b = quote(b); } return a + ".lastIndexOf('" + b + "', 0) == 0"; }, endswith: function(a, b, ignore) { if (ignore) { a = "(" + a + " || '').toLowerCase()"; if (b) { b = b.toLowerCase(); } } if (b) { b = quote(b); } return a + ".indexOf('" + b + "', " + a + ".length - " + (b || "").length + ") >= 0"; }, contains: function(a, b, ignore) { if (ignore) { a = "(" + a + " || '').toLowerCase()"; if (b) { b = b.toLowerCase(); } } if (b) { b = quote(b); } return a + ".indexOf('" + b + "') >= 0"; }, doesnotcontain: function(a, b, ignore) { if (ignore) { a = "(" + a + " || '').toLowerCase()"; if (b) { b = b.toLowerCase(); } } if (b) { b = quote(b); } return a + ".indexOf('" + b + "') == -1"; } }; })(); function Query(data) { this.data = data || []; } Query.filterExpr = function(expression) { var expressions = [], logic = { and: " && ", or: " || " }, idx, length, filter, expr, fieldFunctions = [], operatorFunctions = [], field, operator, filters = expression.filters; for (idx = 0, length = filters.length; idx < length; idx++) { filter = filters[idx]; field = filter.field; operator = filter.operator; if (filter.filters) { expr = Query.filterExpr(filter); //Nested function fields or operators - update their index e.g. __o[0] -> __o[1] filter = expr.expression .replace(/__o\[(\d+)\]/g, function(match, index) { index = +index; return "__o[" + (operatorFunctions.length + index) + "]"; }) .replace(/__f\[(\d+)\]/g, function(match, index) { index = +index; return "__f[" + (fieldFunctions.length + index) + "]"; }); operatorFunctions.push.apply(operatorFunctions, expr.operators); fieldFunctions.push.apply(fieldFunctions, expr.fields); } else { if (typeof field === FUNCTION) { expr = "__f[" + fieldFunctions.length +"](d)"; fieldFunctions.push(field); } else { expr = kendo.expr(field); } if (typeof operator === FUNCTION) { filter = "__o[" + operatorFunctions.length + "](" + expr + ", " + filter.value + ")"; operatorFunctions.push(operator); } else { filter = operators[(operator || "eq").toLowerCase()](expr, filter.value, filter.ignoreCase !== undefined? filter.ignoreCase : true); } } expressions.push(filter); } return { expression: "(" + expressions.join(logic[expression.logic]) + ")", fields: fieldFunctions, operators: operatorFunctions }; }; function normalizeSort(field, dir) { if (field) { var descriptor = typeof field === STRING ? { field: field, dir: dir } : field, descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []); return grep(descriptors, function(d) { return !!d.dir; }); } } var operatorMap = { "==": "eq", equals: "eq", isequalto: "eq", equalto: "eq", equal: "eq", "!=": "neq", ne: "neq", notequals: "neq", isnotequalto: "neq", notequalto: "neq", notequal: "neq", "<": "lt", islessthan: "lt", lessthan: "lt", less: "lt", "<=": "lte", le: "lte", islessthanorequalto: "lte", lessthanequal: "lte", ">": "gt", isgreaterthan: "gt", greaterthan: "gt", greater: "gt", ">=": "gte", isgreaterthanorequalto: "gte", greaterthanequal: "gte", ge: "gte", notsubstringof: "doesnotcontain" }; function normalizeOperator(expression) { var idx, length, filter, operator, filters = expression.filters; if (filters) { for (idx = 0, length = filters.length; idx < length; idx++) { filter = filters[idx]; operator = filter.operator; if (operator && typeof operator === STRING) { filter.operator = operatorMap[operator.toLowerCase()] || operator; } normalizeOperator(filter); } } } function normalizeFilter(expression) { if (expression && !isEmptyObject(expression)) { if (isArray(expression) || !expression.filters) { expression = { logic: "and", filters: isArray(expression) ? expression : [expression] }; } normalizeOperator(expression); return expression; } } Query.normalizeFilter = normalizeFilter; function normalizeAggregate(expressions) { return isArray(expressions) ? expressions : [expressions]; } function normalizeGroup(field, dir) { var descriptor = typeof field === STRING ? { field: field, dir: dir } : field, descriptors = isArray(descriptor) ? descriptor : (descriptor !== undefined ? [descriptor] : []); return map(descriptors, function(d) { return { field: d.field, dir: d.dir || "asc", aggregates: d.aggregates }; }); } Query.prototype = { toArray: function () { return this.data; }, range: function(index, count) { return new Query(this.data.slice(index, index + count)); }, skip: function (count) { return new Query(this.data.slice(count)); }, take: function (count) { return new Query(this.data.slice(0, count)); }, select: function (selector) { return new Query(map(this.data, selector)); }, order: function(selector, dir) { var sort = { dir: dir }; if (selector) { if (selector.compare) { sort.compare = selector.compare; } else { sort.field = selector; } } return new Query(this.data.slice(0).sort(Comparer.create(sort))); }, orderBy: function(selector) { return this.order(selector, "asc"); }, orderByDescending: function(selector) { return this.order(selector, "desc"); }, sort: function(field, dir, comparer) { var idx, length, descriptors = normalizeSort(field, dir), comparers = []; comparer = comparer || Comparer; if (descriptors.length) { for (idx = 0, length = descriptors.length; idx < length; idx++) { comparers.push(comparer.create(descriptors[idx])); } return this.orderBy({ compare: comparer.combine(comparers) }); } return this; }, filter: function(expressions) { var idx, current, length, compiled, predicate, data = this.data, fields, operators, result = [], filter; expressions = normalizeFilter(expressions); if (!expressions || expressions.filters.length === 0) { return this; } compiled = Query.filterExpr(expressions); fields = compiled.fields; operators = compiled.operators; predicate = filter = new Function("d, __f, __o", "return " + compiled.expression); if (fields.length || operators.length) { filter = function(d) { return predicate(d, fields, operators); }; } for (idx = 0, length = data.length; idx < length; idx++) { current = data[idx]; if (filter(current)) { result.push(current); } } return new Query(result); }, group: function(descriptors, allData) { descriptors = normalizeGroup(descriptors || []); allData = allData || this.data; var that = this, result = new Query(that.data), descriptor; if (descriptors.length > 0) { descriptor = descriptors[0]; result = result.groupBy(descriptor).select(function(group) { var data = new Query(allData).filter([ { field: group.field, operator: "eq", value: group.value, ignoreCase: false } ]); return { field: group.field, value: group.value, items: descriptors.length > 1 ? new Query(group.items).group(descriptors.slice(1), data.toArray()).toArray() : group.items, hasSubgroups: descriptors.length > 1, aggregates: data.aggregate(descriptor.aggregates) }; }); } return result; }, groupBy: function(descriptor) { if (isEmptyObject(descriptor) || !this.data.length) { return new Query([]); } var field = descriptor.field, sorted = this._sortForGrouping(field, descriptor.dir || "asc"), accessor = kendo.accessor(field), item, groupValue = accessor.get(sorted[0], field), group = { field: field, value: groupValue, items: [] }, currentValue, idx, len, result = [group]; for(idx = 0, len = sorted.length; idx < len; idx++) { item = sorted[idx]; currentValue = accessor.get(item, field); if(!groupValueComparer(groupValue, currentValue)) { groupValue = currentValue; group = { field: field, value: groupValue, items: [] }; result.push(group); } group.items.push(item); } return new Query(result); }, _sortForGrouping: function(field, dir) { var idx, length, data = this.data; if (!stableSort) { for (idx = 0, length = data.length; idx < length; idx++) { data[idx].__position = idx; } data = new Query(data).sort(field, dir, StableComparer).toArray(); for (idx = 0, length = data.length; idx < length; idx++) { delete data[idx].__position; } return data; } return this.sort(field, dir).toArray(); }, aggregate: function (aggregates) { var idx, len, result = {}; if (aggregates && aggregates.length) { for(idx = 0, len = this.data.length; idx < len; idx++) { calculateAggregate(result, aggregates, this.data[idx], idx, len); } } return result; } }; function groupValueComparer(a, b) { if (a && a.getTime && b && b.getTime) { return a.getTime() === b.getTime(); } return a === b; } function calculateAggregate(accumulator, aggregates, item, index, length) { aggregates = aggregates || []; var idx, aggr, functionName, len = aggregates.length; for (idx = 0; idx < len; idx++) { aggr = aggregates[idx]; functionName = aggr.aggregate; var field = aggr.field; accumulator[field] = accumulator[field] || {}; accumulator[field][functionName] = functions[functionName.toLowerCase()](accumulator[field][functionName], item, kendo.accessor(field), index, length); } } var functions = { sum: function(accumulator, item, accessor) { return (accumulator || 0) + accessor.get(item); }, count: function(accumulator) { return (accumulator || 0) + 1; }, average: function(accumulator, item, accessor, index, length) { accumulator = (accumulator || 0) + accessor.get(item); if(index == length - 1) { accumulator = accumulator / length; } return accumulator; }, max: function(accumulator, item, accessor) { var value = accessor.get(item); accumulator = accumulator || 0; if(accumulator < value) { accumulator = value; } return accumulator; }, min: function(accumulator, item, accessor) { var value = accessor.get(item); if (!isNumber(accumulator)) { accumulator = value; } if(accumulator > value && isNumber(value)) { accumulator = value; } return accumulator; } }; function isNumber(val) { return typeof val === "number" && !isNaN(val); } function toJSON(array) { var idx, length = array.length, result = new Array(length); for (idx = 0; idx < length; idx++) { result[idx] = array[idx].toJSON(); } return result; } Query.process = function(data, options) { options = options || {}; var query = new Query(data), group = options.group, sort = normalizeGroup(group || []).concat(normalizeSort(options.sort || [])), total, filter = options.filter, skip = options.skip, take = options.take; if (filter) { query = query.filter(filter); total = query.toArray().length; } if (sort) { query = query.sort(sort); if (group) { data = query.toArray(); } } if (skip !== undefined && take !== undefined) { query = query.range(skip, take); } if (group) { query = query.group(group, data); } return { total: total, data: query.toArray() }; }; function calculateAggregates(data, options) { options = options || {}; var query = new Query(data), aggregates = options.aggregate, filter = options.filter; if(filter) { query = query.filter(filter); } return query.aggregate(aggregates); } var LocalTransport = Class.extend({ init: function(options) { this.data = options.data; }, read: function(options) { options.success(this.data); }, update: function(options) { options.success(options.data); }, create: function(options) { options.success(options.data); }, destroy: function(options) { options.success(options.data); } }); var RemoteTransport = Class.extend( { init: function(options) { var that = this, parameterMap; options = that.options = extend({}, that.options, options); each(crud, function(index, type) { if (typeof options[type] === STRING) { options[type] = { url: options[type] }; } }); that.cache = options.cache? Cache.create(options.cache) : { find: noop, add: noop }; parameterMap = options.parameterMap; that.parameterMap = isFunction(parameterMap) ? parameterMap : function(options) { var result = {}; each(options, function(option, value) { if (option in parameterMap) { option = parameterMap[option]; if (isPlainObject(option)) { value = option.value(value); option = option.key; } } result[option] = value; }); return result; }; }, options: { parameterMap: identity }, create: function(options) { return ajax(this.setup(options, CREATE)); }, read: function(options) { var that = this, success, error, result, cache = that.cache; options = that.setup(options, READ); success = options.success || noop; error = options.error || noop; result = cache.find(options.data); if(result !== undefined) { success(result); } else { options.success = function(result) { cache.add(options.data, result); success(result); }; $.ajax(options); } }, update: function(options) { return ajax(this.setup(options, UPDATE)); }, destroy: function(options) { return ajax(this.setup(options, DESTROY)); }, setup: function(options, type) { options = options || {}; var that = this, parameters, operation = that.options[type], data = isFunction(operation.data) ? operation.data(options.data) : operation.data; options = extend(true, {}, operation, options); parameters = extend(true, {}, data, options.data); options.data = that.parameterMap(parameters, type); if (isFunction(options.url)) { options.url = options.url(parameters); } return options; } }); var Cache = Class.extend({ init: function() { this._store = {}; }, add: function(key, data) { if(key !== undefined) { this._store[stringify(key)] = data; } }, find: function(key) { return this._store[stringify(key)]; }, clear: function() { this._store = {}; }, remove: function(key) { delete this._store[stringify(key)]; } }); Cache.create = function(options) { var store = { "inmemory": function() { return new Cache(); } }; if (isPlainObject(options) && isFunction(options.find)) { return options; } if (options === true) { return new Cache(); } return store[options](); }; function serializeRecords(data, getters, modelInstance, originalFieldNames, fieldNames) { var record, getter, originalName, idx, length; for (idx = 0, length = data.length; idx < length; idx++) { record = data[idx]; for (getter in getters) { originalName = fieldNames[getter]; if (originalName && originalName !== getter) { record[originalName] = getters[getter](record); delete record[getter]; } } } } function convertRecords(data, getters, modelInstance, originalFieldNames, fieldNames) { var record, getter, originalName, idx, length; for (idx = 0, length = data.length; idx < length; idx++) { record = data[idx]; for (getter in getters) { record[getter] = modelInstance._parse(getter, getters[getter](record)); originalName = fieldNames[getter]; if (originalName && originalName !== getter) { delete record[originalName]; } } } } function convertGroup(data, getters, modelInstance, originalFieldNames, fieldNames) { var record, idx, fieldName, length; for (idx = 0, length = data.length; idx < length; idx++) { record = data[idx]; fieldName = originalFieldNames[record.field]; if (fieldName && fieldName != record.field) { record.field = fieldName; } record.value = modelInstance._parse(record.field, record.value); if (record.hasSubgroups) { convertGroup(record.items, getters, modelInstance, originalFieldNames, fieldNames); } else { convertRecords(record.items, getters, modelInstance, originalFieldNames, fieldNames); } } } function wrapDataAccess(originalFunction, model, converter, getters, originalFieldNames, fieldNames) { return function(data) { data = originalFunction(data); if (data && !isEmptyObject(getters)) { if (toString.call(data) !== "[object Array]" && !(data instanceof ObservableArray)) { data = [data]; } converter(data, getters, new model(), originalFieldNames, fieldNames); } return data || []; }; } var DataReader = Class.extend({ init: function(schema) { var that = this, member, get, model, base; schema = schema || {}; for (member in schema) { get = schema[member]; that[member] = typeof get === STRING ? getter(get) : get; } base = schema.modelBase || Model; if (isPlainObject(that.model)) { that.model = model = base.define(that.model); } if (that.model) { var dataFunction = proxy(that.data, that), groupsFunction = proxy(that.groups, that), serializeFunction = proxy(that.serialize, that), originalFieldNames = {}, getters = {}, serializeGetters = {}, fieldNames = {}, shouldSerialize = false,