todomvc
Version:
> Helping you select an MV\* framework
1,737 lines (1,409 loc) • 118 kB
JavaScript
/*
* 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,