<!DOCTYPE html>
<html>
<head>
<link href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.common.min.css" rel="stylesheet">
<link href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="style.css" />
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="kendo.all.js"></script>
<script src="http://cdn.kendostatic.com/2013.1.319/js/cultures/kendo.culture.fr-BE.min.js"></script>
<script src="http://code.angularjs.org/1.1.5/angular.js"></script>
<script src="angular-kendo.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="myCtrl">
<h1>AngularJS + kendo</h1>
<form name="testForm">
<h2>Date picker</h2>
<div>
<input name="birthDate" id="birthDate" data-ng-model="model.birthDate"
type="text" required kendo-date-picker k-format="'dd/MM/yyyy'" k-rebind="model.minDate"
k-parse-formats="'yyyy-MM-dd'" k-min="model.minDate" k-max="model.maxDate" style="width:100px" />
<span data-ng-show="testForm.birthDate.$error.required" class="help-inline">*</span>
</div>
<h2>Numeric box</h2>
<div>
<p>
<input type="number" step="1" ng-model="model.min" />
<br/>
<input type="number" step="1" ng-model="model.max" />
<br/>
<input type="number" step="1" max="{{model.max}}" min="{{model.min}}" ng-model="val" />
</p>
<input name="val" id="val" data-ng-model="model.val" kendo-numeric-text-box
k-min="model.min" k-max="model.max" />
<!-- no readonly by properties, only by method -->
<span data-ng-show="testForm.val.$error.required" class="help-inline">*</span>
</div>
<div>selected date:{{model.birthDate}}</div>
<div>selected val:{{model.val}}</div>
<button data-ng-click="reset()">reset</button>
</form>
</body>
</html>
// Code goes here
var app = angular.module('app', ['kendo.directives']);
app.controller("myCtrl",['$scope', function ($scope) {
$scope.model={};
$scope.model.minDate=new Date("2000-01-01T00:00:00.000Z");
$scope.model.maxDate=new Date("2099-12-31T00:00:00.000Z");
$scope.model.birthDate=new Date("2013-07-18T00:00:00.000Z");
$scope.model.max=10;
$scope.model.min=1;
$scope.model.val=5;
$scope.reset=function(){
$scope.model.minDate=new Date("2010-01-01T00:00:00.000Z");
$scope.model.birthDate=new Date("2013-07-18T00:00:00.000Z");
$scope.model.max=10;
$scope.model.min=1;
$scope.model.val=5;
}
}]);
/* Styles go here */
'use strict';
// declare all the module
angular.module('kendo.directives', []);
angular.module('kendo.directives', [], ['$provide', function ($provide) {
// Iterate over the kendo.ui and kendo.dataviz.ui namespace objects to get the Kendo UI widgets adding
// them to the 'widgets' array.
var widgets = [];
angular.forEach([kendo.ui, kendo.dataviz && kendo.dataviz.ui], function (namespace) {
angular.forEach(namespace, function (value, key) {
// add all widgets
if (key.match(/^[A-Z]/) && key !== 'Widget') {
widgets.push("kendo" + key);
}
});
});
$provide.value('kendoWidgets', widgets);
}]);
angular.module('kendo.directives').factory('widgetFactory', ['$parse', '$log', 'kendoDecorator', function ($parse, $log, kendoDecorator) {
// k-* attributes that should not be $parsed or $evaluated by gatherOptions
var ignoredAttributes = {
kDataSource: true,
kOptions: true,
kRebind: true
};
var mixin = function (kendoWidget, scope, options, attrName, attrValue) {
// regexp for matching regular options attributes and event handler attributes
// The first matching group will be defined only when the attribute starts by k-on- for event handlers.
// The second matching group will contain the option name.
var matchExp = /k(On)?([A-Z].*)/;
// ignore attributes that do not map to widget configuration options
if (ignoredAttributes[attrName]) {
return;
}
var match = attrName.match(matchExp), optionName, fn;
if (match) {
// Lowercase the first letter to match the option name kendo expects.
optionName = match[2].charAt(0).toLowerCase() + match[2].slice(1);
if (match[1]) {
// This is an event handler attribute (k-on-*)
// Parse the expression so that it get evaluated later.
fn = $parse(attrValue);
// Add a kendo event listener to the options.
options[optionName] = function (e) {
// Make sure this gets invoked in the angularjs lifecycle.
if (scope.$root.$$phase === '$apply' || scope.$root.$$phase === '$digest') {
fn({ kendoEvent: e });
} else {
scope.$apply(function () {
// Invoke the parsed expression with a kendoEvent local that the expression can use.
fn(scope, { kendoEvent: e });
});
}
};
} else {
// Evaluate the angular expression and put its result in the widget's options object.
// Here we make a copy because the kendo widgets make changes to the objects passed in the options
// and kendo-refresh would not be able to refresh with the initial values otherwise.
options[optionName] = angular.copy(scope.$eval(attrValue));
if (options[optionName] === undefined && attrValue.match(/^\w*$/)) {
// if the user put a single word as the attribute value and the expression evaluates to undefined,
// she may have wanted to use a string literal.
$log.warn(kendoWidget + '\'s ' + attrName + ' attribute resolved to undefined. Maybe you meant to use a string literal like: \'' + attrValue + '\'?');
}
}
}
};
// Gather the options from defaults and from attributes
var gatherOptions = function (scope, element, attrs, kendoWidget) {
// TODO: add kendoDefaults value service and use it to get a base options object?
// var options = kendoDefaults[kendoWidget];
// make a deep clone of the options object provided by the k-options attribute, if any.
var options = angular.element.extend(true, {}, scope.$eval(attrs.kOptions));
// Mixin the data from the element's k-* attributes in the options
angular.forEach(attrs, function (value, name) {
mixin(kendoWidget, scope, options, name, value);
});
// The kDataSource directive sets the $kendoDataSource data on the element it is put on.
// A datasource set in this way takes precedence over the one that could have been provided in options object passed
// in the directive's attribute and that is used as the initial options object.
options.dataSource = element.inheritedData('$kendoDataSource') || options.dataSource;
// decorate options, if any decorators have been registered on this element or any global ones are registered for
// the kendo widget
kendoDecorator.decorateOptions(element, kendoWidget, options);
return options;
};
// Create the kendo widget with gathered options
var create = function (scope, element, attrs, kendoWidget) {
// Create the options object
var options = gatherOptions(scope, element, attrs, kendoWidget);
// Bind the kendo widget to the element and return a reference to the widget.
return element[kendoWidget](options).data(kendoWidget);
};
return {
create: create
};
}]);
angular.module('kendo.directives').factory('directiveFactory', ['widgetFactory', '$timeout', '$parse',
function (widgetFactory, $timeout, $parse) {
function exposeWidget(widget, scope, attrs, kendoWidget) {
if (attrs[kendoWidget]) {
// expose the widget object
var set = $parse(attrs[kendoWidget]).assign;
if (set) {
// set the value of the expression to the kendo widget object to expose its api
set(scope, widget);
} else {
throw new Error(kendoWidget + ' attribute used but expression in it is not assignable: ' + attrs[kendoWidget]);
}
}
}
var create = function (kendoWidget) {
return {
// Parse the directive for attributes and classes
restrict: 'ACE',
transclude: true,
require: '?ngModel',
scope: false,
controller: ['$scope', '$attrs', '$element', '$transclude', function ($scope, $attrs, $element, $transclude) {
// Make the element's contents available to the kendo widget to allow creating some widgets from existing elements.
$transclude(function (clone) {
$element.append(clone);
});
}],
link: function (scope, element, attrs, ngModel) {
var widget;
// Bind kendo widget to element only once interpolation on attributes is done.
// Using a $timeout with no delay simply makes sure the function will be executed next in the event queue
// after the current $digest cycle is finished. Other directives on the same element (select for example)
// will have been processed, and interpolation will have happened on the attributes.
$timeout(function () {
// create the kendo widget and bind it to the element.
widget = widgetFactory.create(scope, element, attrs, kendoWidget);
exposeWidget(widget, scope, attrs, kendoWidget);
// if k-rebind attribute is provided, rebind the kendo widget when
// the watched value changes
if (attrs.kRebind) {
// watch for changes on the expression passed in the k-rebind attribute
scope.$watch(attrs.kRebind, function (newValue, oldValue) {
if (newValue !== oldValue) {
// create the kendo widget and bind it to the element.
widget = widgetFactory.create(scope, element, attrs, kendoWidget);
exposeWidget(widget, scope, attrs, kendoWidget);
}
}, true); // watch for object equality. Use native or simple values.
}
// Cleanup after ourselves
scope.$on('$destroy', function () {
widget.destroy();
});
// if ngModel is on the element, we setup bi-directional data binding
if (ngModel) {
if (!widget.value) {
throw new Error('ng-model used but ' + kendoWidget + ' does not define a value accessor');
}
// Angular will invoke $render when the view needs to be updated with the view value.
ngModel.$render = function () {
// Update the widget with the view value.
widget.value(ngModel.$viewValue || null);
};
// if the model value is undefined, then we set the widget value to match ( == null/undefined )
if (widget.value !== undefined) {
widget.value(ngModel.$viewValue || null);
}
// In order to be able to update the angular scope objects, we need to know when the change event is fired for a Kendo UI Widget.
widget.bind("change", function (e) {
if (scope.$root.$$phase === '$apply' || scope.$root.$$phase === '$digest') {
ngModel.$setViewValue(widget.value());
} else {
scope.$apply(function () {
ngModel.$setViewValue(widget.value());
});
}
});
}
});
}
};
};
return {
create: create
};
}
]);
(function (angular) {
var widgets = angular.injector(['kendo.directives']).get('kendoWidgets');
// loop through all the widgets and create a directive
angular.forEach(widgets, function (widget) {
angular.module('kendo.directives').directive(widget, ['directiveFactory',
function (directiveFactory) {
return directiveFactory.create(widget);
}
]);
});
}(angular));
// ## The kendoSource directive allows setting the Kendo UI DataSource of a widget directly from the HTML.
angular.module('kendo.directives').directive('kDataSource', [function () {
// Transforms the object into a Kendo UI DataSource.
var toDataSource = function (ds) {
// TODO: if ds is a $resource, wrap it in a kendo dataSource using an injected service
return kendo.data.DataSource.create(ds);
};
var widgetInstance = function ($element) {
return kendo.widgetInstance($element, kendo.ui) || kendo.widgetInstance($element, kendo.dataviz.ui);
};
return {
// This is an attribute directive
restrict: 'A',
controller: ['$scope', '$attrs', '$element', function ($scope, $attrs, $element) {
// can we intercept the data source
var ds = $scope.$eval($attrs.kDataSource);
// Set $kendoDataSource in the element's data. 3rd parties can define their own dataSource creation
// directive and provide this data on the element.
$element.data('$kendoDataSource', toDataSource(ds));
// if this data source is set to an array, we need to add a watch to keep the widget in sync
if ($.isArray(ds)) {
// add a watch to the array (value comparison: true)
$scope.$watch($attrs.kDataSource, function (oldVal, newVal) {
// if the array values differ
if (oldVal !== newVal) {
// get a reference to the widget
var widget = widgetInstance($element);
// if the widget exists and has a dataSource
if (widget && widget.dataSource) {
// read the data again which updates the widget
widget.dataSource.read();
}
}
}, true);
}
}]
};
}]);
angular.module('kendo.directives').directive('kendoGrid', ['$compile', 'kendoDecorator', '$parse', function ($compile, kendoDecorator, $parse) {
function dataBoundHandler(scope, element, rowDataVar) {
var grid = element.data('kendoGrid');
var rows = grid.tbody.children('tr');
// lnu:create scope toolbar if defined //
var toolbar = element.find(".k-grid-toolbar");
if (toolbar.children().length) {
//hack waiting for proper toolbar created event
toolbar.children().andSelf().unbind();
var toolbarScope = scope.$new();
$compile(toolbar)(toolbarScope);
}
// lnu //
// Here we mimic ng-repeat in that we create a scope for each row that we can then destroy in dataBinding event.
// Creating a scope for each row ensures you don't leak scopes when the
// kendo widget regenerates the dom on pagination for example.
rows.each(function (index, row) {
var rowScope = scope.$new();
// provide index of the row using the same $index var as ngRepeat
rowScope.$index = index;
// provide the data object for that row in the scope
rowScope[rowDataVar] = grid.dataItem(row);
// compile the row. You can now use angular templates in that row.
$compile(row)(rowScope);
});
}
function dataBindingHandler(element) {
// lnu:create scope toolbar if defined //
var toolbar = element.find(".k-grid-toolbar.ng-scope");
if (toolbar.length === 1) {
var rowScope = angular.element(toolbar).scope();
// destroy the scope
rowScope.$destroy();
}
// lnu //
// find all the rows that we compiled in dataBound handler
var rows = element.data('kendoGrid').tbody.children('tr.ng-scope');
// here we need to destroy the scopes that we created in dataBound handler to make sure no scopes are leaked.
rows.each(function (index, rowElement) {
var rowScope = angular.element(rowElement).scope();
// destroy the scope
rowScope.$destroy();
});
}
function createCompileRowsDecorator(scope, rowDataVar) {
return function (element, options) {
// keep a reference on the original event callbacks
var origDataBinding = options.dataBinding;
var origDataBound = options.dataBound;
// The kendoGrid invokes this handler after it has created row elements for the data.
options.dataBound = function () {
dataBoundHandler(scope, element, rowDataVar);
// invoke the original dataBound handler, if any
if (angular.isFunction(origDataBound)) {
origDataBound();
}
};
// The kendoGrid invokes this handler before it creates new rows in the dom
options.dataBinding = function () {
dataBindingHandler(element);
// invoke the original dataBinding handler, if any
if (angular.isFunction(origDataBinding)) {
origDataBinding();
}
};
};
}
function createChangeDecorator(scope, changeExpFn) {
return function (element, options) {
options.change = function (e) {
var cell, multiple, locals = { kendoEvent: e }, elems, items, columns, colIdx;
if (angular.isString(options.selectable)) {
cell = options.selectable.indexOf('cell') !== -1;
multiple = options.selectable.indexOf('multiple') !== -1;
}
elems = locals.selected = this.select();
items = locals.data = [];
columns = locals.columns = [];
for (var i = 0; i < elems.length; i++) {
var dataItem = this.dataItem(cell ? elems[i].parentNode : elems[i]);
if (cell) {
if (angular.element.inArray(dataItem, items) < 0) {
items.push(dataItem);
}
colIdx = angular.element(elems[i]).index();
if (angular.element.inArray(colIdx, columns) < 0) {
columns.push(colIdx);
}
} else {
items.push(dataItem);
}
}
if (!multiple) {
locals.data = items[0];
locals.selected = elems[0];
}
// Make sure this gets invoked in the angularjs lifecycle.
scope.$apply(function () {
// Invoke the parsed expression with a kendoEvent local that the expression can use.
changeExpFn(scope, locals);
});
};
};
}
return {
restrict: 'ACE',
link: function (scope, element, attrs) {
kendoDecorator.addOptionsDecorator(element, createCompileRowsDecorator(scope, 'dataItem'));
// if k-on-change was defined, expose the selected rows/cells and not just the kendo event
if (attrs.kOnChange) {
kendoDecorator.addOptionsDecorator(element, createChangeDecorator(scope, $parse(attrs.kOnChange)));
}
}
};
}]);
angular.module('kendo.directives').provider('kendoDecorator', [function () {
var provider = this, DECORATORS = '$kendoOptionsDecorators';
var globalOptionsDecorators = {};
// add an options decorator to be applied on all specified widget, not specific instances
provider.addGlobalOptionsDecorator = function (widgetName, decorator) {
if (angular.isString(widgetName) && angular.isFunction(decorator)) {
globalOptionsDecorators[widgetName] = globalOptionsDecorators[widgetName] || [];
globalOptionsDecorators[widgetName].push(decorator);
return function () {
globalOptionsDecorators[widgetName].splice(globalOptionsDecorators[widgetName].indexOf(decorator), 1);
};
}
throw new Error('Illegal Arguments');
};
// get the global list of options decorators for a specified widget. Useful for ordering
provider.getGlobalOptionsDecorator = function (widgetName) {
return globalOptionsDecorators[widgetName] || [];
};
provider.$get = [function () {
// returns the decorators associated to the specified element
function getOptionsDecorators(element) {
var decorators = element.data(DECORATORS);
if (!angular.isArray(decorators)) {
decorators = [];
element.data(DECORATORS, decorators);
}
return decorators;
}
function invokeDecorators(element, decorators, opts) {
for (var i = 0; i < decorators.length; i++) {
decorators[i](element, opts);
}
}
// invokes the provided element's decorators and global operators on the provided options object.
function decorateOptions(element, widgetName, opts) {
var i, decorators = provider.getGlobalOptionsDecorator(widgetName);
invokeDecorators(element, decorators, opts);
// get decorators for element
decorators = element.data(DECORATORS) || [];
invokeDecorators(element, decorators, opts);
}
function addOptionsDecorator(element, decorator) {
if (angular.isFunction(decorator)) {
var decorators = getOptionsDecorators(element);
decorators.push(decorator);
return function () {
decorators.splice(decorators.indexOf(decorator), 1);
};
}
}
return {
getOptionsDecorator: getOptionsDecorators,
addOptionsDecorator: addOptionsDecorator,
decorateOptions: decorateOptions
};
}];
}]);
/*
* Kendo UI Complete v2013.1.319 (http://kendoui.com)
* Copyright 2013 Telerik AD. All rights reserved.
*
* Kendo UI Complete commercial licenses may be obtained at
* https://www.kendoui.com/purchase/license-agreement/kendo-ui-complete-commercial.aspx
* If you do not own a commercial license, this file shall be governed by the trial license terms.
*/
(function($, evil, undefined) {
var kendo = window.kendo = window.kendo || {}, extend = $.extend, each = $.each, proxy = $.proxy, isArray = $.isArray, noop = $.noop, isFunction = $.isFunction, math = Math, Template, JSON = window.JSON || {}, support = {}, percentRegExp = /%/, formatRegExp = /\{(\d+)(:[^\}]+)?\}/g, boxShadowRegExp = /(\d+?)px\s*(\d+?)px\s*(\d+?)px\s*(\d+?)?/i, FUNCTION = "function", STRING = "string", NUMBER = "number", OBJECT = "object", NULL = "null", BOOLEAN = "boolean", UNDEFINED = "undefined", getterCache = {}, setterCache = {}, slice = [].slice, globalize = window.Globalize;
function Class() {}
Class.extend = function(proto) {
var base = function() {}, member, that = this, subclass = proto && proto.init ? proto.init : function() {
that.apply(this, arguments);
}, fn;
base.prototype = that.prototype;
fn = subclass.fn = subclass.prototype = new base();
for (member in proto) {
if (typeof proto[member] === OBJECT && !(proto[member] instanceof Array) && proto[member] !== null) {
// Merge object members
fn[member] = extend(true, {}, base.prototype[member], proto[member]);
} else {
fn[member] = proto[member];
}
}
fn.constructor = subclass;
subclass.extend = that.extend;
return subclass;
};
var preventDefault = function() {
this._defaultPrevented = true;
};
var isDefaultPrevented = function() {
return this._defaultPrevented === true;
};
var Observable = Class.extend({
init: function() {
this._events = {};
},
bind: function(eventName, handlers, one) {
var that = this, idx, eventNames = typeof eventName === STRING ? [ eventName ] : eventName, length, original, handler, handlersIsFunction = typeof handlers === FUNCTION, events;
if (handlers === undefined) {
for (idx in eventName) {
that.bind(idx, eventName[idx]);
}
return that;
}
for (idx = 0, length = eventNames.length; idx < length; idx++) {
eventName = eventNames[idx];
handler = handlersIsFunction ? handlers : handlers[eventName];
if (handler) {
if (one) {
original = handler;
handler = function() {
that.unbind(eventName, handler);
original.apply(that, arguments);
};
}
events = that._events[eventName] = that._events[eventName] || [];
events.push(handler);
}
}
return that;
},
one: function(eventNames, handlers) {
return this.bind(eventNames, handlers, true);
},
first: function(eventName, handlers) {
var that = this, idx, eventNames = typeof eventName === STRING ? [ eventName ] : eventName, length, handler, handlersIsFunction = typeof handlers === FUNCTION, events;
for (idx = 0, length = eventNames.length; idx < length; idx++) {
eventName = eventNames[idx];
handler = handlersIsFunction ? handlers : handlers[eventName];
if (handler) {
events = that._events[eventName] = that._events[eventName] || [];
events.unshift(handler);
}
}
return that;
},
trigger: function(eventName, e) {
var that = this, events = that._events[eventName], idx, length;
if (events) {
e = e || {};
e.sender = that;
e._defaultPrevented = false;
e.preventDefault = preventDefault;
e.isDefaultPrevented = isDefaultPrevented;
events = events.slice();
for (idx = 0, length = events.length; idx < length; idx++) {
events[idx].call(that, e);
}
return e._defaultPrevented === true;
}
return false;
},
unbind: function(eventName, handler) {
var that = this, events = that._events[eventName], idx, length;
if (eventName === undefined) {
that._events = {};
} else if (events) {
if (handler) {
for (idx = 0, length = events.length; idx < length; idx++) {
if (events[idx] === handler) {
events.splice(idx, 1);
}
}
} else {
that._events[eventName] = [];
}
}
return that;
}
});
function compilePart(part, stringPart) {
if (stringPart) {
return "'" + part.split("'").join("\\'").split('\\"').join('\\\\\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t") + "'";
} else {
var first = part.charAt(0), rest = part.substring(1);
if (first === "=") {
return "+(" + rest + ")+";
} else if (first === ":") {
return "+e(" + rest + ")+";
} else {
return ";" + part + ";o+=";
}
}
}
var argumentNameRegExp = /^\w+/, encodeRegExp = /\$\{([^}]*)\}/g, escapedCurlyRegExp = /\\\}/g, curlyRegExp = /__CURLY__/g, escapedSharpRegExp = /\\#/g, sharpRegExp = /__SHARP__/g, zeros = [ "", "0", "00", "000", "0000" ];
Template = {
paramName: "data",
// name of the parameter of the generated template
useWithBlock: true,
// whether to wrap the template in a with() block
render: function(template, data) {
var idx, length, html = "";
for (idx = 0, length = data.length; idx < length; idx++) {
html += template(data[idx]);
}
return html;
},
compile: function(template, options) {
var settings = extend({}, this, options), paramName = settings.paramName, argumentName = paramName.match(argumentNameRegExp)[0], useWithBlock = settings.useWithBlock, functionBody = "var o,e=kendo.htmlEncode;", parts, idx;
if (isFunction(template)) {
if (template.length === 2) {
//looks like jQuery.template
return function(d) {
return template($, {
data: d
}).join("");
};
}
return template;
}
functionBody += useWithBlock ? "with(" + paramName + "){" : "";
functionBody += "o=";
parts = template.replace(escapedCurlyRegExp, "__CURLY__").replace(encodeRegExp, "#=e($1)#").replace(curlyRegExp, "}").replace(escapedSharpRegExp, "__SHARP__").split("#");
for (idx = 0; idx < parts.length; idx++) {
functionBody += compilePart(parts[idx], idx % 2 === 0);
}
functionBody += useWithBlock ? ";}" : ";";
functionBody += "return o;";
functionBody = functionBody.replace(sharpRegExp, "#");
try {
return new Function(argumentName, functionBody);
} catch (e) {
throw new Error(kendo.format("Invalid template:'{0}' Generated code:'{1}'", template, functionBody));
}
}
};
function pad(number, digits, end) {
number = number + "";
digits = digits || 2;
end = digits - number.length;
if (end) {
return zeros[digits].substring(0, end) + number;
}
return number;
}
//JSON stringify
(function() {
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = {
"\b": "\\b",
" ": "\\t",
"\n": "\\n",
"\f": "\\f",
"\r": "\\r",
'"': '\\"',
"\\": "\\\\"
}, rep, toString = {}.toString;
if (typeof Date.prototype.toJSON !== FUNCTION) {
Date.prototype.toJSON = function() {
var that = this;
return isFinite(that.valueOf()) ? pad(that.getUTCFullYear(), 4) + "-" + pad(that.getUTCMonth() + 1) + "-" + pad(that.getUTCDate()) + "T" + pad(that.getUTCHours()) + ":" + pad(that.getUTCMinutes()) + ":" + pad(that.getUTCSeconds()) + "Z" : null;
};
String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function() {
return this.valueOf();
};
}
function quote(string) {
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function(a) {
var c = meta[a];
return typeof c === STRING ? c : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
var i, k, v, length, mind = gap, partial, value = holder[key], type;
if (value && typeof value === OBJECT && typeof value.toJSON === FUNCTION) {
value = value.toJSON(key);
}
if (typeof rep === FUNCTION) {
value = rep.call(holder, key, value);
}
type = typeof value;
if (type === STRING) {
return quote(value);
} else if (type === NUMBER) {
return isFinite(value) ? String(value) : NULL;
} else if (type === BOOLEAN || type === NULL) {
return String(value);
} else if (type === OBJECT) {
if (!value) {
return NULL;
}
gap += indent;
partial = [];
if (toString.apply(value) === "[object Array]") {
length = value.length;
for (i = 0; i < length; i++) {
partial[i] = str(i, value) || NULL;
}
v = partial.length === 0 ? "[]" : gap ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" : "[" + partial.join(",") + "]";
gap = mind;
return v;
}
if (rep && typeof rep === OBJECT) {
length = rep.length;
for (i = 0; i < length; i++) {
if (typeof rep[i] === STRING) {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ": " : ":") + v);
}
}
}
} else {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ": " : ":") + v);
}
}
}
}
v = partial.length === 0 ? "{}" : gap ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" : "{" + partial.join(",") + "}";
gap = mind;
return v;
}
}
if (typeof JSON.stringify !== FUNCTION) {
JSON.stringify = function(value, replacer, space) {
var i;
gap = "";
indent = "";
if (typeof space === NUMBER) {
for (i = 0; i < space; i += 1) {
indent += " ";
}
} else if (typeof space === STRING) {
indent = space;
}
rep = replacer;
if (replacer && typeof replacer !== FUNCTION && (typeof replacer !== OBJECT || typeof replacer.length !== NUMBER)) {
throw new Error("JSON.stringify");
}
return str("", {
"": value
});
};
}
})();
// Date and Number formatting
(function() {
var dateFormatRegExp = /dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|HH|H|hh|h|mm|m|fff|ff|f|tt|ss|s|"[^"]*"|'[^']*'/g, standardFormatRegExp = /^(n|c|p|e)(\d*)$/i, literalRegExp = /["'].*?["']/g, commaRegExp = /\,/g, EMPTY = "", POINT = ".", COMMA = ",", SHARP = "#", ZERO = "0", PLACEHOLDER = "??", EN = "en-US";
//cultures
kendo.cultures = {
"en-US": {
name: EN,
numberFormat: {
pattern: [ "-n" ],
decimals: 2,
",": ",",
".": ".",
groupSize: [ 3 ],
percent: {
pattern: [ "-n %", "n %" ],
decimals: 2,
",": ",",
".": ".",
groupSize: [ 3 ],
symbol: "%"
},
currency: {
pattern: [ "($n)", "$n" ],
decimals: 2,
",": ",",
".": ".",
groupSize: [ 3 ],
symbol: "$"
}
},
calendars: {
standard: {
days: {
names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ]
},
months: {
names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
},
AM: [ "AM", "am", "AM" ],
PM: [ "PM", "pm", "PM" ],
patterns: {
d: "M/d/yyyy",
D: "dddd, MMMM dd, yyyy",
F: "dddd, MMMM dd, yyyy h:mm:ss tt",
g: "M/d/yyyy h:mm tt",
G: "M/d/yyyy h:mm:ss tt",
m: "MMMM dd",
M: "MMMM dd",
s: "yyyy'-'MM'-'ddTHH':'mm':'ss",
t: "h:mm tt",
T: "h:mm:ss tt",
u: "yyyy'-'MM'-'dd HH':'mm':'ss'Z'",
y: "MMMM, yyyy",
Y: "MMMM, yyyy"
},
"/": "/",
":": ":",
firstDay: 0,
twoDigitYearMax: 2029
}
}
}
};
function findCulture(culture) {
if (culture) {
if (culture.numberFormat) {
return culture;
}
if (typeof culture === STRING) {
var cultures = kendo.cultures;
return cultures[culture] || cultures[culture.split("-")[0]] || null;
}
return null;
}
return null;
}
function getCulture(culture) {
if (culture) {
culture = findCulture(culture);
}
return culture || kendo.cultures.current;
}
function expandNumberFormat(numberFormat) {
numberFormat.groupSizes = numberFormat.groupSize;
numberFormat.percent.groupSizes = numberFormat.percent.groupSize;
numberFormat.currency.groupSizes = numberFormat.currency.groupSize;
}
kendo.culture = function(cultureName) {
var cultures = kendo.cultures, culture;
if (cultureName !== undefined) {
culture = findCulture(cultureName) || cultures[EN];
culture.calendar = culture.calendars.standard;
cultures.current = culture;
if (globalize) {
expandNumberFormat(culture.numberFormat);
}
} else {
return cultures.current;
}
};
kendo.findCulture = findCulture;
kendo.getCulture = getCulture;
//set current culture to en-US.
kendo.culture(EN);
function formatDate(date, format, culture) {
culture = getCulture(culture);
var calendar = culture.calendars.standard, days = calendar.days, months = calendar.months;
format = calendar.patterns[format] || format;
return format.replace(dateFormatRegExp, function(match) {
var result;
if (match === "d") {
result = date.getDate();
} else if (match === "dd") {
result = pad(date.getDate());
} else if (match === "ddd") {
result = days.namesAbbr[date.getDay()];
} else if (match === "dddd") {
result = days.names[date.getDay()];
} else if (match === "M") {
result = date.getMonth() + 1;
} else if (match === "MM") {
result = pad(date.getMonth() + 1);
} else if (match === "MMM") {
result = months.namesAbbr[date.getMonth()];
} else if (match === "MMMM") {
result = months.names[date.getMonth()];
} else if (match === "yy") {
result = pad(date.getFullYear() % 100);
} else if (match === "yyyy") {
result = pad(date.getFullYear(), 4);
} else if (match === "h") {
result = date.getHours() % 12 || 12;
} else if (match === "hh") {
result = pad(date.getHours() % 12 || 12);
} else if (match === "H") {
result = date.getHours();
} else if (match === "HH") {
result = pad(date.getHours());
} else if (match === "m") {
result = date.getMinutes();
} else if (match === "mm") {
result = pad(date.getMinutes());
} else if (match === "s") {
result = date.getSeconds();
} else if (match === "ss") {
result = pad(date.getSeconds());
} else if (match === "f") {
result = math.floor(date.getMilliseconds() / 100);
} else if (match === "ff") {
result = math.floor(date.getMilliseconds() / 10);
} else if (match === "fff") {
result = date.getMilliseconds();
} else if (match === "tt") {
result = date.getHours() < 12 ? calendar.AM[0] : calendar.PM[0];
}
return result !== undefined ? result : match.slice(1, match.length - 1);
});
}
//number formatting
function formatNumber(number, format, culture) {
culture = getCulture(culture);
var numberFormat = culture.numberFormat, groupSize = numberFormat.groupSize[0], groupSeparator = numberFormat[COMMA], decimal = numberFormat[POINT], precision = numberFormat.decimals, pattern = numberFormat.pattern[0], literals = [], symbol, isCurrency, isPercent, customPrecision, formatAndPrecision, negative = number < 0, integer, fraction, integerLength, fractionLength, replacement = EMPTY, value = EMPTY, idx, length, ch, hasGroup, hasNegativeFormat, decimalIndex, sharpIndex, zeroIndex, hasZero, hasSharp, percentIndex, currencyIndex, startZeroIndex, start = -1, end;
//return empty string if no number
if (number === undefined) {
return EMPTY;
}
if (!isFinite(number)) {
return number;
}
//if no format then return number.toString() or number.toLocaleString() if culture.name is not defined
if (!format) {
return culture.name.length ? number.toLocaleString() : number.toString();
}
formatAndPrecision = standardFormatRegExp.exec(format);
// standard formatting
if (formatAndPrecision) {
format = formatAndPrecision[1].toLowerCase();
isCurrency = format === "c";
isPercent = format === "p";
if (isCurrency || isPercent) {
//get specific number format information if format is currency or percent
numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
groupSize = numberFormat.groupSize[0];
groupSeparator = numberFormat[COMMA];
decimal = numberFormat[POINT];
precision = numberFormat.decimals;
symbol = numberFormat.symbol;
pattern = numberFormat.pattern[negative ? 0 : 1];
}
customPrecision = formatAndPrecision[2];
if (customPrecision) {
precision = +customPrecision;
}
//return number in exponential format
if (format === "e") {
return customPrecision ? number.toExponential(precision) : number.toExponential();
}
// multiply if format is percent
if (isPercent) {
number *= 100;
}
number = number.toFixed(precision);
number = number.split(POINT);
integer = number[0];
fraction = number[1];
//exclude "-" if number is negative.
if (negative) {
integer = integer.substring(1);
}
value = integer;
integerLength = integer.length;
//add group separator to the number if it is longer enough
if (integerLength >= groupSize) {
value = EMPTY;
for (idx = 0; idx < integerLength; idx++) {
if (idx > 0 && (integerLength - idx) % groupSize === 0) {
value += groupSeparator;
}
value += integer.charAt(idx);
}
}
if (fraction) {
value += decimal + fraction;
}
if (format === "n" && !negative) {
return value;
}
number = EMPTY;
for (idx = 0, length = pattern.length; idx < length; idx++) {
ch = pattern.charAt(idx);
if (ch === "n") {
number += value;
} else if (ch === "$" || ch === "%") {
number += symbol;
} else {
number += ch;
}
}
return number;
}
//custom formatting
//
//separate format by sections.
//make number positive
if (negative) {
number = -number;
}
format = format.split(";");
if (negative && format[1]) {
//get negative format
format = format[1];
hasNegativeFormat = true;
} else if (number === 0) {
//format for zeros
format = format[2] || format[0];
if (format.indexOf(SHARP) == -1 && format.indexOf(ZERO) == -1) {
//return format if it is string constant.
return format;
}
} else {
format = format[0];
}
if (format.indexOf("'") > -1 || format.indexOf('"') > -1) {
format = format.replace(literalRegExp, function(match) {
literals.push(match);
return PLACEHOLDER;
});
}
percentIndex = format.indexOf("%");
currencyIndex = format.indexOf("$");
isPercent = percentIndex != -1;
isCurrency = currencyIndex != -1;
//multiply number if the format has percent
if (isPercent) {
if (format[percentIndex - 1] !== "\\") {
number *= 100;
} else {
format = format.split("\\").join("");
}
}
if (isCurrency && format[currencyIndex - 1] === "\\") {
format = format.split("\\").join("");
isCurrency = false;
}
if (isCurrency || isPercent) {
//get specific number format information if format is currency or percent
numberFormat = isCurrency ? numberFormat.currency : numberFormat.percent;
groupSize = numberFormat.groupSize[0];
groupSeparator = numberFormat[COMMA];
decimal = numberFormat[POINT];
precision = numberFormat.decimals;
symbol = numberFormat.symbol;
}
hasGroup = format.indexOf(COMMA) > -1;
if (hasGroup) {
format = format.replace(commaRegExp, EMPTY);
}
decimalIndex = format.indexOf(POINT);
length = format.length;
if (decimalIndex != -1) {
zeroIndex = format.lastIndexOf(ZERO) - decimalIndex;
sharpIndex = format.lastIndexOf(SHARP) - decimalIndex;
fraction = number.toString().split(POINT)[1] || EMPTY;
hasZero = zeroIndex > -1;
hasSharp = sharpIndex > -1;
idx = fraction.length;
if (!hasZero && !hasSharp) {
format = format.substring(0, decimalIndex) + format.substring(decimalIndex + 1);
length = format.length;
decimalIndex = -1;
idx = 0;
}
if (hasZero && zeroIndex > sharpIndex) {
idx = zeroIndex;
} else if (sharpIndex > zeroIndex) {
if (hasSharp && idx > sharpIndex) {
idx = sharpIndex;
} else if (hasZero && idx < zeroIndex) {
idx = zeroIndex;
}
}
if (idx > -1) {
number = number.toFixed(idx);
}
} else {
number = number.toFixed(0);
}
sharpIndex = format.indexOf(SHARP);
startZeroIndex = zeroIndex = format.indexOf(ZERO);
//define the index of the first digit placeholder
if (sharpIndex == -1 && zeroIndex != -1) {
start = zeroIndex;
} else if (sharpIndex != -1 && zeroIndex == -1) {
start = sharpIndex;
} else {
start = sharpIndex > zeroIndex ? zeroIndex : sharpIndex;
}
sharpIndex = format.lastIndexOf(SHARP);
zeroIndex = format.lastIndexOf(ZERO);
//define the index of the last digit placeholder
if (sharpIndex == -1 && zeroIndex != -1) {
end = zeroIndex;
} else if (sharpIndex != -1 && zeroIndex == -1) {
end = sharpIndex;
} else {
end = sharpIndex > zeroIndex ? sharpIndex : zeroIndex;
}
if (start == length) {
end = start;
}
if (start != -1) {
value = number.toString().split(POINT);
integer = value[0];
fraction = value[1] || EMPTY;
integerLength = integer.length;
fractionLength = fraction.length;
//add group separator to the number if it is longer enough
if (hasGroup) {
if (integerLength === groupSize && integerLength < decimalIndex - startZeroIndex) {
integer = groupSeparator + integer;
} else if (integerLength > groupSize) {
value = EMPTY;
for (idx = 0; idx < integerLength; idx++) {
if (idx > 0 && (integerLength - idx) % groupSize === 0) {
value += groupSeparator;
}
value += integer.charAt(idx);
}
integer = value;
}
}
number = format.substring(0, start);
if (negative && !hasNegativeFormat) {
number += "-";
}
for (idx = start; idx < length; idx++) {
ch = format.charAt(idx);
if (decimalIndex == -1) {
if (end - idx < integerLength) {
number += integer;
break;
}
} else {
if (zeroIndex != -1 && zeroIndex < idx) {
replacement = EMPTY;
}
if (decimalIndex - idx <= integerLength && decimalIndex - idx > -1) {
number += integer;
idx = decimalIndex;
}
if (decimalIndex === idx) {
number += (fraction ? decimal : EMPTY) + fraction;
idx += end - decimalIndex + 1;
continue;
}
}
if (ch === ZERO) {
number += ch;
replacement = ch;
} else if (ch === SHARP) {
number += replacement;
}
}
if (end >= start) {
number += format.substring(end + 1);
}
//replace symbol placeholders
if (isCurrency || isPercent) {
value = EMPTY;
for (idx = 0, length = number.length; idx < length; idx++) {
ch = number.charAt(idx);
value += ch === "$" || ch === "%" ? symbol : ch;
}
number = value;
}
if (literals[0]) {
length = literals.length;
for (idx = 0; idx < length; idx++) {
number = number.replace(PLACEHOLDER, literals[idx]);
}
}
}
return number;
}
var toString = function(value, fmt, culture) {
if (fmt) {
if (value instanceof Date) {
return formatDate(value, fmt, culture);
} else if (typeof value === NUMBER) {
return formatNumber(value, fmt, culture);
}
}
return value !== undefined ? value : "";
};
if (globalize) {
toString = proxy(globalize.format, globalize);
}
kendo.format = function(fmt) {
var values = arguments;
return fmt.replace(formatRegExp, function(match, index, placeholderFormat) {
var value = values[parseInt(index, 10) + 1];
return toString(value, placeholderFormat ? placeholderFormat.substring(1) : "");
});
};
kendo._extractFormat = function(format) {
if (format.slice(0, 3) === "{0:") {
format = format.slice(3, format.length - 1);
}
return format;
};
kendo._activeElement = function() {
try {
return document.activeElement;
} catch (e) {
return document.documentElement.activeElement;
}
};
kendo.toString = toString;
})();
(function() {
var nonBreakingSpaceRegExp = /\u00A0/g, exponentRegExp = /[eE][\-+]?[0-9]+/, shortTimeZoneRegExp = /[+|\-]\d{1,2}/, longTimeZoneRegExp = /[+|\-]\d{1,2}:\d{2}/, dateRegExp = /^\/Date\((.*?)\)\/$/, formatsSequence = [ "G", "g", "d", "F", "D", "y", "m", "T", "t" ], numberRegExp = {
2: /^\d{1,2}/,
4: /^\d{4}/
};
function outOfRange(value, start, end) {
return !(value >= start && value <= end);
}
function designatorPredicate(designator) {
return designator.charAt(0);
}
function mapDesignators(designators) {
return $.map(designators, designatorPredicate);
}
//if date's day is different than the typed one - adjust
function adjustDate(date, hours) {
if (!hours && date.getHours() === 23) {
date.setHours(date.getHours() + 2);
}
}
function parseExact(value, format, culture) {
if (!value) {
return null;
}
var lookAhead = function(match) {
var i = 0;
while (format[idx] === match) {
i++;
idx++;
}
if (i > 0) {
idx -= 1;
}
return i;
}, getNumber = function(size) {
var rg = numberRegExp[size] || new RegExp("^\\d{1," + size + "}"), match = value.substr(valueIdx, size).match(rg);
if (match) {
match = match[0];
valueIdx += match.length;
return parseInt(match, 10);
}
return null;
}, getIndexByName = function(names) {
var i = 0, length = names.length, name, nameLength;
for (;i < length; i++) {
name = names[i];
nameLength = name.length;
if (value.substr(valueIdx, nameLength) == name) {
valueIdx += nameLength;
return i + 1;
}
}
return null;
}, checkLiteral = function() {
var result = false;
if (value.charAt(valueIdx) === format[idx]) {
valueIdx++;
result = true;
}
return result;
}, calendar = culture.calendars.standard, year = null, month = null, day = null, hours = null, minutes = null, seconds = null, milliseconds = null, idx = 0, valueIdx = 0, literal = false, date = new Date(), twoDigitYearMax = calendar.twoDigitYearMax || 2029, defaultYear = date.getFullYear(), ch, count, length, pattern, pmHour, UTC, ISO8601, matches, amDesignators, pmDesignators, hoursOffset, minutesOffset;
if (!format) {
format = "d";
}
//if format is part of the patterns get real format
pattern = calendar.patterns[format];
if (pattern) {
format = pattern;
}
format = format.split("");
length = format.length;
for (;idx < length; idx++) {
ch = format[idx];
if (literal) {
if (ch === "'") {
literal = false;
} else {
checkLiteral();
}
} else {
if (ch === "d") {
count = lookAhead("d");
day = count < 3 ? getNumber(2) : getIndexByName(calendar.days[count == 3 ? "namesAbbr" : "names"]);
if (day === null || outOfRange(day, 1, 31)) {
return null;
}
} else if (ch === "M") {
count = lookAhead("M");
month = count < 3 ? getNumber(2) : getIndexByName(calendar.months[count == 3 ? "namesAbbr" : "names"]);
if (month === null || outOfRange(month, 1, 12)) {
return null;
}
month -= 1;
} else if (ch === "y") {
count = lookAhead("y");
year = getNumber(count);
if (year === null) {
return null;
}
if (count == 2) {
if (typeof twoDigitYearMax === "string") {
twoDigitYearMax = defaultYear + parseInt(twoDigitYearMax, 10);
}
year = defaultYear - defaultYear % 100 + year;
if (year > twoDigitYearMax) {
year -= 100;
}
}
} else if (ch === "h") {
lookAhead("h");
hours = getNumber(2);
if (hours == 12) {
hours = 0;
}
if (hours === null || outOfRange(hours, 0, 11)) {
return null;
}
} else if (ch === "H") {
lookAhead("H");
hours = getNumber(2);
if (hours === null || outOfRange(hours, 0, 23)) {
return null;
}
} else if (ch === "m") {
lookAhead("m");
minutes = getNumber(2);
if (minutes === null || outOfRange(minutes, 0, 59)) {
return null;
}
} else if (ch === "s") {
lookAhead("s");
seconds = getNumber(2);
if (seconds === null || outOfRange(seconds, 0, 59)) {
return null;
}
} else if (ch === "f") {
count = lookAhead("f");
milliseconds = getNumber(count);
if (milliseconds !== null && count > 3) {
milliseconds = parseInt(milliseconds.toString().substring(0, 3), 10);
}
if (milliseconds === null || outOfRange(milliseconds, 0, 999)) {
return null;
}
} else if (ch === "t") {
count = lookAhead("t");
amDesignators = calendar.AM;
pmDesignators = calendar.PM;
if (count === 1) {
amDesignators = mapDesignators(amDesignators);
pmDesignators = mapDesignators(pmDesignators);
}
pmHour = getIndexByName(pmDesignators);
if (!pmHour && !getIndexByName(amDesignators)) {
return null;
}
} else if (ch === "z") {
UTC = true;
count = lookAhead("z");
if (value.substr(valueIdx, 1) === "Z") {
if (!ISO8601) {
return null;
}
checkLiteral();
continue;
}
matches = value.substr(valueIdx, 6).match(count > 2 ? longTimeZoneRegExp : shortTimeZoneRegExp);
if (!matches) {
return null;
}
matches = matches[0];
valueIdx = matches.length;
matches = matches.split(":");
hoursOffset = parseInt(matches[0], 10);
if (outOfRange(hoursOffset, -12, 13)) {
return null;
}
if (count > 2) {
minutesOffset = parseInt(matches[1], 10);
if (isNaN(minutesOffset) || outOfRange(minutesOffset, 0, 59)) {
return null;
}
}
} else if (ch === "T") {
ISO8601 = checkLiteral();
} else if (ch === "'") {
literal = true;
checkLiteral();
} else if (!checkLiteral()) {
return null;
}
}
}
if (year === null) {
year = defaultYear;
}
if (pmHour && hours < 12) {
hours += 12;
}
if (day === null) {
day = 1;
}
if (UTC) {
if (hoursOffset) {
hours += -hoursOffset;
}
if (minutesOffset) {
minutes += -minutesOffset;
}
value = new Date(Date.UTC(year, month, day, hours, minutes, seconds, milliseconds));
} else {
value = new Date(year, month, day, hours, minutes, seconds, milliseconds);
adjustDate(value, hours);
}
if (year < 100) {
value.setFullYear(year);
}
if (value.getDate() !== day && UTC === undefined) {
return null;
}
return value;
}
kendo._adjustDate = adjustDate;
kendo.parseDate = function(value, formats, culture) {
if (value instanceof Date) {
return value;
}
var idx = 0, date = null, length, patterns;
if (value && value.indexOf("/D") === 0) {
date = dateRegExp.exec(value);
if (date) {
return new Date(parseInt(date[1], 10));
}
}
culture = kendo.getCulture(culture);
if (!formats) {
formats = [];
patterns = culture.calendar.patterns;
length = formatsSequence.length;
for (;idx < length; idx++) {
formats[idx] = patterns[formatsSequence[idx]];
}
formats[idx] = "ddd MMM dd yyyy HH:mm:ss";
formats[++idx] = "yyyy-MM-ddTHH:mm:ss.fffffffzzz";
formats[++idx] = "yyyy-MM-ddTHH:mm:ss.fffzzz";
formats[++idx] = "yyyy-MM-ddTHH:mm:sszzz";
formats[++idx] = "yyyy-MM-ddTHH:mmzzz";
formats[++idx] = "yyyy-MM-ddTHH:mmzz";
formats[++idx] = "yyyy-MM-ddTHH:mm:ss";
formats[++idx] = "yyyy-MM-ddTHH:mm";
formats[++idx] = "yyyy-MM-dd";
idx = 0;
}
formats = isArray(formats) ? formats : [ formats ];
length = formats.length;
for (;idx < length; idx++) {
date = parseExact(value, formats[idx], culture);
if (date) {
return date;
}
}
return date;
};
kendo.parseInt = function(value, culture) {
var result = kendo.parseFloat(value, culture);
if (result) {
result = result | 0;
}
return result;
};
kendo.parseFloat = function(value, culture, format) {
if (!value && value !== 0) {
return null;
}
if (typeof value === NUMBER) {
return value;
}
value = value.toString();
culture = kendo.getCulture(culture);
var number = culture.numberFormat, percent = number.percent, currency = number.currency, symbol = currency.symbol, percentSymbol = percent.symbol, negative = value.indexOf("-") > -1, parts, isPercent;
//handle exponential number
if (exponentRegExp.test(value)) {
value = parseFloat(value);
if (isNaN(value)) {
value = null;
}
return value;
}
if (value.indexOf(symbol) > -1 || format && format.toLowerCase().indexOf("c") > -1) {
number = currency;
parts = number.pattern[0].replace("$", symbol).split("n");
if (value.indexOf(parts[0]) > -1 && value.indexOf(parts[1]) > -1) {
value = value.replace(parts[0], "").replace(parts[1], "");
negative = true;
}
} else if (value.indexOf(percentSymbol) > -1) {
isPercent = true;
number = percent;
symbol = percentSymbol;
}
value = value.replace("-", "").replace(symbol, "").replace(nonBreakingSpaceRegExp, " ").split(number[","].replace(nonBreakingSpaceRegExp, " ")).join("").replace(number["."], ".");
value = parseFloat(value);
if (isNaN(value)) {
value = null;
} else if (negative) {
value *= -1;
}
if (value && isPercent) {
value /= 100;
}
return value;
};
if (globalize) {
kendo.parseDate = function(value, format, culture) {
if (value instanceof Date) {
return value;
}
return globalize.parseDate(value, format, culture);
};
kendo.parseFloat = function(value, culture) {
if (typeof value === NUMBER) {
return value;
}
if (value === undefined) {
return null;
}
return globalize.parseFloat(value, culture);
};
}
})();
function wrap(element) {
var browser = support.browser, percentage, isRtl = element.css("direction") == "rtl";
if (!element.parent().hasClass("k-animation-container")) {
var shadow = element.css(kendo.support.transitions.css + "box-shadow") || element.css("box-shadow"), radius = shadow ? shadow.match(boxShadowRegExp) || [ 0, 0, 0, 0, 0 ] : [ 0, 0, 0, 0, 0 ], blur = math.max(+radius[3], +(radius[4] || 0)), left = -radius[1] + blur, right = +radius[1] + blur, bottom = +radius[2] + blur, width = element[0].style.width, height = element[0].style.height, percentWidth = percentRegExp.test(width), percentHeight = percentRegExp.test(height);
if (browser.opera) {
// Box shadow can't be retrieved in Opera
left = right = bottom = 5;
}
percentage = percentWidth || percentHeight;
if (!percentWidth) {
width = element.outerWidth();
}
if (!percentHeight) {
height = element.outerHeight();
}
element.wrap($("<div/>").addClass("k-animation-container").css({
width: width,
height: height,
marginLeft: left * (isRtl ? 1 : -1),
paddingLeft: left,
paddingRight: right,
paddingBottom: bottom
}));
if (percentage) {
element.css({
width: "100%",
height: "100%",
boxSizing: "border-box",
mozBoxSizing: "border-box",
webkitBoxSizing: "border-box"
});
}
} else {
var wrapper = element.parent(".k-animation-container"), wrapperStyle = wrapper[0].style;
if (wrapper.is(":hidden")) {
wrapper.show();
}
percentage = percentRegExp.test(wrapperStyle.width) || percentRegExp.test(wrapperStyle.height);
if (!percentage) {
wrapper.css({
width: element.outerWidth(),
height: element.outerHeight()
});
}
}
if (browser.msie && math.floor(browser.version) <= 7) {
element.css({
zoom: 1
});
}
return element.parent();
}
function deepExtend(destination) {
var i = 1, length = arguments.length;
for (i = 1; i < length; i++) {
deepExtendOne(destination, arguments[i]);
}
return destination;
}
function deepExtendOne(destination, source) {
var ObservableArray = kendo.data.ObservableArray, property, propValue, propType, destProp;
for (property in source) {
propValue = source[property];
propType = typeof propValue;
if (propType === OBJECT && propValue !== null && propValue.constructor !== Array && propValue.constructor !== ObservableArray) {
if (propValue instanceof Date) {
destination[property] = new Date(propValue.getTime());
} else {
destProp = destination[property];
if (typeof destProp === OBJECT) {
destination[property] = destProp || {};
} else {
destination[property] = {};
}
deepExtendOne(destination[property], propValue);
}
} else if (propType !== UNDEFINED) {
destination[property] = propValue;
}
}
return destination;
}
function testRx(agent, rxs, dflt) {
for (var rx in rxs) {
if (rxs.hasOwnProperty(rx) && rxs[rx].test(agent)) {
return rx;
}
}
return dflt !== undefined ? dflt : agent;
}
function getComputedStyles(element, properties) {
var styles = {}, computedStyle;
if (document.defaultView && document.defaultView.getComputedStyle) {
computedStyle = document.defaultView.getComputedStyle(element, "");
if (properties) {
$.each(properties, function(idx, value) {
styles[value] = computedStyle.getPropertyValue(value);
});
}
} else {
computedStyle = element.currentStyle;
if (properties) {
$.each(properties, function(idx, value) {
styles[value] = computedStyle[value.replace(/\-(\w)/g, function(strMatch, g1) {
return g1.toUpperCase();
})];
});
}
}
if (!kendo.size(styles)) {
styles = computedStyle;
}
return styles;
}
(function() {
support.scrollbar = function() {
var div = document.createElement("div"), result;
div.style.cssText = "overflow:scroll;overflow-x:hidden;zoom:1;clear:both";
div.innerHTML = " ";
document.body.appendChild(div);
result = div.offsetWidth - div.scrollWidth;
document.body.removeChild(div);
return result;
};
support.isRtl = function(element) {
return $(element).closest(".k-rtl").length > 0;
};
var table = document.createElement("table");
// Internet Explorer does not support setting the innerHTML of TBODY and TABLE elements
try {
table.innerHTML = "<tr><td></td></tr>";
support.tbodyInnerHtml = true;
} catch (e) {
support.tbodyInnerHtml = false;
}
support.touch = "ontouchstart" in window;
support.pointers = navigator.msPointerEnabled;
var transitions = support.transitions = false, transforms = support.transforms = false, elementProto = "HTMLElement" in window ? HTMLElement.prototype : [];
support.hasHW3D = "WebKitCSSMatrix" in window && "m11" in new window.WebKitCSSMatrix() || "MozPerspective" in document.documentElement.style || "msPerspective" in document.documentElement.style;
each([ "Moz", "webkit", "O", "ms" ], function() {
var prefix = this.toString(), hasTransitions = typeof table.style[prefix + "Transition"] === STRING;
if (hasTransitions || typeof table.style[prefix + "Transform"] === STRING) {
var lowPrefix = prefix.toLowerCase();
transforms = {
css: lowPrefix != "ms" ? "-" + lowPrefix + "-" : "",
prefix: prefix,
event: lowPrefix === "o" || lowPrefix === "webkit" ? lowPrefix : ""
};
if (hasTransitions) {
transitions = transforms;
transitions.event = transitions.event ? transitions.event + "TransitionEnd" : "transitionend";
}
return false;
}
});
support.transforms = transforms;
support.transitions = transitions;
support.devicePixelRatio = window.devicePixelRatio === undefined ? 1 : window.devicePixelRatio;
support.screenWidth = window.outerWidth || window.screen ? window.screen.availWidth : window.innerWidth;
support.screenHeight = window.outerHeight || window.screen ? window.screen.availHeight : window.innerHeight;
support.detectOS = function(ua) {
var os = false, minorVersion, match = [], notAndroidPhone = !/mobile safari/i.test(ua), agentRxs = {
fire: /(Silk)\/(\d+)\.(\d+(\.\d+)?)/,
android: /(Android|Android.*(?:Opera|Firefox).*?\/)\s*(\d+)\.(\d+(\.\d+)?)/,
iphone: /(iPhone|iPod).*OS\s+(\d+)[\._]([\d\._]+)/,
ipad: /(iPad).*OS\s+(\d+)[\._]([\d_]+)/,
meego: /(MeeGo).+NokiaBrowser\/(\d+)\.([\d\._]+)/,
webos: /(webOS)\/(\d+)\.(\d+(\.\d+)?)/,
blackberry: /(BlackBerry|BB10).*?Version\/(\d+)\.(\d+(\.\d+)?)/,
playbook: /(PlayBook).*?Tablet\s*OS\s*(\d+)\.(\d+(\.\d+)?)/,
wp: /(Windows Phone(?: OS)?)\s(\d+)\.(\d+(\.\d+)?)/,
windows: /(MSIE)\s+(\d+)\.(\d+(\.\d+)?)/,
ffos: /(Mobile).*rv:(\d+)\.(\d+(\.\d+)?).*Firefox/
}, osRxs = {
ios: /^i(phone|pad|pod)$/i,
android: /^android|fire$/i,
blackberry: /^blackberry|playbook/i,
windows: /windows/,
wp: /wp/,
meego: /meego|ffos/
}, formFactorRxs = {
tablet: /playbook|ipad|fire/i
}, browserRxs = {
omini: /Opera\sMini/i,
omobile: /Opera\sMobi/i,
firefox: /Firefox|Fennec/i,
mobilesafari: /version\/.*safari/i,
chrome: /chrome/i,
webkit: /webkit/i,
ie: /MSIE|Windows\sPhone/i
};
for (var agent in agentRxs) {
if (agentRxs.hasOwnProperty(agent)) {
match = ua.match(agentRxs[agent]);
if (match) {
if (agent == "windows" && "plugins" in navigator) {
return false;
}
// Break if not Metro/Mobile Windows
os = {};
os.device = agent;
os.tablet = testRx(agent, formFactorRxs, false);
os.browser = testRx(ua, browserRxs, "default");
os.name = testRx(agent, osRxs);
os[os.name] = true;
os.majorVersion = match[2];
os.minorVersion = match[3].replace("_", ".");
minorVersion = os.minorVersion.replace(".", "").substr(0, 2);
os.flatVersion = os.majorVersion + minorVersion + new Array(3 - (minorVersion.length < 3 ? minorVersion.length : 2)).join("0");
os.appMode = window.navigator.standalone || /file|local/.test(window.location.protocol) || typeof window.PhoneGap !== UNDEFINED || typeof window.cordova !== UNDEFINED;
// Use file protocol to detect appModes.
if (os.android && (support.devicePixelRatio < 1.5 && os.flatVersion < 400 || notAndroidPhone) && (support.screenWidth > 800 || support.screenHeight > 800)) {
os.tablet = agent;
}
break;
}
}
}
return os;
};
var mobileOS = support.mobileOS = support.detectOS(navigator.userAgent);
support.kineticScrollNeeded = mobileOS && (support.touch || support.pointers);
support.hasNativeScrolling = false;
if (mobileOS.ios && mobileOS.majorVersion > 4 || mobileOS.android && mobileOS.majorVersion > 2 || mobileOS.wp) {
support.hasNativeScrolling = mobileOS;
}
support.mouseAndTouchPresent = support.touch && !(support.mobileOS.ios || support.mobileOS.android);
function detectBrowser(ua) {
var browser = false, match = [], browserRxs = {
webkit: /(chrome)[ \/]([\w.]+)/i,
safari: /(webkit)[ \/]([\w.]+)/i,
opera: /(opera)(?:.*version|)[ \/]([\w.]+)/i,
msie: /(msie) ([\w.]+)/i,
mozilla: /(mozilla)(?:.*? rv:([\w.]+)|)/i
};
for (var agent in browserRxs) {
if (browserRxs.hasOwnProperty(agent)) {
match = ua.match(browserRxs[agent]);
if (match) {
browser = {};
browser[agent] = true;
browser[match[1].toLowerCase()] = true;
browser.version = parseInt(document.documentMode || match[2], 10);
break;
}
}
}
return browser;
}
support.browser = detectBrowser(navigator.userAgent);
(function(browser) {
// add browser-specific CSS class
var cssClass, majorVersion = parseInt(browser.version, 10);
if (browser.msie) {
cssClass = "ie";
} else if (browser.mozilla) {
cssClass = "ff";
} else if (browser.safari) {
cssClass = "safari";
} else if (browser.webkit) {
cssClass = "webkit";
} else if (browser.opera) {
cssClass = "opera";
}
if (cssClass) {
$(document.documentElement).addClass("k-" + cssClass + " k-" + cssClass + majorVersion);
}
})(support.browser);
support.zoomLevel = function() {
return support.touch ? document.documentElement.clientWidth / window.innerWidth : support.pointers ? window.outerWidth / window.innerWidth : 1;
};
support.eventCapture = document.documentElement.addEventListener;
support.placeholder = "placeholder" in document.createElement("input");
support.stableSort = function() {
var sorted = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ].sort(function() {
return 0;
});
return sorted[0] === 0 && sorted[1] === 1 && sorted[2] === 2 && sorted[3] === 3 && sorted[4] === 4 && sorted[5] === 5 && sorted[6] === 6 && sorted[7] === 7 && sorted[8] === 8 && sorted[9] === 9 && sorted[10] === 10 && sorted[11] === 11 && sorted[12] === 12;
}();
support.matchesSelector = elementProto.webkitMatchesSelector || elementProto.mozMatchesSelector || elementProto.msMatchesSelector || elementProto.oMatchesSelector || elementProto.matchesSelector || function(selector) {
var nodeList = document.querySelectorAll ? (this.parentNode || document).querySelectorAll(selector) || [] : $(selector), i = nodeList.length;
while (i--) {
if (nodeList[i] == this) {
return true;
}
}
return false;
};
})();
function size(obj) {
var result = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key) && key != "toJSON") {
// Ignore fake IE7 toJSON.
result++;
}
}
return result;
}
function isNodeEmpty(element) {
return $.trim($(element).contents().filter(function() {
return this.nodeType != 8;
}).html()) === "";
}
function getOffset(element, type, positioned) {
if (!type) {
type = "offset";
}
var result = element[type](), mobileOS = support.mobileOS;
if (support.touch && mobileOS.ios && mobileOS.flatVersion < 410) {
// Extra processing only in broken iOS'
var offset = type == "offset" ? result : element.offset(), position = result.left == offset.left && result.top == offset.top;
if (position) {
return {
top: result.top - window.scrollY,
left: result.left - window.scrollX
};
}
}
if (kendo.support.pointers && !positioned) {
// IE10 touch zoom is living in a separate viewport.
result.top -= window.pageYOffset - document.documentElement.scrollTop;
result.left -= window.pageXOffset - document.documentElement.scrollLeft;
}
return result;
}
var directions = {
left: {
reverse: "right"
},
right: {
reverse: "left"
},
down: {
reverse: "up"
},
up: {
reverse: "down"
},
top: {
reverse: "bottom"
},
bottom: {
reverse: "top"
},
"in": {
reverse: "out"
},
out: {
reverse: "in"
}
};
function parseEffects(input) {
var effects = {};
each(typeof input === "string" ? input.split(" ") : input, function(idx) {
effects[idx] = this;
});
return effects;
}
function fx(element) {
return new kendo.fx.Element(element);
}
$.extend(fx, {
Element: function(element) {
this.element = $(element);
},
promise: function(element, options) {
if (!element.is(":visible")) {
element.css({
display: element.data("olddisplay") || "block"
}).css("display");
}
if (options.hide) {
element.data("olddisplay", element.css("display")).hide();
}
if (options.init) {
options.init();
}
if (options.completeCallback) {
options.completeCallback(element);
}
element.dequeue();
},
transitionPromise: function(element, destination, options) {
var container = kendo.wrap(element);
container.append(destination);
element.hide();
destination.show();
if (options.completeCallback) {
options.completeCallback(element);
}
return element;
}
});
function prepareAnimationOptions(options, duration, reverse, complete) {
if (typeof options === STRING) {
// options is the list of effect names separated by space e.g. animate(element, "fadeIn slideDown")
// only callback is provided e.g. animate(element, options, function() {});
if (isFunction(duration)) {
complete = duration;
duration = 400;
reverse = false;
}
if (isFunction(reverse)) {
complete = reverse;
reverse = false;
}
if (typeof duration === BOOLEAN) {
reverse = duration;
duration = 400;
}
options = {
effects: options,
duration: duration,
reverse: reverse,
complete: complete
};
}
return extend({
//default options
effects: {},
duration: 400,
//jQuery default duration
reverse: false,
init: noop,
teardown: noop,
hide: false
}, options, {
completeCallback: options.complete,
complete: noop
});
}
function animate(element, options, duration, reverse, complete) {
var idx = 0, length = element.length, instance;
for (;idx < length; idx++) {
instance = $(element[idx]);
instance.queue(function() {
fx.promise(instance, prepareAnimationOptions(options, duration, reverse, complete));
});
}
return element;
}
function animateTo(element, destination, options, duration, reverse, complete) {
return fx.transitionPromise(element, destination, prepareAnimationOptions(options, duration, reverse, complete));
}
function toggleClass(element, classes, options, add) {
if (classes) {
classes = classes.split(" ");
each(classes, function(idx, value) {
element.toggleClass(value, add);
});
}
return element;
}
if (!("kendoAnimate" in $.fn)) {
extend($.fn, {
kendoStop: function(clearQueue, gotoEnd) {
return this.stop(clearQueue, gotoEnd);
},
kendoAnimate: function(options, duration, reverse, complete) {
return animate(this, options, duration, reverse, complete);
},
kendoAnimateTo: function(destination, options, duration, reverse, complete) {
return animateTo(this, destination, options, duration, reverse, complete);
},
kendoAddClass: function(classes, options) {
return kendo.toggleClass(this, classes, options, true);
},
kendoRemoveClass: function(classes, options) {
return kendo.toggleClass(this, classes, options, false);
},
kendoToggleClass: function(classes, options, toggle) {
return kendo.toggleClass(this, classes, options, toggle);
}
});
}
var ampRegExp = /&/g, ltRegExp = /</g, gtRegExp = />/g;
function htmlEncode(value) {
return ("" + value).replace(ampRegExp, "&").replace(ltRegExp, "<").replace(gtRegExp, ">");
}
var eventTarget = function(e) {
return e.target;
};
if (support.touch) {
var mobileChrome = support.mobileOS.browser == "chrome" && !support.mobileOS.ios;
eventTarget = function(e) {
var touches = "originalEvent" in e ? e.originalEvent.changedTouches : "changedTouches" in e ? e.changedTouches : null, property = mobileChrome ? "screen" : "client";
return touches ? document.elementFromPoint(touches[0][property + "X"], touches[0][property + "Y"]) : e.target;
};
each([ "swipe", "swipeLeft", "swipeRight", "swipeUp", "swipeDown", "doubleTap", "tap" ], function(m, value) {
$.fn[value] = function(callback) {
return this.bind(value, callback);
};
});
}
if (support.touch) {
if (!support.mobileOS) {
support.mousedown = "mousedown touchstart";
support.mouseup = "mouseup touchend";
support.mousemove = "mousemove touchmove";
support.mousecancel = "mouseleave touchcancel";
support.click = "click";
support.resize = "resize";
} else {
support.mousedown = "touchstart";
support.mouseup = "touchend";
support.mousemove = "touchmove";
support.mousecancel = "touchcancel";
support.click = "touchend";
support.resize = "orientationchange";
}
} else if (support.pointers) {
support.mousemove = "MSPointerMove";
support.mousedown = "MSPointerDown";
support.mouseup = "MSPointerUp";
support.mousecancel = "MSPointerCancel";
support.click = "MSPointerUp";
support.resize = "orientationchange resize";
} else {
support.mousemove = "mousemove";
support.mousedown = "mousedown";
support.mouseup = "mouseup";
support.mousecancel = "mouseleave";
support.click = "click";
support.resize = "resize";
}
var wrapExpression = function(members, paramName) {
var result = paramName || "d", index, idx, length, member, count = 1;
for (idx = 0, length = members.length; idx < length; idx++) {
member = members[idx];
if (member !== "") {
index = member.indexOf("[");
if (index !== 0) {
if (index == -1) {
member = "." + member;
} else {
count++;
member = "." + member.substring(0, index) + " || {})" + member.substring(index);
}
}
count++;
result += member + (idx < length - 1 ? " || {})" : ")");
}
}
return new Array(count).join("(") + result;
}, localUrlRe = /^([a-z]+:)?\/\//i;
extend(kendo, {
ui: kendo.ui || {},
fx: kendo.fx || fx,
mobile: kendo.mobile || {},
data: kendo.data || {},
dataviz: kendo.dataviz || {
ui: {
roles: {}
}
},
keys: {
INSERT: 45,
DELETE: 46,
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
ESC: 27,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
END: 35,
HOME: 36,
SPACEBAR: 32,
PAGEUP: 33,
PAGEDOWN: 34,
F2: 113,
F10: 121,
F12: 123
},
support: kendo.support || support,
animate: kendo.animate || animate,
ns: "",
attr: function(value) {
return "data-" + kendo.ns + value;
},
wrap: wrap,
deepExtend: deepExtend,
getComputedStyles: getComputedStyles,
size: size,
isNodeEmpty: isNodeEmpty,
getOffset: kendo.getOffset || getOffset,
parseEffects: kendo.parseEffects || parseEffects,
toggleClass: kendo.toggleClass || toggleClass,
directions: kendo.directions || directions,
Observable: Observable,
Class: Class,
Template: Template,
template: proxy(Template.compile, Template),
render: proxy(Template.render, Template),
stringify: proxy(JSON.stringify, JSON),
eventTarget: eventTarget,
htmlEncode: htmlEncode,
isLocalUrl: function(url) {
return url && !localUrlRe.test(url);
},
expr: function(expression, safe, paramName) {
expression = expression || "";
if (typeof safe == STRING) {
paramName = safe;
safe = false;
}
paramName = paramName || "d";
if (expression && expression.charAt(0) !== "[") {
expression = "." + expression;
}
if (safe) {
expression = wrapExpression(expression.split("."), paramName);
} else {
expression = paramName + expression;
}
return expression;
},
getter: function(expression, safe) {
return getterCache[expression] = getterCache[expression] || new Function("d", "return " + kendo.expr(expression, safe));
},
setter: function(expression) {
return setterCache[expression] = setterCache[expression] || new Function("d,value", kendo.expr(expression) + "=value");
},
accessor: function(expression) {
return {
get: kendo.getter(expression),
set: kendo.setter(expression)
};
},
guid: function() {
var id = "", i, random;
for (i = 0; i < 32; i++) {
random = math.random() * 16 | 0;
if (i == 8 || i == 12 || i == 16 || i == 20) {
id += "-";
}
id += (i == 12 ? 4 : i == 16 ? random & 3 | 8 : random).toString(16);
}
return id;
},
roleSelector: function(role) {
return role.replace(/(\S+)/g, "[" + kendo.attr("role") + "=$1],").slice(0, -1);
},
logToConsole: function(message) {
var console = window.console;
if (typeof console != "undefined" && console.log) {
console.log(message);
}
}
});
var Widget = Observable.extend({
init: function(element, options) {
var that = this;
that.element = kendo.jQuery(element).handler(that);
Observable.fn.init.call(that);
options = that.options = extend(true, {}, that.options, options);
if (!that.element.attr(kendo.attr("role"))) {
that.element.attr(kendo.attr("role"), (options.name || "").toLowerCase());
}
that.element.data("kendo" + options.prefix + options.name, that);
that.bind(that.events, options);
},
events: [],
options: {
prefix: ""
},
_tabindex: function(target) {
target = target || this.wrapper;
var element = this.element, TABINDEX = "tabindex", tabindex = target.attr(TABINDEX) || element.attr(TABINDEX);
element.removeAttr(TABINDEX);
target.attr(TABINDEX, !isNaN(tabindex) ? tabindex : 0);
},
setOptions: function(options) {
var that = this, idx = 0, length = that.events.length, e;
for (;idx < length; idx++) {
e = that.events[idx];
if (that.options[e] && options[e]) {
that.unbind(e, that.options[e]);
}
}
$.extend(that.options, options);
that.bind(that.events, options);
},
destroy: function() {
var that = this;
that.element.removeData("kendo" + that.options.prefix + that.options.name);
that.unbind();
}
});
kendo.notify = noop;
var templateRegExp = /template$/i, jsonRegExp = /^\s*(?:\{(?:.|\r\n|\n)*\}|\[(?:.|\r\n|\n)*\])\s*$/, jsonFormatRegExp = /^\{(\d+)(:[^\}]+)?\}/, dashRegExp = /([A-Z])/g;
function parseOption(element, option) {
var value;
if (option.indexOf("data") === 0) {
option = option.substring(4);
option = option.charAt(0).toLowerCase() + option.substring(1);
}
option = option.replace(dashRegExp, "-$1");
value = element.getAttribute("data-" + kendo.ns + option);
if (value === null) {
value = undefined;
} else if (value === "null") {
value = null;
} else if (value === "true") {
value = true;
} else if (value === "false") {
value = false;
} else if (!isNaN(parseFloat(value))) {
value = parseFloat(value);
} else if (jsonRegExp.test(value) && !jsonFormatRegExp.test(value)) {
value = evil("(" + value + ")");
}
return value;
}
function parseOptions(element, options) {
var result = {}, option, value;
for (option in options) {
value = parseOption(element, option);
if (value !== undefined) {
if (templateRegExp.test(option)) {
value = kendo.template($("#" + value).html());
}
result[option] = value;
}
}
return result;
}
kendo.initWidget = function(element, options, roles) {
var result, option, widget, idx, length, role, value, dataSource;
// Preserve backwards compatibility with (element, options, namespace) signature, where namespace was kendo.ui
if (!roles) {
roles = kendo.ui.roles;
} else if (roles.roles) {
roles = roles.roles;
}
element = element.nodeType ? element : element[0];
role = element.getAttribute("data-" + kendo.ns + "role");
if (!role) {
return;
}
if (role.indexOf(".") === -1) {
widget = roles[role];
} else {
// full namespace path - like kendo.ui.Widget
widget = kendo.getter(role)(window);
}
if (!widget) {
return;
}
dataSource = parseOption(element, "dataSource");
options = $.extend({}, parseOptions(element, widget.fn.options), options);
if (dataSource) {
if (typeof dataSource === STRING) {
options.dataSource = kendo.getter(dataSource)(window);
} else {
options.dataSource = dataSource;
}
}
for (idx = 0, length = widget.fn.events.length; idx < length; idx++) {
option = widget.fn.events[idx];
value = parseOption(element, option);
if (value !== undefined) {
options[option] = kendo.getter(value)(window);
}
}
result = $(element).data("kendo" + widget.fn.options.prefix + widget.fn.options.name);
if (!result) {
result = new widget(element, options);
} else {
result.setOptions(options);
}
return result;
};
kendo.rolesFromNamespaces = function(namespaces) {
var roles = [], idx, length;
if (!namespaces[0]) {
namespaces = [ kendo.ui, kendo.dataviz.ui ];
}
for (idx = 0, length = namespaces.length; idx < length; idx++) {
roles[idx] = namespaces[idx].roles;
}
return extend.apply(null, [ {} ].concat(roles.reverse()));
};
kendo.init = function(element) {
var roles = kendo.rolesFromNamespaces(slice.call(arguments, 1));
$(element).find("[data-" + kendo.ns + "role]").andSelf().each(function() {
kendo.initWidget(this, {}, roles);
});
};
kendo.destroy = function(element) {
$(element).find("[data-" + kendo.ns + "role]").andSelf().each(function() {
var element = $(this), widget = kendo.widgetInstance(element, kendo.ui) || kendo.widgetInstance(element, kendo.mobile.ui) || kendo.widgetInstance(element, kendo.dataviz.ui);
if (widget) {
widget.destroy();
}
});
};
kendo.parseOptions = parseOptions;
extend(kendo.ui, {
Widget: Widget,
roles: {},
progress: function(container, toggle) {
var mask = container.find(".k-loading-mask");
if (toggle) {
if (!mask.length) {
mask = $("<div class='k-loading-mask'><span class='k-loading-text'>Loading...</span><div class='k-loading-image'/><div class='k-loading-color'/></div>").width("100%").height("100%").prependTo(container).css({
top: container.scrollTop(),
left: container.scrollLeft()
});
}
} else if (mask) {
mask.remove();
}
},
plugin: function(widget, register, prefix) {
var name = widget.fn.options.name, getter;
register = register || kendo.ui;
prefix = prefix || "";
register[name] = widget;
register.roles[name.toLowerCase()] = widget;
getter = "getKendo" + prefix + name;
name = "kendo" + prefix + name;
$.fn[name] = function(options) {
var value = this, args;
if (typeof options === STRING) {
args = slice.call(arguments, 1);
this.each(function() {
var widget = $.data(this, name), method, result;
if (!widget) {
throw new Error(kendo.format("Cannot call method '{0}' of {1} before it is initialized", options, name));
}
method = widget[options];
if (typeof method !== FUNCTION) {
throw new Error(kendo.format("Cannot find method '{0}' of {1}", options, name));
}
result = method.apply(widget, args);
if (result !== undefined) {
value = result;
return false;
}
});
} else {
this.each(function() {
new widget(this, options);
});
}
return value;
};
$.fn[getter] = function() {
return this.data(name);
};
}
});
var ContainerNullObject = {
bind: $.noop
};
var MobileWidget = Widget.extend({
init: function(element, options) {
Widget.fn.init.call(this, element, options);
this.element.autoApplyNS();
this.wrapper = this.element;
},
destroy: function() {
Widget.fn.destroy.call(this);
this.element.kendoDestroy();
},
options: {
prefix: "Mobile"
},
events: [],
view: function() {
var viewElement = this.element.closest(kendo.roleSelector("view splitview modalview"));
return kendo.widgetInstance(viewElement, kendo.mobile.ui);
},
container: function() {
var element = this.element.closest(kendo.roleSelector("view layout modalview"));
return kendo.widgetInstance(element, kendo.mobile.ui) || ContainerNullObject;
}
});
extend(kendo.mobile, {
init: function(element) {
kendo.init(element, kendo.mobile.ui, kendo.ui, kendo.dataviz.ui);
},
ui: {
Widget: MobileWidget,
roles: {},
plugin: function(widget) {
kendo.ui.plugin(widget, kendo.mobile.ui, "Mobile");
}
}
});
kendo.touchScroller = function(elements, options) {
// return the first touch scroller
return $(elements).map(function(idx, element) {
element = $(element);
if (support.kineticScrollNeeded && kendo.mobile.ui.Scroller && !element.data("kendoMobileScroller")) {
element.kendoMobileScroller(options);
return element.data("kendoMobileScroller");
} else {
return false;
}
})[0];
};
kendo.preventDefault = function(e) {
e.preventDefault();
};
kendo.widgetInstance = function(element, suite) {
var widget = suite.roles[element.data(kendo.ns + "role")];
if (widget) {
return element.data("kendo" + widget.fn.options.prefix + widget.fn.options.name);
}
};
kendo.onResize = function(callback) {
var handler = callback;
if (support.mobileOS.android) {
handler = function() {
setTimeout(callback, 200);
};
}
$(window).on(support.resize, handler);
};
kendo.attrValue = function(element, key) {
return element.data(kendo.ns + key);
};
kendo.days = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6
};
function focusable(element, isTabIndexNotNaN) {
var nodeName = element.nodeName.toLowerCase();
return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled : "a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && visible(element);
}
function visible(element) {
return !$(element).parents().andSelf().filter(function() {
return $.css(this, "visibility") === "hidden" || $.expr.filters.hidden(this);
}).length;
}
$.extend($.expr[":"], {
focusable: function(element) {
var idx = $.attr(element, "tabindex");
return focusable(element, !isNaN(idx) && idx > -1);
}
});
var MOUSE_EVENTS = [ "mousedown", "mousemove", "mouseenter", "mouseleave", "mouseover", "mouseout", "mouseup", "click" ];
var EXCLUDE_BUST_CLICK_SELECTOR = "label, input, [data-rel=external]";
var MouseEventNormalizer = {
setupMouseMute: function() {
var idx = 0, length = MOUSE_EVENTS.length, element = document.documentElement;
if (MouseEventNormalizer.mouseTrap || !support.eventCapture) {
return;
}
MouseEventNormalizer.mouseTrap = true;
MouseEventNormalizer.bustClick = false;
MouseEventNormalizer.captureMouse = false;
var handler = function(e) {
if (MouseEventNormalizer.captureMouse) {
if (e.type === "click") {
if (MouseEventNormalizer.bustClick && !$(e.target).is(EXCLUDE_BUST_CLICK_SELECTOR)) {
e.preventDefault();
e.stopPropagation();
}
} else {
e.stopPropagation();
}
}
};
for (;idx < length; idx++) {
element.addEventListener(MOUSE_EVENTS[idx], handler, true);
}
},
muteMouse: function(e) {
MouseEventNormalizer.captureMouse = true;
if (e.data.bustClick) {
MouseEventNormalizer.bustClick = true;
}
clearTimeout(MouseEventNormalizer.mouseTrapTimeoutID);
},
unMuteMouse: function() {
clearTimeout(MouseEventNormalizer.mouseTrapTimeoutID);
MouseEventNormalizer.mouseTrapTimeoutID = setTimeout(function() {
MouseEventNormalizer.captureMouse = false;
MouseEventNormalizer.bustClick = false;
}, 400);
}
};
var eventMap = {
down: "touchstart mousedown",
move: "mousemove touchmove",
up: "mouseup touchend touchcancel",
cancel: "mouseleave touchcancel"
};
if (support.touch && (support.mobileOS.ios || support.mobileOS.android)) {
eventMap = {
down: "touchstart",
move: "touchmove",
up: "touchend touchcancel",
cancel: "touchcancel"
};
}
if (support.pointers) {
eventMap = {
down: "MSPointerDown",
move: "MSPointerMove",
up: "MSPointerUp",
cancel: "MSPointerCancel MSPointerLeave"
};
// Create MSPointerEnter/MSPointerLeave events using mouseover/out and event-time checks
jQuery.each({
MSPointerEnter: "MSPointerOver",
MSPointerLeave: "MSPointerOut"
}, function(orig, fix) {
jQuery.event.special[orig] = {
delegateType: fix,
bindType: fix,
handle: function(event) {
var ret, target = this, related = event.relatedTarget, handleObj = event.handleObj;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if (!related || related !== target && !jQuery.contains(target, related)) {
event.type = handleObj.origType;
ret = handleObj.handler.apply(this, arguments);
event.type = fix;
}
return ret;
}
};
});
}
var getEventMap = function(e) {
return eventMap[e] || e;
}, eventRegEx = /([^ ]+)/g;
kendo.applyEventMap = function(events, ns) {
events = events.replace(eventRegEx, getEventMap);
if (ns) {
events = events.replace(eventRegEx, "$1." + ns);
}
return events;
};
var on = $.fn.on;
function kendoJQuery(selector, context) {
return new kendoJQuery.fn.init(selector, context);
}
extend(true, kendoJQuery, $);
kendoJQuery.fn = kendoJQuery.prototype = new $();
kendoJQuery.fn.constructor = kendoJQuery;
kendoJQuery.fn.init = function(selector, context) {
if (context && context instanceof $ && !(context instanceof kendoJQuery)) {
context = kendoJQuery(context);
}
return $.fn.init.call(this, selector, context, rootjQuery);
};
kendoJQuery.fn.init.prototype = kendoJQuery.fn;
var rootjQuery = kendoJQuery(document);
extend(kendoJQuery.fn, {
handler: function(handler) {
this.data("handler", handler);
return this;
},
autoApplyNS: function(ns) {
this.data("kendoNS", ns || kendo.guid());
return this;
},
on: function() {
var that = this, ns = that.data("kendoNS");
// support for event map signature
if (arguments.length === 1) {
return on.call(that, arguments[0]);
}
var context = that, args = slice.call(arguments);
if (typeof args[args.length - 1] === UNDEFINED) {
args.pop();
}
var callback = args[args.length - 1], events = kendo.applyEventMap(args[0], ns);
// setup mouse trap
if (support.mouseAndTouchPresent && events.search(/mouse|click/) > -1 && this[0] !== document.documentElement) {
MouseEventNormalizer.setupMouseMute();
var selector = args.length === 2 ? null : args[1], bustClick = events.indexOf("click") > -1 && events.indexOf("touchend") > -1;
on.call(this, {
touchstart: MouseEventNormalizer.muteMouse,
touchend: MouseEventNormalizer.unMuteMouse
}, selector, {
bustClick: bustClick
});
}
if (typeof callback === STRING) {
context = that.data("handler");
callback = context[callback];
args[args.length - 1] = function(e) {
callback.call(context, e);
};
}
args[0] = events;
on.apply(that, args);
return that;
},
kendoDestroy: function(ns) {
ns = ns || this.data("kendoNS");
if (ns) {
this.off("." + ns);
}
return this;
}
});
kendo.jQuery = kendoJQuery;
kendo.eventMap = eventMap;
})(jQuery, eval);
/*global kendo_module:true */
if (typeof kendo_module === "undefined") {
kendo_module = function() {};
}
(function($, undefined) {
var kendo = window.kendo, location = window.location, history = window.history, _checkUrlInterval = 50, hashStrip = /^#*/, documentMode = window.document.documentMode, oldIE = kendo.support.browser.msie && (!documentMode || documentMode <= 8), hashChangeSupported = "onhashchange" in window && !oldIE, document = window.document;
var History = kendo.Observable.extend({
start: function(options) {
options = options || {};
var that = this;
that._pushStateRequested = !!options.pushState;
that._pushState = that._pushStateRequested && that._pushStateSupported();
that.root = options.root || "/";
that._interval = 0;
this.bind([ "change", "ready" ], options);
if (that._normalizeUrl()) {
return true;
}
that.current = that._currentLocation();
that._listenToLocationChange();
that.trigger("ready", {
url: that.current
});
},
stop: function() {
$(window).unbind(".kendo");
this.unbind("change");
this.unbind("ready");
clearInterval(this._interval);
},
_normalizeUrl: function() {
var that = this, pushStateUrl, atRoot = that.root == location.pathname, pushStateUrlNeedsTransform = that._pushStateRequested && !that._pushStateSupported() && !atRoot, hashUrlNeedsTransform = that._pushState && atRoot && location.hash;
if (pushStateUrlNeedsTransform) {
location.replace(that.root + "#" + that._stripRoot(location.pathname));
return true;
} else if (hashUrlNeedsTransform) {
pushStateUrl = that._makePushStateUrl(location.hash.replace(hashStrip, ""));
history.replaceState({}, document.title, pushStateUrl);
return false;
}
return false;
},
_listenToLocationChange: function() {
var that = this, _checkUrlProxy = $.proxy(that._checkUrl, that);
if (this._pushState) {
$(window).bind("popstate.kendo", _checkUrlProxy);
} else if (hashChangeSupported) {
$(window).bind("hashchange.kendo", _checkUrlProxy);
} else {
that._interval = setInterval(_checkUrlProxy, _checkUrlInterval);
}
},
_pushStateSupported: function() {
return window.history && window.history.pushState;
},
_checkUrl: function() {
var that = this, current = that._currentLocation();
if (current != that.current) {
that.navigate(current);
}
},
_stripRoot: function(url) {
var that = this;
if (url.indexOf(that.root) === 0) {
return ("/" + url.substr(that.root.length)).replace(/\/\//g, "/");
} else {
return url;
}
},
_makePushStateUrl: function(address) {
var that = this;
if (address.indexOf(that.root) !== 0) {
address = (that.root + address).replace(/\/\//g, "/");
}
return location.protocol + "//" + location.host + address;
},
_currentLocation: function() {
var that = this, current;
if (that._pushState) {
current = location.pathname;
if (location.search) {
current += location.search;
}
return that._stripRoot(current);
} else {
return location.hash.replace(hashStrip, "");
}
},
change: function(callback) {
this.bind("change", callback);
},
navigate: function(to, silent) {
var that = this;
if (to === "#:back") {
history.back();
return;
}
to = to.replace(hashStrip, "");
if (that.current === to || that.current === decodeURIComponent(to)) {
return;
}
if (!silent) {
if (that.trigger("change", {
url: to
})) {
to = that.current;
}
}
if (that._pushState) {
history.pushState({}, document.title, that._makePushStateUrl(to));
that.current = to;
} else {
location.hash = that.current = to;
}
}
});
kendo.history = new History();
})(window.kendo.jQuery);
(function() {
var kendo = window.kendo, history = kendo.history, Observable = kendo.Observable, INIT = "init", ROUTE_MISSING = "routeMissing", CHANGE = "change", optionalParam = /\((.*?)\)/g, namedParam = /(\(\?)?:\w+/g, splatParam = /\*\w+/g, escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
function namedParamReplace(match, optional) {
return optional ? match : "([^/]+)";
}
function routeToRegExp(route) {
return new RegExp("^" + route.replace(escapeRegExp, "\\$&").replace(optionalParam, "(?:$1)?").replace(namedParam, namedParamReplace).replace(splatParam, "(.*?)") + "$");
}
var Route = kendo.Class.extend({
init: function(route, callback) {
if (!(route instanceof RegExp)) {
route = routeToRegExp(route);
}
this.route = route;
this._callback = callback;
},
callback: function(url) {
var params = this.route.exec(url).slice(1), idx = 0, length = params.length;
for (;idx < length; idx++) {
if (typeof params[idx] !== "undefined") {
params[idx] = decodeURIComponent(params[idx]);
}
}
this._callback.apply(null, params);
},
worksWith: function(url) {
if (this.route.test(url)) {
this.callback(url);
return true;
} else {
return false;
}
}
});
var Router = Observable.extend({
init: function(options) {
Observable.fn.init.call(this);
this.routes = [];
this.bind([ INIT, ROUTE_MISSING, CHANGE ], options);
},
destroy: function() {
history.unbind("ready", this._readyProxy);
history.unbind("change", this._urlChangedProxy);
this.unbind();
},
start: function() {
var that = this, readyProxy = function(e) {
if (!e.url) {
e.url = "/";
}
if (!that.trigger(INIT, e)) {
that._urlChanged(e);
}
}, urlChangedProxy = function(e) {
that._urlChanged(e);
};
kendo.history.start({
ready: readyProxy,
change: urlChangedProxy
});
this._urlChangedProxy = urlChangedProxy;
this._readyProxy = readyProxy;
},
route: function(route, callback) {
this.routes.push(new Route(route, callback));
},
navigate: function(url, silent) {
kendo.history.navigate(url, silent);
},
_urlChanged: function(e) {
var url = e.url;
if (!url) {
url = "/";
}
if (this.trigger(CHANGE, {
url: e.url
})) {
e.preventDefault();
return;
}
var idx = 0, routes = this.routes, route, length = routes.length;
for (;idx < length; idx++) {
route = routes[idx];
if (route.worksWith(url)) {
return;
}
}
this.trigger(ROUTE_MISSING, {
url: url
});
}
});
kendo.Router = Router;
})();
(function($, undefined) {
var kendo = window.kendo, Observable = kendo.Observable, SCRIPT = "SCRIPT", INIT = "init", SHOW = "show", HIDE = "hide";
var View = Observable.extend({
init: function(content, options) {
var that = this;
options = options || {};
Observable.fn.init.call(that);
that.content = content;
that.tagName = options.tagName || "div";
that.model = options.model;
that.bind([ INIT, SHOW, HIDE ], options);
},
render: function(container) {
var that = this, element, content;
if (!that.element) {
element = $("<" + that.tagName + " />");
content = $(document.getElementById(that.content) || that.content);
// support passing id without #
element.append(content[0].tagName === SCRIPT ? content.html() : content);
that.element = element;
kendo.bind(that.element, that.model);
this.trigger(INIT);
}
if (container) {
this.trigger(SHOW);
$(container).append(that.element);
}
return that.element;
},
hide: function() {
this.element.detach();
this.trigger(HIDE);
},
destroy: function() {
if (this.element) {
kendo.unbind(this.element);
this.element.remove();
}
}
});
var Layout = View.extend({
init: function(content, options) {
View.fn.init.call(this, content, options);
this.regions = {};
},
showIn: function(container, view) {
var previousView = this.regions[container];
if (previousView) {
previousView.hide();
}
view.render(this.render().find(container), previousView);
this.regions[container] = view;
}
});
kendo.Layout = Layout;
kendo.View = View;
})(window.kendo.jQuery);
(function($, undefined) {
/**
* @name kendo.fx
* @namespace This object contains the fx library that is used by all widgets using animation.
* If this file is not included, all animations will be disabled but the basic functionality preserved.
*/
var kendo = window.kendo, fx = kendo.fx, each = $.each, extend = $.extend, proxy = $.proxy, support = kendo.support, browser = support.browser, transforms = support.transforms, transitions = support.transitions, scaleProperties = {
scale: 0,
scalex: 0,
scaley: 0,
scale3d: 0
}, translateProperties = {
translate: 0,
translatex: 0,
translatey: 0,
translate3d: 0
}, hasZoom = typeof document.documentElement.style.zoom !== "undefined" && !transforms, matrix3dRegExp = /matrix3?d?\s*\(.*,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?,\s*([\d\.\-]+)\w*?/i, cssParamsRegExp = /^(-?[\d\.\-]+)?[\w\s]*,?\s*(-?[\d\.\-]+)?[\w\s]*/i, translateXRegExp = /translatex?$/i, oldEffectsRegExp = /(zoom|fade|expand)(\w+)/, singleEffectRegExp = /(zoom|fade|expand)/, unitRegExp = /[xy]$/i, transformProps = [ "perspective", "rotate", "rotatex", "rotatey", "rotatez", "rotate3d", "scale", "scalex", "scaley", "scalez", "scale3d", "skew", "skewx", "skewy", "translate", "translatex", "translatey", "translatez", "translate3d", "matrix", "matrix3d" ], transform2d = [ "rotate", "scale", "scalex", "scaley", "skew", "skewx", "skewy", "translate", "translatex", "translatey", "matrix" ], transform2units = {
rotate: "deg",
scale: "",
skew: "px",
translate: "px"
}, cssPrefix = transforms.css, Effects = {}, round = Math.round, BLANK = "", PX = "px", NONE = "none", AUTO = "auto", WIDTH = "width", HEIGHT = "height", HIDDEN = "hidden", ORIGIN = "origin", ABORT_ID = "abortId", OVERFLOW = "overflow", TRANSLATE = "translate", COMPLETE_CALLBACK = "completeCallback", TRANSITION = cssPrefix + "transition", TRANSFORM = cssPrefix + "transform", BACKFACE = cssPrefix + "backface-visibility", PERSPECTIVE = cssPrefix + "perspective", DEFAULT_PERSPECTIVE = "800px", TRANSFORM_PERSPECTIVE = "perspective(" + DEFAULT_PERSPECTIVE + ")", directions = {
left: {
reverse: "right",
property: "left",
transition: "translatex",
vertical: false,
modifier: -1
},
right: {
reverse: "left",
property: "left",
transition: "translatex",
vertical: false,
modifier: 1
},
down: {
reverse: "up",
property: "top",
transition: "translatey",
vertical: true,
modifier: 1
},
up: {
reverse: "down",
property: "top",
transition: "translatey",
vertical: true,
modifier: -1
},
top: {
reverse: "bottom"
},
bottom: {
reverse: "top"
},
"in": {
reverse: "out",
modifier: -1
},
out: {
reverse: "in",
modifier: 1
},
vertical: {
reverse: "vertical"
},
horizontal: {
reverse: "horizontal"
}
};
kendo.directions = directions;
extend($.fn, {
kendoStop: function(clearQueue, gotoEnd) {
if (transitions) {
return kendo.fx.stopQueue(this, clearQueue || false, gotoEnd || false);
} else {
return this.stop(clearQueue, gotoEnd);
}
}
});
/* jQuery support for all transform animations (FF 3.5/3.6, Opera 10.x, IE9 */
if (transforms && !transitions) {
each(transform2d, function(idx, value) {
$.fn[value] = function(val) {
if (typeof val == "undefined") {
return animationProperty(this, value);
} else {
var that = $(this)[0], transformValue = value + "(" + val + transform2units[value.replace(unitRegExp, "")] + ")";
if (that.style.cssText.indexOf(TRANSFORM) == -1) {
$(this).css(TRANSFORM, transformValue);
} else {
that.style.cssText = that.style.cssText.replace(new RegExp(value + "\\(.*?\\)", "i"), transformValue);
}
}
return this;
};
$.fx.step[value] = function(fx) {
$(fx.elem)[value](fx.now);
};
});
var curProxy = $.fx.prototype.cur;
$.fx.prototype.cur = function() {
if (transform2d.indexOf(this.prop) != -1) {
return parseFloat($(this.elem)[this.prop]());
}
return curProxy.apply(this, arguments);
};
}
kendo.toggleClass = function(element, classes, options, add) {
if (classes) {
classes = classes.split(" ");
if (transitions) {
options = extend({
exclusive: "all",
duration: 400,
ease: "ease-out"
}, options);
element.css(TRANSITION, options.exclusive + " " + options.duration + "ms " + options.ease);
setTimeout(function() {
element.css(TRANSITION, "").css(HEIGHT);
}, options.duration);
}
each(classes, function(idx, value) {
element.toggleClass(value, add);
});
}
return element;
};
kendo.parseEffects = function(input, mirror) {
var effects = {};
if (typeof input === "string") {
each(input.split(" "), function(idx, value) {
var redirectedEffect = !singleEffectRegExp.test(value), resolved = value.replace(oldEffectsRegExp, function(match, $1, $2) {
return $1 + ":" + $2.toLowerCase();
}), // Support for old zoomIn/fadeOut style, now deprecated.
effect = resolved.split(":"), direction = effect[1], effectBody = {};
if (effect.length > 1) {
effectBody.direction = mirror && redirectedEffect ? directions[direction].reverse : direction;
}
effects[effect[0]] = effectBody;
});
} else {
each(input, function(idx) {
var direction = this.direction;
if (direction && mirror && !singleEffectRegExp.test(idx)) {
this.direction = directions[direction].reverse;
}
effects[idx] = this;
});
}
return effects;
};
function parseInteger(value) {
return parseInt(value, 10);
}
function parseCSS(element, property) {
return parseInteger(element.css(property));
}
function parseTransitionEffects(options) {
var effects = options.effects;
if (effects === "zoom") {
effects = "zoom:in fade:in";
}
if (effects === "fade") {
effects = "fade:in";
}
if (effects === "slide") {
effects = "tile:left";
}
if (/^slide:(.+)$/.test(effects)) {
effects = "tile:" + RegExp.$1;
}
if (effects === "overlay") {
effects = "slideIn:left";
}
if (/^overlay:(.+)$/.test(effects)) {
effects = "slideIn:" + RegExp.$1;
}
options.effects = kendo.parseEffects(effects);
return options;
}
function keys(obj) {
var acc = [];
for (var propertyName in obj) {
acc.push(propertyName);
}
return acc;
}
function strip3DTransforms(properties) {
for (var key in properties) {
if (transformProps.indexOf(key) != -1 && transform2d.indexOf(key) == -1) {
delete properties[key];
}
}
return properties;
}
function normalizeCSS(element, properties) {
var transformation = [], cssValues = {}, lowerKey, key, value, isTransformed;
for (key in properties) {
lowerKey = key.toLowerCase();
isTransformed = transforms && transformProps.indexOf(lowerKey) != -1;
if (!support.hasHW3D && isTransformed && transform2d.indexOf(lowerKey) == -1) {
delete properties[key];
} else {
value = properties[key];
if (isTransformed) {
transformation.push(key + "(" + value + ")");
} else {
cssValues[key] = value;
}
}
}
if (transformation.length) {
cssValues[TRANSFORM] = transformation.join(" ");
}
return cssValues;
}
if (transitions) {
extend(kendo.fx, {
transition: function(element, properties, options) {
var css, delay = 0, oldKeys = element.data("keys") || [], timeoutID;
options = extend({
duration: 200,
ease: "ease-out",
complete: null,
exclusive: "all"
}, options);
var stopTransitionCalled = false;
var stopTransition = function() {
if (!stopTransitionCalled) {
stopTransitionCalled = true;
if (timeoutID) {
clearTimeout(timeoutID);
timeoutID = null;
}
element.removeData(ABORT_ID).dequeue().css(TRANSITION, "").css(TRANSITION);
options.complete.call(element);
}
};
options.duration = $.fx ? $.fx.speeds[options.duration] || options.duration : options.duration;
css = normalizeCSS(element, properties);
$.merge(oldKeys, keys(css));
element.data("keys", $.unique(oldKeys)).height();
element.css(TRANSITION, options.exclusive + " " + options.duration + "ms " + options.ease).css(TRANSITION);
element.css(css).css(TRANSFORM);
/**
* Use transitionEnd event for browsers who support it - but duplicate it with setTimeout, as the transitionEnd event will not be triggered if no CSS properties change.
* This should be cleaned up at some point (widget by widget), and refactored to widgets not relying on the complete callback if no transition occurs.
*
* For IE9 and below, resort to setTimeout.
*/
if (transitions.event) {
element.one(transitions.event, stopTransition);
if (options.duration !== 0) {
delay = 500;
}
}
timeoutID = setTimeout(stopTransition, options.duration + delay);
element.data(ABORT_ID, timeoutID);
element.data(COMPLETE_CALLBACK, stopTransition);
},
stopQueue: function(element, clearQueue, gotoEnd) {
var cssValues, taskKeys = element.data("keys"), retainPosition = !gotoEnd && taskKeys, completeCallback = element.data(COMPLETE_CALLBACK);
if (retainPosition) {
cssValues = kendo.getComputedStyles(element[0], taskKeys);
}
if (completeCallback) {
completeCallback();
}
if (retainPosition) {
element.css(cssValues);
}
return element.removeData("keys").stop(clearQueue);
}
});
}
function animationProperty(element, property) {
if (transforms) {
var transform = element.css(TRANSFORM);
if (transform == NONE) {
return property == "scale" ? 1 : 0;
}
var match = transform.match(new RegExp(property + "\\s*\\(([\\d\\w\\.]+)")), computed = 0;
if (match) {
computed = parseInteger(match[1]);
} else {
match = transform.match(matrix3dRegExp) || [ 0, 0, 0, 0, 0 ];
property = property.toLowerCase();
if (translateXRegExp.test(property)) {
computed = parseFloat(match[3] / match[2]);
} else if (property == "translatey") {
computed = parseFloat(match[4] / match[2]);
} else if (property == "scale") {
computed = parseFloat(match[2]);
} else if (property == "rotate") {
computed = parseFloat(Math.atan2(match[2], match[1]));
}
}
return computed;
} else {
return parseFloat(element.css(property));
}
}
var EffectSet = kendo.Class.extend({
init: function(element, options) {
var that = this;
that.element = element;
that.effects = [];
that.options = options;
that.restore = [];
},
run: function(effects) {
var that = this, effect, idx, jdx, length = effects.length, element = that.element, options = that.options, deferred = $.Deferred(), start = {}, end = {}, target, children, childrenLength;
that.effects = effects;
deferred.then($.proxy(that, "complete"));
element.data("animating", true);
for (idx = 0; idx < length; idx++) {
effect = effects[idx];
effect.setReverse(options.reverse);
effect.setOptions(options);
that.addRestoreProperties(effect.restore);
effect.prepare(start, end);
children = effect.children();
for (jdx = 0, childrenLength = children.length; jdx < childrenLength; jdx++) {
children[jdx].duration(options.duration).run();
}
}
// legacy support for options.properties
for (var effectName in options.effects) {
extend(end, options.effects[effectName].properties);
}
// Show the element initially
if (!element.is(":visible")) {
extend(start, {
display: element.data("olddisplay") || "block"
});
}
if (transforms && !options.reset) {
target = element.data("targetTransform");
if (target) {
start = extend(target, start);
}
}
start = normalizeCSS(element, start);
if (transforms && !transitions) {
start = strip3DTransforms(start);
}
element.css(start).css(TRANSFORM);
// Nudge
for (idx = 0; idx < length; idx++) {
effects[idx].setup();
}
if (options.init) {
options.init();
}
element.data("targetTransform", end);
kendo.fx.animate(element, end, extend({}, options, {
complete: deferred.resolve
}));
return deferred.promise();
},
stop: function() {
$(this.element).kendoStop(true, true);
},
addRestoreProperties: function(restore) {
var element = this.element, value, i = 0, length = restore.length;
for (;i < length; i++) {
value = restore[i];
this.restore.push(value);
if (!element.data(value)) {
element.data(value, element.css(value));
}
}
},
restoreCallback: function() {
var element = this.element;
for (var i = 0, length = this.restore.length; i < length; i++) {
var value = this.restore[i];
element.css(value, element.data(value));
}
},
complete: function() {
var that = this, idx = 0, element = that.element, options = that.options, effects = that.effects, length = effects.length;
element.removeData("animating").dequeue();
// call next animation from the queue
if (options.hide) {
element.data("olddisplay", element.css("display")).hide();
}
this.restoreCallback();
if (hasZoom && !transforms) {
setTimeout($.proxy(this, "restoreCallback"), 0);
}
for (;idx < length; idx++) {
effects[idx].teardown();
}
if (options.completeCallback) {
options.completeCallback(element);
}
}
});
kendo.fx.promise = function(element, options) {
var effects = [], effectClass, effectSet = new EffectSet(element, options), parsedEffects = kendo.parseEffects(options.effects), effect;
options.effects = parsedEffects;
for (var effectName in parsedEffects) {
effectClass = Effects[effectName];
if (effectClass) {
effect = new effectClass(element, parsedEffects[effectName].direction);
effects.push(effect);
}
}
if (effects[0]) {
effectSet.run(effects);
} else {
// Not sure how would an fx promise reach this state - means that you call kendoAnimate with no valid effects? Why?
if (!element.is(":visible")) {
element.css({
display: element.data("olddisplay") || "block"
}).css("display");
}
if (options.init) {
options.init();
}
element.dequeue();
effectSet.complete();
}
};
kendo.fx.transitionPromise = function(element, destination, options) {
kendo.fx.animateTo(element, destination, options);
return element;
};
extend(kendo.fx, {
animate: function(elements, properties, options) {
var useTransition = options.transition !== false;
delete options.transition;
if (transitions && "transition" in fx && useTransition) {
fx.transition(elements, properties, options);
} else {
if (transforms) {
elements.animate(strip3DTransforms(properties), {
queue: false,
show: false,
hide: false,
duration: options.duration,
complete: options.complete
});
} else {
elements.each(function() {
var element = $(this), multiple = {};
each(transformProps, function(idx, value) {
// remove transforms to avoid IE and older browsers confusion
var params, currentValue = properties ? properties[value] + " " : null;
// We need to match
if (currentValue) {
var single = properties;
if (value in scaleProperties && properties[value] !== undefined) {
params = currentValue.match(cssParamsRegExp);
if (transforms) {
extend(single, {
scale: +params[0]
});
}
} else {
if (value in translateProperties && properties[value] !== undefined) {
var position = element.css("position"), isFixed = position == "absolute" || position == "fixed";
if (!element.data(TRANSLATE)) {
if (isFixed) {
element.data(TRANSLATE, {
top: parseCSS(element, "top") || 0,
left: parseCSS(element, "left") || 0,
bottom: parseCSS(element, "bottom"),
right: parseCSS(element, "right")
});
} else {
element.data(TRANSLATE, {
top: parseCSS(element, "marginTop") || 0,
left: parseCSS(element, "marginLeft") || 0
});
}
}
var originalPosition = element.data(TRANSLATE);
params = currentValue.match(cssParamsRegExp);
if (params) {
var dX = value == TRANSLATE + "y" ? +null : +params[1], dY = value == TRANSLATE + "y" ? +params[1] : +params[2];
if (isFixed) {
if (!isNaN(originalPosition.right)) {
if (!isNaN(dX)) {
extend(single, {
right: originalPosition.right - dX
});
}
} else {
if (!isNaN(dX)) {
extend(single, {
left: originalPosition.left + dX
});
}
}
if (!isNaN(originalPosition.bottom)) {
if (!isNaN(dY)) {
extend(single, {
bottom: originalPosition.bottom - dY
});
}
} else {
if (!isNaN(dY)) {
extend(single, {
top: originalPosition.top + dY
});
}
}
} else {
if (!isNaN(dX)) {
extend(single, {
marginLeft: originalPosition.left + dX
});
}
if (!isNaN(dY)) {
extend(single, {
marginTop: originalPosition.top + dY
});
}
}
}
}
}
if (!transforms && value != "scale" && value in single) {
delete single[value];
}
if (single) {
extend(multiple, single);
}
}
});
if (browser.msie) {
delete multiple.scale;
}
element.animate(multiple, {
queue: false,
show: false,
hide: false,
duration: options.duration,
complete: options.complete
});
});
}
}
},
animateTo: function(element, destination, options) {
var direction, commonParent = element.parents().filter(destination.parents()).first(), originalOverflow;
options = parseTransitionEffects(options);
if (!support.mobileOS.android) {
originalOverflow = commonParent.css(OVERFLOW);
commonParent.css(OVERFLOW, "hidden");
}
$.each(options.effects, function(name, definition) {
direction = direction || definition.direction;
});
function complete(animatedElement) {
destination[0].style.cssText = "";
element[0].style.cssText = "";
// Removing the whole style attribute breaks Android.
if (!support.mobileOS.android) {
commonParent.css(OVERFLOW, originalOverflow);
}
if (options.completeCallback) {
options.completeCallback.call(element, animatedElement);
}
}
options.complete = browser.msie ? function() {
setTimeout(complete, 0);
} : complete;
options.previous = options.reverse ? destination : element;
options.reset = true;
// Reset transforms if there are any.
// execute callback only once, and hook up derived animations to previous view only once.
(options.reverse ? element : destination).each(function() {
$(this).kendoAnimate(extend(true, {}, options));
options.complete = null;
options.previous = null;
});
}
});
var Effect = kendo.Class.extend({
init: function(element, direction) {
var that = this;
that.element = element;
that._direction = direction;
that.options = {};
that._additionalEffects = [];
if (!that.restore) {
that.restore = [];
}
},
// Public API
reverse: function() {
this._reverse = true;
return this.run();
},
play: function() {
this._reverse = false;
return this.run();
},
add: function(additional) {
this._additionalEffects.push(additional);
return this;
},
direction: function(value) {
this._direction = value;
return this;
},
duration: function(duration) {
this._duration = duration;
return this;
},
compositeRun: function() {
var that = this, effectSet = new EffectSet(that.element, {
reverse: that._reverse,
duration: that._duration
}), effects = that._additionalEffects.concat([ that ]);
return effectSet.run(effects);
},
run: function() {
if (this._additionalEffects && this._additionalEffects[0]) {
return this.compositeRun();
}
var that = this, element = that.element, idx = 0, restore = that.restore, length = restore.length, value, deferred = $.Deferred(), start = {}, end = {}, target, children = that.children(), childrenLength = children.length;
deferred.then($.proxy(that, "_complete"));
element.data("animating", true);
for (idx = 0; idx < length; idx++) {
value = restore[idx];
if (!element.data(value)) {
element.data(value, element.css(value));
}
}
for (idx = 0; idx < childrenLength; idx++) {
children[idx].duration(that._duration).run();
}
that.prepare(start, end);
if (!element.is(":visible")) {
extend(start, {
display: element.data("olddisplay") || "block"
});
}
if (transforms) {
target = element.data("targetTransform");
if (target) {
start = extend(target, start);
}
}
start = normalizeCSS(element, start);
if (transforms && !transitions) {
start = strip3DTransforms(start);
}
element.css(start).css(TRANSFORM);
// Nudge
that.setup();
element.data("targetTransform", end);
kendo.fx.animate(element, end, {
duration: that._duration,
complete: deferred.resolve
});
return deferred.promise();
},
stop: function() {
var idx = 0, children = this.children(), childrenLength = children.length;
for (idx = 0; idx < childrenLength; idx++) {
children[idx].stop();
}
$(this.element).kendoStop(true, true);
return this;
},
restoreCallback: function() {
var element = this.element;
for (var i = 0, length = this.restore.length; i < length; i++) {
var value = this.restore[i];
element.css(value, element.data(value));
}
},
_complete: function() {
var that = this, element = that.element;
element.removeData("animating").dequeue();
// call next animation from the queue
that.restoreCallback();
if (that.shouldHide()) {
element.data("olddisplay", element.css("display")).hide();
}
if (hasZoom && !transforms) {
setTimeout($.proxy(that, "restoreCallback"), 0);
}
that.teardown();
},
/////////////////////////// Support for kendo.animate;
setOptions: function(options) {
extend(true, this.options, options);
},
children: function() {
return [];
},
shouldHide: $.noop,
setup: $.noop,
prepare: $.noop,
teardown: $.noop,
directions: [],
setReverse: function(reverse) {
this._reverse = reverse;
return this;
}
});
function toUpperCase(letter) {
return letter.toUpperCase();
}
function capitalize(word) {
return word.replace(/^./, toUpperCase);
}
function createEffect(name, definition) {
var effectClass = Effect.extend(definition), directions = effectClass.prototype.directions;
Effects[name] = effectClass;
fx.Element.prototype[name] = function(direction, opt1, opt2, opt3) {
return new effectClass(this.element, direction, opt1, opt2, opt3);
};
each(directions, function(idx, theDirection) {
fx.Element.prototype[name + capitalize(theDirection)] = function(opt1, opt2, opt3) {
return new effectClass(this.element, theDirection, opt1, opt2, opt3);
};
});
}
var FOUR_DIRECTIONS = [ "left", "right", "up", "down" ], IN_OUT = [ "in", "out" ];
createEffect("slideIn", {
directions: FOUR_DIRECTIONS,
prepare: function(start, end) {
var that = this, tmp, element = that.element, direction = directions[that._direction], offset = -direction.modifier * (direction.vertical ? element.outerHeight() : element.outerWidth()), startValue = offset / (that.options && that.options.divisor || 1) + PX, endValue = "0px";
if (that._reverse) {
tmp = start;
start = end;
end = tmp;
}
if (transforms) {
start[direction.transition] = startValue;
end[direction.transition] = endValue;
} else {
start[direction.property] = startValue;
end[direction.property] = endValue;
}
}
});
createEffect("tile", {
directions: FOUR_DIRECTIONS,
init: function(element, direction, previous) {
Effect.prototype.init.call(this, element, direction);
this.options = {
previous: previous
};
},
children: function() {
var that = this, reverse = that._reverse, previous = that.options.previous, dir = that._direction;
var children = [ fx(that.element).slideIn(dir).setReverse(reverse) ];
if (previous) {
children.push(fx(previous).slideIn(directions[dir].reverse).setReverse(!reverse));
}
return children;
}
});
function createToggleEffect(name, property, endValue) {
createEffect(name, {
directions: IN_OUT,
restore: [ property ],
startValue: function(value) {
this._startValue = value;
return this;
},
endValue: function(value) {
this._endValue = value;
return this;
},
shouldHide: function() {
return this._direction === "out" && this._end() === endValue ? !this._reverse : this._reverse;
},
_end: function() {
return this._endValue || endValue;
},
_start: function() {
return this._startValue || 1;
},
prepare: function(start, end) {
var that = this, opacity = that.element.data(property), out = that.shouldHide(), value = isNaN(opacity) || opacity === "" ? that._start() : opacity;
start[property] = end[property] = that._end();
if (out) {
start[property] = value;
} else {
end[property] = value;
}
}
});
}
createToggleEffect("fade", "opacity", 0);
createToggleEffect("zoom", "scale", .01);
createEffect("slideMargin", {
prepare: function(start, end) {
var that = this, element = that.element, options = that.options, origin = element.data(ORIGIN), offset = options.offset, margin, reverse = that._reverse;
if (!reverse && origin === null) {
element.data(ORIGIN, parseFloat(element.css("margin-" + options.axis)));
}
margin = element.data(ORIGIN) || 0;
end["margin-" + options.axis] = !reverse ? margin + offset : margin;
}
});
createEffect("slideTo", {
prepare: function(start, end) {
var that = this, element = that.element, options = that.options, offset = options.offset.split(","), reverse = that._reverse;
if (transforms) {
end.translatex = !reverse ? offset[0] : 0;
end.translatey = !reverse ? offset[1] : 0;
} else {
end.left = !reverse ? offset[0] : 0;
end.top = !reverse ? offset[1] : 0;
}
element.css("left");
}
});
createEffect("expand", {
directions: [ "horizontal", "vertical" ],
restore: [ OVERFLOW ],
prepare: function(start, end) {
var that = this, element = that.element, options = that.options, reverse = that._reverse, property = that._direction === "vertical" ? HEIGHT : WIDTH, setLength = element[0].style[property], oldLength = element.data(property), length = parseFloat(oldLength || setLength), realLength = round(element.css(property, AUTO)[property]());
start.overflow = HIDDEN;
length = options && options.reset ? realLength || length : length || realLength;
end[property] = (reverse ? 0 : length) + PX;
start[property] = (reverse ? length : 0) + PX;
if (oldLength === undefined) {
element.data(property, setLength);
}
},
shouldHide: function() {
return this._reverse;
},
teardown: function() {
var that = this, element = that.element, property = that._direction === "vertical" ? HEIGHT : WIDTH, length = element.data(property);
if (length == AUTO || length === BLANK) {
setTimeout(function() {
element.css(property, AUTO).css(property);
}, 0);
}
}
});
var TRANSFER_START_STATE = {
position: "absolute",
marginLeft: 0,
marginTop: 0,
scale: 1
};
/**
* Intersection point formulas are taken from here - http://zonalandeducation.com/mmts/intersections/intersectionOfTwoLines1/intersectionOfTwoLines1.html
* Formula for a linear function from two points from here - http://demo.activemath.org/ActiveMath2/search/show.cmd?id=mbase://AC_UK_calculus/functions/ex_linear_equation_two_points
* The transform origin point is the intersection point of the two lines from the top left corners/top right corners of the element and target.
* The math and variables below MAY BE SIMPLIFIED (zeroes removed), but this would make the formula too cryptic.
*/
createEffect("transfer", {
init: function(element, target) {
this.element = element;
this.options = {
target: target
};
this.restore = [];
},
setup: function() {
this.element.appendTo(document.body);
},
prepare: function(start, end) {
var that = this, element = that.element, options = that.options, reverse = that._reverse, target = options.target, offset, currentScale = animationProperty(element, "scale"), targetOffset = target.offset(), scale = target.outerHeight() / element.outerHeight();
extend(start, TRANSFER_START_STATE);
end.scale = 1;
element.css(TRANSFORM, "scale(1)").css(TRANSFORM);
offset = element.offset();
element.css(TRANSFORM, "scale(" + currentScale + ")");
var x1 = 0, y1 = 0, x2 = targetOffset.left - offset.left, y2 = targetOffset.top - offset.top, x3 = x1 + element.outerWidth(), y3 = y1, x4 = x2 + target.outerWidth(), y4 = y2, Z1 = (y2 - y1) / (x2 - x1), Z2 = (y4 - y3) / (x4 - x3), X = (y1 - y3 - Z1 * x1 + Z2 * x3) / (Z2 - Z1), Y = y1 + Z1 * (X - x1);
start.top = offset.top;
start.left = offset.left;
start.transformOrigin = X + PX + " " + Y + PX;
if (reverse) {
start.scale = scale;
} else {
end.scale = scale;
}
}
});
var CLIPS = {
top: "rect(auto auto $size auto)",
bottom: "rect($size auto auto auto)",
left: "rect(auto $size auto auto)",
right: "rect(auto auto auto $size)"
};
var ROTATIONS = {
top: {
start: "rotatex(0deg)",
end: "rotatex(180deg)"
},
bottom: {
start: "rotatex(-180deg)",
end: "rotatex(0deg)"
},
left: {
start: "rotatey(0deg)",
end: "rotatey(-180deg)"
},
right: {
start: "rotatey(180deg)",
end: "rotatey(0deg)"
}
};
function clipInHalf(container, direction) {
var vertical = kendo.directions[direction].vertical, size = container[vertical ? HEIGHT : WIDTH]() / 2 + "px";
return CLIPS[direction].replace("$size", size);
}
createEffect("turningPage", {
directions: FOUR_DIRECTIONS,
init: function(element, direction, container) {
Effect.prototype.init.call(this, element, direction);
this._container = container;
},
prepare: function(start, end) {
var that = this, reverse = that._reverse, direction = reverse ? directions[that._direction].reverse : that._direction, rotation = ROTATIONS[direction];
start.zIndex = 1;
if (that._clipInHalf) {
start.clip = clipInHalf(that._container, kendo.directions[direction].reverse);
}
start[BACKFACE] = HIDDEN;
end[TRANSFORM] = TRANSFORM_PERSPECTIVE + (reverse ? rotation.start : rotation.end);
start[TRANSFORM] = TRANSFORM_PERSPECTIVE + (reverse ? rotation.end : rotation.start);
},
setup: function() {
this._container.append(this.element);
},
face: function(value) {
this._face = value;
return this;
},
shouldHide: function() {
var that = this, reverse = that._reverse, face = that._face;
return reverse && !face || !reverse && face;
},
clipInHalf: function(value) {
this._clipInHalf = value;
return this;
},
temporary: function(value) {
this._temporary = value;
return this;
},
teardown: function() {
if (this._temporary) {
this.element.remove();
}
}
});
createEffect("staticPage", {
directions: FOUR_DIRECTIONS,
init: function(element, direction, container) {
Effect.prototype.init.call(this, element, direction);
this._container = container;
},
restore: [ "clip" ],
prepare: function(start) {
var that = this, direction = that._reverse ? directions[that._direction].reverse : that._direction;
start.clip = clipInHalf(that._container, direction);
},
shouldHide: function() {
var that = this, reverse = that._reverse, face = that._face;
return reverse && !face || !reverse && face;
},
face: function(value) {
this._face = value;
return this;
}
});
createEffect("pageturn", {
directions: [ "horizontal", "vertical" ],
init: function(element, direction, face, back) {
Effect.prototype.init.call(this, element, direction);
this.options = {};
this.options.face = face;
this.options.back = back;
},
children: function() {
var that = this, options = that.options, direction = that._direction === "horizontal" ? "left" : "top", reverseDirection = kendo.directions[direction].reverse, reverse = that._reverse, temp, faceClone = options.face.clone(true).removeAttr("id"), backClone = options.back.clone(true).removeAttr("id"), element = that.element;
if (reverse) {
temp = direction;
direction = reverseDirection;
reverseDirection = temp;
}
return [ fx(options.face).staticPage(direction, element).face(true).setReverse(reverse), fx(options.back).staticPage(reverseDirection, element).setReverse(reverse), fx(faceClone).turningPage(direction, element).face(true).clipInHalf(true).temporary(true).setReverse(reverse), fx(backClone).turningPage(reverseDirection, element).clipInHalf(true).temporary(true).setReverse(reverse) ];
},
prepare: function(start) {
start[PERSPECTIVE] = DEFAULT_PERSPECTIVE;
start.transformStyle = "preserve-3d";
},
teardown: function() {
this.element.find(".temp-pages").remove();
}
});
createEffect("flip", {
directions: [ "horizontal", "vertical" ],
init: function(element, direction, face, back) {
Effect.prototype.init.call(this, element, direction);
this.options = {};
this.options.face = face;
this.options.back = back;
},
children: function() {
var that = this, options = that.options, direction = that._direction === "horizontal" ? "left" : "top", reverseDirection = kendo.directions[direction].reverse, reverse = that._reverse, temp, element = that.element;
if (reverse) {
temp = direction;
direction = reverseDirection;
reverseDirection = temp;
}
return [ fx(options.face).turningPage(direction, element).face(true).setReverse(reverse), fx(options.back).turningPage(reverseDirection, element).setReverse(reverse) ];
},
prepare: function(start) {
start[PERSPECTIVE] = DEFAULT_PERSPECTIVE;
start.transformStyle = "preserve-3d";
}
});
var animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
setTimeout(callback, 1e3 / 60);
};
var Animation = kendo.Class.extend({
init: function() {
var that = this;
that._tickProxy = proxy(that._tick, that);
that._started = false;
},
tick: $.noop,
done: $.noop,
onEnd: $.noop,
onCancel: $.noop,
start: function() {
if (!this.done()) {
this._started = true;
animationFrame(this._tickProxy);
}
},
cancel: function() {
this._started = false;
this.onCancel();
},
_tick: function() {
var that = this;
if (!that._started) {
return;
}
that.tick();
if (!that.done()) {
animationFrame(that._tickProxy);
} else {
that._started = false;
that.onEnd();
}
}
});
var Transition = Animation.extend({
init: function(options) {
var that = this;
extend(that, options);
Animation.fn.init.call(that);
},
done: function() {
return this.timePassed() >= this.duration;
},
timePassed: function() {
return Math.min(this.duration, Date.now() - this.startDate);
},
moveTo: function(options) {
var that = this, movable = that.movable;
that.initial = movable[that.axis];
that.delta = options.location - that.initial;
that.duration = options.duration || 300;
that.tick = that._easeProxy(options.ease);
that.startDate = Date.now();
that.start();
},
_easeProxy: function(ease) {
var that = this;
return function() {
that.movable.moveAxis(that.axis, ease(that.timePassed(), that.initial, that.delta, that.duration));
};
}
});
extend(Transition, {
easeOutExpo: function(t, b, c, d) {
return t == d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
},
easeOutBack: function(t, b, c, d, s) {
s = 1.70158;
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
}
});
fx.Animation = Animation;
fx.Transition = Transition;
fx.createEffect = createEffect;
fx.Effects = Effects;
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, extend = $.extend, odataFilters = {
eq: "eq",
neq: "ne",
gt: "gt",
gte: "ge",
lt: "lt",
lte: "le",
contains: "substringof",
doesnotcontain: "substringof",
endswith: "endswith",
startswith: "startswith"
}, mappers = {
pageSize: $.noop,
page: $.noop,
filter: function(params, filter) {
if (filter) {
params.$filter = toOdataFilter(filter);
}
},
sort: function(params, orderby) {
var expr = $.map(orderby, function(value) {
var order = value.field.replace(/\./g, "/");
if (value.dir === "desc") {
order += " desc";
}
return order;
}).join(",");
if (expr) {
params.$orderby = expr;
}
},
skip: function(params, skip) {
if (skip) {
params.$skip = skip;
}
},
take: function(params, take) {
if (take) {
params.$top = take;
}
}
}, defaultDataType = {
read: {
dataType: "jsonp"
}
};
function toOdataFilter(filter) {
var result = [], logic = filter.logic || "and", idx, length, field, type, format, operator, value, ignoreCase, filters = filter.filters;
for (idx = 0, length = filters.length; idx < length; idx++) {
filter = filters[idx];
field = filter.field;
value = filter.value;
operator = filter.operator;
if (filter.filters) {
filter = toOdataFilter(filter);
} else {
ignoreCase = filter.ignoreCase;
field = field.replace(/\./g, "/");
filter = odataFilters[operator];
if (filter && value !== undefined) {
type = $.type(value);
if (type === "string") {
format = "'{1}'";
value = value.replace(/'/g, "''");
if (ignoreCase === true) {
field = "tolower(" + field + ")";
}
} else if (type === "date") {
format = "datetime'{1:yyyy-MM-ddTHH:mm:ss}'";
} else {
format = "{1}";
}
if (filter.length > 3) {
if (filter !== "substringof") {
format = "{0}({2}," + format + ")";
} else {
format = "{0}(" + format + ",{2})";
if (operator === "doesnotcontain") {
format += " eq false";
}
}
} else {
format = "{2} {0} " + format;
}
filter = kendo.format(format, filter, value, field);
}
}
result.push(filter);
}
filter = result.join(" " + logic + " ");
if (result.length > 1) {
filter = "(" + filter + ")";
}
return filter;
}
extend(true, kendo.data, {
schemas: {
odata: {
type: "json",
data: function(data) {
return data.d.results || [ data.d ];
},
total: "d.__count"
}
},
transports: {
odata: {
read: {
cache: true,
// to prevent jQuery from adding cache buster
dataType: "jsonp",
jsonp: "$callback"
},
update: {
cache: true,
dataType: "json",
contentType: "application/json",
// to inform the server the the request body is JSON encoded
type: "PUT"
},
create: {
cache: true,
dataType: "json",
contentType: "application/json",
type: "POST"
},
destroy: {
cache: true,
dataType: "json",
type: "DELETE"
},
parameterMap: function(options, type) {
var params, value, option, dataType;
options = options || {};
type = type || "read";
dataType = (this.options || defaultDataType)[type];
dataType = dataType ? dataType.dataType : "json";
if (type === "read") {
params = {
$inlinecount: "allpages"
};
if (dataType != "json") {
params.$format = "json";
}
for (option in options) {
if (mappers[option]) {
mappers[option](params, options[option]);
} else {
params[option] = options[option];
}
}
} else {
if (dataType !== "json") {
throw new Error("Only json dataType can be used for " + type + " operation.");
}
if (type !== "destroy") {
for (option in options) {
value = options[option];
if (typeof value === "number") {
options[option] = value + "";
}
}
params = kendo.stringify(options);
}
}
return params;
}
}
}
});
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, isArray = $.isArray, isPlainObject = $.isPlainObject, map = $.map, each = $.each, extend = $.extend, getter = kendo.getter, Class = kendo.Class;
var XmlDataReader = Class.extend({
init: function(options) {
var that = this, total = options.total, model = options.model, parse = options.parse, errors = options.errors, data = options.data;
if (model) {
if (isPlainObject(model)) {
if (model.fields) {
each(model.fields, function(field, value) {
if (isPlainObject(value) && value.field) {
value = extend(value, {
field: that.getter(value.field)
});
} else {
value = {
field: that.getter(value)
};
}
model.fields[field] = value;
});
}
var id = model.id;
if (id) {
var idField = {};
idField[that.xpathToMember(id, true)] = {
field: that.getter(id)
};
model.fields = extend(idField, model.fields);
model.id = that.xpathToMember(id);
}
model = kendo.data.Model.define(model);
}
that.model = model;
}
if (total) {
if (typeof total == "string") {
total = that.getter(total);
that.total = function(data) {
return parseInt(total(data), 10);
};
} else if (typeof total == "function") {
that.total = total;
}
}
if (errors) {
if (typeof errors == "string") {
errors = that.getter(errors);
that.errors = function(data) {
return errors(data) || null;
};
} else if (typeof errors == "function") {
that.errors = errors;
}
}
if (data) {
if (typeof data == "string") {
data = that.xpathToMember(data);
that.data = function(value) {
var result = that.evaluate(value, data), modelInstance;
result = isArray(result) ? result : [ result ];
if (that.model && model.fields) {
modelInstance = new that.model();
return map(result, function(value) {
if (value) {
var record = {}, field;
for (field in model.fields) {
record[field] = modelInstance._parse(field, model.fields[field].field(value));
}
return record;
}
});
}
return result;
};
} else if (typeof data == "function") {
that.data = data;
}
}
if (typeof parse == "function") {
var xmlParse = that.parse;
that.parse = function(data) {
var xml = parse.call(that, data);
return xmlParse.call(that, xml);
};
}
},
total: function(result) {
return this.data(result).length;
},
errors: function(data) {
return data ? data.errors : null;
},
parseDOM: function(element) {
var result = {}, parsedNode, node, nodeType, nodeName, member, attribute, attributes = element.attributes, attributeCount = attributes.length, idx;
for (idx = 0; idx < attributeCount; idx++) {
attribute = attributes[idx];
result["@" + attribute.nodeName] = attribute.nodeValue;
}
for (node = element.firstChild; node; node = node.nextSibling) {
nodeType = node.nodeType;
if (nodeType === 3 || nodeType === 4) {
// text nodes or CDATA are stored as #text field
result["#text"] = node.nodeValue;
} else if (nodeType === 1) {
// elements are stored as fields
parsedNode = this.parseDOM(node);
nodeName = node.nodeName;
member = result[nodeName];
if (isArray(member)) {
// elements of same nodeName are stored as array
member.push(parsedNode);
} else if (member !== undefined) {
member = [ member, parsedNode ];
} else {
member = parsedNode;
}
result[nodeName] = member;
}
}
return result;
},
evaluate: function(value, expression) {
var members = expression.split("."), member, result, length, intermediateResult, idx;
while (member = members.shift()) {
value = value[member];
if (isArray(value)) {
result = [];
expression = members.join(".");
for (idx = 0, length = value.length; idx < length; idx++) {
intermediateResult = this.evaluate(value[idx], expression);
intermediateResult = isArray(intermediateResult) ? intermediateResult : [ intermediateResult ];
result.push.apply(result, intermediateResult);
}
return result;
}
}
return value;
},
parse: function(xml) {
var documentElement, tree, result = {};
documentElement = xml.documentElement || $.parseXML(xml).documentElement;
tree = this.parseDOM(documentElement);
result[documentElement.nodeName] = tree;
return result;
},
xpathToMember: function(member, raw) {
if (!member) {
return "";
}
member = member.replace(/^\//, "").replace(/\//g, ".");
// replace all "/" with "."
if (member.indexOf("@") >= 0) {
// replace @attribute with '["@attribute"]'
return member.replace(/\.?(@.*)/, raw ? "$1" : '["$1"]');
}
if (member.indexOf("text()") >= 0) {
// replace ".text()" with '["#text"]'
return member.replace(/(\.?text\(\))/, raw ? "#text" : '["#text"]');
}
return member;
},
getter: function(member) {
return getter(this.xpathToMember(member), true);
}
});
$.extend(true, kendo.data, {
XmlDataReader: XmlDataReader,
readers: {
xml: XmlDataReader
}
});
})(window.kendo.jQuery);
(function($, undefined) {
var extend = $.extend, proxy = $.proxy, isFunction = $.isFunction, isPlainObject = $.isPlainObject, isEmptyObject = $.isEmptyObject, isArray = $.isArray, grep = $.grep, ajax = $.ajax, map, each = $.each, noop = $.noop, kendo = window.kendo, 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);
}
});
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;
}
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;
if (field.indexOf(".")) {
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;
}
path += ".";
}
}
kendo.setter(field)(that, value);
},
set: function(field, value) {
var that = this, current = kendo.getter(field, true)(that);
if (current !== value) {
if (!that.trigger("set", {
field: field,
value: value
})) {
that._set(field, that.wrap(value, field, function() {
return that;
}));
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;
},
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) {
that._set(field, that.wrap(data[field], field, parent));
}
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 = {}, 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;
name = typeof field.field === STRING ? field.field : name;
if (!field.nullable) {
value = proto.defaults[name] = field.defaultValue !== undefined ? field.defaultValue : defaultValues[type.toLowerCase()];
}
if (options.id === name) {
proto._defaultId = value;
}
proto.defaults[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);
},
asc: 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 && !b && a > 0 || b == null) {
return 1;
}
if (b && !a && b > 0) {
return -1;
}
return a > b ? 1 : a < b ? -1 : 0;
};
},
desc: 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 && !b && a > 0 || b == null) {
return -1;
}
if (b && !a && b > 0 || a == null) {
return 1;
}
return a < b ? 1 : a > b ? -1 : 0;
};
},
create: function(descriptor) {
return this[descriptor.dir.toLowerCase()](descriptor.field);
},
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 PositionComparer = 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 (valueB == null) {
return 1;
}
return valueA > valueB ? 1 : valueA < valueB ? -1 : 0;
};
},
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;
}
return valueA < valueB ? 1 : valueA > valueB ? -1 : 0;
};
}
});
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));
},
orderBy: function(selector) {
var result = this.data.slice(0), comparer = isFunction(selector) || !selector ? Comparer.asc(selector) : selector.compare;
return new Query(result.sort(comparer));
},
orderByDescending: function(selector) {
return new Query(this.data.slice(0).sort(Comparer.desc(selector)));
},
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
} ]);
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, PositionComparer).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);
accumulator = accumulator || value;
if (accumulator > value) {
accumulator = value;
}
return accumulator;
}
};
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 convertRecords(data, getters, modelInstance) {
var record, getter, 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));
}
}
}
function convertGroup(data, getters, modelInstance) {
var record, idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
record = data[idx];
record.value = modelInstance._parse(record.field, record.value);
if (record.hasSubgroups) {
convertGroup(record.items, getters, modelInstance);
} else {
convertRecords(record.items, getters, modelInstance);
}
}
}
function wrapDataAccess(originalFunction, model, converter, getters) {
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());
}
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), getters = {};
model = that.model;
if (model.fields) {
each(model.fields, function(field, value) {
if (isPlainObject(value) && value.field) {
getters[value.field] = getter(value.field);
} else {
getters[field] = getter(field);
}
});
}
that.data = wrapDataAccess(dataFunction, model, convertRecords, getters);
that.groups = wrapDataAccess(groupsFunction, model, convertGroup, getters);
}
},
errors: function(data) {
return data ? data.errors : null;
},
parse: identity,
data: identity,
total: function(data) {
return data.length;
},
groups: identity,
status: function(data) {
return data.status;
},
aggregates: function() {
return {};
}
});
function mergeGroups(target, dest, start, count) {
var group, idx = 0, items;
while (dest.length && count) {
group = dest[idx];
items = group.items;
if (target && target.field === group.field && target.value === group.value) {
if (target.hasSubgroups && target.items.length) {
mergeGroups(target.items[target.items.length - 1], group.items, start, count);
} else {
items = items.slice(start, count);
count -= items.length;
target.items = target.items.concat(items);
}
dest.splice(idx--, 1);
} else {
items = items.slice(start, count);
count -= items.length;
group.items = items;
if (!group.items.length) {
dest.splice(idx--, 1);
}
}
start = 0;
if (++idx >= dest.length) {
break;
}
}
}
function flattenGroups(data) {
var idx, length, result = [];
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].hasSubgroups) {
result = result.concat(flattenGroups(data[idx].items));
} else {
result = result.concat(data[idx].items.slice());
}
}
return result;
}
function wrapGroupItems(data, model) {
var idx, length, group, items;
if (model) {
for (idx = 0, length = data.length; idx < length; idx++) {
group = data[idx];
items = group.items;
if (group.hasSubgroups) {
wrapGroupItems(items, model);
} else if (items.length && !(items[0] instanceof model)) {
items.type = model;
items.wrapAll(items, items);
}
}
}
}
function eachGroupItems(data, func) {
var idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].hasSubgroups) {
if (eachGroupItems(data[idx].items, func)) {
return true;
}
} else if (func(data[idx].items, data[idx])) {
return true;
}
}
}
function removeModel(data, model) {
var idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].uid == model.uid) {
model = data[idx];
data.splice(idx, 1);
return model;
}
}
}
function wrapInEmptyGroup(groups, model) {
var parent, group, idx, length;
for (idx = groups.length - 1, length = 0; idx >= length; idx--) {
group = groups[idx];
parent = {
value: model.get(group.field),
field: group.field,
items: parent ? [ parent ] : [ model ],
hasSubgroups: !!parent,
aggregates: {}
};
}
return parent;
}
function indexOfPristineModel(data, model) {
if (model) {
return indexOf(data, function(item) {
return item[model.idField] === model.id;
});
}
return -1;
}
function indexOfModel(data, model) {
if (model) {
return indexOf(data, function(item) {
return item.uid == model.uid;
});
}
return -1;
}
function indexOf(data, comparer) {
var idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
if (comparer(data[idx])) {
return idx;
}
}
return -1;
}
var DataSource = Observable.extend({
init: function(options) {
var that = this, model, data;
if (options) {
data = options.data;
}
options = that.options = extend({}, that.options, options);
that._map = {};
that._prefetch = {};
that._data = [];
that._ranges = [];
that._view = [];
that._pristine = [];
that._destroyed = [];
that._pageSize = options.pageSize;
that._page = options.page || (options.pageSize ? 1 : undefined);
that._sort = normalizeSort(options.sort);
that._filter = normalizeFilter(options.filter);
that._group = normalizeGroup(options.group);
that._aggregate = options.aggregate;
that._total = options.total;
Observable.fn.init.call(that);
that.transport = Transport.create(options, data);
that.reader = new kendo.data.readers[options.schema.type || "json"](options.schema);
model = that.reader.model || {};
that._data = that._observe(that._data);
that.bind([ ERROR, CHANGE, REQUESTSTART, SYNC, REQUESTEND, PROGRESS ], options);
},
options: {
data: [],
schema: {
modelBase: Model
},
serverSorting: false,
serverPaging: false,
serverFiltering: false,
serverGrouping: false,
serverAggregates: false,
batch: false
},
_isServerGrouped: function() {
var group = this.group() || [];
return this.options.serverGrouping && group.length;
},
_flatData: function(data) {
if (this._isServerGrouped()) {
return flattenGroups(data);
}
return data;
},
parent: noop,
get: function(id) {
var idx, length, data = this._flatData(this._data);
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].id == id) {
return data[idx];
}
}
},
getByUid: function(id) {
var idx, length, data = this._flatData(this._data);
if (!data) {
return;
}
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].uid == id) {
return data[idx];
}
}
},
indexOf: function(model) {
return indexOfModel(this._data, model);
},
at: function(index) {
return this._data[index];
},
data: function(value) {
var that = this;
if (value !== undefined) {
that._data = this._observe(value);
that._ranges = [];
that._addRange(that._data);
that._total = that._data.length;
that._process(that._data);
} else {
return that._data;
}
},
view: function() {
return this._view;
},
add: function(model) {
return this.insert(this._data.length, model);
},
insert: function(index, model) {
if (!model) {
model = index;
index = 0;
}
if (!(model instanceof Model)) {
if (this.reader.model) {
model = new this.reader.model(model);
} else {
model = new ObservableObject(model);
}
}
if (this._isServerGrouped()) {
this._data.splice(index, 0, wrapInEmptyGroup(this.group(), model));
} else {
this._data.splice(index, 0, model);
}
return model;
},
remove: function(model) {
var result, that = this, hasGroups = that._isServerGrouped();
this._eachItem(that._data, function(items) {
result = removeModel(items, model);
if (result && hasGroups) {
if (!result.isNew || !result.isNew()) {
that._destroyed.push(result);
}
return true;
}
});
return model;
},
sync: function() {
var that = this, idx, length, created = [], updated = [], destroyed = that._destroyed, data = that._flatData(that._data);
if (!that.reader.model) {
return;
}
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].isNew()) {
created.push(data[idx]);
} else if (data[idx].dirty) {
updated.push(data[idx]);
}
}
var promises = that._send("create", created);
promises.push.apply(promises, that._send("update", updated));
promises.push.apply(promises, that._send("destroy", destroyed));
$.when.apply(null, promises).then(function() {
var idx, length;
for (idx = 0, length = arguments.length; idx < length; idx++) {
that._accept(arguments[idx]);
}
that._change({
action: "sync"
});
that.trigger(SYNC);
});
},
cancelChanges: function(model) {
var that = this, pristine = that._readData(that._pristine);
if (model instanceof kendo.data.Model) {
that._cancelModel(model);
} else {
that._destroyed = [];
that._data = that._observe(pristine);
if (that.options.serverPaging) {
that._total = that.reader.total(that._pristine);
}
that._change();
}
},
hasChanges: function() {
var idx, length, data = this._data;
if (this._destroyed.length) {
return true;
}
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].isNew() || data[idx].dirty) {
return true;
}
}
return false;
},
_accept: function(result) {
var that = this, models = result.models, response = result.response, idx = 0, serverGroup = that._isServerGrouped(), pristine = that._readData(that._pristine), type = result.type, length;
that.trigger(REQUESTEND, {
response: response,
type: type
});
if (response) {
response = that.reader.parse(response);
if (that._handleCustomErrors(response)) {
return;
}
response = that.reader.data(response);
if (!$.isArray(response)) {
response = [ response ];
}
} else {
response = $.map(models, function(model) {
return model.toJSON();
});
}
if (type === "destroy") {
that._destroyed = [];
}
for (idx = 0, length = models.length; idx < length; idx++) {
if (type !== "destroy") {
models[idx].accept(response[idx]);
if (type === "create") {
pristine.push(serverGroup ? wrapInEmptyGroup(that.group(), models[idx]) : response[idx]);
} else if (type === "update") {
that._updatePristineForModel(models[idx], response[idx]);
}
} else {
that._removePristineForModel(models[idx]);
}
}
},
_updatePristineForModel: function(model, values) {
this._executeOnPristineForModel(model, function(index, items) {
extend(true, items[index], values);
});
},
_executeOnPristineForModel: function(model, callback) {
this._eachPristineItem(function(items) {
var index = indexOfPristineModel(items, model);
if (index > -1) {
callback(index, items);
return true;
}
});
},
_removePristineForModel: function(model) {
this._executeOnPristineForModel(model, function(index, items) {
items.splice(index, 1);
});
},
_readData: function(data) {
var read = !this._isServerGrouped() ? this.reader.data : this.reader.groups;
return read(data);
},
_eachPristineItem: function(callback) {
this._eachItem(this._readData(this._pristine), callback);
},
_eachItem: function(data, callback) {
if (data && data.length) {
if (this._isServerGrouped()) {
eachGroupItems(data, callback);
} else {
callback(data);
}
}
},
_pristineForModel: function(model) {
var pristine, idx, callback = function(items) {
idx = indexOfPristineModel(items, model);
if (idx > -1) {
pristine = items[idx];
return true;
}
};
this._eachPristineItem(callback);
return pristine;
},
_cancelModel: function(model) {
var pristine = this._pristineForModel(model), idx;
this._eachItem(this._data, function(items) {
idx = indexOfModel(items, model);
if (idx != -1) {
if (!model.isNew() && pristine) {
items[idx].accept(pristine);
} else {
items.splice(idx, 1);
}
}
});
},
_promise: function(data, models, type) {
var that = this, transport = that.transport;
return $.Deferred(function(deferred) {
transport[type].call(transport, extend({
success: function(response) {
deferred.resolve({
response: response,
models: models,
type: type
});
},
error: function(response, status, error) {
deferred.reject(response);
that.error(response, status, error);
}
}, data));
}).promise();
},
_send: function(method, data) {
var that = this, idx, length, promises = [];
if (that.options.batch) {
if (data.length) {
promises.push(that._promise({
data: {
models: toJSON(data)
}
}, data, method));
}
} else {
for (idx = 0, length = data.length; idx < length; idx++) {
promises.push(that._promise({
data: data[idx].toJSON()
}, [ data[idx] ], method));
}
}
return promises;
},
read: function(data) {
var that = this, params = that._params(data);
that._queueRequest(params, function() {
if (!that.trigger(REQUESTSTART)) {
that.trigger(PROGRESS);
that._ranges = [];
that.transport.read({
data: params,
success: proxy(that.success, that),
error: proxy(that.error, that)
});
} else {
that._dequeueRequest();
}
});
},
success: function(data) {
var that = this, options = that.options;
that.trigger(REQUESTEND, {
response: data,
type: "read"
});
data = that.reader.parse(data);
if (that._handleCustomErrors(data)) {
that._dequeueRequest();
return;
}
that._pristine = isPlainObject(data) ? $.extend(true, {}, data) : data.slice ? data.slice(0) : data;
that._total = that.reader.total(data);
if (that._aggregate && options.serverAggregates) {
that._aggregateResult = that.reader.aggregates(data);
}
data = that._readData(data);
that._data = that._observe(data);
that._addRange(that._data);
that._dequeueRequest();
that._process(that._data);
},
_addRange: function(data) {
var that = this, start = that._skip || 0, end = start + that._flatData(data).length;
that._ranges.push({
start: start,
end: end,
data: data
});
that._ranges.sort(function(x, y) {
return x.start - y.start;
});
},
error: function(xhr, status, errorThrown) {
this._dequeueRequest();
this.trigger(REQUESTEND, {});
this.trigger(ERROR, {
xhr: xhr,
status: status,
errorThrown: errorThrown
});
},
_params: function(data) {
var that = this, options = extend({
take: that.take(),
skip: that.skip(),
page: that.page(),
pageSize: that.pageSize(),
sort: that._sort,
filter: that._filter,
group: that._group,
aggregate: that._aggregate
}, data);
if (!that.options.serverPaging) {
delete options.take;
delete options.skip;
delete options.page;
delete options.pageSize;
}
if (!that.options.serverGrouping) {
delete options.group;
}
if (!that.options.serverFiltering) {
delete options.filter;
}
if (!that.options.serverSorting) {
delete options.sort;
}
if (!that.options.serverAggregates) {
delete options.aggregate;
}
return options;
},
_queueRequest: function(options, callback) {
var that = this;
if (!that._requestInProgress) {
that._requestInProgress = true;
that._pending = undefined;
callback();
} else {
that._pending = {
callback: proxy(callback, that),
options: options
};
}
},
_dequeueRequest: function() {
var that = this;
that._requestInProgress = false;
if (that._pending) {
that._queueRequest(that._pending.options, that._pending.callback);
}
},
_handleCustomErrors: function(response) {
if (this.reader.errors) {
var errors = this.reader.errors(response);
if (errors) {
this.trigger(ERROR, {
xhr: null,
status: "customerror",
errorThrown: "custom error",
errors: errors
});
return true;
}
}
return false;
},
_observe: function(data) {
var that = this, model = that.reader.model, wrap = false;
if (model && data.length) {
wrap = !(data[0] instanceof model);
}
if (data instanceof ObservableArray) {
if (wrap) {
data.type = that.reader.model;
data.wrapAll(data, data);
}
} else {
data = new ObservableArray(data, that.reader.model);
data.parent = function() {
return that.parent();
};
}
if (that._isServerGrouped()) {
wrapGroupItems(data, model);
}
return data.bind(CHANGE, proxy(that._change, that));
},
_change: function(e) {
var that = this, idx, length, action = e ? e.action : "";
if (action === "remove") {
for (idx = 0, length = e.items.length; idx < length; idx++) {
if (!e.items[idx].isNew || !e.items[idx].isNew()) {
that._destroyed.push(e.items[idx]);
}
}
}
if (that.options.autoSync && (action === "add" || action === "remove" || action === "itemchange")) {
that.sync();
} else {
var total = that._total || that.reader.total(that._pristine);
if (action === "add") {
total++;
} else if (action === "remove") {
total--;
} else if (action !== "itemchange" && action !== "sync" && !that.options.serverPaging) {
total = that.reader.total(that._pristine);
}
that._total = total;
that._process(that._data, e);
}
},
_process: function(data, e) {
var that = this, options = {}, result;
if (that.options.serverPaging !== true) {
options.skip = that._skip;
options.take = that._take || that._pageSize;
if (options.skip === undefined && that._page !== undefined && that._pageSize !== undefined) {
options.skip = (that._page - 1) * that._pageSize;
}
}
if (that.options.serverSorting !== true) {
options.sort = that._sort;
}
if (that.options.serverFiltering !== true) {
options.filter = that._filter;
}
if (that.options.serverGrouping !== true) {
options.group = that._group;
}
if (that.options.serverAggregates !== true) {
options.aggregate = that._aggregate;
that._aggregateResult = calculateAggregates(data, options);
}
result = Query.process(data, options);
that._view = result.data;
if (result.total !== undefined && !that.options.serverFiltering) {
that._total = result.total;
}
e = e || {};
e.items = e.items || that._view;
that.trigger(CHANGE, e);
},
_mergeState: function(options) {
var that = this;
if (options !== undefined) {
that._pageSize = options.pageSize;
that._page = options.page;
that._sort = options.sort;
that._filter = options.filter;
that._group = options.group;
that._aggregate = options.aggregate;
that._skip = options.skip;
that._take = options.take;
if (that._skip === undefined) {
that._skip = that.skip();
options.skip = that.skip();
}
if (that._take === undefined && that._pageSize !== undefined) {
that._take = that._pageSize;
options.take = that._take;
}
if (options.sort) {
that._sort = options.sort = normalizeSort(options.sort);
}
if (options.filter) {
that._filter = options.filter = normalizeFilter(options.filter);
}
if (options.group) {
that._group = options.group = normalizeGroup(options.group);
}
if (options.aggregate) {
that._aggregate = options.aggregate = normalizeAggregate(options.aggregate);
}
}
return options;
},
query: function(options) {
var that = this, result, remote = that.options.serverSorting || that.options.serverPaging || that.options.serverFiltering || that.options.serverGrouping || that.options.serverAggregates;
if (remote || that._data === undefined || that._data.length === 0) {
that.read(that._mergeState(options));
} else {
if (!that.trigger(REQUESTSTART)) {
that.trigger(PROGRESS);
result = Query.process(that._data, that._mergeState(options));
if (!that.options.serverFiltering) {
if (result.total !== undefined) {
that._total = result.total;
} else {
that._total = that._data.length;
}
}
that._view = result.data;
that._aggregateResult = calculateAggregates(that._data, options);
that.trigger(REQUESTEND, {});
that.trigger(CHANGE, {
items: result.data
});
}
}
},
fetch: function(callback) {
var that = this;
if (callback && isFunction(callback)) {
that.one(CHANGE, callback);
}
that._query();
},
_query: function(options) {
var that = this;
that.query(extend({}, {
page: that.page(),
pageSize: that.pageSize(),
sort: that.sort(),
filter: that.filter(),
group: that.group(),
aggregate: that.aggregate()
}, options));
},
next: function(options) {
var that = this, page = that.page(), total = that.total();
options = options || {};
if (!page || total && page + 1 > that.totalPages()) {
return;
}
that._skip = page * that.take();
page += 1;
options.page = page;
that._query(options);
return page;
},
prev: function(options) {
var that = this, page = that.page();
options = options || {};
if (!page || page === 1) {
return;
}
that._skip = that._skip - that.take();
page -= 1;
options.page = page;
that._query(options);
return page;
},
page: function(val) {
var that = this, skip;
if (val !== undefined) {
val = math.max(math.min(math.max(val, 1), that.totalPages()), 1);
that._query({
page: val
});
return;
}
skip = that.skip();
return skip !== undefined ? math.round((skip || 0) / (that.take() || 1)) + 1 : undefined;
},
pageSize: function(val) {
var that = this;
if (val !== undefined) {
that._query({
pageSize: val,
page: 1
});
return;
}
return that.take();
},
sort: function(val) {
var that = this;
if (val !== undefined) {
that._query({
sort: val
});
return;
}
return that._sort;
},
filter: function(val) {
var that = this;
if (val === undefined) {
return that._filter;
}
that._query({
filter: val,
page: 1
});
},
group: function(val) {
var that = this;
if (val !== undefined) {
that._query({
group: val
});
return;
}
return that._group;
},
total: function() {
return this._total || 0;
},
aggregate: function(val) {
var that = this;
if (val !== undefined) {
that._query({
aggregate: val
});
return;
}
return that._aggregate;
},
aggregates: function() {
return this._aggregateResult;
},
totalPages: function() {
var that = this, pageSize = that.pageSize() || that.total();
return math.ceil((that.total() || 0) / pageSize);
},
inRange: function(skip, take) {
var that = this, end = math.min(skip + take, that.total());
if (!that.options.serverPaging && that.data.length > 0) {
return true;
}
return that._findRange(skip, end).length > 0;
},
range: function(skip, take) {
skip = math.min(skip || 0, this.total());
var that = this, pageSkip = math.max(math.floor(skip / take), 0) * take, size = math.min(pageSkip + take, that.total()), data;
data = that._findRange(skip, math.min(skip + take, that.total()));
if (data.length) {
that._skip = skip > that.skip() ? math.min(size, (that.totalPages() - 1) * that.take()) : pageSkip;
that._take = take;
var paging = that.options.serverPaging;
var sorting = that.options.serverSorting;
var filtering = that.options.serverFiltering;
try {
that.options.serverPaging = true;
that.options.serverSorting = true;
that.options.serverFiltering = true;
if (paging) {
that._data = data = that._observe(data);
}
that._process(data);
} finally {
that.options.serverPaging = paging;
that.options.serverSorting = sorting;
that.options.serverFiltering = filtering;
}
return;
}
if (take !== undefined) {
if (!that._rangeExists(pageSkip, size)) {
that.prefetch(pageSkip, take, function() {
if (skip > pageSkip && size < that.total() && !that._rangeExists(size, math.min(size + take, that.total()))) {
that.prefetch(size, take, function() {
that.range(skip, take);
});
} else {
that.range(skip, take);
}
});
} else if (pageSkip < skip) {
that.prefetch(size, take, function() {
that.range(skip, take);
});
}
}
},
_findRange: function(start, end) {
var that = this, ranges = that._ranges, range, data = [], skipIdx, takeIdx, startIndex, endIndex, rangeData, rangeEnd, processed, options = that.options, remote = options.serverSorting || options.serverPaging || options.serverFiltering || options.serverGrouping || options.serverAggregates, flatData, count, length;
for (skipIdx = 0, length = ranges.length; skipIdx < length; skipIdx++) {
range = ranges[skipIdx];
if (start >= range.start && start <= range.end) {
count = 0;
for (takeIdx = skipIdx; takeIdx < length; takeIdx++) {
range = ranges[takeIdx];
flatData = that._flatData(range.data);
if (flatData.length && start + count >= range.start) {
rangeData = range.data;
rangeEnd = range.end;
if (!remote) {
var sort = normalizeGroup(that.group() || []).concat(normalizeSort(that.sort() || []));
processed = Query.process(range.data, {
sort: sort,
filter: that.filter()
});
flatData = rangeData = processed.data;
if (processed.total !== undefined) {
rangeEnd = processed.total;
}
}
startIndex = 0;
if (start + count > range.start) {
startIndex = start + count - range.start;
}
endIndex = flatData.length;
if (rangeEnd > end) {
endIndex = endIndex - (rangeEnd - end);
}
count += endIndex - startIndex;
data = that._mergeGroups(data, rangeData, startIndex, endIndex);
if (end <= range.end && count == end - start) {
return data;
}
}
}
break;
}
}
return [];
},
_mergeGroups: function(data, range, startIndex, endIndex) {
if (this._isServerGrouped()) {
var temp = range.toJSON(), prevGroup;
if (data.length) {
prevGroup = data[data.length - 1];
}
mergeGroups(prevGroup, temp, startIndex, endIndex);
return data.concat(temp);
}
return data.concat(range.slice(startIndex, endIndex));
},
skip: function() {
var that = this;
if (that._skip === undefined) {
return that._page !== undefined ? (that._page - 1) * (that.take() || 1) : undefined;
}
return that._skip;
},
take: function() {
return this._take || this._pageSize;
},
_prefetchSuccessHandler: function(skip, size, callback) {
var that = this;
return function(data) {
var found = false, range = {
start: skip,
end: size,
data: []
}, idx, length;
that._dequeueRequest();
for (idx = 0, length = that._ranges.length; idx < length; idx++) {
if (that._ranges[idx].start === skip) {
found = true;
range = that._ranges[idx];
break;
}
}
if (!found) {
that._ranges.push(range);
}
data = that.reader.parse(data);
range.data = that._observe(that._readData(data));
range.end = range.start + that._flatData(range.data).length;
that._ranges.sort(function(x, y) {
return x.start - y.start;
});
that._total = that.reader.total(data);
if (callback) {
callback();
}
};
},
prefetch: function(skip, take, callback) {
var that = this, size = math.min(skip + take, that.total()), options = {
take: take,
skip: skip,
page: skip / take + 1,
pageSize: take,
sort: that._sort,
filter: that._filter,
group: that._group,
aggregate: that._aggregate
};
if (!that._rangeExists(skip, size)) {
clearTimeout(that._timeout);
that._timeout = setTimeout(function() {
that._queueRequest(options, function() {
that.transport.read({
data: options,
success: that._prefetchSuccessHandler(skip, size, callback)
});
});
}, 100);
} else if (callback) {
callback();
}
},
_rangeExists: function(start, end) {
var that = this, ranges = that._ranges, idx, length;
for (idx = 0, length = ranges.length; idx < length; idx++) {
if (ranges[idx].start <= start && ranges[idx].end >= end) {
return true;
}
}
return false;
}
});
var Transport = {};
Transport.create = function(options, data) {
var transport, transportOptions = options.transport;
if (transportOptions) {
transportOptions.read = typeof transportOptions.read === STRING ? {
url: transportOptions.read
} : transportOptions.read;
if (options.type) {
if (kendo.data.transports[options.type] && !isPlainObject(kendo.data.transports[options.type])) {
transport = new kendo.data.transports[options.type](extend(transportOptions, {
data: data
}));
} else {
transportOptions = extend(true, {}, kendo.data.transports[options.type], transportOptions);
}
options.schema = extend(true, {}, kendo.data.schemas[options.type], options.schema);
}
if (!transport) {
transport = isFunction(transportOptions.read) ? transportOptions : new RemoteTransport(transportOptions);
}
} else {
transport = new LocalTransport({
data: options.data
});
}
return transport;
};
DataSource.create = function(options) {
options = options && options.push ? {
data: options
} : options;
var dataSource = options || {}, data = dataSource.data, fields = dataSource.fields, table = dataSource.table, select = dataSource.select, idx, length, model = {}, field;
if (!data && fields && !dataSource.transport) {
if (table) {
data = inferTable(table, fields);
} else if (select) {
data = inferSelect(select, fields);
}
}
if (kendo.data.Model && fields && (!dataSource.schema || !dataSource.schema.model)) {
for (idx = 0, length = fields.length; idx < length; idx++) {
field = fields[idx];
if (field.type) {
model[field.field] = field;
}
}
if (!isEmptyObject(model)) {
dataSource.schema = extend(true, dataSource.schema, {
model: {
fields: model
}
});
}
}
dataSource.data = data;
return dataSource instanceof DataSource ? dataSource : new DataSource(dataSource);
};
function inferSelect(select, fields) {
var options = $(select)[0].children, idx, length, data = [], record, firstField = fields[0], secondField = fields[1], value, option;
for (idx = 0, length = options.length; idx < length; idx++) {
record = {};
option = options[idx];
if (option.disabled) {
continue;
}
record[firstField.field] = option.text;
value = option.attributes.value;
if (value && value.specified) {
value = option.value;
} else {
value = option.text;
}
record[secondField.field] = value;
data.push(record);
}
return data;
}
function inferTable(table, fields) {
var tbody = $(table)[0].tBodies[0], rows = tbody ? tbody.rows : [], idx, length, fieldIndex, fieldCount = fields.length, data = [], cells, record, cell, empty;
for (idx = 0, length = rows.length; idx < length; idx++) {
record = {};
empty = true;
cells = rows[idx].cells;
for (fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
cell = cells[fieldIndex];
if (cell.nodeName.toLowerCase() !== "th") {
empty = false;
record[fields[fieldIndex].field] = cell.innerHTML;
}
}
if (!empty) {
data.push(record);
}
}
return data;
}
var Node = Model.define({
init: function(value) {
var that = this, hasChildren = that.hasChildren || value && value.hasChildren, childrenField = "items", childrenOptions = {};
kendo.data.Model.fn.init.call(that, value);
if (typeof that.children === STRING) {
childrenField = that.children;
}
childrenOptions = {
schema: {
data: childrenField,
model: {
hasChildren: hasChildren,
id: that.idField
}
}
};
if (typeof that.children !== STRING) {
extend(childrenOptions, that.children);
}
childrenOptions.data = value;
if (!hasChildren) {
hasChildren = childrenOptions.schema.data;
}
if (typeof hasChildren === STRING) {
hasChildren = kendo.getter(hasChildren);
}
if (isFunction(hasChildren)) {
that.hasChildren = !!hasChildren.call(that, that);
}
that._childrenOptions = childrenOptions;
if (that.hasChildren) {
that._initChildren();
}
that._loaded = !!(value && value[childrenField]);
},
_initChildren: function() {
var that = this;
if (!(that.children instanceof HierarchicalDataSource)) {
that.children = new HierarchicalDataSource(that._childrenOptions);
that.children.parent = function() {
return that;
};
that.children.bind(CHANGE, function(e) {
e.node = e.node || that;
that.trigger(CHANGE, e);
});
that._updateChildrenField();
}
},
append: function(model) {
this._initChildren();
this.loaded(true);
this.children.add(model);
},
hasChildren: false,
level: function() {
var parentNode = this.parentNode(), level = 0;
while (parentNode && parentNode.parentNode) {
level++;
parentNode = parentNode.parentNode ? parentNode.parentNode() : null;
}
return level;
},
_updateChildrenField: function() {
var fieldName = this._childrenOptions.schema.data;
this[fieldName || "items"] = this.children.data();
},
load: function() {
var that = this, options = {};
if (that.hasChildren) {
that._initChildren();
options[that.idField || "id"] = that.id;
if (!that._loaded) {
that.children._data = undefined;
}
that.children.one(CHANGE, function() {
that._loaded = true;
that._updateChildrenField();
})._query(options);
}
},
parentNode: function() {
var array = this.parent();
return array.parent();
},
loaded: function(value) {
if (value !== undefined) {
this._loaded = value;
} else {
return this._loaded;
}
},
shouldSerialize: function(field) {
return Model.fn.shouldSerialize.call(this, field) && field !== "children" && field !== "_loaded" && field !== "hasChildren" && field !== "_childrenOptions";
}
});
var HierarchicalDataSource = DataSource.extend({
init: function(options) {
var node = Node.define({
children: options
});
DataSource.fn.init.call(this, extend(true, {}, {
schema: {
modelBase: node,
model: node
}
}, options));
},
remove: function(node) {
var parentNode = node.parentNode(), dataSource = this, result;
if (parentNode) {
dataSource = parentNode.children;
}
result = DataSource.fn.remove.call(dataSource, node);
if (parentNode && !dataSource.data().length) {
parentNode.hasChildren = false;
}
return result;
},
insert: function(index, model) {
var parentNode = this.parent();
if (parentNode) {
parentNode.hasChildren = true;
parentNode._initChildren();
}
return DataSource.fn.insert.call(this, index, model);
},
_find: function(method, value) {
var idx, length, node, data, children;
node = DataSource.fn[method].call(this, value);
if (node) {
return node;
}
data = this._flatData(this.data());
if (!data) {
return;
}
for (idx = 0, length = data.length; idx < length; idx++) {
children = data[idx].children;
if (!(children instanceof HierarchicalDataSource)) {
continue;
}
node = children[method](value);
if (node) {
return node;
}
}
},
get: function(id) {
return this._find("get", id);
},
getByUid: function(uid) {
return this._find("getByUid", uid);
}
});
function inferList(list, fields) {
var items = $(list).children(), idx, length, data = [], record, textField = fields[0].field, urlField = fields[1] && fields[1].field, spriteCssClassField = fields[2] && fields[2].field, imageUrlField = fields[3] && fields[3].field, item, id, textChild, className, children;
for (idx = 0, length = items.length; idx < length; idx++) {
record = {};
item = items.eq(idx);
textChild = item[0].firstChild;
children = item.children();
list = children.filter("ul");
children = children.filter(":not(ul)");
id = item.attr("data-id");
if (id) {
record.id = id;
}
if (textChild) {
record[textField] = textChild.nodeType == 3 ? textChild.nodeValue : children.text();
}
if (urlField) {
record[urlField] = children.find("a").attr("href");
}
if (imageUrlField) {
record[imageUrlField] = children.find("img").attr("src");
}
if (spriteCssClassField) {
className = children.find(".k-sprite").prop("className");
record[spriteCssClassField] = className && $.trim(className.replace("k-sprite", ""));
}
if (list.length) {
record.items = inferList(list.eq(0), fields);
}
if (item.attr("data-hasChildren") == "true") {
record.hasChildren = true;
}
data.push(record);
}
return data;
}
HierarchicalDataSource.create = function(options) {
options = options && options.push ? {
data: options
} : options;
var dataSource = options || {}, data = dataSource.data, fields = dataSource.fields, list = dataSource.list;
if (data && data._dataSource) {
return data._dataSource;
}
if (!data && fields && !dataSource.transport) {
if (list) {
data = inferList(list, fields);
}
}
dataSource.data = data;
return dataSource instanceof HierarchicalDataSource ? dataSource : new HierarchicalDataSource(dataSource);
};
extend(true, kendo.data, {
readers: {
json: DataReader
},
Query: Query,
DataSource: DataSource,
HierarchicalDataSource: HierarchicalDataSource,
Node: Node,
ObservableObject: ObservableObject,
ObservableArray: ObservableArray,
LocalTransport: LocalTransport,
RemoteTransport: RemoteTransport,
Cache: Cache,
DataReader: DataReader,
Model: Model
});
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Observable = kendo.Observable, ObservableObject = kendo.data.ObservableObject, ObservableArray = kendo.data.ObservableArray, toString = {}.toString, binders = {}, Class = kendo.Class, innerText, proxy = $.proxy, VALUE = "value", SOURCE = "source", EVENTS = "events", CHECKED = "checked", CHANGE = "change";
(function() {
var a = document.createElement("a");
if (a.innerText !== undefined) {
innerText = "innerText";
} else if (a.textContent !== undefined) {
innerText = "textContent";
}
})();
var Binding = Observable.extend({
init: function(parents, path) {
var that = this;
Observable.fn.init.call(that);
that.source = parents[0];
that.parents = parents;
that.path = path;
that.dependencies = {};
that.dependencies[path] = true;
that.observable = that.source instanceof Observable;
that._access = function(e) {
that.dependencies[e.field] = true;
};
if (that.observable) {
that._change = function(e) {
that.change(e);
};
that.source.bind(CHANGE, that._change);
}
},
change: function(e) {
var dependency, ch, field = e.field, that = this;
if (that.path === "this") {
that.trigger(CHANGE, e);
} else {
for (dependency in that.dependencies) {
if (dependency.indexOf(field) === 0) {
ch = dependency.charAt(field.length);
if (!ch || ch === "." || ch === "[") {
that.trigger(CHANGE, e);
break;
}
}
}
}
},
start: function(source) {
source.bind("get", this._access);
},
stop: function(source) {
source.unbind("get", this._access);
},
get: function() {
var that = this, source = that.source, index = 0, path = that.path, result = source;
if (!that.observable) {
return result;
}
that.start(that.source);
result = source.get(path);
// Traverse the observable hierarchy if the binding is not resolved at the current level.
while (result === undefined && source) {
source = that.parents[++index];
if (source instanceof ObservableObject) {
result = source.get(path);
}
}
// If the result is a function - invoke it
if (typeof result === "function") {
index = path.lastIndexOf(".");
// If the function is a member of a nested observable object make that nested observable the context (this) of the function
if (index > 0) {
source = source.get(path.substring(0, index));
}
// Invoke the function
that.start(source);
result = result.call(source, that.source);
that.stop(source);
}
// If the binding is resolved by a parent object
if (source && source !== that.source) {
that.currentSource = source;
// save parent object
// Listen for changes in the parent object
source.unbind(CHANGE, that._change).bind(CHANGE, that._change);
}
that.stop(that.source);
return result;
},
set: function(value) {
var that = this, source = that.currentSource || that.source;
source.set(that.path, value);
},
destroy: function() {
if (this.observable) {
this.source.unbind(CHANGE, this._change);
}
}
});
var EventBinding = Binding.extend({
get: function() {
var source = this.source, path = this.path, index = 0, handler;
handler = source.get(path);
while (!handler && source) {
source = this.parents[++index];
if (source instanceof ObservableObject) {
handler = source.get(path);
}
}
return proxy(handler, source);
}
});
var TemplateBinding = Binding.extend({
init: function(source, path, template) {
var that = this;
Binding.fn.init.call(that, source, path);
that.template = template;
},
render: function(value) {
var html;
this.start(this.source);
html = kendo.render(this.template, value);
this.stop(this.source);
return html;
}
});
var Binder = Class.extend({
init: function(element, bindings, options) {
this.element = element;
this.bindings = bindings;
this.options = options;
},
bind: function(binding, attribute) {
var that = this;
binding = attribute ? binding[attribute] : binding;
binding.bind(CHANGE, function(e) {
that.refresh(attribute || e);
});
that.refresh(attribute);
},
destroy: function() {}
});
binders.attr = Binder.extend({
refresh: function(key) {
this.element.setAttribute(key, this.bindings.attr[key].get());
}
});
binders.style = Binder.extend({
refresh: function(key) {
this.element.style[key] = this.bindings.style[key].get();
}
});
binders.enabled = Binder.extend({
refresh: function() {
if (this.bindings.enabled.get()) {
this.element.removeAttribute("disabled");
} else {
this.element.setAttribute("disabled", "disabled");
}
}
});
binders.readonly = Binder.extend({
refresh: function() {
if (this.bindings.readonly.get()) {
this.element.setAttribute("readonly", "readonly");
} else {
this.element.removeAttribute("readonly");
}
}
});
binders.disabled = Binder.extend({
refresh: function() {
if (this.bindings.disabled.get()) {
this.element.setAttribute("disabled", "disabled");
} else {
this.element.removeAttribute("disabled");
}
}
});
binders.events = Binder.extend({
init: function(element, bindings, options) {
Binder.fn.init.call(this, element, bindings, options);
this.handlers = {};
},
refresh: function(key) {
var element = $(this.element), binding = this.bindings.events[key], handler = this.handlers[key];
if (handler) {
element.off(key, handler);
}
handler = this.handlers[key] = binding.get();
element.on(key, binding.source, handler);
},
destroy: function() {
var element = $(this.element), handler;
for (handler in this.handlers) {
element.off(handler, this.handlers[handler]);
}
}
});
binders.text = Binder.extend({
refresh: function() {
var text = this.bindings.text.get();
if (text == null) {
text = "";
}
this.element[innerText] = text;
}
});
binders.visible = Binder.extend({
refresh: function() {
if (this.bindings.visible.get()) {
this.element.style.display = "";
} else {
this.element.style.display = "none";
}
}
});
binders.invisible = Binder.extend({
refresh: function() {
if (!this.bindings.invisible.get()) {
this.element.style.display = "";
} else {
this.element.style.display = "none";
}
}
});
binders.html = Binder.extend({
refresh: function() {
this.element.innerHTML = this.bindings.html.get();
}
});
binders.value = Binder.extend({
init: function(element, bindings, options) {
Binder.fn.init.call(this, element, bindings, options);
this._change = proxy(this.change, this);
this.eventName = options.valueUpdate || CHANGE;
$(this.element).on(this.eventName, this._change);
this._initChange = false;
},
change: function() {
this._initChange = this.eventName != CHANGE;
this.bindings[VALUE].set(this.element.value);
this._initChange = false;
},
refresh: function() {
if (!this._initChange) {
var value = this.bindings[VALUE].get();
if (value == null) {
value = "";
}
this.element.value = value;
}
this._initChange = false;
},
destroy: function() {
$(this.element).off(this.eventName, this._change);
}
});
binders.source = Binder.extend({
init: function(element, bindings, options) {
Binder.fn.init.call(this, element, bindings, options);
},
refresh: function(e) {
var that = this, source = that.bindings.source.get();
if (source instanceof ObservableArray) {
e = e || {};
if (e.action == "add") {
that.add(e.index, e.items);
} else if (e.action == "remove") {
that.remove(e.index, e.items);
} else if (e.action != "itemchange") {
that.render();
}
} else {
that.render();
}
},
container: function() {
var element = this.element;
if (element.nodeName.toLowerCase() == "table") {
if (!element.tBodies[0]) {
element.appendChild(document.createElement("tbody"));
}
element = element.tBodies[0];
}
return element;
},
template: function() {
var options = this.options, template = options.template, nodeName = this.container().nodeName.toLowerCase();
if (!template) {
if (nodeName == "select") {
if (options.valueField || options.textField) {
template = kendo.format('<option value="#:{0}#">#:{1}#</option>', options.valueField || options.textField, options.textField || options.valueField);
} else {
template = "<option>#:data#</option>";
}
} else if (nodeName == "tbody") {
template = "<tr><td>#:data#</td></tr>";
} else if (nodeName == "ul" || nodeName == "ol") {
template = "<li>#:data#</li>";
} else {
template = "#:data#";
}
template = kendo.template(template);
}
return template;
},
destroy: function() {
var source = this.bindings.source.get();
source.unbind(CHANGE, this._change);
},
add: function(index, items) {
var element = this.container(), idx, length, child, clone = element.cloneNode(false), reference = element.children[index];
$(clone).html(kendo.render(this.template(), items));
if (clone.children.length) {
for (idx = 0, length = items.length; idx < length; idx++) {
child = clone.children[0];
element.insertBefore(child, reference || null);
bindElement(child, items[idx], this.options.roles, [ items[idx] ].concat(this.bindings.source.parents));
}
}
},
remove: function(index, items) {
var idx, element = this.container();
for (idx = 0; idx < items.length; idx++) {
element.removeChild(element.children[index]);
}
},
render: function() {
var source = this.bindings.source.get(), idx, length, element = this.container(), template = this.template(), parent;
if (!(source instanceof ObservableArray) && toString.call(source) !== "[object Array]") {
if (source.parent) {
parent = source.parent;
}
source = new ObservableArray([ source ]);
if (source.parent) {
source.parent = parent;
}
}
if (this.bindings.template) {
$(element).html(this.bindings.template.render(source));
if (element.children.length) {
for (idx = 0, length = source.length; idx < length; idx++) {
bindElement(element.children[idx], source[idx], this.options.roles, [ source[idx] ].concat(this.bindings.source.parents));
}
}
} else {
$(element).html(kendo.render(template, source));
}
}
});
binders.input = {
checked: Binder.extend({
init: function(element, bindings, options) {
Binder.fn.init.call(this, element, bindings, options);
this._change = proxy(this.change, this);
$(this.element).change(this._change);
},
change: function() {
var element = this.element;
var value = this.value();
if (element.type == "radio") {
this.bindings[CHECKED].set(value);
} else if (element.type == "checkbox") {
var source = this.bindings[CHECKED].get();
var index;
if (source instanceof ObservableArray) {
value = this.element.value;
if (value !== "on" && value !== "off") {
index = source.indexOf(value);
if (index > -1) {
source.splice(index, 1);
} else {
source.push(value);
}
}
} else {
this.bindings[CHECKED].set(value);
}
}
},
refresh: function() {
var value = this.bindings[CHECKED].get(), source = value, element = this.element;
if (element.type == "checkbox") {
if (source instanceof ObservableArray) {
value = this.element.value;
if (source.indexOf(value) >= 0) {
value = true;
}
}
element.checked = value === true;
} else if (element.type == "radio" && value != null) {
if (element.value === value.toString()) {
element.checked = true;
}
}
},
value: function() {
var element = this.element, value = element.value;
if (element.type == "checkbox") {
value = element.checked;
}
return value;
},
destroy: function() {
$(this.element).off(CHANGE, this._change);
}
})
};
binders.select = {
value: Binder.extend({
init: function(target, bindings, options) {
Binder.fn.init.call(this, target, bindings, options);
this._change = proxy(this.change, this);
$(this.element).change(this._change);
},
change: function() {
var values = [], element = this.element, source, field = this.options.valueField || this.options.textField, option, valueIndex, value, idx, length;
for (idx = 0, length = element.options.length; idx < length; idx++) {
option = element.options[idx];
if (option.selected) {
value = option.attributes.value;
if (value && value.specified) {
value = option.value;
} else {
value = option.text;
}
values.push(value);
}
}
if (field) {
source = this.bindings.source.get();
for (valueIndex = 0; valueIndex < values.length; valueIndex++) {
for (idx = 0, length = source.length; idx < length; idx++) {
if (source[idx].get(field) == values[valueIndex]) {
values[valueIndex] = source[idx];
break;
}
}
}
}
value = this.bindings[VALUE].get();
if (value instanceof ObservableArray) {
value.splice.apply(value, [ 0, value.length ].concat(values));
} else if (value instanceof ObservableObject || !field) {
this.bindings[VALUE].set(values[0]);
} else {
this.bindings[VALUE].set(values[0].get(field));
}
},
refresh: function() {
var optionIndex, element = this.element, options = element.options, value = this.bindings[VALUE].get(), values = value, field = this.options.valueField || this.options.textField, found = false, optionValue;
if (!(values instanceof ObservableArray)) {
values = new ObservableArray([ value ]);
}
element.selectedIndex = -1;
for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
value = values[valueIndex];
if (field && value instanceof ObservableObject) {
value = value.get(field);
}
for (optionIndex = 0; optionIndex < options.length; optionIndex++) {
optionValue = options[optionIndex].value;
if (optionValue === "" && value !== "") {
optionValue = options[optionIndex].text;
}
if (optionValue == value) {
options[optionIndex].selected = true;
found = true;
}
}
}
},
destroy: function() {
$(this.element).off(CHANGE, this._change);
}
})
};
binders.widget = {
events: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
this.handlers = {};
},
refresh: function(key) {
var binding = this.bindings.events[key], handler = this.handlers[key];
if (handler) {
this.widget.unbind(key, handler);
}
handler = binding.get();
this.handlers[key] = function(e) {
e.data = binding.source;
handler(e);
if (e.data === binding.source) {
delete e.data;
}
};
this.widget.bind(key, this.handlers[key]);
},
destroy: function() {
var handler;
for (handler in this.handlers) {
this.widget.unbind(handler, this.handlers[handler]);
}
}
}),
checked: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
this._change = proxy(this.change, this);
this.widget.bind(CHANGE, this._change);
},
change: function() {
this.bindings[CHECKED].set(this.value());
},
refresh: function() {
this.widget.check(this.bindings[CHECKED].get() === true);
},
value: function() {
var element = this.element, value = element.value;
if (value == "on" || value == "off") {
value = element.checked;
}
return value;
},
destroy: function() {
this.widget.unbind(CHANGE, this._change);
}
}),
visible: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
},
refresh: function() {
var visible = this.bindings.visible.get();
this.widget.wrapper[0].style.display = visible ? "" : "none";
}
}),
invisible: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
},
refresh: function() {
var invisible = this.bindings.invisible.get();
this.widget.wrapper[0].style.display = invisible ? "none" : "";
}
}),
enabled: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
},
refresh: function() {
if (this.widget.enable) {
this.widget.enable(this.bindings.enabled.get());
}
}
}),
disabled: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
},
refresh: function() {
if (this.widget.enable) {
this.widget.enable(!this.bindings.disabled.get());
}
}
}),
source: Binder.extend({
init: function(widget, bindings, options) {
var that = this;
Binder.fn.init.call(that, widget.element[0], bindings, options);
that.widget = widget;
that._dataBinding = proxy(that.dataBinding, that);
that._dataBound = proxy(that.dataBound, that);
that._itemChange = proxy(that.itemChange, that);
},
itemChange: function(e) {
bindElement(e.item[0], e.data, this._ns(e.ns), [ e.data ].concat(this.bindings.source.parents));
},
dataBinding: function() {
var idx, length, widget = this.widget, items = widget.items();
for (idx = 0, length = items.length; idx < length; idx++) {
unbindElementTree(items[idx]);
}
},
_ns: function(ns) {
ns = ns || kendo.ui;
var all = [ kendo.ui, kendo.dataviz.ui, kendo.mobile.ui ];
all.splice($.inArray(ns, all), 1);
all.unshift(ns);
return kendo.rolesFromNamespaces(all);
},
dataBound: function(e) {
var idx, length, widget = this.widget, items = widget.items(), dataSource = widget.dataSource, view = dataSource.view(), groups = dataSource.group() || [];
if (items.length) {
if (groups.length) {
view = flattenGroups(view);
}
for (idx = 0, length = view.length; idx < length; idx++) {
bindElement(items[idx], view[idx], this._ns(e.ns), [ view[idx] ].concat(this.bindings.source.parents));
}
}
},
refresh: function(e) {
var that = this, source, widget = that.widget;
e = e || {};
if (!e.action) {
that.destroy();
widget.bind("dataBinding", that._dataBinding);
widget.bind("dataBound", that._dataBound);
widget.bind("itemChange", that._itemChange);
source = that.bindings.source.get();
if (widget.dataSource instanceof kendo.data.DataSource && widget.dataSource != source) {
if (source instanceof kendo.data.DataSource) {
widget.setDataSource(source);
} else if (source && source._dataSource) {
widget.setDataSource(source._dataSource);
} else {
widget.dataSource.data(source);
}
}
}
},
destroy: function() {
var widget = this.widget;
widget.unbind("dataBinding", this._dataBinding);
widget.unbind("dataBound", this._dataBound);
widget.unbind("itemChange", this._itemChange);
}
}),
value: Binder.extend({
init: function(widget, bindings, options) {
Binder.fn.init.call(this, widget.element[0], bindings, options);
this.widget = widget;
this._change = $.proxy(this.change, this);
this.widget.first(CHANGE, this._change);
var value = this.bindings.value.get();
this._valueIsObservableObject = value == null || value instanceof ObservableObject;
this._valueIsObservableArray = value instanceof ObservableArray;
this._initChange = false;
},
change: function() {
var value = this.widget.value(), field = this.options.dataValueField || this.options.dataTextField, isArray = toString.call(value) === "[object Array]", isObservableObject = this._valueIsObservableObject, valueIndex, valueLength, values = [], sourceItem, sourceValue, idx, length, source;
this._initChange = true;
if (field) {
if (this.bindings.source) {
source = this.bindings.source.get();
}
if (value === "" && isObservableObject) {
value = null;
} else {
if (!source || source instanceof kendo.data.DataSource) {
source = this.widget.dataSource.view();
}
if (isArray) {
valueLength = value.length;
values = value.slice(0);
}
for (idx = 0, length = source.length; idx < length; idx++) {
sourceItem = source[idx];
sourceValue = sourceItem.get(field);
if (isArray) {
for (valueIndex = 0; valueIndex < valueLength; valueIndex++) {
if (sourceValue == values[valueIndex]) {
values[valueIndex] = sourceItem;
break;
}
}
} else if (sourceValue == value) {
value = isObservableObject ? sourceItem : sourceValue;
break;
}
}
if (values[0]) {
if (this._valueIsObservableArray) {
value = values;
} else if (isObservableObject || !field) {
value = values[0];
} else {
value = values[0].get(field);
}
}
}
}
this.bindings.value.set(value);
this._initChange = false;
},
refresh: function() {
if (!this._initChange) {
var field = this.options.dataValueField || this.options.dataTextField, value = this.bindings.value.get(), idx = 0, length, values = [];
if (field) {
if (value instanceof ObservableArray) {
for (length = value.length; idx < length; idx++) {
values[idx] = value[idx].get(field);
}
value = values;
} else if (value instanceof ObservableObject) {
value = value.get(field);
}
}
this.widget.value(value);
}
this._initChange = false;
},
destroy: function() {
this.widget.unbind(CHANGE, this._change);
}
})
};
var BindingTarget = Class.extend({
init: function(target, options) {
this.target = target;
this.options = options;
this.toDestroy = [];
},
bind: function(bindings) {
var nodeName = this.target.nodeName.toLowerCase(), key, hasValue, hasSource, hasEvents, specificBinders = binders[nodeName] || {};
for (key in bindings) {
if (key == VALUE) {
hasValue = true;
} else if (key == SOURCE) {
hasSource = true;
} else if (key == EVENTS) {
hasEvents = true;
} else {
this.applyBinding(key, bindings, specificBinders);
}
}
if (hasSource) {
this.applyBinding(SOURCE, bindings, specificBinders);
}
if (hasValue) {
this.applyBinding(VALUE, bindings, specificBinders);
}
if (hasEvents) {
this.applyBinding(EVENTS, bindings, specificBinders);
}
},
applyBinding: function(name, bindings, specificBinders) {
var binder = specificBinders[name] || binders[name], toDestroy = this.toDestroy, attribute, binding = bindings[name];
if (binder) {
binder = new binder(this.target, bindings, this.options);
toDestroy.push(binder);
if (binding instanceof Binding) {
binder.bind(binding);
toDestroy.push(binding);
} else {
for (attribute in binding) {
binder.bind(binding, attribute);
toDestroy.push(binding[attribute]);
}
}
} else if (name !== "template") {
throw new Error("The " + name + " binding is not supported by the " + this.target.nodeName.toLowerCase() + " element");
}
},
destroy: function() {
var idx, length, toDestroy = this.toDestroy;
for (idx = 0, length = toDestroy.length; idx < length; idx++) {
toDestroy[idx].destroy();
}
}
});
var WidgetBindingTarget = BindingTarget.extend({
bind: function(bindings) {
var that = this, binding, hasValue = false, hasSource = false;
for (binding in bindings) {
if (binding == VALUE) {
hasValue = true;
} else if (binding == SOURCE) {
hasSource = true;
} else {
that.applyBinding(binding, bindings);
}
}
if (hasSource) {
that.applyBinding(SOURCE, bindings);
}
if (hasValue) {
that.applyBinding(VALUE, bindings);
}
},
applyBinding: function(name, bindings) {
var binder = binders.widget[name], toDestroy = this.toDestroy, attribute, binding = bindings[name];
if (binder) {
binder = new binder(this.target, bindings, this.target.options);
toDestroy.push(binder);
if (binding instanceof Binding) {
binder.bind(binding);
toDestroy.push(binding);
} else {
for (attribute in binding) {
binder.bind(binding, attribute);
toDestroy.push(binding[attribute]);
}
}
} else {
throw new Error("The " + name + " binding is not supported by the " + this.target.options.name + " widget");
}
}
});
function flattenGroups(data) {
var idx, length, result = [];
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].hasSubgroups) {
result = result.concat(flattenGroups(data[idx].items));
} else {
result = result.concat(data[idx].items);
}
}
return result;
}
function bindingTargetForRole(role, element, roles) {
var type = roles[role];
if (type) {
return new WidgetBindingTarget(kendo.initWidget(element, type.options, roles));
}
}
var keyValueRegExp = /[A-Za-z0-9_\-]+:(\{([^}]*)\}|[^,}]+)/g, whiteSpaceRegExp = /\s/g;
function parseBindings(bind) {
var result = {}, idx, length, token, colonIndex, key, value, tokens;
tokens = bind.match(keyValueRegExp);
for (idx = 0, length = tokens.length; idx < length; idx++) {
token = tokens[idx];
colonIndex = token.indexOf(":");
key = token.substring(0, colonIndex);
value = token.substring(colonIndex + 1);
if (value.charAt(0) == "{") {
value = parseBindings(value);
}
result[key] = value;
}
return result;
}
function createBindings(bindings, source, type) {
var binding, result = {};
for (binding in bindings) {
result[binding] = new type(source, bindings[binding]);
}
return result;
}
function bindElement(element, source, roles, parents) {
var role = element.getAttribute("data-" + kendo.ns + "role"), idx, bind = element.getAttribute("data-" + kendo.ns + "bind"), children = element.children, childrenCopy = [], deep = true, bindings, options = {}, target;
parents = parents || [ source ];
if (role || bind) {
unbindElement(element);
}
if (role) {
target = bindingTargetForRole(role, element, roles);
}
if (bind) {
bind = parseBindings(bind.replace(whiteSpaceRegExp, ""));
if (!target) {
options = kendo.parseOptions(element, {
textField: "",
valueField: "",
template: "",
valueUpdate: CHANGE
});
options.roles = roles;
target = new BindingTarget(element, options);
}
target.source = source;
bindings = createBindings(bind, parents, Binding);
if (options.template) {
bindings.template = new TemplateBinding(parents, "", options.template);
}
if (bindings.click) {
bind.events = bind.events || {};
bind.events.click = bind.click;
delete bindings.click;
}
if (bindings.source) {
deep = false;
}
if (bind.attr) {
bindings.attr = createBindings(bind.attr, parents, Binding);
}
if (bind.style) {
bindings.style = createBindings(bind.style, parents, Binding);
}
if (bind.events) {
bindings.events = createBindings(bind.events, parents, EventBinding);
}
target.bind(bindings);
}
if (target) {
element.kendoBindingTarget = target;
}
if (deep && children) {
// https://github.com/telerik/kendo/issues/1240 for the weirdness.
for (idx = 0; idx < children.length; idx++) {
childrenCopy[idx] = children[idx];
}
for (idx = 0; idx < childrenCopy.length; idx++) {
bindElement(childrenCopy[idx], source, roles, parents);
}
}
}
function bind(dom, object) {
var idx, length, roles = kendo.rolesFromNamespaces([].slice.call(arguments, 2));
object = kendo.observable(object);
dom = $(dom);
for (idx = 0, length = dom.length; idx < length; idx++) {
bindElement(dom[idx], object, roles);
}
}
function unbindElement(element) {
var bindingTarget = element.kendoBindingTarget;
if (bindingTarget) {
bindingTarget.destroy();
if ($.support.deleteExpando) {
delete element.kendoBindingTarget;
} else if (element.removeAttribute) {
element.removeAttribute("kendoBindingTarget");
} else {
element.kendoBindingTarget = null;
}
}
}
function unbindElementTree(element) {
var idx, length, children = element.children;
unbindElement(element);
if (children) {
for (idx = 0, length = children.length; idx < length; idx++) {
unbindElementTree(children[idx]);
}
}
}
function unbind(dom) {
var idx, length;
dom = $(dom);
for (idx = 0, length = dom.length; idx < length; idx++) {
unbindElementTree(dom[idx]);
}
}
function notify(widget, namespace) {
var element = widget.element, bindingTarget = element[0].kendoBindingTarget;
if (bindingTarget) {
bind(element, bindingTarget.source, namespace);
}
}
kendo.unbind = unbind;
kendo.bind = bind;
kendo.data.binders = binders;
kendo.data.Binder = Binder;
kendo.notify = notify;
kendo.observable = function(object) {
if (!(object instanceof ObservableObject)) {
object = new ObservableObject(object);
}
return object;
};
kendo.observableHierarchy = function(array) {
var dataSource = kendo.data.HierarchicalDataSource.create(array);
function recursiveRead(data) {
var i, children;
for (i = 0; i < data.length; i++) {
data[i]._initChildren();
children = data[i].children;
children.fetch();
data[i].items = children.data();
recursiveRead(data[i].items);
}
}
dataSource.fetch();
recursiveRead(dataSource.data());
dataSource._data._dataSource = dataSource;
return dataSource._data;
};
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, NS = ".kendoValidator", INVALIDMSG = "k-invalid-msg", INVALIDINPUT = "k-invalid", emailRegExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, urlRegExp = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i, INPUTSELECTOR = ":input:not(:button,[type=submit],[type=reset],[disabled],[readonly])", CHECKBOXSELECTOR = ":checkbox:not([disabled],[readonly])", NUMBERINPUTSELECTOR = "[type=number],[type=range]", BLUR = "blur", NAME = "name", FORM = "form", NOVALIDATE = "novalidate", proxy = $.proxy, patternMatcher = function(value, pattern) {
if (typeof pattern === "string") {
pattern = new RegExp("^(?:" + pattern + ")$");
}
return pattern.test(value);
}, matcher = function(input, selector, pattern) {
var value = input.val();
if (input.filter(selector).length && value !== "") {
return patternMatcher(value, pattern);
}
return true;
}, hasAttribute = function(input, name) {
if (input.length) {
return input[0].attributes[name] !== undefined;
}
return false;
}, nameSpecialCharRegExp = /("|'|\[|\]|\$|\.|\:|\+)/g;
if (!kendo.ui.validator) {
kendo.ui.validator = {
rules: {},
messages: {}
};
}
function resolveRules(element) {
var resolvers = kendo.ui.validator.ruleResolvers || {}, rules = {}, name;
for (name in resolvers) {
$.extend(true, rules, resolvers[name].resolve(element));
}
return rules;
}
function decode(value) {
return value.replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
}
function numberOfDecimalDigits(value) {
value = (value + "").split(".");
if (value.length > 1) {
return value[1].length;
}
return 0;
}
var Validator = Widget.extend({
init: function(element, options) {
var that = this, resolved = resolveRules(element);
options = options || {};
options.rules = $.extend({}, kendo.ui.validator.rules, resolved.rules, options.rules);
options.messages = $.extend({}, kendo.ui.validator.messages, resolved.messages, options.messages);
Widget.fn.init.call(that, element, options);
that._errorTemplate = kendo.template(that.options.errorTemplate);
if (that.element.is(FORM)) {
that.element.attr(NOVALIDATE, NOVALIDATE);
}
that._errors = {};
that._attachEvents();
},
options: {
name: "Validator",
errorTemplate: '<span class="k-widget k-tooltip k-tooltip-validation">' + '<span class="k-icon k-warning"> </span> #=message#</span>',
messages: {
required: "{0} is required",
pattern: "{0} is not valid",
min: "{0} should be greater than or equal to {1}",
max: "{0} should be smaller than or equal to {1}",
step: "{0} is not valid",
email: "{0} is not valid email",
url: "{0} is not valid URL",
date: "{0} is not valid date"
},
rules: {
required: function(input) {
var checkbox = input.filter("[type=checkbox]").length && input.attr("checked") !== "checked", value = input.val();
return !(hasAttribute(input, "required") && (value === "" || !value || checkbox));
},
pattern: function(input) {
if (input.filter("[type=text],[type=email],[type=url],[type=tel],[type=search],[type=password]").filter("[pattern]").length && input.val() !== "") {
return patternMatcher(input.val(), input.attr("pattern"));
}
return true;
},
min: function(input) {
if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[min]").length && input.val() !== "") {
var min = parseFloat(input.attr("min")) || 0, val = parseFloat(input.val());
return min <= val;
}
return true;
},
max: function(input) {
if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[max]").length && input.val() !== "") {
var max = parseFloat(input.attr("max")) || 0, val = parseFloat(input.val());
return max >= val;
}
return true;
},
step: function(input) {
if (input.filter(NUMBERINPUTSELECTOR + ",[" + kendo.attr("type") + "=number]").filter("[step]").length && input.val() !== "") {
var min = parseFloat(input.attr("min")) || 0, step = parseFloat(input.attr("step")) || 1, val = parseFloat(input.val()), decimals = numberOfDecimalDigits(step), raise;
if (decimals) {
raise = Math.pow(10, decimals);
return (val - min) * raise % (step * raise) / Math.pow(100, decimals) === 0;
}
return (val - min) % step === 0;
}
return true;
},
email: function(input) {
return matcher(input, "[type=email],[" + kendo.attr("type") + "=email]", emailRegExp);
},
url: function(input) {
return matcher(input, "[type=url],[" + kendo.attr("type") + "=url]", urlRegExp);
},
date: function(input) {
if (input.filter("[type^=date],[" + kendo.attr("type") + "=date]").length && input.val() !== "") {
return kendo.parseDate(input.val(), input.attr(kendo.attr("format"))) !== null;
}
return true;
}
},
validateOnBlur: true
},
destroy: function() {
Widget.fn.destroy.call(this);
this.element.off(NS);
},
_submit: function(e) {
if (!this.validate()) {
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
return false;
}
return true;
},
_attachEvents: function() {
var that = this;
if (that.element.is(FORM)) {
that.element.on("submit" + NS, proxy(that._submit, that));
}
if (that.options.validateOnBlur) {
if (!that.element.is(INPUTSELECTOR)) {
that.element.on(BLUR + NS, INPUTSELECTOR, function() {
that.validateInput($(this));
});
that.element.on("click" + NS, CHECKBOXSELECTOR, function() {
that.validateInput($(this));
});
} else {
that.element.on(BLUR + NS, function() {
that.validateInput(that.element);
});
if (that.element.is(CHECKBOXSELECTOR)) {
that.element.on("click" + NS, function() {
that.validateInput(that.element);
});
}
}
}
},
validate: function() {
var that = this, inputs, idx, invalid = false, length;
that._errors = {};
if (!that.element.is(INPUTSELECTOR)) {
inputs = that.element.find(INPUTSELECTOR);
for (idx = 0, length = inputs.length; idx < length; idx++) {
if (!that.validateInput(inputs.eq(idx))) {
invalid = true;
}
}
return !invalid;
}
return that.validateInput(that.element);
},
validateInput: function(input) {
input = $(input);
var that = this, template = that._errorTemplate, result = that._checkValidity(input), valid = result.valid, className = "." + INVALIDMSG, fieldName = input.attr(NAME) || "", lbl = that._findMessageContainer(fieldName).add(input.next(className)).hide(), messageText;
input.removeAttr("aria-invalid");
if (!valid) {
messageText = that._extractMessage(input, result.key);
that._errors[fieldName] = messageText;
var messageLabel = $(template({
message: decode(messageText)
}));
that._decorateMessageContainer(messageLabel, fieldName);
if (!lbl.replaceWith(messageLabel).length) {
messageLabel.insertAfter(input);
}
messageLabel.show();
input.attr("aria-invalid", true);
}
input.toggleClass(INVALIDINPUT, !valid);
return valid;
},
hideMessages: function() {
var that = this, className = "." + INVALIDMSG, element = that.element;
if (!element.is(INPUTSELECTOR)) {
element.find(className).hide();
} else {
element.next(className).hide();
}
},
_findMessageContainer: function(fieldName) {
var locators = kendo.ui.validator.messageLocators, name, containers = this.element.find("." + INVALIDMSG + "[" + kendo.attr("for") + "=" + fieldName.replace(nameSpecialCharRegExp, "\\$1") + "]");
for (name in locators) {
containers = containers.add(locators[name].locate(this.element, fieldName));
}
return containers;
},
_decorateMessageContainer: function(container, fieldName) {
var locators = kendo.ui.validator.messageLocators, name;
container.addClass(INVALIDMSG).attr(kendo.attr("for"), fieldName || "");
for (name in locators) {
locators[name].decorate(container, fieldName);
}
container.attr("role", "alert");
},
_extractMessage: function(input, ruleKey) {
var that = this, customMessage = that.options.messages[ruleKey], fieldName = input.attr(NAME);
customMessage = $.isFunction(customMessage) ? customMessage(input) : customMessage;
return kendo.format(input.attr(kendo.attr(ruleKey + "-msg")) || input.attr("validationMessage") || input.attr("title") || customMessage || "", fieldName, input.attr(ruleKey));
},
_checkValidity: function(input) {
var rules = this.options.rules, rule;
for (rule in rules) {
if (!rules[rule](input)) {
return {
valid: false,
key: rule
};
}
}
return {
valid: true
};
},
errors: function() {
var results = [], errors = this._errors, error;
for (error in errors) {
results.push(errors[error]);
}
return results;
}
});
kendo.ui.plugin(Validator);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, support = kendo.support, pointers = support.pointers, document = window.document, SURFACE = $(document.documentElement), Class = kendo.Class, Observable = kendo.Observable, now = $.now, extend = $.extend, OS = support.mobileOS, invalidZeroEvents = OS && OS.android, // UserEvents events
PRESS = "press", SELECT = "select", START = "start", MOVE = "move", END = "end", CANCEL = "cancel", TAP = "tap", GESTURESTART = "gesturestart", GESTURECHANGE = "gesturechange", GESTUREEND = "gestureend", GESTURETAP = "gesturetap";
function touchDelta(touch1, touch2) {
var x1 = touch1.x.location, y1 = touch1.y.location, x2 = touch2.x.location, y2 = touch2.y.location, dx = x1 - x2, dy = y1 - y2;
return {
center: {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
},
distance: Math.sqrt(dx * dx + dy * dy)
};
}
function getTouches(e) {
var touches = [], originalEvent = e.originalEvent, currentTarget = e.currentTarget, idx = 0, length, changedTouches, touch;
if (e.api) {
touches.push({
id: 2,
// hardcoded ID for API call;
event: e,
target: e.target,
currentTarget: e.target,
location: e
});
} else if (e.type.match(/touch/)) {
changedTouches = originalEvent ? originalEvent.changedTouches : [];
for (length = changedTouches.length; idx < length; idx++) {
touch = changedTouches[idx];
touches.push({
location: touch,
event: e,
target: touch.target,
currentTarget: currentTarget,
id: touch.identifier
});
}
} else if (support.pointers) {
touches.push({
location: originalEvent,
event: e,
target: e.target,
currentTarget: currentTarget,
id: originalEvent.pointerId
});
} else {
touches.push({
id: 1,
// hardcoded ID for mouse event;
event: e,
target: e.target,
currentTarget: currentTarget,
location: e
});
}
return touches;
}
var TouchAxis = Class.extend({
init: function(axis, location) {
var that = this;
that.axis = axis;
that._updateLocationData(location);
that.startLocation = that.location;
that.velocity = that.delta = 0;
that.timeStamp = now();
},
move: function(location) {
var that = this, offset = location["page" + that.axis], timeStamp = now(), timeDelta = timeStamp - that.timeStamp || 1;
// Firing manually events in tests can make this 0;
if (!offset && invalidZeroEvents) {
return;
}
that.delta = offset - that.location;
that._updateLocationData(location);
that.initialDelta = offset - that.startLocation;
that.velocity = that.delta / timeDelta;
that.timeStamp = timeStamp;
},
_updateLocationData: function(location) {
var that = this, axis = that.axis;
that.location = location["page" + axis];
that.client = location["client" + axis];
that.screen = location["screen" + axis];
}
});
var Touch = Class.extend({
init: function(userEvents, target, touchInfo) {
var that = this;
extend(that, {
x: new TouchAxis("X", touchInfo.location),
y: new TouchAxis("Y", touchInfo.location),
userEvents: userEvents,
target: target,
currentTarget: touchInfo.currentTarget,
initialTouch: touchInfo.target,
id: touchInfo.id,
_moved: false,
_finished: false
});
that.notifyInit = function() {
that._trigger(PRESS, touchInfo);
};
},
move: function(touchInfo) {
var that = this;
if (that._finished) {
return;
}
that.x.move(touchInfo.location);
that.y.move(touchInfo.location);
if (!that._moved) {
if (that._withinIgnoreThreshold()) {
return;
}
if (!UserEvents.current || UserEvents.current === that.userEvents) {
that._start(touchInfo);
} else {
return that.dispose();
}
}
// Event handlers may cancel the drag in the START event handler, hence the double check for pressed.
if (!that._finished) {
that._trigger(MOVE, touchInfo);
}
},
end: function(touchInfo) {
var that = this;
that.endTime = now();
if (that._finished) {
return;
}
if (that._moved) {
that._trigger(END, touchInfo);
} else {
that._trigger(TAP, touchInfo);
}
that.dispose();
},
dispose: function() {
var that = this, userEvents = that.userEvents, activeTouches = userEvents.touches;
that._finished = true;
activeTouches.splice($.inArray(that, activeTouches), 1);
},
skip: function() {
this.dispose();
},
cancel: function() {
this.dispose();
},
isMoved: function() {
return this._moved;
},
_start: function(touchInfo) {
this.startTime = now();
this._moved = true;
this._trigger(START, touchInfo);
},
_trigger: function(name, touchInfo) {
var that = this, jQueryEvent = touchInfo.event, data = {
touch: that,
x: that.x,
y: that.y,
target: that.target,
event: jQueryEvent
};
if (that.userEvents.notify(name, data)) {
jQueryEvent.preventDefault();
}
},
_withinIgnoreThreshold: function() {
var xDelta = this.x.initialDelta, yDelta = this.y.initialDelta;
return Math.sqrt(xDelta * xDelta + yDelta * yDelta) <= this.userEvents.threshold;
}
});
function preventTrigger(e) {
e.preventDefault();
var target = $(e.data.root), // Determine the correct parent to receive the event and bubble.
parent = target.closest(".k-widget").parent();
if (!parent[0]) {
parent = target.parent();
}
parent.trigger($.Event(e.type, {
target: target[0]
}));
}
var UserEvents = Observable.extend({
init: function(element, options) {
var that = this, filter, ns = kendo.guid();
options = options || {};
filter = that.filter = options.filter;
that.threshold = options.threshold || 0;
that.touches = [];
that._maxTouches = options.multiTouch ? 2 : 1;
that.allowSelection = options.allowSelection;
that.eventNS = ns;
element = $(element).handler(that);
Observable.fn.init.call(that);
extend(that, {
element: element,
surface: options.global ? SURFACE : options.surface || element,
stopPropagation: options.stopPropagation,
pressed: false
});
that.surface.handler(that).on(kendo.applyEventMap("move", ns), "_move").on(kendo.applyEventMap("up cancel", ns), "_end");
element.on(kendo.applyEventMap("down", ns), filter, "_start");
if (pointers) {
element.css("-ms-touch-action", "pinch-zoom double-tap-zoom");
}
if (options.preventDragEvent) {
element.on(kendo.applyEventMap("dragstart", ns), kendo.preventDefault);
}
element.on(kendo.applyEventMap("mousedown selectstart", ns), filter, {
root: element
}, "_select");
if (support.eventCapture) {
var downEvents = kendo.eventMap.up.split(" "), idx = 0, length = downEvents.length, surfaceElement = that.surface[0], preventIfMoving = function(e) {
if (that._isMoved()) {
e.preventDefault();
}
};
for (;idx < length; idx++) {
surfaceElement.addEventListener(downEvents[idx], preventIfMoving, true);
}
}
that.bind([ PRESS, TAP, START, MOVE, END, CANCEL, GESTURESTART, GESTURECHANGE, GESTUREEND, GESTURETAP, SELECT ], options);
},
destroy: function() {
var that = this;
that.element.kendoDestroy(that.eventNS);
that.surface.kendoDestroy(that.eventNS);
that._disposeAll();
that.unbind();
},
capture: function() {
UserEvents.current = this;
},
cancel: function() {
this._disposeAll();
this.trigger(CANCEL);
},
notify: function(eventName, data) {
var that = this, touches = that.touches;
if (this._isMultiTouch()) {
switch (eventName) {
case MOVE:
eventName = GESTURECHANGE;
break;
case END:
eventName = GESTUREEND;
break;
case TAP:
eventName = GESTURETAP;
break;
}
extend(data, {
touches: touches
}, touchDelta(touches[0], touches[1]));
}
return this.trigger(eventName, data);
},
// API
press: function(x, y, target) {
this._apiCall("_start", x, y, target);
},
move: function(x, y) {
this._apiCall("_move", x, y);
},
end: function(x, y) {
this._apiCall("_end", x, y);
},
_isMultiTouch: function() {
return this.touches.length > 1;
},
_maxTouchesReached: function() {
return this.touches.length >= this._maxTouches;
},
_disposeAll: function() {
$.each(this.touches, function() {
this.dispose();
});
},
_isMoved: function() {
return $.grep(this.touches, function(touch) {
return touch.isMoved();
}).length;
},
_select: function(e) {
if (!this.allowSelection || this.trigger(SELECT, {
event: e
})) {
preventTrigger(e);
}
},
_start: function(e) {
var that = this, idx = 0, filter = that.filter, target, touches = getTouches(e), length = touches.length, touch;
if (that._maxTouchesReached()) {
return;
}
UserEvents.current = null;
that.currentTarget = e.currentTarget;
if (that.stopPropagation) {
e.stopPropagation();
}
for (;idx < length; idx++) {
if (that._maxTouchesReached()) {
break;
}
touch = touches[idx];
if (filter) {
target = $(touch.currentTarget);
} else {
target = that.element;
}
if (!target.length) {
continue;
}
touch = new Touch(that, target, touch);
that.touches.push(touch);
touch.notifyInit();
if (that._isMultiTouch()) {
that.notify("gesturestart", {});
}
}
},
_move: function(e) {
this._eachTouch("move", e);
},
_end: function(e) {
this._eachTouch("end", e);
},
_eachTouch: function(methodName, e) {
var that = this, dict = {}, touches = getTouches(e), activeTouches = that.touches, idx, touch, touchInfo, matchingTouch;
for (idx = 0; idx < activeTouches.length; idx++) {
touch = activeTouches[idx];
dict[touch.id] = touch;
}
for (idx = 0; idx < touches.length; idx++) {
touchInfo = touches[idx];
matchingTouch = dict[touchInfo.id];
if (matchingTouch) {
matchingTouch[methodName](touchInfo);
}
}
},
_apiCall: function(type, x, y, target) {
this[type]({
api: true,
pageX: x,
pageY: y,
target: target || this.element,
stopPropagation: $.noop,
preventDefault: $.noop
});
}
});
kendo.getTouches = getTouches;
kendo.touchDelta = touchDelta;
kendo.UserEvents = UserEvents;
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, support = kendo.support, document = window.document, Class = kendo.Class, Widget = kendo.ui.Widget, Observable = kendo.Observable, UserEvents = kendo.UserEvents, proxy = $.proxy, extend = $.extend, getOffset = kendo.getOffset, draggables = {}, dropTargets = {}, dropAreas = {}, lastDropTarget, OS = support.mobileOS, invalidZeroEvents = OS && OS.android, mobileChrome = invalidZeroEvents && OS.browser == "chrome", KEYUP = "keyup", CHANGE = "change", // Draggable events
DRAGSTART = "dragstart", DRAG = "drag", DRAGEND = "dragend", DRAGCANCEL = "dragcancel", // DropTarget events
DRAGENTER = "dragenter", DRAGLEAVE = "dragleave", DROP = "drop";
function contains(parent, child) {
try {
return $.contains(parent, child) || parent == child;
} catch (e) {
return false;
}
}
function elementUnderCursor(e) {
if (mobileChrome) {
return document.elementFromPoint(e.x.screen, e.y.screen);
} else {
return document.elementFromPoint(e.x.client, e.y.client);
}
}
function numericCssPropery(element, property) {
return parseInt(element.css(property), 10) || 0;
}
function within(value, range) {
return Math.min(Math.max(value, range.min), range.max);
}
function containerBoundaries(container, element) {
var offset = getOffset(container), minX = offset.left + numericCssPropery(container, "borderLeftWidth") + numericCssPropery(container, "paddingLeft"), minY = offset.top + numericCssPropery(container, "borderTopWidth") + numericCssPropery(container, "paddingTop"), maxX = minX + container.width() - element.outerWidth(true), maxY = minY + container.height() - element.outerHeight(true);
return {
x: {
min: minX,
max: maxX
},
y: {
min: minY,
max: maxY
}
};
}
function checkTarget(target, targets, areas) {
var theTarget, theFilter, i = 0, targetLen = targets && targets.length, areaLen = areas && areas.length;
while (target && target.parentNode) {
for (i = 0; i < targetLen; i++) {
theTarget = targets[i];
if (theTarget.element[0] === target) {
return {
target: theTarget,
targetElement: target
};
}
}
for (i = 0; i < areaLen; i++) {
theFilter = areas[i];
if (support.matchesSelector.call(target, theFilter.options.filter)) {
return {
target: theFilter,
targetElement: target
};
}
}
target = target.parentNode;
}
return undefined;
}
var TapCapture = Observable.extend({
init: function(element, options) {
var that = this, domElement = element[0];
that.capture = false;
$.each(kendo.eventMap.down.split(" "), function() {
domElement.addEventListener(this, proxy(that._press, that), true);
});
$.each(kendo.eventMap.up.split(" "), function() {
domElement.addEventListener(this, proxy(that._release, that), true);
});
Observable.fn.init.call(that);
that.bind([ "press", "release" ], options || {});
},
captureNext: function() {
this.capture = true;
},
cancelCapture: function() {
this.capture = false;
},
_press: function(e) {
var that = this;
that.trigger("press");
if (that.capture) {
e.preventDefault();
}
},
_release: function(e) {
var that = this;
that.trigger("release");
if (that.capture) {
e.preventDefault();
that.cancelCapture();
}
}
});
var PaneDimension = Observable.extend({
init: function(options) {
var that = this;
Observable.fn.init.call(that);
that.forcedEnabled = false;
$.extend(that, options);
that.scale = 1;
that.max = 0;
if (that.horizontal) {
that.measure = "width";
that.scrollSize = "scrollWidth";
that.axis = "x";
} else {
that.measure = "height";
that.scrollSize = "scrollHeight";
that.axis = "y";
}
},
outOfBounds: function(offset) {
return offset > this.max || offset < this.min;
},
forceEnabled: function() {
this.forcedEnabled = true;
},
getSize: function() {
return this.container[this.measure]();
},
getTotal: function() {
return this.element[0][this.scrollSize];
},
rescale: function(scale) {
this.scale = scale;
},
update: function(silent) {
var that = this, total = that.getTotal(), scaledTotal = total * that.scale, size = that.getSize();
that.size = size;
that.total = scaledTotal;
that.min = Math.min(that.max, that.size - scaledTotal);
that.minScale = that.size / total;
that.enabled = that.forcedEnabled || scaledTotal > size;
if (!silent) {
that.trigger(CHANGE, that);
}
}
});
var PaneDimensions = Observable.extend({
init: function(options) {
var that = this, refresh = proxy(that.refresh, that);
Observable.fn.init.call(that);
that.x = new PaneDimension(extend({
horizontal: true
}, options));
that.y = new PaneDimension(extend({
horizontal: false
}, options));
that.forcedMinScale = options.minScale;
that.bind(CHANGE, options);
kendo.onResize(refresh);
},
rescale: function(newScale) {
this.x.rescale(newScale);
this.y.rescale(newScale);
this.refresh();
},
refresh: function() {
var that = this;
that.x.update();
that.y.update();
that.enabled = that.x.enabled || that.y.enabled;
that.minScale = that.forcedMinScale || Math.max(that.x.minScale, that.y.minScale);
that.trigger(CHANGE);
}
});
var PaneAxis = Observable.extend({
init: function(options) {
var that = this;
extend(that, options);
Observable.fn.init.call(that);
},
dragMove: function(delta) {
var that = this, dimension = that.dimension, axis = that.axis, movable = that.movable, position = movable[axis] + delta;
if (!dimension.enabled) {
return;
}
if (position < dimension.min && delta < 0 || position > dimension.max && delta > 0) {
delta *= that.resistance;
}
movable.translateAxis(axis, delta);
that.trigger(CHANGE, that);
}
});
var Pane = Class.extend({
init: function(options) {
var that = this, x, y, resistance, movable;
extend(that, {
elastic: true
}, options);
resistance = that.elastic ? .5 : 0;
movable = that.movable;
that.x = x = new PaneAxis({
axis: "x",
dimension: that.dimensions.x,
resistance: resistance,
movable: movable
});
that.y = y = new PaneAxis({
axis: "y",
dimension: that.dimensions.y,
resistance: resistance,
movable: movable
});
that.userEvents.bind([ "move", "end", "gesturestart", "gesturechange" ], {
gesturestart: function(e) {
that.gesture = e;
},
gesturechange: function(e) {
var previousGesture = that.gesture, previousCenter = previousGesture.center, center = e.center, scaleDelta = e.distance / previousGesture.distance, minScale = that.dimensions.minScale, coordinates;
if (movable.scale <= minScale && scaleDelta < 1) {
// Resist shrinking. Instead of shrinking from 1 to 0.5, it will shrink to 0.5 + (1 /* minScale */ - 0.5) * 0.8 = 0.9;
scaleDelta += (1 - scaleDelta) * .8;
}
coordinates = {
x: (movable.x - previousCenter.x) * scaleDelta + center.x - movable.x,
y: (movable.y - previousCenter.y) * scaleDelta + center.y - movable.y
};
movable.scaleWith(scaleDelta);
x.dragMove(coordinates.x);
y.dragMove(coordinates.y);
that.dimensions.rescale(movable.scale);
that.gesture = e;
},
move: function(e) {
if (x.dimension.enabled || y.dimension.enabled) {
x.dragMove(e.x.delta);
y.dragMove(e.y.delta);
e.preventDefault();
} else {
e.touch.skip();
}
},
end: function(e) {
e.preventDefault();
}
});
}
});
var TRANSFORM_STYLE = support.transitions.prefix + "Transform", round = Math.round, translate;
if (support.hasHW3D) {
translate = function(x, y, scale) {
return "translate3d(" + round(x) + "px," + round(y) + "px,0) scale(" + scale + ")";
};
} else {
translate = function(x, y, scale) {
return "translate(" + round(x) + "px," + round(y) + "px) scale(" + scale + ")";
};
}
var Movable = Observable.extend({
init: function(element) {
var that = this;
Observable.fn.init.call(that);
that.element = $(element);
that.element[0].style.webkitTransformOrigin = "left top";
that.x = 0;
that.y = 0;
that.scale = 1;
that._saveCoordinates(translate(that.x, that.y, that.scale));
},
translateAxis: function(axis, by) {
this[axis] += by;
this.refresh();
},
scaleTo: function(scale) {
this.scale = scale;
this.refresh();
},
scaleWith: function(scaleDelta) {
this.scale *= scaleDelta;
this.refresh();
},
translate: function(coordinates) {
this.x += coordinates.x;
this.y += coordinates.y;
this.refresh();
},
moveAxis: function(axis, value) {
this[axis] = value;
this.refresh();
},
moveTo: function(coordinates) {
extend(this, coordinates);
this.refresh();
},
refresh: function() {
var that = this, newCoordinates = translate(that.x, that.y, that.scale);
if (newCoordinates != that.coordinates) {
that.element[0].style[TRANSFORM_STYLE] = newCoordinates;
that._saveCoordinates(newCoordinates);
that.trigger(CHANGE);
}
},
_saveCoordinates: function(coordinates) {
this.coordinates = coordinates;
}
});
var DropTarget = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
var group = that.options.group;
if (!(group in dropTargets)) {
dropTargets[group] = [ that ];
} else {
dropTargets[group].push(that);
}
},
events: [ DRAGENTER, DRAGLEAVE, DROP ],
options: {
name: "DropTarget",
group: "default"
},
destroy: function() {
var groupName = this.options.group, group = dropTargets[groupName] || dropAreas[groupName], i;
if (group.length > 1) {
Widget.fn.destroy.call(this);
for (i = 0; i < group.length; i++) {
if (group[i] == this) {
group.splice(i, 1);
break;
}
}
} else {
DropTarget.destroyGroup(groupName);
}
},
_trigger: function(eventName, e) {
var that = this, draggable = draggables[that.options.group];
if (draggable) {
return that.trigger(eventName, extend({}, e.event, {
draggable: draggable,
dropTarget: e.dropTarget
}));
}
},
_over: function(e) {
this._trigger(DRAGENTER, e);
},
_out: function(e) {
this._trigger(DRAGLEAVE, e);
},
_drop: function(e) {
var that = this, draggable = draggables[that.options.group];
if (draggable) {
draggable.dropped = !that._trigger(DROP, e);
}
}
});
DropTarget.destroyGroup = function(groupName) {
var group = dropTargets[groupName] || dropAreas[groupName], i;
if (group) {
for (i = 0; i < group.length; i++) {
Widget.fn.destroy.call(group[i]);
}
group.length = 0;
delete dropTargets[groupName];
delete dropAreas[groupName];
}
};
DropTarget._cache = dropTargets;
var DropTargetArea = DropTarget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
var group = that.options.group;
if (!(group in dropAreas)) {
dropAreas[group] = [ that ];
} else {
dropAreas[group].push(that);
}
},
options: {
name: "DropTargetArea",
group: "default",
filter: null
}
});
var Draggable = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.userEvents = new UserEvents(that.element, {
global: true,
stopPropagation: true,
filter: that.options.filter,
threshold: that.options.distance,
start: proxy(that._start, that),
move: proxy(that._drag, that),
end: proxy(that._end, that),
cancel: proxy(that._cancel, that)
});
that._afterEndHandler = proxy(that._afterEnd, that);
that.captureEscape = function(e) {
if (e.keyCode === kendo.keys.ESC) {
that._trigger(DRAGCANCEL, {
event: e
});
that.userEvents.cancel();
}
};
},
events: [ DRAGSTART, DRAG, DRAGEND, DRAGCANCEL ],
options: {
name: "Draggable",
distance: 5,
group: "default",
cursorOffset: null,
axis: null,
container: null,
dropped: false
},
_updateHint: function(e) {
var that = this, coordinates, options = that.options, boundaries = that.boundaries, axis = options.axis, cursorOffset = that.options.cursorOffset;
if (cursorOffset) {
coordinates = {
left: e.x.location + cursorOffset.left,
top: e.y.location + cursorOffset.top
};
} else {
that.hintOffset.left += e.x.delta;
that.hintOffset.top += e.y.delta;
coordinates = $.extend({}, that.hintOffset);
}
if (boundaries) {
coordinates.top = within(coordinates.top, boundaries.y);
coordinates.left = within(coordinates.left, boundaries.x);
}
if (axis === "x") {
delete coordinates.top;
} else if (axis === "y") {
delete coordinates.left;
}
that.hint.css(coordinates);
},
_start: function(e) {
var that = this, options = that.options, container = options.container, hint = options.hint;
that.currentTarget = e.target;
that.currentTargetOffset = getOffset(that.currentTarget);
if (hint) {
if (that.hint) {
that.hint.stop(true, true).remove();
}
that.hint = $.isFunction(hint) ? $(hint.call(that, that.currentTarget)) : hint;
var offset = getOffset(that.currentTarget);
that.hintOffset = offset;
that.hint.css({
position: "absolute",
zIndex: 2e4,
// the Window's z-index is 10000 and can be raised because of z-stacking
left: offset.left,
top: offset.top
}).appendTo(document.body);
}
draggables[options.group] = that;
that.dropped = false;
if (container) {
that.boundaries = containerBoundaries(container, that.hint);
}
if (that._trigger(DRAGSTART, e)) {
that.userEvents.cancel();
that._afterEnd();
}
$(document).on(KEYUP, that.captureEscape);
},
_drag: function(e) {
var that = this;
e.preventDefault();
that._withDropTarget(e, function(target, targetElement) {
if (!target) {
if (lastDropTarget) {
lastDropTarget._trigger(DRAGLEAVE, extend(e, {
dropTarget: $(lastDropTarget.targetElement)
}));
lastDropTarget = null;
}
return;
}
if (lastDropTarget) {
if (targetElement === lastDropTarget.targetElement) {
return;
}
lastDropTarget._trigger(DRAGLEAVE, extend(e, {
dropTarget: $(lastDropTarget.targetElement)
}));
}
target._trigger(DRAGENTER, extend(e, {
dropTarget: $(targetElement)
}));
lastDropTarget = extend(target, {
targetElement: targetElement
});
});
that._trigger(DRAG, e);
if (that.hint) {
that._updateHint(e);
}
},
_end: function(e) {
var that = this;
that._withDropTarget(e, function(target, targetElement) {
if (target) {
target._drop(extend({}, e, {
dropTarget: $(targetElement)
}));
lastDropTarget = null;
}
});
that._trigger(DRAGEND, e);
that._cancel(e.event);
},
_cancel: function() {
var that = this;
if (that.hint && !that.dropped) {
setTimeout(function() {
that.hint.stop(true, true).animate(that.currentTargetOffset, "fast", that._afterEndHandler);
}, 0);
} else {
that._afterEnd();
}
},
_trigger: function(eventName, e) {
var that = this;
return that.trigger(eventName, extend({}, e.event, {
x: e.x,
y: e.y,
currentTarget: that.currentTarget,
dropTarget: e.dropTarget
}));
},
_withDropTarget: function(e, callback) {
var that = this, target, result, options = that.options, targets = dropTargets[options.group], areas = dropAreas[options.group];
if (targets && targets.length || areas && areas.length) {
target = elementUnderCursor(e);
if (that.hint && contains(that.hint[0], target)) {
that.hint.hide();
target = elementUnderCursor(e);
// IE8 does not return the element in iframe from first attempt
if (!target) {
target = elementUnderCursor(e);
}
that.hint.show();
}
result = checkTarget(target, targets, areas);
if (result) {
callback(result.target, result.targetElement);
} else {
callback();
}
}
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that._afterEnd();
that.userEvents.destroy();
},
_afterEnd: function() {
var that = this;
if (that.hint) {
that.hint.remove();
}
delete draggables[that.options.group];
that.trigger("destroy");
$(document).off(KEYUP, that.captureEscape);
}
});
kendo.ui.plugin(DropTarget);
kendo.ui.plugin(DropTargetArea);
kendo.ui.plugin(Draggable);
kendo.TapCapture = TapCapture;
kendo.containerBoundaries = containerBoundaries;
extend(kendo.ui, {
Pane: Pane,
PaneDimensions: PaneDimensions,
Movable: Movable
});
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, fx = kendo.fx, ui = mobile.ui, proxy = $.proxy, extend = $.extend, Widget = ui.Widget, Class = kendo.Class, Movable = kendo.ui.Movable, Pane = kendo.ui.Pane, PaneDimensions = kendo.ui.PaneDimensions, Transition = fx.Transition, Animation = fx.Animation, abs = Math.abs, SNAPBACK_DURATION = 500, SCROLLBAR_OPACITY = .7, FRICTION = .93, OUT_OF_BOUNDS_FRICTION = .5, RELEASECLASS = "km-scroller-release", REFRESHCLASS = "km-scroller-refresh", PULL = "pull", CHANGE = "change", RESIZE = "resize", SCROLL = "scroll";
var ZoomSnapBack = Animation.extend({
init: function(options) {
var that = this;
Animation.fn.init.call(that);
extend(that, options);
that.userEvents.bind("gestureend", proxy(that.start, that));
that.tapCapture.bind("press", proxy(that.cancel, that));
},
done: function() {
return this.dimensions.minScale - this.movable.scale < .01;
},
tick: function() {
var movable = this.movable;
movable.scaleWith(1.1);
this.dimensions.rescale(movable.scale);
},
onEnd: function() {
var movable = this.movable;
movable.scaleTo(this.dimensions.minScale);
this.dimensions.rescale(movable.scale);
}
});
var DragInertia = Animation.extend({
init: function(options) {
var that = this;
Animation.fn.init.call(that);
extend(that, options, {
transition: new Transition({
axis: options.axis,
movable: options.movable,
onEnd: function() {
that._end();
}
})
});
that.tapCapture.bind("press", function() {
that.cancel();
});
that.userEvents.bind("end", proxy(that.start, that));
that.userEvents.bind("gestureend", proxy(that.start, that));
that.userEvents.bind("tap", proxy(that.onEnd, that));
},
onCancel: function() {
this.transition.cancel();
},
freeze: function(location) {
var that = this;
that.cancel();
that._moveTo(location);
},
onEnd: function() {
var that = this;
if (that._outOfBounds()) {
that._snapBack();
} else {
that._end();
}
},
done: function() {
return abs(this.velocity) < 1;
},
start: function(e) {
var that = this;
if (!that.dimension.enabled) {
return;
}
if (that._outOfBounds()) {
that._snapBack();
} else {
that.velocity = e.touch[that.axis].velocity * 16;
if (that.velocity) {
that.tapCapture.captureNext();
Animation.fn.start.call(that);
}
}
},
tick: function() {
var that = this, dimension = that.dimension, friction = that._outOfBounds() ? OUT_OF_BOUNDS_FRICTION : FRICTION, delta = that.velocity *= friction, location = that.movable[that.axis] + delta;
if (!that.elastic && dimension.outOfBounds(location)) {
location = Math.max(Math.min(location, dimension.max), dimension.min);
that.velocity = 0;
}
that.movable.moveAxis(that.axis, location);
},
_end: function() {
this.tapCapture.cancelCapture();
this.end();
},
_outOfBounds: function() {
return this.dimension.outOfBounds(this.movable[this.axis]);
},
_snapBack: function() {
var that = this, dimension = that.dimension, snapBack = that.movable[that.axis] > dimension.max ? dimension.max : dimension.min;
that._moveTo(snapBack);
},
_moveTo: function(location) {
this.transition.moveTo({
location: location,
duration: SNAPBACK_DURATION,
ease: Transition.easeOutExpo
});
}
});
var ScrollBar = Class.extend({
init: function(options) {
var that = this, horizontal = options.axis === "x", element = $('<div class="km-touch-scrollbar km-' + (horizontal ? "horizontal" : "vertical") + '-scrollbar" />');
extend(that, options, {
element: element,
elementSize: 0,
movable: new Movable(element),
scrollMovable: options.movable,
size: horizontal ? "width" : "height"
});
that.scrollMovable.bind(CHANGE, proxy(that._move, that));
that.container.append(element);
},
_move: function() {
var that = this, axis = that.axis, dimension = that.dimension, paneSize = dimension.size, scrollMovable = that.scrollMovable, sizeRatio = paneSize / dimension.total, position = Math.round(-scrollMovable[axis] * sizeRatio), size = Math.round(paneSize * sizeRatio);
if (position + size > paneSize) {
size = paneSize - position;
} else if (position < 0) {
size += position;
position = 0;
}
if (that.elementSize != size) {
that.element.css(that.size, size + "px");
that.elementSize = size;
}
that.movable.moveAxis(axis, position);
},
show: function() {
this.element.css({
opacity: SCROLLBAR_OPACITY,
visibility: "visible"
});
},
hide: function() {
this.element.css({
opacity: 0
});
}
});
var Scroller = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
if (that.options.useNative && kendo.support.hasNativeScrolling) {
element.addClass("km-native-scroller").prepend('<div class="km-scroll-header"/>');
extend(that, {
scrollElement: element,
fixedContainer: element.children().first()
});
return;
}
element.css("overflow", "hidden").addClass("km-scroll-wrapper").wrapInner('<div class="km-scroll-container"/>').prepend('<div class="km-scroll-header"/>');
var inner = element.children().eq(1), tapCapture = new kendo.TapCapture(element), movable = new Movable(inner), dimensions = new PaneDimensions({
element: inner,
container: element,
forcedEnabled: that.options.zoom,
change: function() {
that.trigger(RESIZE);
}
}), userEvents = new kendo.UserEvents(element, {
allowSelection: true,
preventDragEvent: true,
multiTouch: that.options.zoom,
start: function(e) {
dimensions.refresh();
var velocityX = abs(e.x.velocity), velocityY = abs(e.y.velocity);
if (dimensions.x.enabled && velocityX * 2 >= velocityY || dimensions.y.enabled && velocityY * 2 >= velocityX) {
userEvents.capture();
} else {
userEvents.cancel();
}
}
}), pane = new Pane({
movable: movable,
dimensions: dimensions,
userEvents: userEvents,
elastic: that.options.elastic
}), zoomSnapBack = new ZoomSnapBack({
movable: movable,
dimensions: dimensions,
userEvents: userEvents,
tapCapture: tapCapture
});
movable.bind(CHANGE, function() {
that.scrollTop = -movable.y;
that.scrollLeft = -movable.x;
that.trigger(SCROLL, {
scrollTop: that.scrollTop,
scrollLeft: that.scrollLeft
});
});
extend(that, {
movable: movable,
dimensions: dimensions,
zoomSnapBack: zoomSnapBack,
userEvents: userEvents,
pane: pane,
tapCapture: tapCapture,
pulled: false,
scrollElement: inner,
fixedContainer: element.children().first()
});
that._initAxis("x");
that._initAxis("y");
dimensions.refresh();
if (that.options.pullToRefresh) {
that._initPullToRefresh();
}
kendo.onResize($.proxy(that.reset, that));
},
scrollHeight: function() {
return this.scrollElement[0].scrollHeight;
},
scrollWidth: function() {
return this.scrollElement[0].scrollWidth;
},
options: {
name: "Scroller",
zoom: false,
pullOffset: 140,
elastic: true,
useNative: false,
pullTemplate: "Pull to refresh",
releaseTemplate: "Release to refresh",
refreshTemplate: "Refreshing"
},
events: [ PULL, SCROLL, RESIZE ],
setOptions: function(options) {
var that = this;
Widget.fn.setOptions.call(that, options);
if (options.pullToRefresh) {
that._initPullToRefresh();
}
},
reset: function() {
this.movable.moveTo({
x: 0,
y: 0
});
},
scrollTo: function(x, y) {
this.movable.moveTo({
x: x,
y: y
});
},
pullHandled: function() {
var that = this;
that.refreshHint.removeClass(REFRESHCLASS);
that.hintContainer.html(that.pullTemplate({}));
that.yinertia.onEnd();
that.xinertia.onEnd();
},
destroy: function() {
Widget.fn.destroy.call(this);
this.userEvents.destroy();
},
_initPullToRefresh: function() {
var that = this;
that.dimensions.y.forceEnabled();
that.pullTemplate = kendo.template(that.options.pullTemplate);
that.releaseTemplate = kendo.template(that.options.releaseTemplate);
that.refreshTemplate = kendo.template(that.options.refreshTemplate);
that.scrollElement.prepend('<span class="km-scroller-pull"><span class="km-icon"></span><span class="km-template">' + that.pullTemplate({}) + "</span></span>");
that.refreshHint = that.scrollElement.children().first();
that.hintContainer = that.refreshHint.children(".km-template");
that.pane.y.bind("change", proxy(that._paneChange, that));
that.userEvents.bind("end", proxy(that._dragEnd, that));
},
_dragEnd: function() {
var that = this;
if (!that.pulled) {
return;
}
that.pulled = false;
that.refreshHint.removeClass(RELEASECLASS).addClass(REFRESHCLASS);
that.hintContainer.html(that.refreshTemplate({}));
that.yinertia.freeze(that.options.pullOffset / 2);
that.trigger("pull");
},
_paneChange: function() {
var that = this;
if (that.movable.y / OUT_OF_BOUNDS_FRICTION > that.options.pullOffset) {
if (!that.pulled) {
that.pulled = true;
that.refreshHint.removeClass(REFRESHCLASS).addClass(RELEASECLASS);
that.hintContainer.html(that.releaseTemplate({}));
}
} else if (that.pulled) {
that.pulled = false;
that.refreshHint.removeClass(RELEASECLASS);
that.hintContainer.html(that.pullTemplate({}));
}
},
_initAxis: function(axis) {
var that = this, movable = that.movable, dimension = that.dimensions[axis], tapCapture = that.tapCapture, scrollBar = new ScrollBar({
axis: axis,
movable: movable,
dimension: dimension,
container: that.element
}), inertia = new DragInertia({
axis: axis,
movable: movable,
tapCapture: tapCapture,
userEvents: that.userEvents,
dimension: dimension,
elastic: that.options.elastic,
end: function() {
scrollBar.hide();
}
});
that[axis + "inertia"] = inertia;
that.pane[axis].bind(CHANGE, function() {
scrollBar.show();
});
}
});
ui.plugin(Scroller);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, proxy = $.proxy, NS = ".kendoGroupable", indicatorTmpl = kendo.template('<div class="k-group-indicator" data-#=data.ns#field="${data.field}" data-#=data.ns#title="${data.title || ""}" data-#=data.ns#dir="${data.dir || "asc"}">' + '<a href="\\#" class="k-link">' + '<span class="k-icon k-si-arrow-${(data.dir || "asc") == "asc" ? "n" : "s"}">(sorted ${(data.dir || "asc") == "asc" ? "ascending": "descending"})</span>' + "${data.title ? data.title: data.field}" + "</a>" + '<a class="k-button k-button-icon k-button-bare">' + '<span class="k-icon k-group-delete"></span>' + "</a>" + "</div>", {
useWithBlock: false
}), hint = function(target) {
return $('<div class="k-header k-drag-clue" />').css({
width: target.width(),
paddingLeft: target.css("paddingLeft"),
paddingRight: target.css("paddingRight"),
lineHeight: target.height() + "px",
paddingTop: target.css("paddingTop"),
paddingBottom: target.css("paddingBottom")
}).html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field"))).prepend('<span class="k-icon k-drag-status k-denied" />');
}, dropCue = $('<div class="k-grouping-dropclue"/>'), nameSpecialCharRegExp = /("|'|\[|\]|\$|\.|\:|\+)/g;
function dropCueOffsetTop(element) {
return element.position().top + 3;
}
var Groupable = Widget.extend({
init: function(element, options) {
var that = this, groupContainer, group = kendo.guid(), intializePositions = proxy(that._intializePositions, that), draggable, dropCuePositions = that._dropCuePositions = [];
Widget.fn.init.call(that, element, options);
that.draggable = draggable = that.options.draggable || new kendo.ui.Draggable(that.element, {
filter: that.options.draggableElements,
hint: hint,
group: group
});
groupContainer = that.groupContainer = $(that.options.groupContainer, that.element).kendoDropTarget({
group: draggable.options.group,
dragenter: function(e) {
if (that._canDrag(e.draggable.currentTarget)) {
e.draggable.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
dropCue.css({
top: dropCueOffsetTop(groupContainer),
left: 0
}).appendTo(groupContainer);
}
},
dragleave: function(e) {
e.draggable.hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
dropCue.remove();
},
drop: function(e) {
var targetElement = e.draggable.currentTarget, field = targetElement.attr(kendo.attr("field")), title = targetElement.attr(kendo.attr("title")), sourceIndicator = that.indicator(field), dropCuePositions = that._dropCuePositions, lastCuePosition = dropCuePositions[dropCuePositions.length - 1], position;
if (!targetElement.hasClass("k-group-indicator") && !that._canDrag(targetElement)) {
return;
}
if (lastCuePosition) {
position = that._dropCuePosition(kendo.getOffset(dropCue).left + parseInt(lastCuePosition.element.css("marginLeft"), 10) + parseInt(lastCuePosition.element.css("marginRight"), 10));
if (position && that._canDrop($(sourceIndicator), position.element, position.left)) {
if (position.before) {
position.element.before(sourceIndicator || that.buildIndicator(field, title));
} else {
position.element.after(sourceIndicator || that.buildIndicator(field, title));
}
that._change();
}
} else {
that.groupContainer.append(that.buildIndicator(field, title));
that._change();
}
}
}).kendoDraggable({
filter: "div.k-group-indicator",
hint: hint,
group: draggable.options.group,
dragcancel: proxy(that._dragCancel, that),
dragstart: function(e) {
var element = e.currentTarget, marginLeft = parseInt(element.css("marginLeft"), 10), left = element.position().left - marginLeft;
intializePositions();
dropCue.css({
top: dropCueOffsetTop(groupContainer),
left: left
}).appendTo(groupContainer);
this.hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
},
dragend: function() {
that._dragEnd(this);
},
drag: proxy(that._drag, that)
}).on("click" + NS, ".k-button", function(e) {
e.preventDefault();
that._removeIndicator($(this).parent());
}).on("click" + NS, ".k-link", function(e) {
var current = $(this).parent(), newIndicator = that.buildIndicator(current.attr(kendo.attr("field")), current.attr(kendo.attr("title")), current.attr(kendo.attr("dir")) == "asc" ? "desc" : "asc");
current.before(newIndicator).remove();
that._change();
e.preventDefault();
});
draggable.bind([ "dragend", "dragcancel", "dragstart", "drag" ], {
dragend: function() {
that._dragEnd(this);
},
dragcancel: proxy(that._dragCancel, that),
dragstart: function(e) {
var element, marginRight, left;
if (!that.options.allowDrag && !that._canDrag(e.currentTarget)) {
e.preventDefault();
return;
}
intializePositions();
if (dropCuePositions.length) {
element = dropCuePositions[dropCuePositions.length - 1].element;
marginRight = parseInt(element.css("marginRight"), 10);
left = element.position().left + element.outerWidth() + marginRight;
} else {
left = 0;
}
},
drag: proxy(that._drag, that)
});
that.dataSource = that.options.dataSource;
if (that.dataSource) {
that._refreshHandler = proxy(that.refresh, that);
that.dataSource.bind("change", that._refreshHandler);
}
},
refresh: function() {
var that = this, dataSource = that.dataSource;
that.groupContainer.empty().append($.map(dataSource.group() || [], function(item) {
var fieldName = item.field.replace(nameSpecialCharRegExp, "\\$1");
var element = that.element.find(that.options.filter).filter("[" + kendo.attr("field") + "=" + fieldName + "]");
return that.buildIndicator(item.field, element.attr(kendo.attr("title")), item.dir);
}).join(""));
that._invalidateGroupContainer();
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.groupContainer.off(NS).kendoDropTarget("destroy").kendoDraggable("destroy");
if (!that.options.draggable) {
that.draggable.destroy();
}
if (that.dataSource && that._refreshHandler) {
that.dataSource.unbind("change", that._refreshHandler);
}
},
options: {
name: "Groupable",
filter: "th",
draggableElements: "th",
messages: {
empty: "Drag a column header and drop it here to group by that column"
}
},
indicator: function(field) {
var indicators = $(".k-group-indicator", this.groupContainer);
return $.grep(indicators, function(item) {
return $(item).attr(kendo.attr("field")) === field;
})[0];
},
buildIndicator: function(field, title, dir) {
return indicatorTmpl({
field: field.replace(/"/g, "'"),
dir: dir,
title: title,
ns: kendo.ns
});
},
descriptors: function() {
var that = this, indicators = $(".k-group-indicator", that.groupContainer), aggregates, names, field, idx, length;
aggregates = that.element.find(that.options.filter).map(function() {
var cell = $(this), aggregate = cell.attr(kendo.attr("aggregates")), member = cell.attr(kendo.attr("field"));
if (aggregate && aggregate !== "") {
names = aggregate.split(",");
aggregate = [];
for (idx = 0, length = names.length; idx < length; idx++) {
aggregate.push({
field: member,
aggregate: names[idx]
});
}
}
return aggregate;
}).toArray();
return $.map(indicators, function(item) {
item = $(item);
field = item.attr(kendo.attr("field"));
return {
field: field,
dir: item.attr(kendo.attr("dir")),
aggregates: aggregates || []
};
});
},
_removeIndicator: function(indicator) {
var that = this;
indicator.remove();
that._invalidateGroupContainer();
that._change();
},
_change: function() {
var that = this;
if (that.dataSource) {
that.dataSource.group(that.descriptors());
}
},
_dropCuePosition: function(position) {
var dropCuePositions = this._dropCuePositions;
if (!dropCue.is(":visible") || dropCuePositions.length === 0) {
return;
}
position = Math.ceil(position);
var lastCuePosition = dropCuePositions[dropCuePositions.length - 1], right = lastCuePosition.right, marginLeft = parseInt(lastCuePosition.element.css("marginLeft"), 10), marginRight = parseInt(lastCuePosition.element.css("marginRight"), 10);
if (position >= right) {
position = {
left: lastCuePosition.element.position().left + lastCuePosition.element.outerWidth() + marginRight,
element: lastCuePosition.element,
before: false
};
} else {
position = $.grep(dropCuePositions, function(item) {
return item.left <= position && position <= item.right;
})[0];
if (position) {
position = {
left: position.element.position().left - marginLeft,
element: position.element,
before: true
};
}
}
return position;
},
_drag: function(event) {
var position = this._dropCuePosition(event.x.location);
if (position) {
dropCue.css({
left: position.left
});
}
},
_canDrag: function(element) {
var field = element.attr(kendo.attr("field"));
return element.attr(kendo.attr("groupable")) != "false" && field && (element.hasClass("k-group-indicator") || !this.indicator(field));
},
_canDrop: function(source, target, position) {
var next = source.next();
return source[0] !== target[0] && (!next[0] || target[0] !== next[0] || position > next.position().left);
},
_dragEnd: function(draggable) {
var that = this, field = draggable.currentTarget.attr(kendo.attr("field")), sourceIndicator = that.indicator(field);
if (draggable !== that.options.draggable && !draggable.dropped && sourceIndicator) {
that._removeIndicator($(sourceIndicator));
}
that._dragCancel();
},
_dragCancel: function() {
dropCue.remove();
this._dropCuePositions = [];
},
_intializePositions: function() {
var that = this, indicators = $(".k-group-indicator", that.groupContainer), left;
that._dropCuePositions = $.map(indicators, function(item) {
item = $(item);
left = kendo.getOffset(item).left;
return {
left: parseInt(left, 10),
right: parseInt(left + item.outerWidth(), 10),
element: item
};
});
},
_invalidateGroupContainer: function() {
var groupContainer = this.groupContainer;
if (groupContainer.is(":empty")) {
groupContainer.html(this.options.messages.empty);
}
}
});
kendo.ui.plugin(Groupable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, CHANGE = "change", KREORDERABLE = "k-reorderable";
function toggleHintClass(hint, denied) {
hint = $(hint);
if (denied) {
hint.find(".k-drag-status").removeClass("k-add").addClass("k-denied");
} else {
hint.find(".k-drag-status").removeClass("k-denied").addClass("k-add");
}
}
var Reorderable = Widget.extend({
init: function(element, options) {
var that = this, draggable, group = kendo.guid() + "-reorderable";
Widget.fn.init.call(that, element, options);
element = that.element.addClass(KREORDERABLE);
options = that.options;
that.draggable = draggable = options.draggable || new kendo.ui.Draggable(element, {
group: group,
filter: options.filter,
hint: options.hint
});
that.reorderDropCue = $('<div class="k-reorder-cue"><div class="k-icon k-i-arrow-s"></div><div class="k-icon k-i-arrow-n"></div></div>');
element.find(draggable.options.filter).kendoDropTarget({
group: draggable.options.group,
dragenter: function(e) {
if (!that._draggable) {
return;
}
var dropTarget = this.element, offset, same = dropTarget[0] === that._draggable[0];
toggleHintClass(e.draggable.hint, same);
if (!same) {
offset = kendo.getOffset(dropTarget);
that.reorderDropCue.css({
height: dropTarget.outerHeight(),
top: offset.top,
left: offset.left + (dropTarget.index() > that._draggable.index() ? dropTarget.outerWidth() : 0)
}).appendTo(document.body);
}
},
dragleave: function(e) {
toggleHintClass(e.draggable.hint, true);
that.reorderDropCue.remove();
},
drop: function() {
if (!that._draggable) {
return;
}
var draggableElement = that._draggable[0], dropTarget = this.element[0], container;
if (draggableElement !== dropTarget) {
container = element.find(draggable.options.filter);
that.trigger(CHANGE, {
element: that._draggable,
oldIndex: container.index(draggableElement),
newIndex: container.index(dropTarget)
});
}
}
});
draggable.bind([ "dragcancel", "dragend", "dragstart" ], {
dragcancel: function() {
that.reorderDropCue.remove();
that._draggable = null;
},
dragend: function() {
that.reorderDropCue.remove();
that._draggable = null;
},
dragstart: function(e) {
that._draggable = e.currentTarget;
}
});
},
options: {
name: "Reorderable",
filter: "*"
},
events: [ CHANGE ],
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
if (that.draggable) {
that.draggable.destroy();
}
kendo.destroy(that.element);
}
});
kendo.ui.plugin(Reorderable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, proxy = $.proxy, isFunction = $.isFunction, extend = $.extend, HORIZONTAL = "horizontal", VERTICAL = "vertical", START = "start", RESIZE = "resize", RESIZEEND = "resizeend";
var Resizable = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.orientation = that.options.orientation.toLowerCase() != VERTICAL ? HORIZONTAL : VERTICAL;
that._positionMouse = that.orientation == HORIZONTAL ? "x" : "y";
that._position = that.orientation == HORIZONTAL ? "left" : "top";
that._sizingDom = that.orientation == HORIZONTAL ? "outerWidth" : "outerHeight";
that.draggable = new ui.Draggable(element, {
distance: 0,
filter: options.handle,
drag: proxy(that._resize, that),
dragcancel: proxy(that._cancel, that),
dragstart: proxy(that._start, that),
dragend: proxy(that._stop, that)
});
that.userEvents = that.draggable.userEvents;
},
events: [ RESIZE, RESIZEEND, START ],
options: {
name: "Resizable",
orientation: HORIZONTAL
},
_max: function(e) {
var that = this, hintSize = that.hint ? that.hint[that._sizingDom]() : 0, size = that.options.max;
return isFunction(size) ? size(e) : size !== undefined ? that._initialElementPosition + size - hintSize : size;
},
_min: function(e) {
var that = this, size = that.options.min;
return isFunction(size) ? size(e) : size !== undefined ? that._initialElementPosition + size : size;
},
_start: function(e) {
var that = this, hint = that.options.hint, el = $(e.currentTarget);
that._initialElementPosition = el.position()[that._position];
that._initialMousePosition = e[that._positionMouse].startLocation;
if (hint) {
that.hint = isFunction(hint) ? $(hint(el)) : hint;
that.hint.css({
position: "absolute"
}).css(that._position, that._initialElementPosition).appendTo(that.element);
}
that.trigger(START, e);
that._maxPosition = that._max(e);
that._minPosition = that._min(e);
$(document.body).css("cursor", el.css("cursor"));
},
_resize: function(e) {
var that = this, handle = $(e.currentTarget), maxPosition = that._maxPosition, minPosition = that._minPosition, currentPosition = that._initialElementPosition + (e[that._positionMouse].location - that._initialMousePosition), position;
position = minPosition !== undefined ? Math.max(minPosition, currentPosition) : currentPosition;
that.position = position = maxPosition !== undefined ? Math.min(maxPosition, position) : position;
if (that.hint) {
that.hint.toggleClass(that.options.invalidClass || "", position == maxPosition || position == minPosition).css(that._position, position);
}
that.resizing = true;
that.trigger(RESIZE, extend(e, {
position: position
}));
},
_stop: function(e) {
var that = this;
if (that.hint) {
that.hint.remove();
}
that.resizing = false;
that.trigger(RESIZEEND, extend(e, {
position: that.position
}));
$(document.body).css("cursor", "");
},
_cancel: function(e) {
var that = this;
if (that.hint) {
that.position = undefined;
that.hint.css(that._position, that._initialElementPosition);
that._stop(e);
}
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
if (that.draggable) {
that.draggable.destroy();
}
},
press: function(target) {
if (!target) {
return;
}
var position = target.position(), that = this;
that.userEvents.press(position.left, position.top, target[0]);
that.targetPosition = position;
that.target = target;
},
move: function(delta) {
var that = this, orientation = that._position, position = that.targetPosition, current = that.position;
if (current === undefined) {
current = position[orientation];
}
position[orientation] = current + delta;
that.userEvents.move(position.left, position.top);
},
end: function() {
this.userEvents.end();
this.target = this.position = undefined;
}
});
kendo.ui.plugin(Resizable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, proxy = $.proxy, DIR = "dir", ASC = "asc", SINGLE = "single", FIELD = "field", DESC = "desc", NS = ".kendoSortable", TLINK = ".k-link", ARIASORT = "aria-sort", Widget = kendo.ui.Widget;
var Sortable = Widget.extend({
init: function(element, options) {
var that = this, link;
Widget.fn.init.call(that, element, options);
that._refreshHandler = proxy(that.refresh, that);
that.dataSource = that.options.dataSource.bind("change", that._refreshHandler);
link = that.element.find(TLINK);
if (!link[0]) {
link = that.element.wrapInner('<a class="k-link" href="#"/>').find(TLINK);
}
that.link = link;
that.element.on("click" + NS, proxy(that._click, that));
},
options: {
name: "Sortable",
mode: SINGLE,
allowUnsort: true
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.element.off(NS);
that.dataSource.unbind("change", that._refreshHandler);
},
refresh: function() {
var that = this, sort = that.dataSource.sort() || [], idx, length, descriptor, dir, element = that.element, field = element.attr(kendo.attr(FIELD));
element.removeAttr(kendo.attr(DIR));
element.removeAttr(ARIASORT);
for (idx = 0, length = sort.length; idx < length; idx++) {
descriptor = sort[idx];
if (field == descriptor.field) {
element.attr(kendo.attr(DIR), descriptor.dir);
}
}
dir = element.attr(kendo.attr(DIR));
element.find(".k-i-arrow-n,.k-i-arrow-s").remove();
if (dir === ASC) {
$('<span class="k-icon k-i-arrow-n" />').appendTo(that.link);
element.attr(ARIASORT, "ascending");
} else if (dir === DESC) {
$('<span class="k-icon k-i-arrow-s" />').appendTo(that.link);
element.attr(ARIASORT, "descending");
}
},
_click: function(e) {
var that = this, element = that.element, field = element.attr(kendo.attr(FIELD)), dir = element.attr(kendo.attr(DIR)), options = that.options, sort = that.dataSource.sort() || [], idx, length;
if (dir === ASC) {
dir = DESC;
} else if (dir === DESC && options.allowUnsort) {
dir = undefined;
} else {
dir = ASC;
}
if (options.mode === SINGLE) {
sort = [ {
field: field,
dir: dir
} ];
} else if (options.mode === "multiple") {
for (idx = 0, length = sort.length; idx < length; idx++) {
if (sort[idx].field === field) {
sort.splice(idx, 1);
break;
}
}
sort.push({
field: field,
dir: dir
});
}
e.preventDefault();
that.dataSource.sort(sort);
}
});
kendo.ui.plugin(Sortable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, proxy = $.proxy, abs = Math.abs, ARIASELECTED = "aria-selected", SELECTED = "k-state-selected", ACTIVE = "k-state-selecting", SELECTABLE = "k-selectable", CHANGE = "change", NS = ".kendoSelectable", UNSELECTING = "k-state-unselecting", supportEventDelegation = false;
(function($) {
(function() {
$('<div class="parent"><span /></div>').on("click", ">*", function() {
supportEventDelegation = true;
}).find("span").click().end().off();
})();
})($);
var Selectable = Widget.extend({
init: function(element, options) {
var that = this, multiple;
Widget.fn.init.call(that, element, options);
that._marquee = $("<div class='k-marquee'></div>");
that._lastActive = null;
that.element.addClass(SELECTABLE);
multiple = that.options.multiple;
that.userEvents = new kendo.UserEvents(that.element, {
global: true,
allowSelection: true,
filter: (!supportEventDelegation ? "." + SELECTABLE + " " : "") + that.options.filter,
tap: proxy(that._tap, that)
});
if (multiple) {
that.userEvents.bind("start", proxy(that._start, that)).bind("move", proxy(that._move, that)).bind("end", proxy(that._end, that)).bind("select", proxy(that._select, that));
}
},
events: [ CHANGE ],
options: {
name: "Selectable",
filter: ">*",
multiple: false
},
_tap: function(e) {
var target = $(e.target), that = this, ctrlKey = e.event.ctrlKey, multiple = that.options.multiple, shiftKey = multiple && e.event.shiftKey, selected, whichCode = e.event.which, buttonCode = e.event.button;
//in case of hierarchy or right-click
if (target.closest("." + SELECTABLE)[0] !== that.element[0] || whichCode && whichCode == 3 || buttonCode && buttonCode == 2) {
return;
}
selected = target.hasClass(SELECTED);
if (!multiple || !ctrlKey) {
that.clear();
}
if (shiftKey) {
that.selectRange(that._firstSelectee(), target);
} else {
if (selected && ctrlKey) {
that._unselect(target);
that._notify(CHANGE);
} else {
that.value(target);
}
that._lastActive = that._downTarget = target;
}
},
_start: function(e) {
var that = this, target = $(e.target), selected = target.hasClass(SELECTED), ctrlKey = e.event.ctrlKey;
that._downTarget = target;
//in case of hierarchy
if (target.closest("." + SELECTABLE)[0] !== that.element[0]) {
that.userEvents.cancel();
that._downTarget = null;
return;
}
that._marquee.appendTo(document.body).css({
left: e.x.client + 1,
top: e.y.client + 1,
width: 0,
height: 0
});
if (!ctrlKey) {
that.clear();
}
if (selected) {
that._selectElement(target, true);
if (ctrlKey) {
target.addClass(UNSELECTING);
}
}
},
_move: function(e) {
var that = this, position = {
left: e.x.startLocation > e.x.location ? e.x.location : e.x.startLocation,
top: e.y.startLocation > e.y.location ? e.y.location : e.y.startLocation,
width: abs(e.x.initialDelta),
height: abs(e.y.initialDelta)
}, items = that.element.find(that.options.filter);
that._marquee.css(position);
invalidateSelectables(items, that._downTarget[0], position, e.event.ctrlKey);
e.preventDefault();
},
_end: function() {
var that = this;
that._marquee.remove();
that._unselect(that.element.find(that.options.filter + "." + UNSELECTING)).removeClass(UNSELECTING);
that.value(that.element.find(that.options.filter + "." + ACTIVE));
that._lastActive = that._downTarget;
},
value: function(val) {
var that = this, selectElement = proxy(that._selectElement, that);
if (val) {
val.each(function() {
selectElement(this);
});
that._notify(CHANGE);
return;
}
return that.element.find(that.options.filter + "." + SELECTED);
},
_firstSelectee: function() {
var that = this, selected;
if (that._lastActive !== null) {
return that._lastActive;
}
selected = that.value();
return selected.length > 0 ? selected[0] : that.element.find(that.options.filter);
},
_selectElement: function(element, preventNotify) {
var toSelect = $(element), isPrevented = !preventNotify && this._notify("select", {
element: element
});
toSelect.removeClass(ACTIVE);
if (!isPrevented) {
toSelect.addClass(SELECTED);
if (this.options.aria) {
toSelect.attr(ARIASELECTED, true);
}
}
},
_notify: function(name, args) {
args = args || {};
return this.trigger(name, args);
},
_unselect: function(element) {
element.removeClass(SELECTED);
if (this.options.aria) {
element.attr(ARIASELECTED, false);
}
return element;
},
_select: function(e) {
if ($(e.event.target).is("input,a,textarea")) {
this.userEvents.cancel();
this._downTarget = null;
} else {
e.preventDefault();
}
},
clear: function() {
var items = this.element.find(this.options.filter + "." + SELECTED);
this._unselect(items);
},
selectRange: function(start, end) {
var that = this, found = false, idx, length, tmp, toSelect, items = that.element.find(that.options.filter), selectElement = proxy(that._selectElement, that);
start = $(start)[0];
end = $(end)[0];
for (idx = 0, length = items.length; idx < length; idx++) {
toSelect = items[idx];
if (found) {
selectElement(toSelect);
found = toSelect !== end;
} else if (toSelect === start) {
found = start !== end;
selectElement(toSelect);
} else if (toSelect === end) {
tmp = start;
start = end;
end = tmp;
found = true;
selectElement(toSelect);
} else {
$(toSelect).removeClass(SELECTED);
}
}
that._notify(CHANGE);
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.element.off(NS);
that.userEvents.destroy();
}
});
function collision(element, position) {
var elementPosition = kendo.getOffset(element), right = position.left + position.width, bottom = position.top + position.height;
elementPosition.right = elementPosition.left + element.outerWidth();
elementPosition.bottom = elementPosition.top + element.outerHeight();
return !(elementPosition.left > right || elementPosition.right < position.left || elementPosition.top > bottom || elementPosition.bottom < position.top);
}
function invalidateSelectables(items, target, position, ctrlKey) {
var idx, length, toSelect;
for (idx = 0, length = items.length; idx < length; idx++) {
toSelect = items.eq(idx);
if (collision(toSelect, position)) {
if (toSelect.hasClass(SELECTED)) {
if (ctrlKey && target !== toSelect[0]) {
toSelect.removeClass(SELECTED).addClass(UNSELECTING);
}
} else if (!toSelect.hasClass(ACTIVE) && !toSelect.hasClass(UNSELECTING)) {
toSelect.addClass(ACTIVE);
}
} else {
if (toSelect.hasClass(ACTIVE)) {
toSelect.removeClass(ACTIVE);
} else if (ctrlKey && toSelect.hasClass(UNSELECTING)) {
toSelect.removeClass(UNSELECTING).addClass(SELECTED);
}
}
}
}
kendo.ui.plugin(Selectable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, proxy = $.proxy, FIRST = ".k-i-seek-w", LAST = ".k-i-seek-e", PREV = ".k-i-arrow-w", NEXT = ".k-i-arrow-e", CHANGE = "change", NS = ".kendoPager", CLICK = "click", KEYDOWN = "keydown", DISABLED = "disabled", iconTemplate = kendo.template('<a href="\\#" title="#=text#" class="k-link"><span class="k-icon #= className #">#=text#</span></a>');
function button(template, idx, text, numeric) {
return template({
idx: idx,
text: text,
ns: kendo.ns,
numeric: numeric
});
}
function icon(className, text) {
return iconTemplate({
className: className.substring(1),
text: text
});
}
function update(element, selector, page, disabled) {
element.find(selector).parent().attr(kendo.attr("page"), page).attr("tabindex", -1).toggleClass("k-state-disabled", disabled);
}
function first(element, page) {
update(element, FIRST, 1, page <= 1);
}
function prev(element, page) {
update(element, PREV, Math.max(1, page - 1), page <= 1);
}
function next(element, page, totalPages) {
update(element, NEXT, Math.min(totalPages, page + 1), page >= totalPages);
}
function last(element, page, totalPages) {
update(element, LAST, totalPages, page >= totalPages);
}
var Pager = Widget.extend({
init: function(element, options) {
var that = this, page, totalPages;
Widget.fn.init.call(that, element, options);
options = that.options;
that.dataSource = kendo.data.DataSource.create(options.dataSource);
that.linkTemplate = kendo.template(that.options.linkTemplate);
that.selectTemplate = kendo.template(that.options.selectTemplate);
page = that.page();
totalPages = that.totalPages();
that._refreshHandler = proxy(that.refresh, that);
that.dataSource.bind(CHANGE, that._refreshHandler);
if (options.previousNext) {
if (!that.element.find(FIRST).length) {
that.element.append(icon(FIRST, options.messages.first));
first(that.element, page, totalPages);
}
if (!that.element.find(PREV).length) {
that.element.append(icon(PREV, options.messages.previous));
prev(that.element, page, totalPages);
}
}
if (options.numeric) {
that.list = that.element.find(".k-pager-numbers");
if (!that.list.length) {
that.list = $('<ul class="k-pager-numbers k-reset" />').appendTo(that.element);
}
}
if (options.input) {
if (!that.element.find(".k-pager-input").length) {
that.element.append('<span class="k-pager-input k-label">' + options.messages.page + '<input class="k-textbox">' + kendo.format(options.messages.of, totalPages) + "</span>");
}
that.element.on(KEYDOWN + NS, ".k-pager-input input", proxy(that._keydown, that));
}
if (options.previousNext) {
if (!that.element.find(NEXT).length) {
that.element.append(icon(NEXT, options.messages.next));
next(that.element, page, totalPages);
}
if (!that.element.find(LAST).length) {
that.element.append(icon(LAST, options.messages.last));
last(that.element, page, totalPages);
}
}
if (options.pageSizes) {
if (!that.element.find(".k-pager-sizes").length) {
$('<span class="k-pager-sizes k-label"><select/>' + options.messages.itemsPerPage + "</span>").appendTo(that.element).find("select").html($.map($.isArray(options.pageSizes) ? options.pageSizes : [ 5, 10, 20 ], function(page) {
return "<option>" + page + "</option>";
}).join("")).end().appendTo(that.element);
}
that.element.find(".k-pager-sizes select").val(that.pageSize());
if (kendo.ui.DropDownList) {
that.element.find(".k-pager-sizes select").show().kendoDropDownList();
}
that.element.on(CHANGE + NS, ".k-pager-sizes select", proxy(that._change, that));
}
if (options.refresh) {
if (!that.element.find(".k-pager-refresh").length) {
that.element.append('<a href="#" class="k-pager-refresh k-link" title="' + options.messages.refresh + '"><span class="k-icon k-i-refresh">' + options.messages.refresh + "</span></a>");
}
that.element.on(CLICK + NS, ".k-pager-refresh", proxy(that._refreshClick, that));
}
if (options.info) {
if (!that.element.find(".k-pager-info").length) {
that.element.append('<span class="k-pager-info k-label" />');
}
}
that.element.on(CLICK + NS, "a", proxy(that._click, that)).addClass("k-pager-wrap k-widget");
if (options.autoBind) {
that.refresh();
}
kendo.notify(that);
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.element.off(NS);
that.dataSource.unbind(CHANGE, that._refreshHandler);
},
events: [ CHANGE ],
options: {
name: "Pager",
selectTemplate: '<li><span class="k-state-selected">#=text#</span></li>',
linkTemplate: '<li><a tabindex="-1" href="\\#" class="k-link" data-#=ns#page="#=idx#">#=text#</a></li>',
buttonCount: 10,
autoBind: true,
numeric: true,
info: true,
input: false,
previousNext: true,
pageSizes: false,
refresh: false,
messages: {
display: "{0} - {1} of {2} items",
empty: "No items to display",
page: "Page",
of: "of {0}",
itemsPerPage: "items per page",
first: "Go to the first page",
previous: "Go to the previous page",
next: "Go to the next page",
last: "Go to the last page",
refresh: "Refresh"
}
},
setDataSource: function(dataSource) {
var that = this;
that.dataSource.unbind(CHANGE, that._refreshHandler);
that.dataSource = that.options.dataSource = dataSource;
dataSource.bind(CHANGE, that._refreshHandler);
if (that.options.autoBind) {
dataSource.fetch();
}
},
refresh: function() {
var that = this, idx, end, start = 1, html = "", reminder, page = that.page(), options = that.options, pageSize = that.pageSize(), total = that.dataSource.total(), totalPages = that.totalPages(), linkTemplate = that.linkTemplate, buttonCount = options.buttonCount;
if (options.numeric) {
if (page > buttonCount) {
reminder = page % buttonCount;
start = reminder === 0 ? page - buttonCount + 1 : page - reminder + 1;
}
end = Math.min(start + buttonCount - 1, totalPages);
if (start > 1) {
html += button(linkTemplate, start - 1, "...", false);
}
for (idx = start; idx <= end; idx++) {
html += button(idx == page ? that.selectTemplate : linkTemplate, idx, idx, true);
}
if (end < totalPages) {
html += button(linkTemplate, idx, "...", false);
}
if (html === "") {
html = that.selectTemplate({
text: 0
});
}
that.list.html(html);
}
if (options.info) {
if (total > 0) {
html = kendo.format(options.messages.display, (page - 1) * pageSize + 1, // first item in the page
Math.min(page * pageSize, total), // last item in the page
total);
} else {
html = options.messages.empty;
}
that.element.find(".k-pager-info").html(html);
}
if (options.input) {
that.element.find(".k-pager-input").html(that.options.messages.page + '<input class="k-textbox">' + kendo.format(options.messages.of, totalPages)).find("input").val(page).attr(DISABLED, total < 1).toggleClass("k-state-disabled", total < 1);
}
if (options.previousNext) {
first(that.element, page, totalPages);
prev(that.element, page, totalPages);
next(that.element, page, totalPages);
last(that.element, page, totalPages);
}
if (options.pageSizes) {
that.element.find(".k-pager-sizes select").val(pageSize).filter("[" + kendo.attr("role") + "=dropdownlist]").kendoDropDownList("value", pageSize).kendoDropDownList("text", pageSize);
}
},
_keydown: function(e) {
if (e.keyCode === kendo.keys.ENTER) {
var input = this.element.find(".k-pager-input").find("input"), page = parseInt(input.val(), 10);
if (isNaN(page) || page < 1 || page > this.totalPages()) {
page = this.page();
}
input.val(page);
this.page(page);
}
},
_refreshClick: function(e) {
e.preventDefault();
this.dataSource.read();
},
_change: function(e) {
var pageSize = parseInt(e.currentTarget.value, 10);
if (!isNaN(pageSize)) {
this.dataSource.pageSize(pageSize);
}
},
_click: function(e) {
var target = $(e.currentTarget);
e.preventDefault();
if (!target.is(".k-state-disabled")) {
this.page(target.attr(kendo.attr("page")));
}
},
totalPages: function() {
return Math.ceil((this.dataSource.total() || 0) / this.pageSize());
},
pageSize: function() {
return this.dataSource.pageSize() || this.dataSource.total();
},
page: function(page) {
if (page !== undefined) {
this.dataSource.page(page);
this.trigger(CHANGE, {
index: page
});
} else {
if (this.dataSource.total() > 0) {
return this.dataSource.page();
} else {
return 0;
}
}
}
});
ui.plugin(Pager);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, support = kendo.support, getOffset = kendo.getOffset, activeElement = kendo._activeElement, OPEN = "open", CLOSE = "close", DEACTIVATE = "deactivate", ACTIVATE = "activate", CENTER = "center", LEFT = "left", RIGHT = "right", TOP = "top", BOTTOM = "bottom", ABSOLUTE = "absolute", HIDDEN = "hidden", BODY = "body", LOCATION = "location", POSITION = "position", VISIBLE = "visible", EFFECTS = "effects", ACTIVE = "k-state-active", ACTIVEBORDER = "k-state-border", ACTIVECHILDREN = ".k-picker-wrap, .k-dropdown-wrap, .k-link", MOUSEDOWN = "down", WINDOW = $(window), DOCUMENT_ELEMENT = $(document.documentElement), RESIZE_SCROLL = "resize scroll", cssPrefix = support.transitions.css, TRANSFORM = cssPrefix + "transform", extend = $.extend, NS = ".kendoPopup", styles = [ "font-family", "font-size", "font-stretch", "font-style", "font-weight", "line-height" ];
function contains(container, target) {
return container === target || $.contains(container, target);
}
var Popup = Widget.extend({
init: function(element, options) {
var that = this, parentPopup;
options = options || {};
if (options.isRtl) {
options.origin = options.origin || BOTTOM + " " + RIGHT;
options.position = options.position || TOP + " " + RIGHT;
}
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
that.collisions = options.collision ? options.collision.split(" ") : [];
if (that.collisions.length === 1) {
that.collisions.push(that.collisions[0]);
}
parentPopup = $(that.options.anchor).closest(".k-popup,.k-group").filter(":not([class^=km-])");
// When popup is in another popup, make it relative.
options.appendTo = $($(options.appendTo)[0] || parentPopup[0] || BODY);
that.element.hide().addClass("k-popup k-group k-reset").toggleClass("k-rtl", !!options.isRtl).css({
position: ABSOLUTE
}).appendTo(options.appendTo).on("mouseenter" + NS, function() {
that._hovered = true;
}).on("mouseleave" + NS, function() {
that._hovered = false;
});
that.wrapper = $();
if (options.animation === false) {
options.animation = {
open: {
effects: {}
},
close: {
hide: true,
effects: {}
}
};
}
extend(options.animation.open, {
complete: function() {
that.wrapper.css({
overflow: VISIBLE
});
// Forcing refresh causes flickering in mobile.
that.trigger(ACTIVATE);
}
});
extend(options.animation.close, {
complete: function() {
that.wrapper.hide();
var location = that.wrapper.data(LOCATION), anchor = $(options.anchor), direction, dirClass;
if (location) {
that.wrapper.css(location);
}
if (options.anchor != BODY) {
direction = anchor.hasClass(ACTIVEBORDER + "-down") ? "down" : "up";
dirClass = ACTIVEBORDER + "-" + direction;
anchor.removeClass(dirClass).children(ACTIVECHILDREN).removeClass(ACTIVE).removeClass(dirClass);
element.removeClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
}
that._closing = false;
that.trigger(DEACTIVATE);
}
});
that._mousedownProxy = function(e) {
that._mousedown(e);
};
that._resizeProxy = function(e) {
that._resize(e);
};
if (options.toggleTarget) {
$(options.toggleTarget).on(options.toggleEvent + NS, $.proxy(that.toggle, that));
}
},
events: [ OPEN, ACTIVATE, CLOSE, DEACTIVATE ],
options: {
name: "Popup",
toggleEvent: "click",
origin: BOTTOM + " " + LEFT,
position: TOP + " " + LEFT,
anchor: BODY,
collision: "flip fit",
viewport: window,
copyAnchorStyles: true,
animation: {
open: {
effects: "slideIn:down",
transition: true,
duration: 200
},
close: {
// if close animation effects are defined, they will be used instead of open.reverse
duration: 100,
hide: true
}
}
},
destroy: function() {
var that = this, options = that.options, element = that.element.off(NS), parent;
Widget.fn.destroy.call(that);
if (options.toggleTarget) {
$(options.toggleTarget).off(NS);
}
DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy);
WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);
if (options.appendTo[0] === document.body) {
parent = element.parent(".k-animation-container");
if (parent[0]) {
parent.remove();
} else {
element.remove();
}
}
kendo.destroy(that.element.children());
},
open: function(x, y) {
var that = this, fixed = {
isFixed: !isNaN(parseInt(y, 10)),
x: x,
y: y
}, element = that.element, options = that.options, direction = "down", animation, wrapper, anchor = $(options.anchor);
if (!that.visible()) {
if (options.copyAnchorStyles) {
element.css(kendo.getComputedStyles(anchor[0], styles));
}
if (element.data("animating") || that.trigger(OPEN)) {
return;
}
DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy).bind(MOUSEDOWN, that._mousedownProxy);
// this binding hangs iOS in editor
if (!(support.mobileOS.ios || support.mobileOS.android)) {
WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy).bind(RESIZE_SCROLL, that._resizeProxy);
}
that.wrapper = wrapper = kendo.wrap(element).css({
overflow: HIDDEN,
display: "block",
position: ABSOLUTE
});
if (support.mobileOS.android) {
wrapper.add(anchor).css(TRANSFORM, "translatez(0)");
}
wrapper.css(POSITION);
if ($(options.appendTo)[0] == document.body) {
wrapper.css(TOP, "-10000px");
}
animation = extend(true, {}, options.animation.open);
that.flipped = that._position(fixed);
animation.effects = kendo.parseEffects(animation.effects, that.flipped);
direction = animation.effects.slideIn ? animation.effects.slideIn.direction : direction;
if (options.anchor != BODY) {
var dirClass = ACTIVEBORDER + "-" + direction;
element.addClass(ACTIVEBORDER + "-" + kendo.directions[direction].reverse);
anchor.addClass(dirClass).children(ACTIVECHILDREN).addClass(ACTIVE).addClass(dirClass);
}
element.data(EFFECTS, animation.effects).kendoStop(true).kendoAnimate(animation);
}
},
toggle: function() {
var that = this;
that[that.visible() ? CLOSE : OPEN]();
},
visible: function() {
return this.element.is(":" + VISIBLE);
},
close: function() {
var that = this, options = that.options, wrap, animation, openEffects, closeEffects;
if (that.visible()) {
wrap = that.wrapper[0] ? that.wrapper : kendo.wrap(that.element).hide();
if (that._closing || that.trigger(CLOSE)) {
return;
}
// Close all inclusive popups.
that.element.find(".k-popup").each(function() {
var that = $(this), popup = that.data("kendoPopup");
if (popup) {
popup.close();
}
});
DOCUMENT_ELEMENT.unbind(MOUSEDOWN, that._mousedownProxy);
WINDOW.unbind(RESIZE_SCROLL, that._resizeProxy);
animation = extend(true, {}, options.animation.close);
openEffects = that.element.data(EFFECTS);
closeEffects = animation.effects;
if (!closeEffects && !kendo.size(closeEffects) && openEffects && kendo.size(openEffects)) {
animation.effects = openEffects;
animation.reverse = true;
}
that._closing = true;
that.element.kendoStop(true);
wrap.css({
overflow: HIDDEN
});
// stop callback will remove hidden overflow
that.element.kendoAnimate(animation);
}
},
_resize: function(e) {
var that = this;
if (e.type === "resize") {
clearTimeout(that._resizeTimeout);
that._resizeTimeout = setTimeout(function() {
that._position();
that._resizeTimeout = null;
}, 50);
} else {
if (!that._hovered && !contains(that.element[0], activeElement())) {
that.close();
}
}
},
_mousedown: function(e) {
var that = this, container = that.element[0], options = that.options, anchor = $(options.anchor)[0], toggleTarget = options.toggleTarget, target = kendo.eventTarget(e), popup = $(target).closest(".k-popup")[0];
if (popup && popup !== that.element[0]) {
return;
}
if (!contains(container, target) && !contains(anchor, target) && !(toggleTarget && contains($(toggleTarget)[0], target))) {
that.close();
}
},
_fit: function(position, size, viewPortSize) {
var output = 0;
if (position + size > viewPortSize) {
output = viewPortSize - (position + size);
}
if (position < 0) {
output = -position;
}
return output;
},
_flip: function(offset, size, anchorSize, viewPortSize, origin, position, boxSize) {
var output = 0;
boxSize = boxSize || size;
if (position !== origin && position !== CENTER && origin !== CENTER) {
if (offset + boxSize > viewPortSize) {
output += -(anchorSize + size);
}
if (offset + output < 0) {
output += anchorSize + size;
}
}
return output;
},
_position: function(fixed) {
var that = this, element = that.element.css(POSITION, ""), wrapper = that.wrapper, options = that.options, viewport = $(options.viewport), viewportOffset = $(viewport).offset(), anchor = $(options.anchor), origins = options.origin.toLowerCase().split(" "), positions = options.position.toLowerCase().split(" "), collisions = that.collisions, zoomLevel = support.zoomLevel(), siblingContainer, parents, parentZIndex, zIndex = 10002, idx = 0, length;
siblingContainer = anchor.parents().filter(wrapper.siblings());
if (siblingContainer[0]) {
parentZIndex = Number($(siblingContainer).css("zIndex"));
if (parentZIndex) {
zIndex = parentZIndex + 1;
} else {
parents = anchor.parentsUntil(siblingContainer);
for (length = parents.length; idx < length; idx++) {
parentZIndex = Number($(parents[idx]).css("zIndex"));
if (parentZIndex && zIndex < parentZIndex) {
zIndex = parentZIndex + 1;
}
}
}
}
wrapper.css("zIndex", zIndex);
if (fixed && fixed.isFixed) {
wrapper.css({
left: fixed.x,
top: fixed.y
});
} else {
wrapper.css(that._align(origins, positions));
}
var pos = getOffset(wrapper, POSITION, anchor[0] === wrapper.offsetParent()[0]), offset = getOffset(wrapper), anchorParent = anchor.offsetParent().parent(".k-animation-container,.k-popup,.k-group");
// If the parent is positioned, get the current positions
if (anchorParent.length) {
pos = getOffset(wrapper, POSITION, true);
offset = getOffset(wrapper);
}
if (viewport[0] === window) {
offset.top -= window.pageYOffset || document.documentElement.scrollTop || 0;
offset.left -= window.pageXOffset || document.documentElement.scrollLeft || 0;
} else {
offset.top -= viewportOffset.top;
offset.left -= viewportOffset.left;
}
if (!that.wrapper.data(LOCATION)) {
// Needed to reset the popup location after every closure - fixes the resize bugs.
wrapper.data(LOCATION, extend({}, pos));
}
var offsets = extend({}, offset), location = extend({}, pos);
if (collisions[0] === "fit") {
location.top += that._fit(offsets.top, wrapper.outerHeight(), viewport.height() / zoomLevel);
}
if (collisions[1] === "fit") {
location.left += that._fit(offsets.left, wrapper.outerWidth(), viewport.width() / zoomLevel);
}
var flipPos = extend({}, location);
if (collisions[0] === "flip") {
location.top += that._flip(offsets.top, element.outerHeight(), anchor.outerHeight(), viewport.height() / zoomLevel, origins[0], positions[0], wrapper.outerHeight());
}
if (collisions[1] === "flip") {
location.left += that._flip(offsets.left, element.outerWidth(), anchor.outerWidth(), viewport.width() / zoomLevel, origins[1], positions[1], wrapper.outerWidth());
}
element.css(POSITION, ABSOLUTE);
wrapper.css(location);
return location.left != flipPos.left || location.top != flipPos.top;
},
_align: function(origin, position) {
var that = this, element = that.wrapper, anchor = $(that.options.anchor), verticalOrigin = origin[0], horizontalOrigin = origin[1], verticalPosition = position[0], horizontalPosition = position[1], anchorOffset = getOffset(anchor), appendTo = $(that.options.appendTo), appendToOffset, width = element.outerWidth(), height = element.outerHeight(), anchorWidth = anchor.outerWidth(), anchorHeight = anchor.outerHeight(), top = anchorOffset.top, left = anchorOffset.left, round = Math.round;
if (appendTo[0] != document.body) {
appendToOffset = getOffset(appendTo);
top -= appendToOffset.top;
left -= appendToOffset.left;
}
if (verticalOrigin === BOTTOM) {
top += anchorHeight;
}
if (verticalOrigin === CENTER) {
top += round(anchorHeight / 2);
}
if (verticalPosition === BOTTOM) {
top -= height;
}
if (verticalPosition === CENTER) {
top -= round(height / 2);
}
if (horizontalOrigin === RIGHT) {
left += anchorWidth;
}
if (horizontalOrigin === CENTER) {
left += round(anchorWidth / 2);
}
if (horizontalPosition === RIGHT) {
left -= width;
}
if (horizontalPosition === CENTER) {
left -= round(width / 2);
}
return {
top: top,
left: left
};
}
});
ui.plugin(Popup);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, Popup = kendo.ui.Popup, isFunction = $.isFunction, isPlainObject = $.isPlainObject, extend = $.extend, proxy = $.proxy, DOCUMENT = $(document), isLocalUrl = kendo.isLocalUrl, ARIAIDSUFFIX = "_tt_active", DESCRIBEDBY = "aria-describedby", SHOW = "show", HIDE = "hide", ERROR = "error", CONTENTLOAD = "contentLoad", REQUESTSTART = "requestStart", KCONTENTFRAME = "k-content-frame", TEMPLATE = '<div role="tooltip" class="k-widget k-tooltip#if (!autoHide) {# k-tooltip-closable#}#">#if (!autoHide) {# <div class="k-tooltip-button"><a href="\\#" class="k-icon k-i-close">close</a></div> #}#' + '<div class="k-tooltip-content"></div>' + '#if (callout){ #<div class="k-callout k-callout-#=dir#"></div>#}#' + "</div>", IFRAMETEMPLATE = kendo.template("<iframe frameborder='0' class='" + KCONTENTFRAME + "' " + "src='#= content.url #'>" + "This page requires frames in order to show content" + "</iframe>"), NS = ".kendoTooltip", POSITIONS = {
bottom: {
origin: "bottom center",
position: "top center"
},
top: {
origin: "top center",
position: "bottom center"
},
left: {
origin: "center left",
position: "center right",
collision: "fit flip"
},
right: {
origin: "center right",
position: "center left",
collision: "fit flip"
},
center: {
position: "center center",
origin: "center center"
}
}, REVERSE = {
top: "bottom",
bottom: "top",
left: "right",
right: "left",
center: "center"
}, DIRCLASSES = {
bottom: "n",
top: "s",
left: "e",
right: "w",
center: "n"
}, DIMENSIONS = {
horizontal: {
offset: "top",
size: "height"
},
vertical: {
offset: "left",
size: "width"
}
}, DEFAULTCONTENT = function(e) {
return e.target.data(kendo.ns + "title");
};
function restoreTitle(element) {
while (element.length) {
restoreTitleAttributeForElement(element);
element = element.parent();
}
}
function restoreTitleAttributeForElement(element) {
var title = element.data(kendo.ns + "title");
if (title) {
element.attr("title", title);
element.removeData(kendo.ns + "title");
}
}
function saveTitleAttributeForElement(element) {
var title = element.attr("title");
if (title) {
element.data(kendo.ns + "title", title);
element.attr("title", "");
}
}
function saveTitleAttributes(element) {
while (element.length) {
saveTitleAttributeForElement(element);
element = element.parent();
}
}
var Tooltip = Widget.extend({
init: function(element, options) {
var that = this, axis;
Widget.fn.init.call(that, element, options);
axis = that.options.position.match(/left|right/) ? "horizontal" : "vertical";
that.dimensions = DIMENSIONS[axis];
that._documentKeyDownHandler = proxy(that._documentKeyDown, that);
that.element.on(that.options.showOn + NS, that.options.filter, proxy(that._showOn, that)).on("mouseenter" + NS, that.options.filter, proxy(that._mouseenter, that));
if (this.options.autoHide) {
that.element.on("mouseleave" + NS, that.options.filter, proxy(that._mouseleave, that));
}
},
options: {
name: "Tooltip",
filter: "",
content: DEFAULTCONTENT,
showAfter: 100,
callout: true,
position: "bottom",
showOn: "mouseenter",
autoHide: true,
animation: {
open: {
effects: "fade:in",
duration: 0
},
close: {
effects: "fade:out",
duration: 40,
hide: true
}
}
},
events: [ SHOW, HIDE, CONTENTLOAD, ERROR, REQUESTSTART ],
_mouseenter: function(e) {
saveTitleAttributes($(e.currentTarget));
},
_showOn: function(e) {
var that = this;
if (that.options.showOn && that.options.showOn.match(/click|focus/)) {
that._show($(e.currentTarget));
} else {
clearTimeout(that.timeout);
that.timeout = setTimeout(function() {
that._show($(e.currentTarget));
}, that.options.showAfter);
}
},
_appendContent: function(target) {
var that = this, contentOptions = that.options.content, element = that.content, showIframe = that.options.iframe, iframe;
if (isPlainObject(contentOptions) && contentOptions.url) {
if (!("iframe" in that.options)) {
showIframe = !isLocalUrl(contentOptions.url);
}
that.trigger(REQUESTSTART, {
options: contentOptions,
target: target
});
if (!showIframe) {
element.empty();
kendo.ui.progress(element, true);
// perform AJAX request
that._ajaxRequest(contentOptions);
} else {
element.hide();
iframe = element.find("." + KCONTENTFRAME)[0];
if (iframe) {
// refresh existing iframe
iframe.src = contentOptions.url || iframe.src;
} else {
element.html(IFRAMETEMPLATE({
content: contentOptions
}));
}
element.find("." + KCONTENTFRAME).off("load" + NS).on("load" + NS, function() {
that.trigger(CONTENTLOAD);
element.show();
});
}
} else if (contentOptions && isFunction(contentOptions)) {
contentOptions = contentOptions({
target: target
});
that.content.html(contentOptions);
} else {
that.content.html(contentOptions);
}
},
_ajaxRequest: function(options) {
var that = this;
jQuery.ajax(extend({
type: "GET",
dataType: "html",
cache: false,
error: function(xhr, status) {
kendo.ui.progress(that.content, false);
that.trigger(ERROR, {
status: status,
xhr: xhr
});
},
success: proxy(function(data) {
kendo.ui.progress(that.content, false);
that.content.html(data);
that.trigger(CONTENTLOAD);
}, that)
}, options));
},
_documentKeyDown: function(e) {
if (e.keyCode === kendo.keys.ESC) {
this.hide();
}
},
hide: function() {
if (this.popup) {
this.popup.close();
}
},
show: function(target) {
saveTitleAttributes(target);
this._show(target);
},
_show: function(target) {
var that = this, current = that.target();
if (!that.popup) {
that._initPopup();
}
if (current && current[0] != target[0]) {
that.popup.close();
that.popup.element.kendoStop(true, true);
}
if (!current || current[0] != target[0]) {
that._appendContent(target);
that.popup.options.anchor = target;
}
that.popup.one("deactivate", function() {
restoreTitle(target);
target.removeAttr(DESCRIBEDBY);
this.element.removeAttr("id").attr("aria-hidden", true);
DOCUMENT.off("keydown" + NS, that._documentKeyDownHandler);
});
that.popup.open();
},
_initPopup: function() {
var that = this, options = that.options, wrapper = $(kendo.template(TEMPLATE)({
callout: options.callout && options.position !== "center",
dir: DIRCLASSES[options.position],
autoHide: options.autoHide
}));
that.popup = new Popup(wrapper, extend({
activate: function() {
var anchor = this.options.anchor, ariaId = anchor[0].id || that.element[0].id;
if (ariaId) {
anchor.attr(DESCRIBEDBY, ariaId + ARIAIDSUFFIX);
this.element.attr("id", ariaId + ARIAIDSUFFIX);
}
if (options.callout) {
that._positionCallout();
}
this.element.removeAttr("aria-hidden");
DOCUMENT.on("keydown" + NS, that._documentKeyDownHandler);
that.trigger(SHOW);
},
close: function() {
that.trigger(HIDE);
},
copyAnchorStyles: false,
animation: options.animation
}, POSITIONS[options.position]));
wrapper.css({
width: options.width,
height: options.height
});
that.content = wrapper.find(".k-tooltip-content");
that.arrow = wrapper.find(".k-callout");
if (options.autoHide) {
wrapper.on("mouseleave" + NS, proxy(that._mouseleave, that));
} else {
wrapper.on("click" + NS, ".k-tooltip-button", proxy(that._closeButtonClick, that));
}
},
_closeButtonClick: function(e) {
e.preventDefault();
this.hide();
},
_mouseleave: function(e) {
if (this.popup) {
var element = $(e.currentTarget), offset = element.offset(), pageX = e.pageX, pageY = e.pageY;
offset.right = offset.left + element.outerWidth();
offset.bottom = offset.top + element.outerHeight();
if (pageX > offset.left && pageX < offset.right && pageY > offset.top && pageY < offset.bottom) {
return;
}
this.popup.close();
} else {
restoreTitle($(e.currentTarget));
}
clearTimeout(this.timeout);
},
_positionCallout: function() {
var that = this, position = that.options.position, dimensions = that.dimensions, offset = dimensions.offset, popup = that.popup, anchor = popup.options.anchor, anchorOffset = $(anchor).offset(), arrowBorder = parseInt(that.arrow.css("borderWidth"), 10), elementOffset = $(popup.element).offset(), cssClass = DIRCLASSES[popup.flipped ? REVERSE[position] : position], offsetAmount = anchorOffset[offset] - elementOffset[offset] + $(anchor)[dimensions.size]() / 2 - arrowBorder;
that.arrow.removeClass("k-callout-n k-callout-s k-callout-w k-callout-e").addClass("k-callout-" + cssClass).css(offset, offsetAmount);
},
target: function() {
if (this.popup) {
return this.popup.options.anchor;
}
return null;
},
destroy: function() {
var popup = this.popup;
if (popup) {
popup.element.off(NS);
popup.destroy();
}
this.element.off(NS);
DOCUMENT.off("keydown" + NS, this._documentKeyDownHandler);
Widget.fn.destroy.call(this);
}
});
kendo.ui.plugin(Tooltip);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, keys = kendo.keys, support = kendo.support, htmlEncode = kendo.htmlEncode, activeElement = kendo._activeElement, ID = "id", LI = "li", CHANGE = "change", CHARACTER = "character", FOCUSED = "k-state-focused", HOVER = "k-state-hover", LOADING = "k-loading", OPEN = "open", CLOSE = "close", SELECT = "select", PROGRESS = "progress", REQUESTEND = "requestEnd", WIDTH = "width", extend = $.extend, proxy = $.proxy, browser = support.browser, isIE8 = browser.msie && browser.version < 9, quotRegExp = /"/g, alternativeNames = {
ComboBox: "DropDownList",
DropDownList: "ComboBox"
};
var List = Widget.extend({
init: function(element, options) {
var that = this, ns = that.ns, id;
Widget.fn.init.call(that, element, options);
element = that.element;
that._isSelect = element.is(SELECT);
that._template();
that.ul = $('<ul unselectable="on" class="k-list k-reset"/>').css({
overflow: support.kineticScrollNeeded ? "" : "auto"
}).on("mouseenter" + ns, LI, function() {
$(this).addClass(HOVER);
}).on("mouseleave" + ns, LI, function() {
$(this).removeClass(HOVER);
}).on("click" + ns, LI, proxy(that._click, that)).attr({
tabIndex: -1,
role: "listbox",
"aria-hidden": true
});
that.list = $("<div class='k-list-container'/>").append(that.ul).on("mousedown" + ns, function(e) {
e.preventDefault();
});
id = element.attr(ID);
if (id) {
that.list.attr(ID, id + "-list");
that.ul.attr(ID, id + "_listbox");
that._optionID = id + "_option_selected";
}
that._initValue();
},
setOptions: function(options) {
Widget.fn.setOptions.call(this, options);
if (options && options.enable !== undefined) {
options.enabled = options.enable;
}
},
focus: function() {
this._focused.focus();
},
readonly: function(readonly) {
this._editable({
readonly: readonly === undefined ? true : readonly,
disable: false
});
},
enable: function(enable) {
this._editable({
readonly: false,
disable: !(enable = enable === undefined ? true : enable)
});
},
_filterSource: function(filter) {
var that = this, options = that.options, dataSource = that.dataSource, expression = dataSource.filter() || {};
removeFiltersForField(expression, options.dataTextField);
if (filter) {
expression = expression.filters || [];
expression.push(filter);
}
dataSource.filter(expression);
},
_initValue: function() {
var that = this, value = that.options.value;
if (value) {
that.element.val(value);
} else {
value = that.element.val();
}
that._old = value;
},
_ignoreCase: function() {
var that = this, model = that.dataSource.reader.model, field;
if (model && model.fields) {
field = model.fields[that.options.dataTextField];
if (field && field.type && field.type !== "string") {
that.options.ignoreCase = false;
}
}
},
items: function() {
return this.ul[0].children;
},
current: function(candidate) {
var that = this, id = that._optionID;
if (candidate !== undefined) {
if (that._current) {
that._current.removeClass(FOCUSED).removeAttr("aria-selected").removeAttr(ID);
that._focused.removeAttr("aria-activedescendant");
}
if (candidate) {
candidate.addClass(FOCUSED);
that._scroll(candidate);
if (id) {
candidate.attr("id", id);
that._focused.attr("aria-activedescendant", id);
}
}
that._current = candidate;
} else {
return that._current;
}
},
destroy: function() {
var that = this, ns = that.ns;
Widget.fn.destroy.call(that);
that._unbindDataSource();
that.ul.off(ns);
that.list.off(ns);
that.popup.destroy();
if (that._form) {
that._form.off("reset", that._resetHandler);
}
},
dataItem: function(index) {
var that = this;
if (index === undefined) {
index = that.selectedIndex;
}
return that._data()[index];
},
_accessors: function() {
var that = this, element = that.element, options = that.options, getter = kendo.getter, textField = element.attr(kendo.attr("text-field")), valueField = element.attr(kendo.attr("value-field"));
if (textField) {
options.dataTextField = textField;
}
if (valueField) {
options.dataValueField = valueField;
}
that._text = getter(options.dataTextField);
that._value = getter(options.dataValueField);
},
_aria: function(id) {
var that = this, options = that.options, element = that._focused;
if (options.suggest !== undefined) {
element.attr("aria-autocomplete", options.suggest ? "both" : "list");
}
id = id ? id + " " + that.ul[0].id : that.ul[0].id;
element.attr("aria-owns", id);
that.ul.attr("aria-live", !options.filter || options.filter === "none" ? "off" : "polite");
},
_blur: function() {
var that = this;
that._change();
that.close();
},
_change: function() {
var that = this, index = that.selectedIndex, optionValue = that.options.value, value = that.value(), trigger;
if (that._isSelect && !that._bound && optionValue) {
value = optionValue;
}
if (value !== that._old) {
trigger = true;
} else if (index !== undefined && index !== that._oldIndex) {
trigger = true;
}
if (trigger) {
that._old = value;
that._oldIndex = index;
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_click: function(e) {
if (!e.isDefaultPrevented()) {
this._accept($(e.currentTarget));
}
},
_data: function() {
return this.dataSource.view();
},
_enable: function() {
var that = this, options = that.options, disabled = that.element.is("[disabled]");
if (options.enable !== undefined) {
options.enabled = options.enable;
}
if (!options.enabled || disabled) {
that.enable(false);
} else {
that.readonly(that.element.is("[readonly]"));
}
},
_focus: function(li) {
var that = this;
if (that.popup.visible() && li && that.trigger(SELECT, {
item: li
})) {
that.close();
return;
}
that._select(li);
that._triggerCascade();
that._blur();
},
_index: function(value) {
var that = this, idx, length, data = that._data();
for (idx = 0, length = data.length; idx < length; idx++) {
if (that._dataValue(data[idx]) == value) {
return idx;
}
}
return -1;
},
_dataValue: function(dataItem) {
var value = this._value(dataItem);
if (value === undefined) {
value = this._text(dataItem);
}
return value;
},
_height: function(length) {
if (length) {
var that = this, list = that.list, visible = that.popup.visible(), height = that.options.height;
list = list.add(list.parent(".k-animation-container")).show().height(that.ul[0].scrollHeight > height ? height : "auto");
if (!visible) {
list.hide();
}
}
},
_adjustListWidth: function() {
var list = this.list, width = list[0].style.width, wrapper = this.wrapper, computedStyle, computedWidth;
if (!list.data(WIDTH) && width) {
return;
}
computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();
if (computedStyle && (browser.mozilla || browser.msie)) {
// getComputedStyle returns different box in FF and IE.
computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
}
width = computedWidth - (list.outerWidth() - list.width());
list.css({
fontFamily: wrapper.css("font-family"),
width: width
}).data(WIDTH, width);
return true;
},
_popup: function() {
var that = this, list = that.list, focused = that._focused, options = that.options, wrapper = that.wrapper;
that.popup = new ui.Popup(list, extend({}, options.popup, {
anchor: wrapper,
open: function(e) {
that._adjustListWidth();
if (that.trigger(OPEN)) {
e.preventDefault();
} else {
focused.attr("aria-expanded", true);
that.ul.attr("aria-hidden", false);
}
},
close: function(e) {
if (that.trigger(CLOSE)) {
e.preventDefault();
} else {
focused.attr("aria-expanded", false);
that.ul.attr("aria-hidden", true);
}
},
animation: options.animation,
isRtl: support.isRtl(wrapper)
}));
that.popup.one(OPEN, function() {
that._height(that._data().length);
});
that._touchScroller = kendo.touchScroller(that.popup.element);
},
_makeUnselectable: function() {
if (isIE8) {
this.list.find("*").attr("unselectable", "on");
}
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_toggle: function(open) {
var that = this;
open = open !== undefined ? open : !that.popup.visible();
if (!support.touch && that._focused[0] !== activeElement()) {
that._focused.focus();
}
that[open ? OPEN : CLOSE]();
},
_scroll: function(item) {
if (!item) {
return;
}
if (item[0]) {
item = item[0];
}
var ul = this.ul[0], itemOffsetTop = item.offsetTop, itemOffsetHeight = item.offsetHeight, ulScrollTop = ul.scrollTop, ulOffsetHeight = ul.clientHeight, bottomDistance = itemOffsetTop + itemOffsetHeight;
ul.scrollTop = ulScrollTop > itemOffsetTop ? itemOffsetTop : bottomDistance > ulScrollTop + ulOffsetHeight ? bottomDistance - ulOffsetHeight : ulScrollTop;
},
_template: function() {
var that = this, options = that.options, template = options.template, hasDataSource = options.dataSource;
if (that._isSelect && that.element[0].length) {
if (!hasDataSource) {
options.dataTextField = options.dataTextField || "text";
options.dataValueField = options.dataValueField || "value";
}
}
if (!template) {
that.template = kendo.template('<li tabindex="-1" role="option" unselectable="on" class="k-item">${' + kendo.expr(options.dataTextField, "data") + "}</li>", {
useWithBlock: false
});
} else {
template = kendo.template(template);
that.template = function(data) {
return '<li tabindex="-1" role="option" unselectable="on" class="k-item">' + template(data) + "</li>";
};
}
},
_triggerCascade: function() {
var that = this, value = that.value();
if (!that._bound && value || that._old !== value) {
that.trigger("cascade");
}
},
_unbindDataSource: function() {
var that = this;
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(PROGRESS, that._progressHandler).unbind(REQUESTEND, that._requestEndHandler);
}
});
extend(List, {
caret: function(element) {
var caret, selection = element.ownerDocument.selection;
if (selection) {
caret = Math.abs(selection.createRange().moveStart(CHARACTER, -element.value.length));
} else {
caret = element.selectionStart;
}
return caret;
},
selectText: function(element, selectionStart, selectionEnd) {
try {
if (element.createTextRange) {
element.focus();
var textRange = element.createTextRange();
textRange.collapse(true);
textRange.moveStart(CHARACTER, selectionStart);
textRange.moveEnd(CHARACTER, selectionEnd - selectionStart);
textRange.select();
} else {
element.setSelectionRange(selectionStart, selectionEnd);
}
} catch (e) {}
},
inArray: function(node, parentNode) {
var idx, length, siblings = parentNode.children;
if (!node || node.parentNode !== parentNode) {
return -1;
}
for (idx = 0, length = siblings.length; idx < length; idx++) {
if (node === siblings[idx]) {
return idx;
}
}
return -1;
}
});
kendo.ui.List = List;
ui.Select = List.extend({
init: function(element, options) {
List.fn.init.call(this, element, options);
this._initial = this.element.val();
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.options.autoBind) {
this.dataSource.fetch();
}
},
close: function() {
this.popup.close();
},
select: function(li) {
var that = this;
if (li === undefined) {
return that.selectedIndex;
} else {
that._select(li);
that._triggerCascade();
that._old = that._accessor();
that._oldIndex = that.selectedIndex;
}
},
_accessor: function(value, idx) {
var element = this.element, isSelect = this._isSelect, option, selectedIndex;
element = element[0];
if (value === undefined) {
if (isSelect) {
selectedIndex = element.selectedIndex;
if (selectedIndex > -1) {
option = element.options[selectedIndex];
if (option) {
value = option.value;
}
}
} else {
value = element.value;
}
return value;
} else {
if (isSelect) {
element.selectedIndex = idx;
} else {
element.value = value;
}
}
},
_hideBusy: function() {
var that = this;
clearTimeout(that._busy);
that._arrow.removeClass(LOADING);
that._focused.attr("aria-busy", false);
that._busy = null;
},
_showBusy: function() {
var that = this;
that._request = true;
if (that._busy) {
return;
}
that._busy = setTimeout(function() {
that._focused.attr("aria-busy", true);
that._arrow.addClass(LOADING);
}, 100);
},
_requestEnd: function() {
this._request = false;
},
_dataSource: function() {
var that = this, element = that.element, options = that.options, dataSource = options.dataSource || {}, idx;
dataSource = $.isArray(dataSource) ? {
data: dataSource
} : dataSource;
if (that._isSelect) {
idx = element[0].selectedIndex;
if (idx > -1) {
options.index = idx;
}
dataSource.select = element;
dataSource.fields = [ {
field: options.dataTextField
}, {
field: options.dataValueField
} ];
}
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._showBusy, that);
that._requestEndHandler = proxy(that._requestEnd, that);
}
that.dataSource = kendo.data.DataSource.create(dataSource).bind(CHANGE, that._refreshHandler).bind(PROGRESS, that._progressHandler).bind(REQUESTEND, that._requestEndHandler);
},
_get: function(li) {
var that = this, data = that._data(), idx, length;
if (typeof li === "function") {
for (idx = 0, length = data.length; idx < length; idx++) {
if (li(data[idx])) {
li = idx;
break;
}
}
}
if (typeof li === "number") {
if (li < 0) {
return $();
}
li = $(that.ul[0].children[li]);
}
if (li && li.nodeType) {
li = $(li);
}
return li;
},
_move: function(e) {
var that = this, key = e.keyCode, ul = that.ul[0], methodName = that.popup.visible() ? "_select" : "_accept", current = that._current, down = key === keys.DOWN, pressed;
if (key === keys.UP || down) {
if (e.altKey) {
that.toggle(down);
} else if (down) {
if (!current || that.selectedIndex === -1 && !that.value() && current[0] === ul.firstChild) {
current = ul.firstChild;
} else {
current = current[0].nextSibling;
}
that[methodName](current);
} else {
that[methodName](current ? current[0].previousSibling : ul.lastChild);
}
e.preventDefault();
pressed = true;
} else if (key === keys.ENTER || key === keys.TAB) {
if (that.popup.visible()) {
e.preventDefault();
}
that._accept(current);
pressed = true;
} else if (key === keys.ESC) {
if (that.popup.visible()) {
e.preventDefault();
}
that.close();
pressed = true;
}
return pressed;
},
_selectItem: function(value) {
var that = this, options = that.options;
value = that._selectedValue || options.value || that._accessor();
if (value) {
that.value(value);
} else if (!that._bound) {
that.select(options.index);
}
},
_fetchItems: function(value) {
var that = this, hasItems = that.ul[0].firstChild;
//if request is started avoid datasource.fetch
if (that._request) {
return true;
}
if (!that._fetch && !hasItems) {
if (that.options.cascadeFrom) {
return !hasItems;
}
that.dataSource.one(CHANGE, function() {
that.value(value);
that._fetch = false;
});
that._fetch = true;
that.dataSource.fetch();
return true;
}
},
_options: function(data, optionLabel) {
var that = this, element = that.element, selectedIndex = element[0].selectedIndex, length = data.length, options = "", option, dataItem, dataText, dataValue, idx = 0;
if (optionLabel) {
options = optionLabel;
selectedIndex += 1;
idx = 1;
}
for (;idx < length; idx++) {
option = "<option";
dataItem = data[idx];
dataText = that._text(dataItem);
dataValue = that._value(dataItem);
if (dataValue !== undefined) {
dataValue += "";
if (dataValue.indexOf('"') !== -1) {
dataValue = dataValue.replace(quotRegExp, """);
}
option += ' value="' + dataValue + '"';
}
option += ">";
if (dataText !== undefined) {
option += htmlEncode(dataText);
}
option += "</option>";
options += option;
}
element.html(options);
element[0].selectedIndex = selectedIndex;
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
setTimeout(function() {
that.value(that._initial);
});
};
that._form = form.on("reset", that._resetHandler);
}
},
_cascade: function() {
var that = this, options = that.options, cascade = options.cascadeFrom, parent, parentElement, select, valueField, change;
if (cascade) {
that._selectedValue = options.value || that._accessor();
parentElement = $("#" + cascade);
parent = parentElement.data("kendo" + options.name);
if (!parent) {
parent = parentElement.data("kendo" + alternativeNames[options.name]);
}
if (!parent) {
return;
}
valueField = parent.options.dataValueField;
change = function() {
var value = that._selectedValue || that.value();
if (value) {
that.value(value);
if (!that.dataSource.view()[0] || that.selectedIndex === -1) {
that._clearSelection(parent, true);
}
} else {
that.select(options.index);
}
that.enable();
};
select = function() {
var dataItem = parent.dataItem(), filterValue = dataItem ? parent._value(dataItem) : null, expressions, filters;
if (filterValue) {
expressions = that.dataSource.filter() || {};
removeFiltersForField(expressions, valueField);
filters = expressions.filters || [];
filters.push({
field: valueField,
operator: "eq",
value: filterValue
});
that.dataSource.one(CHANGE, change).filter(filters);
} else {
that.enable(false);
that._clearSelection(parent);
}
that._triggerCascade();
};
parent.bind("cascade", function() {
select();
});
//refresh was called
if (parent._bound) {
select();
} else if (!parent.value()) {
that.enable(false);
}
}
}
});
function removeFiltersForField(expression, field) {
if (expression.filters) {
expression.filters = $.grep(expression.filters, function(filter) {
removeFiltersForField(filter, field);
if (filter.filters) {
return filter.filters.length;
} else {
return filter.field != field;
}
});
}
}
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, support = kendo.support, ui = kendo.ui, Widget = ui.Widget, parse = kendo.parseDate, adjustDate = kendo._adjustDate, keys = kendo.keys, extractFormat = kendo._extractFormat, template = kendo.template, getCulture = kendo.getCulture, transitions = kendo.support.transitions, transitionOrigin = transitions ? transitions.css + "transform-origin" : "", cellTemplate = template('<td#=data.cssClass# role="gridcell"><a tabindex="-1" class="k-link" href="\\#" data-#=data.ns#value="#=data.dateString#">#=data.value#</a></td>', {
useWithBlock: false
}), emptyCellTemplate = template('<td role="gridcell"> </td>', {
useWithBlock: false
}), browser = kendo.support.browser, isIE8 = browser.msie && browser.version < 9, ns = ".kendoCalendar", CLICK = "click" + ns, KEYDOWN_NS = "keydown" + ns, ID = "id", MIN = "min", LEFT = "left", SLIDE = "slideIn", MONTH = "month", CENTURY = "century", CHANGE = "change", NAVIGATE = "navigate", VALUE = "value", HOVER = "k-state-hover", DISABLED = "k-state-disabled", FOCUSED = "k-state-focused", OTHERMONTH = "k-other-month", OTHERMONTHCLASS = ' class="' + OTHERMONTH + '"', TODAY = "k-nav-today", CELLSELECTOR = "td:has(.k-link)", BLUR = "blur" + ns, FOCUS = "focus", FOCUS_WITH_NS = FOCUS + ns, MOUSEENTER = support.touch ? "touchstart" : "mouseenter", MOUSEENTER_WITH_NS = support.touch ? "touchstart" + ns : "mouseenter" + ns, MOUSELEAVE = support.touch ? "touchend" + ns + " touchmove" + ns : "mouseleave" + ns, MS_PER_MINUTE = 6e4, MS_PER_DAY = 864e5, PREVARROW = "_prevArrow", NEXTARROW = "_nextArrow", ARIA_DISABLED = "aria-disabled", ARIA_SELECTED = "aria-selected", proxy = $.proxy, extend = $.extend, DATE = Date, views = {
month: 0,
year: 1,
decade: 2,
century: 3
};
var Calendar = Widget.extend({
init: function(element, options) {
var that = this, value, id;
Widget.fn.init.call(that, element, options);
element = that.wrapper = that.element;
options = that.options;
options.url = window.unescape(options.url);
that._templates();
that._header();
that._footer(that.footer);
id = element.addClass("k-widget k-calendar").on(MOUSEENTER_WITH_NS + " " + MOUSELEAVE, CELLSELECTOR, mousetoggle).on(KEYDOWN_NS, "table.k-content", proxy(that._move, that)).on(CLICK, CELLSELECTOR, function(e) {
var link = e.currentTarget.firstChild;
if (link.href.indexOf("#") != -1) {
e.preventDefault();
}
that._click($(link));
}).on("mouseup" + ns, function() {
that._focusView(that.options.focusOnNav !== false);
}).attr(ID);
if (id) {
that._cellID = id + "_cell_selected";
}
value = options.value;
normalize(options);
that._index = views[options.start];
that._current = new DATE(+restrictValue(value, options.min, options.max));
that._addClassProxy = function() {
that._active = true;
that._cell.addClass(FOCUSED);
};
that._removeClassProxy = function() {
that._active = false;
that._cell.removeClass(FOCUSED);
};
that.value(value);
kendo.notify(that);
},
options: {
name: "Calendar",
value: null,
min: new DATE(1900, 0, 1),
max: new DATE(2099, 11, 31),
dates: [],
url: "",
culture: "",
footer: "",
format: "",
month: {},
start: MONTH,
depth: MONTH,
animation: {
horizontal: {
effects: SLIDE,
reverse: true,
duration: 500,
divisor: 2
},
vertical: {
effects: "zoomIn",
duration: 400
}
}
},
events: [ CHANGE, NAVIGATE ],
setOptions: function(options) {
normalize(options);
Widget.fn.setOptions.call(this, options);
},
destroy: function() {
var that = this, today = that._today;
that.element.off(ns);
that._title.off(ns);
that[PREVARROW].off(ns);
that[NEXTARROW].off(ns);
kendo.destroy(that._view);
if (today) {
kendo.destroy(today.off(ns));
}
Widget.fn.destroy.call(that);
},
current: function() {
return this._current;
},
view: function() {
return this._view;
},
focus: function(table) {
table = table || this._table;
this._bindTable(table);
table.focus();
},
min: function(value) {
return this._option(MIN, value);
},
max: function(value) {
return this._option("max", value);
},
navigateToPast: function() {
this._navigate(PREVARROW, -1);
},
navigateToFuture: function() {
this._navigate(NEXTARROW, 1);
},
navigateUp: function() {
var that = this, index = that._index;
if (that._title.hasClass(DISABLED)) {
return;
}
that.navigate(that._current, ++index);
},
navigateDown: function(value) {
var that = this, index = that._index, depth = that.options.depth;
if (!value) {
return;
}
if (index === views[depth]) {
if (+that._value != +value) {
that.value(value);
that.trigger(CHANGE);
}
return;
}
that.navigate(value, --index);
},
navigate: function(value, view) {
view = isNaN(view) ? views[view] : view;
var that = this, options = that.options, culture = options.culture, min = options.min, max = options.max, title = that._title, from = that._table, old = that._oldTable, selectedValue = that._value, currentValue = that._current, future = value && +value > +currentValue, vertical = view !== undefined && view !== that._index, to, currentView, compare, disabled;
if (!value) {
value = currentValue;
} else {
that._current = value = new DATE(+restrictValue(value, min, max));
}
if (view === undefined) {
view = that._index;
} else {
that._index = view;
}
that._view = currentView = calendar.views[view];
compare = currentView.compare;
disabled = view === views[CENTURY];
title.toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);
disabled = compare(value, min) < 1;
that[PREVARROW].toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);
disabled = compare(value, max) > -1;
that[NEXTARROW].toggleClass(DISABLED, disabled).attr(ARIA_DISABLED, disabled);
if (from && old && old.data("animating")) {
old.kendoStop(true, true);
from.kendoStop(true, true);
}
that._oldTable = from;
if (!from || that._changeView) {
title.html(currentView.title(value, min, max, culture));
that._table = to = $(currentView.content(extend({
min: min,
max: max,
date: value,
url: options.url,
dates: options.dates,
format: options.format,
culture: culture
}, that[currentView.name])));
makeUnselectable(to);
that._animate({
from: from,
to: to,
vertical: vertical,
future: future
});
that._focus(value);
that.trigger(NAVIGATE);
}
if (view === views[options.depth] && selectedValue) {
that._class("k-state-selected", currentView.toDateString(selectedValue));
}
that._class(FOCUSED, currentView.toDateString(value));
if (!from && that._cell) {
that._cell.removeClass(FOCUSED);
}
that._changeView = true;
},
value: function(value) {
var that = this, view = that._view, options = that.options, old = that._view, min = options.min, max = options.max;
if (value === undefined) {
return that._value;
}
value = parse(value, options.format, options.culture);
if (value !== null) {
value = new DATE(+value);
if (!isInRange(value, min, max)) {
value = null;
}
}
that._value = value;
if (old && value === null && that._cell) {
that._cell.removeClass("k-state-selected");
} else {
that._changeView = !value || view && view.compare(value, that._current) !== 0;
that.navigate(value);
}
},
_move: function(e) {
var that = this, options = that.options, key = e.keyCode, view = that._view, index = that._index, currentValue = new DATE(+that._current), isRtl = kendo.support.isRtl(that.wrapper), value, prevent, method, temp;
if (e.target === that._table[0]) {
that._active = true;
}
if (e.ctrlKey) {
if (key == keys.RIGHT && !isRtl || key == keys.LEFT && isRtl) {
that.navigateToFuture();
prevent = true;
} else if (key == keys.LEFT && !isRtl || key == keys.RIGHT && isRtl) {
that.navigateToPast();
prevent = true;
} else if (key == keys.UP) {
that.navigateUp();
prevent = true;
} else if (key == keys.DOWN) {
that._click($(that._cell[0].firstChild));
prevent = true;
}
} else {
if (key == keys.RIGHT && !isRtl || key == keys.LEFT && isRtl) {
value = 1;
prevent = true;
} else if (key == keys.LEFT && !isRtl || key == keys.RIGHT && isRtl) {
value = -1;
prevent = true;
} else if (key == keys.UP) {
value = index === 0 ? -7 : -4;
prevent = true;
} else if (key == keys.DOWN) {
value = index === 0 ? 7 : 4;
prevent = true;
} else if (key == keys.ENTER) {
that._click($(that._cell[0].firstChild));
prevent = true;
} else if (key == keys.HOME || key == keys.END) {
method = key == keys.HOME ? "first" : "last";
temp = view[method](currentValue);
currentValue = new DATE(temp.getFullYear(), temp.getMonth(), temp.getDate(), currentValue.getHours(), currentValue.getMinutes(), currentValue.getSeconds(), currentValue.getMilliseconds());
prevent = true;
} else if (key == keys.PAGEUP) {
prevent = true;
that.navigateToPast();
} else if (key == keys.PAGEDOWN) {
prevent = true;
that.navigateToFuture();
}
if (value || method) {
if (!method) {
view.setDate(currentValue, value);
}
that._focus(restrictValue(currentValue, options.min, options.max));
}
}
if (prevent) {
e.preventDefault();
}
return that._current;
},
_animate: function(options) {
var that = this, from = options.from, to = options.to, active = that._active;
if (!from) {
to.insertAfter(that.element[0].firstChild);
that._bindTable(to);
} else if (from.parent().data("animating")) {
from.parent().kendoStop(true, true).remove();
from.remove();
to.insertAfter(that.element[0].firstChild);
that._focusView(active);
} else if (!from.is(":visible") || that.options.animation === false) {
to.insertAfter(from);
from.remove();
that._focusView(active);
} else {
that[options.vertical ? "_vertical" : "_horizontal"](from, to, options.future);
}
},
_horizontal: function(from, to, future) {
var that = this, active = that._active, horizontal = that.options.animation.horizontal, effects = horizontal.effects, viewWidth = from.outerWidth();
if (effects && effects.indexOf(SLIDE) != -1) {
from.add(to).css({
width: viewWidth
});
from.wrap("<div/>");
that._focusView(active, from);
from.parent().css({
position: "relative",
width: viewWidth * 2,
"float": LEFT,
"margin-left": future ? 0 : -viewWidth
});
to[future ? "insertAfter" : "insertBefore"](from);
extend(horizontal, {
effects: SLIDE + ":" + (future ? "right" : LEFT),
complete: function() {
from.remove();
to.unwrap();
that._focusView(active);
that._oldTable = undefined;
}
});
from.parent().kendoStop(true, true).kendoAnimate(horizontal);
}
},
_vertical: function(from, to) {
var that = this, vertical = that.options.animation.vertical, effects = vertical.effects, active = that._active, //active state before from's blur
cell, position;
if (effects && effects.indexOf("zoom") != -1) {
to.css({
position: "absolute",
top: from.prev().outerHeight(),
left: 0
}).insertBefore(from);
if (transitionOrigin) {
cell = that._cellByDate(that._view.toDateString(that._current));
position = cell.position();
position = position.left + parseInt(cell.width() / 2, 10) + "px" + " " + (position.top + parseInt(cell.height() / 2, 10) + "px");
to.css(transitionOrigin, position);
}
from.kendoStop(true, true).kendoAnimate({
effects: "fadeOut",
duration: 600,
complete: function() {
from.remove();
to.css({
position: "static",
top: 0,
left: 0
});
that._focusView(active);
that._oldTable = undefined;
}
});
to.kendoStop(true, true).kendoAnimate(vertical);
}
},
_cellByDate: function(value) {
return this._table.find("td:not(." + OTHERMONTH + ")").filter(function() {
return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
});
},
_class: function(className, value) {
var that = this, id = that._cellID, cell = that._cell;
if (cell) {
cell.removeAttr(ARIA_SELECTED).removeAttr(ID);
}
cell = that._table.find("td:not(." + OTHERMONTH + ")").removeClass(className).filter(function() {
return $(this.firstChild).attr(kendo.attr(VALUE)) === value;
}).attr(ARIA_SELECTED, true);
if (className === FOCUSED && !that._active && that.options.focusOnNav !== false) {
className = "";
}
cell.addClass(className);
if (cell[0]) {
that._cell = cell;
}
if (id) {
cell.attr(ID, id);
that._table.removeAttr("aria-activedescendant").attr("aria-activedescendant", id);
}
},
_bindTable: function(table) {
table.on(FOCUS_WITH_NS, this._addClassProxy).on(BLUR, this._removeClassProxy);
},
_click: function(link) {
var that = this, options = that.options, currentValue = new Date(+that._current), value = link.attr(kendo.attr(VALUE)).split("/");
//Safari cannot create correctly date from "1/1/2090"
value = new DATE(value[0], value[1], value[2]);
adjustDate(value);
that._view.setDate(currentValue, value);
that.navigateDown(restrictValue(currentValue, options.min, options.max));
},
_focus: function(value) {
var that = this, view = that._view;
if (view.compare(value, that._current) !== 0) {
that.navigate(value);
} else {
that._current = value;
that._class(FOCUSED, view.toDateString(value));
}
},
_focusView: function(active, table) {
if (active) {
this.focus(table);
}
},
_footer: function(template) {
var that = this, element = that.element, today = new DATE(), footer = element.find(".k-footer");
if (!template) {
that._toggle(false);
footer.hide();
return;
}
if (!footer[0]) {
footer = $('<div class="k-footer"><a href="#" class="k-link k-nav-today"></a></div>').appendTo(element);
}
that._today = footer.show().find(".k-link").html(template(today)).attr("title", kendo.toString(today, "D", that.options.culture));
that._toggle();
},
_header: function() {
var that = this, element = that.element, active = that.options.focusOnNav !== false, links;
if (!element.find(".k-header")[0]) {
element.html('<div class="k-header">' + '<a href="#" role="button" class="k-link k-nav-prev"><span class="k-icon k-i-arrow-w"></span></a>' + '<a href="#" role="button" aria-live="assertive" aria-atomic="true" class="k-link k-nav-fast"></a>' + '<a href="#" role="button" class="k-link k-nav-next"><span class="k-icon k-i-arrow-e"></span></a>' + "</div>");
}
links = element.find(".k-link").on(MOUSEENTER_WITH_NS + " " + MOUSELEAVE + " " + FOCUS_WITH_NS + " " + BLUR, mousetoggle).click(false);
that._title = links.eq(1).on(CLICK, function() {
that._focusView(active);
that.navigateUp();
});
that[PREVARROW] = links.eq(0).on(CLICK, function() {
that._focusView(active);
that.navigateToPast();
});
that[NEXTARROW] = links.eq(2).on(CLICK, function() {
that._focusView(active);
that.navigateToFuture();
});
},
_navigate: function(arrow, modifier) {
var that = this, index = that._index + 1, currentValue = new DATE(+that._current);
arrow = that[arrow];
if (!arrow.hasClass(DISABLED)) {
if (index > 3) {
currentValue.setFullYear(currentValue.getFullYear() + 100 * modifier);
} else {
calendar.views[index].setDate(currentValue, modifier);
}
that.navigate(currentValue);
}
},
_option: function(option, value) {
var that = this, options = that.options, selectedValue = +that._value, bigger, navigate, arrow = NEXTARROW;
if (value === undefined) {
return options[option];
}
value = parse(value, options.format, options.culture);
if (!value) {
return;
}
options[option] = new DATE(+value);
navigate = that._view.compare(value, that._current);
if (option === MIN) {
bigger = +value > selectedValue;
navigate = navigate > -1;
arrow = PREVARROW;
} else {
bigger = selectedValue > +value;
navigate = navigate < 1;
}
if (bigger) {
that.value(null);
} else if (navigate) {
that.navigate();
} else {
that[arrow].toggleClass(DISABLED, false).attr(ARIA_DISABLED, false);
}
that._toggle();
},
_toggle: function(toggle) {
var that = this, options = that.options, link = that._today;
if (toggle === undefined) {
toggle = isInRange(new DATE(), options.min, options.max);
}
if (link) {
link.off(CLICK);
if (toggle) {
link.addClass(TODAY).removeClass(DISABLED).on(CLICK, proxy(that._todayClick, that));
} else {
link.removeClass(TODAY).addClass(DISABLED).on(CLICK, prevent);
}
}
},
_todayClick: function(e) {
var that = this, depth = views[that.options.depth], today = new DATE();
e.preventDefault();
if (that._view.compare(that._current, today) === 0 && that._index == depth) {
that._changeView = false;
}
that._value = today;
that.navigate(today, depth);
that.trigger(CHANGE);
},
_templates: function() {
var that = this, options = that.options, footer = options.footer, month = options.month, content = month.content, empty = month.empty;
that.month = {
content: template('<td#=data.cssClass# role="gridcell"><a tabindex="-1" class="k-link#=data.linkClass#" href="#=data.url#" ' + kendo.attr("value") + '="#=data.dateString#" title="#=data.title#">' + (content || "#=data.value#") + "</a></td>", {
useWithBlock: !!content
}),
empty: template('<td role="gridcell">' + (empty || " ") + "</td>", {
useWithBlock: !!empty
})
};
if (footer !== false) {
that.footer = template(footer || '#= kendo.toString(data,"D","' + options.culture + '") #', {
useWithBlock: false
});
}
}
});
ui.plugin(Calendar);
var calendar = {
firstDayOfMonth: function(date) {
return new DATE(date.getFullYear(), date.getMonth(), 1);
},
firstVisibleDay: function(date, calendarInfo) {
calendarInfo = calendarInfo || kendo.culture().calendar;
var firstDay = calendarInfo.firstDay, firstVisibleDay = new DATE(date.getFullYear(), date.getMonth(), 0, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
while (firstVisibleDay.getDay() != firstDay) {
calendar.setTime(firstVisibleDay, -1 * MS_PER_DAY);
}
return firstVisibleDay;
},
setTime: function(date, time) {
var tzOffsetBefore = date.getTimezoneOffset(), resultDATE = new DATE(date.getTime() + time), tzOffsetDiff = resultDATE.getTimezoneOffset() - tzOffsetBefore;
date.setTime(resultDATE.getTime() + tzOffsetDiff * MS_PER_MINUTE);
},
views: [ {
name: MONTH,
title: function(date, min, max, culture) {
return getCalendarInfo(culture).months.names[date.getMonth()] + " " + date.getFullYear();
},
content: function(options) {
var that = this, idx = 0, min = options.min, max = options.max, date = options.date, dates = options.dates, format = options.format, culture = options.culture, navigateUrl = options.url, hasUrl = navigateUrl && dates[0], currentCalendar = getCalendarInfo(culture), firstDayIdx = currentCalendar.firstDay, days = currentCalendar.days, names = shiftArray(days.names, firstDayIdx), shortNames = shiftArray(days.namesShort, firstDayIdx), start = calendar.firstVisibleDay(date, currentCalendar), firstDayOfMonth = that.first(date), lastDayOfMonth = that.last(date), toDateString = that.toDateString, today = new DATE(), html = '<table tabindex="0" role="grid" class="k-content" cellspacing="0"><thead><tr role="row">';
for (;idx < 7; idx++) {
html += '<th scope="col" title="' + names[idx] + '">' + shortNames[idx] + "</th>";
}
today = new DATE(today.getFullYear(), today.getMonth(), today.getDate());
adjustDate(today);
today = +today;
return view({
cells: 42,
perRow: 7,
html: html += '</tr></thead><tbody><tr role="row">',
start: new DATE(start.getFullYear(), start.getMonth(), start.getDate()),
min: new DATE(min.getFullYear(), min.getMonth(), min.getDate()),
max: new DATE(max.getFullYear(), max.getMonth(), max.getDate()),
content: options.content,
empty: options.empty,
setter: that.setDate,
build: function(date) {
var cssClass = [], day = date.getDay(), linkClass = "", url = "#";
if (date < firstDayOfMonth || date > lastDayOfMonth) {
cssClass.push(OTHERMONTH);
}
if (+date === today) {
cssClass.push("k-today");
}
if (day === 0 || day === 6) {
cssClass.push("k-weekend");
}
if (hasUrl && inArray(+date, dates)) {
url = navigateUrl.replace("{0}", kendo.toString(date, format, culture));
linkClass = " k-action-link";
}
return {
date: date,
dates: dates,
ns: kendo.ns,
title: kendo.toString(date, "D", culture),
value: date.getDate(),
dateString: toDateString(date),
cssClass: cssClass[0] ? ' class="' + cssClass.join(" ") + '"' : "",
linkClass: linkClass,
url: url
};
}
});
},
first: function(date) {
return calendar.firstDayOfMonth(date);
},
last: function(date) {
var last = new DATE(date.getFullYear(), date.getMonth() + 1, 0), first = calendar.firstDayOfMonth(date), timeOffset = Math.abs(last.getTimezoneOffset() - first.getTimezoneOffset());
if (timeOffset) {
last.setHours(first.getHours() + timeOffset / 60);
}
return last;
},
compare: function(date1, date2) {
var result, month1 = date1.getMonth(), year1 = date1.getFullYear(), month2 = date2.getMonth(), year2 = date2.getFullYear();
if (year1 > year2) {
result = 1;
} else if (year1 < year2) {
result = -1;
} else {
result = month1 == month2 ? 0 : month1 > month2 ? 1 : -1;
}
return result;
},
setDate: function(date, value) {
var hours = date.getHours();
if (value instanceof DATE) {
date.setFullYear(value.getFullYear(), value.getMonth(), value.getDate());
} else {
calendar.setTime(date, value * MS_PER_DAY);
}
adjustDate(date, hours);
},
toDateString: function(date) {
return date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate();
}
}, {
name: "year",
title: function(date) {
return date.getFullYear();
},
content: function(options) {
var namesAbbr = getCalendarInfo(options.culture).months.namesAbbr, toDateString = this.toDateString, min = options.min, max = options.max;
return view({
min: new DATE(min.getFullYear(), min.getMonth(), 1),
max: new DATE(max.getFullYear(), max.getMonth(), 1),
start: new DATE(options.date.getFullYear(), 0, 1),
setter: this.setDate,
build: function(date) {
return {
value: namesAbbr[date.getMonth()],
ns: kendo.ns,
dateString: toDateString(date),
cssClass: ""
};
}
});
},
first: function(date) {
return new DATE(date.getFullYear(), 0, date.getDate());
},
last: function(date) {
return new DATE(date.getFullYear(), 11, date.getDate());
},
compare: function(date1, date2) {
return compare(date1, date2);
},
setDate: function(date, value) {
var month, hours = date.getHours();
if (value instanceof DATE) {
month = value.getMonth();
date.setFullYear(value.getFullYear(), month, date.getDate());
if (month !== date.getMonth()) {
date.setDate(0);
}
} else {
month = date.getMonth() + value;
date.setMonth(month);
if (month > 11) {
month -= 12;
}
if (month > 0 && date.getMonth() != month) {
date.setDate(0);
}
}
adjustDate(date, hours);
},
toDateString: function(date) {
return date.getFullYear() + "/" + date.getMonth() + "/1";
}
}, {
name: "decade",
title: function(date, min, max) {
return title(date, min, max, 10);
},
content: function(options) {
var year = options.date.getFullYear(), toDateString = this.toDateString;
return view({
start: new DATE(year - year % 10 - 1, 0, 1),
min: new DATE(options.min.getFullYear(), 0, 1),
max: new DATE(options.max.getFullYear(), 0, 1),
setter: this.setDate,
build: function(date, idx) {
return {
value: date.getFullYear(),
ns: kendo.ns,
dateString: toDateString(date),
cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
};
}
});
},
first: function(date) {
var year = date.getFullYear();
return new DATE(year - year % 10, date.getMonth(), date.getDate());
},
last: function(date) {
var year = date.getFullYear();
return new DATE(year - year % 10 + 9, date.getMonth(), date.getDate());
},
compare: function(date1, date2) {
return compare(date1, date2, 10);
},
setDate: function(date, value) {
setDate(date, value, 1);
},
toDateString: function(date) {
return date.getFullYear() + "/0/1";
}
}, {
name: CENTURY,
title: function(date, min, max) {
return title(date, min, max, 100);
},
content: function(options) {
var year = options.date.getFullYear(), min = options.min.getFullYear(), max = options.max.getFullYear(), toDateString = this.toDateString, minYear = min, maxYear = max;
minYear = minYear - minYear % 10;
maxYear = maxYear - maxYear % 10;
if (maxYear - minYear < 10) {
maxYear = minYear + 9;
}
return view({
start: new DATE(year - year % 100 - 10, 0, 1),
min: new DATE(minYear, 0, 1),
max: new DATE(maxYear, 0, 1),
setter: this.setDate,
build: function(date, idx) {
var start = date.getFullYear(), end = start + 9;
if (start < min) {
start = min;
}
if (end > max) {
end = max;
}
return {
ns: kendo.ns,
value: start + " - " + end,
dateString: toDateString(date),
cssClass: idx === 0 || idx == 11 ? OTHERMONTHCLASS : ""
};
}
});
},
first: function(date) {
var year = date.getFullYear();
return new DATE(year - year % 100, date.getMonth(), date.getDate());
},
last: function(date) {
var year = date.getFullYear();
return new DATE(year - year % 100 + 99, date.getMonth(), date.getDate());
},
compare: function(date1, date2) {
return compare(date1, date2, 100);
},
setDate: function(date, value) {
setDate(date, value, 10);
},
toDateString: function(date) {
var year = date.getFullYear();
return year - year % 10 + "/0/1";
}
} ]
};
function title(date, min, max, modular) {
var start = date.getFullYear(), minYear = min.getFullYear(), maxYear = max.getFullYear(), end;
start = start - start % modular;
end = start + (modular - 1);
if (start < minYear) {
start = minYear;
}
if (end > maxYear) {
end = maxYear;
}
return start + "-" + end;
}
function view(options) {
var idx = 0, data, min = options.min, max = options.max, start = options.start, setter = options.setter, build = options.build, length = options.cells || 12, cellsPerRow = options.perRow || 4, content = options.content || cellTemplate, empty = options.empty || emptyCellTemplate, html = options.html || '<table tabindex="0" role="grid" class="k-content k-meta-view" cellspacing="0"><tbody><tr role="row">';
for (;idx < length; idx++) {
if (idx > 0 && idx % cellsPerRow === 0) {
html += '</tr><tr role="row">';
}
data = build(start, idx);
html += isInRange(start, min, max) ? content(data) : empty(data);
setter(start, 1);
}
return html + "</tr></tbody></table>";
}
function compare(date1, date2, modifier) {
var year1 = date1.getFullYear(), start = date2.getFullYear(), end = start, result = 0;
if (modifier) {
start = start - start % modifier;
end = start - start % modifier + modifier - 1;
}
if (year1 > end) {
result = 1;
} else if (year1 < start) {
result = -1;
}
return result;
}
function restrictValue(value, min, max) {
var today = new DATE();
today = new DATE(today.getFullYear(), today.getMonth(), today.getDate());
if (value) {
today = new DATE(+value);
}
if (min > today) {
today = new DATE(+min);
} else if (max < today) {
today = new DATE(+max);
}
return today;
}
function isInRange(date, min, max) {
return +date >= +min && +date <= +max;
}
function shiftArray(array, idx) {
return array.slice(idx).concat(array.slice(0, idx));
}
function setDate(date, value, multiplier) {
value = value instanceof DATE ? value.getFullYear() : date.getFullYear() + multiplier * value;
date.setFullYear(value);
}
function mousetoggle(e) {
$(this).toggleClass(HOVER, MOUSEENTER.indexOf(e.type) > -1 || e.type == FOCUS);
}
function prevent(e) {
e.preventDefault();
}
function getCalendarInfo(culture) {
return getCulture(culture).calendars.standard;
}
function normalize(options) {
var start = views[options.start], depth = views[options.depth], culture = getCulture(options.culture);
options.format = extractFormat(options.format || culture.calendars.standard.patterns.d);
if (isNaN(start)) {
start = 0;
options.start = MONTH;
}
if (depth === undefined || depth > start) {
options.depth = MONTH;
}
if (!options.dates) {
options.dates = [];
}
}
function makeUnselectable(element) {
if (isIE8) {
element.find("*").attr("unselectable", "on");
}
}
function inArray(date, dates) {
for (var i = 0, length = dates.length; i < length; i++) {
if (date === +dates[i]) {
return true;
}
}
return false;
}
function isEqualDatePart(value1, value2) {
if (value1) {
return value1.getFullYear() === value2.getFullYear() && value1.getMonth() === value2.getMonth() && value1.getDate() === value2.getDate();
}
return false;
}
calendar.isEqualDatePart = isEqualDatePart;
calendar.makeUnselectable = makeUnselectable;
calendar.restrictValue = restrictValue;
calendar.isInRange = isInRange;
calendar.normalize = normalize;
calendar.viewsEnum = views;
kendo.calendar = calendar;
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, parse = kendo.parseDate, keys = kendo.keys, template = kendo.template, activeElement = kendo._activeElement, DIV = "<div />", SPAN = "<span />", ns = ".kendoDatePicker", CLICK = "click" + ns, OPEN = "open", CLOSE = "close", CHANGE = "change", DATEVIEW = "dateView", DISABLED = "disabled", READONLY = "readonly", DEFAULT = "k-state-default", FOCUSED = "k-state-focused", SELECTED = "k-state-selected", STATEDISABLED = "k-state-disabled", HOVER = "k-state-hover", KEYDOWN = "keydown" + ns, HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, MOUSEDOWN = "mousedown" + ns, ID = "id", MIN = "min", MAX = "max", MONTH = "month", ARIA_DISABLED = "aria-disabled", ARIA_EXPANDED = "aria-expanded", ARIA_HIDDEN = "aria-hidden", ARIA_READONLY = "aria-readonly", calendar = kendo.calendar, isInRange = calendar.isInRange, restrictValue = calendar.restrictValue, isEqualDatePart = calendar.isEqualDatePart, extend = $.extend, proxy = $.proxy, DATE = Date;
function normalize(options) {
var parseFormats = options.parseFormats, format = options.format;
calendar.normalize(options);
parseFormats = $.isArray(parseFormats) ? parseFormats : [ parseFormats ];
if ($.inArray(format, parseFormats) === -1) {
parseFormats.splice(0, 0, options.format);
}
options.parseFormats = parseFormats;
}
function preventDefault(e) {
e.preventDefault();
}
var DateView = function(options) {
var that = this, id, body = document.body, sharedCalendar = DatePicker.sharedCalendar, div = $(DIV).attr(ARIA_HIDDEN, "true").addClass("k-calendar-container").appendTo(body);
if (!sharedCalendar) {
sharedCalendar = DatePicker.sharedCalendar = new ui.Calendar($(DIV).attr(ID, kendo.guid()).hide().appendTo(body), {
focusOnNav: false
});
calendar.makeUnselectable(sharedCalendar.element);
}
that.calendar = sharedCalendar;
that.options = options = options || {};
id = options.id;
if (id) {
id += "_dateview";
div.attr(ID, id);
that._dateViewID = id;
}
that.popup = new ui.Popup(div, extend(options.popup, options, {
name: "Popup",
isRtl: kendo.support.isRtl(options.anchor)
}));
that.div = div;
that._templates();
that.value(options.value);
};
DateView.prototype = {
_calendar: function() {
var that = this, popup = that.popup, options = that.options, calendar = that.calendar, element = calendar.element;
if (element.data(DATEVIEW) !== that) {
element.appendTo(popup.element).data(DATEVIEW, that).off(CLICK + " " + KEYDOWN).on(CLICK, "td:has(.k-link)", proxy(that._click, that)).on(MOUSEDOWN, preventDefault).show();
calendar.unbind(CHANGE).bind(CHANGE, options);
calendar.month = that.month;
calendar.options.dates = options.dates;
calendar.options.depth = options.depth;
calendar.options.culture = options.culture;
calendar._footer(that.footer);
calendar.min(options.min);
calendar.max(options.max);
calendar.navigate(that._value || that._current, options.start);
that.value(that._value);
}
},
destroy: function() {
var that = this, calendar = that.calendar, element = calendar.element, dv = element.data(DATEVIEW), popups;
if (dv === undefined || dv === that) {
popups = $(".k-calendar-container");
if (popups.length > 1) {
element.hide().appendTo(document.body);
} else {
element.off(ns);
calendar.destroy();
calendar.element.remove();
DatePicker.sharedCalendar = null;
}
}
that.popup.destroy();
},
open: function() {
var that = this;
that._calendar();
that.popup.open();
},
close: function() {
this.popup.close();
},
min: function(value) {
this._option(MIN, value);
},
max: function(value) {
this._option(MAX, value);
},
toggle: function() {
var that = this;
that[that.popup.visible() ? CLOSE : OPEN]();
},
move: function(e) {
var that = this, key = e.keyCode, calendar = that.calendar, selectIsClicked = e.ctrlKey && key == keys.DOWN || key == keys.ENTER;
if (key == keys.ESC) {
that.close();
return;
}
if (e.altKey) {
if (key == keys.DOWN) {
that.open();
e.preventDefault();
} else if (key == keys.UP) {
that.close();
e.preventDefault();
}
return;
}
if (!that.popup.visible()) {
return;
}
if (selectIsClicked && calendar._cell.hasClass(SELECTED)) {
that.close();
e.preventDefault();
return;
}
that._current = calendar._move(e);
},
value: function(value) {
var that = this, calendar = that.calendar, options = that.options;
that._value = value;
that._current = new DATE(+restrictValue(value, options.min, options.max));
if (calendar.element.data(DATEVIEW) === that) {
calendar.value(value);
}
},
_click: function(e) {
if (e.currentTarget.className.indexOf(SELECTED) !== -1) {
this.close();
}
},
_option: function(option, value) {
var that = this, options = that.options, calendar = that.calendar;
options[option] = value;
if (calendar.element.data(DATEVIEW) === that) {
calendar[option](value);
}
},
_templates: function() {
var that = this, options = that.options, footer = options.footer, month = options.month || {}, content = month.content, empty = month.empty;
that.month = {
content: template('<td#=data.cssClass#><a tabindex="-1" class="k-link" href="\\#" ' + kendo.attr("value") + '="#=data.dateString#" title="#=data.title#">' + (content || "#=data.value#") + "</a></td>", {
useWithBlock: !!content
}),
empty: template("<td>" + (empty || " ") + "</td>", {
useWithBlock: !!empty
})
};
if (footer !== false) {
that.footer = template(footer || '#= kendo.toString(data,"D","' + options.culture + '") #', {
useWithBlock: false
});
}
}
};
DateView.normalize = normalize;
kendo.DateView = DateView;
var DatePicker = Widget.extend({
init: function(element, options) {
var that = this, disabled, div;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
normalize(options);
that._wrapper();
that.dateView = new DateView(extend({}, options, {
id: element.attr(ID),
anchor: that.wrapper,
change: function() {
// calendar is the current scope
that._change(this.value());
that.close();
},
close: function(e) {
if (that.trigger(CLOSE)) {
e.preventDefault();
} else {
element.attr(ARIA_EXPANDED, false);
div.attr(ARIA_HIDDEN, true);
}
},
open: function(e) {
var options = that.options, date;
if (that.trigger(OPEN)) {
e.preventDefault();
} else {
if (that.element.val() !== that._oldText) {
date = parse(element.val(), options.parseFormats, options.culture);
if (!date) {
that.dateView.value(date);
} else {
that.dateView._current = date;
that.dateView.calendar._focus(date);
}
}
element.attr(ARIA_EXPANDED, true);
div.attr(ARIA_HIDDEN, false);
}
}
}));
div = that.dateView.div;
that._icon();
element[0].type = "text";
element.addClass("k-input").attr({
role: "textbox",
"aria-haspopup": true,
"aria-expanded": false,
"aria-owns": that.dateView._dateViewID
});
that._reset();
that._template();
disabled = element.is("[disabled]");
if (disabled) {
that.enable(false);
} else {
that.readonly(element.is("[readonly]"));
}
that.value(options.value || that.element.val());
kendo.notify(that);
},
events: [ OPEN, CLOSE, CHANGE ],
options: {
name: "DatePicker",
value: null,
footer: "",
format: "",
culture: "",
parseFormats: [],
min: new Date(1900, 0, 1),
max: new Date(2099, 11, 31),
start: MONTH,
depth: MONTH,
animation: {},
month: {},
dates: [],
ARIATemplate: 'Current focused date is #=kendo.toString(data.current, "D")#'
},
setOptions: function(options) {
var that = this, dateView = that.dateView, dateViewOptions = dateView.options;
Widget.fn.setOptions.call(that, options);
normalize(that.options);
dateView.options = extend(dateViewOptions, that.options, {
change: dateViewOptions.change,
close: dateViewOptions.close,
open: dateViewOptions.open
});
},
_editable: function(options) {
var that = this, icon = that._dateIcon.off(ns), element = that.element.off(ns), wrapper = that._inputWrapper.off(ns), readonly = options.readonly, disable = options.disable;
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
element.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false).on("keydown" + ns, proxy(that._keydown, that)).on("blur" + ns, proxy(that._blur, that)).on("focus" + ns, function() {
that._inputWrapper.addClass(FOCUSED);
});
icon.on(CLICK, proxy(that._click, that)).on(MOUSEDOWN, preventDefault);
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
element.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
readonly: function(readonly) {
this._editable({
readonly: readonly === undefined ? true : readonly,
disable: false
});
},
enable: function(enable) {
this._editable({
readonly: false,
disable: !(enable = enable === undefined ? true : enable)
});
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.dateView.destroy();
that.element.off(ns);
that._dateIcon.off(ns);
that._inputWrapper.off(ns);
if (that._form) {
that._form.off("reset", that._resetHandler);
}
},
open: function() {
this.dateView.open();
},
close: function() {
this.dateView.close();
},
min: function(value) {
return this._option(MIN, value);
},
max: function(value) {
return this._option(MAX, value);
},
value: function(value) {
var that = this;
if (value === undefined) {
return that._value;
}
that._old = that._update(value);
if (that._old === null) {
that.element.val("");
}
that._oldText = that.element.val();
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_blur: function() {
var that = this, value = that.element.val();
that.close();
if (value !== that._oldText) {
that._change(value);
}
that._inputWrapper.removeClass(FOCUSED);
},
_click: function() {
var that = this, element = that.element;
that.dateView.toggle();
if (!kendo.support.touch && element[0] !== activeElement()) {
element.focus();
}
},
_change: function(value) {
var that = this;
value = that._update(value);
if (+that._old != +value) {
that._old = value;
that._oldText = that.element.val();
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_keydown: function(e) {
var that = this, dateView = that.dateView, value = that.element.val();
if (!dateView.popup.visible() && e.keyCode == keys.ENTER && value !== that._oldText) {
that._change(value);
} else {
dateView.move(e);
that._updateARIA(dateView._current);
}
},
_icon: function() {
var that = this, element = that.element, icon;
icon = element.next("span.k-select");
if (!icon[0]) {
icon = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-calendar">select</span></span>').insertAfter(element);
}
that._dateIcon = icon.attr({
role: "button",
"aria-controls": that.dateView._dateViewID
});
},
_option: function(option, value) {
var that = this, options = that.options;
if (value === undefined) {
return options[option];
}
value = parse(value, options.parseFormats, options.culture);
if (!value) {
return;
}
options[option] = new DATE(+value);
that.dateView[option](value);
},
_update: function(value) {
var that = this, options = that.options, min = options.min, max = options.max, date = parse(value, options.parseFormats, options.culture), formattedValue;
if (+date === +that._value) {
formattedValue = kendo.toString(date, options.format, options.culture);
if (formattedValue !== value) {
that.element.val(date === null ? value : formattedValue);
}
return date;
}
if (date !== null && isEqualDatePart(date, min)) {
date = restrictValue(date, min, max);
} else if (!isInRange(date, min, max)) {
date = null;
}
that._value = date;
that.dateView.value(date);
that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
that._updateARIA(date);
return date;
},
_wrapper: function() {
var that = this, element = that.element, wrapper;
wrapper = element.parents(".k-datepicker");
if (!wrapper[0]) {
wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
wrapper = wrapper.wrap(SPAN).parent();
}
wrapper[0].style.cssText = element[0].style.cssText;
element.css({
width: "100%",
height: element[0].style.height
});
that.wrapper = wrapper.addClass("k-widget k-datepicker k-header").addClass(element[0].className);
that._inputWrapper = $(wrapper[0].firstChild);
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
that.value(element[0].defaultValue);
};
that._form = form.on("reset", that._resetHandler);
}
},
_template: function() {
this._ariaTemplate = template(this.options.ARIATemplate);
},
_updateARIA: function(date) {
this.element.attr("aria-label", this._ariaTemplate({
current: date
}));
}
});
ui.plugin(DatePicker);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, support = kendo.support, activeElement = kendo._activeElement, placeholderSupported = support.placeholder, ui = kendo.ui, keys = kendo.keys, DataSource = kendo.data.DataSource, List = ui.List, ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", DEFAULT = "k-state-default", DISABLED = "disabled", READONLY = "readonly", FOCUSED = "k-state-focused", SELECTED = "k-state-selected", STATEDISABLED = "k-state-disabled", HOVER = "k-state-hover", ns = ".kendoAutoComplete", HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, caretPosition = List.caret, selectText = List.selectText, proxy = $.proxy;
function indexOfWordAtCaret(caret, text, separator) {
return separator ? text.substring(0, caret).split(separator).length - 1 : 0;
}
function wordAtCaret(caret, text, separator) {
return text.split(separator)[indexOfWordAtCaret(caret, text, separator)];
}
function replaceWordAtCaret(caret, text, word, separator) {
var words = text.split(separator);
words.splice(indexOfWordAtCaret(caret, text, separator), 1, word);
if (separator && words[words.length - 1] !== "") {
words.push("");
}
return words.join(separator);
}
function moveCaretAtEnd(element) {
var length = element.value.length;
selectText(element, length, length);
}
var AutoComplete = List.extend({
init: function(element, options) {
var that = this, wrapper;
that.ns = ns;
options = $.isArray(options) ? {
dataSource: options
} : options;
List.fn.init.call(that, element, options);
element = that.element;
options = that.options;
options.placeholder = options.placeholder || element.attr("placeholder");
if (placeholderSupported) {
element.attr("placeholder", options.placeholder);
}
that._wrapper();
that._loader();
that._accessors();
that._dataSource();
that._ignoreCase();
element[0].type = "text";
wrapper = that.wrapper;
that._popup();
element.addClass("k-input").on("keydown" + ns, proxy(that._keydown, that)).on("paste" + ns, proxy(that._search, that)).on("focus" + ns, function() {
that._prev = that._accessor();
that._placeholder(false);
wrapper.addClass(FOCUSED);
}).on("blur" + ns, function() {
that._change();
that._placeholder();
wrapper.removeClass(FOCUSED);
}).attr({
autocomplete: "off",
role: "textbox",
"aria-haspopup": true
});
that._enable();
that._old = that._accessor();
if (element[0].id) {
element.attr("aria-owns", that.ul[0].id);
}
that._aria();
that._placeholder();
kendo.notify(that);
},
options: {
name: "AutoComplete",
enabled: true,
suggest: false,
template: "",
dataTextField: "",
minLength: 1,
delay: 200,
height: 200,
filter: "startswith",
ignoreCase: true,
highlightFirst: false,
separator: null,
placeholder: "",
animation: {}
},
_dataSource: function() {
var that = this;
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._showBusy, that);
}
that.dataSource = DataSource.create(that.options.dataSource).bind("change", that._refreshHandler).bind("progress", that._progressHandler);
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
},
events: [ "open", "close", "change", "select", "dataBinding", "dataBound" ],
setOptions: function(options) {
List.fn.setOptions.call(this, options);
this._template();
this._accessors();
this._aria();
},
_editable: function(options) {
var that = this, element = that.element, wrapper = that.wrapper.off(ns), readonly = options.readonly, disable = options.disable;
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
element.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false);
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
element.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
close: function() {
var that = this, current = that._current;
if (current) {
current.removeClass(SELECTED);
}
that.current(null);
that.popup.close();
},
destroy: function() {
var that = this;
that.element.off(ns);
that.wrapper.off(ns);
List.fn.destroy.call(that);
},
refresh: function() {
var that = this, ul = that.ul[0], popup = that.popup, options = that.options, data = that._data(), length = data.length, action;
that.trigger("dataBinding");
ul.innerHTML = kendo.render(that.template, data);
that._height(length);
if (popup.visible()) {
popup._position();
}
if (length) {
if (options.highlightFirst) {
that.current($(ul.firstChild));
}
if (options.suggest) {
that.suggest($(ul.firstChild));
}
}
if (that._open) {
that._open = false;
action = length ? "open" : "close";
if (that._typing && that.element[0] !== activeElement()) {
action = "close";
}
popup[action]();
that._typing = undefined;
}
if (that._touchScroller) {
that._touchScroller.reset();
}
that._makeUnselectable();
that._hideBusy();
that.trigger("dataBound");
},
select: function(li) {
this._select(li);
},
search: function(word) {
var that = this, options = that.options, ignoreCase = options.ignoreCase, separator = options.separator, length;
word = word || that._accessor();
that._current = null;
clearTimeout(that._typing);
if (separator) {
word = wordAtCaret(caretPosition(that.element[0]), word, separator);
}
length = word.length;
if (!length) {
that.popup.close();
} else if (length >= that.options.minLength) {
that._open = true;
that._filterSource({
value: ignoreCase ? word.toLowerCase() : word,
operator: options.filter,
field: options.dataTextField,
ignoreCase: ignoreCase
});
}
},
suggest: function(word) {
var that = this, key = that._last, value = that._accessor(), element = that.element[0], caret = caretPosition(element), separator = that.options.separator, words = value.split(separator), wordIndex = indexOfWordAtCaret(caret, value, separator), selectionEnd = caret, idx;
if (key == keys.BACKSPACE || key == keys.DELETE) {
that._last = undefined;
return;
}
word = word || "";
if (typeof word !== "string") {
idx = List.inArray(word[0], that.ul[0]);
if (idx > -1) {
word = that._text(that._data()[idx]);
} else {
word = "";
}
}
if (caret <= 0) {
caret = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
}
idx = value.substring(0, caret).lastIndexOf(separator);
idx = idx > -1 ? caret - (idx + separator.length) : caret;
value = words[wordIndex].substring(0, idx);
if (word) {
idx = word.toLowerCase().indexOf(value.toLowerCase());
if (idx > -1) {
word = word.substring(idx + value.length);
selectionEnd = caret + word.length;
value += word;
}
if (separator && words[words.length - 1] !== "") {
words.push("");
}
}
words[wordIndex] = value;
that._accessor(words.join(separator || ""));
selectText(element, caret, selectionEnd);
},
value: function(value) {
if (value !== undefined) {
this._accessor(value);
this._old = value;
} else {
return this._accessor();
}
},
_accessor: function(value) {
var that = this, element = that.element[0];
if (value !== undefined) {
element.value = value === null ? "" : value;
that._placeholder();
} else {
value = element.value;
if (element.className.indexOf("k-readonly") > -1) {
if (value === that.options.placeholder) {
return "";
} else {
return value;
}
}
return value;
}
},
_accept: function(li) {
var that = this;
that._focus(li);
moveCaretAtEnd(that.element[0]);
},
_keydown: function(e) {
var that = this, ul = that.ul[0], key = e.keyCode, current = that._current, visible = that.popup.visible();
that._last = key;
if (key === keys.DOWN) {
if (visible) {
that._move(current ? current.next() : $(ul.firstChild));
}
e.preventDefault();
} else if (key === keys.UP) {
if (visible) {
that._move(current ? current.prev() : $(ul.lastChild));
}
e.preventDefault();
} else if (key === keys.ENTER || key === keys.TAB) {
if (key === keys.ENTER && that.popup.visible()) {
e.preventDefault();
}
that._accept(current);
} else if (key === keys.ESC) {
if (that.popup.visible()) {
e.preventDefault();
}
that.close();
} else {
that._search();
}
},
_move: function(li) {
var that = this;
li = li[0] ? li : null;
that.current(li);
if (that.options.suggest) {
that.suggest(li);
}
},
_hideBusy: function() {
var that = this;
clearTimeout(that._busy);
that._loading.hide();
that.element.attr("aria-busy", false);
that._busy = null;
},
_showBusy: function() {
var that = this;
if (that._busy) {
return;
}
that._busy = setTimeout(function() {
that.element.attr("aria-busy", true);
that._loading.show();
}, 100);
},
_placeholder: function(show) {
if (placeholderSupported) {
return;
}
var that = this, element = that.element, placeholder = that.options.placeholder, value;
if (placeholder) {
value = element.val();
if (show === undefined) {
show = !value;
}
if (!show) {
if (value !== placeholder) {
placeholder = value;
} else {
placeholder = "";
}
}
if (value === that._old && !show) {
return;
}
element.toggleClass("k-readonly", show).val(placeholder);
}
},
_search: function() {
var that = this;
clearTimeout(that._typing);
that._typing = setTimeout(function() {
if (that._prev !== that._accessor()) {
that._prev = that._accessor();
that.search();
}
}, that.options.delay);
},
_select: function(li) {
var that = this, separator = that.options.separator, data = that._data(), text, idx;
li = $(li);
if (li[0] && !li.hasClass(SELECTED)) {
idx = List.inArray(li[0], that.ul[0]);
if (idx > -1) {
data = data[idx];
text = that._text(data);
if (separator) {
text = replaceWordAtCaret(caretPosition(that.element[0]), that._accessor(), text, separator);
}
that._accessor(text);
that.current(li.addClass(SELECTED));
}
}
},
_loader: function() {
this._loading = $('<span class="k-icon k-loading" style="display:none"></span>').insertAfter(this.element);
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_wrapper: function() {
var that = this, element = that.element, DOMelement = element[0], wrapper;
wrapper = element.parent();
if (!wrapper.is("span.k-widget")) {
wrapper = element.wrap("<span />").parent();
}
//aria
wrapper.attr("tabindex", -1);
wrapper.attr("role", "presentation");
//end
wrapper[0].style.cssText = DOMelement.style.cssText;
element.css({
width: "100%",
height: DOMelement.style.height
});
that._focused = that.element;
that.wrapper = wrapper.addClass("k-widget k-autocomplete k-header").addClass(DOMelement.className);
}
});
ui.plugin(AutoComplete);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Select = ui.Select, os = kendo.support.mobileOS, ns = ".kendoDropDownList", DISABLED = "disabled", READONLY = "readonly", CHANGE = "change", SELECT = "select", FOCUSED = "k-state-focused", DEFAULT = "k-state-default", STATEDISABLED = "k-state-disabled", ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", SELECTED = "k-state-selected", HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, TABINDEX = "tabindex", proxy = $.proxy;
var DropDownList = Select.extend({
init: function(element, options) {
var that = this, index = options && options.index, optionLabel, useOptionLabel, text;
that.ns = ns;
options = $.isArray(options) ? {
dataSource: options
} : options;
Select.fn.init.call(that, element, options);
that._focusHandler = function() {
that.wrapper.focus();
};
options = that.options;
element = that.element.on("focus" + ns, that._focusHandler);
that._reset();
that._word = "";
that._wrapper();
that._tabindex();
that.wrapper.data(TABINDEX, that.wrapper.attr(TABINDEX));
that._aria();
that._span();
that._popup();
that._mobile();
that._accessors();
that._dataSource();
that._ignoreCase();
that._enable();
that._cascade();
that._oldIndex = that.selectedIndex = -1;
if (index !== undefined) {
options.index = index;
}
if (options.autoBind) {
that.dataSource.fetch();
} else {
text = options.text || "";
if (!text) {
optionLabel = that._optionLabelText(options.optionLabel), useOptionLabel = optionLabel && options.index === 0;
if (element.is(SELECT)) {
if (useOptionLabel) {
text = optionLabel;
} else {
text = element.children(":selected").text();
}
} else if (!element[0].value && useOptionLabel) {
text = optionLabel;
}
}
that.text(text);
}
kendo.notify(that);
},
options: {
name: "DropDownList",
enabled: true,
autoBind: true,
index: 0,
text: null,
value: null,
template: "",
delay: 500,
height: 200,
dataTextField: "",
dataValueField: "",
optionLabel: "",
cascadeFrom: "",
ignoreCase: true,
animation: {}
},
events: [ "open", "close", CHANGE, "select", "dataBinding", "dataBound", "cascade" ],
setOptions: function(options) {
Select.fn.setOptions.call(this, options);
this._template();
this._accessors();
this._aria();
},
destroy: function() {
var that = this;
that.wrapper.off(ns);
that.element.off(ns);
that._inputWrapper.off(ns);
Select.fn.destroy.call(that);
},
open: function() {
var that = this;
if (!that.ul[0].firstChild) {
that._open = true;
if (!that._request) {
that.dataSource.fetch();
}
} else {
that.popup.open();
that._scroll(that._current);
}
},
toggle: function(toggle) {
this._toggle(toggle);
},
refresh: function() {
var that = this, data = that._data(), length = data.length, optionLabel = that.options.optionLabel;
that.trigger("dataBinding");
if (that._current) {
that.current(null);
}
that.ul[0].innerHTML = kendo.render(that.template, data);
that._height(length);
if (that.popup.visible()) {
that.popup._position();
}
if (that.element.is(SELECT)) {
if (optionLabel && length) {
optionLabel = that._optionLabelText(optionLabel);
optionLabel = '<option value="">' + optionLabel + "</option>";
}
that._options(data, optionLabel);
}
if (that._open) {
that._open = false;
that.toggle(!!length);
}
that._hideBusy();
that._makeUnselectable();
if (!that._fetch && length) {
that._selectItem();
}
that._bound = true;
that.trigger("dataBound");
},
search: function(word) {
if (word) {
var that = this, ignoreCase = that.options.ignoreCase;
if (ignoreCase) {
word = word.toLowerCase();
}
that._select(function(dataItem) {
var text = that._text(dataItem);
if (text !== undefined) {
text = text + "";
if (ignoreCase) {
text = text.toLowerCase();
}
return text.indexOf(word) === 0;
}
});
}
},
text: function(text) {
var span = this.span;
if (text !== undefined) {
span.text(text);
} else {
return span.text();
}
},
value: function(value) {
var that = this, idx, hasValue;
if (value !== undefined) {
if (value !== null) {
value = value.toString();
}
that._selectedValue = value;
hasValue = value || that.options.optionLabel && !that.element[0].disabled && value === "";
if (hasValue && that._fetchItems(value)) {
return;
}
idx = that._index(value);
that.select(idx > -1 ? idx : 0);
} else {
return that._accessor();
}
},
_editable: function(options) {
var that = this, element = that.element, disable = options.disable, readonly = options.readonly, wrapper = that.wrapper.off(ns), dropDownWrapper = that._inputWrapper.off(HOVEREVENTS);
if (!readonly && !disable) {
element.removeAttr(DISABLED).removeAttr(READONLY);
dropDownWrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
wrapper.attr(TABINDEX, wrapper.data(TABINDEX)).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false).on("click" + ns, function(e) {
that._blured = false;
e.preventDefault();
that.toggle();
}).on("keydown" + ns, proxy(that._keydown, that)).on("keypress" + ns, proxy(that._keypress, that)).on("focusin" + ns, function() {
dropDownWrapper.addClass(FOCUSED);
that._blured = false;
}).on("focusout" + ns, function() {
if (!that._blured) {
that._triggerCascade();
that._blur();
dropDownWrapper.removeClass(FOCUSED);
that._blured = true;
element.blur();
}
});
} else {
if (disable) {
wrapper.removeAttr(TABINDEX);
dropDownWrapper.addClass(STATEDISABLED).removeClass(DEFAULT);
} else {
dropDownWrapper.addClass(DEFAULT).removeClass(STATEDISABLED);
}
element.attr(DISABLED, disable).attr(READONLY, readonly);
wrapper.attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
_accept: function(li) {
this._focus(li);
},
_optionLabelText: function() {
var options = this.options, dataTextField = options.dataTextField, optionLabel = options.optionLabel;
if (optionLabel && dataTextField && typeof optionLabel === "object") {
return this._text(optionLabel);
}
return optionLabel;
},
_data: function() {
var that = this, options = that.options, optionLabel = options.optionLabel, textField = options.dataTextField, valueField = options.dataValueField, data = that.dataSource.view(), length = data.length, first = optionLabel, idx = 0;
if (optionLabel && length) {
if (typeof optionLabel === "object") {
first = optionLabel;
} else if (textField) {
first = {};
textField = textField.split(".");
valueField = valueField.split(".");
assign(first, valueField, "");
assign(first, textField, optionLabel);
}
first = new kendo.data.ObservableArray([ first ]);
for (;idx < length; idx++) {
first.push(data[idx]);
}
data = first;
}
return data;
},
_keydown: function(e) {
var that = this, key = e.keyCode, keys = kendo.keys, ul = that.ul[0];
if (key === keys.LEFT) {
key = keys.UP;
} else if (key === keys.RIGHT) {
key = keys.DOWN;
}
e.keyCode = key;
that._move(e);
if (key === keys.HOME) {
e.preventDefault();
that._select(ul.firstChild);
} else if (key === keys.END) {
e.preventDefault();
that._select(ul.lastChild);
}
},
_selectNext: function(character, index) {
var that = this, ignoreCase = that.options.ignoreCase, data = that._data(), length = data.length, text;
for (;index < length; index++) {
text = that._text(data[index]);
if (text) {
text = text + "";
if (ignoreCase) {
text = text.toLowerCase();
}
if (text.indexOf(character) === 0) {
that._select(index);
that._triggerEvents();
return true;
}
}
}
return false;
},
_keypress: function(e) {
var that = this;
setTimeout(function() {
var character = String.fromCharCode(e.keyCode || e.charCode), index = that.selectedIndex;
if (that.options.ignoreCase) {
character = character.toLowerCase();
}
if (character === that._last && index > -1) {
that._word = character;
if (that._selectNext(character, index + 1)) {
return;
}
} else {
that._word += character;
}
that._last = character;
that._search();
});
},
_popup: function() {
Select.fn._popup.call(this);
this.popup.one("open", function() {
this.wrapper = kendo.wrap(this.element).addClass("km-popup");
});
},
_search: function() {
var that = this, dataSource = that.dataSource, word = that._word;
clearTimeout(that._typing);
that._typing = setTimeout(function() {
that._word = "";
}, that.options.delay);
if (!that.ul[0].firstChild) {
dataSource.one(CHANGE, function() {
if (dataSource.data()[0]) {
that.search(word);
}
}).fetch();
return;
}
that.search(word);
that._triggerEvents();
},
_select: function(li) {
var that = this, current = that._current, data = that._data(), value, text, idx;
li = that._get(li);
if (li && li[0] && !li.hasClass(SELECTED)) {
if (current) {
current.removeClass(SELECTED);
}
idx = ui.List.inArray(li[0], that.ul[0]);
if (idx > -1) {
data = data[idx];
text = that._text(data);
value = that._value(data);
that.selectedIndex = idx;
that.text(text);
that._accessor(value !== undefined ? value : text, idx);
that._selectedValue = that._accessor();
that.current(li.addClass(SELECTED));
if (that._optionID) {
that._current.attr("aria-selected", true);
}
}
}
},
_triggerEvents: function() {
if (!this.popup.visible()) {
this._triggerCascade();
this._change();
}
},
_mobile: function() {
var that = this, popup = that.popup, root = popup.element.parents(".km-root").eq(0);
if (root.length && os) {
popup.options.animation.open.effects = os.android || os.meego ? "fadeIn" : os.ios || os.wp ? "slideIn:up" : popup.options.animation.open.effects;
}
},
_span: function() {
var that = this, wrapper = that.wrapper, SELECTOR = "span.k-input", span;
span = wrapper.find(SELECTOR);
if (!span[0]) {
wrapper.append('<span unselectable="on" class="k-dropdown-wrap k-state-default"><span unselectable="on" class="k-input"> </span><span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-arrow-s">select</span></span></span>').append(that.element);
span = wrapper.find(SELECTOR);
}
that.span = span;
that._inputWrapper = $(wrapper[0].firstChild);
that._arrow = wrapper.find(".k-icon").mousedown(function(e) {
e.preventDefault();
});
},
_wrapper: function() {
var that = this, element = that.element, DOMelement = element[0], wrapper;
wrapper = element.parent();
if (!wrapper.is("span.k-widget")) {
wrapper = element.wrap("<span />").parent();
wrapper[0].style.cssText = DOMelement.style.cssText;
}
element.hide();
that._focused = that.wrapper = wrapper.addClass("k-widget k-dropdown k-header").addClass(DOMelement.className).css("display", "").attr({
unselectable: "on",
role: "listbox",
"aria-haspopup": true,
"aria-expanded": false
});
},
_clearSelection: function() {
var that = this, optionLabel = that.options.optionLabel;
if (that.dataSource.view()[0] && optionLabel) {
that.select(0);
return;
}
that.text(optionLabel);
that.element.val("");
that.selectedIndex = -1;
}
});
function assign(instance, fields, value) {
var idx = 0, lastIndex = fields.length - 1, field;
for (;idx < lastIndex; ++idx) {
field = fields[idx];
if (!(field in instance)) {
instance[field] = {};
}
instance = instance[field];
}
instance[fields[lastIndex]] = value;
}
ui.plugin(DropDownList);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, List = ui.List, Select = ui.Select, support = kendo.support, placeholderSupported = support.placeholder, activeElement = kendo._activeElement, keys = kendo.keys, ns = ".kendoComboBox", CLICK = "click" + ns, MOUSEDOWN = "mousedown" + ns, DISABLED = "disabled", READONLY = "readonly", CHANGE = "change", DEFAULT = "k-state-default", FOCUSED = "k-state-focused", STATEDISABLED = "k-state-disabled", ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", STATE_SELECTED = "k-state-selected", STATE_FILTER = "filter", STATE_ACCEPT = "accept", STATE_REBIND = "rebind", HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, NULL = null, proxy = $.proxy;
var ComboBox = Select.extend({
init: function(element, options) {
var that = this, text;
that.ns = ns;
options = $.isArray(options) ? {
dataSource: options
} : options;
Select.fn.init.call(that, element, options);
that._focusHandler = function() {
that.input.focus();
};
options = that.options;
element = that.element.on("focus" + ns, that._focusHandler);
options.placeholder = options.placeholder || element.attr("placeholder");
that._reset();
that._wrapper();
that._input();
that._tabindex(that.input);
that._popup();
that._accessors();
that._dataSource();
that._ignoreCase();
that._enable();
that._cascade();
that._aria();
that._oldIndex = that.selectedIndex = -1;
if (options.autoBind) {
that._filterSource();
} else {
text = options.text;
if (!text && that._isSelect) {
text = element.children(":selected").text();
}
if (text) {
that.input.val(text);
}
}
if (!text) {
that._placeholder();
}
kendo.notify(that);
},
options: {
name: "ComboBox",
enabled: true,
index: -1,
text: null,
value: null,
autoBind: true,
delay: 200,
dataTextField: "",
dataValueField: "",
minLength: 0,
height: 200,
highlightFirst: true,
template: "",
filter: "none",
placeholder: "",
suggest: false,
ignoreCase: true,
animation: {}
},
events: [ "open", "close", CHANGE, "select", "dataBinding", "dataBound", "cascade" ],
setOptions: function(options) {
Select.fn.setOptions.call(this, options);
this._template();
this._accessors();
this._aria();
},
current: function(li) {
var that = this, current = that._current;
if (li === undefined) {
return current;
}
if (current) {
current.removeClass(STATE_SELECTED);
}
Select.fn.current.call(that, li);
},
destroy: function() {
var that = this;
that.input.off(ns);
that.element.off(ns);
that._inputWrapper.off(ns);
Select.fn.destroy.call(that);
},
_editable: function(options) {
var that = this, disable = options.disable, readonly = options.readonly, wrapper = that._inputWrapper.off(ns), input = that.element.add(that.input.off(ns)), arrow = that._arrow.parent().off(CLICK + " " + MOUSEDOWN);
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
input.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false);
arrow.on(CLICK, function() {
that.toggle();
}).on(MOUSEDOWN, function(e) {
e.preventDefault();
});
that.input.on("keydown" + ns, proxy(that._keydown, that)).on("focus" + ns, function() {
wrapper.addClass(FOCUSED);
that._placeholder(false);
}).on("blur" + ns, function() {
wrapper.removeClass(FOCUSED);
clearTimeout(that._typing);
if (that.options.text !== that.input.val()) {
that.text(that.text());
}
that._placeholder();
that._blur();
that.element.blur();
});
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
input.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
open: function() {
var that = this, serverFiltering = that.dataSource.options.serverFiltering;
if (that.popup.visible()) {
return;
}
if (!that.ul[0].firstChild || that._state === STATE_ACCEPT && !serverFiltering) {
that._open = true;
that._state = STATE_REBIND;
that._filterSource();
} else {
that.popup.open();
that._scroll(that._current);
}
},
refresh: function() {
var that = this, ul = that.ul[0], options = that.options, state = that._state, data = that._data(), length = data.length, value, open, custom;
that.trigger("dataBinding");
ul.innerHTML = kendo.render(that.template, data);
that._height(length);
if (that.popup.visible()) {
that.popup._position();
}
if (that._isSelect) {
if (state === STATE_REBIND) {
that._state = "";
value = that.value();
}
custom = that._option;
that._option = undefined;
that._options(data);
if (custom && custom[0].selected) {
that._custom(custom.val());
}
}
if (length) {
if (options.highlightFirst) {
that.current($(ul.firstChild));
}
if (options.suggest && that.input.val() && that._request !== undefined) {
that.suggest($(ul.firstChild));
}
}
if (state !== STATE_FILTER && !that._fetch) {
that._selectItem();
}
if (that._open) {
that._open = false;
open = !!length;
if (that._typing && that.input[0] !== activeElement()) {
open = false;
}
that.toggle(open);
that._typing = undefined;
}
if (that._touchScroller) {
that._touchScroller.reset();
}
that._makeUnselectable();
that._hideBusy();
that._bound = true;
that.trigger("dataBound");
},
search: function(word) {
word = typeof word === "string" ? word : this.text();
var that = this, length = word.length, options = that.options, ignoreCase = options.ignoreCase, filter = options.filter, field = options.dataTextField;
clearTimeout(that._typing);
if (length >= options.minLength) {
that._state = STATE_FILTER;
if (filter === "none") {
that._filter(word);
} else {
that._open = true;
that._filterSource({
value: ignoreCase ? word.toLowerCase() : word,
field: field,
operator: filter,
ignoreCase: ignoreCase
});
}
}
},
suggest: function(word) {
var that = this, element = that.input[0], value = that.text(), caret = List.caret(element), key = that._last, idx;
if (key == keys.BACKSPACE || key == keys.DELETE) {
that._last = undefined;
return;
}
word = word || "";
if (typeof word !== "string") {
idx = List.inArray(word[0], that.ul[0]);
if (idx > -1) {
word = that._text(that.dataSource.view()[idx]);
} else {
word = "";
}
}
if (caret <= 0) {
caret = value.toLowerCase().indexOf(word.toLowerCase()) + 1;
}
if (word) {
idx = word.toLowerCase().indexOf(value.toLowerCase());
if (idx > -1) {
value += word.substring(idx + value.length);
}
} else {
value = value.substring(0, caret);
}
if (value.length !== caret || !word) {
element.value = value;
List.selectText(element, caret, value.length);
}
},
text: function(text) {
text = text === null ? "" : text;
var that = this, textAccessor = that._text, input = that.input[0], ignoreCase = that.options.ignoreCase, loweredText = text, dataItem;
if (text !== undefined) {
dataItem = that.dataItem();
if (dataItem && textAccessor(dataItem) === text) {
that._triggerCascade();
return;
}
if (ignoreCase) {
loweredText = loweredText.toLowerCase();
}
that._select(function(data) {
data = textAccessor(data);
if (ignoreCase) {
data = (data + "").toLowerCase();
}
return data === loweredText;
});
if (that.selectedIndex < 0) {
that._custom(text);
input.value = text;
}
that._triggerCascade();
} else {
return input.value;
}
},
toggle: function(toggle) {
var that = this;
that._toggle(toggle);
},
value: function(value) {
var that = this, idx;
if (value !== undefined) {
if (value !== null) {
value = value.toString();
}
that._selectedValue = value;
if (!that._open && value && that._fetchItems(value)) {
return;
}
idx = that._index(value);
if (idx > -1) {
that.select(idx);
} else {
that.current(NULL);
that._custom(value);
that.text(value);
that._placeholder();
}
that._prev = that._old = that._accessor();
that._oldIndex = that.selectedIndex;
} else {
return that._accessor();
}
},
_accept: function(li) {
var that = this;
if (li) {
that._focus(li);
} else {
that.text(that.text());
that._change();
}
},
_custom: function(value) {
var that = this, element = that.element, custom = that._option;
if (that._state === STATE_FILTER) {
that._state = STATE_ACCEPT;
}
if (that._isSelect) {
if (!custom) {
custom = that._option = $("<option/>");
element.append(custom);
}
custom.text(value);
custom[0].selected = true;
} else {
element.val(value);
}
that._selectedValue = value;
},
_filter: function(word) {
var that = this, options = that.options, dataSource = that.dataSource, ignoreCase = options.ignoreCase, predicate = function(dataItem) {
var text = that._text(dataItem);
if (text !== undefined) {
text = text + "";
if (text !== "" && word === "") {
return false;
}
if (ignoreCase) {
text = text.toLowerCase();
}
return text.indexOf(word) === 0;
}
};
if (ignoreCase) {
word = word.toLowerCase();
}
if (!that.ul[0].firstChild) {
dataSource.one(CHANGE, function() {
if (dataSource.data()[0]) {
that.search(word);
}
}).fetch();
return;
}
if (that._highlight(predicate) !== -1) {
if (options.suggest && that._current) {
that.suggest(that._current);
}
that.open();
}
that._hideBusy();
},
_highlight: function(li) {
var that = this, idx;
if (li === undefined || li === null) {
return -1;
}
li = that._get(li);
idx = List.inArray(li[0], that.ul[0]);
if (idx == -1) {
if (that.options.highlightFirst && !that.text()) {
li = $(that.ul[0].firstChild);
} else {
li = NULL;
}
}
that.current(li);
return idx;
},
_input: function() {
var that = this, element = that.element.removeClass("k-input")[0], accessKey = element.accessKey, wrapper = that.wrapper, SELECTOR = "input.k-input", name = element.name || "", input;
if (name) {
name = 'name="' + name + '_input" ';
}
input = wrapper.find(SELECTOR);
if (!input[0]) {
wrapper.append('<span tabindex="-1" unselectable="on" class="k-dropdown-wrap k-state-default"><input ' + name + 'class="k-input" type="text" autocomplete="off"/><span tabindex="-1" unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-arrow-s">select</span></span></span>').append(that.element);
input = wrapper.find(SELECTOR);
}
input[0].style.cssText = element.style.cssText;
if (element.maxLength > -1) {
input[0].maxLength = element.maxLength;
}
input.addClass(element.className).val(element.value).css({
width: "100%",
height: element.style.height
}).attr({
role: "combobox",
"aria-expanded": false
}).show();
if (placeholderSupported) {
input.attr("placeholder", that.options.placeholder);
}
if (accessKey) {
element.accessKey = "";
input[0].accessKey = accessKey;
}
that._focused = that.input = input;
that._inputWrapper = $(wrapper[0].firstChild);
that._arrow = wrapper.find(".k-icon").attr({
role: "button",
tabIndex: -1
});
if (element.id) {
that._arrow.attr("aria-controls", that.ul[0].id);
}
},
_keydown: function(e) {
var that = this, key = e.keyCode;
that._last = key;
clearTimeout(that._typing);
if (key != keys.TAB && !that._move(e)) {
that._search();
}
},
_placeholder: function(show) {
if (placeholderSupported) {
return;
}
var that = this, input = that.input, placeholder = that.options.placeholder, value;
if (placeholder) {
value = that.value();
if (show === undefined) {
show = !value;
}
input.toggleClass("k-readonly", show);
if (!show) {
if (!value) {
placeholder = "";
} else {
return;
}
}
input.val(placeholder);
}
},
_search: function() {
var that = this;
that._typing = setTimeout(function() {
var value = that.text();
if (that._prev !== value) {
that._prev = value;
that.search(value);
}
}, that.options.delay);
},
_select: function(li) {
var that = this, text, value, data = that._data(), idx = that._highlight(li);
that.selectedIndex = idx;
if (idx !== -1) {
if (that._state === STATE_FILTER) {
that._state = STATE_ACCEPT;
}
that._current.addClass(STATE_SELECTED);
data = data[idx];
text = that._text(data);
value = that._value(data);
that._prev = that.input[0].value = text;
that._accessor(value !== undefined ? value : text, idx);
that._selectedValue = that._accessor();
that._placeholder();
if (that._optionID) {
that._current.attr("aria-selected", true);
}
}
},
_wrapper: function() {
var that = this, element = that.element, wrapper = element.parent();
if (!wrapper.is("span.k-widget")) {
wrapper = element.hide().wrap("<span />").parent();
wrapper[0].style.cssText = element[0].style.cssText;
}
that.wrapper = wrapper.addClass("k-widget k-combobox k-header").addClass(element[0].className).css("display", "");
},
_clearSelection: function(parent, isFiltered) {
var that = this, hasValue = parent._selectedValue || parent.value(), custom = hasValue && parent.selectedIndex === -1;
if (isFiltered || !hasValue || custom) {
that.value("");
}
}
});
ui.plugin(ComboBox);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, List = ui.List, keys = kendo.keys, activeElement = kendo._activeElement, ObservableArray = kendo.data.ObservableArray, proxy = $.proxy, ID = "id", LI = "li", ACCEPT = "accept", FILTER = "filter", OPEN = "open", CLOSE = "close", CHANGE = "change", PROGRESS = "progress", SELECT = "select", NEXT = "nextSibling", PREV = "previousSibling", HIDE = ' style="display:none"', ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", FOCUSEDCLASS = "k-state-focused", HIDDENCLASS = "k-loading-hidden", HOVERCLASS = "k-state-hover", STATEDISABLED = "k-state-disabled", DISABLED = "disabled", READONLY = "readonly", ns = ".kendoMultiSelect", CLICK = "click" + ns, KEYDOWN = "keydown" + ns, MOUSEENTER = "mouseenter" + ns, MOUSELEAVE = "mouseleave" + ns, HOVEREVENTS = MOUSEENTER + " " + MOUSELEAVE, quotRegExp = /"/g, styles = [ "font-family", "font-size", "font-stretch", "font-style", "font-weight", "letter-spacing", "text-transform", "line-height" ];
var MultiSelect = List.extend({
init: function(element, options) {
var that = this, id;
that.ns = ns;
List.fn.init.call(that, element, options);
that._wrapper();
that._tagList();
that._input();
that._textContainer();
that._loader();
that._tabindex(that.input);
options = that.options;
element = that.element.attr("multiple", "multiple").hide();
if (!options.placeholder) {
options.placeholder = element.data("placeholder");
}
id = element.attr(ID);
if (id) {
that._tagID = id + "_tag_active";
id = id + "_taglist";
that.tagList.attr(ID, id);
}
that._aria(id);
that._dataSource();
that._ignoreCase();
that._accessors();
that._popup();
that._values = [];
that._dataItems = [];
that._reset();
that._enable();
that._placeholder();
if (options.autoBind) {
that.dataSource.fetch();
}
kendo.notify(that);
},
options: {
name: "MultiSelect",
enabled: true,
autoBind: true,
highlightFirst: true,
dataTextField: "",
dataValueField: "",
filter: "startswith",
ignoreCase: true,
minLength: 0,
delay: 100,
value: null,
maxSelectedItems: null,
itemTemplate: "",
tagTemplate: "",
placeholder: "",
height: 200,
animation: {}
},
events: [ OPEN, CLOSE, CHANGE, SELECT, "dataBinding", "dataBound" ],
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.options.autoBind) {
this.dataSource.fetch();
}
},
setOptions: function(options) {
List.fn.setOptions.call(this, options);
this._template();
this._accessors();
this._aria(this.tagList.attr(ID));
},
current: function(candidate) {
this.currentTag(null);
return List.fn.current.call(this, candidate);
},
currentTag: function(candidate) {
var that = this;
if (candidate !== undefined) {
if (that._currentTag) {
that._currentTag.removeClass(FOCUSEDCLASS).removeAttr(ID);
that.input.removeAttr("aria-activedescendant");
}
if (candidate) {
candidate.addClass(FOCUSEDCLASS).attr(ID, that._tagID);
that.input.attr("aria-activedescendant", that._tagID);
}
that._currentTag = candidate;
} else {
return that._currentTag;
}
},
dataItems: function() {
return this._dataItems;
},
destroy: function() {
var that = this, ns = that.ns;
that.wrapper.off(ns);
that.tagList.off(ns);
that.input.off(ns);
List.fn.destroy.call(that);
},
_editable: function(options) {
var that = this, disable = options.disable, readonly = options.readonly, wrapper = that.wrapper.off(ns), tagList = that.tagList.off(ns), input = that.element.add(that.input.off(ns));
if (!readonly && !disable) {
wrapper.removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover).on("mousedown" + ns, function(e) {
e.preventDefault();
if (e.target.className.indexOf("k-delete") == -1) {
that.open();
}
if (that.input[0] !== activeElement()) {
that.input.focus();
}
});
that.input.on(KEYDOWN, proxy(that._keydown, that)).on("paste" + ns, proxy(that._search, that)).on("focus" + ns, function() {
that._placeholder(false);
}).on("blur" + ns, function() {
clearTimeout(that._typing);
that._placeholder();
that.close();
if (that._state === FILTER) {
that._state = ACCEPT;
}
});
input.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false);
tagList.on(MOUSEENTER, LI, function() {
$(this).addClass(HOVERCLASS);
}).on(MOUSELEAVE, LI, function() {
$(this).removeClass(HOVERCLASS);
}).on(CLICK, ".k-delete", function(e) {
that._unselect($(e.target).closest(LI));
that._change();
that.close();
});
} else {
if (disable) {
wrapper.addClass(STATEDISABLED);
} else {
wrapper.removeClass(STATEDISABLED);
}
input.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
close: function() {
this.popup.close();
this.current(null);
},
open: function() {
var that = this;
if (!that.ul[0].firstChild || that._state === ACCEPT) {
that._state = "";
that._open = true;
that._filterSource();
} else if (that._visibleItems && that._allowSelection()) {
that.popup.open();
that.current(that.options.highlightFirst ? $(first(that.ul[0])) : null);
}
},
toggle: function(toggle) {
this[toggle ? OPEN : CLOSE]();
},
refresh: function() {
var that = this, li = null, length;
that.trigger("dataBinding");
length = that._render(that.dataSource.view());
that._height(length);
if (that._setInitialValues) {
that._setInitialValues = false;
that.value(that._initialValues);
}
if (that._open) {
that._open = false;
that.toggle(length);
}
if (that.popup.visible()) {
that.popup._position();
if (that.options.highlightFirst) {
li = $(first(that.ul[0]));
}
}
that.current(li);
if (that._touchScroller) {
that._touchScroller.reset();
}
that._makeUnselectable();
that._hideBusy();
that.trigger("dataBound");
},
search: function(word) {
var that = this, options = that.options, ignoreCase = options.ignoreCase, filter = options.filter, field = options.dataTextField;
clearTimeout(that._typing);
word = typeof word === "string" ? word : that.input.val();
if (word.length >= options.minLength) {
that._state = FILTER;
that._open = true;
that._filterSource({
value: ignoreCase ? word.toLowerCase() : word,
field: field,
operator: filter,
ignoreCase: ignoreCase
});
}
},
value: function(value) {
var that = this, tags = $(that.tagList[0].children), length = tags.length, dataItemIndex, idx = 0;
if (value === undefined) {
return that._values;
}
if (that._fetchItems(value)) {
return;
}
for (;idx < length; idx++) {
that._unselect(tags.eq(idx));
}
if (value !== null) {
value = $.isArray(value) || value instanceof ObservableArray ? value : [ value ];
for (idx = 0, length = value.length; idx < length; idx++) {
dataItemIndex = that._index(value[idx]);
if (dataItemIndex > -1) {
that._select(dataItemIndex);
}
}
that._old = that._values.slice();
}
},
_dataSource: function() {
var that = this, element = that.element, options = that.options, dataSource = options.dataSource || {};
dataSource = $.isArray(dataSource) ? {
data: dataSource
} : dataSource;
dataSource.select = element;
dataSource.fields = [ {
field: options.dataTextField
}, {
field: options.dataValueField
} ];
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._showBusy, that);
}
that.dataSource = kendo.data.DataSource.create(dataSource).bind(CHANGE, that._refreshHandler).bind(PROGRESS, that._progressHandler);
},
_fetchItems: function(value) {
var that = this;
if (!that._fetch && !that.ul[0].firstChild) {
that.dataSource.one(CHANGE, function() {
that.value(value);
that._fetch = false;
});
that._fetch = true;
that.dataSource.fetch();
return true;
}
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
setTimeout(function() {
that.value(that._initialValues);
});
};
that._form = form.on("reset", that._resetHandler);
}
},
_initValue: function() {
var that = this, value = that.options.value || that.element.val();
if (value === null) {
value = [];
}
that._old = that._initialValues = value;
that._setInitialValues = !!value[0];
},
_change: function() {
var that = this, value = that.value();
if (!compare(value, that._old)) {
that._old = value.slice();
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_click: function(e) {
var that = this, li = $(e.currentTarget);
if (!e.isDefaultPrevented()) {
if (that.trigger(SELECT, {
item: li
})) {
that.close();
return;
}
that._select(li);
that._change();
that.close();
}
},
_item: function(item, direction) {
item = item[direction]();
if (item[0] && !item.is(":visible")) {
item = this._item(item, direction);
}
return item;
},
_keydown: function(e) {
var that = this, key = e.keyCode, tag = that._currentTag, current = that._current, hasValue = that.input.val(), isRtl = kendo.support.isRtl(that.wrapper), visible = that.popup.visible();
if (key === keys.DOWN) {
e.preventDefault();
if (!visible) {
that.open();
return;
}
if (current) {
current = sibling(current[0], NEXT);
} else {
current = that.ul[0].firstChild;
}
if (current) {
that.current($(current));
}
} else if (key === keys.UP) {
if (visible) {
if (current) {
current = sibling(current[0], PREV);
} else {
current = that.ul[0].lastChild;
}
that.current($(current));
if (!that._current[0]) {
that.close();
}
}
e.preventDefault();
} else if (key === keys.LEFT && !isRtl || key === keys.RIGHT && isRtl) {
if (!hasValue) {
tag = tag ? tag.prev() : $(that.tagList[0].lastChild);
if (tag[0]) {
that.currentTag(tag);
}
}
} else if (key === keys.RIGHT && !isRtl || key === keys.LEFT && isRtl) {
if (!hasValue && tag) {
tag = tag.next();
that.currentTag(tag[0] ? tag : null);
}
} else if (key === keys.ENTER) {
if (visible) {
if (current) {
that._select(current);
}
that._change();
that.close();
e.preventDefault();
}
} else if (key === keys.ESC) {
if (visible) {
e.preventDefault();
} else {
that.currentTag(null);
}
that.close();
} else if (key === keys.HOME) {
if (visible) {
that.current($(first(that.ul[0])));
} else if (!hasValue) {
tag = that.tagList[0].firstChild;
if (tag) {
that.currentTag($(tag));
}
}
} else if (key === keys.END) {
if (visible) {
that.current($(last(that.ul[0])));
} else if (!hasValue) {
tag = that.tagList[0].lastChild;
if (tag) {
that.currentTag($(tag));
}
}
} else if ((key === keys.DELETE || key === keys.BACKSPACE) && !hasValue) {
if (key === keys.BACKSPACE && !tag) {
tag = $(that.tagList[0].lastChild);
}
if (tag && tag[0]) {
that._unselect(tag);
that._change();
that.close();
}
} else {
clearTimeout(that._typing);
that._search();
}
},
_hideBusy: function() {
var that = this;
clearTimeout(that._busy);
that.input.attr("aria-busy", false);
that._loading.addClass(HIDDENCLASS);
that._busy = null;
},
_showBusy: function() {
var that = this;
if (that._busy) {
return;
}
that._busy = setTimeout(function() {
that.input.attr("aria-busy", true);
that._loading.removeClass(HIDDENCLASS);
}, 100);
},
_placeholder: function(show) {
var that = this, input = that.input;
if (show === undefined) {
show = false;
if (input[0] !== activeElement()) {
show = !that._dataItems[0];
}
that.wrapper.removeClass(FOCUSEDCLASS);
} else {
that.wrapper.addClass(FOCUSEDCLASS);
}
input.toggleClass("k-readonly", show).val(show ? that.options.placeholder : "");
that._scale();
},
_scale: function() {
var that = this, wrapperWidth = that.wrapper.width(), span = that._span.text(that.input.val()), textWidth;
if (!wrapperWidth) {
span.appendTo(document.documentElement);
wrapperWidth = textWidth = span.width() + 25;
span.appendTo(this.wrapper);
} else {
textWidth = span.width() + 25;
}
that.input.width(textWidth > wrapperWidth ? wrapperWidth : textWidth);
},
_option: function(dataItem, selected) {
var option = "<option", dataText = this._text(dataItem), dataValue = this._value(dataItem);
if (dataValue !== undefined) {
dataValue += "";
if (dataValue.indexOf('"') !== -1) {
dataValue = dataValue.replace(quotRegExp, """);
}
option += ' value="' + dataValue + '"';
}
if (selected) {
option += ' selected="selected"';
}
option += ">";
if (dataText !== undefined) {
option += kendo.htmlEncode(dataText);
}
return option += "</option>";
},
_render: function(data) {
var that = this, length = data.length, template = that.itemTemplate, values = that._dataItems.slice(0), visibleItems = 0, idx = 0, options = "", html = "", dataItem, selected;
for (;idx < length; idx++) {
dataItem = data[idx];
selected = that._selected(values, dataItem);
html += template(dataItem, idx, selected);
options += that._option(dataItem, selected);
if (!selected) {
visibleItems += 1;
}
}
length = values.length;
if (length) {
for (idx = 0; idx < length; idx++) {
options += that._option(values[idx], true);
}
}
that.ul[0].innerHTML = html;
that.element.html(options);
that._visibleItems = visibleItems;
return visibleItems;
},
_selected: function(values, dataItem) {
var that = this, textAccessor = that._text, valueAccessor = that._value, value = valueAccessor(dataItem), length = values.length, selected = false, dataValue, idx = 0;
if (value === undefined) {
value = textAccessor(dataItem);
}
for (;idx < length; idx++) {
dataItem = values[idx];
dataValue = valueAccessor(dataItem);
if (dataValue === undefined) {
dataValue = textAccessor(dataItem);
}
if (dataValue !== undefined && dataValue === value) {
selected = true;
break;
}
}
if (selected) {
values.splice(idx, 1);
}
return selected;
},
_search: function() {
var that = this;
that._typing = setTimeout(function() {
var value = that.input.val();
if (that._prev !== value) {
that._scale();
that._prev = value;
that.search(value);
}
}, that.options.delay);
},
_allowSelection: function() {
var max = this.options.maxSelectedItems;
return max === null || max > this._values.length;
},
_select: function(li) {
var that = this, values = that._values, dataItem, idx;
if (!that._allowSelection()) {
return;
}
if (!isNaN(li)) {
idx = li;
that.ul[0].children[idx].style.display = "none";
} else {
idx = li.hide().data("idx");
}
that.element[0].children[idx].selected = true;
dataItem = that.dataSource.view()[idx];
that.tagList.append(that.tagTemplate(dataItem));
that._dataItems.push(dataItem);
values.push(that._dataValue(dataItem));
that._visibleItems -= 1;
that.currentTag(null);
that._placeholder();
that._height(that._visibleItems);
if (that._state === FILTER) {
that._state = ACCEPT;
}
},
_unselect: function(tag) {
var that = this, index = tag.index(), dataItem, value, options, option, length;
tag.remove();
that.currentTag(null);
that._values.splice(index, 1);
dataItem = that._dataItems.splice(index, 1)[0];
value = that._dataValue(dataItem);
index = that._index(value);
if (index !== -1) {
$(that.ul[0].children[index]).show();
that.element[0].children[index].selected = false;
that._visibleItems += 1;
that._height(that._visibleItems);
} else {
index = that.dataSource.view().length;
options = that.element[0].children;
length = options.length;
for (;index < length; index++) {
option = options[index];
if (option.value == value) {
option.selected = false;
break;
}
}
}
that._placeholder();
},
_template: function() {
var that = this, options = that.options, itemTemplate = options.itemTemplate, tagTemplate = options.tagTemplate, hasDataSource = options.dataSource, textTemplate;
if (that.element[0].length && !hasDataSource) {
options.dataTextField = options.dataTextField || "text";
options.dataValueField = options.dataValueField || "value";
}
textTemplate = kendo.template("#:" + kendo.expr(options.dataTextField, "data") + "#", {
useWithBlock: false
});
itemTemplate = itemTemplate ? kendo.template(itemTemplate) : textTemplate;
tagTemplate = tagTemplate ? kendo.template(tagTemplate) : textTemplate;
that.itemTemplate = function(data, idx, hide) {
return '<li tabindex="-1" role="option" data-idx="' + idx + '" unselectable="on" class="k-item"' + (hide ? HIDE : "") + ">" + itemTemplate(data) + "</li>";
};
that.tagTemplate = function(data) {
return '<li class="k-button"><span>' + tagTemplate(data) + '</span><span class="k-icon k-delete">delete</span></li>';
};
},
_input: function() {
var that = this, accessKey = that.element[0].accessKey, input = that._innerWrapper.children("input.k-input");
if (!input[0]) {
input = $('<input class="k-input" style="width: 25px" />').appendTo(that._innerWrapper);
}
that.element.removeAttr("accesskey");
that._focused = that.input = input.attr({
accesskey: accessKey,
role: "listbox",
"aria-expanded": false
});
},
_tagList: function() {
var that = this, tagList = that._innerWrapper.children("ul");
if (!tagList[0]) {
tagList = $('<ul role="listbox" unselectable="on" class="k-reset"/>').appendTo(that._innerWrapper);
}
that.tagList = tagList;
},
_loader: function() {
this._loading = $('<span class="k-icon k-loading ' + HIDDENCLASS + '"></span>').insertAfter(this.input);
},
_textContainer: function() {
var computedStyles = kendo.getComputedStyles(this.input[0], styles);
computedStyles.position = "absolute";
computedStyles.visibility = "hidden";
this._span = $("<span/>").css(computedStyles).appendTo(this.wrapper);
},
_wrapper: function() {
var that = this, element = that.element, wrapper = element.parent("span.k-multiselect");
if (!wrapper[0]) {
wrapper = element.wrap('<div class="k-widget k-multiselect k-header" />').parent();
wrapper[0].style.cssText = element[0].style.cssText;
$('<div class="k-multiselect-wrap k-floatwrap" />').insertBefore(element);
}
that.wrapper = wrapper.addClass(element[0].className).css("display", "");
that._innerWrapper = $(wrapper[0].firstChild);
}
});
function compare(a, b) {
var length;
if (a === null && b !== null || a !== null && b === null) {
return false;
}
length = a.length;
if (length !== b.length) {
return false;
}
while (length--) {
if (a[length] !== b[length]) {
return false;
}
}
return true;
}
function first(ul) {
var item = ul.firstChild;
if (item && item.style.display === "none") {
item = sibling(item, NEXT);
}
return item;
}
function last(ul) {
var item = ul.lastChild;
if (item && item.style.display === "none") {
item = sibling(item, PREV);
}
return item;
}
function sibling(item, direction) {
item = item[direction];
if (item && item.style.display === "none") {
item = sibling(item, direction);
}
return item;
}
ui.plugin(MultiSelect);
})(window.kendo.jQuery);
(function($, parseInt, undefined) {
// WARNING: removing the following jshint declaration and turning
// == into === to make JSHint happy will break functionality.
/*jshint eqnull:true */
var kendo = window.kendo, Class = kendo.Class, ui = kendo.ui, Widget = ui.Widget, KEYS = kendo.keys, BACKGROUNDCOLOR = "background-color", UNSELECTABLE = "unselectable", ITEMSELECTEDCLASS = "k-state-selected", SIMPLEPALETTE = "000000,7f7f7f,880015,ed1c24,ff7f27,fff200,22b14c,00a2e8,3f48cc,a349a4,ffffff,c3c3c3,b97a57,ffaec9,ffc90e,efe4b0,b5e61d,99d9ea,7092be,c8bfe7", WEBPALETTE = "FFFFFF,FFCCFF,FF99FF,FF66FF,FF33FF,FF00FF,CCFFFF,CCCCFF,CC99FF,CC66FF,CC33FF,CC00FF,99FFFF,99CCFF,9999FF,9966FF,9933FF,9900FF,FFFFCC,FFCCCC,FF99CC,FF66CC,FF33CC,FF00CC,CCFFCC,CCCCCC,CC99CC,CC66CC,CC33CC,CC00CC,99FFCC,99CCCC,9999CC,9966CC,9933CC,9900CC,FFFF99,FFCC99,FF9999,FF6699,FF3399,FF0099,CCFF99,CCCC99,CC9999,CC6699,CC3399,CC0099,99FF99,99CC99,999999,996699,993399,990099,FFFF66,FFCC66,FF9966,FF6666,FF3366,FF0066,CCFF66,CCCC66,CC9966,CC6666,CC3366,CC0066,99FF66,99CC66,999966,996666,993366,990066,FFFF33,FFCC33,FF9933,FF6633,FF3333,FF0033,CCFF33,CCCC33,CC9933,CC6633,CC3333,CC0033,99FF33,99CC33,999933,996633,993333,990033,FFFF00,FFCC00,FF9900,FF6600,FF3300,FF0000,CCFF00,CCCC00,CC9900,CC6600,CC3300,CC0000,99FF00,99CC00,999900,996600,993300,990000,66FFFF,66CCFF,6699FF,6666FF,6633FF,6600FF,33FFFF,33CCFF,3399FF,3366FF,3333FF,3300FF,00FFFF,00CCFF,0099FF,0066FF,0033FF,0000FF,66FFCC,66CCCC,6699CC,6666CC,6633CC,6600CC,33FFCC,33CCCC,3399CC,3366CC,3333CC,3300CC,00FFCC,00CCCC,0099CC,0066CC,0033CC,0000CC,66FF99,66CC99,669999,666699,663399,660099,33FF99,33CC99,339999,336699,333399,330099,00FF99,00CC99,009999,006699,003399,000099,66FF66,66CC66,669966,666666,663366,660066,33FF66,33CC66,339966,336666,333366,330066,00FF66,00CC66,009966,006666,003366,000066,66FF33,66CC33,669933,666633,663333,660033,33FF33,33CC33,339933,336633,333333,330033,00FF33,00CC33,009933,006633,003333,000033,66FF00,66CC00,669900,666600,663300,660000,33FF00,33CC00,339900,336600,333300,330000,00FF00,00CC00,009900,006600,003300,000000", APPLY_CANCEL = {
apply: "Apply",
cancel: "Cancel"
}, NS = ".kendoColorTools", CLICK_NS = "click" + NS, KEYDOWN_NS = "keydown" + NS, browser = kendo.support.browser, isIE8 = browser.msie && browser.version < 9;
var ColorSelector = Widget.extend({
init: function(element, options) {
var that = this, ariaId;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
that._value = options.value = parse(options.value);
ariaId = that._ariaId = options.ariaId;
if (ariaId) {
element.attr("aria-labelledby", ariaId);
}
if (options._standalone) {
that._triggerSelect = that._triggerChange;
}
},
options: {
value: null,
_standalone: true
},
events: [ "change", "select", "cancel" ],
color: function(value) {
if (value !== undefined) {
this._updateUI(this._value = parse(value));
}
return this._value;
},
value: function(color) {
color = this.color(color);
if (color) {
if (this.options.opacity) {
color = color.toCssRgba();
} else {
color = color.toCss();
}
}
return color || null;
},
enable: function(enable) {
if (arguments.length === 0) {
enable = true;
}
if (enable) {
$(".k-disabled-overlay", this.wrapper).remove();
} else {
this.wrapper.append("<div class='k-disabled-overlay'></div>");
}
this._onEnable(enable);
},
_select: function(color, nohooks) {
var prev = this._value;
color = this.color(color);
if (!nohooks) {
if (!color.equals(prev)) {
this.trigger("change", {
value: this.value()
});
} else if (!this._standalone) {
this.trigger("cancel");
}
}
},
_triggerSelect: function(color) {
triggerEvent(this, "select", color);
},
_triggerChange: function(color) {
triggerEvent(this, "change", color);
},
destroy: function() {
if (this.element) {
this.element.off(NS);
}
if (this.wrapper) {
this.wrapper.off(NS).find("*").off(NS);
}
this.wrapper = null;
Widget.fn.destroy.call(this);
},
_updateUI: $.noop,
_selectOnHide: function() {
return null;
},
_cancel: function() {
this.trigger("cancel");
}
});
function triggerEvent(self, type, color) {
color = parse(color);
if (color && !color.equals(self.color())) {
if (type == "change") {
// UI is already updated. setting _value directly
// rather than calling self.color(color) to avoid an
// endless loop.
self._value = color;
}
if (color.a != 1) {
color = color.toCssRgba();
} else {
color = color.toCss();
}
self.trigger(type, {
value: color
});
}
}
var ColorPalette = ColorSelector.extend({
init: function(element, options) {
var that = this;
ColorSelector.fn.init.call(that, element, options);
element = that.wrapper = that.element;
options = that.options;
var colors = options.palette;
if (colors == "websafe") {
colors = WEBPALETTE;
options.columns = 18;
} else if (colors == "basic") {
colors = SIMPLEPALETTE;
}
if (typeof colors == "string") {
colors = colors.split(",");
}
if ($.isArray(colors)) {
colors = $.map(colors, function(x) {
return parse(x);
});
}
element.addClass("k-widget k-colorpalette").append($(that._template({
colors: colors,
tileSize: options.tileSize,
value: that._value,
id: options.ariaId
}))).on(CLICK_NS, ".k-item", function(ev) {
that._select($(ev.currentTarget).find("div").css(BACKGROUNDCOLOR));
}).find("*").attr(UNSELECTABLE, "on").end().attr("tabIndex", 0).on(KEYDOWN_NS, bind(that._keydown, that));
var tileSize = options.tileSize, width, height;
if (tileSize) {
if (/number|string/.test(typeof tileSize)) {
width = height = parseFloat(tileSize);
} else if (typeof tileSize == "object") {
width = parseFloat(tileSize.width);
height = parseFloat(tileSize.height);
} else {
throw new Error("Unsupported value for the 'tileSize' argument");
}
element.find(".k-item").css({
width: width,
height: height
});
}
if (options.columns) {
element.css("width", options.columns * (width || 14));
}
},
focus: function() {
this.wrapper.focus();
},
options: {
name: "ColorPalette",
columns: 10,
tileSize: null,
palette: "basic"
},
_onEnable: function(enable) {
if (enable) {
this.wrapper.removeAttr("tabIndex");
} else {
this.wrapper.attr("tabIndex", 0);
}
},
_keydown: function(e) {
var selected, that = this, wrapper = that.wrapper, items = wrapper.find(".k-item"), current = items.filter("." + ITEMSELECTEDCLASS).get(0), keyCode = e.keyCode;
if (keyCode == KEYS.LEFT) {
selected = relative(items, current, -1);
} else if (keyCode == KEYS.RIGHT) {
selected = relative(items, current, 1);
} else if (keyCode == KEYS.DOWN) {
selected = relative(items, current, that.options.columns);
} else if (keyCode == KEYS.UP) {
selected = relative(items, current, -that.options.columns);
} else if (keyCode == KEYS.ENTER) {
preventDefault(e);
if (current) {
this._select($("div", current).css(BACKGROUNDCOLOR));
}
} else if (keyCode == KEYS.ESC) {
this._cancel();
}
if (selected) {
preventDefault(e);
selected = $(selected);
$(current).removeClass(ITEMSELECTEDCLASS).removeAttr("aria-selected");
selected.addClass(ITEMSELECTEDCLASS).attr("aria-selected", true);
try {
var color = parse(selected.find("div").css(BACKGROUNDCOLOR));
that._triggerSelect(color);
} catch (ex) {}
}
},
_updateUI: function(color) {
var that = this, el = null;
that.wrapper.find(".k-item." + ITEMSELECTEDCLASS).removeClass(ITEMSELECTEDCLASS).removeAttr("aria-selected");
that.wrapper.find(".k-item div").each(function() {
var c = parse($(this).css(BACKGROUNDCOLOR));
if (c && c.equals(color)) {
el = this.parentNode;
}
});
$(el).addClass(ITEMSELECTEDCLASS).attr("aria-selected", true);
},
_template: kendo.template('<ul class="k-palette k-reset">' + "# for (var i = 0; i < colors.length; i++) { #" + '<li #=(id && i === 0) ? "id=\\""+id+"\\" aria-selected=\\"true\\"" : "" # class="k-item #= colors[i].equals(value) ? "' + ITEMSELECTEDCLASS + '" : "" #" aria-label="#= colors[i].toCss() #">' + '<div style="background-color:#= colors[i].toCss() #"></div>' + "</li>" + "# } #" + "</ul>")
});
var FlatColorPicker = ColorSelector.extend({
init: function(element, options) {
var that = this;
ColorSelector.fn.init.call(that, element, options);
options = that.options;
element = that.element;
that.wrapper = element.addClass("k-widget k-flatcolorpicker").append(that._template(options)).find("*").attr(UNSELECTABLE, "on").end();
if (isIE8) {
// IE filters require absolute URLs
that._applyIEFilter();
}
that._hueElements = $(".k-hsv-rectangle, .k-transparency-slider .k-slider-track", element);
that._selectedColor = $(".k-selected-color-display", element);
that._colorAsText = $("input.k-color-value", element);
that._sliders();
that._hsvArea();
that._updateUI(that._value || new _RGB(1, 0, 0, 1));
element.find("input.k-color-value").on(KEYDOWN_NS, function(ev) {
if (ev.keyCode == KEYS.ENTER) {
try {
var color = parse(this.value);
var val = that.color();
$(this).removeClass("k-state-error");
that._select(color, !color.equals(val));
} catch (ex) {
$(this).addClass("k-state-error");
}
}
}).end().on(CLICK_NS, ".k-controls button.apply", function() {
// calling select for the currently displayed
// color will trigger the "change" event.
that._select(that._getHSV());
}).on(CLICK_NS, ".k-controls button.cancel", function() {
// but on cancel, we simply select the previous
// value (again, triggers "change" event).
that._updateUI(that.color());
that._cancel();
});
},
destroy: function() {
this._hueSlider.destroy();
if (this._opacitySlider) {
this._opacitySlider.destroy();
}
this._hueSlider = this._opacitySlider = this._hsvRect = this._hsvHandle = this._hueElements = this._selectedColor = this._colorAsText = null;
ColorSelector.fn.destroy.call(this);
},
options: {
name: "FlatColorPicker",
opacity: false,
buttons: false,
input: true,
preview: true,
messages: APPLY_CANCEL
},
_applyIEFilter: function() {
var track = this.element.find(".k-hue-slider .k-slider-track")[0], url = track.currentStyle.backgroundImage;
url = url.replace(/^url\([\'\"]?|[\'\"]?\)$/g, "");
track.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url + "', sizingMethod='scale')";
},
_sliders: function() {
var that = this, element = that.element;
function hueChange(e) {
that._updateUI(that._getHSV(e.value, null, null, null));
}
that._hueSlider = element.find(".k-hue-slider").kendoSlider({
min: 0,
max: 359,
tickPlacement: "none",
showButtons: false,
slide: hueChange,
change: hueChange
}).data("kendoSlider");
function opacityChange(e) {
that._updateUI(that._getHSV(null, null, null, e.value / 100));
}
that._opacitySlider = element.find(".k-transparency-slider").kendoSlider({
min: 0,
max: 100,
tickPlacement: "none",
showButtons: false,
slide: opacityChange,
change: opacityChange
}).data("kendoSlider");
},
_hsvArea: function() {
var that = this, element = that.element, hsvRect = element.find(".k-hsv-rectangle"), hsvHandle = hsvRect.find(".k-draghandle").attr("tabIndex", 0).on(KEYDOWN_NS, bind(that._keydown, that));
function update(x, y) {
var offset = this.offset, dx = x - offset.left, dy = y - offset.top, rw = this.width, rh = this.height;
dx = dx < 0 ? 0 : dx > rw ? rw : dx;
dy = dy < 0 ? 0 : dy > rh ? rh : dy;
that._svChange(dx / rw, 1 - dy / rh);
}
that._hsvEvents = new kendo.UserEvents(hsvRect, {
global: true,
press: function(e) {
this.offset = kendo.getOffset(hsvRect);
this.width = hsvRect.width();
this.height = hsvRect.height();
hsvHandle.focus();
update.call(this, e.x.location, e.y.location);
},
start: function() {
hsvRect.addClass("k-dragging");
hsvHandle.focus();
},
move: function(e) {
e.preventDefault();
update.call(this, e.x.location, e.y.location);
},
end: function() {
hsvRect.removeClass("k-dragging");
}
});
that._hsvRect = hsvRect;
that._hsvHandle = hsvHandle;
},
_onEnable: function(enable) {
this._hueSlider.enable(enable);
if (this._opacitySlider) {
this._opacitySlider.enable(enable);
}
this.wrapper.find("input").attr("disabled", !enable);
var handle = this._hsvRect.find(".k-draghandle");
if (enable) {
handle.attr("tabIndex", 0);
} else {
handle.removeAttr("tabIndex");
}
},
_keydown: function(ev) {
var that = this;
function move(prop, d) {
var c = that._getHSV();
c[prop] += d * (ev.shiftKey ? .01 : .05);
if (c[prop] < 0) {
c[prop] = 0;
}
if (c[prop] > 1) {
c[prop] = 1;
}
that._updateUI(c);
preventDefault(ev);
}
function hue(d) {
var c = that._getHSV();
c.h += d * (ev.shiftKey ? 1 : 5);
if (c.h < 0) {
c.h = 0;
}
if (c.h > 359) {
c.h = 359;
}
that._updateUI(c);
preventDefault(ev);
}
switch (ev.keyCode) {
case KEYS.LEFT:
if (ev.ctrlKey) {
hue(-1);
} else {
move("s", -1);
}
break;
case KEYS.RIGHT:
if (ev.ctrlKey) {
hue(1);
} else {
move("s", 1);
}
break;
case KEYS.UP:
move(ev.ctrlKey && that._opacitySlider ? "a" : "v", 1);
break;
case KEYS.DOWN:
move(ev.ctrlKey && that._opacitySlider ? "a" : "v", -1);
break;
case KEYS.ENTER:
that._select(that._getHSV());
break;
case KEYS.F2:
that.wrapper.find("input.k-color-value").focus().select();
break;
case KEYS.ESC:
that._cancel();
break;
}
},
focus: function() {
this._hsvHandle.focus();
},
_getHSV: function(h, s, v, a) {
var rect = this._hsvRect, width = rect.width(), height = rect.height(), handlePosition = this._hsvHandle.position();
if (h == null) {
h = this._hueSlider.value();
}
if (s == null) {
s = handlePosition.left / width;
}
if (v == null) {
v = 1 - handlePosition.top / height;
}
if (a == null) {
a = this._opacitySlider ? this._opacitySlider.value() / 100 : 1;
}
return new _HSV(h, s, v, a);
},
_svChange: function(s, v) {
var color = this._getHSV(null, s, v, null);
this._updateUI(color);
},
_updateUI: function(color) {
var that = this, rect = that._hsvRect;
if (!color) {
return;
}
that._selectedColor.css(BACKGROUNDCOLOR, color.toDisplay());
that._colorAsText.val(that._opacitySlider ? color.toCssRgba() : color.toCss());
that._triggerSelect(color);
color = color.toHSV();
that._hsvHandle.css({
// saturation is 0 on the left side, full (1) on the right
left: color.s * rect.width() + "px",
// value is 0 on the bottom, full on the top.
top: (1 - color.v) * rect.height() + "px"
});
that._hueElements.css(BACKGROUNDCOLOR, new _HSV(color.h, 1, 1, 1).toCss());
that._hueSlider.value(color.h);
if (that._opacitySlider) {
that._opacitySlider.value(100 * color.a);
}
},
_selectOnHide: function() {
return this.options.buttons ? null : this._getHSV();
},
_template: kendo.template("# if (preview) { #" + '<div class="k-selected-color"><div class="k-selected-color-display"><input spellcheck="false" class="k-color-value" #= !data.input ? \'style="visibility: hidden;"\' : "" #></div></div>' + "# } #" + '<div class="k-hsv-rectangle"><div class="k-hsv-gradient"></div><div class="k-draghandle"></div></div>' + '<input class="k-hue-slider" />' + "# if (opacity) { #" + '<input class="k-transparency-slider" />' + "# } #" + "# if (buttons) { #" + '<div class="k-controls"><button class="k-button apply">#: messages.apply #</button> <button class="k-button cancel">#: messages.cancel #</button></div>' + "# } #")
});
/* -----[ color utils ]----- */
function hex(n, width, pad) {
if (!pad) {
pad = "0";
}
n = n.toString(16);
while (width > n.length) {
n = "0" + n;
}
return n;
}
function fixed(n) {
return parseFloat((+n).toFixed(3));
}
var Color = Class.extend({
toHSV: function() {
return this;
},
toRGB: function() {
return this;
},
toHex: function() {
return this.toBytes().toHex();
},
toBytes: function() {
return this;
},
toCss: function() {
return "#" + this.toHex();
},
toCssRgba: function() {
var rgb = this.toBytes();
return "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + fixed(this.a) + ")";
},
toDisplay: function() {
if (isIE8) {
return this.toCss();
}
return this.toCssRgba();
},
equals: function(c) {
return c === this || c !== null && this.toHex() == parse(c).toHex();
},
diff: function(c2) {
if (c2 == null) {
return NaN;
}
var c1 = this.toBytes();
c2 = c2.toBytes();
return Math.sqrt(Math.pow((c1.r - c2.r) * .3, 2) + Math.pow((c1.g - c2.g) * .59, 2) + Math.pow((c1.b - c2.b) * .11, 2));
},
clone: function() {
var c = this.toBytes();
if (c === this) {
c = new _Bytes(c.r, c.g, c.b, c.a);
}
return c;
}
});
var _RGB = Color.extend({
init: function(r, g, b, a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
},
toHSV: function() {
var min, max, delta, h, s, v;
var r = this.r, g = this.g, b = this.b;
min = Math.min(r, g, b);
max = Math.max(r, g, b);
v = max;
delta = max - min;
if (delta === 0) {
return new _HSV(0, 0, v, this.a);
}
if (max !== 0) {
s = delta / max;
if (r == max) {
h = (g - b) / delta;
} else if (g == max) {
h = 2 + (b - r) / delta;
} else {
h = 4 + (r - g) / delta;
}
h *= 60;
if (h < 0) {
h += 360;
}
} else {
s = 0;
h = -1;
}
return new _HSV(h, s, v, this.a);
},
toBytes: function() {
return new _Bytes(this.r * 255, this.g * 255, this.b * 255, this.a);
}
});
var _Bytes = _RGB.extend({
init: function(r, g, b, a) {
this.r = Math.round(r);
this.g = Math.round(g);
this.b = Math.round(b);
this.a = a;
},
toRGB: function() {
return new _RGB(this.r / 255, this.g / 255, this.b / 255, this.a);
},
toHSV: function() {
return this.toRGB().toHSV();
},
toHex: function() {
return hex(this.r, 2) + hex(this.g, 2) + hex(this.b, 2);
},
toBytes: function() {
return this;
}
});
var _HSV = Color.extend({
init: function(h, s, v, a) {
this.h = h;
this.s = s;
this.v = v;
this.a = a;
},
toRGB: function() {
var h = this.h, s = this.s, v = this.v;
var i, r, g, b, f, p, q, t;
if (s === 0) {
r = g = b = v;
} else {
h /= 60;
i = Math.floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default:
r = v;
g = p;
b = q;
break;
}
}
return new _RGB(r, g, b, this.a);
},
toBytes: function() {
return this.toRGB().toBytes();
}
});
function parse(color, nothrow) {
if (color == null || color == "transparent") {
return null;
}
if (color instanceof Color) {
return color;
}
var m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
if (m) {
return new _Bytes(parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16), 1);
}
m = /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color);
if (m) {
return new _Bytes(parseInt(m[1] + m[1], 16), parseInt(m[2] + m[2], 16), parseInt(m[3] + m[3], 16), 1);
}
m = /^rgb\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/.exec(color);
if (m) {
return new _Bytes(parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), 1);
}
m = /^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)/.exec(color);
if (m) {
return new _Bytes(parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10), parseFloat(m[4]));
}
m = /^rgb\(\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*\)/.exec(color);
if (m) {
return new _RGB(parseFloat(m[1]) / 100, parseFloat(m[2]) / 100, parseFloat(m[3]) / 100, 1);
}
m = /^rgba\(\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9]*\.?[0-9]+)%\s*,\s*([0-9.]+)\s*\)/.exec(color);
if (m) {
return new _RGB(parseFloat(m[1]) / 100, parseFloat(m[2]) / 100, parseFloat(m[3]) / 100, parseFloat(m[4]));
}
if (!nothrow) {
throw new Error("Cannot parse color: " + color);
}
return undefined;
}
function relative(array, element, delta) {
array = Array.prototype.slice.call(array);
var n = array.length;
var pos = array.indexOf(element);
if (pos < 0) {
return delta < 0 ? array[n - 1] : array[0];
}
pos += delta;
if (pos < 0) {
pos += n;
} else {
pos %= n;
}
return array[pos];
}
/* -----[ The ColorPicker widget ]----- */
var ColorPicker = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
options = that.options;
element = that.element;
var value;
if (element.val()) {
value = parse(element.val(), true);
} else {
value = parse(options.value, true);
}
that._value = options.value = value;
var content = that.wrapper = $(that._template(options));
element.hide().after(content);
if (element.is("input")) {
element.appendTo(content);
}
that.enable(!element.attr("disabled"));
var accesskey = element.attr("accesskey");
if (accesskey) {
element.attr("accesskey", null);
content.attr("accesskey", accesskey);
}
that.bind("activate", function(ev) {
if (!ev.isDefaultPrevented()) {
that.toggle();
}
});
that._updateUI(value);
},
destroy: function() {
this.wrapper.add("*").off(NS);
if (this._popup) {
this._selector.destroy();
this._popup.destroy();
}
this._selector = this._popup = this.wrapper = null;
Widget.fn.destroy.call(this);
},
enable: function(enable) {
var that = this, wrapper = that.wrapper, innerWrapper = wrapper.children(".k-picker-wrap"), icon = innerWrapper.find(".k-select");
if (arguments.length === 0) {
enable = true;
}
that.element.attr("disabled", !enable);
wrapper.attr("disabled", !enable);
icon.off(NS).on("mousedown" + NS, preventDefault);
if (enable) {
wrapper.removeClass("k-state-disabled").attr("tabIndex", 0).on("mouseenter" + NS, function() {
innerWrapper.addClass("k-state-hover");
}).on("mouseleave" + NS, function() {
innerWrapper.removeClass("k-state-hover");
}).on("focus" + NS, function() {
innerWrapper.addClass("k-state-focused");
}).on("blur" + NS, function() {
innerWrapper.removeClass("k-state-focused");
}).on(KEYDOWN_NS, bind(that._keydown, that)).on(CLICK_NS, ".k-icon", bind(that.toggle, that)).on(CLICK_NS, that.options.toolIcon ? ".k-tool-icon" : ".k-selected-color", function() {
that.trigger("activate");
});
} else {
wrapper.addClass("k-state-disabled").removeAttr("tabIndex").add("*", wrapper).off(NS);
}
},
_template: kendo.template('<span class="k-widget k-colorpicker k-header">' + '<span class="k-picker-wrap k-state-default">' + "# if (toolIcon) { #" + '<span class="k-tool-icon #= toolIcon #">' + '<span class="k-selected-color"></span>' + "</span>" + "# } else { #" + '<span class="k-selected-color"></span>' + "# } #" + '<span class="k-select" unselectable="on">' + '<span class="k-icon k-i-arrow-s" unselectable="on"></span>' + "</span>" + "</span>" + "</span>"),
options: {
name: "ColorPicker",
palette: null,
columns: 10,
toolIcon: null,
value: null,
messages: APPLY_CANCEL,
opacity: false,
buttons: true,
preview: true
},
events: [ "activate", "change", "select", "open", "close" ],
open: function() {
this._getPopup().open();
},
close: function() {
this._getPopup().close();
},
toggle: function() {
this._getPopup().toggle();
},
color: ColorSelector.fn.color,
value: ColorSelector.fn.value,
_select: ColorSelector.fn._select,
_triggerSelect: ColorSelector.fn._triggerSelect,
_isInputTypeColor: function() {
var el = this.element[0];
return /^input$/i.test(el.tagName) && /^color$/i.test(el.type);
},
_updateUI: function(value) {
if (value) {
if (this._isInputTypeColor() || value.a == 1) {
// seems that input type="color" doesn't support opacity
// in colors; the only accepted format is hex #RRGGBB
this.element.val(value.toCss());
} else {
this.element.val(value.toCssRgba());
}
}
this._triggerSelect(value);
this.wrapper.find(".k-selected-color").css(BACKGROUNDCOLOR, value ? value.toDisplay() : "transparent");
},
_keydown: function(ev) {
var key = ev.keyCode;
if (this._getPopup().visible()) {
if (key == KEYS.ESC) {
this._selector._cancel();
} else {
this._selector._keydown(ev);
}
preventDefault(ev);
} else if (key == KEYS.ENTER || key == KEYS.DOWN) {
this.open();
preventDefault(ev);
}
},
_getPopup: function() {
var that = this, popup = that._popup;
if (!popup) {
var options = this.options;
var selectorType;
if (options.palette) {
selectorType = ColorPalette;
} else {
selectorType = FlatColorPicker;
}
options._standalone = false;
var selector = this._selector = new selectorType($("<div />").appendTo(document.body), options);
that._popup = popup = selector.wrapper.kendoPopup({
anchor: that.wrapper
}).data("kendoPopup");
selector.bind({
select: function(ev) {
that._updateUI(parse(ev.value));
},
change: function() {
that._select(selector.color());
that.close();
},
cancel: function() {
that.close();
}
});
popup.bind({
close: function(ev) {
if (that.trigger("close")) {
ev.preventDefault();
return;
}
var color = selector._selectOnHide();
if (!color) {
that.wrapper.focus();
that._updateUI(that.color());
} else {
that._select(color);
}
},
open: function(ev) {
if (that.trigger("open")) {
ev.preventDefault();
}
},
activate: function() {
selector._select(that.color(), true);
selector.focus();
}
});
}
return popup;
}
});
function preventDefault(ev) {
ev.preventDefault();
}
function bind(callback, obj) {
return function() {
return callback.apply(obj, arguments);
};
}
ui.plugin(ColorPalette);
ui.plugin(FlatColorPicker);
ui.plugin(ColorPicker);
kendo.parseColor = parse;
kendo.Color = {
fromBytes: function(r, g, b, a) {
return new _Bytes(r, g, b, a != null ? a : 1);
},
fromRGB: function(r, g, b, a) {
return new _RGB(r, g, b, a != null ? a : 1);
},
fromHSV: function(h, s, v, a) {
return new _HSV(h, s, v, a != null ? a : 1);
}
};
})(jQuery, parseInt);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, proxy = $.proxy, extend = $.extend, grep = $.grep, map = $.map, inArray = $.inArray, ACTIVE = "k-state-selected", ASC = "asc", DESC = "desc", CHANGE = "change", INIT = "init", POPUP = "kendoPopup", FILTERMENU = "kendoFilterMenu", MENU = "kendoMenu", NS = ".kendoColumnMenu", nameSpecialCharRegExp = /(\[|\]|\$|\.|\:|\+)/g, Widget = ui.Widget;
function trim(text) {
return $.trim(text).replace(/ /gi, "");
}
var ColumnMenu = Widget.extend({
init: function(element, options) {
var that = this, link;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
that.owner = options.owner;
that.dataSource = options.dataSource;
that.field = element.attr(kendo.attr("field"));
link = element.find(".k-header-column-menu");
if (!link[0]) {
link = element.prepend('<a class="k-header-column-menu" href="#"><span class="k-icon k-i-arrowhead-s"/></a>').find(".k-header-column-menu");
}
that.link = link.attr("tabindex", -1).on("click" + NS, proxy(that._click, that));
that.wrapper = $('<div class="k-column-menu"/>');
},
_init: function() {
var that = this, options = that.options;
that.wrapper.html(kendo.template(template)({
ns: kendo.ns,
messages: options.messages,
sortable: options.sortable,
filterable: options.filterable,
columns: that._ownerColumns(),
showColumns: options.columns
}));
that.popup = that.wrapper[POPUP]({
anchor: that.link,
open: proxy(that._open, that),
activate: proxy(that._activate, that),
close: that.options.closeCallback
}).data(POPUP);
that._menu();
that._sort();
that._columns();
that._filter();
that.trigger(INIT, {
field: that.field,
container: that.wrapper
});
},
events: [ INIT ],
options: {
name: "ColumnMenu",
messages: {
sortAscending: "Sort Ascending",
sortDescending: "Sort Descending",
filter: "Filter",
columns: "Columns"
},
columns: true,
sortable: true,
filterable: true
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
if (that.filterMenu) {
that.filterMenu.destroy();
}
that.dataSource.unbind("refresh", that._refreshHandler);
if (that.options.columns) {
that.owner.unbind("columnShow", that._updateColumnsMenuHandler);
that.owner.unbind("columnHide", that._updateColumnsMenuHandler);
}
if (that.menu) {
that.menu.element.off(NS);
that.menu.destroy();
}
that.wrapper.off(NS);
if (that.popup) {
that.popup.destroy();
}
that.link.off(NS);
},
close: function() {
this.menu.close();
this.popup.close();
this.popup.element.off("keydown" + NS);
},
_click: function(e) {
e.preventDefault();
e.stopPropagation();
if (!this.popup) {
this._init();
}
this.popup.toggle();
},
_open: function() {
var that = this;
$(".k-column-menu").not(that.wrapper).each(function() {
$(this).data(POPUP).close();
});
that.popup.element.on("keydown" + NS, function(e) {
if (e.keyCode == kendo.keys.ESC) {
that.close();
}
});
},
_activate: function() {
this.menu.element.focus();
},
_ownerColumns: function() {
var columns = this.owner.columns, menuColumns = grep(columns, function(col) {
var result = true, title = trim(col.title || "");
if (col.menu === false || !col.field && !title.length) {
result = false;
}
return result;
});
return map(menuColumns, function(col) {
return {
field: col.field || col.title,
title: col.title || col.field,
hidden: col.hidden,
index: inArray(col, columns)
};
});
},
_menu: function() {
this.menu = this.wrapper.children()[MENU]({
orientation: "vertical",
closeOnClick: false
}).data(MENU);
},
_sort: function() {
var that = this;
if (that.options.sortable) {
that.refresh();
that._refreshHandler = proxy(that.refresh, that);
that.dataSource.bind(CHANGE, that._refreshHandler);
that.menu.bind("select", function(e) {
var item = $(e.item), dir;
if (item.hasClass("k-sort-asc")) {
dir = ASC;
} else if (item.hasClass("k-sort-desc")) {
dir = DESC;
}
if (!dir) {
return;
}
item.parent().find(".k-sort-" + (dir == ASC ? DESC : ASC)).removeClass(ACTIVE);
that._sortDataSource(item, dir);
that.close();
});
}
},
_sortDataSource: function(item, dir) {
var that = this, sortable = that.options.sortable, dataSource = that.dataSource, idx, length, sort = dataSource.sort() || [];
if (item.hasClass(ACTIVE) && sortable && sortable.allowUnsort !== false) {
item.removeClass(ACTIVE);
dir = undefined;
} else {
item.addClass(ACTIVE);
}
if (sortable === true || sortable.mode === "single") {
sort = [ {
field: that.field,
dir: dir
} ];
} else {
for (idx = 0, length = sort.length; idx < length; idx++) {
if (sort[idx].field === that.field) {
sort.splice(idx, 1);
break;
}
}
sort.push({
field: that.field,
dir: dir
});
}
dataSource.sort(sort);
},
_columns: function() {
var that = this;
if (that.options.columns) {
that._updateColumnsMenu();
that._updateColumnsMenuHandler = proxy(that._updateColumnsMenu, that);
that.owner.bind([ "columnHide", "columnShow" ], that._updateColumnsMenuHandler);
that.menu.bind("select", function(e) {
var item = $(e.item), input, index, column, columns = that.owner.columns, field;
if (!item.parent().closest("li.k-columns-item")[0]) {
return;
}
input = item.find(":checkbox");
if (input.attr("disabled")) {
return;
}
field = input.attr(kendo.attr("field"));
column = grep(columns, function(column) {
return column.field == field || column.title == field;
})[0];
index = inArray(column, columns);
if (column.hidden === true) {
that.owner.showColumn(index);
} else {
that.owner.hideColumn(index);
}
});
}
},
_updateColumnsMenu: function() {
var attr = "[" + kendo.attr("field") + "=", columns = this._ownerColumns(), allselector = map(columns, function(col) {
return attr + '"' + col.field.replace(nameSpecialCharRegExp, "\\$1") + '"]';
}).join(","), visible = grep(columns, function(field) {
return !field.hidden;
}), selector = map(visible, function(col) {
return attr + '"' + col.field.replace(nameSpecialCharRegExp, "\\$1") + '"]';
}).join(",");
this.wrapper.find(allselector).prop("checked", false);
this.wrapper.find(selector).prop("checked", true).prop("disabled", visible.length == 1);
},
_filter: function() {
var that = this, options = that.options;
if (options.filterable !== false) {
that.filterMenu = that.wrapper.find(".k-filterable")[FILTERMENU](extend(true, {}, {
appendToElement: true,
dataSource: options.dataSource,
values: options.values,
field: that.field
}, options.filterable)).data(FILTERMENU);
}
},
refresh: function() {
var that = this, sort = that.options.dataSource.sort() || [], descriptor, field = that.field, idx, length;
that.wrapper.find(".k-sort-asc, .k-sort-desc").removeClass(ACTIVE);
for (idx = 0, length = sort.length; idx < length; idx++) {
descriptor = sort[idx];
if (field == descriptor.field) {
that.wrapper.find(".k-sort-" + descriptor.dir).addClass(ACTIVE);
}
}
}
});
var template = "<ul>" + "#if(sortable){#" + '<li class="k-item k-sort-asc"><span class="k-link"><span class="k-sprite k-i-sort-asc"></span>${messages.sortAscending}</span></li>' + '<li class="k-item k-sort-desc"><span class="k-link"><span class="k-sprite k-i-sort-desc"></span>${messages.sortDescending}</span></li>' + "#if(showColumns || filterable){#" + '<li class="k-separator"></li>' + "#}#" + "#}#" + "#if(showColumns){#" + '<li class="k-item k-columns-item"><span class="k-link"><span class="k-sprite k-i-columns"></span>${messages.columns}</span><ul>' + "#for (var col in columns) {#" + '<li><input type="checkbox" data-#=ns#field="#=columns[col].field#" data-#=ns#index="#=columns[col].index#"/>#=columns[col].title#</li>' + "#}#" + "</ul></li>" + "#if(filterable){#" + '<li class="k-separator"></li>' + "#}#" + "#}#" + "#if(filterable){#" + '<li class="k-item k-filter-item"><span class="k-link"><span class="k-sprite k-filter"></span>${messages.filter}</span><ul>' + '<li><div class="k-filterable"></div></li>' + "</ul></li>" + "#}#" + "</ul>";
ui.plugin(ColumnMenu);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, DataSource = kendo.data.DataSource, Groupable = ui.Groupable, tbodySupportsInnerHtml = kendo.support.tbodyInnerHtml, activeElement = kendo._activeElement, Widget = ui.Widget, keys = kendo.keys, isPlainObject = $.isPlainObject, extend = $.extend, map = $.map, grep = $.grep, isArray = $.isArray, inArray = $.inArray, proxy = $.proxy, isFunction = $.isFunction, isEmptyObject = $.isEmptyObject, math = Math, PROGRESS = "progress", ERROR = "error", DATA_CELL = ":not(.k-group-cell):not(.k-hierarchy-cell):visible", SELECTION_CELL_SELECTOR = "tbody>tr:not(.k-grouping-row):not(.k-detail-row):not(.k-group-footer) > td:not(.k-group-cell):not(.k-hierarchy-cell)", NAVROW = "tr:not(.k-footer-template):visible", NAVCELL = ":not(.k-group-cell):not(.k-hierarchy-cell):visible", FIRSTNAVITEM = NAVROW + ":first>" + NAVCELL + ":first", HEADERCELLS = "th.k-header:not(.k-group-cell,.k-hierarchy-cell)", GROUPINGDRAGGABLES = HEADERCELLS + ":visible[" + kendo.attr("field") + "]", GROUPINGFILTER = HEADERCELLS + "[" + kendo.attr("field") + "]", NS = ".kendoGrid", EDIT = "edit", SAVE = "save", REMOVE = "remove", DETAILINIT = "detailInit", FILTERMENUINIT = "filterMenuInit", COLUMNMENUINIT = "columnMenuInit", CHANGE = "change", COLUMNHIDE = "columnHide", COLUMNSHOW = "columnShow", SAVECHANGES = "saveChanges", DATABOUND = "dataBound", DETAILEXPAND = "detailExpand", DETAILCOLLAPSE = "detailCollapse", FOCUSED = "k-state-focused", SELECTED = "k-state-selected", COLUMNRESIZE = "columnResize", COLUMNREORDER = "columnReorder", CLICK = "click", HEIGHT = "height", TABINDEX = "tabIndex", FUNCTION = "function", STRING = "string", DELETECONFIRM = "Are you sure you want to delete this record?", formatRegExp = /(\}|\#)/gi, indicatorWidth = 3, templateHashRegExp = /#/gi, whitespaceRegExp = "[\\x20\\t\\r\\n\\f]", nonDataCellsRegExp = new RegExp("(^|" + whitespaceRegExp + ")" + "(k-group-cell|k-hierarchy-cell)" + "(" + whitespaceRegExp + "|$)"), COMMANDBUTTONTMPL = '<a class="k-button k-button-icontext #=className#" #=attr# href="\\#"><span class="#=iconClass# #=imageClass#"></span>#=text#</a>', isRtl = false;
var VirtualScrollable = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._refreshHandler = proxy(that.refresh, that);
that.setDataSource(options.dataSource);
that.wrap();
},
setDataSource: function(dataSource) {
var that = this;
if (that.dataSource) {
that.dataSource.unbind(CHANGE, that._refreshHandler);
}
that.dataSource = dataSource;
that.dataSource.bind(CHANGE, that._refreshHandler);
},
options: {
name: "VirtualScrollable",
itemHeight: $.noop
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.dataSource.unbind(CHANGE, that._refreshHandler);
that.wrapper.add(that.verticalScrollbar).off(NS);
if (that.drag) {
that.drag.destroy();
}
},
wrap: function() {
var that = this, // workaround for IE issue where scroll is not raised if container is same width as the scrollbar
scrollbar = kendo.support.scrollbar() + 1, element = that.element, wrapper;
element.css({
width: "auto",
overflow: "hidden"
}).css(isRtl ? "padding-left" : "padding-right", scrollbar);
that.content = element.children().first();
wrapper = that.wrapper = that.content.wrap('<div class="k-virtual-scrollable-wrap"/>').parent().bind("DOMMouseScroll" + NS + " mousewheel" + NS, proxy(that._wheelScroll, that));
if (kendo.support.kineticScrollNeeded) {
that.drag = new kendo.UserEvents(that.wrapper, {
global: true,
move: function(e) {
that.verticalScrollbar.scrollTop(that.verticalScrollbar.scrollTop() - e.y.delta);
wrapper.scrollLeft(wrapper.scrollLeft() - e.x.delta);
e.preventDefault();
}
});
}
that.verticalScrollbar = $('<div class="k-scrollbar k-scrollbar-vertical" />').css({
width: scrollbar
}).appendTo(element).bind("scroll" + NS, proxy(that._scroll, that));
},
_wheelScroll: function(e) {
var that = this, scrollTop = that.verticalScrollbar.scrollTop(), originalEvent = e.originalEvent, deltaY = originalEvent.wheelDeltaY, delta;
if (originalEvent.wheelDelta) {
// Webkit and IE
if (deltaY === undefined || deltaY) {
// IE does not have deltaY, thus always scroll (horizontal scrolling is treated as vertical)
delta = originalEvent.wheelDelta;
}
} else if (originalEvent.detail && originalEvent.axis === originalEvent.VERTICAL_AXIS) {
// Firefox and Opera
delta = -originalEvent.detail * 10;
}
if (delta) {
e.preventDefault();
that.verticalScrollbar.scrollTop(scrollTop + -delta);
}
},
_scroll: function(e) {
var that = this, scrollTop = e.currentTarget.scrollTop, dataSource = that.dataSource, rowHeight = that.itemHeight, skip = dataSource.skip() || 0, start = that._rangeStart || skip, height = that.element.innerHeight(), isScrollingUp = !!(that._scrollbarTop && that._scrollbarTop > scrollTop), firstItemIndex = math.max(math.floor(scrollTop / rowHeight), 0), lastItemIndex = math.max(firstItemIndex + math.floor(height / rowHeight), 0);
that._scrollTop = scrollTop - start * rowHeight;
that._scrollbarTop = scrollTop;
if (!that._fetch(firstItemIndex, lastItemIndex, isScrollingUp)) {
that.wrapper[0].scrollTop = that._scrollTop;
}
},
_fetch: function(firstItemIndex, lastItemIndex, scrollingUp) {
var that = this, dataSource = that.dataSource, itemHeight = that.itemHeight, take = dataSource.take(), rangeStart = that._rangeStart || dataSource.skip() || 0, currentSkip = math.floor(firstItemIndex / take) * take, fetching = false, prefetchAt = .33;
if (firstItemIndex < rangeStart) {
fetching = true;
rangeStart = math.max(0, lastItemIndex - take);
that._scrollTop = (firstItemIndex - rangeStart) * itemHeight;
that._page(rangeStart, take);
} else if (lastItemIndex >= rangeStart + take && !scrollingUp) {
fetching = true;
rangeStart = firstItemIndex;
that._scrollTop = itemHeight;
that._page(rangeStart, take);
} else if (!that._fetching) {
if (firstItemIndex < currentSkip + take - take * prefetchAt && firstItemIndex > take) {
dataSource.prefetch(currentSkip - take, take);
}
if (lastItemIndex > currentSkip + take * prefetchAt) {
dataSource.prefetch(currentSkip + take, take);
}
}
return fetching;
},
_page: function(skip, take) {
var that = this, dataSource = that.dataSource;
clearTimeout(that._timeout);
that._fetching = true;
that._rangeStart = skip;
if (dataSource.inRange(skip, take)) {
dataSource.range(skip, take);
} else {
kendo.ui.progress(that.wrapper.parent(), true);
that._timeout = setTimeout(function() {
dataSource.range(skip, take);
}, 100);
}
},
refresh: function() {
var that = this, html = "", maxHeight = 25e4, dataSource = that.dataSource, rangeStart = that._rangeStart, scrollbar = !kendo.support.kineticScrollNeeded ? kendo.support.scrollbar() : 0, wrapperElement = that.wrapper[0], totalHeight, idx, itemHeight;
kendo.ui.progress(that.wrapper.parent(), false);
clearTimeout(that._timeout);
itemHeight = that.itemHeight = that.options.itemHeight() || 0;
var addScrollBarHeight = wrapperElement.scrollWidth > wrapperElement.offsetWidth ? scrollbar : 0;
totalHeight = dataSource.total() * itemHeight + addScrollBarHeight;
for (idx = 0; idx < math.floor(totalHeight / maxHeight); idx++) {
html += '<div style="width:1px;height:' + maxHeight + 'px"></div>';
}
if (totalHeight % maxHeight) {
html += '<div style="width:1px;height:' + totalHeight % maxHeight + 'px"></div>';
}
that.verticalScrollbar.html(html);
wrapperElement.scrollTop = that._scrollTop;
if (that.drag) {
that.drag.cancel();
}
if (rangeStart && !that._fetching) {
// we are rebound from outside local range should be reset
that._rangeStart = dataSource.skip();
}
that._fetching = false;
}
});
function groupCells(count) {
return new Array(count + 1).join('<td class="k-group-cell"> </td>');
}
function stringifyAttributes(attributes) {
var attr, result = " ";
if (attributes) {
if (typeof attributes === STRING) {
return attributes;
}
for (attr in attributes) {
result += attr + '="' + attributes[attr] + '"';
}
}
return result;
}
var defaultCommands = {
create: {
text: "Add new record",
imageClass: "k-add",
className: "k-grid-add",
iconClass: "k-icon"
},
cancel: {
text: "Cancel changes",
imageClass: "k-cancel",
className: "k-grid-cancel-changes",
iconClass: "k-icon"
},
save: {
text: "Save changes",
imageClass: "k-update",
className: "k-grid-save-changes",
iconClass: "k-icon"
},
destroy: {
text: "Delete",
imageClass: "k-delete",
className: "k-grid-delete",
iconClass: "k-icon"
},
edit: {
text: "Edit",
imageClass: "k-edit",
className: "k-grid-edit",
iconClass: "k-icon"
},
update: {
text: "Update",
imageClass: "k-update",
className: "k-grid-update",
iconClass: "k-icon"
},
canceledit: {
text: "Cancel",
imageClass: "k-cancel",
className: "k-grid-cancel",
iconClass: "k-icon"
}
};
function heightAboveHeader(context) {
var top = 0;
$("> .k-grouping-header, > .k-grid-toolbar", context).each(function() {
top += this.offsetHeight;
});
return top;
}
function cursor(context, value) {
$("th, th .k-grid-filter, th .k-link", context).add(document.body).css("cursor", value);
}
function buildEmptyAggregatesObject(aggregates) {
var idx, length, aggregate = {}, fieldsMap = {};
if (!isEmptyObject(aggregates)) {
if (!isArray(aggregates)) {
aggregates = [ aggregates ];
}
for (idx = 0, length = aggregates.length; idx < length; idx++) {
aggregate[aggregates[idx].aggregate] = 0;
fieldsMap[aggregates[idx].field] = aggregate;
}
}
return fieldsMap;
}
function reorder(selector, sourceIndex, destIndex) {
var source = selector.eq(sourceIndex), dest = selector.eq(destIndex);
source[sourceIndex > destIndex ? "insertBefore" : "insertAfter"](dest);
}
function attachCustomCommandEvent(context, container, commands) {
var idx, length, command, commandName;
commands = !isArray(commands) ? [ commands ] : commands;
for (idx = 0, length = commands.length; idx < length; idx++) {
command = commands[idx];
if (isPlainObject(command) && command.click) {
commandName = command.name || command.text;
container.on(CLICK + NS, "a.k-grid-" + (commandName || "").replace(/\s/g, ""), {
commandName: commandName
}, proxy(command.click, context));
}
}
}
function visibleColumns(columns) {
return grep(columns, function(column) {
return !column.hidden;
});
}
function addHiddenStyle(attr) {
attr = attr || {};
var style = attr.style;
if (!style) {
style = "display:none";
} else {
style = style.replace(/((.*)?display)(.*)?:([^;]*)/i, "$1:none");
if (style === attr.style) {
style = style.replace(/(.*)?/i, "display:none;$1");
}
}
return extend({}, attr, {
style: style
});
}
function removeHiddenStyle(attr) {
attr = attr || {};
var style = attr.style;
if (style) {
attr.style = style.replace(/(display\s*:\s*none\s*;?)*/gi, "");
}
return attr;
}
function normalizeCols(table, visibleColumns, hasDetails, groups) {
var colgroup = table.find(">colgroup"), width, browser = kendo.support.browser, cols = map(visibleColumns, function(column) {
width = column.width;
if (width && parseInt(width, 10) !== 0) {
return kendo.format('<col style="width:{0}"/>', typeof width === STRING ? width : width + "px");
}
return "<col />";
});
if (hasDetails || colgroup.find(".k-hierarchy-col").length) {
cols.splice(0, 0, '<col class="k-hierarchy-col" />');
}
if (colgroup.length) {
colgroup.remove();
}
colgroup = $("<colgroup/>").append($(new Array(groups + 1).join('<col class="k-group-col">') + cols.join("")));
table.prepend(colgroup);
// fill gap after column hiding
if (browser.msie && browser.version == 8) {
table.css("display", "inline-table");
window.setTimeout(function() {
table.css("display", "");
}, 1);
}
}
function convertToObject(array) {
var result = {}, item, idx, length;
for (idx = 0, length = array.length; idx < length; idx++) {
item = array[idx];
result[item.value] = item.text;
}
return result;
}
function formatGroupValue(value, format, columnValues) {
var isForiegnKey = columnValues && columnValues.length && isPlainObject(columnValues[0]) && "value" in columnValues[0], groupValue = isForiegnKey ? convertToObject(columnValues)[value] : value;
groupValue = groupValue != null ? groupValue : "";
return format ? kendo.format(format, groupValue) : groupValue;
}
function setCellVisibility(cells, index, visible) {
var pad = 0, state, cell = cells[pad];
while (cell) {
state = visible ? true : cell.style.display !== "none";
if (state && !nonDataCellsRegExp.test(cell.className) && --index < 0) {
cell.style.display = visible ? "" : "none";
break;
}
cell = cells[++pad];
}
}
var Grid = Widget.extend({
init: function(element, options) {
var that = this;
options = isArray(options) ? {
dataSource: options
} : options;
Widget.fn.init.call(that, element, options);
isRtl = kendo.support.isRtl(element);
that._element();
that._aria();
that._columns(that.options.columns);
that._dataSource();
that._tbody();
that._pageable();
that._thead();
that._groupable();
that._toolbar();
that._setContentHeight();
that._templates();
that._navigatable();
that._selectable();
that._details();
that._editable();
that._attachCustomCommandsEvent();
if (that.options.autoBind) {
that.dataSource.fetch();
} else {
that._footer();
}
kendo.notify(that);
},
events: [ CHANGE, "dataBinding", "cancel", DATABOUND, DETAILEXPAND, DETAILCOLLAPSE, DETAILINIT, FILTERMENUINIT, COLUMNMENUINIT, EDIT, SAVE, REMOVE, SAVECHANGES, COLUMNRESIZE, COLUMNREORDER, COLUMNSHOW, COLUMNHIDE ],
setDataSource: function(dataSource) {
var that = this;
that.options.dataSource = dataSource;
that._dataSource();
that._pageable();
if (that.options.groupable) {
that._groupable();
}
that._thead();
if (that.virtualScrollable) {
that.virtualScrollable.setDataSource(that.options.dataSource);
}
if (that.options.autoBind) {
dataSource.fetch();
}
},
options: {
name: "Grid",
columns: [],
toolbar: null,
autoBind: true,
filterable: false,
scrollable: true,
sortable: false,
selectable: false,
navigatable: false,
pageable: false,
editable: false,
groupable: false,
rowTemplate: "",
altRowTemplate: "",
dataSource: {},
height: null,
resizable: false,
reorderable: false,
columnMenu: false,
detailTemplate: null
},
destroy: function() {
var that = this, element;
Widget.fn.destroy.call(that);
if (that.pager) {
that.pager.destroy();
}
if (that.groupable) {
that.groupable.destroy();
}
if (that.virtualScrollable) {
that.virtualScrollable.destroy();
}
that._destroyColumnAttachments();
that._destroyEditable();
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(PROGRESS, that._progressHandler).unbind(ERROR, that._errorHandler);
element = that.element.add(that.wrapper).add(that.table).add(that.thead).add(that.wrapper.find(">.k-grid-toolbar"));
if (that.content) {
element = element.add(that.content).add(that.content.find(">.k-virtual-scrollable-wrap"));
}
element.off(NS);
kendo.destroy(that.wrapper);
},
setOptions: function(options) {
var that = this;
Widget.fn.setOptions.call(this, options);
that._templates();
},
items: function() {
return this.tbody.children(":not(.k-grouping-row,.k-detail-row,.k-group-footer)");
},
_destroyColumnAttachments: function() {
var that = this;
that.thead.find("th").each(function() {
var th = $(this), filterMenu = th.data("kendoFilterMenu"), sortable = th.data("kendoSortable"), columnMenu = th.data("kendoColumnMenu");
if (filterMenu) {
filterMenu.destroy();
}
if (sortable) {
sortable.destroy();
}
if (columnMenu) {
columnMenu.destroy();
}
});
},
_attachCustomCommandsEvent: function() {
var that = this, columns = that.columns || [], command, idx, length;
for (idx = 0, length = columns.length; idx < length; idx++) {
command = columns[idx].command;
if (command) {
attachCustomCommandEvent(that, that.wrapper, command);
}
}
},
_aria: function() {
var id = this.element.attr("id") || "aria";
if (id) {
this._cellId = id + "_active_cell";
}
},
_element: function() {
var that = this, table = that.element;
if (!table.is("table")) {
if (that.options.scrollable) {
table = that.element.find("> .k-grid-content > table");
} else {
table = that.element.children("table");
}
if (!table.length) {
table = $("<table />").appendTo(that.element);
}
}
that.table = table.attr("cellspacing", 0).attr("role", that._hasDetails() ? "treegrid" : "grid");
that._wrapper();
},
_positionColumnResizeHandle: function(container) {
var that = this, scrollable = that.options.scrollable, resizeHandle = that.resizeHandle, left;
that.thead.on("mousemove" + NS, "th:not(.k-group-cell,.k-hierarchy-cell)", function(e) {
var th = $(this), clientX = e.clientX, winScrollLeft = $(window).scrollLeft(), position = th.offset().left + (!isRtl ? this.offsetWidth : 0);
if (clientX + winScrollLeft > position - indicatorWidth && clientX + winScrollLeft < position + indicatorWidth) {
if (!resizeHandle) {
resizeHandle = that.resizeHandle = $('<div class="k-resize-handle"/>');
container.append(resizeHandle);
}
if (!isRtl) {
left = this.offsetWidth;
th.prevAll(":visible").each(function() {
left += this.offsetWidth;
});
} else {
var headerWrap = th.closest(".k-grid-header-wrap"), ieCorrection = kendo.support.browser.msie ? headerWrap.scrollLeft() : 0, webkitCorrection = kendo.support.browser.webkit ? headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - headerWrap.scrollLeft() : 0, firefoxCorrection = kendo.support.browser.mozilla ? headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - (headerWrap[0].scrollWidth - headerWrap[0].offsetWidth - headerWrap.scrollLeft()) : 0;
left = th.position().left - webkitCorrection + firefoxCorrection - ieCorrection;
}
resizeHandle.css({
top: scrollable ? 0 : heightAboveHeader(that.wrapper),
left: left - indicatorWidth,
height: th.outerHeight(),
width: indicatorWidth * 3
}).data("th", th).show();
} else {
if (resizeHandle) {
resizeHandle.hide();
} else {
cursor(that.wrapper, "");
}
}
});
},
_resizable: function() {
var that = this, options = that.options, container, columnStart, columnWidth, gridWidth, col;
if (options.resizable) {
container = options.scrollable ? that.wrapper.find(".k-grid-header-wrap:first") : that.wrapper;
that._positionColumnResizeHandle(container);
container.kendoResizable({
handle: ".k-resize-handle",
hint: function(handle) {
return $('<div class="k-grid-resize-indicator" />').css({
height: handle.data("th").outerHeight() + that.tbody.attr("clientHeight")
});
},
start: function(e) {
var th = $(e.currentTarget).data("th"), index = $.inArray(th[0], th.parent().children(":visible")), contentTable = that.tbody.parent(), footer = that.footer || $();
cursor(that.wrapper, "col-resize");
if (options.scrollable) {
col = that.thead.parent().find("col:eq(" + index + ")").add(contentTable.children("colgroup").find("col:eq(" + index + ")")).add(footer.find("colgroup").find("col:eq(" + index + ")"));
} else {
col = contentTable.children("colgroup").find("col:eq(" + index + ")");
}
columnStart = e.x.location;
columnWidth = th.outerWidth();
gridWidth = that.tbody.outerWidth();
},
resize: function(e) {
var rtlMultiplier = isRtl ? -1 : 1, width = columnWidth + e.x.location * rtlMultiplier - columnStart * rtlMultiplier, footer = that.footer || $();
if (width > 10) {
col.css("width", width);
if (options.scrollable) {
that._footerWidth = gridWidth + e.x.location * rtlMultiplier - columnStart * rtlMultiplier;
that.tbody.parent().add(that.thead.parent()).add(footer.find("table")).css("width", that._footerWidth);
}
}
},
resizeend: function(e) {
var th = $(e.currentTarget).data("th"), newWidth = th.outerWidth(), column;
cursor(that.wrapper, "");
if (columnWidth != newWidth) {
column = that.columns[th.parent().find("th:not(.k-group-cell,.k-hierarchy-cell)").index(th)];
column.width = newWidth;
that.trigger(COLUMNRESIZE, {
column: column,
oldWidth: columnWidth,
newWidth: newWidth
});
}
that.resizeHandle.hide();
}
});
}
},
_draggable: function() {
var that = this;
if (that.options.reorderable) {
if (that._draggableInstance) {
that._draggableInstance.destroy();
}
that._draggableInstance = that.wrapper.kendoDraggable({
group: kendo.guid(),
filter: that.content ? ">.k-grid-header " + HEADERCELLS : ">table>.k-grid-header " + HEADERCELLS,
hint: function(target) {
return $('<div class="k-header k-drag-clue" />').css({
width: target.width(),
paddingLeft: target.css("paddingLeft"),
paddingRight: target.css("paddingRight"),
lineHeight: target.height() + "px",
paddingTop: target.css("paddingTop"),
paddingBottom: target.css("paddingBottom")
}).html(target.attr(kendo.attr("title")) || target.attr(kendo.attr("field")) || target.text()).prepend('<span class="k-icon k-drag-status k-denied" />');
}
}).data("kendoDraggable");
}
},
_reorderable: function() {
var that = this;
if (that.options.reorderable) {
that.wrapper.kendoReorderable({
draggable: that._draggableInstance,
change: function(e) {
var newIndex = inArray(that.columns[e.newIndex], that.columns), column = that.columns[e.oldIndex];
that.trigger(COLUMNREORDER, {
newIndex: newIndex,
oldIndex: inArray(column, that.columns),
column: column
});
that.reorderColumn(newIndex, column);
}
});
}
},
reorderColumn: function(destIndex, column) {
var that = this, sourceIndex = inArray(column, that.columns), colSourceIndex = inArray(column, visibleColumns(that.columns)), colDestIndex = inArray(that.columns[destIndex], visibleColumns(that.columns)), rows, idx, length, footer = that.footer || that.wrapper.find(".k-grid-footer");
if (sourceIndex === destIndex) {
return;
}
that.columns.splice(sourceIndex, 1);
that.columns.splice(destIndex, 0, column);
that._templates();
reorder(that.thead.prev().find("col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, colDestIndex);
if (that.options.scrollable) {
reorder(that.tbody.prev().find("col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, colDestIndex);
}
reorder(that.thead.find(".k-header:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
if (footer && footer.length) {
reorder(footer.find(".k-grid-footer-wrap>table>colgroup>col:not(.k-group-col,.k-hierarchy-col)"), colSourceIndex, colDestIndex);
reorder(footer.find(".k-footer-template>td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
}
rows = that.tbody.children(":not(.k-grouping-row,.k-detail-row)");
for (idx = 0, length = rows.length; idx < length; idx += 1) {
reorder(rows.eq(idx).find(">td:not(.k-group-cell,.k-hierarchy-cell)"), sourceIndex, destIndex);
}
},
cellIndex: function(td) {
return $(td).parent().children("td:not(.k-group-cell,.k-hierarchy-cell)").index(td);
},
_modelForContainer: function(container) {
container = $(container);
if (!container.is("tr") && this._editMode() !== "popup") {
container = container.closest("tr");
}
var id = container.attr(kendo.attr("uid"));
return this.dataSource.getByUid(id);
},
_editable: function() {
var that = this, selectable = that.selectable && that.selectable.options.multiple, editable = that.options.editable, handler = function() {
var target = activeElement(), cell = that._editContainer;
if (cell && !$.contains(cell[0], target) && cell[0] !== target && !$(target).closest(".k-animation-container").length) {
if (that.editable.end()) {
that.closeCell();
}
}
};
if (editable) {
var mode = that._editMode();
if (mode === "incell") {
if (editable.update !== false) {
that.wrapper.on(CLICK + NS, "tr:not(.k-grouping-row) > td", function(e) {
var td = $(this);
if (td.hasClass("k-hierarchy-cell") || td.hasClass("k-detail-cell") || td.hasClass("k-group-cell") || td.hasClass("k-edit-cell") || td.has("a.k-grid-delete").length || td.has("button.k-grid-delete").length || td.closest("tbody")[0] !== that.tbody[0] || $(e.target).is(":input")) {
return;
}
if (that.editable) {
if (that.editable.end()) {
if (selectable) {
$(activeElement()).blur();
}
that.closeCell();
that.editCell(td);
}
} else {
that.editCell(td);
}
}).on("focusin" + NS, function() {
clearTimeout(that.timer);
that.timer = null;
}).on("focusout" + NS, function() {
that.timer = setTimeout(handler, 1);
});
}
} else {
if (editable.update !== false) {
that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible a.k-grid-edit", function(e) {
e.preventDefault();
that.editRow($(this).closest("tr"));
});
}
}
if (editable.destroy !== false) {
that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible .k-grid-delete", function(e) {
e.preventDefault();
e.stopPropagation();
that.removeRow($(this).closest("tr"));
});
} else {
//Required for the MVC server wrapper delete button
that.wrapper.on(CLICK + NS, "tbody>tr:not(.k-detail-row,.k-grouping-row):visible button.k-grid-delete", function(e) {
if (!that._confirmation()) {
e.preventDefault();
}
});
}
}
},
editCell: function(cell) {
var that = this, column = that.columns[that.cellIndex(cell)], model = that._modelForContainer(cell);
if (model && (!model.editable || model.editable(column.field)) && !column.command && column.field) {
that._attachModelChange(model);
that._editContainer = cell;
that.editable = cell.addClass("k-edit-cell").kendoEditable({
fields: {
field: column.field,
format: column.format,
editor: column.editor,
values: column.values
},
model: model,
change: function(e) {
if (that.trigger(SAVE, {
values: e.values,
container: cell,
model: model
})) {
e.preventDefault();
}
}
}).data("kendoEditable");
cell.parent().addClass("k-grid-edit-row");
that.trigger(EDIT, {
container: cell,
model: model
});
}
},
_destroyEditable: function() {
var that = this;
var destroy = function() {
if (that.editable) {
that._detachModelChange();
that.editable.destroy();
that.editable = null;
that._editContainer = null;
}
};
if (that.editable) {
if (that._editMode() === "popup") {
that._editContainer.data("kendoWindow").bind("deactivate", destroy).close();
} else {
destroy();
}
}
},
_attachModelChange: function(model) {
var that = this;
that._modelChangeHandler = function(e) {
that._modelChange({
field: e.field,
model: this
});
};
model.bind("change", that._modelChangeHandler);
},
_detachModelChange: function() {
var that = this, container = that._editContainer, model = that._modelForContainer(container);
if (model) {
model.unbind(CHANGE, that._modelChangeHandler);
}
},
closeCell: function() {
var that = this, cell, id, column, model;
if (!that._editContainer) {
return;
}
cell = that._editContainer.removeClass("k-edit-cell");
id = cell.closest("tr").attr(kendo.attr("uid"));
column = that.columns[that.cellIndex(cell)];
model = that.dataSource.getByUid(id);
cell.parent().removeClass("k-grid-edit-row");
that._destroyEditable();
// editable should be destoryed before content of the container is changed
that._displayCell(cell, column, model);
if (cell.hasClass("k-dirty-cell")) {
$('<span class="k-dirty"/>').prependTo(cell);
}
},
_displayCell: function(cell, column, dataItem) {
var that = this, state = {
storage: {},
count: 0
}, settings = extend({}, kendo.Template, that.options.templateSettings), tmpl = kendo.template(that._cellTmpl(column, state), settings);
if (state.count > 0) {
tmpl = proxy(tmpl, state.storage);
}
cell.empty().html(tmpl(dataItem));
},
removeRow: function(row) {
var that = this, model, mode;
if (!that._confirmation()) {
return;
}
row = $(row).hide();
model = that._modelForContainer(row);
if (model && !that.trigger(REMOVE, {
row: row,
model: model
})) {
mode = that._editMode();
if (mode !== "incell") {
that.cancelRow();
}
that.dataSource.remove(model);
if (mode === "inline" || mode === "popup") {
that.dataSource.sync();
}
}
},
_editMode: function() {
var mode = "incell", editable = this.options.editable;
if (editable !== true) {
if (typeof editable == "string") {
mode = editable;
} else {
mode = editable.mode || mode;
}
}
return mode;
},
editRow: function(row) {
var that = this, model = that._modelForContainer(row), mode = that._editMode(), navigatable = that.options.navigatable, container;
that.cancelRow();
if (model) {
that._attachModelChange(model);
if (mode === "popup") {
that._createPopupEditor(model);
} else if (mode === "inline") {
that._createInlineEditor(row, model);
} else if (mode === "incell") {
$(row).children(DATA_CELL).each(function() {
var cell = $(this);
var column = that.columns[cell.index()];
model = that._modelForContainer(cell);
if (model && (!model.editable || model.editable(column.field)) && column.field) {
that.editCell(cell);
return false;
}
});
}
container = that._editContainer;
container.on(CLICK + NS, "a.k-grid-cancel", function(e) {
e.preventDefault();
e.stopPropagation();
if (that.trigger("cancel", {
container: container,
model: model
})) {
return;
}
var currentIndex = that.items().index($(that.current()).parent());
that.cancelRow();
if (navigatable) {
that.current(that.items().eq(currentIndex).children().filter(NAVCELL).first());
focusTable(that.table, true);
}
});
container.on(CLICK + NS, "a.k-grid-update", function(e) {
e.preventDefault();
e.stopPropagation();
that.saveRow();
});
}
},
_createPopupEditor: function(model) {
var that = this, html = "<div " + kendo.attr("uid") + '="' + model.uid + '"><div class="k-edit-form-container">', column, command, fields = [], idx, length, tmpl, updateText, cancelText, tempCommand, attr, editable = that.options.editable, template = editable.template, options = isPlainObject(editable) ? editable.window : {}, settings = extend({}, kendo.Template, that.options.templateSettings);
if (template) {
if (typeof template === STRING) {
template = window.unescape(template);
}
html += kendo.template(template, settings)(model);
for (idx = 0, length = that.columns.length; idx < length; idx++) {
column = that.columns[idx];
if (column.command) {
tempCommand = getCommand(column.command, "edit");
if (tempCommand) {
command = tempCommand;
}
}
}
} else {
for (idx = 0, length = that.columns.length; idx < length; idx++) {
column = that.columns[idx];
if (!column.command) {
html += '<div class="k-edit-label"><label for="' + column.field + '">' + (column.title || column.field || "") + "</label></div>";
if ((!model.editable || model.editable(column.field)) && column.field) {
fields.push({
field: column.field,
format: column.format,
editor: column.editor,
values: column.values
});
html += "<div " + kendo.attr("container-for") + '="' + column.field + '" class="k-edit-field"></div>';
} else {
var state = {
storage: {},
count: 0
};
tmpl = kendo.template(that._cellTmpl(column, state), settings);
if (state.count > 0) {
tmpl = proxy(tmpl, state.storage);
}
html += '<div class="k-edit-field">' + tmpl(model) + "</div>";
}
} else if (column.command) {
tempCommand = getCommand(column.command, "edit");
if (tempCommand) {
command = tempCommand;
}
}
}
}
if (command) {
if (isPlainObject(command)) {
if (command.text && isPlainObject(command.text)) {
updateText = command.text.update;
cancelText = command.text.cancel;
}
if (command.attr) {
attr = command.attr;
}
}
}
html += that._createButton({
name: "update",
text: updateText,
attr: attr
}) + that._createButton({
name: "canceledit",
text: cancelText,
attr: attr
});
html += "</div></div>";
var container = that._editContainer = $(html).appendTo(that.wrapper).eq(0).kendoWindow(extend({
modal: true,
resizable: false,
draggable: true,
title: "Edit",
visible: false,
close: function(e) {
if (e.userTriggered) {
if (that.trigger("cancel", {
container: container,
model: model
})) {
e.preventDefault();
return;
}
var currentIndex = that.items().index($(that.current()).parent());
that.cancelRow();
if (that.options.navigatable) {
that.current(that.items().eq(currentIndex).children().filter(NAVCELL).first());
focusTable(that.table, true);
}
}
}
}, options));
that.editable = that._editContainer.kendoEditable({
fields: fields,
model: model,
clearContainer: false
}).data("kendoEditable");
container.data("kendoWindow").center().open();
that.trigger(EDIT, {
container: container,
model: model
});
},
_createInlineEditor: function(row, model) {
var that = this, column, cell, command, fields = [];
row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
cell = $(this);
column = that.columns[that.cellIndex(cell)];
if (!column.command && column.field && (!model.editable || model.editable(column.field))) {
fields.push({
field: column.field,
format: column.format,
editor: column.editor,
values: column.values
});
cell.attr(kendo.attr("container-for"), column.field);
cell.empty();
} else if (column.command) {
command = getCommand(column.command, "edit");
if (command) {
cell.empty();
var updateText, cancelText, attr;
if (isPlainObject(command)) {
if (command.text && isPlainObject(command.text)) {
updateText = command.text.update;
cancelText = command.text.cancel;
}
if (command.attr) {
attr = command.attr;
}
}
$(that._createButton({
name: "update",
text: updateText,
attr: attr
}) + that._createButton({
name: "canceledit",
text: cancelText,
attr: attr
})).appendTo(cell);
}
}
});
that._editContainer = row;
that.editable = row.addClass("k-grid-edit-row").kendoEditable({
fields: fields,
model: model,
clearContainer: false
}).data("kendoEditable");
that.trigger(EDIT, {
container: row,
model: model
});
},
cancelRow: function() {
var that = this, container = that._editContainer, model;
if (container) {
model = that._modelForContainer(container);
that.dataSource.cancelChanges(model);
if (that._editMode() !== "popup") {
that._displayRow(container);
} else {
that._displayRow(that.items().filter("[" + kendo.attr("uid") + "=" + model.uid + "]"));
}
that._destroyEditable();
}
},
saveRow: function() {
var that = this, container = that._editContainer, model = that._modelForContainer(container), editable = that.editable;
if (container && editable && editable.end() && !that.trigger(SAVE, {
container: container,
model: model
})) {
that.dataSource.sync();
}
},
_displayRow: function(row) {
var that = this, model = that._modelForContainer(row);
if (model) {
row.replaceWith($((row.hasClass("k-alt") ? that.altRowTemplate : that.rowTemplate)(model)));
}
},
_showMessage: function(text) {
return window.confirm(text);
},
_confirmation: function() {
var that = this, editable = that.options.editable, confirmation = editable === true || typeof editable === STRING ? DELETECONFIRM : editable.confirmation;
return confirmation !== false && confirmation != null ? that._showMessage(confirmation) : true;
},
cancelChanges: function() {
this.dataSource.cancelChanges();
},
saveChanges: function() {
var that = this;
if ((that.editable && that.editable.end() || !that.editable) && !that.trigger(SAVECHANGES)) {
that.dataSource.sync();
}
},
addRow: function() {
var that = this, index, dataSource = that.dataSource, mode = that._editMode(), createAt = that.options.editable.createAt || "", pageSize = dataSource.pageSize(), view = dataSource.view() || [];
if (that.editable && that.editable.end() || !that.editable) {
if (mode != "incell") {
that.cancelRow();
}
index = dataSource.indexOf(view[0]);
if (createAt.toLowerCase() == "bottom") {
index += view.length;
if (pageSize && !dataSource.options.serverPaging && pageSize <= view.length) {
index -= 1;
}
}
if (index < 0) {
if (dataSource.page() > dataSource.totalPages()) {
index = (dataSource.page() - 1) * pageSize;
} else {
index = 0;
}
}
var model = dataSource.insert(index, {}), id = model.uid, row = that.table.find("tr[" + kendo.attr("uid") + "=" + id + "]"), cell = row.children("td:not(.k-group-cell,.k-hierarchy-cell)").eq(that._firstEditableColumnIndex(row));
if ((mode === "inline" || mode === "popup") && row.length) {
that.editRow(row);
} else if (cell.length) {
that.editCell(cell);
}
}
},
_firstEditableColumnIndex: function(container) {
var that = this, column, columns = that.columns, idx, length, model = that._modelForContainer(container);
for (idx = 0, length = columns.length; idx < length; idx++) {
column = columns[idx];
if (model && (!model.editable || model.editable(column.field)) && !column.command && column.field) {
return idx;
}
}
return -1;
},
_toolbar: function() {
var that = this, wrapper = that.wrapper, toolbar = that.options.toolbar, editable = that.options.editable, container;
if (toolbar) {
container = that.wrapper.find(".k-grid-toolbar");
if (!container.length) {
if (!isFunction(toolbar)) {
toolbar = typeof toolbar === STRING ? toolbar : that._toolbarTmpl(toolbar).replace(templateHashRegExp, "\\#");
toolbar = proxy(kendo.template(toolbar), that);
}
container = $('<div class="k-toolbar k-grid-toolbar" />').html(toolbar({})).prependTo(wrapper);
}
if (editable && editable.create !== false) {
container.on(CLICK + NS, ".k-grid-add", function(e) {
e.preventDefault();
that.addRow();
}).on(CLICK + NS, ".k-grid-cancel-changes", function(e) {
e.preventDefault();
that.cancelChanges();
}).on(CLICK + NS, ".k-grid-save-changes", function(e) {
e.preventDefault();
that.saveChanges();
});
}
}
},
_toolbarTmpl: function(commands) {
var that = this, idx, length, html = "";
if (isArray(commands)) {
for (idx = 0, length = commands.length; idx < length; idx++) {
html += that._createButton(commands[idx]);
}
}
return html;
},
_createButton: function(command) {
var template = command.template || COMMANDBUTTONTMPL, commandName = typeof command === STRING ? command : command.name || command.text, options = {
className: "k-grid-" + (commandName || "").replace(/\s/g, ""),
text: commandName,
imageClass: "",
attr: "",
iconClass: ""
};
if (!commandName && !(isPlainObject(command) && command.template)) {
throw new Error("Custom commands should have name specified");
}
if (isPlainObject(command)) {
if (command.className) {
command.className += " " + options.className;
}
if (commandName === "edit" && isPlainObject(command.text)) {
command = extend(true, {}, command);
command.text = command.text.edit;
}
if (command.attr && isPlainObject(command.attr)) {
command.attr = stringifyAttributes(command.attr);
}
options = extend(true, options, defaultCommands[commandName], command);
} else {
options = extend(true, options, defaultCommands[commandName]);
}
return kendo.template(template)(options);
},
_groupable: function() {
var that = this;
that.table.on(CLICK + NS, ".k-grouping-row .k-i-collapse, .k-grouping-row .k-i-expand", function(e) {
var element = $(this), group = element.closest("tr");
if (element.hasClass("k-i-collapse")) {
that.collapseGroup(group);
} else {
that.expandGroup(group);
}
e.preventDefault();
e.stopPropagation();
});
that._attachGroupable();
},
_attachGroupable: function() {
var that = this, wrapper = that.wrapper, groupable = that.options.groupable;
if (groupable) {
if (!wrapper.has("div.k-grouping-header")[0]) {
$("<div> </div>").addClass("k-grouping-header").prependTo(wrapper);
}
if (that.groupable) {
that.groupable.destroy();
}
that.groupable = new Groupable(wrapper, extend({}, groupable, {
draggable: that._draggableInstance,
groupContainer: ">div.k-grouping-header",
dataSource: that.dataSource,
draggableElements: that.content ? ">.k-grid-header " + GROUPINGDRAGGABLES : ">table>.k-grid-header " + GROUPINGDRAGGABLES,
filter: that.content ? ">.k-grid-header " + GROUPINGFILTER : ">table>.k-grid-header " + GROUPINGFILTER,
allowDrag: that.options.reorderable
}));
}
},
_selectable: function() {
var that = this, multi, cell, selectable = that.options.selectable;
if (selectable) {
multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;
cell = typeof selectable === STRING && selectable.toLowerCase().indexOf("cell") > -1;
that.selectable = new kendo.ui.Selectable(that.table, {
filter: ">" + (cell ? SELECTION_CELL_SELECTOR : "tbody>tr:not(.k-grouping-row,.k-detail-row,.k-group-footer)"),
aria: true,
multiple: multi,
change: function() {
that.trigger(CHANGE);
}
});
if (that.options.navigatable) {
that.table.on("keydown" + NS, function(e) {
var current = that.current();
if (e.keyCode === keys.SPACEBAR && e.target == that.table[0] && !current.is(".k-edit-cell,.k-header") && current.parent().is(":not(.k-grouping-row,.k-detail-row,.k-group-footer)")) {
e.preventDefault();
e.stopPropagation();
current = cell ? current : current.parent();
if (multi) {
if (!e.ctrlKey) {
that.selectable.clear();
} else {
if (current.hasClass(SELECTED)) {
current.removeClass(SELECTED);
that.trigger(CHANGE);
return;
}
}
} else {
that.selectable.clear();
}
that.selectable.value(current);
}
});
}
}
},
clearSelection: function() {
var that = this;
that.selectable.clear();
that.trigger(CHANGE);
},
select: function(items) {
var that = this, selectable = that.selectable;
items = $(items);
if (items.length) {
if (!selectable.options.multiple) {
selectable.clear();
items = items.first();
}
selectable.value(items);
return;
}
return selectable.value();
},
current: function(element) {
var that = this, scrollable = that.options.scrollable, current = that._current, table = that.table.add(that.thead.parent());
if (element !== undefined && element.length) {
if (!current || current[0] !== element[0]) {
if (current) {
current.removeClass(FOCUSED).removeAttr("id");
table.removeAttr("aria-activedescendant");
}
element.attr("id", that._cellId);
that._current = element.addClass(FOCUSED);
table.attr("aria-activedescendant", that._cellId);
if (element.length && scrollable) {
if ($.contains(that.content[0], element[0])) {
that._scrollTo(element.parent()[0], that.content[0]);
}
if (scrollable.virtual) {
that._scrollTo(element[0], that.content.find(">.k-virtual-scrollable-wrap")[0]);
} else {
that._scrollTo(element[0], that.content[0]);
}
}
}
}
return that._current;
},
_removeCurrent: function() {
if (this._current) {
this._current.removeClass(FOCUSED);
this._current = null;
}
},
_scrollTo: function(element, container) {
var elementToLowercase = element.tagName.toLowerCase(), isHorizontal = elementToLowercase === "td" || elementToLowercase === "th", elementOffset = element[isHorizontal ? "offsetLeft" : "offsetTop"], elementOffsetDir = element[isHorizontal ? "offsetWidth" : "offsetHeight"], containerScroll = container[isHorizontal ? "scrollLeft" : "scrollTop"], containerOffsetDir = container[isHorizontal ? "clientWidth" : "clientHeight"], bottomDistance = elementOffset + elementOffsetDir, result = 0;
if (containerScroll > elementOffset) {
result = elementOffset;
} else if (bottomDistance > containerScroll + containerOffsetDir) {
if (elementOffsetDir <= containerOffsetDir) {
result = bottomDistance - containerOffsetDir;
} else {
result = elementOffset;
}
} else {
result = containerScroll;
}
container[isHorizontal ? "scrollLeft" : "scrollTop"] = result;
},
_navigatable: function() {
var that = this, currentProxy = proxy(that.current, that), table = that.table, headerTable = that.thead.parent(), dataTable = table, isRtl = kendo.support.isRtl(that.element);
if (!that.options.navigatable) {
return;
}
if (that.options.scrollable) {
dataTable = table.add(headerTable);
headerTable.attr(TABINDEX, -1);
}
headerTable.on("keydown" + NS, function(e) {
if (e.altKey && e.keyCode == keys.DOWN) {
currentProxy().find(".k-grid-filter, .k-header-column-menu").click();
e.stopImmediatePropagation();
}
}).find("a.k-link").attr("tabIndex", -1);
table.attr(TABINDEX, math.max(table.attr(TABINDEX) || 0, 0)).on("mousedown" + NS + " keydown" + NS, ".k-detail-cell", function(e) {
if (e.target !== e.currentTarget) {
e.stopImmediatePropagation();
}
});
dataTable.on(kendo.support.touch ? "touchstart" + NS : "mousedown" + NS, NAVROW + ">" + NAVCELL, proxy(tableClick, that)).on("focus" + NS, function() {
var current = currentProxy();
if (current && current.is(":visible")) {
current.addClass(FOCUSED);
} else {
currentProxy($(this).find(FIRSTNAVITEM));
}
if (this == table[0]) {
headerTable.attr(TABINDEX, -1);
table.attr(TABINDEX, 0);
} else {
table.attr(TABINDEX, -1);
headerTable.attr(TABINDEX, 0);
}
}).on("focusout" + NS, function() {
var current = currentProxy();
if (current) {
current.removeClass(FOCUSED);
}
}).on("keydown" + NS, function(e) {
var key = e.keyCode, handled = false, canHandle = !e.isDefaultPrevented() && !$(e.target).is(":button,a,:input,a>.k-icon"), pageable = that.options.pageable, dataSource = that.dataSource, isInCell = that._editMode() == "incell", active, currentIndex, row, index, tableToFocus, shiftKey = e.shiftKey, browser = kendo.support.browser, current = currentProxy();
if (current && current.is("th")) {
canHandle = true;
}
if (canHandle && key == keys.UP) {
if (current) {
row = current.parent().prevAll(NAVROW).first();
if (!row[0]) {
tableToFocus = that.thead.parent();
focusTable(tableToFocus, true);
row = tableToFocus.find(NAVROW).first();
}
index = current.index();
current = row.children().eq(index);
if (!current[0] || !current.is(NAVCELL)) {
current = row.children(NAVCELL).first();
}
} else {
current = table.find(FIRSTNAVITEM);
}
handled = true;
currentProxy(current);
} else if (canHandle && key == keys.DOWN) {
if (current) {
row = current.parent().nextAll(NAVROW).first();
if (!row[0] && current.is("th")) {
focusTable(that.tbody.parent());
row = that.tbody.find(NAVROW).first();
}
index = current.index();
current = row.children().eq(index);
if (!current[0] || !current.is(NAVCELL)) {
current = row.children(NAVCELL).first();
}
} else {
current = table.find(FIRSTNAVITEM);
}
handled = true;
currentProxy(current);
} else if (canHandle && key == (isRtl ? keys.RIGHT : keys.LEFT)) {
currentProxy(current ? current.prevAll(DATA_CELL + ":first") : table.find(FIRSTNAVITEM));
handled = true;
} else if (canHandle && key == (isRtl ? keys.LEFT : keys.RIGHT)) {
if (current) {
if (current.next()[0]) {
current = current.nextAll(DATA_CELL + ":first");
}
} else {
current = table.find(FIRSTNAVITEM);
}
handled = true;
currentProxy(current);
} else if (canHandle && pageable && keys.PAGEDOWN == key) {
dataSource.page(dataSource.page() + 1);
handled = true;
} else if (canHandle && pageable && keys.PAGEUP == key) {
dataSource.page(dataSource.page() - 1);
handled = true;
} else if (key == keys.ENTER || keys.F2 == key) {
current = current ? current : table.find(FIRSTNAVITEM);
if (current.is("th")) {
current.find(".k-link").click();
handled = true;
} else if (current.parent().is(".k-master-row,.k-grouping-row")) {
current.parent().find(".k-icon:first").click();
handled = true;
} else {
var focusable = current.find(":focusable:first");
if (!current.hasClass("k-edit-cell") && focusable[0] && current.hasClass("k-state-focused")) {
focusable.focus();
handled = true;
} else if (that.options.editable && !$(e.target).is(":button,.k-button")) {
that._handleEditing(current);
handled = true;
}
}
} else if (keys.ESC == key) {
active = activeElement();
if (current && $.contains(current[0], active) && !current.hasClass("k-edit-cell") && !current.parent().hasClass("k-grid-edit-row")) {
focusTable(that.table[0], true);
handled = true;
} else if (that._editContainer && (!current || that._editContainer.has(current[0]) || current[0] === that._editContainer[0])) {
if (isInCell) {
that.closeCell();
} else {
currentIndex = that.items().index($(current).parent());
if (active) {
active.blur();
}
that.cancelRow();
if (currentIndex >= 0) {
that.current(that.items().eq(currentIndex).children().filter(NAVCELL).first());
}
}
if (browser.msie && browser.version < 9) {
document.body.focus();
}
focusTable(table, true);
handled = true;
}
} else if (keys.TAB == key) {
var cell;
current = $(current);
if (that.options.editable && isInCell) {
cell = $(activeElement()).closest(".k-edit-cell");
if (cell[0] && cell[0] !== current[0]) {
current = cell;
}
}
cell = shiftKey ? current.prevAll(DATA_CELL + ":first") : current.nextAll(":visible:first");
if (!cell.length) {
cell = current.parent()[shiftKey ? "prevAll" : "nextAll"]("tr:not(.k-grouping-row):not(.k-detail-row):visible:first").children(DATA_CELL + (shiftKey ? ":last" : ":first"));
}
if (!current.is("th") && cell.length && that.options.editable && isInCell) {
that._handleEditing(current, cell);
handled = true;
}
}
if (handled) {
//prevent browser scrolling
e.preventDefault();
//required in hierarchy
e.stopPropagation();
}
});
},
_handleEditing: function(current, next) {
var that = this, active = $(activeElement()), mode = that._editMode(), editContainer = that._editContainer, focusable, isEdited;
if (mode == "incell") {
isEdited = current.hasClass("k-edit-cell");
} else {
isEdited = current.parent().hasClass("k-grid-edit-row");
}
if (that.editable) {
if ($.contains(editContainer[0], active[0])) {
active.blur();
if (kendo.support.browser.opera) {
active.change();
}
}
if (!that.editable) {
focusTable(that.table);
return;
}
if (that.editable.end()) {
if (mode == "incell") {
that.closeCell();
} else {
that.saveRow();
isEdited = true;
}
} else {
if (mode == "incell") {
that.current(editContainer);
} else {
that.current(editContainer.children().filter(DATA_CELL).first());
}
focusable = editContainer.find(":focusable:first")[0];
if (focusable) {
focusable.focus();
}
return;
}
}
if (next) {
that.current(next);
}
focusTable(that.table, true);
if (!isEdited && !next || next) {
if (mode == "incell") {
that.editCell(that.current());
} else {
that.editRow(that.current().parent());
}
}
},
_wrapper: function() {
var that = this, table = that.table, height = that.options.height, wrapper = that.element;
if (!wrapper.is("div")) {
wrapper = wrapper.wrap("<div/>").parent();
}
that.wrapper = wrapper.addClass("k-grid k-widget");
/*
.attr(TABINDEX, math.max(table.attr(TABINDEX) || 0, 0));
table.removeAttr(TABINDEX);
*/
if (height) {
that.wrapper.css(HEIGHT, height);
table.css(HEIGHT, "auto");
}
},
_tbody: function() {
var that = this, table = that.table, tbody;
tbody = table.find(">tbody");
if (!tbody.length) {
tbody = $("<tbody/>").appendTo(table);
}
that.tbody = tbody;
},
_scrollable: function() {
var that = this, header, table, options = that.options, scrollable = options.scrollable, hasVirtualScroll = scrollable !== true && scrollable.virtual && !that.virtualScrollable, scrollbar = !kendo.support.kineticScrollNeeded || hasVirtualScroll ? kendo.support.scrollbar() : 0;
if (scrollable) {
header = that.wrapper.children(".k-grid-header");
if (!header[0]) {
header = $('<div class="k-grid-header" />').insertBefore(that.table);
}
// workaround for IE issue where scroll is not raised if container is same width as the scrollbar
header.css(isRtl ? "padding-left" : "padding-right", scrollable.virtual ? scrollbar + 1 : scrollbar);
table = $('<table role="grid" cellspacing="0" />');
table.append(that.thead);
header.empty().append($('<div class="k-grid-header-wrap" />').append(table));
that.content = that.table.parent();
if (that.content.is(".k-virtual-scrollable-wrap")) {
that.content = that.content.parent();
}
if (!that.content.is(".k-grid-content, .k-virtual-scrollable-wrap")) {
that.content = that.table.wrap('<div class="k-grid-content" />').parent();
}
if (hasVirtualScroll) {
that.virtualScrollable = new VirtualScrollable(that.content, {
dataSource: that.dataSource,
itemHeight: proxy(that._averageRowHeight, that)
});
}
that.scrollables = header.children(".k-grid-header-wrap");
// the footer may exists if rendered from the server
var footer = that.wrapper.find(".k-grid-footer"), webKitRtlCorrection = isRtl && kendo.support.browser.webkit ? scrollbar : 0;
if (footer.length) {
that.scrollables = that.scrollables.add(footer.children(".k-grid-footer-wrap"));
}
if (scrollable.virtual) {
that.content.find(">.k-virtual-scrollable-wrap").bind("scroll" + NS, function() {
that.scrollables.scrollLeft(this.scrollLeft + webKitRtlCorrection);
});
} else {
that.content.bind("scroll" + NS, function() {
that.scrollables.scrollLeft(this.scrollLeft + webKitRtlCorrection);
});
var touchScroller = kendo.touchScroller(that.content);
if (touchScroller && touchScroller.movable) {
touchScroller.movable.bind("change", function(e) {
that.scrollables.scrollLeft(-e.sender.x);
});
}
}
}
},
_setContentHeight: function() {
var that = this, options = that.options, height = that.wrapper.innerHeight(), header = that.wrapper.children(".k-grid-header"), scrollbar = kendo.support.scrollbar();
if (options.scrollable) {
height -= header.outerHeight();
if (that.pager) {
height -= that.pager.element.outerHeight();
}
if (options.groupable) {
height -= that.wrapper.children(".k-grouping-header").outerHeight();
}
if (options.toolbar) {
height -= that.wrapper.children(".k-grid-toolbar").outerHeight();
}
if (that.footerTemplate) {
height -= that.wrapper.children(".k-grid-footer").outerHeight();
}
var isGridHeightSet = function(el) {
var initialHeight, newHeight;
if (el[0].style.height) {
return true;
} else {
initialHeight = el.height();
}
el.height("auto");
newHeight = el.height();
if (initialHeight != newHeight) {
el.height("");
return true;
}
el.height("");
return false;
};
if (isGridHeightSet(that.wrapper)) {
// set content height only if needed
if (height > scrollbar * 2) {
// do not set height if proper scrollbar cannot be displayed
that.content.height(height);
} else {
that.content.height(scrollbar * 2 + 1);
}
}
}
},
_averageRowHeight: function() {
var that = this, rowHeight = that._rowHeight;
if (!that._rowHeight) {
that._rowHeight = rowHeight = that.table.outerHeight() / that.items().length;
that._sum = rowHeight;
that._measures = 1;
}
var currentRowHeight = that.table.outerHeight() / that.items().length;
if (rowHeight !== currentRowHeight) {
that._measures++;
that._sum += currentRowHeight;
that._rowHeight = that._sum / that._measures;
}
return rowHeight;
},
_dataSource: function() {
var that = this, options = that.options, pageable, dataSource = options.dataSource;
dataSource = isArray(dataSource) ? {
data: dataSource
} : dataSource;
if (isPlainObject(dataSource)) {
extend(dataSource, {
table: that.table,
fields: that.columns
});
pageable = options.pageable;
if (isPlainObject(pageable) && pageable.pageSize !== undefined) {
dataSource.pageSize = pageable.pageSize;
}
}
if (that.dataSource && that._refreshHandler) {
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(PROGRESS, that._progressHandler).unbind(ERROR, that._errorHandler);
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._requestStart, that);
that._errorHandler = proxy(that._error, that);
}
that.dataSource = DataSource.create(dataSource).bind(CHANGE, that._refreshHandler).bind(PROGRESS, that._progressHandler).bind(ERROR, that._errorHandler);
},
_error: function() {
this._progress(false);
},
_requestStart: function() {
this._progress(true);
},
_modelChange: function(e) {
var that = this, model = e.model, row = that.tbody.find("tr[" + kendo.attr("uid") + "=" + model.uid + "]"), cell, column, isAlt = row.hasClass("k-alt"), tmp, idx = that.items().index(row), length;
if (row.children(".k-edit-cell").length && !that.options.rowTemplate) {
row.children(":not(.k-group-cell,.k-hierarchy-cell)").each(function() {
cell = $(this);
column = that.columns[that.cellIndex(cell)];
if (column.field === e.field) {
if (!cell.hasClass("k-edit-cell")) {
that._displayCell(cell, column, model);
$('<span class="k-dirty"/>').prependTo(cell);
} else {
cell.addClass("k-dirty-cell");
}
}
});
} else if (!row.hasClass("k-grid-edit-row")) {
tmp = (isAlt ? that.altRowTemplate : that.rowTemplate)(model);
row.replaceWith(tmp);
tmp = that.items().eq(idx);
for (idx = 0, length = that.columns.length; idx < length; idx++) {
column = that.columns[idx];
if (column.field === e.field) {
cell = tmp.children(":not(.k-group-cell,.k-hierarchy-cell)").eq(idx);
$('<span class="k-dirty"/>').prependTo(cell);
}
}
that.trigger("itemChange", {
item: tmp,
data: model,
ns: ui
});
}
},
_pageable: function() {
var that = this, wrapper, pageable = that.options.pageable;
if (pageable) {
wrapper = that.wrapper.children("div.k-grid-pager");
if (!wrapper.length) {
wrapper = $('<div class="k-pager-wrap k-grid-pager"/>').appendTo(that.wrapper);
}
if (that.pager) {
that.pager.destroy();
}
if (typeof pageable === "object" && pageable instanceof kendo.ui.Pager) {
that.pager = pageable;
} else {
that.pager = new kendo.ui.Pager(wrapper, extend({}, pageable, {
dataSource: that.dataSource
}));
}
}
},
_footer: function() {
var that = this, aggregates = that.dataSource.aggregates(), html = "", footerTemplate = that.footerTemplate, options = that.options, footerWrap, footer = that.footer || that.wrapper.find(".k-grid-footer");
if (footerTemplate) {
aggregates = !isEmptyObject(aggregates) ? aggregates : buildEmptyAggregatesObject(that.dataSource.aggregate());
html = $(that._wrapFooter(footerTemplate(aggregates)));
if (footer.length) {
var tmp = html;
footer.replaceWith(tmp);
footer = that.footer = tmp;
} else {
if (options.scrollable) {
footer = that.footer = options.pageable ? html.insertBefore(that.wrapper.children("div.k-grid-pager")) : html.appendTo(that.wrapper);
} else {
footer = that.footer = html.insertBefore(that.tbody);
}
}
} else if (footer && !that.footer) {
that.footer = footer;
}
if (footer.length) {
if (options.scrollable) {
footerWrap = footer.attr("tabindex", -1).children(".k-grid-footer-wrap");
that.scrollables = that.scrollables.not(".k-grid-footer-wrap").add(footerWrap);
}
if (that._footerWidth) {
footer.find("table").css("width", that._footerWidth);
}
if (footerWrap) {
footerWrap.scrollLeft(that.content.scrollLeft());
}
}
},
_wrapFooter: function(footerRow) {
var that = this, html = "", scrollbar = !kendo.support.mobileOS ? kendo.support.scrollbar() : 0;
if (that.options.scrollable) {
html = $('<div class="k-grid-footer"><div class="k-grid-footer-wrap"><table cellspacing="0"><tbody>' + footerRow + "</tbody></table></div></div>");
that._appendCols(html.find("table"));
html.css(isRtl ? "padding-left" : "padding-right", scrollbar);
// Update inner fix.
return html;
}
return '<tfoot class="k-grid-footer">' + footerRow + "</tfoot>";
},
_columnMenu: function() {
var that = this, menu, columns = that.columns, column, options = that.options, columnMenu = options.columnMenu, menuOptions, sortable, filterable, closeCallback = function() {
focusTable(that.thead.parent(), true);
}, initCallback = function(e) {
that.trigger(COLUMNMENUINIT, {
field: e.field,
container: e.container
});
}, cell;
if (columnMenu) {
if (typeof columnMenu == "boolean") {
columnMenu = {};
}
that.thead.find("th:not(.k-hierarchy-cell,.k-group-cell)").each(function(index) {
column = columns[index];
cell = $(this);
if (!column.command && (column.field || cell.attr("data-" + kendo.ns + "field"))) {
menu = cell.data("kendoColumnMenu");
if (menu) {
menu.destroy();
}
sortable = column.sortable !== false && columnMenu.sortable !== false ? options.sortable : false;
filterable = options.filterable && column.filterable !== false && columnMenu.filterable !== false ? extend({}, column.filterable, options.filterable) : false;
menuOptions = {
dataSource: that.dataSource,
values: column.values,
columns: columnMenu.columns,
sortable: sortable,
filterable: filterable,
messages: columnMenu.messages,
owner: that,
closeCallback: closeCallback,
init: initCallback
};
cell.kendoColumnMenu(menuOptions);
}
});
}
},
_filterable: function() {
var that = this, columns = that.columns, cell, filterMenu, closeCallback = function() {
focusTable(that.thead.parent(), true);
}, filterable = that.options.filterable;
if (filterable && !that.options.columnMenu) {
that.thead.find("th:not(.k-hierarchy-cell,.k-group-cell)").each(function(index) {
cell = $(this);
if (columns[index].filterable !== false && !columns[index].command && (columns[index].field || cell.attr("data-" + kendo.ns + "field"))) {
filterMenu = cell.data("kendoFilterMenu");
if (filterMenu) {
filterMenu.destroy();
}
cell.kendoFilterMenu(extend(true, {}, filterable, columns[index].filterable, {
dataSource: that.dataSource,
values: columns[index].values,
closeCallback: closeCallback,
init: function(e) {
that.trigger(FILTERMENUINIT, {
field: e.field,
container: e.container
});
}
}));
}
});
}
},
_sortable: function() {
var that = this, columns = that.columns, column, cell, sortableInstance, sortable = that.options.sortable;
if (sortable) {
that.thead.find("th:not(.k-hierarchy-cell,.k-group-cell)").each(function(index) {
column = columns[index];
if (column.sortable !== false && !column.command && column.field) {
cell = $(this);
sortableInstance = cell.data("kendoSortable");
if (sortableInstance) {
sortableInstance.destroy();
}
cell.attr("data-" + kendo.ns + "field", column.field).kendoSortable(extend({}, sortable, {
dataSource: that.dataSource,
aria: true
}));
}
});
}
},
_columns: function(columns) {
var that = this, table = that.table, encoded, cols = table.find("col"), dataSource = that.options.dataSource;
// using HTML5 data attributes as a configuration option e.g. <th data-field="foo">Foo</foo>
columns = columns.length ? columns : map(table.find("th"), function(th, idx) {
th = $(th);
var sortable = th.attr(kendo.attr("sortable")), filterable = th.attr(kendo.attr("filterable")), type = th.attr(kendo.attr("type")), groupable = th.attr(kendo.attr("groupable")), field = th.attr(kendo.attr("field")), menu = th.attr(kendo.attr("menu"));
if (!field) {
field = th.text().replace(/\s|[^A-z0-9]/g, "");
}
return {
field: field,
type: type,
sortable: sortable !== "false",
filterable: filterable !== "false",
groupable: groupable !== "false",
menu: menu,
template: th.attr(kendo.attr("template")),
width: cols.eq(idx).css("width")
};
});
encoded = !(that.table.find("tbody tr").length > 0 && (!dataSource || !dataSource.transport));
that.columns = map(columns, function(column) {
column = typeof column === STRING ? {
field: column
} : column;
if (column.hidden) {
column.attributes = addHiddenStyle(column.attributes);
column.footerAttributes = addHiddenStyle(column.footerAttributes);
column.headerAttributes = addHiddenStyle(column.headerAttributes);
}
return extend({
encoded: encoded
}, column);
});
},
_groups: function() {
var group = this.dataSource.group();
return group ? group.length : 0;
},
_tmpl: function(rowTemplate, alt) {
var that = this, settings = extend({}, kendo.Template, that.options.templateSettings), idx, length = that.columns.length, template, state = {
storage: {},
count: 0
}, column, type, hasDetails = that._hasDetails(), className = [], groups = that._groups();
if (!rowTemplate) {
rowTemplate = "<tr";
if (alt) {
className.push("k-alt");
}
if (hasDetails) {
className.push("k-master-row");
}
if (className.length) {
rowTemplate += ' class="' + className.join(" ") + '"';
}
if (length) {
// data item is an object
rowTemplate += " " + kendo.attr("uid") + '="#=' + kendo.expr("uid", settings.paramName) + '#"';
}
rowTemplate += " role='row'>";
if (groups > 0) {
rowTemplate += groupCells(groups);
}
if (hasDetails) {
rowTemplate += '<td class="k-hierarchy-cell"><a class="k-icon k-plus" href="\\#" tabindex="-1"></a></td>';
}
for (idx = 0; idx < length; idx++) {
column = that.columns[idx];
template = column.template;
type = typeof template;
rowTemplate += "<td" + stringifyAttributes(column.attributes) + " role='gridcell'>";
rowTemplate += that._cellTmpl(column, state);
rowTemplate += "</td>";
}
rowTemplate += "</tr>";
}
rowTemplate = kendo.template(rowTemplate, settings);
if (state.count > 0) {
return proxy(rowTemplate, state.storage);
}
return rowTemplate;
},
_headerCellText: function(column) {
var that = this, settings = extend({}, kendo.Template, that.options.templateSettings), template = column.headerTemplate, type = typeof template, text = column.title || column.field || "";
if (type === FUNCTION) {
text = kendo.template(template, settings)({});
} else if (type === STRING) {
text = template;
}
return text;
},
_cellTmpl: function(column, state) {
var that = this, settings = extend({}, kendo.Template, that.options.templateSettings), template = column.template, paramName = settings.paramName, field = column.field, html = "", idx, length, format = column.format, type = typeof template, columnValues = column.values;
if (column.command) {
if (isArray(column.command)) {
for (idx = 0, length = column.command.length; idx < length; idx++) {
html += that._createButton(column.command[idx]);
}
return html.replace(templateHashRegExp, "\\#");
}
return that._createButton(column.command).replace(templateHashRegExp, "\\#");
}
if (type === FUNCTION) {
state.storage["tmpl" + state.count] = template;
html += "#=this.tmpl" + state.count + "(" + paramName + ")#";
state.count++;
} else if (type === STRING) {
html += template;
} else if (columnValues && columnValues.length && isPlainObject(columnValues[0]) && "value" in columnValues[0] && field) {
html += "#var v =" + kendo.stringify(convertToObject(columnValues)) + "#";
html += "#var f = v[";
if (!settings.useWithBlock) {
html += paramName + ".";
}
html += field + "]#";
html += "${f != null ? f : ''}";
} else {
html += column.encoded ? "#:" : "#=";
if (format) {
html += 'kendo.format("' + format.replace(formatRegExp, "\\$1") + '",';
}
if (field) {
field = kendo.expr(field, paramName);
html += field + "==null?'':" + field;
} else {
html += "''";
}
if (format) {
html += ")";
}
html += "#";
}
return html;
},
_templates: function() {
var that = this, options = that.options, dataSource = that.dataSource, groups = dataSource.group(), footer = that.footer || that.wrapper.find(".k-grid-footer"), aggregates = dataSource.aggregate();
that.rowTemplate = that._tmpl(options.rowTemplate);
that.altRowTemplate = that._tmpl(options.altRowTemplate || options.rowTemplate, true);
if (that._hasDetails()) {
that.detailTemplate = that._detailTmpl(options.detailTemplate || "");
}
if (that._group && !isEmptyObject(aggregates) || !isEmptyObject(aggregates) && !footer.length || grep(that.columns, function(column) {
return column.footerTemplate;
}).length) {
that.footerTemplate = that._footerTmpl(aggregates, "footerTemplate", "k-footer-template");
}
if (groups && grep(that.columns, function(column) {
return column.groupFooterTemplate;
}).length) {
aggregates = $.map(groups, function(g) {
return g.aggregates;
});
that.groupFooterTemplate = that._footerTmpl(aggregates, "groupFooterTemplate", "k-group-footer");
}
},
_footerTmpl: function(aggregates, templateName, rowClass) {
var that = this, settings = extend({}, kendo.Template, that.options.templateSettings), paramName = settings.paramName, html = "", idx, length, columns = that.columns, template, type, storage = {}, count = 0, scope = {}, groups = that._groups(), fieldsMap = buildEmptyAggregatesObject(aggregates), column;
html += '<tr class="' + rowClass + '">';
if (groups > 0) {
html += groupCells(groups);
}
if (that._hasDetails()) {
html += '<td class="k-hierarchy-cell"> </td>';
}
for (idx = 0, length = that.columns.length; idx < length; idx++) {
column = columns[idx];
template = column[templateName];
type = typeof template;
html += "<td" + stringifyAttributes(column.footerAttributes) + ">";
if (template) {
if (type !== FUNCTION) {
scope = fieldsMap[column.field] ? extend({}, settings, {
paramName: paramName + "." + column.field
}) : {};
template = kendo.template(template, scope);
}
storage["tmpl" + count] = template;
html += "#=this.tmpl" + count + "(" + paramName + ")#";
count++;
} else {
html += " ";
}
html += "</td>";
}
html += "</tr>";
html = kendo.template(html, settings);
if (count > 0) {
return proxy(html, storage);
}
return html;
},
_detailTmpl: function(template) {
var that = this, html = "", settings = extend({}, kendo.Template, that.options.templateSettings), paramName = settings.paramName, templateFunctionStorage = {}, templateFunctionCount = 0, groups = that._groups(), colspan = visibleColumns(that.columns).length, type = typeof template;
html += '<tr class="k-detail-row">';
if (groups > 0) {
html += groupCells(groups);
}
html += '<td class="k-hierarchy-cell"></td><td class="k-detail-cell"' + (colspan ? ' colspan="' + colspan + '"' : "") + ">";
if (type === FUNCTION) {
templateFunctionStorage["tmpl" + templateFunctionCount] = template;
html += "#=this.tmpl" + templateFunctionCount + "(" + paramName + ")#";
templateFunctionCount++;
} else {
html += template;
}
html += "</td></tr>";
html = kendo.template(html, settings);
if (templateFunctionCount > 0) {
return proxy(html, templateFunctionStorage);
}
return html;
},
_hasDetails: function() {
var that = this;
return that.options.detailTemplate !== null || (that._events[DETAILINIT] || []).length;
},
_details: function() {
var that = this;
that.table.on(CLICK + NS, ".k-hierarchy-cell .k-plus, .k-hierarchy-cell .k-minus", function(e) {
var button = $(this), expanding = button.hasClass("k-plus"), masterRow = button.closest("tr.k-master-row"), detailRow, detailTemplate = that.detailTemplate, data, hasDetails = that._hasDetails();
button.toggleClass("k-plus", !expanding).toggleClass("k-minus", expanding);
if (hasDetails && !masterRow.next().hasClass("k-detail-row")) {
data = that.dataItem(masterRow);
$(detailTemplate(data)).addClass(masterRow.hasClass("k-alt") ? "k-alt" : "").insertAfter(masterRow);
that.trigger(DETAILINIT, {
masterRow: masterRow,
detailRow: masterRow.next(),
data: data,
detailCell: masterRow.next().find(".k-detail-cell")
});
}
detailRow = masterRow.next();
that.trigger(expanding ? DETAILEXPAND : DETAILCOLLAPSE, {
masterRow: masterRow,
detailRow: detailRow
});
detailRow.toggle(expanding);
if (that._current) {
that._current.attr("aria-expanded", expanding);
}
e.preventDefault();
return false;
});
},
dataItem: function(tr) {
return this._data[this.tbody.find("> tr:not(.k-grouping-row,.k-detail-row,.k-group-footer)").index($(tr))];
},
expandRow: function(tr) {
$(tr).find("> td .k-plus, > td .k-i-expand").click();
},
collapseRow: function(tr) {
$(tr).find("> td .k-minus, > td .k-i-collapse").click();
},
_thead: function() {
var that = this, columns = that.columns, hasDetails = that._hasDetails() && columns.length, idx, length, html = "", thead = that.table.find(">thead"), tr, text, th;
if (!thead.length) {
thead = $("<thead/>").insertBefore(that.tbody);
}
tr = that.element.find("tr:has(th):first");
if (!tr.length) {
tr = thead.children().first();
if (!tr.length) {
tr = $("<tr/>");
}
}
if (!tr.children().length) {
if (hasDetails) {
html += '<th class="k-hierarchy-cell"> </th>';
}
for (idx = 0, length = columns.length; idx < length; idx++) {
th = columns[idx];
text = that._headerCellText(th);
if (!th.command) {
html += "<th role='columnheader' " + kendo.attr("field") + "='" + (th.field || "") + "' ";
if (th.title) {
html += kendo.attr("title") + '="' + th.title.replace(/'/g, "'") + '" ';
}
if (th.groupable !== undefined) {
html += kendo.attr("groupable") + "='" + th.groupable + "' ";
}
if (th.aggregates) {
html += kendo.attr("aggregates") + "='" + th.aggregates + "'";
}
html += stringifyAttributes(th.headerAttributes);
html += ">" + text + "</th>";
} else {
html += "<th" + stringifyAttributes(th.headerAttributes) + ">" + text + "</th>";
}
}
tr.html(html);
} else if (hasDetails && !tr.find(".k-hierarchy-cell")[0]) {
tr.prepend('<th class="k-hierarchy-cell"> </th>');
}
tr.find("th").addClass("k-header");
if (!that.options.scrollable) {
thead.addClass("k-grid-header");
}
tr.find("script").remove().end().appendTo(thead);
if (that.thead) {
that._destroyColumnAttachments();
}
that.thead = thead;
that._sortable();
that._filterable();
that._scrollable();
that._updateCols();
that._resizable();
that._draggable();
that._reorderable();
if (that.groupable) {
that._attachGroupable();
}
that._columnMenu();
},
_updateCols: function() {
var that = this;
that._appendCols(that.thead.parent().add(that.table));
},
_appendCols: function(table) {
var that = this;
normalizeCols(table, visibleColumns(that.columns), that._hasDetails(), that._groups());
},
_autoColumns: function(schema) {
if (schema && schema.toJSON) {
var that = this, field;
schema = schema.toJSON();
for (field in schema) {
that.columns.push({
field: field
});
}
that._thead();
that._templates();
}
},
_rowsHtml: function(data) {
var that = this, html = "", idx, length, rowTemplate = that.rowTemplate, altRowTemplate = that.altRowTemplate;
for (idx = 0, length = data.length; idx < length; idx++) {
if (idx % 2) {
html += altRowTemplate(data[idx]);
} else {
html += rowTemplate(data[idx]);
}
that._data.push(data[idx]);
}
return html;
},
_groupRowHtml: function(group, colspan, level) {
var that = this, html = "", idx, length, field = group.field, column = grep(that.columns, function(column) {
return column.field == field;
})[0] || {}, template = column.groupHeaderTemplate, text = (column.title || field) + ": " + formatGroupValue(group.value, column.format, column.values), data = extend({}, {
field: group.field,
value: group.value
}, group.aggregates[group.field]), footerDefaults = that._groupAggregatesDefaultObject || {}, groupItems = group.items;
if (template) {
text = typeof template === FUNCTION ? template(data) : kendo.template(template)(data);
}
html += '<tr class="k-grouping-row">' + groupCells(level) + '<td colspan="' + colspan + '" aria-expanded="true">' + '<p class="k-reset">' + '<a class="k-icon k-i-collapse" href="#" tabindex="-1"></a>' + text + "</p></td></tr>";
if (group.hasSubgroups) {
for (idx = 0, length = groupItems.length; idx < length; idx++) {
html += that._groupRowHtml(groupItems[idx], colspan - 1, level + 1);
}
} else {
html += that._rowsHtml(groupItems);
}
if (that.groupFooterTemplate) {
html += that.groupFooterTemplate(extend(footerDefaults, group.aggregates));
}
return html;
},
collapseGroup: function(group) {
group = $(group).find(".k-icon").addClass("k-i-expand").removeClass("k-i-collapse").end();
var level = group.find(".k-group-cell").length, footerCount = 1, offset, tr;
group.find("td:first").attr("aria-expanded", false);
group.nextAll("tr").each(function() {
tr = $(this);
offset = tr.find(".k-group-cell").length;
if (tr.hasClass("k-grouping-row")) {
footerCount++;
} else if (tr.hasClass("k-group-footer")) {
footerCount--;
}
if (offset <= level || tr.hasClass("k-group-footer") && footerCount < 0) {
return false;
}
tr.hide();
});
},
expandGroup: function(group) {
group = $(group).find(".k-icon").addClass("k-i-collapse").removeClass("k-i-expand").end();
var that = this, level = group.find(".k-group-cell").length, tr, offset, groupsCount = 1;
group.find("td:first").attr("aria-expanded", true);
group.nextAll("tr").each(function() {
tr = $(this);
offset = tr.find(".k-group-cell").length;
if (offset <= level) {
return false;
}
if (offset == level + 1 && !tr.hasClass("k-detail-row")) {
tr.show();
if (tr.hasClass("k-grouping-row") && tr.find(".k-icon").hasClass("k-i-collapse")) {
that.expandGroup(tr);
}
if (tr.hasClass("k-master-row") && tr.find(".k-icon").hasClass("k-minus")) {
tr.next().show();
}
}
if (tr.hasClass("k-grouping-row")) {
groupsCount++;
}
if (tr.hasClass("k-group-footer")) {
if (groupsCount == 1) {
tr.show();
} else {
groupsCount--;
}
}
});
},
_updateHeader: function(groups) {
var that = this, cells = that.thead.find("th.k-group-cell"), length = cells.length;
if (groups > length) {
$(new Array(groups - length + 1).join('<th class="k-group-cell k-header"> </th>')).prependTo(that.thead.find("tr"));
} else if (groups < length) {
length = length - groups;
$(grep(cells, function(item, index) {
return length > index;
})).remove();
}
},
_firstDataItem: function(data, grouped) {
if (data && grouped) {
if (data.hasSubgroups) {
data = this._firstDataItem(data.items[0], grouped);
} else {
data = data.items[0];
}
}
return data;
},
hideColumn: function(column) {
var that = this, rows, row, cell, tables, idx, cols, colWidth, width = 0, length, footer = that.footer || that.wrapper.find(".k-grid-footer"), columns = that.columns, columnIndex, browser = kendo.support.browser;
if (typeof column == "number") {
column = columns[column];
} else {
column = grep(columns, function(item) {
return item.field === column;
})[0];
}
if (!column || column.hidden) {
return;
}
columnIndex = inArray(column, visibleColumns(columns));
column.hidden = true;
column.attributes = addHiddenStyle(column.attributes);
column.footerAttributes = addHiddenStyle(column.footerAttributes);
column.headerAttributes = addHiddenStyle(column.headerAttributes);
that._templates();
that._updateCols();
that.thead.find(">tr>th:not(.k-hierarchy-cell,.k-group-cell):visible").eq(columnIndex).hide();
if (footer) {
that._appendCols(footer.find("table:first"));
footer.find(".k-footer-template>td:not(.k-hierarchy-cell,.k-group-cell):visible").eq(columnIndex).hide();
}
rows = that.tbody.children();
for (idx = 0, length = rows.length; idx < length; idx += 1) {
row = rows.eq(idx);
if (row.is(".k-grouping-row,.k-detail-row")) {
cell = row.children(":not(.k-group-cell):first,.k-detail-cell").last();
cell.attr("colspan", parseInt(cell.attr("colspan"), 10) - 1);
} else {
if (row.hasClass("k-grid-edit-row") && (cell = row.children(".k-edit-container")[0])) {
cell = $(cell);
cell.attr("colspan", parseInt(cell.attr("colspan"), 10) - 1);
cell.find("col").eq(columnIndex).remove();
row = cell.find("tr:first");
}
setCellVisibility(row[0].cells, columnIndex, false);
}
}
cols = that.thead.prev().find("col");
for (idx = 0, length = cols.length; idx < length; idx += 1) {
colWidth = cols[idx].style.width;
if (colWidth && colWidth.indexOf("%") == -1) {
width += parseInt(colWidth, 10);
} else {
width = 0;
break;
}
}
tables = $(">.k-grid-header table:first,>.k-grid-footer table:first", that.wrapper).add(that.table);
that._footerWidth = null;
if (width) {
tables.width(width);
that._footerWidth = width;
}
if (browser.msie && browser.version == 8) {
tables.css("display", "inline-table");
setTimeout(function() {
tables.css("display", "table");
}, 1);
}
that.trigger(COLUMNHIDE, {
column: column
});
},
showColumn: function(column) {
var that = this, rows, idx, length, row, cell, tables, width, colWidth, cols, columns = that.columns, footer = that.footer || that.wrapper.find(".k-grid-footer"), columnIndex;
if (typeof column == "number") {
column = columns[column];
} else {
column = grep(columns, function(item) {
return item.field === column;
})[0];
}
if (!column || !column.hidden) {
return;
}
columnIndex = inArray(column, columns);
column.hidden = false;
column.attributes = removeHiddenStyle(column.attributes);
column.footerAttributes = removeHiddenStyle(column.footerAttributes);
column.headerAttributes = removeHiddenStyle(column.headerAttributes);
that._templates();
that._updateCols();
that.thead.find(">tr>th:not(.k-hierarchy-cell,.k-group-cell)").eq(columnIndex).show();
if (footer) {
that._appendCols(footer.find("table:first"));
footer.find(".k-footer-template>td:not(.k-hierarchy-cell,.k-group-cell)").eq(columnIndex).show();
}
rows = that.tbody.children();
for (idx = 0, length = rows.length; idx < length; idx += 1) {
row = rows.eq(idx);
if (row.is(".k-grouping-row,.k-detail-row")) {
cell = row.children(":not(.k-group-cell):first,.k-detail-cell").last();
cell.attr("colspan", parseInt(cell.attr("colspan"), 10) + 1);
} else {
if (row.hasClass("k-grid-edit-row") && (cell = row.children(".k-edit-container")[0])) {
cell = $(cell);
cell.attr("colspan", parseInt(cell.attr("colspan"), 10) + 1);
normalizeCols(cell.find(">form>table"), visibleColumns(columns), false, 0);
row = cell.find("tr:first");
}
setCellVisibility(row[0].cells, columnIndex, true);
}
}
tables = $(">.k-grid-header table:first,>.k-grid-footer table:first", that.wrapper).add(that.table);
if (!column.width) {
tables.width("");
} else {
width = 0;
cols = that.thead.prev().find("col");
for (idx = 0, length = cols.length; idx < length; idx += 1) {
colWidth = cols[idx].style.width;
if (colWidth.indexOf("%") > -1) {
width = 0;
break;
}
width += parseInt(colWidth, 10);
}
that._footerWidth = null;
if (width) {
tables.width(width);
that._footerWidth = width;
}
}
that.trigger(COLUMNSHOW, {
column: column
});
},
_progress: function(toggle) {
var that = this, element = that.element.is("table") ? that.element.parent() : that.content && that.content.length ? that.content : that.element;
kendo.ui.progress(element, toggle);
},
refresh: function(e) {
var that = this, length, idx, html = "", data = that.dataSource.view(), navigatable = that.options.navigatable, tbody, placeholder, currentIndex, current = $(that.current()), isCurrentInHeader = false, groups = (that.dataSource.group() || []).length, colspan = groups + visibleColumns(that.columns).length, active;
if (e && e.action === "itemchange" && that.editable) {
// skip rebinding if editing is in progress
return;
}
e = e || {};
if (that.trigger("dataBinding", {
action: e.action || "rebind",
index: e.index,
items: e.items
})) {
return;
}
active = activeElement();
if (navigatable && (that.table[0] === active || $.contains(that.table[0], active) || that._editContainer && that._editContainer.data("kendoWindow"))) {
isCurrentInHeader = current.is("th");
currentIndex = 0;
if (isCurrentInHeader) {
currentIndex = that.thead.find("th:not(.k-group-cell)").index(current);
}
}
that._destroyEditable();
that._progress(false);
that._data = [];
if (!that.columns.length) {
that._autoColumns(that._firstDataItem(data[0], groups));
colspan = groups + that.columns.length;
}
that._group = groups > 0 || that._group;
if (that._group) {
that._templates();
that._updateCols();
that._updateHeader(groups);
that._group = groups > 0;
}
if (groups > 0) {
if (that.detailTemplate) {
colspan++;
}
if (that.groupFooterTemplate) {
that._groupAggregatesDefaultObject = buildEmptyAggregatesObject(that.dataSource.aggregate());
}
for (idx = 0, length = data.length; idx < length; idx++) {
html += that._groupRowHtml(data[idx], colspan, 0);
}
} else {
html += that._rowsHtml(data);
}
if (tbodySupportsInnerHtml) {
that.tbody[0].innerHTML = html;
} else {
placeholder = document.createElement("div");
placeholder.innerHTML = "<table><tbody>" + html + "</tbody></table>";
tbody = placeholder.firstChild.firstChild;
that.table[0].replaceChild(tbody, that.tbody[0]);
that.tbody = $(tbody);
}
that._footer();
that._setContentHeight();
if (currentIndex >= 0) {
that._removeCurrent();
if (!isCurrentInHeader) {
that.current(that.items().eq(currentIndex).children().filter(DATA_CELL).first());
} else {
that.current(that.thead.find("th:not(.k-group-cell)").eq(currentIndex));
}
if (that._current) {
focusTable(that._current.closest("table")[0], true);
}
}
that.trigger(DATABOUND);
}
});
function getCommand(commands, name) {
var idx, length, command;
if (typeof commands === STRING && commands === name) {
return commands;
}
if (isPlainObject(commands) && commands.name === name) {
return commands;
}
if (isArray(commands)) {
for (idx = 0, length = commands.length; idx < length; idx++) {
command = commands[idx];
if (typeof command === STRING && command === name || command.name === name) {
return command;
}
}
}
return null;
}
function focusTable(table, direct) {
var msie = kendo.support.browser.msie;
if (direct === true) {
table = $(table);
var condition = msie && table.parent().is(".k-grid-content,.k-grid-header-wrap"), scrollTop, scrollLeft;
if (condition) {
scrollTop = table.parent().scrollTop();
scrollLeft = table.parent().scrollLeft();
}
if (msie) {
try {
//The setActive method does not cause the document to scroll to the active object in the current page
table[0].setActive();
} catch (e) {
table[0].focus();
}
} else {
table[0].focus();
}
if (condition) {
table.parent().scrollTop(scrollTop);
table.parent().scrollLeft(scrollLeft);
}
} else {
$(table).one("focusin", function(e) {
e.preventDefault();
}).focus();
}
}
function tableClick(e) {
var currentTarget = $(e.currentTarget), isHeader = currentTarget.is("th"), currentTable = currentTarget.closest("table")[0];
if (currentTable !== this.table[0] && currentTable !== this.thead.parent()[0]) {
return;
}
this.current(currentTarget);
if (isHeader || !$(e.target).is(":button,a,:input,a>.k-icon,textarea,span.k-icon,.k-input")) {
setTimeout(function() {
//DOMElement.focus() only for header, because IE doesn't really focus the table
focusTable(currentTable, true);
});
}
if (isHeader) {
e.preventDefault();
}
}
ui.plugin(Grid);
ui.plugin(VirtualScrollable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, CHANGE = "change", CANCEL = "cancel", DATABOUND = "dataBound", DATABINDING = "dataBinding", Widget = kendo.ui.Widget, keys = kendo.keys, FOCUSSELECTOR = ">*", PROGRESS = "progress", ERROR = "error", FOCUSED = "k-state-focused", SELECTED = "k-state-selected", KEDITITEM = "k-edit-item", STRING = "string", EDIT = "edit", REMOVE = "remove", SAVE = "save", CLICK = "click", NS = ".kendoListView", proxy = $.proxy, activeElement = kendo._activeElement, progress = kendo.ui.progress, DataSource = kendo.data.DataSource;
var ListView = Widget.extend({
init: function(element, options) {
var that = this;
options = $.isArray(options) ? {
dataSource: options
} : options;
Widget.fn.init.call(that, element, options);
options = that.options;
that.wrapper = element = that.element;
if (element[0].id) {
that._itemId = element[0].id + "_lv_active";
}
that._element();
that._dataSource();
that.template = kendo.template(options.template || "");
that.altTemplate = kendo.template(options.altTemplate || options.template);
that.editTemplate = kendo.template(options.editTemplate || "");
that._navigatable();
that._selectable();
that._pageable();
that._crudHandlers();
if (that.options.autoBind) {
that.dataSource.fetch();
}
kendo.notify(that);
},
events: [ CHANGE, CANCEL, DATABINDING, DATABOUND, EDIT, REMOVE, SAVE ],
options: {
name: "ListView",
autoBind: true,
selectable: false,
navigatable: false,
template: "",
altTemplate: "",
editTemplate: ""
},
_item: function(action) {
return this.element.children()[action]();
},
items: function() {
return this.element.children();
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.options.autoBind) {
dataSource.fetch();
}
},
_unbindDataSource: function() {
var that = this;
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(PROGRESS, that._progressHandler).unbind(ERROR, that._errorHandler);
},
_dataSource: function() {
var that = this;
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._progress, that);
that._errorHandler = proxy(that._error, that);
}
that.dataSource = DataSource.create(that.options.dataSource).bind(CHANGE, that._refreshHandler).bind(PROGRESS, that._progressHandler).bind(ERROR, that._errorHandler);
},
_progress: function() {
progress(this.element, true);
},
_error: function() {
progress(this.element, false);
},
_element: function() {
this.element.addClass("k-widget k-listview").attr("role", "listbox");
},
refresh: function(e) {
var that = this, view = that.dataSource.view(), data, items, item, html = "", idx, length, template = that.template, altTemplate = that.altTemplate, active = activeElement();
if (e && e.action === "itemchange") {
if (!that.editable) {
data = e.items[0];
idx = $.inArray(data, view);
if (idx >= 0) {
that.items().eq(idx).replaceWith(template(data));
item = that.items().eq(idx);
item.attr(kendo.attr("uid"), data.uid);
that.trigger("itemChange", {
item: item,
data: data
});
}
}
return;
}
e = e || {};
if (that.trigger(DATABINDING, {
action: e.action || "rebind",
items: e.items,
index: e.index
})) {
return;
}
that._destroyEditable();
for (idx = 0, length = view.length; idx < length; idx++) {
if (idx % 2) {
html += altTemplate(view[idx]);
} else {
html += template(view[idx]);
}
}
that.element.html(html);
items = that.items();
for (idx = 0, length = view.length; idx < length; idx++) {
items.eq(idx).attr(kendo.attr("uid"), view[idx].uid).attr("role", "option").attr("aria-selected", "false");
}
if (that.element[0] === active && that.options.navigatable) {
that.current(items.eq(0));
}
that.trigger(DATABOUND);
},
_pageable: function() {
var that = this, pageable = that.options.pageable, settings, pagerId;
if ($.isPlainObject(pageable)) {
pagerId = pageable.pagerId;
settings = $.extend({}, pageable, {
dataSource: that.dataSource,
pagerId: null
});
that.pager = new kendo.ui.Pager($("#" + pagerId), settings);
}
},
_selectable: function() {
var that = this, multi, current, selectable = that.options.selectable, navigatable = that.options.navigatable;
if (selectable) {
multi = typeof selectable === STRING && selectable.toLowerCase().indexOf("multiple") > -1;
if (multi) {
that.element.attr("aria-multiselectable", true);
}
that.selectable = new kendo.ui.Selectable(that.element, {
aria: true,
multiple: multi,
filter: FOCUSSELECTOR,
change: function() {
that.trigger(CHANGE);
}
});
if (navigatable) {
that.element.on("keydown" + NS, function(e) {
if (e.keyCode === keys.SPACEBAR) {
current = that.current();
if (e.target == e.currentTarget) {
e.preventDefault();
}
if (multi) {
if (!e.ctrlKey) {
that.selectable.clear();
} else {
if (current && current.hasClass(SELECTED)) {
current.removeClass(SELECTED);
return;
}
}
} else {
that.selectable.clear();
}
that.selectable.value(current);
}
});
}
}
},
current: function(candidate) {
var that = this, element = that.element, current = that._current, id = that._itemId;
if (candidate === undefined) {
return current;
}
if (current) {
if (current[0].id === id) {
current.removeAttr("id");
}
current.removeClass(FOCUSED);
element.removeAttr("aria-activedescendant");
}
if (candidate && candidate[0]) {
id = candidate[0].id || id;
that._scrollTo(candidate[0]);
element.attr("aria-activedescendant", id);
candidate.addClass(FOCUSED).attr("id", id);
}
that._current = candidate;
},
_scrollTo: function(element) {
var that = this, container, UseJQueryoffset = false, SCROLL = "scroll";
if (that.wrapper.css("overflow") == "auto" || that.wrapper.css("overflow") == SCROLL) {
container = that.wrapper[0];
} else {
container = window;
UseJQueryoffset = true;
}
var scrollDirectionFunc = function(direction, dimension) {
var elementOffset = UseJQueryoffset ? $(element).offset()[direction.toLowerCase()] : element["offset" + direction], elementDimension = element["client" + dimension], containerScrollAmount = $(container)[SCROLL + direction](), containerDimension = $(container)[dimension.toLowerCase()]();
if (elementOffset + elementDimension > containerScrollAmount + containerDimension) {
$(container)[SCROLL + direction](elementOffset + elementDimension - containerDimension);
} else if (elementOffset < containerScrollAmount) {
$(container)[SCROLL + direction](elementOffset);
}
};
scrollDirectionFunc("Top", "Height");
scrollDirectionFunc("Left", "Width");
},
_navigatable: function() {
var that = this, navigatable = that.options.navigatable, element = that.element, clickCallback = function(e) {
that.current($(e.currentTarget));
if (!$(e.target).is(":button,a,:input,a>.k-icon,textarea")) {
element.focus();
}
};
if (navigatable) {
that._tabindex();
element.on("focus" + NS, function() {
var current = that._current;
if (!current || !current.is(":visible")) {
current = that._item("first");
}
that.current(current);
}).on("focusout" + NS, function() {
if (that._current) {
that._current.removeClass(FOCUSED);
}
}).on("keydown" + NS, function(e) {
var key = e.keyCode, current = that.current(), target = $(e.target), canHandle = !target.is(":button,textarea,a,a>.t-icon,input"), isTextBox = target.is(":text"), preventDefault = kendo.preventDefault, editItem = element.find("." + KEDITITEM), active = activeElement(), idx;
if (!canHandle && !isTextBox && keys.ESC != key || isTextBox && keys.ESC != key && keys.ENTER != key) {
return;
}
if (keys.UP === key || keys.LEFT === key) {
if (current) {
current = current.prev();
}
that.current(!current || !current[0] ? that._item("last") : current);
preventDefault(e);
} else if (keys.DOWN === key || keys.RIGHT === key) {
if (current) {
current = current.next();
}
that.current(!current || !current[0] ? that._item("first") : current);
preventDefault(e);
} else if (keys.PAGEUP === key) {
that.current(null);
that.dataSource.page(that.dataSource.page() - 1);
preventDefault(e);
} else if (keys.PAGEDOWN === key) {
that.current(null);
that.dataSource.page(that.dataSource.page() + 1);
preventDefault(e);
} else if (keys.HOME === key) {
that.current(that._item("first"));
preventDefault(e);
} else if (keys.END === key) {
that.current(that._item("last"));
preventDefault(e);
} else if (keys.ENTER === key) {
if (editItem.length !== 0 && (canHandle || isTextBox)) {
idx = that.items().index(editItem);
if (active) {
active.blur();
}
that.save();
var focusAgain = function() {
that.element.trigger("focus");
that.current(that.items().eq(idx));
};
that.one("dataBound", focusAgain);
} else if (that.options.editTemplate !== "") {
that.edit(current);
}
} else if (keys.ESC === key) {
editItem = element.find("." + KEDITITEM);
if (editItem.length === 0) {
return;
}
idx = that.items().index(editItem);
that.cancel();
that.element.trigger("focus");
that.current(that.items().eq(idx));
}
});
element.on("mousedown" + NS + " touchstart" + NS, FOCUSSELECTOR, proxy(clickCallback, that));
}
},
clearSelection: function() {
var that = this;
that.selectable.clear();
that.trigger(CHANGE);
},
select: function(items) {
var that = this, selectable = that.selectable;
items = $(items);
if (items.length) {
if (!selectable.options.multiple) {
selectable.clear();
items = items.first();
}
selectable.value(items);
return;
}
return selectable.value();
},
_destroyEditable: function() {
var that = this;
if (that.editable) {
that.editable.destroy();
delete that.editable;
}
},
_modelFromElement: function(element) {
var uid = element.attr(kendo.attr("uid"));
return this.dataSource.getByUid(uid);
},
_closeEditable: function(validate) {
var that = this, editable = that.editable, data, index, template = that.template, valid = true;
if (editable) {
if (validate) {
valid = editable.end();
}
if (valid) {
if (editable.element.index() % 2) {
template = that.altTemplate;
}
data = that._modelFromElement(editable.element);
that._destroyEditable();
index = editable.element.index();
editable.element.replaceWith(template(data));
that.items().eq(index).attr(kendo.attr("uid"), data.uid);
}
}
return valid;
},
edit: function(item) {
var that = this, data = that._modelFromElement(item), container, index = item.index();
that.cancel();
item.replaceWith(that.editTemplate(data));
container = that.items().eq(index).addClass(KEDITITEM).attr(kendo.attr("uid"), data.uid);
that.editable = container.kendoEditable({
model: data,
clearContainer: false,
errorTemplate: false
}).data("kendoEditable");
that.trigger(EDIT, {
model: data,
item: container
});
},
save: function() {
var that = this, editable = that.editable, model;
if (!editable) {
return;
}
editable = editable.element;
model = that._modelFromElement(editable);
if (!that.trigger(SAVE, {
model: model,
item: editable
}) && that._closeEditable(true)) {
that.dataSource.sync();
}
},
remove: function(item) {
var that = this, dataSource = that.dataSource, data = that._modelFromElement(item);
if (!that.trigger(REMOVE, {
model: data,
item: item
})) {
item.hide();
dataSource.remove(data);
dataSource.sync();
}
},
add: function() {
var that = this, dataSource = that.dataSource, index = dataSource.indexOf((dataSource.view() || [])[0]);
if (index < 0) {
index = 0;
}
that.cancel();
dataSource.insert(index, {});
that.edit(that.element.children().first());
},
cancel: function() {
var that = this, dataSource = that.dataSource;
if (that.editable) {
var container = that.editable.element;
var model = that._modelFromElement(container);
if (!that.trigger(CANCEL, {
model: model,
container: container
})) {
dataSource.cancelChanges(model);
that._closeEditable(false);
}
}
},
_crudHandlers: function() {
var that = this, clickNS = CLICK + NS;
that.element.on(clickNS, ".k-edit-button", function(e) {
var item = $(this).closest("[" + kendo.attr("uid") + "]");
that.edit(item);
e.preventDefault();
});
that.element.on(clickNS, ".k-delete-button", function(e) {
var item = $(this).closest("[" + kendo.attr("uid") + "]");
that.remove(item);
e.preventDefault();
});
that.element.on(clickNS, ".k-update-button", function(e) {
that.save();
e.preventDefault();
});
that.element.on(clickNS, ".k-cancel-button", function(e) {
that.cancel();
e.preventDefault();
});
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that._unbindDataSource();
that._destroyEditable();
that.element.off(NS);
if (that.pager) {
that.pager.destroy();
}
if (that.selectable) {
that.selectable.destroy();
}
kendo.destroy(that.element);
}
});
kendo.ui.plugin(ListView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, isPlainObject = $.isPlainObject, proxy = $.proxy, extend = $.extend, placeholderSupported = kendo.support.placeholder, browser = kendo.support.browser, isFunction = $.isFunction, trimSlashesRegExp = /(^\/|\/$)/g, CHANGE = "change", APPLY = "apply", ERROR = "error", CLICK = "click", NS = ".kendoImageBrowser", BREADCRUBMSNS = ".kendoBreadcrumbs", SEARCHBOXNS = ".kendoSearchBox", NAMEFIELD = "name", SIZEFIELD = "size", TYPEFIELD = "type", DEFAULTSORTORDER = {
field: TYPEFIELD,
dir: "asc"
}, ARRANGEBYTMPL = kendo.template('<li data-#=ns#value="#=value#" class="k-item">${text}</li>'), EMPTYTILE = kendo.template('<li class="k-tile-empty"><strong>${text}</strong></li>'), TOOLBARTMPL = '<div class="k-widget k-toolbar k-floatwrap">' + '<div class="k-toolbar-wrap">' + "#if(showUpload) { # " + '<div class="k-widget k-upload"><div class="k-button k-button-icontext k-button-bare k-upload-button">' + '<span class="k-icon k-add"></span>#=messages.uploadFile#<input type="file" name="file" /></div></div>' + "#}#" + "#if(showCreate) {#" + '<button type="button" class="k-button k-button-icon k-button-bare"><span class="k-icon k-addfolder"></span></button>' + "#}#" + "#if(showDelete) {#" + '<button type="button" class="k-button k-button-icon k-button-bare k-state-disabled"><span class="k-icon k-delete"></span></button> ' + "#}#" + "</div>" + '<div class="k-tiles-arrange">#=messages.orderBy#: <a href="\\#" class="k-link"><span>#=messages.orderByName#</span><span class="k-icon k-i-arrow-s"></span></a>' + "</div>" + "</div>";
extend(true, kendo.data, {
schemas: {
imagebrowser: {
data: function(data) {
return data.items || data || [];
},
model: {
id: "name",
fields: {
name: "name",
size: "size",
type: "type"
}
}
}
}
});
extend(true, kendo.data, {
transports: {
imagebrowser: kendo.data.RemoteTransport.extend({
init: function(options) {
kendo.data.RemoteTransport.fn.init.call(this, $.extend(true, {}, this.options, options));
},
_call: function(type, options) {
options.data = $.extend({}, options.data, {
path: this.options.path()
});
if (isFunction(this.options[type])) {
this.options[type].call(this, options);
} else {
kendo.data.RemoteTransport.fn[type].call(this, options);
}
},
read: function(options) {
this._call("read", options);
},
create: function(options) {
this._call("create", options);
},
destroy: function(options) {
this._call("destroy", options);
},
update: function() {},
options: {
read: {
type: "POST"
},
update: {
type: "POST"
},
create: {
type: "POST"
},
destroy: {
type: "POST"
}
}
})
}
});
function bindDragEventWrappers(element, onDragEnter, onDragLeave) {
var hideInterval, lastDrag;
element.on("dragenter" + NS, function() {
onDragEnter();
lastDrag = new Date();
if (!hideInterval) {
hideInterval = setInterval(function() {
var sinceLastDrag = new Date() - lastDrag;
if (sinceLastDrag > 100) {
onDragLeave();
clearInterval(hideInterval);
hideInterval = null;
}
}, 100);
}
}).on("dragover" + NS, function() {
lastDrag = new Date();
});
}
var offsetTop;
if (browser.msie && browser.version < 8) {
offsetTop = function(element) {
return element.offsetTop;
};
} else {
offsetTop = function(element) {
return element.offsetTop - $(element).height();
};
}
function fieldName(fields, name) {
var descriptor = fields[name];
if (isPlainObject(descriptor)) {
return descriptor.field || name;
}
return descriptor;
}
function concatPaths(path, name) {
if (path === undefined || !path.match(/\/$/)) {
path = (path || "") + "/";
}
return path + name;
}
function sizeFormatter(value) {
if (!value) {
return "";
}
var suffix = " bytes";
if (value >= 1073741824) {
suffix = " GB";
value /= 1073741824;
} else if (value >= 1048576) {
suffix = " MB";
value /= 1048576;
} else if (value >= 1024) {
suffix = " KB";
value /= 1024;
}
return Math.round(value * 100) / 100 + suffix;
}
var ImageBrowser = Widget.extend({
init: function(element, options) {
var that = this;
options = options || {};
Widget.fn.init.call(that, element, options);
that.element.addClass("k-imagebrowser");
that.element.on(CLICK + NS, ".k-toolbar button:not(.k-state-disabled):has(.k-delete)", proxy(that._deleteClick, that)).on(CLICK + NS, ".k-toolbar button:not(.k-state-disabled):has(.k-addfolder)", proxy(that._addClick, that)).on("keydown" + NS, "li.k-state-selected input", proxy(that._directoryKeyDown, that)).on("blur" + NS, "li.k-state-selected input", proxy(that._directoryBlur, that));
that._dataSource();
that.refresh();
that.path(that.options.path);
},
options: {
name: "ImageBrowser",
messages: {
uploadFile: "Upload",
orderBy: "Arrange by",
orderByName: "Name",
orderBySize: "Size",
directoryNotFound: "A directory with this name was not found.",
emptyFolder: "Empty Folder",
deleteFile: 'Are you sure you want to delete "{0}"?',
invalidFileType: 'The selected file "{0}" is not valid. Supported file types are {1}.',
overwriteFile: 'A file with name "{0}" already exists in the current directory. Do you want to overwrite it?',
dropFilesHere: "drop files here to upload",
search: "Search"
},
transport: {},
path: "/",
fileTypes: "*.png,*.gif,*.jpg,*.jpeg"
},
events: [ ERROR, CHANGE, APPLY ],
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.dataSource.unbind(ERROR, that._errorHandler);
that.element.add(that.list).add(that.toolbar).off(NS);
if (that.arrangeByPopup) {
that.arrangeByPopup.destroy();
}
kendo.destroy(that.element);
},
value: function() {
var that = this, selected = that._selectedItem(), path, imageUrl = that.options.transport.imageUrl;
if (selected && selected.get(that._getFieldName(TYPEFIELD)) === "f") {
path = concatPaths(that.path(), selected.get(that._getFieldName(NAMEFIELD))).replace(trimSlashesRegExp, "");
if (imageUrl) {
path = isFunction(imageUrl) ? imageUrl(path) : kendo.format(imageUrl, path);
}
return path;
}
},
_selectedItem: function() {
var listView = this.listView, selected = listView.select();
if (selected.length) {
return this.dataSource.getByUid(selected.attr(kendo.attr("uid")));
}
},
_toolbar: function() {
var that = this, template = kendo.template(TOOLBARTMPL), messages = that.options.messages, link, popup, arrangeBy = [ {
text: messages.orderByName,
value: "name",
ns: kendo.ns
}, {
text: messages.orderBySize,
value: "size",
ns: kendo.ns
} ];
that.toolbar = $(template({
messages: messages,
showUpload: that.options.transport.uploadUrl,
showCreate: that.options.transport.create,
showDelete: that.options.transport.destroy
})).appendTo(that.element).find(".k-upload input").kendoUpload({
multiple: false,
localization: {
dropFilesHere: messages.dropFilesHere
},
async: {
saveUrl: that.options.transport.uploadUrl,
autoUpload: true
},
upload: proxy(that._fileUpload, that)
}).end();
that.upload = that.toolbar.find(".k-upload input").data("kendoUpload");
link = that.toolbar.find(".k-tiles-arrange a");
that.arrangeByPopup = popup = $("<ul>" + kendo.render(ARRANGEBYTMPL, arrangeBy) + "</ul>").kendoPopup({
anchor: link
}).on(CLICK + NS, "li", function() {
var item = $(this), field = item.attr(kendo.attr("value"));
that.toolbar.find(".k-tiles-arrange a span:first").html(item.text());
popup.close();
that.orderBy(field);
}).data("kendoPopup");
link.on(CLICK + NS, function(e) {
e.preventDefault();
popup.toggle();
});
that._attachDropzoneEvents();
},
_attachDropzoneEvents: function() {
var that = this;
if (that.options.transport.uploadUrl) {
bindDragEventWrappers($(document.documentElement), $.proxy(that._dropEnter, that), $.proxy(that._dropLeave, that));
that._scrollHandler = proxy(that._positionDropzone, that);
}
},
_dropEnter: function() {
this._positionDropzone();
$(document).on("scroll" + NS, this._scrollHandler);
},
_dropLeave: function() {
this._removeDropzone();
$(document).off("scroll" + NS, this._scrollHandler);
},
_positionDropzone: function() {
var that = this, element = that.element, offset = element.offset();
that.toolbar.find(".k-dropzone").addClass("k-imagebrowser-dropzone").offset(offset).css({
width: element[0].clientWidth,
height: element[0].clientHeight,
lineHeight: element[0].clientHeight + "px"
});
},
_removeDropzone: function() {
this.toolbar.find(".k-dropzone").removeClass("k-imagebrowser-dropzone").css({
width: "",
height: "",
lineHeight: "",
top: "",
left: ""
});
},
_deleteClick: function() {
var that = this, item = that.listView.select(), message = kendo.format(that.options.messages.deleteFile, item.find("strong").text());
if (item.length && that._showMessage(message, "confirm")) {
that.listView.remove(item);
}
},
_addClick: function() {
this.createDirectory();
},
_fileUpload: function(e) {
var that = this, options = that.options, fileTypes = options.fileTypes, filterRegExp = new RegExp(("(" + fileTypes.split(",").join(")|(") + ")").replace(/\*\./g, ".*."), "i"), fileName = e.files[0].name, fileNameField = that._getFieldName(NAMEFIELD), sizeField = that._getFieldName(SIZEFIELD), model;
if (filterRegExp.test(fileName)) {
e.data = {
path: that.path()
};
model = that._createFile(fileName);
if (!model) {
e.preventDefault();
} else {
that.upload.one("success", function(e) {
model.set(fileNameField, e.response[fileNameField]);
model.set(sizeField, e.response[sizeField]);
that._tiles = that.listView.items().filter("[" + kendo.attr("type") + "=f]");
that._scroll();
});
}
} else {
e.preventDefault();
that._showMessage(kendo.format(options.messages.invalidFileType, fileName, fileTypes));
}
},
_findFile: function(name) {
var data = this.dataSource.data(), idx, result, typeField = this._getFieldName(TYPEFIELD), nameField = this._getFieldName(NAMEFIELD), length;
name = name.toLowerCase();
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].get(typeField) === "f" && data[idx].get(nameField).toLowerCase() === name) {
result = data[idx];
break;
}
}
return result;
},
_createFile: function(fileName) {
var that = this, idx, length, index = 0, model = {}, typeField = that._getFieldName(TYPEFIELD), view = that.dataSource.view(), file = that._findFile(fileName);
if (file && !that._showMessage(kendo.format(that.options.messages.overwriteFile, fileName), "confirm")) {
return null;
}
if (file) {
return file;
}
for (idx = 0, length = view.length; idx < length; idx++) {
if (view[idx].get(typeField) === "f") {
index = idx;
break;
}
}
model[typeField] = "f";
model[that._getFieldName(NAMEFIELD)] = fileName;
model[that._getFieldName(SIZEFIELD)] = 0;
return that.dataSource.insert(++index, model);
},
createDirectory: function() {
var that = this, idx, length, lastDirectoryIdx = 0, typeField = that._getFieldName(TYPEFIELD), nameField = that._getFieldName(NAMEFIELD), view = that.dataSource.data(), name = that._nameDirectory(), model = new that.dataSource.reader.model();
for (idx = 0, length = view.length; idx < length; idx++) {
if (view[idx].get(typeField) === "d") {
lastDirectoryIdx = idx;
}
}
model.set(typeField, "d");
model.set(nameField, name);
that.listView.one("dataBound", function() {
var selected = that.listView.items().filter("[" + kendo.attr("uid") + "=" + model.uid + "]"), input = selected.find("input");
if (selected.length) {
this.edit(selected);
}
this.element.scrollTop(selected.attr("offsetTop") - this.element[0].offsetHeight);
setTimeout(function() {
input.select();
});
}).one("save", function(e) {
var value = e.model.get(nameField);
if (!value) {
e.model.set(nameField, name);
} else {
e.model.set(nameField, that._nameExists(value, model.uid) ? that._nameDirectory() : value);
}
});
that.dataSource.insert(++lastDirectoryIdx, model);
},
_directoryKeyDown: function(e) {
if (e.keyCode == 13) {
e.currentTarget.blur();
}
},
_directoryBlur: function() {
this.listView.save();
},
_nameExists: function(name, uid) {
var data = this.dataSource.data(), typeField = this._getFieldName(TYPEFIELD), nameField = this._getFieldName(NAMEFIELD), idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].get(typeField) === "d" && data[idx].get(nameField).toLowerCase() === name.toLowerCase() && data[idx].uid !== uid) {
return true;
}
}
return false;
},
_nameDirectory: function() {
var name = "New folder", data = this.dataSource.data(), directoryNames = [], typeField = this._getFieldName(TYPEFIELD), nameField = this._getFieldName(NAMEFIELD), candidate, idx, length;
for (idx = 0, length = data.length; idx < length; idx++) {
if (data[idx].get(typeField) === "d" && data[idx].get(nameField).toLowerCase().indexOf(name.toLowerCase()) > -1) {
directoryNames.push(data[idx].get(nameField));
}
}
if ($.inArray(name, directoryNames) > -1) {
idx = 2;
do {
candidate = name + " (" + idx + ")";
idx++;
} while ($.inArray(candidate, directoryNames) > -1);
name = candidate;
}
return name;
},
orderBy: function(field) {
this.dataSource.sort([ {
field: this._getFieldName(TYPEFIELD),
dir: "asc"
}, {
field: this._getFieldName(field),
dir: "asc"
} ]);
},
search: function(name) {
this.dataSource.filter({
field: this._getFieldName(NAMEFIELD),
operator: "contains",
value: name
});
},
_content: function() {
var that = this;
that.list = $('<ul class="k-reset k-floats k-tiles" />').appendTo(that.element).on("scroll" + NS, proxy(that._scroll, that)).on("dblclick" + NS, "li", proxy(that._dblClick, that));
that.listView = new kendo.ui.ListView(that.list, {
dataSource: that.dataSource,
template: that._itemTmpl(),
editTemplate: that._editTmpl(),
selectable: true,
autoBind: false,
dataBinding: function(e) {
that.toolbar.find(".k-delete").parent().addClass("k-state-disabled");
if (e.action === "remove" || e.action === "sync") {
e.preventDefault();
}
},
dataBound: function() {
if (that.dataSource.view().length) {
that._tiles = this.items().filter("[" + kendo.attr("type") + "=f]");
that._scroll();
} else {
this.wrapper.append(EMPTYTILE({
text: that.options.messages.emptyFolder
}));
}
},
change: proxy(that._listViewChange, that)
});
},
_dblClick: function(e) {
var that = this, li = $(e.currentTarget);
if (li.filter("[" + kendo.attr("type") + "=d]").length) {
var folder = that.dataSource.getByUid(li.attr(kendo.attr("uid")));
if (folder) {
that.path(concatPaths(that.path(), folder.get(that._getFieldName(NAMEFIELD))));
that.breadcrumbs.value(that.path());
}
} else if (li.filter("[" + kendo.attr("type") + "=f]").length) {
that.trigger(APPLY);
}
},
_listViewChange: function() {
var selected = this._selectedItem();
if (selected) {
this.toolbar.find(".k-delete").parent().removeClass("k-state-disabled");
if (selected.get(this._getFieldName(TYPEFIELD)) === "f") {
this.trigger(CHANGE);
}
}
},
_dataSource: function() {
var that = this, options = that.options, transport = options.transport, typeSortOrder = extend({}, DEFAULTSORTORDER), nameSortOrder = {
field: NAMEFIELD,
dir: "asc"
}, schema, dataSource = {
type: transport.type || "imagebrowser",
sort: [ typeSortOrder, nameSortOrder ]
};
if (isPlainObject(transport)) {
transport.path = proxy(that.path, that);
dataSource.transport = transport;
}
if (isPlainObject(options.schema)) {
dataSource.schema = options.schema;
if (isPlainObject(options.schema.model) && options.schema.model.fields) {
typeSortOrder.field = fieldName(options.schema.model.fields, TYPEFIELD);
nameSortOrder.field = fieldName(options.schema.model.fields, NAMEFIELD);
}
} else if (transport.type && isPlainObject(kendo.data.schemas[transport.type])) {
schema = kendo.data.schemas[transport.type];
if (isPlainObject(schema.model) && schema.model.fields) {
typeSortOrder.field = fieldName(schema.model.fields, TYPEFIELD);
nameSortOrder.field = fieldName(schema.model.fields, NAMEFIELD);
}
}
if (that.dataSource && that._errorHandler) {
that.dataSource.unbind(ERROR, that._errorHandler);
} else {
that._errorHandler = proxy(that._error, that);
}
that.dataSource = kendo.data.DataSource.create(dataSource).bind(ERROR, that._errorHandler);
},
_navigation: function() {
var that = this, navigation = $('<div class="k-floatwrap"><input/><input/></div>').appendTo(this.element);
that.breadcrumbs = navigation.find("input:first").kendoBreadcrumbs({
value: that.options.path,
change: function() {
that.path(this.value());
}
}).data("kendoBreadcrumbs");
that.searchBox = navigation.parent().find("input:last").kendoSearchBox({
label: that.options.messages.search,
change: function() {
that.search(this.value());
}
}).data("kendoSearchBox");
},
_error: function(e) {
var that = this, status;
if (!that.trigger(ERROR, e)) {
status = e.xhr.status;
if (e.status == "error") {
if (status == "404") {
that._showMessage(that.options.messages.directoryNotFound);
} else if (status != "0") {
that._showMessage("Error! The requested URL returned " + status + " - " + e.xhr.statusText);
}
} else if (status == "timeout") {
that._showMessage("Error! Server timeout.");
}
}
},
_showMessage: function(message, type) {
return window[type || "alert"](message);
},
refresh: function() {
var that = this;
that._navigation();
that._toolbar();
that._content();
},
_loadImage: function(li) {
var that = this, element = $(li), dataItem = that.dataSource.getByUid(element.attr(kendo.attr("uid"))), name = dataItem.get(that._getFieldName(NAMEFIELD)), thumbnailUrl = that.options.transport.thumbnailUrl, img = $("<img />", {
alt: name
}).hide().on("load" + NS, function() {
$(this).prev().remove().end().addClass("k-image").fadeIn();
}), urlJoin = "?";
element.find(".k-loading").after(img);
if (isFunction(thumbnailUrl)) {
thumbnailUrl = thumbnailUrl(that.path(), encodeURIComponent(name));
} else {
if (thumbnailUrl.indexOf("?") >= 0) {
urlJoin = "&";
}
thumbnailUrl = thumbnailUrl + urlJoin + "path=" + that.path() + encodeURIComponent(name);
}
// IE8 will trigger the load event immediately when the src is assign
// if the image is loaded from the cache
img.attr("src", thumbnailUrl);
li.loaded = true;
},
_scroll: function() {
var that = this;
if (that.options.transport && that.options.transport.thumbnailUrl) {
clearTimeout(that._timeout);
that._timeout = setTimeout(function() {
var height = that.list.outerHeight(), viewTop = that.list.scrollTop(), viewBottom = viewTop + height;
that._tiles.each(function() {
var top = offsetTop(this), bottom = top + this.offsetHeight;
if (top >= viewTop && top < viewBottom || bottom >= viewTop && bottom < viewBottom) {
that._loadImage(this);
}
if (top > viewBottom) {
return false;
}
});
that._tiles = that._tiles.filter(function() {
return !this.loaded;
});
}, 250);
}
},
_editTmpl: function() {
var that = this, html = '<li class="k-tile k-state-selected" ' + kendo.attr("uid") + '="#=uid#" ';
html += kendo.attr("type") + '="${' + that._getFieldName(TYPEFIELD) + '}">';
html += "#if(" + that._getFieldName(TYPEFIELD) + ' == "d") { #';
html += '<div class="k-thumb"><span class="k-icon k-folder"></span></div>';
html += "#}else{#";
html += '<div class="k-thumb"><span class="k-icon k-loading"></span></div>';
html += "#}#";
html += "#if(" + that._getFieldName(TYPEFIELD) + ' == "d") { #';
html += '<input class="k-input" ' + kendo.attr("bind") + '="value:' + that._getFieldName(NAMEFIELD) + '"/>';
html += "#}#";
html += "</li>";
return proxy(kendo.template(html), {
sizeFormatter: sizeFormatter
});
},
_itemTmpl: function() {
var that = this, html = '<li class="k-tile" ' + kendo.attr("uid") + '="#=uid#" ';
html += kendo.attr("type") + '="${' + that._getFieldName(TYPEFIELD) + '}">';
html += "#if(" + that._getFieldName(TYPEFIELD) + ' == "d") { #';
html += '<div class="k-thumb"><span class="k-icon k-folder"></span></div>';
html += "#}else{#";
if (that.options.transport && that.options.transport.thumbnailUrl) {
html += '<div class="k-thumb"><span class="k-icon k-loading"></span></div>';
} else {
html += '<div class="k-thumb"><span class="k-icon k-file"></span></div>';
}
html += "#}#";
html += "<strong>${" + that._getFieldName(NAMEFIELD) + "}</strong>";
html += "#if(" + that._getFieldName(TYPEFIELD) + ' == "f") { # <span class="k-filesize">${this.sizeFormatter(' + that._getFieldName(SIZEFIELD) + ")}</span> #}#";
html += "</li>";
return proxy(kendo.template(html), {
sizeFormatter: sizeFormatter
});
},
_getFieldName: function(name) {
return fieldName(this.dataSource.reader.model.fields, name);
},
path: function(value) {
var that = this, path = that._path || "";
if (value !== undefined) {
that._path = value.replace(trimSlashesRegExp, "") + "/";
that.dataSource.read({
path: that._path
});
return;
}
if (path) {
path = path.replace(trimSlashesRegExp, "");
}
return path === "/" || path === "" ? "" : path + "/";
}
});
var SearchBox = Widget.extend({
init: function(element, options) {
var that = this;
options = options || {};
Widget.fn.init.call(that, element, options);
if (placeholderSupported) {
that.element.attr("placeholder", that.options.label);
}
that._wrapper();
that.element.on("keydown" + SEARCHBOXNS, proxy(that._keydown, that)).on("change" + SEARCHBOXNS, proxy(that._updateValue, that));
that.wrapper.on(CLICK + SEARCHBOXNS, "a", proxy(that._click, that));
if (!placeholderSupported) {
that.element.on("focus" + SEARCHBOXNS, proxy(that._focus, that)).on("blur" + SEARCHBOXNS, proxy(that._blur, that));
}
},
options: {
name: "SearchBox",
label: "Search",
value: ""
},
events: [ CHANGE ],
destroy: function() {
var that = this;
that.wrapper.add(that.element).add(that.label).off(SEARCHBOXNS);
Widget.fn.destroy.call(that);
},
_keydown: function(e) {
if (e.keyCode === 13) {
this._updateValue();
}
},
_click: function(e) {
e.preventDefault();
this._updateValue();
},
_updateValue: function() {
var that = this, value = that.element.val();
if (value !== that.value()) {
that.value(value);
that.trigger(CHANGE);
}
},
_blur: function() {
this._updateValue();
this._toggleLabel();
},
_toggleLabel: function() {
if (!placeholderSupported) {
this.label.toggle(!this.element.val());
}
},
_focus: function() {
this.label.hide();
},
_wrapper: function() {
var element = this.element, wrapper = element.parents(".k-search-wrap");
element[0].style.width = "";
element.addClass("k-input k-textbox");
if (!wrapper.length) {
wrapper = element.wrap($('<div class="k-widget k-search-wrap k-textbox"/>')).parent();
if (!placeholderSupported) {
$('<label style="display:block">' + this.options.label + "</label>").insertBefore(element);
}
$('<a href="#" class="k-icon k-i-search k-search"/>').appendTo(wrapper);
}
this.wrapper = wrapper;
this.label = wrapper.find(">label");
},
value: function(value) {
var that = this;
if (value !== undefined) {
that.options.value = value;
that.element.val(value);
that._toggleLabel();
return;
}
return that.options.value;
}
});
var Breadcrumbs = Widget.extend({
init: function(element, options) {
var that = this;
options = options || {};
Widget.fn.init.call(that, element, options);
that._wrapper();
that.wrapper.on("focus" + BREADCRUBMSNS, "input", proxy(that._focus, that)).on("blur" + BREADCRUBMSNS, "input", proxy(that._blur, that)).on("keydown" + BREADCRUBMSNS, "input", proxy(that._keydown, that)).on(CLICK + BREADCRUBMSNS, "a.k-i-arrow-n:first", proxy(that._rootClick, that)).on(CLICK + BREADCRUBMSNS, "a:not(.k-i-arrow-n)", proxy(that._click, that));
that.value(that.options.value);
},
options: {
name: "Breadcrumbs",
gap: 50
},
events: [ CHANGE ],
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.wrapper.add(that.wrapper.find("input")).add(that.wrapper.find("a")).off(BREADCRUBMSNS);
},
_update: function(val) {
val = (val || "").charAt(0) === "/" ? val : "/" + (val || "");
if (val !== this.value()) {
this.value(val);
this.trigger(CHANGE);
}
},
_click: function(e) {
e.preventDefault();
this._update(this._path($(e.target).prevAll("a:not(.k-i-arrow-n)").andSelf()));
},
_rootClick: function(e) {
e.preventDefault();
this._update("");
},
_focus: function() {
var that = this, element = that.element;
that.overlay.hide();
that.element.val(that.value());
setTimeout(function() {
element.select();
});
},
_blur: function() {
if (this.overlay.is(":visible")) {
return;
}
var that = this, element = that.element, val = element.val().replace(/\/{2,}/g, "/");
that.overlay.show();
element.val("");
that._update(val);
},
_keydown: function(e) {
var that = this;
if (e.keyCode === 13) {
that._blur();
setTimeout(function() {
that.overlay.find("a:first").focus();
});
}
},
_wrapper: function() {
var element = this.element, wrapper = element.parents(".k-breadcrumbs"), overlay;
element[0].style.width = "";
element.addClass("k-input");
if (!wrapper.length) {
wrapper = element.wrap($('<div class="k-widget k-breadcrumbs k-header k-state-default"/>')).parent();
}
overlay = wrapper.find(".k-breadcrumbs-wrap");
if (!overlay.length) {
overlay = $('<div class="k-breadcrumbs-wrap"/>').appendTo(wrapper);
}
this.wrapper = wrapper;
this.overlay = overlay;
},
refresh: function() {
var html = "", value = this.value(), segments, segment, idx, length;
if (value === undefined || !value.match(/^\//)) {
value = "/" + (value || "");
}
segments = value.split("/");
for (idx = 0, length = segments.length; idx < length; idx++) {
segment = segments[idx];
if (segment) {
if (!html) {
html += '<a href="#" class="k-icon k-i-arrow-n">root</a>';
}
html += '<a class="k-link" href="#">' + segments[idx] + "</a>";
html += '<span class="k-icon k-i-arrow-e">></span>';
}
}
this.overlay.empty().append($(html));
this._adjustSectionWidth();
},
_adjustSectionWidth: function() {
var that = this, wrapper = that.wrapper, width = wrapper.width() - that.options.gap, links = that.overlay.find("a"), a;
links.each(function(index) {
a = $(this);
if (a.parent().width() > width) {
if (index == links.length - 1) {
a.width(width);
} else {
a.prev().andSelf().hide();
}
}
});
},
value: function(val) {
if (val !== undefined) {
this._value = val.replace(/\/{2,}/g, "/");
this.refresh();
return;
}
return this._value;
},
_path: function(trail) {
return "/" + $.map(trail, function(b) {
return $(b).text();
}).join("/");
}
});
kendo.ui.plugin(ImageBrowser);
kendo.ui.plugin(Breadcrumbs);
kendo.ui.plugin(SearchBox);
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, Widget = kendo.ui.Widget, os = kendo.support.mobileOS, browser = kendo.support.browser, extend = $.extend, deepExtend = kendo.deepExtend, NS = ".kendoEditor", keys = kendo.keys;
// options can be: template (as string), cssClass, title, defaultValue
var ToolTemplate = Class.extend({
init: function(options) {
this.options = options;
},
getHtml: function() {
var options = this.options;
return kendo.template(options.template)({
cssClass: options.cssClass,
tooltip: options.title,
initialValue: options.initialValue
});
}
});
var EditorUtils = {
select: function(editor) {
editor.trigger("select", {});
},
editorWrapperTemplate: '<table cellspacing="4" cellpadding="0" class="k-widget k-editor k-header" role="presentation"><tbody>' + '<tr role="presentation"><td class="k-editor-toolbar-wrap" role="presentation"><ul class="k-editor-toolbar" role="toolbar"></ul></td></tr>' + '<tr><td class="k-editable-area"></td></tr>' + "</tbody></table>",
buttonTemplate: '<li class="k-editor-button" role="presentation">' + '<a href="" role="button" class="k-tool-icon #= cssClass #" unselectable="on" title="#= tooltip #">#= tooltip #</a>' + "</li>",
colorPickerTemplate: '<li class="k-editor-colorpicker" role="presentation"><div class="k-colorpicker #= cssClass #"></div></li>',
comboBoxTemplate: '<li class="k-editor-combobox">' + '<select title="#= tooltip #" class="#= cssClass #"></select>' + "</li>",
dropDownListTemplate: '<li class="k-editor-selectbox">' + '<select title="#= tooltip #" class="#= cssClass #"></select>' + "</li>",
separatorTemplate: '<li class="k-separator"></li>',
focusable: ".k-colorpicker,a.k-tool-icon:not(.k-state-disabled),.k-selectbox, .k-combobox .k-input",
wrapTextarea: function(textarea) {
var w = textarea[0].style.width, h = textarea[0].style.height, template = EditorUtils.editorWrapperTemplate, editorWrap = $(template).insertBefore(textarea).width(w).height(h), editArea = editorWrap.find(".k-editable-area");
textarea.appendTo(editArea).addClass("k-content k-raw-content").hide();
return textarea.closest(".k-editor");
},
renderTools: function(editor, tools) {
var editorTools = {}, currentTool, tool, i, nativeTools = editor._nativeTools, template, options, toolsArea = $(editor.element).closest(".k-editor").find(".k-editor-toolbar");
if (tools) {
for (i = 0; i < tools.length; i++) {
currentTool = tools[i];
options = null;
if ($.isPlainObject(currentTool)) {
if (currentTool.name && editor.tools[currentTool.name]) {
$.extend(editor.tools[currentTool.name].options, currentTool);
editorTools[currentTool.name] = editor.tools[currentTool.name];
options = editorTools[currentTool.name].options;
} else {
options = extend({
cssClass: "k-i-custom",
type: "button",
tooltip: ""
}, currentTool);
if (options.name) {
options.cssClass = "k-" + (options.name == "custom" ? "i-custom" : options.name);
}
if (!options.template) {
if (options.type == "button") {
options.template = EditorUtils.buttonTemplate;
}
}
}
} else if (editor.tools[currentTool]) {
editorTools[currentTool] = editor.tools[currentTool];
options = editorTools[currentTool].options;
}
if (!options) {
continue;
}
template = options.template;
if (template) {
if (template.getHtml) {
template = template.getHtml();
} else {
if (!$.isFunction(template)) {
template = kendo.template(template);
}
template = template(options);
}
if (template.indexOf("<li") !== 0) {
template = "<li class='k-editor-template'>" + template + "</li>";
}
tool = $(template).appendTo(toolsArea);
if (options.type == "button" && options.exec) {
tool.find(".k-tool-icon").click($.proxy(options.exec, editor.element[0]));
}
}
}
}
for (i = 0; i < nativeTools.length; i++) {
if (!editorTools[nativeTools[i]]) {
editorTools[nativeTools[i]] = editor.tools[nativeTools[i]];
}
}
editor.options.tools = editorTools;
},
decorateStyleToolItems: function(textarea) {
var selectBox = textarea.data.closest(".k-editor").find(".k-style").data("kendoSelectBox");
if (!selectBox) {
return;
}
var classes = selectBox.dataSource.view();
selectBox.list.find(".k-item").each(function(idx, element) {
var item = $(element), text = item.text(), style = kendo.ui.editor.Dom.inlineStyle(textarea.data.data("kendoEditor").document, "span", {
className: classes[idx].value
});
item.html('<span unselectable="on" style="display:block;' + style + '">' + text + "</span>");
});
},
createContentElement: function(textarea, stylesheets) {
var iframe, wnd, doc, rtlStyle = kendo.support.isRtl(textarea) ? "direction:rtl;" : "";
textarea.hide();
iframe = $("<iframe />", {
src: 'javascript:""',
frameBorder: "0"
}).css("display", "").addClass("k-content").insertBefore(textarea)[0];
wnd = iframe.contentWindow || iframe;
if (stylesheets.length > 0) {
$(iframe).one("load", textarea, EditorUtils.decorateStyleToolItems);
}
doc = wnd.document || iframe.contentDocument;
doc.open();
doc.write("<!DOCTYPE html><html><head>" + "<meta charset='utf-8' />" + "<style>" + "html,body{padding:0;margin:0;background:#fff;height:100%;min-height:100%;}" + "body{font: 12px/1.5 Verdana,Geneva,sans-serif;padding-top:1px;margin-top:-1px;" + "word-wrap: break-word;-webkit-nbsp-mode: space;-webkit-line-break: after-white-space;" + rtlStyle + "}" + "h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em}h3{font-size:1.16em}h4{font-size:1em}h5{font-size:.83em}h6{font-size:.7em}" + "p{margin:0 0 1em;padding:0 .2em}.k-marker{display:none;}.k-paste-container,.Apple-style-span{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}" + "ul,ol{padding-left:2.5em}" + "a{color:#00a}" + "code{font-size:1.23em}" + "</style>" + $.map(stylesheets, function(href) {
return "<link rel='stylesheet' href='" + href + "'>";
}).join("") + "</head><body contenteditable='true'></body></html>");
doc.close();
return wnd;
},
initializeContentElement: function(editor) {
var isFirstKeyDown = true;
editor.window = EditorUtils.createContentElement($(editor.textarea), editor.options.stylesheets);
editor.document = editor.window.contentDocument || editor.window.document;
editor.body = editor.document.body;
$(editor.document).on("keydown" + NS, function(e) {
var range;
if (e.keyCode === keys.F10) {
// Handling with timeout to avoid the default IE menu
setTimeout(function() {
var TABINDEX = "tabIndex", element = editor.wrapper, tabIndex = element.attr(TABINDEX);
// Chrome can't focus something which has already been focused
element.attr(TABINDEX, tabIndex || 0).focus().find("li:has(" + focusable + ")").first().focus();
if (!tabIndex && tabIndex !== 0) {
element.removeAttr(TABINDEX);
}
}, 100);
e.preventDefault();
return;
} else if (e.keyCode === keys.BACKSPACE) {
range = editor.getRange();
var ancestor, emptyParagraphContent = kendo.support.browser.msie ? "" : '<br _moz_dirty="" />', dom = kendo.ui.editor.Dom;
range.deleteContents();
ancestor = range.commonAncestorContainer;
if (dom.name(ancestor) === "p" && ancestor.innerHTML === "") {
ancestor.innerHTML = emptyParagraphContent;
range.setStart(ancestor, 0);
range.collapse(true);
editor.selectRange(range);
}
}
var toolName = editor.keyboard.toolFromShortcut(editor.options.tools, e);
if (toolName) {
e.preventDefault();
if (!/undo|redo/.test(toolName)) {
editor.keyboard.endTyping(true);
}
editor.exec(toolName);
return false;
}
if (editor.keyboard.isTypingKey(e) && editor.pendingFormats.hasPending()) {
if (isFirstKeyDown) {
isFirstKeyDown = false;
} else {
range = editor.getRange();
editor.pendingFormats.apply(range);
editor.selectRange(range);
}
}
editor.keyboard.clearTimeout();
editor.keyboard.keydown(e);
}).on("keyup" + NS, function(e) {
var selectionCodes = [ 8, 9, 33, 34, 35, 36, 37, 38, 39, 40, 40, 45, 46 ];
if ($.inArray(e.keyCode, selectionCodes) > -1 || e.keyCode == 65 && e.ctrlKey && !e.altKey && !e.shiftKey) {
editor.pendingFormats.clear();
select(editor);
}
if (editor.keyboard.isTypingKey(e)) {
if (editor.pendingFormats.hasPending()) {
var range = editor.getRange();
editor.pendingFormats.apply(range);
editor.selectRange(range);
}
} else {
isFirstKeyDown = true;
}
editor.keyboard.keyup(e);
}).on("mousedown" + NS, function(e) {
editor.pendingFormats.clear();
var target = $(e.target);
if (!browser.gecko && e.which == 2 && target.is("a[href]")) {
window.open(target.attr("href"), "_new");
}
}).on("mouseup" + NS, function() {
select(editor);
}).on("click" + NS, function(e) {
var dom = kendo.ui.editor.Dom, range;
if (dom.name(e.target) === "img") {
range = editor.createRange();
range.selectNode(e.target);
editor.selectRange(range);
}
});
$(editor.window).on("blur" + NS, function() {
var old = editor.textarea.value, value = editor.encodedValue();
editor.update();
if (value != old) {
editor.trigger("change");
}
});
$(editor.body).on("cut" + NS + " paste" + NS, function(e) {
editor.clipboard["on" + e.type](e);
});
},
formatByName: function(name, format) {
for (var i = 0; i < format.length; i++) {
if ($.inArray(name, format[i].tags) >= 0) {
return format[i];
}
}
},
registerTool: function(toolName, tool) {
if (tool.options && tool.options.template) {
tool.options.template.options.cssClass = "k-" + toolName;
}
Editor.fn._tools[toolName] = tool;
},
registerFormat: function(formatName, format) {
Editor.fn.options.formats[formatName] = format;
},
createDialog: function(windowContent, editor, initOptions) {
var isRtl = kendo.support.isRtl(editor.wrapper), win = $(windowContent).appendTo(document.body).kendoWindow(initOptions);
if (isRtl) {
win.closest(".k-window").addClass("k-rtl");
}
return win;
}
};
var select = EditorUtils.select, focusable = EditorUtils.focusable, wrapTextarea = EditorUtils.wrapTextarea, renderTools = EditorUtils.renderTools, initializeContentElement = EditorUtils.initializeContentElement;
var messages = {
bold: "Bold",
italic: "Italic",
underline: "Underline",
strikethrough: "Strikethrough",
superscript: "Superscript",
subscript: "Subscript",
justifyCenter: "Center text",
justifyLeft: "Align text left",
justifyRight: "Align text right",
justifyFull: "Justify",
insertUnorderedList: "Insert unordered list",
insertOrderedList: "Insert ordered list",
indent: "Indent",
outdent: "Outdent",
createLink: "Insert hyperlink",
unlink: "Remove hyperlink",
insertImage: "Insert image",
insertHtml: "Insert HTML",
viewHtml: "View HTML",
fontName: "Select font family",
fontNameInherit: "(inherited font)",
fontSize: "Select font size",
fontSizeInherit: "(inherited size)",
formatBlock: "Format",
foreColor: "Color",
backColor: "Background color",
style: "Styles",
emptyFolder: "Empty Folder",
uploadFile: "Upload",
orderBy: "Arrange by:",
orderBySize: "Size",
orderByName: "Name",
invalidFileType: 'The selected file "{0}" is not valid. Supported file types are {1}.',
deleteFile: 'Are you sure you want to delete "{0}"?',
overwriteFile: 'A file with name "{0}" already exists in the current directory. Do you want to overwrite it?',
directoryNotFound: "A directory with this name was not found.",
imageWebAddress: "Web address",
imageAltText: "Alternate text",
linkWebAddress: "Web address",
linkText: "Text",
linkToolTip: "ToolTip",
linkOpenInNewWindow: "Open link in new window",
dialogInsert: "Insert",
dialogButtonSeparator: "or",
dialogCancel: "Cancel"
};
var supportedBrowser = !os || os.ios && os.flatVersion >= 500 || !os.ios && typeof document.documentElement.contentEditable != "undefined";
var Editor = Widget.extend({
init: function(element, options) {
/* suppress initialization in mobile webkit devices (w/o proper contenteditable support) */
if (!supportedBrowser) {
return;
}
var that = this, wrapper, value, editorNS = kendo.ui.editor;
Widget.fn.init.call(that, element, options);
that.tools = deepExtend({}, kendo.ui.Editor.fn._tools);
that.options = deepExtend({}, that.options, options);
element = $(element);
element.closest("form").on("submit" + NS, function() {
that.update();
});
for (var id in that.tools) {
that.tools[id].name = id.toLowerCase();
}
that.textarea = element.attr("autocomplete", "off")[0];
wrapper = that.wrapper = wrapTextarea(element);
if (that.textarea.id) {
wrapper.find(".k-editor-toolbar").attr("aria-controls", that.textarea.id);
}
renderTools(that, that.options.tools);
initializeContentElement(that);
that.keyboard = new editorNS.Keyboard([ new editorNS.TypingHandler(that), new editorNS.SystemHandler(that) ]);
that.clipboard = new editorNS.Clipboard(this);
that.pendingFormats = new editorNS.PendingFormats(this);
that.undoRedoStack = new editorNS.UndoRedoStack();
if (options && options.value) {
value = options.value;
} else {
// indented HTML introduces problematic ranges in IE
value = element.val().replace(/[\r\n\v\f\t ]+/gi, " ");
}
that.value(value);
function toolFromClassName(element) {
var tool = $.grep(element.className.split(" "), function(x) {
return !/^k-(widget|tool-icon|state-hover|header|combobox|dropdown|selectbox|colorpicker)$/i.test(x);
});
return tool[0] ? tool[0].substring(tool[0].lastIndexOf("-") + 1) : "custom";
}
function appendShortcutSequence(localizedText, tool) {
if (!tool.key) {
return localizedText;
}
var res = localizedText + " (";
if (tool.ctrl) {
res += "Ctrl + ";
}
if (tool.shift) {
res += "Shift + ";
}
if (tool.alt) {
res += "Alt + ";
}
res += tool.key + ")";
return res;
}
var toolbarItems = ".k-editor-toolbar > li > *, .k-editor-toolbar > li select", buttons = ".k-editor-button .k-tool-icon", enabledButtons = buttons + ":not(.k-state-disabled)", disabledButtons = buttons + ".k-state-disabled";
wrapper.find(".k-combobox .k-input").keydown(function(e) {
var combobox = $(this).closest(".k-combobox").data("kendoComboBox"), key = e.keyCode;
if (key == keys.RIGHT || key == keys.LEFT) {
combobox.close();
} else if (key == keys.DOWN) {
if (!combobox.dropDown.isOpened()) {
e.stopImmediatePropagation();
combobox.open();
}
}
});
wrapper.on("mouseenter" + NS, enabledButtons, function() {
$(this).addClass("k-state-hover");
}).on("mouseleave" + NS, enabledButtons, function() {
$(this).removeClass("k-state-hover");
}).on("mousedown" + NS, buttons, false).on("keydown" + NS, focusable, function(e) {
var closestLi = $(this).closest("li"), focusableTool = "li:has(" + focusable + ")", focusElement, keyCode = e.keyCode;
if (keyCode == keys.RIGHT) {
focusElement = closestLi.nextAll(focusableTool).first().find(focusable);
} else if (keyCode == keys.LEFT) {
focusElement = closestLi.prevAll(focusableTool).first().find(focusable);
} else if (keyCode == keys.ESC) {
focusElement = that;
} else if (keyCode == keys.TAB && !(e.ctrlKey || e.altKey)) {
// skip tabbing to disabled tools, and focus the editing area when running out of tools
if (e.shiftKey) {
focusElement = closestLi.prevAll(focusableTool).first().find(focusable);
if (focusElement.length) {
e.preventDefault();
} else {
return;
}
} else {
e.preventDefault();
focusElement = closestLi.nextAll(focusableTool).first().find(focusable);
if (!focusElement.length) {
focusElement = that;
}
}
}
if (focusElement) {
focusElement.focus();
}
}).on("click" + NS, enabledButtons, function(e) {
e.preventDefault();
e.stopPropagation();
that.exec(toolFromClassName(this));
}).on("click" + NS, disabledButtons, function(e) {
e.preventDefault();
}).find(toolbarItems).each(function() {
var toolName = toolFromClassName(this), options = that.options, tool = options.tools[toolName], description = options.messages[toolName], $this = $(this);
if (!tool) {
return;
}
if (toolName == "fontSize" || toolName == "fontName") {
var inheritText = options.messages[toolName + "Inherit"] || messages[toolName + "Inherit"];
$this.find("input").val(inheritText).end().find("span.k-input").text(inheritText).end();
}
tool.initialize($this, {
title: appendShortcutSequence(description, tool),
editor: that
});
});
that.bind("select", function() {
var range = that.getRange();
var nodes = editorNS.RangeUtils.textNodes(range);
if (!nodes.length) {
nodes = [ range.startContainer ];
}
wrapper.find(toolbarItems).each(function() {
var tool = that.options.tools[toolFromClassName(this)];
if (tool) {
tool.update($(this), nodes, that.pendingFormats);
}
});
});
that._DOMNodeInsertedHandler = function(e) {
that._DOMNodeInserted(e);
};
that._endTypingHandler = function() {
that._endTyping();
};
$(document).on("DOMNodeInserted", that._DOMNodeInsertedHandler).on("mousedown", that._endTypingHandler);
kendo.notify(that);
},
_endTyping: function() {
var that = this;
try {
if (that.keyboard.isTypingInProgress()) {
that.keyboard.endTyping(true);
}
if (!that.selectionRestorePoint) {
that.selectionRestorePoint = new kendo.ui.editor.RestorePoint(that.getRange());
}
} catch (e) {}
},
_DOMNodeInserted: function(e) {
var that = this, wrapper = that.wrapper;
if ($.contains(e.target, wrapper[0]) || wrapper[0] == e.target) {
// preserve updated value before re-initializing
// don't use update() to prevent the editor from encoding the content too early
that.textarea.value = that.value();
wrapper.find("iframe").remove();
initializeContentElement(that);
that.value(that.textarea.value);
}
},
events: [ "select", "change", "execute", "error", "paste", "keydown", "keyup" ],
options: {
name: "Editor",
messages: messages,
formats: {},
encoded: true,
stylesheets: [],
dialogOptions: {
modal: true,
resizable: false,
draggable: true,
animation: false
},
fontName: [ {
text: "Arial",
value: "Arial,Helvetica,sans-serif"
}, {
text: "Courier New",
value: "'Courier New',Courier,monospace"
}, {
text: "Georgia",
value: "Georgia,serif"
}, {
text: "Impact",
value: "Impact,Charcoal,sans-serif"
}, {
text: "Lucida Console",
value: "'Lucida Console',Monaco,monospace"
}, {
text: "Tahoma",
value: "Tahoma,Geneva,sans-serif"
}, {
text: "Times New Roman",
value: "'Times New Roman',Times,serif"
}, {
text: "Trebuchet MS",
value: "'Trebuchet MS',Helvetica,sans-serif"
}, {
text: "Verdana",
value: "Verdana,Geneva,sans-serif"
} ],
fontSize: [ {
text: "1 (8pt)",
value: "xx-small"
}, {
text: "2 (10pt)",
value: "x-small"
}, {
text: "3 (12pt)",
value: "small"
}, {
text: "4 (14pt)",
value: "medium"
}, {
text: "5 (18pt)",
value: "large"
}, {
text: "6 (24pt)",
value: "x-large"
}, {
text: "7 (36pt)",
value: "xx-large"
} ],
formatBlock: [ {
text: "Paragraph",
value: "p"
}, {
text: "Quotation",
value: "blockquote"
}, {
text: "Heading 1",
value: "h1"
}, {
text: "Heading 2",
value: "h2"
}, {
text: "Heading 3",
value: "h3"
}, {
text: "Heading 4",
value: "h4"
}, {
text: "Heading 5",
value: "h5"
}, {
text: "Heading 6",
value: "h6"
} ],
tools: [ "bold", "italic", "underline", "strikethrough", "fontName", "fontSize", "foreColor", "backColor", "justifyLeft", "justifyCenter", "justifyRight", "justifyFull", "insertUnorderedList", "insertOrderedList", "indent", "outdent", "formatBlock", "createLink", "unlink", "insertImage" ]
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
$(that.window).add(that.document).add(that.wrapper).add(that.element.closest("form")).off(NS);
$(document).off("DOMNodeInserted", that._DOMNodeInsertedHandler).off("mousedown", that._endTypingHandler);
kendo.destroy(that.wrapper);
},
_nativeTools: [ "insertLineBreak", "insertParagraph", "redo", "undo", "insertHtml" ],
_tools: {
undo: {
options: {
key: "Z",
ctrl: true
}
},
redo: {
options: {
key: "Y",
ctrl: true
}
}
},
tools: {},
// tools collection is copied from _tools during initialization
value: function(html) {
var body = this.body, dom = kendo.ui.editor.Dom, currentHtml = kendo.ui.editor.Serializer.domToXhtml(body);
if (html === undefined) {
return currentHtml;
}
if (html == currentHtml) {
return;
}
this.pendingFormats.clear();
var onerrorRe = /onerror\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/i;
// handle null value passed as a parameter
html = (html || "").replace(/<!\[CDATA\[(.*)?\]\]>/g, "<!--[CDATA[$1]]-->").replace(/<script([^>]*)>(.*)?<\/script>/gi, "<telerik:script $1>$2</telerik:script>").replace(/<img([^>]*)>/gi, function(match) {
return match.replace(onerrorRe, "");
}).replace(/(<\/?img[^>]*>)[\r\n\v\f\t ]+/gi, "$1");
if (!browser.msie) {
// Add <br/>s to empty paragraphs in mozilla/chrome, to make them focusable
html = html.replace(/<p([^>]*)>(\s*)?<\/p>/gi, '<p $1><br _moz_dirty="" /></p>');
}
if (browser.msie && browser.version < 9) {
// Internet Explorer removes comments from the beginning of the html
html = "<br/>" + html;
var originalSrc = "originalsrc", originalHref = "originalhref";
// IE < 8 makes href and src attributes absolute
html = html.replace(/href\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalHref + '="$1"');
html = html.replace(/src\s*=\s*(?:'|")?([^'">\s]*)(?:'|")?/, originalSrc + '="$1"');
body.innerHTML = html;
dom.remove(body.firstChild);
$(body).find("telerik\\:script,script,link,img,a").each(function() {
var node = this;
if (node[originalHref]) {
node.setAttribute("href", node[originalHref]);
node.removeAttribute(originalHref);
}
if (node[originalSrc]) {
node.setAttribute("src", node[originalSrc]);
node.removeAttribute(originalSrc);
}
});
} else {
body.innerHTML = html;
if (browser.msie) {
// having unicode characters creates denormalized DOM tree in IE9
dom.normalize(body);
setTimeout(function() {
// fix for IE9 OL bug -- https://connect.microsoft.com/IE/feedback/details/657695/ordered-list-numbering-changes-from-correct-to-0-0
var ols = body.getElementsByTagName("ol"), i, ol, originalStart;
for (i = 0; i < ols.length; i++) {
ol = ols[i];
originalStart = ol.getAttribute("start");
ol.setAttribute("start", 1);
if (originalStart) {
ol.setAttribute("start", originalStart);
} else {
ol.removeAttribute(originalStart);
}
}
}, 1);
}
}
this.selectionRestorePoint = null;
this.update();
},
focus: function() {
this.window.focus();
},
update: function(value) {
this.textarea.value = value || this.options.encoded ? this.encodedValue() : this.value();
},
encodedValue: function() {
return kendo.ui.editor.Dom.encode(this.value());
},
createRange: function(document) {
return kendo.ui.editor.RangeUtils.createRange(document || this.document);
},
getSelection: function() {
return kendo.ui.editor.SelectionUtils.selectionFromDocument(this.document);
},
selectRange: function(range) {
this.focus();
var selection = this.getSelection();
selection.removeAllRanges();
selection.addRange(range);
},
getRange: function() {
var selection = this.getSelection(), range = selection.rangeCount > 0 ? selection.getRangeAt(0) : this.createRange(), doc = this.document;
if (range.startContainer == doc && range.endContainer == doc && !range.startOffset && !range.endOffset) {
range.setStart(this.body, 0);
range.collapse(true);
}
return range;
},
selectedHtml: function() {
return kendo.ui.editor.Serializer.domToXhtml(this.getRange().cloneContents());
},
paste: function(html) {
this.clipboard.paste(html);
},
exec: function(name, params) {
var that = this, range, body, id, tool = "", pendingTool;
name = name.toLowerCase();
// restore selection
if (!that.keyboard.isTypingInProgress()) {
that.focus();
range = that.getRange();
body = that.document.body;
}
// exec tool
for (id in that.options.tools) {
if (id.toLowerCase() == name) {
tool = that.options.tools[id];
break;
}
}
if (tool) {
range = that.getRange();
if (!/undo|redo/i.test(name) && tool.willDelayExecution(range)) {
// clone our tool to apply params only once
pendingTool = $.extend({}, tool);
$.extend(pendingTool.options, {
params: params
});
that.pendingFormats.toggle(pendingTool);
select(that);
return;
}
var command = tool.command ? tool.command(extend({
range: range
}, params)) : null;
that.trigger("execute", {
name: name,
command: command
});
if (/undo|redo/i.test(name)) {
that.undoRedoStack[name]();
} else if (command) {
if (!command.managesUndoRedo) {
that.undoRedoStack.push(command);
}
command.editor = that;
command.exec();
if (command.async) {
command.change = $.proxy(function() {
select(that);
}, that);
return;
}
}
select(that);
}
}
});
kendo.ui.plugin(Editor);
var Tool = Class.extend({
init: function(options) {
this.options = options;
},
initialize: function(ui, options) {
ui.attr({
unselectable: "on",
title: options.title
});
},
command: function(commandArguments) {
return new this.options.command(commandArguments);
},
update: function() {},
willDelayExecution: function() {
return false;
}
});
Tool.exec = function(editor, name, value) {
editor.exec(name, {
value: value
});
};
var FormatTool = Tool.extend({
init: function(options) {
Tool.fn.init.call(this, options);
},
command: function(commandArguments) {
var that = this;
return new kendo.ui.editor.FormatCommand(extend(commandArguments, {
formatter: that.options.formatter
}));
},
update: function($ui, nodes, pendingFormats) {
var isPending = pendingFormats.isPending(this.name), isFormatted = this.options.finder.isFormatted(nodes), isActive = isPending ? !isFormatted : isFormatted;
$ui.toggleClass("k-state-active", isActive);
$ui.attr("aria-pressed", isActive);
}
});
EditorUtils.registerTool("separator", new Tool({
template: new ToolTemplate({
template: EditorUtils.separatorTemplate
})
}));
// Exports ================================================================
extend(kendo.ui, {
editor: {
ToolTemplate: ToolTemplate,
EditorUtils: EditorUtils,
Tool: Tool,
FormatTool: FormatTool
}
});
})(window.jQuery);
(function($) {
var kendo = window.kendo, map = $.map, extend = $.extend, browser = kendo.support.browser, STYLE = "style", FLOAT = "float", CSSFLOAT = "cssFloat", STYLEFLOAT = "styleFloat", CLASS = "class", KMARKER = "k-marker";
function makeMap(items) {
var obj = {}, i, len;
for (i = 0, len = items.length; i < len; i++) {
obj[items[i]] = true;
}
return obj;
}
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed".split(",")), blockElements = "div,p,h1,h2,h3,h4,h5,h6,address,applet,blockquote,button,center,dd,dir,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,pre,script,table,tbody,td,tfoot,th,thead,tr,ul".split(","), block = makeMap(blockElements), inlineElements = "span,em,a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,strike,strong,sub,sup,textarea,tt,u,var".split(","), inline = makeMap(inlineElements), fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected".split(","));
var normalize = function(node) {
if (node.nodeType == 1) {
node.normalize();
}
};
if (browser.msie && browser.version >= 8) {
normalize = function(parent) {
if (parent.nodeType == 1 && parent.firstChild) {
var prev = parent.firstChild, node = prev;
while (true) {
node = node.nextSibling;
if (!node) {
break;
}
if (node.nodeType == 3 && prev.nodeType == 3) {
node.nodeValue = prev.nodeValue + node.nodeValue;
Dom.remove(prev);
}
prev = node;
}
}
};
}
var whitespace = /^\s+$/, rgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i, amp = /&/g, openTag = /</g, closeTag = />/g, nbsp = /\u00a0/g, bom = /\ufeff/g, persistedScrollTop;
var cssAttributes = ("color,padding-left,padding-right,padding-top,padding-bottom," + "background-color,background-attachment,background-image,background-position,background-repeat," + "border-top-style,border-top-width,border-top-color," + "border-bottom-style,border-bottom-width,border-bottom-color," + "border-left-style,border-left-width,border-left-color," + "border-right-style,border-right-width,border-right-color," + "font-family,font-size,font-style,font-variant,font-weight,line-height").split(",");
var Dom = {
findNodeIndex: function(node) {
var i = 0;
while (true) {
node = node.previousSibling;
if (!node) {
break;
}
i++;
}
return i;
},
isDataNode: function(node) {
return node && node.nodeValue !== null && node.data !== null;
},
isAncestorOf: function(parent, node) {
try {
return !Dom.isDataNode(parent) && ($.contains(parent, Dom.isDataNode(node) ? node.parentNode : node) || node.parentNode == parent);
} catch (e) {
return false;
}
},
isAncestorOrSelf: function(root, node) {
return Dom.isAncestorOf(root, node) || root == node;
},
findClosestAncestor: function(root, node) {
if (Dom.isAncestorOf(root, node)) {
while (node && node.parentNode != root) {
node = node.parentNode;
}
}
return node;
},
getNodeLength: function(node) {
return Dom.isDataNode(node) ? node.length : node.childNodes.length;
},
splitDataNode: function(node, offset) {
var newNode = node.cloneNode(false), denormalizedText = "", iterator = node;
while (iterator.nextSibling && iterator.nextSibling.nodeType == 3 && iterator.nextSibling.nodeValue) {
denormalizedText += iterator.nextSibling.nodeValue;
iterator = iterator.nextSibling;
}
node.deleteData(offset, node.length);
newNode.deleteData(0, offset);
newNode.nodeValue += denormalizedText;
Dom.insertAfter(newNode, node);
},
attrEquals: function(node, attributes) {
for (var key in attributes) {
var value = node[key];
if (key == FLOAT) {
value = node[$.support.cssFloat ? CSSFLOAT : STYLEFLOAT];
}
if (typeof value == "object") {
if (!Dom.attrEquals(value, attributes[key])) {
return false;
}
} else if (value != attributes[key]) {
return false;
}
}
return true;
},
blockParentOrBody: function(node) {
return Dom.parentOfType(node, blockElements) || node.ownerDocument.body;
},
blockParents: function(nodes) {
var blocks = [], i, len;
for (i = 0, len = nodes.length; i < len; i++) {
var block = Dom.parentOfType(nodes[i], Dom.blockElements);
if (block && $.inArray(block, blocks) < 0) {
blocks.push(block);
}
}
return blocks;
},
windowFromDocument: function(document) {
return document.defaultView || document.parentWindow;
},
normalize: normalize,
blockElements: blockElements,
inlineElements: inlineElements,
empty: empty,
fillAttrs: fillAttrs,
toHex: function(color) {
var matches = rgb.exec(color);
if (!matches) {
return color;
}
return "#" + map(matches.slice(1), function(x) {
x = parseInt(x, 10).toString(16);
return x.length > 1 ? x : "0" + x;
}).join("");
},
encode: function(value) {
return value.replace(amp, "&").replace(openTag, "<").replace(closeTag, ">").replace(nbsp, " ");
},
name: function(node) {
return node.nodeName.toLowerCase();
},
significantChildNodes: function(node) {
return $.grep(node.childNodes, function(child) {
return child.nodeType != 3 || !Dom.isWhitespace(child);
});
},
lastTextNode: function(node) {
var result = null;
if (node.nodeType == 3) {
return node;
}
for (var child = node.lastChild; child; child = child.previousSibling) {
result = Dom.lastTextNode(child);
if (result) {
return result;
}
}
return result;
},
is: function(node, nodeName) {
return Dom.name(node) == nodeName;
},
isMarker: function(node) {
return node.className == KMARKER;
},
isWhitespace: function(node) {
return whitespace.test(node.nodeValue);
},
isBlock: function(node) {
return block[Dom.name(node)];
},
isEmpty: function(node) {
return empty[Dom.name(node)];
},
isInline: function(node) {
return inline[Dom.name(node)];
},
scrollContainer: function(doc) {
var wnd = Dom.windowFromDocument(doc), scrollContainer = (wnd.contentWindow || wnd).document || wnd.ownerDocument || wnd;
if (kendo.support.browser.webkit || scrollContainer.compatMode == "BackCompat") {
scrollContainer = scrollContainer.body;
} else {
scrollContainer = scrollContainer.documentElement;
}
return scrollContainer;
},
scrollTo: function(node) {
var element = $(Dom.isDataNode(node) ? node.parentNode : node), wnd = Dom.windowFromDocument(node.ownerDocument), windowHeight = wnd.innerHeight, elementTop, elementHeight, scrollContainer = Dom.scrollContainer(node.ownerDocument);
if (Dom.name(element[0]) == "br") {
element = element.parent();
}
elementTop = element.offset().top;
elementHeight = element[0].offsetHeight;
if (elementHeight + elementTop > scrollContainer.scrollTop + windowHeight) {
scrollContainer.scrollTop = elementHeight + elementTop - windowHeight;
}
},
persistScrollTop: function(doc) {
persistedScrollTop = Dom.scrollContainer(doc).scrollTop;
},
restoreScrollTop: function(doc) {
Dom.scrollContainer(doc).scrollTop = persistedScrollTop;
},
insertAt: function(parent, newElement, position) {
parent.insertBefore(newElement, parent.childNodes[position] || null);
},
insertBefore: function(newElement, referenceElement) {
if (referenceElement.parentNode) {
return referenceElement.parentNode.insertBefore(newElement, referenceElement);
} else {
return referenceElement;
}
},
insertAfter: function(newElement, referenceElement) {
return referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
},
remove: function(node) {
node.parentNode.removeChild(node);
},
trim: function(parent) {
for (var i = parent.childNodes.length - 1; i >= 0; i--) {
var node = parent.childNodes[i];
if (Dom.isDataNode(node)) {
if (!node.nodeValue.replace(bom, "").length) {
Dom.remove(node);
}
if (Dom.isWhitespace(node)) {
Dom.insertBefore(node, parent);
}
} else if (node.className != KMARKER) {
Dom.trim(node);
if (!node.childNodes.length && !Dom.isEmpty(node)) {
Dom.remove(node);
}
}
}
return parent;
},
parentOfType: function(node, tags) {
do {
node = node.parentNode;
} while (node && !Dom.ofType(node, tags));
return node;
},
ofType: function(node, tags) {
return $.inArray(Dom.name(node), tags) >= 0;
},
changeTag: function(referenceElement, tagName) {
var newElement = Dom.create(referenceElement.ownerDocument, tagName), attributes = referenceElement.attributes, i, len, name, value, attribute;
for (i = 0, len = attributes.length; i < len; i++) {
attribute = attributes[i];
if (attribute.specified) {
// IE < 8 cannot set class or style via setAttribute
name = attribute.nodeName;
value = attribute.nodeValue;
if (name == CLASS) {
newElement.className = value;
} else if (name == STYLE) {
newElement.style.cssText = referenceElement.style.cssText;
} else {
newElement.setAttribute(name, value);
}
}
}
while (referenceElement.firstChild) {
newElement.appendChild(referenceElement.firstChild);
}
Dom.insertBefore(newElement, referenceElement);
Dom.remove(referenceElement);
return newElement;
},
wrap: function(node, wrapper) {
Dom.insertBefore(wrapper, node);
wrapper.appendChild(node);
return wrapper;
},
unwrap: function(node) {
var parent = node.parentNode;
while (node.firstChild) {
parent.insertBefore(node.firstChild, node);
}
parent.removeChild(node);
},
create: function(document, tagName, attributes) {
return Dom.attr(document.createElement(tagName), attributes);
},
attr: function(element, attributes) {
attributes = extend({}, attributes);
if (attributes && STYLE in attributes) {
Dom.style(element, attributes.style);
delete attributes.style;
}
for (var attr in attributes) {
if (attributes[attr] === null) {
element.removeAttribute(attr);
delete attributes[attr];
}
}
return extend(element, attributes);
},
style: function(node, value) {
$(node).css(value || {});
},
unstyle: function(node, value) {
for (var key in value) {
if (key == FLOAT) {
key = $.support.cssFloat ? CSSFLOAT : STYLEFLOAT;
}
node.style[key] = "";
}
if (node.style.cssText === "") {
node.removeAttribute(STYLE);
}
},
inlineStyle: function(document, name, attributes) {
var span = $(Dom.create(document, name, attributes)), style;
document.body.appendChild(span[0]);
style = map(cssAttributes, function(value) {
if (browser.msie && value == "line-height" && span.css(value) == "1px") {
return "line-height:1.5";
} else {
return value + ":" + span.css(value);
}
}).join(";");
span.remove();
return style;
},
removeClass: function(node, classNames) {
var className = " " + node.className + " ", classes = classNames.split(" "), i, len;
for (i = 0, len = classes.length; i < len; i++) {
className = className.replace(" " + classes[i] + " ", " ");
}
className = $.trim(className);
if (className.length) {
node.className = className;
} else {
node.removeAttribute(CLASS);
}
},
commonAncestor: function() {
var count = arguments.length, paths = [], minPathLength = Infinity, output = null, i, ancestors, node, first, j;
if (!count) {
return null;
}
if (count == 1) {
return arguments[0];
}
for (i = 0; i < count; i++) {
ancestors = [];
node = arguments[i];
while (node) {
ancestors.push(node);
node = node.parentNode;
}
paths.push(ancestors.reverse());
minPathLength = Math.min(minPathLength, ancestors.length);
}
if (count == 1) {
return paths[0][0];
}
for (i = 0; i < minPathLength; i++) {
first = paths[0][i];
for (j = 1; j < count; j++) {
if (first != paths[j][i]) {
return output;
}
}
output = first;
}
return output;
}
};
kendo.ui.editor.Dom = Dom;
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var kendo = window.kendo, Editor = kendo.ui.editor, dom = Editor.Dom, extend = $.extend;
var fontSizeMappings = "xx-small,x-small,small,medium,large,x-large,xx-large".split(","), quoteRe = /"/g, brRe = /<br[^>]*>/i, emptyPRe = /<p><\/p>/i, cssDeclaration = /([\w|\-]+)\s*:\s*([^;]+);?/i;
var Serializer = {
domToXhtml: function(root) {
var result = [];
var tagMap = {
"telerik:script": {
start: function(node) {
result.push("<script");
attr(node);
result.push(">");
},
end: function() {
result.push("</script>");
}
},
b: {
start: function() {
result.push("<strong>");
},
end: function() {
result.push("</strong>");
}
},
i: {
start: function() {
result.push("<em>");
},
end: function() {
result.push("</em>");
}
},
u: {
start: function() {
result.push('<span style="text-decoration:underline;">');
},
end: function() {
result.push("</span>");
}
},
iframe: {
start: function(node) {
result.push("<iframe");
attr(node);
result.push(">");
},
end: function() {
result.push("</iframe>");
}
},
font: {
start: function(node) {
result.push('<span style="');
var color = node.getAttribute("color");
var size = fontSizeMappings[node.getAttribute("size")];
var face = node.getAttribute("face");
if (color) {
result.push("color:");
result.push(dom.toHex(color));
result.push(";");
}
if (face) {
result.push("font-face:");
result.push(face);
result.push(";");
}
if (size) {
result.push("font-size:");
result.push(size);
result.push(";");
}
result.push('">');
},
end: function() {
result.push("</span>");
}
}
};
function attr(node) {
var specifiedAttributes = [], attributes = node.attributes, attribute, i, l, trim = $.trim;
if (dom.is(node, "img")) {
var width = node.style.width, height = node.style.height, $node = $(node);
if (width) {
$node.attr("width", parseInt(width, 10));
dom.unstyle(node, {
width: undefined
});
}
if (height) {
$node.attr("height", parseInt(height, 10));
dom.unstyle(node, {
height: undefined
});
}
}
for (i = 0, l = attributes.length; i < l; i++) {
attribute = attributes[i];
var name = attribute.nodeName;
// In IE < 8 the 'value' attribute is not returned as 'specified'. The same goes for type="text"
if (attribute.specified || name == "value" && !node.value || name == "type" && attribute.nodeValue == "text") {
// altHtml is injected by IE8 when an <object> tag is used in the Editor
if (name.indexOf("_moz") < 0 && name != "complete" && name != "altHtml") {
specifiedAttributes.push(attribute);
}
}
}
if (!specifiedAttributes.length) {
return;
}
specifiedAttributes.sort(function(a, b) {
return a.nodeName > b.nodeName ? 1 : a.nodeName < b.nodeName ? -1 : 0;
});
for (i = 0, l = specifiedAttributes.length; i < l; i++) {
attribute = specifiedAttributes[i];
var attributeName = attribute.nodeName;
var attributeValue = attribute.nodeValue;
result.push(" ");
result.push(attributeName);
result.push('="');
if (attributeName == "style") {
// In IE < 8 the style attribute does not return proper nodeValue
var css = trim(attributeValue || node.style.cssText).split(";");
for (var cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
var pair = css[cssIndex];
if (pair.length) {
var match = cssDeclaration.exec(pair);
var property = trim(match[1].toLowerCase()), value = trim(match[2]);
if (property == "font-size-adjust" || property == "font-stretch") {
continue;
}
if (property.indexOf("color") >= 0) {
value = dom.toHex(value);
}
if (property.indexOf("font") >= 0) {
value = value.replace(quoteRe, "'");
}
result.push(property);
result.push(":");
result.push(value);
result.push(";");
}
}
} else if (attributeName == "src" || attributeName == "href") {
result.push(node.getAttribute(attributeName, 2));
} else {
result.push(dom.fillAttrs[attributeName] ? attributeName : attributeValue);
}
result.push('"');
}
}
function children(node, skip) {
for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
child(childNode, skip);
}
}
function child(node, skip) {
var nodeType = node.nodeType, tagName, mapper, parent, value, previous;
if (nodeType == 1) {
tagName = dom.name(node);
if (!tagName || (node.attributes._moz_dirty || node.attributes._moz_editor_bogus_node) && dom.is(node, "br") || node.className == "k-marker") {
return;
}
mapper = tagMap[tagName];
if (mapper) {
mapper.start(node);
children(node);
mapper.end(node);
return;
}
result.push("<");
result.push(tagName);
attr(node);
if (dom.empty[tagName]) {
result.push(" />");
} else {
result.push(">");
children(node, skip || dom.is(node, "pre"));
result.push("</");
result.push(tagName);
result.push(">");
}
} else if (nodeType == 3) {
value = node.nodeValue;
if (!skip && $.support.leadingWhitespace) {
parent = node.parentNode;
previous = node.previousSibling;
if (!previous) {
previous = (dom.isInline(parent) ? parent : node).previousSibling;
}
if (!previous || previous.innerHTML === "" || dom.isBlock(previous)) {
value = value.replace(/^[\r\n\v\f\t ]+/, "");
}
value = value.replace(/ +/, " ");
}
result.push(dom.encode(value));
} else if (nodeType == 4) {
result.push("<![CDATA[");
result.push(node.data);
result.push("]]>");
} else if (nodeType == 8) {
if (node.data.indexOf("[CDATA[") < 0) {
result.push("<!--");
result.push(node.data);
result.push("-->");
} else {
result.push("<!");
result.push(node.data);
result.push(">");
}
}
}
children(root);
result = result.join("");
// if serialized dom contains only whitespace elements, consider it empty (required field validation)
if (result.replace(brRe, "").replace(emptyPRe, "") === "") {
return "";
}
return result;
}
};
extend(Editor, {
Serializer: Serializer
});
})(window.kendo.jQuery);
(function($) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, browser = kendo.support.browser, dom = Editor.Dom, findNodeIndex = dom.findNodeIndex, isDataNode = dom.isDataNode, findClosestAncestor = dom.findClosestAncestor, getNodeLength = dom.getNodeLength, normalize = dom.normalize;
var SelectionUtils = {
selectionFromWindow: function(window) {
if (!("getSelection" in window)) {
return new W3CSelection(window.document);
}
return window.getSelection();
},
selectionFromRange: function(range) {
var rangeDocument = RangeUtils.documentFromRange(range);
return SelectionUtils.selectionFromDocument(rangeDocument);
},
selectionFromDocument: function(document) {
return SelectionUtils.selectionFromWindow(dom.windowFromDocument(document));
}
};
var W3CRange = Class.extend({
init: function(doc) {
$.extend(this, {
ownerDocument: doc,
/* not part of the spec; used when cloning ranges, traversing the dom and creating fragments */
startContainer: doc,
endContainer: doc,
commonAncestorContainer: doc,
startOffset: 0,
endOffset: 0,
collapsed: true
});
},
// Positioning Methods
setStart: function(node, offset) {
this.startContainer = node;
this.startOffset = offset;
updateRangeProperties(this);
fixIvalidRange(this, true);
},
setEnd: function(node, offset) {
this.endContainer = node;
this.endOffset = offset;
updateRangeProperties(this);
fixIvalidRange(this, false);
},
setStartBefore: function(node) {
this.setStart(node.parentNode, findNodeIndex(node));
},
setStartAfter: function(node) {
this.setStart(node.parentNode, findNodeIndex(node) + 1);
},
setEndBefore: function(node) {
this.setEnd(node.parentNode, findNodeIndex(node));
},
setEndAfter: function(node) {
this.setEnd(node.parentNode, findNodeIndex(node) + 1);
},
selectNode: function(node) {
this.setStartBefore(node);
this.setEndAfter(node);
},
selectNodeContents: function(node) {
this.setStart(node, 0);
this.setEnd(node, node[node.nodeType === 1 ? "childNodes" : "nodeValue"].length);
},
collapse: function(toStart) {
var that = this;
if (toStart) {
that.setEnd(that.startContainer, that.startOffset);
} else {
that.setStart(that.endContainer, that.endOffset);
}
},
// Editing Methods
deleteContents: function() {
var that = this, range = that.cloneRange();
if (that.startContainer != that.commonAncestorContainer) {
that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
}
that.collapse(true);
(function deleteSubtree(iterator) {
while (iterator.next()) {
if (iterator.hasPartialSubtree()) {
deleteSubtree(iterator.getSubtreeIterator());
} else {
iterator.remove();
}
}
})(new RangeIterator(range));
},
cloneContents: function() {
// clone subtree
var document = RangeUtils.documentFromRange(this);
return function cloneSubtree(iterator) {
var node, frag = document.createDocumentFragment();
while (node = iterator.next()) {
node = node.cloneNode(!iterator.hasPartialSubtree());
if (iterator.hasPartialSubtree()) {
node.appendChild(cloneSubtree(iterator.getSubtreeIterator()));
}
frag.appendChild(node);
}
return frag;
}(new RangeIterator(this));
},
extractContents: function() {
var that = this, range = that.cloneRange();
if (that.startContainer != that.commonAncestorContainer) {
that.setStartAfter(findClosestAncestor(that.commonAncestorContainer, that.startContainer));
}
that.collapse(true);
var document = RangeUtils.documentFromRange(that);
return function extractSubtree(iterator) {
var node, frag = document.createDocumentFragment();
while (node = iterator.next()) {
if (iterator.hasPartialSubtree()) {
node = node.cloneNode(false);
node.appendChild(extractSubtree(iterator.getSubtreeIterator()));
} else {
iterator.remove(that.originalRange);
}
frag.appendChild(node);
}
return frag;
}(new RangeIterator(range));
},
insertNode: function(node) {
var that = this;
if (isDataNode(that.startContainer)) {
if (that.startOffset != that.startContainer.nodeValue.length) {
dom.splitDataNode(that.startContainer, that.startOffset);
}
dom.insertAfter(node, that.startContainer);
} else {
dom.insertAt(that.startContainer, node, that.startOffset);
}
that.setStart(that.startContainer, that.startOffset);
},
cloneRange: function() {
// fast copy
return $.extend(new W3CRange(this.ownerDocument), {
startContainer: this.startContainer,
endContainer: this.endContainer,
commonAncestorContainer: this.commonAncestorContainer,
startOffset: this.startOffset,
endOffset: this.endOffset,
collapsed: this.collapsed,
originalRange: this
});
},
// used for debug purposes
toString: function() {
var startNodeName = this.startContainer.nodeName, endNodeName = this.endContainer.nodeName;
return [ startNodeName == "#text" ? this.startContainer.nodeValue : startNodeName, "(", this.startOffset, ") : ", endNodeName == "#text" ? this.endContainer.nodeValue : endNodeName, "(", this.endOffset, ")" ].join("");
}
});
/* can be used in Range.compareBoundaryPoints if we need it one day */
function compareBoundaries(start, end, startOffset, endOffset) {
if (start == end) {
return endOffset - startOffset;
}
// end is child of start
var container = end;
while (container && container.parentNode != start) {
container = container.parentNode;
}
if (container) {
return findNodeIndex(container) - startOffset;
}
// start is child of end
container = start;
while (container && container.parentNode != end) {
container = container.parentNode;
}
if (container) {
return endOffset - findNodeIndex(container) - 1;
}
// deep traversal
var root = dom.commonAncestor(start, end);
var startAncestor = start;
while (startAncestor && startAncestor.parentNode != root) {
startAncestor = startAncestor.parentNode;
}
if (!startAncestor) {
startAncestor = root;
}
var endAncestor = end;
while (endAncestor && endAncestor.parentNode != root) {
endAncestor = endAncestor.parentNode;
}
if (!endAncestor) {
endAncestor = root;
}
if (startAncestor == endAncestor) {
return 0;
}
return findNodeIndex(endAncestor) - findNodeIndex(startAncestor);
}
function fixIvalidRange(range, toStart) {
function isInvalidRange(range) {
try {
return compareBoundaries(range.startContainer, range.endContainer, range.startOffset, range.endOffset) < 0;
} catch (ex) {
// range was initially invalid (e.g. when cloned from invalid range) - it must be fixed
return true;
}
}
if (isInvalidRange(range)) {
if (toStart) {
range.commonAncestorContainer = range.endContainer = range.startContainer;
range.endOffset = range.startOffset;
} else {
range.commonAncestorContainer = range.startContainer = range.endContainer;
range.startOffset = range.endOffset;
}
range.collapsed = true;
}
}
function updateRangeProperties(range) {
range.collapsed = range.startContainer == range.endContainer && range.startOffset == range.endOffset;
var node = range.startContainer;
while (node && node != range.endContainer && !dom.isAncestorOf(node, range.endContainer)) {
node = node.parentNode;
}
range.commonAncestorContainer = node;
}
var RangeIterator = Class.extend({
init: function(range) {
$.extend(this, {
range: range,
_current: null,
_next: null,
_end: null
});
if (range.collapsed) {
return;
}
var root = range.commonAncestorContainer;
this._next = range.startContainer == root && !isDataNode(range.startContainer) ? range.startContainer.childNodes[range.startOffset] : findClosestAncestor(root, range.startContainer);
this._end = range.endContainer == root && !isDataNode(range.endContainer) ? range.endContainer.childNodes[range.endOffset] : findClosestAncestor(root, range.endContainer).nextSibling;
},
hasNext: function() {
return !!this._next;
},
next: function() {
var that = this, current = that._current = that._next;
that._next = that._current && that._current.nextSibling != that._end ? that._current.nextSibling : null;
if (isDataNode(that._current)) {
if (that.range.endContainer == that._current) {
current = current.cloneNode(true);
current.deleteData(that.range.endOffset, current.length - that.range.endOffset);
}
if (that.range.startContainer == that._current) {
current = current.cloneNode(true);
current.deleteData(0, that.range.startOffset);
}
}
return current;
},
traverse: function(callback) {
var that = this, current;
function next() {
that._current = that._next;
that._next = that._current && that._current.nextSibling != that._end ? that._current.nextSibling : null;
return that._current;
}
while (current = next()) {
if (that.hasPartialSubtree()) {
that.getSubtreeIterator().traverse(callback);
} else {
callback(current);
}
}
return current;
},
remove: function(originalRange) {
var that = this, inStartContainer = that.range.startContainer == that._current, inEndContainer = that.range.endContainer == that._current, start, end, delta;
if (isDataNode(that._current) && (inStartContainer || inEndContainer)) {
start = inStartContainer ? that.range.startOffset : 0;
end = inEndContainer ? that.range.endOffset : that._current.length;
delta = end - start;
if (originalRange && (inStartContainer || inEndContainer)) {
if (that._current == originalRange.startContainer && start <= originalRange.startOffset) {
originalRange.startOffset -= delta;
}
if (that._current == originalRange.endContainer && end <= originalRange.endOffset) {
originalRange.endOffset -= delta;
}
}
that._current.deleteData(start, delta);
} else {
var parent = that._current.parentNode;
if (originalRange && (that.range.startContainer == parent || that.range.endContainer == parent)) {
var nodeIndex = findNodeIndex(that._current);
if (parent == originalRange.startContainer && nodeIndex <= originalRange.startOffset) {
originalRange.startOffset -= 1;
}
if (parent == originalRange.endContainer && nodeIndex < originalRange.endOffset) {
originalRange.endOffset -= 1;
}
}
dom.remove(that._current);
}
},
hasPartialSubtree: function() {
return !isDataNode(this._current) && (dom.isAncestorOrSelf(this._current, this.range.startContainer) || dom.isAncestorOrSelf(this._current, this.range.endContainer));
},
getSubtreeIterator: function() {
var that = this, subRange = that.range.cloneRange();
subRange.selectNodeContents(that._current);
if (dom.isAncestorOrSelf(that._current, that.range.startContainer)) {
subRange.setStart(that.range.startContainer, that.range.startOffset);
}
if (dom.isAncestorOrSelf(that._current, that.range.endContainer)) {
subRange.setEnd(that.range.endContainer, that.range.endOffset);
}
return new RangeIterator(subRange);
}
});
var W3CSelection = Class.extend({
init: function(doc) {
this.ownerDocument = doc;
this.rangeCount = 1;
},
addRange: function(range) {
var textRange = this.ownerDocument.body.createTextRange();
// end container should be adopted first in order to prevent selection with negative length
adoptContainer(textRange, range, false);
adoptContainer(textRange, range, true);
textRange.select();
},
removeAllRanges: function() {
this.ownerDocument.selection.empty();
},
getRangeAt: function() {
var textRange, range = new W3CRange(this.ownerDocument), selection = this.ownerDocument.selection, element;
try {
textRange = selection.createRange();
element = textRange.item ? textRange.item(0) : textRange.parentElement();
if (element.ownerDocument != this.ownerDocument) {
return range;
}
} catch (ex) {
return range;
}
if (selection.type == "Control") {
range.selectNode(textRange.item(0));
} else {
adoptEndPoint(textRange, range, true);
adoptEndPoint(textRange, range, false);
if (range.startContainer.nodeType == 9) {
range.setStart(range.endContainer, range.startOffset);
}
if (range.endContainer.nodeType == 9) {
range.setEnd(range.startContainer, range.endOffset);
}
if (textRange.compareEndPoints("StartToEnd", textRange) === 0) {
range.collapse(false);
}
var startContainer = range.startContainer, endContainer = range.endContainer, body = this.ownerDocument.body;
if (!range.collapsed && range.startOffset === 0 && range.endOffset == getNodeLength(range.endContainer) && // check for full body selection
!(startContainer == endContainer && isDataNode(startContainer) && startContainer.parentNode == body)) {
// but not when single textnode is selected
var movedStart = false, movedEnd = false;
while (findNodeIndex(startContainer) === 0 && startContainer == startContainer.parentNode.firstChild && startContainer != body) {
startContainer = startContainer.parentNode;
movedStart = true;
}
while (findNodeIndex(endContainer) == getNodeLength(endContainer.parentNode) - 1 && endContainer == endContainer.parentNode.lastChild && endContainer != body) {
endContainer = endContainer.parentNode;
movedEnd = true;
}
if (startContainer == body && endContainer == body && movedStart && movedEnd) {
range.setStart(startContainer, 0);
range.setEnd(endContainer, getNodeLength(body));
}
}
}
return range;
}
});
function adoptContainer(textRange, range, start) {
// find anchor node and offset
var container = range[start ? "startContainer" : "endContainer"];
var offset = range[start ? "startOffset" : "endOffset"], textOffset = 0;
var anchorNode = isDataNode(container) ? container : container.childNodes[offset] || null;
var anchorParent = isDataNode(container) ? container.parentNode : container;
// visible data nodes need a text offset
if (container.nodeType == 3 || container.nodeType == 4) {
textOffset = offset;
}
// create a cursor element node to position range (since we can't select text nodes)
var cursorNode = anchorParent.insertBefore(dom.create(range.ownerDocument, "a"), anchorNode);
var cursor = range.ownerDocument.body.createTextRange();
cursor.moveToElementText(cursorNode);
dom.remove(cursorNode);
cursor[start ? "moveStart" : "moveEnd"]("character", textOffset);
cursor.collapse(false);
textRange.setEndPoint(start ? "StartToStart" : "EndToStart", cursor);
}
function adoptEndPoint(textRange, range, start) {
var cursorNode = dom.create(range.ownerDocument, "a"), cursor = textRange.duplicate();
cursor.collapse(start);
var parent = cursor.parentElement();
do {
parent.insertBefore(cursorNode, cursorNode.previousSibling);
cursor.moveToElementText(cursorNode);
} while (cursor.compareEndPoints(start ? "StartToStart" : "StartToEnd", textRange) > 0 && cursorNode.previousSibling);
cursor.setEndPoint(start ? "EndToStart" : "EndToEnd", textRange);
var target = cursorNode.nextSibling;
if (!target) {
// at end of text node
target = cursorNode.previousSibling;
if (target && isDataNode(target)) {
// in case of collapsed range in empty tag
range.setEnd(target, target.nodeValue.length);
dom.remove(cursorNode);
} else {
range.selectNodeContents(parent);
dom.remove(cursorNode);
range.endOffset -= 1;
}
return;
}
dom.remove(cursorNode);
if (isDataNode(target)) {
range[start ? "setStart" : "setEnd"](target, cursor.text.length);
} else {
range[start ? "setStartBefore" : "setEndBefore"](target);
}
}
var RangeEnumerator = Class.extend({
init: function(range) {
this.enumerate = function() {
var nodes = [];
function visit(node) {
if (dom.is(node, "img") || node.nodeType == 3 && !dom.isWhitespace(node)) {
nodes.push(node);
} else {
node = node.firstChild;
while (node) {
visit(node);
node = node.nextSibling;
}
}
}
new RangeIterator(range).traverse(visit);
return nodes;
};
}
});
var RestorePoint = Class.extend({
init: function(range) {
var that = this;
that.range = range;
that.rootNode = RangeUtils.documentFromRange(range);
that.body = that.rootNode.body;
that.html = that.body.innerHTML;
that.startContainer = that.nodeToPath(range.startContainer);
that.endContainer = that.nodeToPath(range.endContainer);
that.startOffset = that.offset(range.startContainer, range.startOffset);
that.endOffset = that.offset(range.endContainer, range.endOffset);
},
index: function(node) {
var result = 0, lastType = node.nodeType;
while (node = node.previousSibling) {
var nodeType = node.nodeType;
if (nodeType != 3 || lastType != nodeType) {
result++;
}
lastType = nodeType;
}
return result;
},
offset: function(node, value) {
if (node.nodeType == 3) {
while ((node = node.previousSibling) && node.nodeType == 3) {
value += node.nodeValue.length;
}
}
return value;
},
nodeToPath: function(node) {
var path = [];
while (node != this.rootNode) {
path.push(this.index(node));
node = node.parentNode;
}
return path;
},
toRangePoint: function(range, start, path, denormalizedOffset) {
var node = this.rootNode, length = path.length, offset = denormalizedOffset;
while (length--) {
node = node.childNodes[path[length]];
}
while (node.nodeType == 3 && node.nodeValue.length < offset) {
offset -= node.nodeValue.length;
node = node.nextSibling;
}
range[start ? "setStart" : "setEnd"](node, offset);
},
toRange: function() {
var that = this, result = that.range.cloneRange();
that.toRangePoint(result, true, that.startContainer, that.startOffset);
that.toRangePoint(result, false, that.endContainer, that.endOffset);
return result;
}
});
var Marker = Class.extend({
init: function() {
this.caret = null;
},
addCaret: function(range) {
var that = this;
that.caret = dom.create(RangeUtils.documentFromRange(range), "span", {
className: "k-marker"
});
range.insertNode(that.caret);
range.selectNode(that.caret);
return that.caret;
},
removeCaret: function(range) {
var that = this, previous = that.caret.previousSibling, startOffset = 0;
if (previous) {
startOffset = isDataNode(previous) ? previous.nodeValue.length : findNodeIndex(previous);
}
var container = that.caret.parentNode;
var containerIndex = previous ? findNodeIndex(previous) : 0;
dom.remove(that.caret);
normalize(container);
var node = container.childNodes[containerIndex];
if (isDataNode(node)) {
range.setStart(node, startOffset);
} else if (node) {
var textNode = dom.lastTextNode(node);
if (textNode) {
range.setStart(textNode, textNode.nodeValue.length);
} else {
range[previous ? "setStartAfter" : "setStartBefore"](node);
}
} else {
if (!browser.msie && !container.innerHTML) {
container.innerHTML = '<br _moz_dirty="" />';
}
range.selectNodeContents(container);
}
range.collapse(true);
},
add: function(range, expand) {
var that = this;
if (expand && range.collapsed) {
that.addCaret(range);
range = RangeUtils.expand(range);
}
var rangeBoundary = range.cloneRange();
rangeBoundary.collapse(false);
that.end = dom.create(RangeUtils.documentFromRange(range), "span", {
className: "k-marker"
});
rangeBoundary.insertNode(that.end);
rangeBoundary = range.cloneRange();
rangeBoundary.collapse(true);
that.start = that.end.cloneNode(true);
rangeBoundary.insertNode(that.start);
range.setStartBefore(that.start);
range.setEndAfter(that.end);
normalize(range.commonAncestorContainer);
return range;
},
remove: function(range) {
var that = this, start = that.start, end = that.end, shouldNormalizeStart, shouldNormalizeEnd, shouldNormalize;
normalize(range.commonAncestorContainer);
while (!start.nextSibling && start.parentNode) {
start = start.parentNode;
}
while (!end.previousSibling && end.parentNode) {
end = end.parentNode;
}
// merely accessing the siblings will solve range issues in IE
shouldNormalizeStart = start.previousSibling && start.previousSibling.nodeType == 3 && start.nextSibling && start.nextSibling.nodeType == 3;
shouldNormalizeEnd = end.previousSibling && end.previousSibling.nodeType == 3 && end.nextSibling && end.nextSibling.nodeType == 3;
shouldNormalize = shouldNormalizeStart && shouldNormalizeEnd;
start = start.nextSibling;
end = end.previousSibling;
var collapsed = false;
var collapsedToStart = false;
// collapsed range
if (start == that.end) {
collapsedToStart = !!that.start.previousSibling;
start = end = that.start.previousSibling || that.end.nextSibling;
collapsed = true;
}
dom.remove(that.start);
dom.remove(that.end);
if (!start || !end) {
range.selectNodeContents(range.commonAncestorContainer);
range.collapse(true);
return;
}
var startOffset = collapsed ? isDataNode(start) ? start.nodeValue.length : start.childNodes.length : 0;
var endOffset = isDataNode(end) ? end.nodeValue.length : end.childNodes.length;
if (start.nodeType == 3) {
while (start.previousSibling && start.previousSibling.nodeType == 3) {
start = start.previousSibling;
startOffset += start.nodeValue.length;
}
}
if (end.nodeType == 3) {
while (end.previousSibling && end.previousSibling.nodeType == 3) {
end = end.previousSibling;
endOffset += end.nodeValue.length;
}
}
var startIndex = findNodeIndex(start), startParent = start.parentNode;
var endIndex = findNodeIndex(end), endParent = end.parentNode;
for (var startPointer = start; startPointer.previousSibling; startPointer = startPointer.previousSibling) {
if (startPointer.nodeType == 3 && startPointer.previousSibling.nodeType == 3) {
startIndex--;
}
}
for (var endPointer = end; endPointer.previousSibling; endPointer = endPointer.previousSibling) {
if (endPointer.nodeType == 3 && endPointer.previousSibling.nodeType == 3) {
endIndex--;
}
}
normalize(startParent);
if (start.nodeType == 3) {
start = startParent.childNodes[startIndex];
}
normalize(endParent);
if (end.nodeType == 3) {
end = endParent.childNodes[endIndex];
}
if (collapsed) {
if (start.nodeType == 3) {
range.setStart(start, startOffset);
} else {
range[collapsedToStart ? "setStartAfter" : "setStartBefore"](start);
}
range.collapse(true);
} else {
if (start.nodeType == 3) {
range.setStart(start, startOffset);
} else {
range.setStartBefore(start);
}
if (end.nodeType == 3) {
range.setEnd(end, endOffset);
} else {
range.setEndAfter(end);
}
}
if (that.caret) {
that.removeCaret(range);
}
}
});
var boundary = /[\u0009-\u000d]|\u0020|\u00a0|\ufeff|\.|,|;|:|!|\(|\)|\?/;
var RangeUtils = {
nodes: function(range) {
var nodes = RangeUtils.textNodes(range);
if (!nodes.length) {
range.selectNodeContents(range.commonAncestorContainer);
nodes = RangeUtils.textNodes(range);
if (!nodes.length) {
nodes = dom.significantChildNodes(range.commonAncestorContainer);
}
}
return nodes;
},
textNodes: function(range) {
return new RangeEnumerator(range).enumerate();
},
documentFromRange: function(range) {
var startContainer = range.startContainer;
return startContainer.nodeType == 9 ? startContainer : startContainer.ownerDocument;
},
createRange: function(document) {
if (browser.msie && browser.version < 9) {
return new W3CRange(document);
}
return document.createRange();
},
selectRange: function(range) {
var image = RangeUtils.image(range);
if (image) {
range.setStartAfter(image);
range.setEndAfter(image);
}
var selection = SelectionUtils.selectionFromRange(range);
selection.removeAllRanges();
selection.addRange(range);
},
split: function(range, node, trim) {
function partition(start) {
var partitionRange = range.cloneRange();
partitionRange.collapse(start);
partitionRange[start ? "setStartBefore" : "setEndAfter"](node);
var contents = partitionRange.extractContents();
if (trim) {
contents = dom.trim(contents);
}
dom[start ? "insertBefore" : "insertAfter"](contents, node);
}
partition(true);
partition(false);
},
getMarkers: function(range) {
var markers = [];
new RangeIterator(range).traverse(function(node) {
if (node.className == "k-marker") {
markers.push(node);
}
});
return markers;
},
image: function(range) {
var nodes = [];
new RangeIterator(range).traverse(function(node) {
if (dom.is(node, "img")) {
nodes.push(node);
}
});
if (nodes.length == 1) {
return nodes[0];
}
},
expand: function(range) {
var result = range.cloneRange();
var startContainer = result.startContainer.childNodes[result.startOffset === 0 ? 0 : result.startOffset - 1];
var endContainer = result.endContainer.childNodes[result.endOffset];
if (!isDataNode(startContainer) || !isDataNode(endContainer)) {
return result;
}
var beforeCaret = startContainer.nodeValue;
var afterCaret = endContainer.nodeValue;
if (!beforeCaret || !afterCaret) {
return result;
}
var startOffset = beforeCaret.split("").reverse().join("").search(boundary);
var endOffset = afterCaret.search(boundary);
if (!startOffset || !endOffset) {
return result;
}
endOffset = endOffset == -1 ? afterCaret.length : endOffset;
startOffset = startOffset == -1 ? 0 : beforeCaret.length - startOffset;
result.setStart(startContainer, startOffset);
result.setEnd(endContainer, endOffset);
return result;
},
isExpandable: function(range) {
var node = range.startContainer;
var rangeDocument = RangeUtils.documentFromRange(range);
if (node == rangeDocument || node == rangeDocument.body) {
return false;
}
var result = range.cloneRange();
var value = node.nodeValue;
if (!value) {
return false;
}
var beforeCaret = value.substring(0, result.startOffset);
var afterCaret = value.substring(result.startOffset);
var startOffset = 0, endOffset = 0;
if (beforeCaret) {
startOffset = beforeCaret.split("").reverse().join("").search(boundary);
}
if (afterCaret) {
endOffset = afterCaret.search(boundary);
}
return startOffset && endOffset;
}
};
extend(Editor, {
SelectionUtils: SelectionUtils,
W3CRange: W3CRange,
RangeIterator: RangeIterator,
W3CSelection: W3CSelection,
RangeEnumerator: RangeEnumerator,
RestorePoint: RestorePoint,
Marker: Marker,
RangeUtils: RangeUtils
});
})(window.kendo.jQuery);
(function($) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, Editor = kendo.ui.editor, EditorUtils = Editor.EditorUtils, registerTool = EditorUtils.registerTool, dom = Editor.Dom, RangeUtils = Editor.RangeUtils, selectRange = RangeUtils.selectRange, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate, RestorePoint = Editor.RestorePoint, Marker = Editor.Marker, extend = $.extend;
var Command = Class.extend({
init: function(options) {
var that = this;
that.options = options;
that.restorePoint = new RestorePoint(options.range);
that.marker = new Marker();
that.formatter = options.formatter;
},
getRange: function() {
return this.restorePoint.toRange();
},
lockRange: function(expand) {
return this.marker.add(this.getRange(), expand);
},
releaseRange: function(range) {
this.marker.remove(range);
selectRange(range);
},
undo: function() {
var point = this.restorePoint;
point.body.innerHTML = point.html;
selectRange(point.toRange());
},
redo: function() {
this.exec();
},
exec: function() {
var that = this, range = that.lockRange(true);
that.formatter.editor = that.editor;
that.formatter.toggle(range);
that.releaseRange(range);
}
});
var GenericCommand = Class.extend({
init: function(startRestorePoint, endRestorePoint) {
this.body = startRestorePoint.body;
this.startRestorePoint = startRestorePoint;
this.endRestorePoint = endRestorePoint;
},
redo: function() {
this.body.innerHTML = this.endRestorePoint.html;
selectRange(this.endRestorePoint.toRange());
},
undo: function() {
this.body.innerHTML = this.startRestorePoint.html;
selectRange(this.startRestorePoint.toRange());
}
});
var InsertHtmlCommand = Command.extend({
init: function(options) {
Command.fn.init.call(this, options);
this.managesUndoRedo = true;
},
exec: function() {
var editor = this.editor;
var range = editor.getRange();
var startRestorePoint = new RestorePoint(range);
editor.clipboard.paste(this.options.value || "");
editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
editor.focus();
}
});
var InsertHtmlTool = Tool.extend({
initialize: function(ui, initOptions) {
var editor = initOptions.editor, options = this.options, dataSource = options.items ? options.items : editor.options.insertHtml;
new Editor.SelectBox(ui, {
dataSource: dataSource,
dataTextField: "text",
dataValueField: "value",
change: function() {
Tool.exec(editor, "insertHtml", this.value());
},
title: editor.options.messages.insertHtml,
highlightFirst: false
});
},
command: function(commandArguments) {
return new InsertHtmlCommand(commandArguments);
},
update: function(ui) {
var selectbox = ui.data("kendoSelectBox") || ui.find("select").data("kendoSelectBox");
selectbox.close();
selectbox.value(selectbox.options.title);
}
});
var UndoRedoStack = Class.extend({
init: function() {
this.stack = [];
this.currentCommandIndex = -1;
},
push: function(command) {
var that = this;
that.stack = that.stack.slice(0, that.currentCommandIndex + 1);
that.currentCommandIndex = that.stack.push(command) - 1;
},
undo: function() {
if (this.canUndo()) {
this.stack[this.currentCommandIndex--].undo();
}
},
redo: function() {
if (this.canRedo()) {
this.stack[++this.currentCommandIndex].redo();
}
},
canUndo: function() {
return this.currentCommandIndex >= 0;
},
canRedo: function() {
return this.currentCommandIndex != this.stack.length - 1;
}
});
var TypingHandler = Class.extend({
init: function(editor) {
this.editor = editor;
},
keydown: function(e) {
var that = this, editor = that.editor, keyboard = editor.keyboard, isTypingKey = keyboard.isTypingKey(e), evt = extend($.Event(), e);
that.editor.trigger("keydown", evt);
if (evt.isDefaultPrevented()) {
e.preventDefault();
}
if (!evt.isDefaultPrevented() && isTypingKey && !keyboard.isTypingInProgress()) {
var range = editor.getRange();
that.startRestorePoint = new RestorePoint(range);
keyboard.startTyping(function() {
editor.selectionRestorePoint = that.endRestorePoint = new RestorePoint(editor.getRange());
editor.undoRedoStack.push(new GenericCommand(that.startRestorePoint, that.endRestorePoint));
});
return true;
}
return false;
},
keyup: function(e) {
var keyboard = this.editor.keyboard;
this.editor.trigger("keyup", e);
if (keyboard.isTypingInProgress()) {
keyboard.endTyping();
return true;
}
return false;
}
});
var SystemHandler = Class.extend({
init: function(editor) {
this.editor = editor;
this.systemCommandIsInProgress = false;
},
createUndoCommand: function() {
var that = this;
that.endRestorePoint = new RestorePoint(that.editor.getRange());
that.editor.undoRedoStack.push(new GenericCommand(that.startRestorePoint, that.endRestorePoint));
that.startRestorePoint = that.endRestorePoint;
},
changed: function() {
if (this.startRestorePoint) {
return this.startRestorePoint.html != this.editor.body.innerHTML;
}
return false;
},
keydown: function(e) {
var that = this, editor = that.editor, keyboard = editor.keyboard;
if (keyboard.isModifierKey(e)) {
if (keyboard.isTypingInProgress()) {
keyboard.endTyping(true);
}
that.startRestorePoint = new RestorePoint(editor.getRange());
return true;
}
if (keyboard.isSystem(e)) {
that.systemCommandIsInProgress = true;
if (that.changed()) {
that.systemCommandIsInProgress = false;
that.createUndoCommand();
}
return true;
}
return false;
},
keyup: function(e) {
var that = this;
if (that.systemCommandIsInProgress && that.changed()) {
that.systemCommandIsInProgress = false;
that.createUndoCommand(e);
return true;
}
return false;
}
});
var Keyboard = Class.extend({
init: function(handlers) {
this.handlers = handlers;
this.typingInProgress = false;
},
isCharacter: function(keyCode) {
return keyCode >= 48 && keyCode <= 90 || keyCode >= 96 && keyCode <= 111 || keyCode >= 186 && keyCode <= 192 || keyCode >= 219 && keyCode <= 222;
},
toolFromShortcut: function(tools, e) {
var key = String.fromCharCode(e.keyCode), toolName, toolOptions;
for (toolName in tools) {
toolOptions = $.extend({
ctrl: false,
alt: false,
shift: false
}, tools[toolName].options);
if ((toolOptions.key == key || toolOptions.key == e.keyCode) && toolOptions.ctrl == e.ctrlKey && toolOptions.alt == e.altKey && toolOptions.shift == e.shiftKey) {
return toolName;
}
}
},
isTypingKey: function(e) {
var keyCode = e.keyCode;
return this.isCharacter(keyCode) && !e.ctrlKey && !e.altKey || keyCode == 32 || keyCode == 13 || keyCode == 8 || keyCode == 46 && !e.shiftKey && !e.ctrlKey && !e.altKey;
},
isModifierKey: function(e) {
var keyCode = e.keyCode;
return keyCode == 17 && !e.shiftKey && !e.altKey || keyCode == 16 && !e.ctrlKey && !e.altKey || keyCode == 18 && !e.ctrlKey && !e.shiftKey;
},
isSystem: function(e) {
return e.keyCode == 46 && e.ctrlKey && !e.altKey && !e.shiftKey;
},
startTyping: function(callback) {
this.onEndTyping = callback;
this.typingInProgress = true;
},
stopTyping: function() {
this.typingInProgress = false;
if (this.onEndTyping) {
this.onEndTyping();
}
},
endTyping: function(force) {
var that = this;
that.clearTimeout();
if (force) {
that.stopTyping();
} else {
that.timeout = window.setTimeout($.proxy(that.stopTyping, that), 1e3);
}
},
isTypingInProgress: function() {
return this.typingInProgress;
},
clearTimeout: function() {
window.clearTimeout(this.timeout);
},
notify: function(e, what) {
var i, handlers = this.handlers;
for (i = 0; i < handlers.length; i++) {
if (handlers[i][what](e)) {
break;
}
}
},
keydown: function(e) {
this.notify(e, "keydown");
},
keyup: function(e) {
this.notify(e, "keyup");
}
});
var Clipboard = Class.extend({
init: function(editor) {
this.editor = editor;
this.cleaners = [ new MSWordFormatCleaner(), new WebkitFormatCleaner() ];
},
htmlToFragment: function(html) {
var editor = this.editor, doc = editor.document, container = dom.create(doc, "div"), fragment = doc.createDocumentFragment();
container.innerHTML = html;
while (container.firstChild) {
fragment.appendChild(container.firstChild);
}
return fragment;
},
isBlock: function(html) {
return /<(div|p|ul|ol|table|h[1-6])/i.test(html);
},
oncut: function() {
var editor = this.editor, startRestorePoint = new RestorePoint(editor.getRange());
setTimeout(function() {
editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
});
},
onpaste: function(e) {
var editor = this.editor, range = editor.getRange(), bom = "", startRestorePoint = new RestorePoint(range), clipboardNode = dom.create(editor.document, "div", {
className: "k-paste-container",
innerHTML: bom
});
dom.persistScrollTop(editor.document);
editor.body.appendChild(clipboardNode);
if (editor.body.createTextRange) {
e.preventDefault();
var r = editor.createRange();
r.selectNodeContents(clipboardNode);
editor.selectRange(r);
var textRange = editor.body.createTextRange();
textRange.moveToElementText(clipboardNode);
$(editor.body).unbind("paste");
textRange.execCommand("Paste");
$(editor.body).bind("paste", $.proxy(arguments.callee, this));
} else {
var clipboardRange = editor.createRange();
clipboardRange.selectNodeContents(clipboardNode);
selectRange(clipboardRange);
}
range.deleteContents();
setTimeout(function() {
var html, args = {
html: ""
};
selectRange(range);
// iOS5 will substitute the paste container with its own element
if (!clipboardNode.parentNode) {
clipboardNode = editor.body.lastChild;
}
dom.remove(clipboardNode);
if (clipboardNode.lastChild && dom.is(clipboardNode.lastChild, "br")) {
dom.remove(clipboardNode.lastChild);
}
html = clipboardNode.innerHTML;
if (html != bom) {
args.html = html;
}
editor.trigger("paste", args);
editor.clipboard.paste(args.html, true);
editor.undoRedoStack.push(new GenericCommand(startRestorePoint, new RestorePoint(editor.getRange())));
Editor.EditorUtils.select(editor);
});
},
splittableParent: function(block, node) {
var parentNode, body;
if (block) {
return dom.parentOfType(node, [ "p", "ul", "ol" ]) || node.parentNode;
}
parentNode = node.parentNode;
body = node.ownerDocument.body;
if (dom.isInline(parentNode)) {
while (parentNode.parentNode != body && !dom.isBlock(parentNode.parentNode)) {
parentNode = parentNode.parentNode;
}
}
return parentNode;
},
paste: function(html, clean) {
var editor = this.editor, i, l;
for (i = 0, l = this.cleaners.length; i < l; i++) {
if (this.cleaners[i].applicable(html)) {
html = this.cleaners[i].clean(html);
}
}
if (clean) {
// remove br elements which immediately precede block elements
html = html.replace(/(<br>(\s| )*)+(<\/?(div|p|li|col|t))/gi, "$3");
// remove empty inline elements
html = html.replace(/<(a|span)[^>]*><\/\1>/gi, "");
}
// It is possible in IE to copy just <li> tags
html = html.replace(/^<li/i, "<ul><li").replace(/li>$/g, "li></ul>");
var block = this.isBlock(html);
var range = editor.getRange();
range.deleteContents();
if (range.startContainer == editor.document) {
range.selectNodeContents(editor.body);
}
var marker = new Marker();
var caret = marker.addCaret(range);
var parent = this.splittableParent(block, caret);
var unwrap = false;
if (!/body|td/.test(dom.name(parent)) && (block || dom.isInline(parent))) {
range.selectNode(caret);
RangeUtils.split(range, parent, true);
unwrap = true;
}
var fragment = this.htmlToFragment(html);
if (fragment.firstChild && fragment.firstChild.className === "k-paste-container") {
var fragmentsHtml = [];
for (i = 0, l = fragment.childNodes.length; i < l; i++) {
fragmentsHtml.push(fragment.childNodes[i].innerHTML);
}
fragment = this.htmlToFragment(fragmentsHtml.join("<br />"));
}
range.insertNode(fragment);
parent = this.splittableParent(block, caret);
if (unwrap) {
while (caret.parentNode != parent) {
dom.unwrap(caret.parentNode);
}
dom.unwrap(caret.parentNode);
}
dom.normalize(range.commonAncestorContainer);
caret.style.display = "inline";
dom.restoreScrollTop(editor.document);
dom.scrollTo(caret);
marker.removeCaret(range);
selectRange(range);
}
});
var MSWordFormatCleaner = Class.extend({
init: function() {
this.replacements = [ /<\?xml[^>]*>/gi, "", /<!--(.|\n)*?-->/g, "", /* comments */
/"/g, "'", /* encoded quotes (in attributes) */
/(?:<br> [\s\r\n]+|<br>)*(<\/?(h[1-6]|hr|p|div|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|address|pre|form|blockquote|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br> [\s\r\n]+|<br>)*/g, "$1", /<br><br>/g, "<BR><BR>", /<br>/g, " ", /<table([^>]*)>(\s| )+<t/gi, "<table$1><t", /<tr[^>]*>(\s| )*<\/tr>/gi, "", /<tbody[^>]*>(\s| )*<\/tbody>/gi, "", /<table[^>]*>(\s| )*<\/table>/gi, "", /<BR><BR>/g, "<br>", /^\s*( )+/gi, "", /( |<br[^>]*>)+\s*$/gi, "", /mso-[^;"]*;?/gi, "", /* office-related CSS attributes */
/<(\/?)b(\s[^>]*)?>/gi, "<$1strong$2>", /<(\/?)i(\s[^>]*)?>/gi, "<$1em$2>", /<\/?(meta|link|style|o:|v:|x:)[^>]*>((?:.|\n)*?<\/(meta|link|style|o:|v:|x:)[^>]*>)?/gi, "", /* external references and namespaced tags */
/style=(["|'])\s*\1/g, "" ];
},
applicable: function(html) {
return /class="?Mso|style="[^"]*mso-/i.test(html);
},
listType: function(html) {
if (/^[\u2022\u00b7\u00a7\u00d8o]\u00a0+/.test(html)) {
return "ul";
}
if (/^\s*\w+[\.\)]\u00a0{2,}/.test(html)) {
return "ol";
}
},
lists: function(html) {
var placeholder = dom.create(document, "div", {
innerHTML: html
});
var blockChildren = $(dom.blockElements.join(","), placeholder);
var lastMargin = -1, lastType, levels = {
ul: {},
ol: {}
}, li = placeholder;
for (var i = 0; i < blockChildren.length; i++) {
var p = blockChildren[i];
html = p.innerHTML.replace(/<\/?\w+[^>]*>/g, "").replace(/ /g, "Â ");
var type = this.listType(html);
if (!type || dom.name(p) != "p") {
if (!p.innerHTML) {
dom.remove(p);
} else {
levels = {
ul: {},
ol: {}
};
li = placeholder;
lastMargin = -1;
}
continue;
}
var margin = parseFloat(p.style.marginLeft || 0);
var list = levels[type][margin];
if (margin > lastMargin || !list) {
list = dom.create(document, type);
if (li == placeholder) {
dom.insertBefore(list, p);
} else {
li.appendChild(list);
}
levels[type][margin] = list;
}
if (lastType != type) {
for (var key in levels) {
for (var child in levels[key]) {
if ($.contains(list, levels[key][child])) {
delete levels[key][child];
}
}
}
}
dom.remove(p.firstChild);
li = dom.create(document, "li", {
innerHTML: p.innerHTML
});
list.appendChild(li);
dom.remove(p);
lastMargin = margin;
lastType = type;
}
return placeholder.innerHTML;
},
stripEmptyAnchors: function(html) {
return html.replace(/<a([^>]*)>\s*<\/a>/gi, function(a, attributes) {
if (!attributes || attributes.indexOf("href") < 0) {
return "";
}
return a;
});
},
clean: function(html) {
var that = this, replacements = that.replacements, i, l;
for (i = 0, l = replacements.length; i < l; i += 2) {
html = html.replace(replacements[i], replacements[i + 1]);
}
html = that.stripEmptyAnchors(html);
html = that.lists(html);
html = html.replace(/\s+class="?[^"\s>]*"?/gi, "");
return html;
}
});
var WebkitFormatCleaner = Class.extend({
init: function() {
this.replacements = [ /\s+class="Apple-style-span[^"]*"/gi, "", /<(div|p|h[1-6])\s+style="[^"]*"/gi, "<$1", /^<div>(.*)<\/div>$/, "$1" ];
},
applicable: function(html) {
return /class="?Apple-style-span|style="[^"]*-webkit-nbsp-mode/i.test(html);
},
clean: function(html) {
var that = this, replacements = that.replacements, i, l;
for (i = 0, l = replacements.length; i < l; i += 2) {
html = html.replace(replacements[i], replacements[i + 1]);
}
return html;
}
});
extend(Editor, {
Command: Command,
GenericCommand: GenericCommand,
InsertHtmlCommand: InsertHtmlCommand,
InsertHtmlTool: InsertHtmlTool,
UndoRedoStack: UndoRedoStack,
TypingHandler: TypingHandler,
SystemHandler: SystemHandler,
Keyboard: Keyboard,
Clipboard: Clipboard,
MSWordFormatCleaner: MSWordFormatCleaner,
WebkitFormatCleaner: WebkitFormatCleaner
});
registerTool("insertHtml", new InsertHtmlTool({
template: new ToolTemplate({
template: EditorUtils.dropDownListTemplate,
title: "Insert HTML",
initialValue: "Insert HTML"
})
}));
})(window.kendo.jQuery);
(function($) {
var kendo = window.kendo, Class = kendo.Class, Editor = kendo.ui.editor, formats = kendo.ui.Editor.fn.options.formats, EditorUtils = Editor.EditorUtils, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate, FormatTool = Editor.FormatTool, dom = Editor.Dom, RangeUtils = Editor.RangeUtils, extend = $.extend, registerTool = Editor.EditorUtils.registerTool, registerFormat = Editor.EditorUtils.registerFormat, KMARKER = "k-marker";
var InlineFormatFinder = Class.extend({
init: function(format) {
this.format = format;
},
numberOfSiblings: function(referenceNode) {
var textNodesCount = 0, elementNodesCount = 0, markerCount = 0, parentNode = referenceNode.parentNode, node;
for (node = parentNode.firstChild; node; node = node.nextSibling) {
if (node != referenceNode) {
if (node.className == KMARKER) {
markerCount++;
} else if (node.nodeType == 3) {
textNodesCount++;
} else {
elementNodesCount++;
}
}
}
if (markerCount > 1 && parentNode.firstChild.className == KMARKER && parentNode.lastChild.className == KMARKER) {
// full node selection
return 0;
} else {
return elementNodesCount + textNodesCount;
}
},
findSuitable: function(sourceNode, skip) {
if (!skip && this.numberOfSiblings(sourceNode) > 0) {
return null;
}
return dom.parentOfType(sourceNode, this.format[0].tags);
},
findFormat: function(sourceNode) {
var format = this.format, attrEquals = dom.attrEquals, i, len, node, tags, attributes;
for (i = 0, len = format.length; i < len; i++) {
node = sourceNode;
tags = format[i].tags;
attributes = format[i].attr;
if (node && dom.ofType(node, tags) && attrEquals(node, attributes)) {
return node;
}
while (node) {
node = dom.parentOfType(node, tags);
if (node && attrEquals(node, attributes)) {
return node;
}
}
}
return null;
},
isFormatted: function(nodes) {
var i, len;
for (i = 0, len = nodes.length; i < len; i++) {
if (this.findFormat(nodes[i])) {
return true;
}
}
return false;
}
});
var InlineFormatter = Class.extend({
init: function(format, values) {
var that = this;
that.finder = new InlineFormatFinder(format);
that.attributes = extend({}, format[0].attr, values);
that.tag = format[0].tags[0];
},
wrap: function(node) {
return dom.wrap(node, dom.create(node.ownerDocument, this.tag, this.attributes));
},
activate: function(range, nodes) {
var that = this;
if (that.finder.isFormatted(nodes)) {
that.split(range);
that.remove(nodes);
} else {
that.apply(nodes);
}
},
toggle: function(range) {
var nodes = RangeUtils.textNodes(range);
if (nodes.length > 0) {
this.activate(range, nodes);
}
},
apply: function(nodes) {
var that = this, formatNodes = [], i, l, node, formatNode;
for (i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
formatNode = that.finder.findSuitable(node);
if (formatNode) {
dom.attr(formatNode, that.attributes);
} else {
formatNode = that.wrap(node);
}
formatNodes.push(formatNode);
}
that.consolidate(formatNodes);
},
remove: function(nodes) {
var that = this, i, l, formatNode;
for (i = 0, l = nodes.length; i < l; i++) {
formatNode = that.finder.findFormat(nodes[i]);
if (formatNode) {
if (that.attributes && that.attributes.style) {
dom.unstyle(formatNode, that.attributes.style);
if (!formatNode.style.cssText) {
dom.unwrap(formatNode);
}
} else {
dom.unwrap(formatNode);
}
}
}
},
split: function(range) {
var nodes = RangeUtils.textNodes(range), l = nodes.length, i, formatNode;
if (l > 0) {
for (i = 0; i < l; i++) {
formatNode = this.finder.findFormat(nodes[i]);
if (formatNode) {
RangeUtils.split(range, formatNode, true);
}
}
}
},
consolidate: function(nodes) {
var node, last;
while (nodes.length > 1) {
node = nodes.pop();
last = nodes[nodes.length - 1];
if (node.previousSibling && node.previousSibling.className == KMARKER) {
last.appendChild(node.previousSibling);
}
if (node.tagName == last.tagName && node.previousSibling == last && node.style.cssText == last.style.cssText) {
while (node.firstChild) {
last.appendChild(node.firstChild);
}
dom.remove(node);
}
}
}
});
var GreedyInlineFormatFinder = InlineFormatFinder.extend({
init: function(format, greedyProperty) {
var that = this;
that.format = format;
that.greedyProperty = greedyProperty;
InlineFormatFinder.fn.init.call(that, format);
},
getInlineCssValue: function(node) {
var attributes = node.attributes, trim = $.trim, i, l, attribute, name, attributeValue, css, pair, cssIndex, len, propertyAndValue, property, value;
if (!attributes) {
return;
}
for (i = 0, l = attributes.length; i < l; i++) {
attribute = attributes[i];
name = attribute.nodeName;
attributeValue = attribute.nodeValue;
if (attribute.specified && name == "style") {
css = trim(attributeValue || node.style.cssText).split(";");
for (cssIndex = 0, len = css.length; cssIndex < len; cssIndex++) {
pair = css[cssIndex];
if (pair.length) {
propertyAndValue = pair.split(":");
property = trim(propertyAndValue[0].toLowerCase());
value = trim(propertyAndValue[1]);
if (property != this.greedyProperty) {
continue;
}
return property.indexOf("color") >= 0 ? dom.toHex(value) : value;
}
}
}
}
},
getFormatInner: function(node) {
var $node = $(dom.isDataNode(node) ? node.parentNode : node), parents = $node.parents().andSelf(), i, len, value;
for (i = 0, len = parents.length; i < len; i++) {
value = this.greedyProperty == "className" ? parents[i].className : this.getInlineCssValue(parents[i]);
if (value) {
return value;
}
}
return "inherit";
},
getFormat: function(nodes) {
var result = this.getFormatInner(nodes[0]), i, len;
for (i = 1, len = nodes.length; i < len; i++) {
if (result != this.getFormatInner(nodes[i])) {
return "";
}
}
return result;
},
isFormatted: function(nodes) {
return this.getFormat(nodes) !== "";
}
});
var GreedyInlineFormatter = InlineFormatter.extend({
init: function(format, values, greedyProperty) {
var that = this;
InlineFormatter.fn.init.call(that, format, values);
that.greedyProperty = greedyProperty;
that.values = values;
that.finder = new GreedyInlineFormatFinder(format, greedyProperty);
},
activate: function(range, nodes) {
var that = this, camelCase, greedyProperty = that.greedyProperty, action = "apply";
that.split(range);
if (greedyProperty) {
camelCase = greedyProperty.replace(/-([a-z])/, function(all, letter) {
return letter.toUpperCase();
});
if (that.values.style[camelCase] == "inherit") {
action = "remove";
}
}
that[action](nodes);
}
});
function inlineFormatWillDelayExecution(range) {
return range.collapsed && !RangeUtils.isExpandable(range);
}
var InlineFormatTool = FormatTool.extend({
init: function(options) {
FormatTool.fn.init.call(this, extend(options, {
finder: new InlineFormatFinder(options.format),
formatter: function() {
return new InlineFormatter(options.format);
}
}));
this.willDelayExecution = inlineFormatWillDelayExecution;
}
});
var DelayedExecutionTool = Tool.extend({
willDelayExecution: inlineFormatWillDelayExecution,
update: function(ui, nodes, pendingFormats) {
var list = ui.data(this.type), pendingFormat = pendingFormats.getPending(this.name), format;
if (pendingFormat && pendingFormat.options.params) {
format = pendingFormat.options.params.value;
} else {
format = this.finder.getFormat(nodes);
}
list.close();
list.value(format);
}
});
var FontTool = DelayedExecutionTool.extend({
init: function(options) {
var that = this;
Tool.fn.init.call(that, options);
// IE has single selection hence we are using select box instead of combobox
that.type = kendo.support.browser.msie || kendo.support.touch ? "kendoDropDownList" : "kendoComboBox";
that.format = [ {
tags: [ "span" ]
} ];
that.finder = new GreedyInlineFormatFinder(that.format, options.cssAttr);
},
command: function(commandArguments) {
var options = this.options, format = this.format, style = {};
return new Editor.FormatCommand(extend(commandArguments, {
formatter: function() {
style[options.domAttr] = commandArguments.value;
return new GreedyInlineFormatter(format, {
style: style
}, options.cssAttr);
}
}));
},
initialize: function(ui, initOptions) {
var editor = initOptions.editor, options = this.options, toolName = options.name, dataSource, defaultValue = [];
if (options.defaultValue) {
defaultValue = [ {
text: editor.options.messages[options.defaultValue[0].text],
value: options.defaultValue[0].value
} ];
}
dataSource = defaultValue.concat(options.items ? options.items : editor.options[toolName]);
ui[this.type]({
dataTextField: "text",
dataValueField: "value",
dataSource: dataSource,
change: function() {
Tool.exec(editor, toolName, this.value());
},
highlightFirst: false
});
ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");
ui.data(this.type).value("inherit");
}
});
var ColorTool = Tool.extend({
init: function(options) {
Tool.fn.init.call(this, options);
this.options = options;
this.format = [ {
tags: [ "span" ]
} ];
},
update: function() {
this._widget.close();
},
command: function(commandArguments) {
var options = this.options, format = this.format, style = {};
return new Editor.FormatCommand(extend(commandArguments, {
formatter: function() {
style[options.domAttr] = commandArguments.value;
return new GreedyInlineFormatter(format, {
style: style
}, options.cssAttr);
}
}));
},
willDelayExecution: inlineFormatWillDelayExecution,
initialize: function(ui, initOptions) {
var editor = initOptions.editor, toolName = this.name;
ui = this._widget = new kendo.ui.ColorPicker(ui, {
value: "#000",
toolIcon: "k-" + this.options.name,
palette: "websafe",
change: function() {
var color = ui.value();
if (color) {
Tool.exec(editor, toolName, color);
}
}
});
ui.bind("activate", function(ev) {
ev.preventDefault();
ui.trigger("change");
editor.focus();
});
ui.element.attr("title", initOptions.title);
}
});
var StyleTool = DelayedExecutionTool.extend({
init: function(options) {
var that = this;
Tool.fn.init.call(that, options);
that.type = "kendoSelectBox";
that.format = [ {
tags: [ "span" ]
} ];
that.finder = new GreedyInlineFormatFinder(that.format, "className");
},
command: function(commandArguments) {
var format = this.format;
return new Editor.FormatCommand(extend(commandArguments, {
formatter: function() {
return new GreedyInlineFormatter(format, {
className: commandArguments.value
});
}
}));
},
initialize: function(ui, initOptions) {
var editor = initOptions.editor, options = this.options;
new Editor.SelectBox(ui, {
dataTextField: "text",
dataValueField: "value",
dataSource: options.items || editor.options.style,
title: editor.options.messages.style,
change: function() {
Tool.exec(editor, "style", this.value());
},
highlightFirst: false
});
ui.closest(".k-widget").removeClass("k-" + this.name).find("*").andSelf().attr("unselectable", "on");
}
});
extend(Editor, {
InlineFormatFinder: InlineFormatFinder,
InlineFormatter: InlineFormatter,
GreedyInlineFormatFinder: GreedyInlineFormatFinder,
GreedyInlineFormatter: GreedyInlineFormatter,
InlineFormatTool: InlineFormatTool,
FontTool: FontTool,
ColorTool: ColorTool,
StyleTool: StyleTool
});
registerTool("style", new Editor.StyleTool({
template: new ToolTemplate({
template: EditorUtils.dropDownListTemplate,
title: "Styles"
})
}));
registerFormat("bold", [ {
tags: [ "strong" ]
}, {
tags: [ "span" ],
attr: {
style: {
fontWeight: "bold"
}
}
} ]);
registerTool("bold", new InlineFormatTool({
key: "B",
ctrl: true,
format: formats.bold,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Bold"
})
}));
registerFormat("italic", [ {
tags: [ "em" ]
}, {
tags: [ "span" ],
attr: {
style: {
fontStyle: "italic"
}
}
} ]);
registerTool("italic", new InlineFormatTool({
key: "I",
ctrl: true,
format: formats.italic,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Italic"
})
}));
registerFormat("underline", [ {
tags: [ "span" ],
attr: {
style: {
textDecoration: "underline"
}
}
} ]);
registerTool("underline", new InlineFormatTool({
key: "U",
ctrl: true,
format: formats.underline,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Underline"
})
}));
registerFormat("strikethrough", [ {
tags: [ "del" ]
}, {
tags: [ "span" ],
attr: {
style: {
textDecoration: "line-through"
}
}
} ]);
registerTool("strikethrough", new InlineFormatTool({
format: formats.strikethrough,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Strikethrough"
})
}));
registerFormat("superscript", [ {
tags: [ "sup" ]
} ]);
registerTool("superscript", new InlineFormatTool({
format: formats.superscript,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Superscript"
})
}));
registerFormat("subscript", [ {
tags: [ "sub" ]
} ]);
registerTool("subscript", new InlineFormatTool({
format: formats.subscript,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Subscript"
})
}));
registerTool("foreColor", new ColorTool({
cssAttr: "color",
domAttr: "color",
name: "foreColor",
template: new ToolTemplate({
template: EditorUtils.colorPickerTemplate,
title: "Color"
})
}));
registerTool("backColor", new ColorTool({
cssAttr: "background-color",
domAttr: "backgroundColor",
name: "backColor",
template: new ToolTemplate({
template: EditorUtils.colorPickerTemplate,
title: "Background Color"
})
}));
registerTool("fontName", new FontTool({
cssAttr: "font-family",
domAttr: "fontFamily",
name: "fontName",
defaultValue: [ {
text: "fontNameInherit",
value: "inherit"
} ],
template: new ToolTemplate({
template: EditorUtils.comboBoxTemplate,
title: "Font Name"
})
}));
registerTool("fontSize", new FontTool({
cssAttr: "font-size",
domAttr: "fontSize",
name: "fontSize",
defaultValue: [ {
text: "fontSizeInherit",
value: "inherit"
} ],
template: new ToolTemplate({
template: EditorUtils.comboBoxTemplate,
title: "Font Size"
})
}));
})(window.kendo.jQuery);
(function($) {
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, formats = kendo.ui.Editor.fn.options.formats, dom = Editor.Dom, Command = Editor.Command, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate, FormatTool = Editor.FormatTool, EditorUtils = Editor.EditorUtils, registerTool = EditorUtils.registerTool, registerFormat = EditorUtils.registerFormat, RangeUtils = Editor.RangeUtils;
var BlockFormatFinder = Class.extend({
init: function(format) {
this.format = format;
},
contains: function(node, children) {
var i, len, child;
for (i = 0, len = children.length; i < len; i++) {
child = children[i];
if (!child || !dom.isAncestorOrSelf(node, child)) {
return false;
}
}
return true;
},
findSuitable: function(nodes) {
var format = this.format, suitable = [], i, len, candidate;
for (i = 0, len = nodes.length; i < len; i++) {
candidate = dom.ofType(nodes[i], format[0].tags) ? nodes[i] : dom.parentOfType(nodes[i], format[0].tags);
if (!candidate) {
return [];
}
if ($.inArray(candidate, suitable) < 0) {
suitable.push(candidate);
}
}
for (i = 0, len = suitable.length; i < len; i++) {
if (this.contains(suitable[i], suitable)) {
return [ suitable[i] ];
}
}
return suitable;
},
findFormat: function(sourceNode) {
var format = this.format, i, len, node, tags, attributes;
for (i = 0, len = format.length; i < len; i++) {
node = sourceNode;
tags = format[i].tags;
attributes = format[i].attr;
while (node) {
if (dom.ofType(node, tags) && dom.attrEquals(node, attributes)) {
return node;
}
node = node.parentNode;
}
}
return null;
},
getFormat: function(nodes) {
var that = this, findFormat = function(node) {
return that.findFormat(dom.isDataNode(node) ? node.parentNode : node);
}, result = findFormat(nodes[0]), i, len;
if (!result) {
return "";
}
for (i = 1, len = nodes.length; i < len; i++) {
if (result != findFormat(nodes[i])) {
return "";
}
}
return result.nodeName.toLowerCase();
},
isFormatted: function(nodes) {
for (var i = 0, len = nodes.length; i < len; i++) {
if (!this.findFormat(nodes[i])) {
return false;
}
}
return true;
}
});
var BlockFormatter = Class.extend({
init: function(format, values) {
this.format = format;
this.values = values;
this.finder = new BlockFormatFinder(format);
},
wrap: function(tag, attributes, nodes) {
var commonAncestor = nodes.length == 1 ? dom.blockParentOrBody(nodes[0]) : dom.commonAncestor.apply(null, nodes);
if (dom.isInline(commonAncestor)) {
commonAncestor = dom.blockParentOrBody(commonAncestor);
}
var ancestors = dom.significantChildNodes(commonAncestor), position = dom.findNodeIndex(ancestors[0]), wrapper = dom.create(commonAncestor.ownerDocument, tag, attributes), i, ancestor;
for (i = 0; i < ancestors.length; i++) {
ancestor = ancestors[i];
if (dom.isBlock(ancestor)) {
dom.attr(ancestor, attributes);
if (wrapper.childNodes.length) {
dom.insertBefore(wrapper, ancestor);
wrapper = wrapper.cloneNode(false);
}
position = dom.findNodeIndex(ancestor) + 1;
continue;
}
wrapper.appendChild(ancestor);
}
if (wrapper.firstChild) {
dom.insertAt(commonAncestor, wrapper, position);
}
},
apply: function(nodes) {
var that = this, formatNodes = dom.is(nodes[0], "img") ? [ nodes[0] ] : that.finder.findSuitable(nodes), formatToApply = formatNodes.length ? EditorUtils.formatByName(dom.name(formatNodes[0]), that.format) : that.format[0], tag = formatToApply.tags[0], attributes = extend({}, formatToApply.attr, that.values), i, len;
if (formatNodes.length) {
for (i = 0, len = formatNodes.length; i < len; i++) {
dom.attr(formatNodes[i], attributes);
}
} else {
that.wrap(tag, attributes, nodes);
}
},
remove: function(nodes) {
var i, l, formatNode, namedFormat;
for (i = 0, l = nodes.length; i < l; i++) {
formatNode = this.finder.findFormat(nodes[i]);
if (formatNode) {
if (dom.ofType(formatNode, [ "p", "img", "li" ])) {
namedFormat = EditorUtils.formatByName(dom.name(formatNode), this.format);
if (namedFormat.attr.style) {
dom.unstyle(formatNode, namedFormat.attr.style);
}
if (namedFormat.attr.className) {
dom.removeClass(formatNode, namedFormat.attr.className);
}
} else {
dom.unwrap(formatNode);
}
}
}
},
toggle: function(range) {
var that = this, nodes = RangeUtils.nodes(range);
if (that.finder.isFormatted(nodes)) {
that.remove(nodes);
} else {
that.apply(nodes);
}
}
});
var GreedyBlockFormatter = Class.extend({
init: function(format, values) {
var that = this;
that.format = format;
that.values = values;
that.finder = new BlockFormatFinder(format);
},
apply: function(nodes) {
var format = this.format, blocks = dom.blockParents(nodes), formatTag = format[0].tags[0], i, len, list, formatter, range;
if (blocks.length) {
for (i = 0, len = blocks.length; i < len; i++) {
if (dom.is(blocks[i], "li")) {
list = blocks[i].parentNode;
formatter = new Editor.ListFormatter(list.nodeName.toLowerCase(), formatTag);
range = this.editor.createRange();
range.selectNode(blocks[i]);
formatter.toggle(range);
} else {
dom.changeTag(blocks[i], formatTag);
}
}
} else {
new BlockFormatter(format, this.values).apply(nodes);
}
},
toggle: function(range) {
var nodes = RangeUtils.textNodes(range);
if (!nodes.length) {
range.selectNodeContents(range.commonAncestorContainer);
nodes = RangeUtils.textNodes(range);
if (!nodes.length) {
nodes = dom.significantChildNodes(range.commonAncestorContainer);
}
}
this.apply(nodes);
}
});
var FormatCommand = Command.extend({
init: function(options) {
options.formatter = options.formatter();
Command.fn.init.call(this, options);
}
});
var BlockFormatTool = FormatTool.extend({
init: function(options) {
FormatTool.fn.init.call(this, extend(options, {
finder: new BlockFormatFinder(options.format),
formatter: function() {
return new BlockFormatter(options.format);
}
}));
}
});
var FormatBlockTool = Tool.extend({
init: function(options) {
Tool.fn.init.call(this, options);
this.finder = new BlockFormatFinder([ {
tags: dom.blockElements
} ]);
},
command: function(commandArguments) {
return new FormatCommand(extend(commandArguments, {
formatter: function() {
return new GreedyBlockFormatter([ {
tags: [ commandArguments.value ]
} ], {});
}
}));
},
update: function(ui, nodes) {
var list;
if (ui.is("select")) {
list = ui.data("kendoSelectBox");
} else {
list = ui.find("select").data("kendoSelectBox");
}
list.close();
list.value(this.finder.getFormat(nodes));
},
initialize: function(ui, initOptions) {
var editor = initOptions.editor, toolName = "formatBlock";
new Editor.SelectBox(ui, {
dataTextField: "text",
dataValueField: "value",
dataSource: this.options.items ? this.options.items : editor.options.formatBlock,
title: editor.options.messages.formatBlock,
change: function() {
Tool.exec(editor, toolName, this.value());
},
highlightFirst: false
});
ui.closest(".k-widget").removeClass("k-" + toolName).find("*").andSelf().attr("unselectable", "on");
}
});
extend(Editor, {
BlockFormatFinder: BlockFormatFinder,
BlockFormatter: BlockFormatter,
GreedyBlockFormatter: GreedyBlockFormatter,
FormatCommand: FormatCommand,
BlockFormatTool: BlockFormatTool,
FormatBlockTool: FormatBlockTool
});
registerTool("formatBlock", new FormatBlockTool({
template: new ToolTemplate({
template: EditorUtils.dropDownListTemplate
})
}));
registerFormat("justifyLeft", [ {
tags: dom.blockElements,
attr: {
style: {
textAlign: "left"
}
}
}, {
tags: [ "img" ],
attr: {
style: {
"float": "left"
}
}
} ]);
registerTool("justifyLeft", new BlockFormatTool({
format: formats.justifyLeft,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Justify Left"
})
}));
registerFormat("justifyCenter", [ {
tags: dom.blockElements,
attr: {
style: {
textAlign: "center"
}
}
}, {
tags: [ "img" ],
attr: {
style: {
display: "block",
marginLeft: "auto",
marginRight: "auto"
}
}
} ]);
registerTool("justifyCenter", new BlockFormatTool({
format: formats.justifyCenter,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Justify Center"
})
}));
registerFormat("justifyRight", [ {
tags: dom.blockElements,
attr: {
style: {
textAlign: "right"
}
}
}, {
tags: [ "img" ],
attr: {
style: {
"float": "right"
}
}
} ]);
registerTool("justifyRight", new BlockFormatTool({
format: formats.justifyRight,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Justify Right"
})
}));
registerFormat("justifyFull", [ {
tags: dom.blockElements,
attr: {
style: {
textAlign: "justify"
}
}
} ]);
registerTool("justifyFull", new BlockFormatTool({
format: formats.justifyFull,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Justify Full"
})
}));
})(window.kendo.jQuery);
(function($) {
// Imports ================================================================
var kendo = window.kendo, extend = $.extend, Editor = kendo.ui.editor, dom = Editor.Dom, Command = Editor.Command, Tool = Editor.Tool, BlockFormatter = Editor.BlockFormatter, normalize = dom.normalize, RangeUtils = Editor.RangeUtils, registerTool = Editor.EditorUtils.registerTool;
var ParagraphCommand = Command.extend({
init: function(options) {
this.options = options;
Command.fn.init.call(this, options);
},
exec: function() {
var range = this.getRange(), doc = RangeUtils.documentFromRange(range), parent, previous, next, container, emptyParagraphContent = kendo.support.browser.msie ? "" : '<br _moz_dirty="" />', paragraph, marker, li, heading, rng, // necessary while the emptyParagraphContent is empty under IE
blocks = "p,h1,h2,h3,h4,h5,h6".split(","), startInBlock = dom.parentOfType(range.startContainer, blocks), endInBlock = dom.parentOfType(range.endContainer, blocks), shouldTrim = startInBlock && !endInBlock || !startInBlock && endInBlock;
function clean(node) {
if (node.firstChild && dom.is(node.firstChild, "br")) {
dom.remove(node.firstChild);
}
if (dom.isDataNode(node) && !node.nodeValue) {
node = node.parentNode;
}
if (node && !dom.is(node, "img")) {
while (node.firstChild && node.firstChild.nodeType == 1) {
node = node.firstChild;
}
if (node.innerHTML === "") {
node.innerHTML = emptyParagraphContent;
}
}
}
range.deleteContents();
marker = dom.create(doc, "a");
range.insertNode(marker);
if (!marker.parentNode) {
// inserting paragraph in Firefox full body range
container = range.commonAncestorContainer;
container.innerHTML = "";
container.appendChild(marker);
}
normalize(marker.parentNode);
li = dom.parentOfType(marker, [ "li" ]);
heading = dom.parentOfType(marker, "h1,h2,h3,h4,h5,h6".split(","));
if (li) {
rng = range.cloneRange();
rng.selectNode(li);
// hitting 'enter' in empty li
if (!RangeUtils.textNodes(rng).length) {
paragraph = dom.create(doc, "p");
if (li.nextSibling) {
RangeUtils.split(rng, li.parentNode);
}
dom.insertAfter(paragraph, li.parentNode);
dom.remove(li.parentNode.childNodes.length == 1 ? li.parentNode : li);
paragraph.innerHTML = emptyParagraphContent;
next = paragraph;
}
} else if (heading && !marker.nextSibling) {
paragraph = dom.create(doc, "p");
dom.insertAfter(paragraph, heading);
paragraph.innerHTML = emptyParagraphContent;
dom.remove(marker);
next = paragraph;
}
if (!next) {
if (!(li || heading)) {
new BlockFormatter([ {
tags: [ "p" ]
} ]).apply([ marker ]);
}
range.selectNode(marker);
parent = dom.parentOfType(marker, [ li ? "li" : heading ? dom.name(heading) : "p" ]);
RangeUtils.split(range, parent, shouldTrim);
previous = parent.previousSibling;
if (dom.is(previous, "li") && previous.firstChild && !dom.is(previous.firstChild, "br")) {
previous = previous.firstChild;
}
next = parent.nextSibling;
if (dom.is(next, "li") && next.firstChild && !dom.is(next.firstChild, "br")) {
next = next.firstChild;
}
dom.remove(parent);
clean(previous);
clean(next);
// normalize updates the caret display in Gecko
normalize(previous);
}
normalize(next);
if (dom.is(next, "img")) {
range.setStartBefore(next);
} else {
range.selectNodeContents(next);
var textNode = RangeUtils.textNodes(range)[0];
if (textNode) {
range.selectNodeContents(textNode);
}
}
range.collapse(true);
dom.scrollTo(next);
RangeUtils.selectRange(range);
}
});
var NewLineCommand = Command.extend({
init: function(options) {
this.options = options;
Command.fn.init.call(this, options);
},
exec: function() {
var range = this.getRange();
range.deleteContents();
var br = dom.create(RangeUtils.documentFromRange(range), "br");
range.insertNode(br);
normalize(br.parentNode);
if (!kendo.support.browser.msie && (!br.nextSibling || dom.isWhitespace(br.nextSibling))) {
//Gecko and WebKit cannot put the caret after only one br.
var filler = br.cloneNode(true);
filler.setAttribute("_moz_dirty", "");
dom.insertAfter(filler, br);
}
range.setStartAfter(br);
range.collapse(true);
dom.scrollTo(br.nextSibling);
RangeUtils.selectRange(range);
}
});
extend(Editor, {
ParagraphCommand: ParagraphCommand,
NewLineCommand: NewLineCommand
});
registerTool("insertLineBreak", new Tool({
key: 13,
shift: true,
command: NewLineCommand
}));
registerTool("insertParagraph", new Tool({
key: 13,
command: ParagraphCommand
}));
})(window.kendo.jQuery);
(function($) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, dom = Editor.Dom, RangeUtils = Editor.RangeUtils, EditorUtils = Editor.EditorUtils, Command = Editor.Command, ToolTemplate = Editor.ToolTemplate, FormatTool = Editor.FormatTool, BlockFormatFinder = Editor.BlockFormatFinder, textNodes = RangeUtils.textNodes, registerTool = Editor.EditorUtils.registerTool;
var ListFormatFinder = BlockFormatFinder.extend({
init: function(tag) {
this.tag = tag;
var tags = this.tags = [ tag == "ul" ? "ol" : "ul", tag ];
BlockFormatFinder.fn.init.call(this, [ {
tags: tags
} ]);
},
isFormatted: function(nodes) {
var formatNodes = [], formatNode;
for (var i = 0; i < nodes.length; i++) {
if ((formatNode = this.findFormat(nodes[i])) && dom.name(formatNode) == this.tag) {
formatNodes.push(formatNode);
}
}
if (formatNodes.length < 1) {
return false;
}
if (formatNodes.length != nodes.length) {
return false;
}
// check if sequential lists are selected
for (i = 0; i < formatNodes.length; i++) {
if (formatNodes[i].parentNode != formatNode.parentNode) {
break;
}
if (formatNodes[i] != formatNode) {
return false;
}
}
return true;
},
findSuitable: function(nodes) {
var candidate = dom.parentOfType(nodes[0], this.tags);
if (candidate && dom.name(candidate) == this.tag) {
return candidate;
}
return null;
}
});
var ListFormatter = Class.extend({
init: function(tag, unwrapTag) {
var that = this;
that.finder = new ListFormatFinder(tag);
that.tag = tag;
that.unwrapTag = unwrapTag;
},
wrap: function(list, nodes) {
var li = dom.create(list.ownerDocument, "li"), i, node;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
if (dom.is(node, "li")) {
list.appendChild(node);
continue;
}
if (dom.is(node, "ul") || dom.is(node, "ol")) {
while (node.firstChild) {
list.appendChild(node.firstChild);
}
continue;
}
if (dom.is(node, "td")) {
while (node.firstChild) {
li.appendChild(node.firstChild);
}
list.appendChild(li);
node.appendChild(list);
list = list.cloneNode(false);
li = li.cloneNode(false);
continue;
}
li.appendChild(node);
if (dom.isBlock(node)) {
list.appendChild(li);
dom.unwrap(node);
li = li.cloneNode(false);
}
}
if (li.firstChild) {
list.appendChild(li);
}
},
containsAny: function(parent, nodes) {
for (var i = 0; i < nodes.length; i++) {
if (dom.isAncestorOrSelf(parent, nodes[i])) {
return true;
}
}
return false;
},
suitable: function(candidate, nodes) {
if (candidate.className == "k-marker") {
var sibling = candidate.nextSibling;
if (sibling && dom.isBlock(sibling)) {
return false;
}
sibling = candidate.previousSibling;
if (sibling && dom.isBlock(sibling)) {
return false;
}
}
return this.containsAny(candidate, nodes) || dom.isInline(candidate) || candidate.nodeType == 3;
},
split: function(range) {
var nodes = textNodes(range), start, end;
if (nodes.length) {
start = dom.parentOfType(nodes[0], [ "li" ]);
end = dom.parentOfType(nodes[nodes.length - 1], [ "li" ]);
range.setStartBefore(start);
range.setEndAfter(end);
for (var i = 0, l = nodes.length; i < l; i++) {
var formatNode = this.finder.findFormat(nodes[i]);
if (formatNode) {
var parents = $(formatNode).parents("ul,ol");
if (parents[0]) {
RangeUtils.split(range, parents.last()[0], true);
} else {
RangeUtils.split(range, formatNode, true);
}
}
}
}
},
merge: function(tag, formatNode) {
var prev = formatNode.previousSibling, next;
while (prev && (prev.className == "k-marker" || prev.nodeType == 3 && dom.isWhitespace(prev))) {
prev = prev.previousSibling;
}
// merge with previous list
if (prev && dom.name(prev) == tag) {
while (formatNode.firstChild) {
prev.appendChild(formatNode.firstChild);
}
dom.remove(formatNode);
formatNode = prev;
}
next = formatNode.nextSibling;
while (next && (next.className == "k-marker" || next.nodeType == 3 && dom.isWhitespace(next))) {
next = next.nextSibling;
}
// merge with next list
if (next && dom.name(next) == tag) {
while (formatNode.lastChild) {
next.insertBefore(formatNode.lastChild, next.firstChild);
}
dom.remove(formatNode);
}
},
applyOnSection: function(section, nodes) {
var tag = this.tag, commonAncestor;
if (nodes.length == 1) {
commonAncestor = dom.parentOfType(nodes[0], [ "ul", "ol" ]);
} else {
commonAncestor = dom.commonAncestor.apply(null, nodes);
}
if (!commonAncestor) {
commonAncestor = dom.parentOfType(nodes[0], [ "p", "td" ]) || nodes[0].ownerDocument.body;
}
if (dom.isInline(commonAncestor)) {
commonAncestor = dom.blockParentOrBody(commonAncestor);
}
var ancestors = [];
var formatNode = this.finder.findSuitable(nodes);
if (!formatNode) {
formatNode = new ListFormatFinder(tag == "ul" ? "ol" : "ul").findSuitable(nodes);
}
var childNodes = dom.significantChildNodes(commonAncestor);
if (!childNodes.length) {
childNodes = nodes;
}
if (/table|tbody/.test(dom.name(commonAncestor))) {
childNodes = $.map(nodes, function(node) {
return dom.parentOfType(node, [ "td" ]);
});
}
function pushAncestor() {
ancestors.push(this);
}
for (var i = 0; i < childNodes.length; i++) {
var child = childNodes[i];
var nodeName = dom.name(child);
if (this.suitable(child, nodes) && (!formatNode || !dom.isAncestorOrSelf(formatNode, child))) {
if (formatNode && (nodeName == "ul" || nodeName == "ol")) {
// merging lists
//Array.prototype.push.apply(ancestors, $.toArray(child.childNodes));
$.each(child.childNodes, pushAncestor);
dom.remove(child);
} else {
ancestors.push(child);
}
}
}
if (ancestors.length == childNodes.length && commonAncestor != nodes[0].ownerDocument.body && !/table|tbody|tr|td/.test(dom.name(commonAncestor))) {
ancestors = [ commonAncestor ];
}
if (!formatNode) {
formatNode = dom.create(commonAncestor.ownerDocument, tag);
dom.insertBefore(formatNode, ancestors[0]);
}
this.wrap(formatNode, ancestors);
if (!dom.is(formatNode, tag)) {
dom.changeTag(formatNode, tag);
}
this.merge(tag, formatNode);
},
apply: function(nodes) {
var i = 0, sections = [], lastSection, lastNodes, section;
// split nodes into sections that need to be different lists
do {
section = dom.parentOfType(nodes[i], [ "td", "body" ]);
if (!lastSection || section != lastSection) {
if (lastSection) {
sections.push({
section: lastSection,
nodes: lastNodes
});
}
lastNodes = [ nodes[i] ];
lastSection = section;
} else {
lastNodes.push(nodes[i]);
}
i++;
} while (i < nodes.length);
sections.push({
section: lastSection,
nodes: lastNodes
});
for (i = 0; i < sections.length; i++) {
this.applyOnSection(sections[i].section, sections[i].nodes);
}
},
unwrap: function(ul) {
var fragment = ul.ownerDocument.createDocumentFragment(), unwrapTag = this.unwrapTag, parents, li, p, child;
for (li = ul.firstChild; li; li = li.nextSibling) {
p = dom.create(ul.ownerDocument, unwrapTag || "p");
while (li.firstChild) {
child = li.firstChild;
if (dom.isBlock(child)) {
if (p.firstChild) {
fragment.appendChild(p);
p = dom.create(ul.ownerDocument, unwrapTag || "p");
}
fragment.appendChild(child);
} else {
p.appendChild(child);
}
}
if (p.firstChild) {
fragment.appendChild(p);
}
}
parents = $(ul).parents("ul,ol");
if (parents[0]) {
dom.insertAfter(fragment, parents.last()[0]);
parents.last().remove();
} else {
dom.insertAfter(fragment, ul);
}
dom.remove(ul);
},
remove: function(nodes) {
var formatNode;
for (var i = 0, l = nodes.length; i < l; i++) {
formatNode = this.finder.findFormat(nodes[i]);
if (formatNode) {
this.unwrap(formatNode);
}
}
},
toggle: function(range) {
var that = this, nodes = textNodes(range), ancestor = range.commonAncestorContainer;
if (!nodes.length) {
range.selectNodeContents(ancestor);
nodes = textNodes(range);
if (!nodes.length) {
var text = ancestor.ownerDocument.createTextNode("");
range.startContainer.appendChild(text);
nodes = [ text ];
range.selectNode(text.parentNode);
}
}
if (that.finder.isFormatted(nodes)) {
that.split(range);
that.remove(nodes);
} else {
that.apply(nodes);
}
}
});
var ListCommand = Command.extend({
init: function(options) {
options.formatter = new ListFormatter(options.tag);
Command.fn.init.call(this, options);
}
});
var ListTool = FormatTool.extend({
init: function(options) {
this.options = options;
FormatTool.fn.init.call(this, extend(options, {
finder: new ListFormatFinder(options.tag)
}));
},
command: function(commandArguments) {
return new ListCommand(extend(commandArguments, {
tag: this.options.tag
}));
}
});
extend(Editor, {
ListFormatFinder: ListFormatFinder,
ListFormatter: ListFormatter,
ListCommand: ListCommand,
ListTool: ListTool
});
registerTool("insertUnorderedList", new ListTool({
tag: "ul",
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Remove Link"
})
}));
registerTool("insertOrderedList", new ListTool({
tag: "ol",
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Remove Link"
})
}));
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, dom = Editor.Dom, RangeUtils = Editor.RangeUtils, EditorUtils = Editor.EditorUtils, Command = Editor.Command, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate, InlineFormatter = Editor.InlineFormatter, InlineFormatFinder = Editor.InlineFormatFinder, textNodes = RangeUtils.textNodes, registerTool = Editor.EditorUtils.registerTool;
var LinkFormatFinder = Class.extend({
findSuitable: function(sourceNode) {
return dom.parentOfType(sourceNode, [ "a" ]);
}
});
var LinkFormatter = Class.extend({
init: function() {
this.finder = new LinkFormatFinder();
},
apply: function(range, attributes) {
var nodes = textNodes(range), markers, doc, formatter, a;
if (attributes.innerHTML) {
markers = RangeUtils.getMarkers(range);
doc = RangeUtils.documentFromRange(range);
range.deleteContents();
a = dom.create(doc, "a", attributes);
range.insertNode(a);
if (dom.name(a.parentNode) == "a") {
dom.insertAfter(a, a.parentNode);
}
if (markers.length > 1) {
dom.insertAfter(markers[markers.length - 1], a);
dom.insertAfter(markers[1], a);
dom[nodes.length > 0 ? "insertBefore" : "insertAfter"](markers[0], a);
}
} else {
formatter = new InlineFormatter([ {
tags: [ "a" ]
} ], attributes);
formatter.finder = this.finder;
formatter.apply(nodes);
}
}
});
var UnlinkCommand = Command.extend({
init: function(options) {
options.formatter = /** @ignore */ {
toggle: function(range) {
new InlineFormatter([ {
tags: [ "a" ]
} ]).remove(textNodes(range));
}
};
this.options = options;
Command.fn.init.call(this, options);
}
});
var LinkCommand = Command.extend({
init: function(options) {
var cmd = this;
cmd.options = options;
Command.fn.init.call(cmd, options);
cmd.formatter = new LinkFormatter();
if (!options.url) {
cmd.attributes = null;
cmd.async = true;
} else {
this.exec = function() {
this.formatter.apply(options.range, {
href: options.url,
innerHTML: options.text || options.url,
target: options.target
});
};
}
},
_dialogTemplate: function(showText) {
return kendo.template('<div class="k-editor-dialog">' + "<ol>" + "<li class='k-form-text-row'>" + "<label for='k-editor-link-url'>#: messages.linkWebAddress #</label>" + "<input type='text' class='k-input' id='k-editor-link-url'>" + "</li>" + "# if (showText) { #" + "<li class='k-form-text-row'>" + "<label for='k-editor-link-text'>#: messages.linkText #</label>" + "<input type='text' class='k-input' id='k-editor-link-text'>" + "</li>" + "# } #" + "<li class='k-form-text-row'>" + "<label for='k-editor-link-title'>#: messages.linkToolTip #</label>" + "<input type='text' class='k-input' id='k-editor-link-title'>" + "</li>" + "<li class='k-form-checkbox-row'>" + "<input type='checkbox' id='k-editor-link-target'>" + "<label for='k-editor-link-target'>#: messages.linkOpenInNewWindow #</label>" + "</li>" + "</ol>" + "<div class='k-button-wrapper'>" + '<button class="k-dialog-insert k-button">#: messages.dialogInsert #</button>' + " #: messages.dialogButtonSeparator # " + '<a href="\\#" class="k-dialog-close k-link">#: messages.dialogCancel #</a>' + "</div>" + "</div>")({
messages: this.editor.options.messages,
showText: showText
});
},
exec: function() {
var that = this, range = that.getRange(), collapsed = range.collapsed, nodes, initialText = null, messages = that.editor.options.messages;
range = that.lockRange(true);
nodes = textNodes(range);
function apply(e) {
var element = dialog.element, href = $("#k-editor-link-url", element).val(), title, text, target;
if (href && href != "http://") {
that.attributes = {
href: href
};
title = $("#k-editor-link-title", element).val();
if (title) {
that.attributes.title = title;
}
text = $("#k-editor-link-text", element).val();
if (text !== initialText) {
that.attributes.innerHTML = text || href;
}
target = $("#k-editor-link-target", element).is(":checked");
that.attributes.target = target ? "_blank" : null;
that.formatter.apply(range, that.attributes);
}
close(e);
if (that.change) {
that.change();
}
}
function close(e) {
e.preventDefault();
dialog.destroy();
dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
that.releaseRange(range);
}
var a = nodes.length ? that.formatter.finder.findSuitable(nodes[0]) : null;
var showText = nodes.length <= 1 || nodes.length == 2 && collapsed;
var dialog = EditorUtils.createDialog(that._dialogTemplate(showText), that.editor, extend({}, that.editor.options.dialogOptions, {
title: messages.createLink,
close: close,
visible: false
})).find(".k-dialog-insert").click(apply).end().find(".k-dialog-close").click(close).end().find(".k-form-text-row input").keydown(function(e) {
var keys = kendo.keys;
if (e.keyCode == keys.ENTER) {
apply(e);
} else if (e.keyCode == keys.ESC) {
close(e);
}
}).end().find("#k-editor-link-url").val(a ? a.getAttribute("href", 2) : "http://").end().find("#k-editor-link-text").val(nodes.length > 0 ? nodes.length == 1 ? nodes[0].nodeValue : nodes[0].nodeValue + nodes[1].nodeValue : "").end().find("#k-editor-link-title").val(a ? a.title : "").end().find("#k-editor-link-target").attr("checked", a ? a.target == "_blank" : false).end().data("kendoWindow").center().open();
if (showText && nodes.length > 0) {
initialText = $("#k-editor-link-text", dialog.element).val();
}
$("#k-editor-link-url", dialog.element).focus().select();
},
redo: function() {
var that = this, range = that.lockRange(true);
that.formatter.apply(range, that.attributes);
that.releaseRange(range);
}
});
var UnlinkTool = Tool.extend({
init: function(options) {
this.options = options;
this.finder = new InlineFormatFinder([ {
tags: [ "a" ]
} ]);
Tool.fn.init.call(this, $.extend(options, {
command: UnlinkCommand
}));
},
initialize: function(ui, options) {
Tool.fn.initialize.call(this, ui, options);
ui.addClass("k-state-disabled");
},
update: function(ui, nodes) {
ui.toggleClass("k-state-disabled", !this.finder.isFormatted(nodes)).removeClass("k-state-hover");
}
});
extend(kendo.ui.editor, {
LinkFormatFinder: LinkFormatFinder,
LinkFormatter: LinkFormatter,
UnlinkCommand: UnlinkCommand,
LinkCommand: LinkCommand,
UnlinkTool: UnlinkTool
});
registerTool("createLink", new Tool({
key: "K",
ctrl: true,
command: LinkCommand,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Create Link"
})
}));
registerTool("unlink", new UnlinkTool({
key: "K",
ctrl: true,
shift: true,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Remove Link"
})
}));
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, extend = $.extend, Editor = kendo.ui.editor, EditorUtils = Editor.EditorUtils, dom = Editor.Dom, registerTool = EditorUtils.registerTool, ToolTemplate = Editor.ToolTemplate, RangeUtils = Editor.RangeUtils, Command = Editor.Command, keys = kendo.keys, KEDITORIMAGEURL = "#k-editor-image-url", KEDITORIMAGETITLE = "#k-editor-image-title";
var ImageCommand = Command.extend({
init: function(options) {
var that = this;
Command.fn.init.call(that, options);
that.async = true;
that.attributes = {};
},
insertImage: function(img, range) {
var attributes = this.attributes;
if (attributes.src && attributes.src != "http://") {
if (!img) {
img = dom.create(RangeUtils.documentFromRange(range), "img", attributes);
img.onload = img.onerror = function() {
img.removeAttribute("complete");
img.removeAttribute("width");
img.removeAttribute("height");
};
range.deleteContents();
range.insertNode(img);
range.setStartAfter(img);
range.setEndAfter(img);
RangeUtils.selectRange(range);
return true;
} else {
dom.attr(img, attributes);
}
}
return false;
},
_dialogTemplate: function(showBrowser) {
return kendo.template('<div class="k-editor-dialog">' + "# if (showBrowser) { #" + '<div class="k-imagebrowser"></div>' + "# } #" + "<ol>" + '<li class="k-form-text-row">' + '<label for="k-editor-image-url">#: messages.imageWebAddress #</label>' + '<input type="text" class="k-input" id="k-editor-image-url">' + "</li>" + '<li class="k-form-text-row">' + '<label for="k-editor-image-title">#: messages.imageAltText #</label>' + '<input type="text" class="k-input" id="k-editor-image-title">' + "</li>" + "</ol>" + '<div class="k-button-wrapper">' + '<button class="k-dialog-insert k-button">#: messages.dialogInsert #</button>' + " #: messages.dialogButtonSeparator # " + '<a href="\\#" class="k-dialog-close k-link">#: messages.dialogCancel #</a>' + "</div>" + "</div>")({
messages: this.editor.options.messages,
showBrowser: showBrowser
});
},
redo: function() {
var that = this, range = that.lockRange();
if (!that.insertImage(RangeUtils.image(range), range)) {
that.releaseRange(range);
}
},
exec: function() {
var that = this, range = that.lockRange(), applied = false, img = RangeUtils.image(range), dialog, dialogWidth, options = that.editor.options, messages = options.messages, imageBrowser = options.imageBrowser, showBrowser = !!(kendo.ui.ImageBrowser && imageBrowser && imageBrowser.transport && imageBrowser.transport.read !== undefined);
function apply(e) {
var element = dialog.element;
that.attributes = {
src: element.find(KEDITORIMAGEURL).val().replace(/ /g, "%20"),
alt: element.find(KEDITORIMAGETITLE).val()
};
applied = that.insertImage(img, range);
close(e);
if (that.change) {
that.change();
}
}
function close(e) {
e.preventDefault();
dialog.destroy();
dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
if (!applied) {
that.releaseRange(range);
}
}
function keyDown(e) {
if (e.keyCode == keys.ENTER) {
apply(e);
} else if (e.keyCode == keys.ESC) {
close(e);
}
}
dialogWidth = showBrowser ? {
width: "960px",
height: "650px"
} : {};
dialog = EditorUtils.createDialog(that._dialogTemplate(showBrowser), that.editor, extend(dialogWidth, options.dialogOptions, {
title: messages.insertImage,
close: close,
visible: false,
resizable: showBrowser,
activate: function() {
if (showBrowser) {
var that = this;
new kendo.ui.ImageBrowser(that.element.find(".k-imagebrowser"), extend({}, imageBrowser, {
change: function() {
that.element.find(KEDITORIMAGEURL).val(this.value());
},
apply: apply
}));
}
}
})).find(".k-dialog-insert").click(apply).end().find(".k-dialog-close").click(close).end().find(".k-form-text-row input").keydown(keyDown).end().find(KEDITORIMAGEURL).val(img ? img.getAttribute("src", 2) : "http://").end().find(KEDITORIMAGETITLE).val(img ? img.alt : "").end().data("kendoWindow").center().open();
$(KEDITORIMAGEURL, dialog.element).focus().select();
}
});
kendo.ui.editor.ImageCommand = ImageCommand;
registerTool("insertImage", new Editor.Tool({
command: ImageCommand,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Insert Image"
})
}));
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, DropDownList = kendo.ui.DropDownList;
var SelectBox = DropDownList.extend({
init: function(element, options) {
var that = this;
DropDownList.fn.init.call(that, element, options);
that.value(that.options.title);
},
options: {
name: "SelectBox"
},
value: function(value) {
var that = this, result = DropDownList.fn.value.call(that, value);
if (value === undefined) {
return result;
}
if (value !== DropDownList.fn.value.call(that)) {
that.text(that.options.title);
that._current.removeClass("k-state-selected");
that.current(null);
that._oldIndex = that.selectedIndex = -1;
}
}
});
kendo.ui.plugin(SelectBox);
kendo.ui.editor.SelectBox = SelectBox;
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, dom = Editor.Dom, EditorUtils = Editor.EditorUtils, registerTool = EditorUtils.registerTool, Command = Editor.Command, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate, RangeUtils = Editor.RangeUtils, blockElements = dom.blockElements, BlockFormatFinder = Editor.BlockFormatFinder, BlockFormatter = Editor.BlockFormatter;
function indent(node, value) {
var isRtl = $(node).css("direction") == "rtl", indentDirection = isRtl ? "Right" : "Left", property = dom.name(node) != "td" ? "margin" + indentDirection : "padding" + indentDirection;
if (value === undefined) {
return node.style[property] || 0;
} else {
if (value > 0) {
node.style[property] = value + "px";
} else {
node.style[property] = "";
if (!node.style.cssText) {
node.removeAttribute("style");
}
}
}
}
var IndentFormatter = Class.extend({
init: function() {
this.finder = new BlockFormatFinder([ {
tags: dom.blockElements
} ]);
},
apply: function(nodes) {
var formatNodes = this.finder.findSuitable(nodes), targets = [], i, len, formatNode, parentList, sibling;
if (formatNodes.length) {
for (i = 0, len = formatNodes.length; i < len; i++) {
if (dom.is(formatNodes[i], "li")) {
if (!$(formatNodes[i]).index()) {
targets.push(formatNodes[i].parentNode);
} else if ($.inArray(formatNodes[i].parentNode, targets) < 0) {
targets.push(formatNodes[i]);
}
} else {
targets.push(formatNodes[i]);
}
}
while (targets.length) {
formatNode = targets.shift();
if (dom.is(formatNode, "li")) {
parentList = formatNode.parentNode;
sibling = $(formatNode).prev("li");
var siblingList = sibling.find("ul,ol").last();
var nestedList = $(formatNode).children("ul,ol")[0];
if (nestedList && sibling[0]) {
if (siblingList[0]) {
siblingList.append(formatNode);
siblingList.append($(nestedList).children());
dom.remove(nestedList);
} else {
sibling.append(nestedList);
nestedList.insertBefore(formatNode, nestedList.firstChild);
}
} else {
nestedList = sibling.children("ul,ol")[0];
if (!nestedList) {
nestedList = dom.create(formatNode.ownerDocument, dom.name(parentList));
sibling.append(nestedList);
}
while (formatNode && formatNode.parentNode == parentList) {
nestedList.appendChild(formatNode);
formatNode = targets.shift();
}
}
} else {
var marginLeft = parseInt(indent(formatNode), 10) + 30;
indent(formatNode, marginLeft);
for (var targetIndex = 0; targetIndex < targets.length; targetIndex++) {
if ($.contains(formatNode, targets[targetIndex])) {
targets.splice(targetIndex, 1);
}
}
}
}
} else {
var formatter = new BlockFormatter([ {
tags: "p"
} ], {
style: {
marginLeft: 30
}
});
formatter.apply(nodes);
}
},
remove: function(nodes) {
var formatNodes = this.finder.findSuitable(nodes), targetNode, i, len, list, listParent, siblings, formatNode, marginLeft;
for (i = 0, len = formatNodes.length; i < len; i++) {
formatNode = $(formatNodes[i]);
if (formatNode.is("li")) {
list = formatNode.parent();
listParent = list.parent();
// listParent will be ul or ol in case of invalid dom - <ul><li></li><ul><li></li></ul></ul>
if (listParent.is("li,ul,ol") && !indent(list[0])) {
// skip already processed nodes
if (targetNode && $.contains(targetNode, listParent[0])) {
continue;
}
siblings = formatNode.nextAll("li");
if (siblings.length) {
$(list[0].cloneNode(false)).appendTo(formatNode).append(siblings);
}
if (listParent.is("li")) {
formatNode.insertAfter(listParent);
} else {
formatNode.appendTo(listParent);
}
if (!list.children("li").length) {
list.remove();
}
continue;
} else {
if (targetNode == list[0]) {
// removing format on sibling LI elements
continue;
}
targetNode = list[0];
}
} else {
targetNode = formatNodes[i];
}
marginLeft = parseInt(indent(targetNode), 10) - 30;
indent(targetNode, marginLeft);
}
}
});
var IndentCommand = Command.extend({
init: function(options) {
options.formatter = /** @ignore */ {
toggle: function(range) {
new IndentFormatter().apply(RangeUtils.nodes(range));
}
};
Command.fn.init.call(this, options);
}
});
var OutdentCommand = Command.extend({
init: function(options) {
options.formatter = {
toggle: function(range) {
new IndentFormatter().remove(RangeUtils.nodes(range));
}
};
Command.fn.init.call(this, options);
}
});
var OutdentTool = Tool.extend({
init: function(options) {
Tool.fn.init.call(this, options);
this.finder = new BlockFormatFinder([ {
tags: blockElements
} ]);
},
update: function(ui, nodes) {
var suitable = this.finder.findSuitable(nodes), isOutdentable, listParentsCount, i, len;
for (i = 0, len = suitable.length; i < len; i++) {
isOutdentable = indent(suitable[i]);
if (!isOutdentable) {
listParentsCount = $(suitable[i]).parents("ul,ol").length;
isOutdentable = dom.is(suitable[i], "li") && (listParentsCount > 1 || indent(suitable[i].parentNode)) || dom.ofType(suitable[i], [ "ul", "ol" ]) && listParentsCount > 0;
}
if (isOutdentable) {
ui.removeClass("k-state-disabled");
return;
}
}
ui.addClass("k-state-disabled").removeClass("k-state-hover");
}
});
extend(Editor, {
IndentFormatter: IndentFormatter,
IndentCommand: IndentCommand,
OutdentCommand: OutdentCommand,
OutdentTool: OutdentTool
});
registerTool("indent", new Tool({
command: IndentCommand,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Indent"
})
}));
registerTool("outdent", new OutdentTool({
command: OutdentCommand,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "Outdent"
})
}));
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, extend = $.extend, Editor = kendo.ui.editor, dom = Editor.Dom, RangeUtils = Editor.RangeUtils, EditorUtils = Editor.EditorUtils, Command = Editor.Command, Tool = Editor.Tool, ToolTemplate = Editor.ToolTemplate;
var ViewHtmlCommand = Command.extend({
init: function(options) {
var cmd = this;
cmd.options = options;
Command.fn.init.call(cmd, options);
cmd.attributes = null;
cmd.async = true;
},
exec: function() {
var that = this, editor = that.editor, range = editor.getRange(), dialog = $(ViewHtmlCommand.template).appendTo(document.body), content = ViewHtmlCommand.indent(editor.value()), textarea = ".k-editor-textarea", messages = editor.options.messages;
function apply(e) {
editor.value(dialog.find(textarea).val());
close(e);
if (that.change) {
that.change();
}
editor.trigger("change");
}
function close(e) {
e.preventDefault();
dialog.data("kendoWindow").destroy();
dom.windowFromDocument(RangeUtils.documentFromRange(range)).focus();
}
dialog.kendoWindow(extend({}, editor.options.dialogOptions, {
title: messages.viewHtml,
close: close
})).hide().find(textarea).val(content).end().find(".k-dialog-update").click(apply).end().find(".k-dialog-close").click(close).end().show().data("kendoWindow").center();
dialog.find(textarea).focus();
}
});
extend(ViewHtmlCommand, {
template: "<div class='k-editor-dialog'>" + "<textarea class='k-editor-textarea k-input'></textarea>" + "<div class='k-button-wrapper'>" + "<button class='k-dialog-update k-button'>Update</button>" + " or " + "<a href='#' class='k-dialog-close k-link'>Close</a>" + "</div>" + "</div>",
indent: function(content) {
return content.replace(/<\/(p|li|ul|ol|h[1-6]|table|tr|td|th)>/gi, "</$1>\n").replace(/<(ul|ol)([^>]*)><li/gi, "<$1$2>\n<li").replace(/<br \/>/gi, "<br />\n").replace(/\n$/, "");
}
});
kendo.ui.editor.ViewHtmlCommand = ViewHtmlCommand;
Editor.EditorUtils.registerTool("viewHtml", new Tool({
command: ViewHtmlCommand,
template: new ToolTemplate({
template: EditorUtils.buttonTemplate,
title: "View HTML"
})
}));
})(window.kendo.jQuery);
(function($) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, extend = $.extend, Editor = kendo.ui.editor, RangeUtils = Editor.RangeUtils, Marker = Editor.Marker;
var PendingFormats = Class.extend({
init: function(editor) {
this.editor = editor;
this.formats = [];
},
apply: function(range) {
if (!this.hasPending()) {
return;
}
var marker = new Marker();
marker.addCaret(range);
var caret = range.startContainer.childNodes[range.startOffset];
var target = caret.previousSibling;
if (target) {
/* under IE, target may be a zero-length text node */
if (!target.nodeValue) {
target = target.previousSibling;
}
range.setStart(target, target.nodeValue.length - 1);
} else {
range.setStart(range.startContainer, range.startOffset);
}
marker.add(range);
if (!RangeUtils.textNodes(range).length) {
marker.remove(range);
range.collapse(true);
this.editor.selectRange(range);
return;
}
var textNode = marker.end.previousSibling.previousSibling;
var pendingFormat, formats = this.formats;
for (var i = 0; i < formats.length; i++) {
pendingFormat = formats[i];
var command = pendingFormat.command(extend({
range: range
}, pendingFormat.options.params));
command.editor = this.editor;
command.exec();
range.selectNode(textNode);
}
marker.remove(range);
if (textNode.parentNode) {
range.setStart(textNode, 1);
range.collapse(true);
}
this.clear();
this.editor.selectRange(range);
},
hasPending: function() {
return this.formats.length > 0;
},
isPending: function(format) {
return !!this.getPending(format);
},
getPending: function(format) {
var formats = this.formats;
for (var i = 0; i < formats.length; i++) {
if (formats[i].name == format) {
return formats[i];
}
}
return;
},
toggle: function(format) {
var formats = this.formats;
for (var i = 0; i < formats.length; i++) {
if (formats[i].name == format.name) {
if (formats[i].params && formats[i].params.value != format.params.value) {
formats[i].params.value = format.params.value;
} else {
formats.splice(i, 1);
}
return;
}
}
formats.push(format);
},
clear: function() {
this.formats = [];
}
});
extend(Editor, {
PendingFormats: PendingFormats
});
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, keys = kendo.keys, ui = kendo.ui, Widget = ui.Widget, activeElement = kendo._activeElement, extractFormat = kendo._extractFormat, parse = kendo.parseFloat, placeholderSupported = kendo.support.placeholder, getCulture = kendo.getCulture, CHANGE = "change", DISABLED = "disabled", READONLY = "readonly", INPUT = "k-input", SPIN = "spin", ns = ".kendoNumericTextBox", TOUCHEND = "touchend", MOUSELEAVE = "mouseleave" + ns, MOUSEUP = "touchcancel" + ns + " " + "touchend" + ns + " mouseup" + ns + " " + MOUSELEAVE, HOVEREVENTS = "mouseenter" + ns + " " + MOUSELEAVE, DEFAULT = "k-state-default", FOCUSED = "k-state-focused", HOVER = "k-state-hover", FOCUS = "focus", POINT = ".", SELECTED = "k-state-selected", STATEDISABLED = "k-state-disabled", ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", NULL = null, proxy = $.proxy, decimals = {
190: ".",
188: ","
};
var NumericTextBox = Widget.extend({
init: function(element, options) {
var that = this, isStep = options && options.step !== undefined, min, max, step, value, disabled;
Widget.fn.init.call(that, element, options);
options = that.options;
element = that.element.on("blur" + ns, proxy(that._focusout, that)).attr("role", "spinbutton");
options.placeholder = options.placeholder || element.attr("placeholder");
that._reset();
that._wrapper();
that._arrows();
that._input();
if (!kendo.support.mobileOS) {
that._text.on(FOCUS + ns, proxy(that._click, that));
} else {
that._text.on(TOUCHEND + ns + " " + FOCUS + ns, function(e) {
that._toggleText(false);
if (e.type === FOCUS) {
element.focus();
}
});
}
min = that.min(element.attr("min"));
max = that.max(element.attr("max"));
step = that._parse(element.attr("step"));
if (options.min === NULL && min !== NULL) {
options.min = min;
}
if (options.max === NULL && max !== NULL) {
options.max = max;
}
if (!isStep && step !== NULL) {
options.step = step;
}
element.attr("aria-valuemin", options.min).attr("aria-valuemax", options.max);
options.format = extractFormat(options.format);
value = options.value;
that.value(value !== NULL ? value : element.val());
disabled = element.is("[disabled]");
if (disabled) {
that.enable(false);
} else {
that.readonly(element.is("[readonly]"));
}
kendo.notify(that);
},
options: {
name: "NumericTextBox",
decimals: NULL,
min: NULL,
max: NULL,
value: NULL,
step: 1,
culture: "",
format: "n",
spinners: true,
placeholder: "",
upArrowText: "Increase value",
downArrowText: "Decrease value"
},
events: [ CHANGE, SPIN ],
_editable: function(options) {
var that = this, element = that.element, disable = options.disable, readonly = options.readonly, text = that._text.add(element), wrapper = that._inputWrapper.off(HOVEREVENTS);
that._toggleText(true);
that._upArrowEventHandler.unbind("press");
that._downArrowEventHandler.unbind("press");
element.off("keydown" + ns).off("paste" + ns);
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
text.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false);
that._upArrowEventHandler.bind("press", function(e) {
e.preventDefault();
that._spin(1);
that._upArrow.addClass(SELECTED);
});
that._downArrowEventHandler.bind("press", function(e) {
e.preventDefault();
that._spin(-1);
that._downArrow.addClass(SELECTED);
});
that.element.on("keydown" + ns, proxy(that._keydown, that)).on("paste" + ns, proxy(that._paste, that));
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
text.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
readonly: function(readonly) {
this._editable({
readonly: readonly === undefined ? true : readonly,
disable: false
});
},
enable: function(enable) {
this._editable({
readonly: false,
disable: !(enable = enable === undefined ? true : enable)
});
},
destroy: function() {
var that = this;
that.element.add(that._text).add(that._upArrow).add(that._downArrow).add(that._inputWrapper).off(ns);
if (that._form) {
that._form.off("reset", that._resetHandler);
}
Widget.fn.destroy.call(that);
},
min: function(value) {
return this._option("min", value);
},
max: function(value) {
return this._option("max", value);
},
step: function(value) {
return this._option("step", value);
},
value: function(value) {
var that = this, adjusted;
if (value === undefined) {
return that._value;
}
value = that._parse(value);
adjusted = that._adjust(value);
if (value !== adjusted) {
return;
}
that._update(value);
that._old = that._value;
},
focus: function() {
this._focusin();
},
_adjust: function(value) {
var that = this, options = that.options, min = options.min, max = options.max;
if (value === NULL) {
return value;
}
if (min !== NULL && value < min) {
value = min;
} else if (max !== NULL && value > max) {
value = max;
}
return value;
},
_arrows: function() {
var that = this, arrows, options = that.options, spinners = options.spinners, element = that.element;
arrows = element.siblings(".k-icon");
if (!arrows[0]) {
arrows = $(buttonHtml("n", options.upArrowText) + buttonHtml("s", options.downArrowText)).insertAfter(element);
arrows.wrapAll('<span class="k-select"/>');
}
arrows.on(MOUSEUP, function() {
clearTimeout(that._spinning);
arrows.removeClass(SELECTED);
});
if (!spinners) {
arrows.parent().toggle(spinners);
that._inputWrapper.addClass("k-expand-padding");
}
that._upArrow = arrows.eq(0);
that._upArrowEventHandler = new kendo.UserEvents(that._upArrow);
that._downArrow = arrows.eq(1);
that._downArrowEventHandler = new kendo.UserEvents(that._downArrow);
},
_blur: function() {
var that = this;
that._toggleText(true);
that._change(that.element.val());
},
_click: function(e) {
var that = this;
clearTimeout(that._focusing);
that._focusing = setTimeout(function() {
var input = e.target, idx = caret(input)[0], value = input.value.substring(0, idx), format = that._format(that.options.format), group = format[","], groupRegExp = new RegExp("\\" + group, "g"), extractRegExp = new RegExp("([\\d\\" + group + "]+)(\\" + format[POINT] + ")?(\\d+)?"), result = extractRegExp.exec(value), caretPosition = 0;
if (result) {
caretPosition = result[0].replace(groupRegExp, "").length;
if (value.indexOf("(") != -1 && that._value < 0) {
caretPosition++;
}
}
that._focusin();
caret(that.element[0], caretPosition);
});
},
_change: function(value) {
var that = this;
that._update(value);
value = that._value;
if (that._old != value) {
that._old = value;
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_culture: function(culture) {
return culture || getCulture(this.options.culture);
},
_focusin: function() {
var that = this;
that._inputWrapper.addClass(FOCUSED);
that._toggleText(false);
that.element[0].focus();
},
_focusout: function() {
var that = this;
clearTimeout(that._focusing);
that._inputWrapper.removeClass(FOCUSED).removeClass(HOVER);
that._blur();
},
_format: function(format, culture) {
var numberFormat = this._culture(culture).numberFormat;
format = format.toLowerCase();
if (format.indexOf("c") > -1) {
numberFormat = numberFormat.currency;
} else if (format.indexOf("p") > -1) {
numberFormat = numberFormat.percent;
}
return numberFormat;
},
_input: function() {
var that = this, CLASSNAME = "k-formatted-value", element = that.element.addClass(INPUT).show()[0], accessKey = element.accessKey, wrapper = that.wrapper, text;
text = wrapper.find(POINT + CLASSNAME);
if (!text[0]) {
text = $('<input type="text"/>').insertBefore(element).addClass(CLASSNAME);
}
element.type = "text";
text[0].tabIndex = element.tabIndex;
text[0].style.cssText = element.style.cssText;
text.prop("placeholder", that.options.placeholder);
if (accessKey) {
text.attr("accesskey", accessKey);
element.accessKey = "";
}
that._text = text.addClass(element.className);
},
_keydown: function(e) {
var that = this, key = e.keyCode;
if (key == keys.DOWN) {
that._step(-1);
} else if (key == keys.UP) {
that._step(1);
} else if (key == keys.ENTER) {
that._change(that.element.val());
}
if (that._prevent(key, e.shiftKey) && !e.ctrlKey) {
e.preventDefault();
}
},
_paste: function(e) {
var that = this, element = e.target, value = element.value;
setTimeout(function() {
if (that._parse(element.value) === NULL) {
that._update(value);
}
});
},
_prevent: function(key, shiftKey) {
var that = this, element = that.element[0], value = element.value, options = that.options, min = options.min, numberFormat = that._format(options.format), separator = numberFormat[POINT], precision = options.decimals, selection = caret(element), selectionStart = selection[0], selectionEnd = selection[1], textSelected = selectionStart === 0 && selectionEnd === value.length, prevent = true, number;
if (precision === NULL) {
precision = numberFormat.decimals;
}
if (key > 16 && key < 21 || key > 32 && key < 37 || key > 47 && key < 58 || key > 95 && key < 106 || key == keys.INSERT || key == keys.DELETE || key == keys.LEFT || key == keys.RIGHT || key == keys.TAB || key == keys.BACKSPACE || key == keys.ENTER) {
prevent = false;
if (shiftKey) {
number = parseInt(String.fromCharCode(key), 10);
if (!isNaN(number)) {
number = number + "";
value = value.substring(0, selectionStart) + number + value.substring(selectionEnd);
if (element.maxLength === -1 || element.maxLength >= value.length) {
element.value = value;
caret(element, selectionStart + number.length);
}
prevent = true;
}
}
} else if ((decimals[key] === separator || key == 110) && precision > 0 && (value.indexOf(separator) == -1 || textSelected)) {
if (key == 110) {
element.value = value.substring(0, selectionStart) + separator + value.substring(selectionEnd);
caret(element, selectionStart + separator.length);
} else if (!shiftKey) {
prevent = false;
}
} else if ((min === NULL || min < 0) && value.indexOf("-") == -1 && (key == 189 || key == 109 || key == 173) && selectionStart === 0) {
//sign
prevent = false;
}
return prevent;
},
_option: function(option, value) {
var that = this, options = that.options;
if (value === undefined) {
return options[option];
}
value = that._parse(value);
if (!value && option === "step") {
return;
}
options[option] = that._parse(value);
that.element.attr("aria-value" + option, options[option]);
},
_spin: function(step, timeout) {
var that = this;
timeout = timeout || 500;
clearTimeout(that._spinning);
that._spinning = setTimeout(function() {
that._spin(step, 50);
}, timeout);
that._step(step);
},
_step: function(step) {
var that = this, element = that.element, value = that._parse(element.val()) || 0;
if (activeElement() != element[0]) {
that._focusin();
}
value += that.options.step * step;
that._update(that._adjust(value));
that.trigger(SPIN);
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_toggleText: function(toggle) {
var that = this;
that._text.toggle(toggle);
that.element.toggle(!toggle);
},
_parse: function(value, culture) {
return parse(value, this._culture(culture), this.options.format);
},
_update: function(value) {
var that = this, options = that.options, format = options.format, decimals = options.decimals, culture = that._culture(), numberFormat = that._format(format, culture), isNotNull;
if (decimals === NULL) {
decimals = numberFormat.decimals;
}
value = that._parse(value, culture);
isNotNull = value !== NULL;
if (isNotNull) {
value = parseFloat(value.toFixed(decimals));
}
that._value = value = that._adjust(value);
that._placeholder(kendo.toString(value, format, culture));
that.element.val(isNotNull ? value.toString().replace(POINT, numberFormat[POINT]) : "").attr("aria-valuenow", value);
},
_placeholder: function(value) {
this._text.val(value);
if (!placeholderSupported && !value) {
this._text.val(this.options.placeholder);
}
},
_wrapper: function() {
var that = this, element = that.element, DOMElement = element[0], wrapper;
wrapper = element.parents(".k-numerictextbox");
if (!wrapper.is("span.k-numerictextbox")) {
wrapper = element.hide().wrap('<span class="k-numeric-wrap k-state-default" />').parent();
wrapper = wrapper.wrap("<span/>").parent();
}
wrapper[0].style.cssText = DOMElement.style.cssText;
DOMElement.style.width = "";
that.wrapper = wrapper.addClass("k-widget k-numerictextbox").addClass(DOMElement.className).css("display", "");
that._inputWrapper = $(wrapper[0].firstChild);
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
setTimeout(function() {
that.value(element[0].value);
});
};
that._form = form.on("reset", that._resetHandler);
}
}
});
function buttonHtml(className, text) {
return '<span unselectable="on" class="k-link"><span unselectable="on" class="k-icon k-i-arrow-' + className + '" title="' + text + '">' + text + "</span></span>";
}
function caret(element, position) {
var range, isPosition = position !== undefined;
if (element.selectionStart !== undefined) {
if (isPosition) {
element.focus();
element.setSelectionRange(position, position);
} else {
position = [ element.selectionStart, element.selectionEnd ];
}
} else if (document.selection) {
if ($(element).is(":visible")) {
element.focus();
}
range = document.selection.createRange();
if (isPosition) {
range.move("character", position);
range.select();
} else {
var rangeElement = element.createTextRange(), rangeDuplicated = rangeElement.duplicate(), selectionStart, selectionEnd;
rangeElement.moveToBookmark(range.getBookmark());
rangeDuplicated.setEndPoint("EndToStart", rangeElement);
selectionStart = rangeDuplicated.text.length;
selectionEnd = selectionStart + rangeElement.text.length;
position = [ selectionStart, selectionEnd ];
}
}
return position;
}
ui.plugin(NumericTextBox);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, activeElement = kendo._activeElement, touch = kendo.support.touch && kendo.support.mobileOS, mobile = touch || kendo.support.pointers, MOUSEDOWN = "mousedown", CLICK = "click", extend = $.extend, proxy = $.proxy, each = $.each, template = kendo.template, keys = kendo.keys, Widget = ui.Widget, excludedNodesRegExp = /^(ul|a|div)$/i, NS = ".kendoMenu", IMG = "img", OPEN = "open", MENU = "k-menu", LINK = "k-link", LAST = "k-last", CLOSE = "close", TIMER = "timer", FIRST = "k-first", IMAGE = "k-image", SELECT = "select", ZINDEX = "zIndex", ACTIVATE = "activate", DEACTIVATE = "deactivate", MOUSEENTER = kendo.support.pointers ? "MSPointerOver" : "mouseenter", MOUSELEAVE = kendo.support.pointers ? "MSPointerOut" : "mouseleave", KENDOPOPUP = "kendoPopup", DEFAULTSTATE = "k-state-default", HOVERSTATE = "k-state-hover", FOCUSEDSTATE = "k-state-focused", DISABLEDSTATE = "k-state-disabled", groupSelector = ".k-group", allItemsSelector = ":not(.k-list) > .k-item", disabledSelector = ".k-item.k-state-disabled", itemSelector = ".k-item:not(.k-state-disabled)", linkSelector = ".k-item:not(.k-state-disabled) > .k-link", exclusionSelector = ":not(.k-item.k-separator)", nextSelector = exclusionSelector + ":eq(0)", lastSelector = exclusionSelector + ":last", templateSelector = "div:not(.k-animation-container,.k-list-container)", templates = {
content: template("<div class='k-content k-group' tabindex='-1'>#= content(item) #</div>"),
group: template("<ul class='#= groupCssClass(group) #'#= groupAttributes(group) # role='menu' aria-hidden='true'>" + "#= renderItems(data) #" + "</ul>"),
itemWrapper: template("<#= tag(item) # class='#= textClass(item) #'#= textAttributes(item) #>" + "#= image(item) ##= sprite(item) ##= text(item) #" + "#= arrow(data) #" + "</#= tag(item) #>"),
item: template("<li class='#= wrapperCssClass(group, item) #' role='menuitem' #=item.items ? \"aria-haspopup='true'\": \"\"#" + "#=item.enabled === false ? \"aria-disabled='true'\" : ''#>" + "#= itemWrapper(data) #" + "# if (item.items) { #" + "#= subGroup({ items: item.items, menu: menu, group: { expanded: item.expanded } }) #" + "# } else if (item.content || item.contentUrl) { #" + "#= renderContent(data) #" + "# } #" + "</li>"),
image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
arrow: template("<span class='#= arrowClass(item, group) #'></span>"),
sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
empty: template("")
}, rendering = {
wrapperCssClass: function(group, item) {
var result = "k-item", index = item.index;
if (item.enabled === false) {
result += " k-state-disabled";
} else {
result += " k-state-default";
}
if (group.firstLevel && index === 0) {
result += " k-first";
}
if (index == group.length - 1) {
result += " k-last";
}
if (item.cssClass) {
result += " " + item.cssClass;
}
return result;
},
textClass: function() {
return LINK;
},
textAttributes: function(item) {
return item.url ? " href='" + item.url + "'" : "";
},
arrowClass: function(item, group) {
var result = "k-icon";
if (group.horizontal) {
result += " k-i-arrow-s";
} else {
result += " k-i-arrow-e";
}
return result;
},
text: function(item) {
return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
},
tag: function(item) {
return item.url ? "a" : "span";
},
groupAttributes: function(group) {
return group.expanded !== true ? " style='display:none'" : "";
},
groupCssClass: function() {
return "k-group";
},
content: function(item) {
return item.content ? item.content : " ";
}
};
function getEffectDirection(direction, root) {
direction = direction.split(" ")[!root + 0] || direction;
return direction.replace("top", "up").replace("bottom", "down");
}
function parseDirection(direction, root, isRtl) {
direction = direction.split(" ")[!root + 0] || direction;
var output = {
origin: [ "bottom", isRtl ? "right" : "left" ],
position: [ "top", isRtl ? "right" : "left" ]
}, horizontal = /left|right/.test(direction);
if (horizontal) {
output.origin = [ "top", direction ];
output.position[1] = kendo.directions[direction].reverse;
} else {
output.origin[0] = direction;
output.position[0] = kendo.directions[direction].reverse;
}
output.origin = output.origin.join(" ");
output.position = output.position.join(" ");
return output;
}
function contains(parent, child) {
try {
return $.contains(parent, child);
} catch (e) {
return false;
}
}
function updateItemClasses(item) {
item = $(item);
item.addClass("k-item").children(IMG).addClass(IMAGE);
item.children("a").addClass(LINK).children(IMG).addClass(IMAGE);
item.filter(":not([disabled])").addClass(DEFAULTSTATE);
item.filter(".k-separator:empty").append(" ");
item.filter("li[disabled]").addClass(DISABLEDSTATE).removeAttr("disabled").attr("aria-disabled", true);
if (!item.filter("[role]").length) {
item.attr("role", "menuitem");
}
if (!item.children("." + LINK).length) {
item.contents().filter(function() {
return !this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue));
}).wrapAll("<span class='" + LINK + "'/>");
}
updateArrow(item);
updateFirstLast(item);
}
function updateArrow(item) {
item = $(item);
item.find("> .k-link > [class*=k-i-arrow]").remove();
item.filter(":has(.k-group)").children(".k-link:not(:has([class*=k-i-arrow]))").each(function() {
var item = $(this), parent = item.parent().parent();
item.append("<span class='k-icon " + (parent.hasClass(MENU + "-horizontal") ? "k-i-arrow-s" : "k-i-arrow-e") + "'/>");
});
}
function updateFirstLast(item) {
item = $(item);
item.filter(".k-first:not(:first-child)").removeClass(FIRST);
item.filter(".k-last:not(:last-child)").removeClass(LAST);
item.filter(":first-child").addClass(FIRST);
item.filter(":last-child").addClass(LAST);
}
var Menu = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.wrapper = that.element;
options = that.options;
that._initData(options);
that._updateClasses();
that._animations(options);
that.nextItemZIndex = 100;
that._tabindex();
that._focusProxy = proxy(that._focusHandler, that);
element.on("touchstart MSPointerDown", that._focusProxy).on(CLICK + NS, disabledSelector, false).on(CLICK + NS, itemSelector, proxy(that._click, that)).on("keydown" + NS, proxy(that._keydown, that)).on("focus" + NS, proxy(that._focus, that)).on("focus" + NS, ".k-content", proxy(that._focus, that)).on("blur" + NS, proxy(that._removeHoverItem, that)).on(MOUSEENTER + NS, itemSelector, proxy(that._mouseenter, that)).on(MOUSELEAVE + NS, itemSelector, proxy(that._mouseleave, that)).on(MOUSEENTER + NS + " " + MOUSELEAVE + NS + " " + MOUSEDOWN + NS + " " + CLICK + NS, linkSelector, proxy(that._toggleHover, that));
if (options.openOnClick) {
that.clicked = false;
that._documentClickHandler = proxy(that._documentClick, that);
$(document).click(that._documentClickHandler);
}
element.attr("role", "menubar");
if (element[0].id) {
that._ariaId = kendo.format("{0}_mn_active", element[0].id);
}
kendo.notify(that);
},
events: [ OPEN, CLOSE, ACTIVATE, DEACTIVATE, SELECT ],
options: {
name: "Menu",
animation: {
open: {
duration: 200
},
close: {
// if close animation effects are defined, they will be used instead of open.reverse
duration: 100
}
},
orientation: "horizontal",
direction: "default",
openOnClick: false,
closeOnClick: true,
hoverDelay: 100
},
_initData: function(options) {
var that = this;
if (options.dataSource) {
that.element.empty();
that.append(options.dataSource, that.element);
}
},
setOptions: function(options) {
var animation = this.options.animation;
this._animations(options);
options.animation = extend(true, animation, options.animation);
if ("dataSource" in options) {
this._initData(options);
}
Widget.fn.setOptions.call(this, options);
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.element.off(NS);
if (that._documentClickHandler) {
$(document).unbind("click", that._documentClickHandler);
}
},
enable: function(element, enable) {
this._toggleDisabled(element, enable !== false);
return this;
},
disable: function(element) {
this._toggleDisabled(element, false);
return this;
},
append: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find("> .k-group, > .k-animation-container > .k-group") : null);
each(inserted.items, function() {
inserted.group.append(this);
updateArrow(this);
});
updateArrow(referenceItem);
updateFirstLast(inserted.group.find(".k-first, .k-last").add(inserted.items));
return this;
},
insertBefore: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.parent());
each(inserted.items, function() {
referenceItem.before(this);
updateArrow(this);
updateFirstLast(this);
});
updateFirstLast(referenceItem);
return this;
},
insertAfter: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.parent());
each(inserted.items, function() {
referenceItem.after(this);
updateArrow(this);
updateFirstLast(this);
});
updateFirstLast(referenceItem);
return this;
},
_insert: function(item, referenceItem, parent) {
var that = this, items, groups;
if (!referenceItem || !referenceItem.length) {
parent = that.element;
}
var plain = $.isPlainObject(item), groupData = {
firstLevel: parent.hasClass(MENU),
horizontal: parent.hasClass(MENU + "-horizontal"),
expanded: true,
length: parent.children().length
};
if (referenceItem && !parent.length) {
parent = $(Menu.renderGroup({
group: groupData
})).appendTo(referenceItem);
}
if (plain || $.isArray(item)) {
// is JSON
items = $.map(plain ? [ item ] : item, function(value, idx) {
if (typeof value === "string") {
return $(value);
} else {
return $(Menu.renderItem({
group: groupData,
item: extend(value, {
index: idx
})
}));
}
});
} else {
items = $(item);
groups = items.find("> ul").addClass("k-group").attr("role", "menu");
items = items.filter("li");
items.add(groups.find("> li")).each(function() {
updateItemClasses(this);
});
}
return {
items: items,
group: parent
};
},
remove: function(element) {
element = this.element.find(element);
var that = this, parent = element.parentsUntil(that.element, allItemsSelector), group = element.parent("ul");
element.remove();
if (group && !group.children(allItemsSelector).length) {
var container = group.parent(".k-animation-container");
if (container.length) {
container.remove();
} else {
group.remove();
}
}
if (parent.length) {
parent = parent.eq(0);
updateArrow(parent);
updateFirstLast(parent);
}
return that;
},
open: function(element) {
var that = this, options = that.options, horizontal = options.orientation == "horizontal", direction = options.direction, isRtl = kendo.support.isRtl(that.wrapper);
element = that.element.find(element);
if (/^(top|bottom|default)$/.test(direction)) {
if (isRtl) {
direction = horizontal ? (direction + " left").replace("default", "bottom") : "left";
} else {
direction = horizontal ? (direction + " right").replace("default", "bottom") : "right";
}
}
element.siblings().find(">.k-popup:visible,>.k-animation-container>.k-popup:visible").each(function() {
var popup = $(this).data("kendoPopup");
if (popup) {
popup.close();
}
});
element.each(function() {
var li = $(this);
clearTimeout(li.data(TIMER));
li.data(TIMER, setTimeout(function() {
var ul = li.find(".k-group:first:hidden"), popup;
if (ul[0] && that.trigger(OPEN, {
item: li[0]
}) === false) {
li.data(ZINDEX, li.css(ZINDEX));
li.css(ZINDEX, that.nextItemZIndex++);
popup = ul.data(KENDOPOPUP);
var root = li.parent().hasClass(MENU), parentHorizontal = root && horizontal, directions = parseDirection(direction, root, isRtl), effects = options.animation.open.effects, openEffects = effects !== undefined ? effects : "slideIn:" + getEffectDirection(direction, root);
if (!popup) {
popup = ul.kendoPopup({
activate: function() {
that.trigger(ACTIVATE, {
item: this.wrapper.parent()
});
},
deactivate: function() {
that.trigger(DEACTIVATE, {
item: this.wrapper.parent()
});
},
origin: directions.origin,
position: directions.position,
collision: options.popupCollision !== undefined ? options.popupCollision : parentHorizontal ? "fit" : "fit flip",
anchor: li,
appendTo: li,
animation: {
open: extend(true, {
effects: openEffects
}, options.animation.open),
close: options.animation.close
},
close: function(e) {
var li = e.sender.wrapper.parent();
if (!that.trigger(CLOSE, {
item: li[0]
})) {
li.css(ZINDEX, li.data(ZINDEX));
li.removeData(ZINDEX);
} else {
e.preventDefault();
}
}
}).data(KENDOPOPUP);
} else {
popup = ul.data(KENDOPOPUP);
popup.options.origin = directions.origin;
popup.options.position = directions.position;
popup.options.animation.open.effects = openEffects;
}
ul.removeAttr("aria-hidden");
popup.open();
}
}, that.options.hoverDelay));
});
return that;
},
close: function(items) {
var that = this, element = that.element;
items = element.find(items);
if (!items.length) {
items = element.find(">.k-item");
}
items.each(function() {
var li = $(this);
clearTimeout(li.data(TIMER));
li.data(TIMER, setTimeout(function() {
var popup = li.find(".k-group:first:visible").data(KENDOPOPUP);
if (popup) {
popup.close();
popup.element.attr("aria-hidden", true);
}
}, that.options.hoverDelay));
});
return that;
},
_toggleDisabled: function(items, enable) {
this.element.find(items).each(function() {
$(this).toggleClass(DEFAULTSTATE, enable).toggleClass(DISABLEDSTATE, !enable).attr("aria-disabled", !enable);
});
},
_toggleHover: function(e) {
var target = $(kendo.eventTarget(e) || e.target).closest(allItemsSelector), isEnter = e.type == MOUSEENTER || MOUSEDOWN.indexOf(e.type) !== -1;
if (!target.parents("li." + DISABLEDSTATE).length) {
target.toggleClass(HOVERSTATE, isEnter || e.type == "mousedown" || e.type == "click");
}
this._removeHoverItem();
},
_removeHoverItem: function() {
var oldHoverItem = this._hoverItem();
if (oldHoverItem && oldHoverItem.hasClass(FOCUSEDSTATE)) {
oldHoverItem.removeClass(FOCUSEDSTATE);
this._oldHoverItem = null;
}
},
_updateClasses: function() {
var element = this.element, items;
element.addClass("k-widget k-reset k-header " + MENU).addClass(MENU + "-" + this.options.orientation);
element.find("li > ul").addClass("k-group").attr("role", "menu").attr("aria-hidden", element.is(":visible")).end().find("li > div").addClass("k-content").attr("tabindex", "-1");
// Capture the focus before the Menu
items = element.find("> li,.k-group > li");
items.each(function() {
updateItemClasses(this);
});
},
_mouseenter: function(e) {
var that = this, element = $(e.currentTarget), hasChildren = element.children(".k-animation-container").length || element.children(groupSelector).length;
if (e.delegateTarget != element.parents(".k-menu")[0]) {
return;
}
if (!that.options.openOnClick || that.clicked) {
if (!contains(e.currentTarget, e.relatedTarget) && hasChildren) {
that.open(element);
}
}
if (that.options.openOnClick && that.clicked || mobile) {
element.siblings().each(proxy(function(_, sibling) {
that.close(sibling);
}, that));
}
},
_mouseleave: function(e) {
var that = this, element = $(e.currentTarget), hasChildren = element.children(".k-animation-container").length || element.children(groupSelector).length;
if (element.parentsUntil(".k-animation-container", ".k-list-container,.k-calendar-container")[0]) {
e.stopImmediatePropagation();
return;
}
if (!that.options.openOnClick && !touch && !(kendo.support.pointers && e.originalEvent.pointerType == 2) && !contains(e.currentTarget, e.relatedTarget) && hasChildren) {
that.close(element);
}
},
_click: function(e) {
var that = this, openHandle, options = that.options, target = $(kendo.eventTarget(e)), nodeName = target[0] ? target[0].nodeName.toUpperCase() : "", formNode = nodeName == "INPUT" || nodeName == "SELECT" || nodeName == "BUTTON", link = target.closest("." + LINK), element = target.closest(allItemsSelector), href = link.attr("href"), childGroup, childGroupVisible, isLink = !!href && href.charAt(href.length - 1) != "#";
if (element.children(templateSelector)[0]) {
return;
}
if (element.hasClass(DISABLEDSTATE)) {
e.preventDefault();
return;
}
if (!e.handled && that.trigger(SELECT, {
item: element[0]
}) && !formNode) {
// We shouldn't stop propagation and shoudn't prevent form elements.
e.preventDefault();
}
e.handled = true;
childGroup = element.children(groupSelector + ",.k-animation-container");
childGroupVisible = childGroup.is(":visible");
if (options.closeOnClick && !isLink && (!childGroup.length || options.openOnClick && childGroupVisible)) {
element.removeClass(HOVERSTATE).css("height");
// Force refresh for Chrome
that._oldHoverItem = that._findRootParent(element);
that.close(link.parentsUntil(that.element, allItemsSelector));
that.clicked = false;
if ("MSPointerUp".indexOf(e.type) != -1) {
e.preventDefault();
}
return;
}
if ((!element.parent().hasClass(MENU) || !options.openOnClick) && mobile) {
return;
}
if (!isLink && !formNode) {
e.preventDefault();
}
that.clicked = true;
openHandle = childGroup.is(":visible") ? CLOSE : OPEN;
if (!options.closeOnClick && openHandle == CLOSE) {
return;
}
that[openHandle](element);
},
_documentClick: function(e) {
if (contains(this.element[0], e.target)) {
return;
}
this.clicked = false;
},
_focus: function(e) {
var that = this, target = e.target, hoverItem = that._hoverItem(), active = activeElement();
if (target != that.wrapper[0] && !$(target).is(":focusable")) {
e.stopPropagation();
$(target).closest(".k-content").closest(".k-group").closest(".k-item").addClass(FOCUSEDSTATE);
that.wrapper.focus();
return;
}
if (active === e.currentTarget) {
if (hoverItem.length) {
that._moveHover([], hoverItem);
} else if (!that._oldHoverItem) {
that._moveHover([], that.wrapper.children().first());
}
}
},
_keydown: function(e) {
var that = this, key = e.keyCode, hoverItem = that._oldHoverItem, target, belongsToVertical, hasChildren, isRtl = kendo.support.isRtl(that.wrapper);
if (e.target != e.currentTarget && key != keys.ESC) {
return;
}
if (!hoverItem) {
hoverItem = that._oldHoverItem = that._hoverItem();
}
belongsToVertical = that._itemBelongsToVertival(hoverItem);
hasChildren = that._itemHasChildren(hoverItem);
if (key == keys.RIGHT) {
target = that[isRtl ? "_itemLeft" : "_itemRight"](hoverItem, belongsToVertical, hasChildren);
} else if (key == keys.LEFT) {
target = that[isRtl ? "_itemRight" : "_itemLeft"](hoverItem, belongsToVertical, hasChildren);
} else if (key == keys.DOWN) {
target = that._itemDown(hoverItem, belongsToVertical, hasChildren);
} else if (key == keys.UP) {
target = that._itemUp(hoverItem, belongsToVertical, hasChildren);
} else if (key == keys.ESC) {
target = that._itemEsc(hoverItem, belongsToVertical);
} else if (key == keys.ENTER || key == keys.SPACEBAR) {
target = hoverItem.children(".k-link");
if (target.length > 0) {
that._click({
target: target[0],
preventDefault: function() {}
});
that._moveHover(hoverItem, that._findRootParent(hoverItem));
}
} else if (key == keys.TAB) {
target = that._findRootParent(hoverItem);
that.close(target);
that._moveHover(hoverItem, target);
return;
}
if (target && target[0]) {
e.preventDefault();
e.stopPropagation();
}
},
_hoverItem: function() {
return this.wrapper.find(".k-item.k-state-hover,.k-item.k-state-focused").filter(":visible");
},
_itemBelongsToVertival: function(item) {
var menuIsVertical = this.wrapper.hasClass("k-menu-vertical");
if (!item.length) {
return menuIsVertical;
}
return item.parent().hasClass("k-group") || menuIsVertical;
},
_itemHasChildren: function(item) {
if (!item.length) {
return false;
}
return item.children("ul.k-group, div.k-animation-container").length > 0;
},
_moveHover: function(item, nextItem) {
var that = this, id = that._ariaId;
if (item.length && nextItem.length) {
item.removeClass(FOCUSEDSTATE);
if (item[0].id === id) {
item.removeAttr("id");
}
}
if (nextItem.length) {
if (nextItem[0].id) {
id = nextItem[0].id;
}
nextItem.addClass(FOCUSEDSTATE);
that._oldHoverItem = nextItem;
if (id) {
that.element.removeAttr("aria-activedescendant");
nextItem.attr("id", id);
that.element.attr("aria-activedescendant", id);
}
}
},
_findRootParent: function(item) {
if (item.parent().hasClass("k-menu")) {
return item;
} else {
return item.parentsUntil(".k-menu", "li.k-item").last();
}
},
_isRootItem: function(item) {
return item.parent().hasClass("k-menu");
},
_itemRight: function(item, belongsToVertical, hasChildren) {
var that = this, nextItem, parentItem;
if (!belongsToVertical) {
nextItem = item.nextAll(nextSelector);
if (!nextItem.length) {
nextItem = item.prevAll(lastSelector);
}
} else if (hasChildren) {
that.open(item);
nextItem = item.find(".k-group").children().first();
} else if (that.options.orientation == "horizontal") {
parentItem = that._findRootParent(item);
that.close(parentItem);
nextItem = parentItem.nextAll(nextSelector);
}
if (nextItem && !nextItem.length) {
nextItem = that.wrapper.children(".k-item").first();
} else if (!nextItem) {
nextItem = [];
}
that._moveHover(item, nextItem);
return nextItem;
},
_itemLeft: function(item, belongsToVertical) {
var that = this, nextItem;
if (!belongsToVertical) {
nextItem = item.prevAll(nextSelector);
if (!nextItem.length) {
nextItem = item.nextAll(lastSelector);
}
} else {
nextItem = item.parent().closest(".k-item");
that.close(nextItem);
if (that._isRootItem(nextItem) && that.options.orientation == "horizontal") {
nextItem = nextItem.prevAll(nextSelector);
}
}
if (!nextItem.length) {
nextItem = that.wrapper.children(".k-item").last();
}
that._moveHover(item, nextItem);
return nextItem;
},
_itemDown: function(item, belongsToVertical, hasChildren) {
var that = this, nextItem;
if (!belongsToVertical) {
if (!hasChildren || item.hasClass(DISABLEDSTATE)) {
return;
} else {
that.open(item);
nextItem = item.find(".k-group").children().first();
}
} else {
nextItem = item.nextAll(nextSelector);
}
if (!nextItem.length && item.length) {
nextItem = item.parent().children().first();
} else if (!item.length) {
nextItem = that.wrapper.children(".k-item").first();
}
that._moveHover(item, nextItem);
return nextItem;
},
_itemUp: function(item, belongsToVertical) {
var that = this, nextItem;
if (!belongsToVertical) {
return;
} else {
nextItem = item.prevAll(nextSelector);
}
if (!nextItem.length && item.length) {
nextItem = item.parent().children().last();
} else if (!item.length) {
nextItem = that.wrapper.children(".k-item").last();
}
that._moveHover(item, nextItem);
return nextItem;
},
_itemEsc: function(item, belongsToVertical) {
var that = this, nextItem;
if (!belongsToVertical) {
return item;
} else {
nextItem = item.parent().closest(".k-item");
that.close(nextItem);
that._moveHover(item, nextItem);
}
return nextItem;
},
_focusHandler: function(e) {
var that = this, item = $(kendo.eventTarget(e)).closest(allItemsSelector);
setTimeout(function() {
that._moveHover([], item);
if (item.children(".k-content")[0]) {
item.parent().closest(".k-item").removeClass(FOCUSEDSTATE);
}
}, 200);
},
_animations: function(options) {
if (options && "animation" in options && !options.animation) {
options.animation = {
open: {
effects: {}
},
close: {
hide: true,
effects: {}
}
};
}
}
});
// client-side rendering
extend(Menu, {
renderItem: function(options) {
options = extend({
menu: {},
group: {}
}, options);
var empty = templates.empty, item = options.item;
return templates.item(extend(options, {
image: item.imageUrl ? templates.image : empty,
sprite: item.spriteCssClass ? templates.sprite : empty,
itemWrapper: templates.itemWrapper,
renderContent: Menu.renderContent,
arrow: item.items || item.content ? templates.arrow : empty,
subGroup: Menu.renderGroup
}, rendering));
},
renderGroup: function(options) {
return templates.group(extend({
renderItems: function(options) {
var html = "", i = 0, items = options.items, len = items ? items.length : 0, group = extend({
length: len
}, options.group);
for (;i < len; i++) {
html += Menu.renderItem(extend(options, {
group: group,
item: extend({
index: i
}, items[i])
}));
}
return html;
}
}, options, rendering));
},
renderContent: function(options) {
return templates.content(extend(options, rendering));
}
});
kendo.ui.plugin(Menu);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, Widget = ui.Widget, extend = $.extend, isFunction = $.isFunction, isPlainObject = $.isPlainObject, inArray = $.inArray, nameSpecialCharRegExp = /("|'|\[|\]|\$|\.|\:|\+)/g, ERRORTEMPLATE = '<div class="k-widget k-tooltip k-tooltip-validation" style="margin:0.5em"><span class="k-icon k-warning"> </span>' + '#=message#<div class="k-callout k-callout-n"></div></div>', CHANGE = "change";
var specialRules = [ "url", "email", "number", "date", "boolean" ];
function fieldType(field) {
field = field != null ? field : "";
return field.type || $.type(field) || "string";
}
function convertToValueBinding(container) {
container.find(":input:not(:button, [" + kendo.attr("role") + "=upload], [" + kendo.attr("skip") + "], [type=file]), select").each(function() {
var bindAttr = kendo.attr("bind"), binding = this.getAttribute(bindAttr) || "", bindingName = this.type === "checkbox" || this.type === "radio" ? "checked:" : "value:", fieldName = this.name;
if (binding.indexOf(bindingName) === -1 && fieldName) {
binding += (binding.length ? "," : "") + bindingName + fieldName;
$(this).attr(bindAttr, binding);
}
});
}
function createAttributes(options) {
var field = (options.model.fields || options.model)[options.field], type = fieldType(field), validation = field ? field.validation : {}, ruleName, DATATYPE = kendo.attr("type"), BINDING = kendo.attr("bind"), rule, attr = {
name: options.field
};
for (ruleName in validation) {
rule = validation[ruleName];
if (inArray(ruleName, specialRules) >= 0) {
attr[DATATYPE] = ruleName;
} else if (!isFunction(rule)) {
attr[ruleName] = isPlainObject(rule) ? rule.value || ruleName : rule;
}
attr[kendo.attr(ruleName + "-msg")] = rule.message;
}
if (inArray(type, specialRules) >= 0) {
attr[DATATYPE] = type;
}
attr[BINDING] = (type === "boolean" ? "checked:" : "value:") + options.field;
return attr;
}
function convertItems(items) {
var idx, length, item, value, text, result;
if (items && items.length) {
result = [];
for (idx = 0, length = items.length; idx < length; idx++) {
item = items[idx];
text = item.text || item.value || item;
value = item.value == null ? item.text || item : item.value;
result[idx] = {
text: text,
value: value
};
}
}
return result;
}
var editors = {
number: function(container, options) {
var attr = createAttributes(options);
$('<input type="text"/>').attr(attr).appendTo(container).kendoNumericTextBox({
format: options.format
});
$("<span " + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
},
date: function(container, options) {
var attr = createAttributes(options), format = options.format;
if (format) {
format = kendo._extractFormat(format);
}
attr[kendo.attr("format")] = format;
$('<input type="text"/>').attr(attr).appendTo(container).kendoDatePicker({
format: options.format
});
$("<span " + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
},
string: function(container, options) {
var attr = createAttributes(options);
$('<input type="text" class="k-input k-textbox"/>').attr(attr).appendTo(container);
},
"boolean": function(container, options) {
var attr = createAttributes(options);
$('<input type="checkbox" />').attr(attr).appendTo(container);
},
values: function(container, options) {
var attr = createAttributes(options);
$("<select " + kendo.attr("text-field") + '="text"' + kendo.attr("value-field") + '="value"' + kendo.attr("source") + "='" + kendo.stringify(convertItems(options.values)).replace(/\'/g, "'") + "'" + kendo.attr("role") + '="dropdownlist"/>').attr(attr).appendTo(container);
$("<span " + kendo.attr("for") + '="' + options.field + '" class="k-invalid-msg"/>').hide().appendTo(container);
}
};
function addValidationRules(modelField, rules) {
var validation = modelField ? modelField.validation || {} : {}, rule;
for (rule in validation) {
if (isFunction(validation[rule])) {
rules[rule] = validation[rule];
}
}
}
var Editable = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._validateProxy = $.proxy(that._validate, that);
that.refresh();
},
events: [ CHANGE ],
options: {
name: "Editable",
editors: editors,
clearContainer: true,
errorTemplate: ERRORTEMPLATE
},
editor: function(field, modelField) {
var that = this, editors = that.options.editors, isObject = isPlainObject(field), fieldName = isObject ? field.field : field, model = that.options.model || {}, isValuesEditor = isObject && field.values, type = isValuesEditor ? "values" : fieldType(modelField), isCustomEditor = isObject && field.editor, editor = isCustomEditor ? field.editor : editors[type], container = that.element.find("[" + kendo.attr("container-for") + "=" + fieldName.replace(nameSpecialCharRegExp, "\\$1") + "]");
editor = editor ? editor : editors["string"];
if (isCustomEditor && typeof field.editor === "string") {
editor = function(container) {
container.append(field.editor);
};
}
container = container.length ? container : that.element;
editor(container, extend(true, {}, isObject ? field : {
field: fieldName
}, {
model: model
}));
},
_validate: function(e) {
var that = this, isBoolean = typeof e.value === "boolean", input, preventChangeTrigger = that._validationEventInProgress, values = {};
values[e.field] = e.value;
input = $(":input[" + kendo.attr("bind") + '="' + (isBoolean ? "checked:" : "value:") + e.field.replace(nameSpecialCharRegExp, "\\$1") + '"]', that.element);
try {
that._validationEventInProgress = true;
if (!that.validatable.validateInput(input) || !preventChangeTrigger && that.trigger(CHANGE, {
values: values
})) {
e.preventDefault();
}
} finally {
that._validationEventInProgress = false;
}
},
end: function() {
return this.validatable.validate();
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.options.model.unbind("set", that._validateProxy);
kendo.unbind(that.element);
kendo.destroy(that.element);
that.element.removeData("kendoValidator");
},
refresh: function() {
var that = this, idx, length, fields = that.options.fields || [], container = that.options.clearContainer ? that.element.empty() : that.element, model = that.options.model || {}, rules = {}, field, isObject, fieldName, modelField, modelFields;
if (!$.isArray(fields)) {
fields = [ fields ];
}
for (idx = 0, length = fields.length; idx < length; idx++) {
field = fields[idx];
isObject = isPlainObject(field);
fieldName = isObject ? field.field : field;
modelField = (model.fields || model)[fieldName];
addValidationRules(modelField, rules);
that.editor(field, modelField);
}
if (!length) {
modelFields = model.fields || model;
for (fieldName in modelFields) {
addValidationRules(modelFields[fieldName], rules);
}
}
convertToValueBinding(container);
kendo.bind(container, that.options.model);
that.options.model.bind("set", that._validateProxy);
that.validatable = container.kendoValidator({
validateOnBlur: false,
errorTemplate: that.options.errorTemplate || undefined,
rules: rules
}).data("kendoValidator");
container.find(":focusable:first").focus();
}
});
ui.plugin(Editable);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, proxy = $.proxy, POPUP = "kendoPopup", INIT = "init", NS = ".kendoFilterMenu", EQ = "Is equal to", NEQ = "Is not equal to", roles = {
number: "numerictextbox",
date: "datepicker"
}, isFunction = $.isFunction, Widget = ui.Widget;
var booleanTemplate = "<div>" + '<div class="k-filter-help-text">#=messages.info#</div>' + "<label>" + '<input type="radio" data-#=ns#bind="checked: filters[0].value" value="true" name="filters[0].value"/>' + "#=messages.isTrue#" + "</label>" + "<label>" + '<input type="radio" data-#=ns#bind="checked: filters[0].value" value="false" name="filters[0].value"/>' + "#=messages.isFalse#" + "</label>" + "<div>" + '<button type="submit" class="k-button">#=messages.filter#</button>' + '<button type="reset" class="k-button">#=messages.clear#</button>' + "</div>" + "</div>";
var defaultTemplate = "<div>" + '<div class="k-filter-help-text">#=messages.info#</div>' + '<select data-#=ns#bind="value: filters[0].operator" data-#=ns#role="dropdownlist">' + "#for(var op in operators){#" + '<option value="#=op#">#=operators[op]#</option>' + "#}#" + "</select>" + "#if(values){#" + '<select data-#=ns#bind="value:filters[0].value" data-#=ns#text-field="text" data-#=ns#value-field="value" data-#=ns#source=\'#=kendo.stringify(values).replace(/\'/g,"&\\#39;")#\' data-#=ns#role="dropdownlist" data-#=ns#option-label="#=messages.selectValue#">' + "</select>" + "#}else{#" + '<input data-#=ns#bind="value:filters[0].value" class="k-textbox" type="text" #=role ? "data-" + ns + "role=\'" + role + "\'" : ""# />' + "#}#" + "#if(extra){#" + '<select class="k-filter-and" data-#=ns#bind="value: logic" data-#=ns#role="dropdownlist">' + '<option value="and">#=messages.and#</option>' + '<option value="or">#=messages.or#</option>' + "</select>" + '<select data-#=ns#bind="value: filters[1].operator" data-#=ns#role="dropdownlist">' + "#for(var op in operators){#" + '<option value="#=op#">#=operators[op]#</option>' + "#}#" + "</select>" + "#if(values){#" + '<select data-#=ns#bind="value:filters[1].value" data-#=ns#text-field="text" data-#=ns#value-field="value" data-#=ns#source=\'#=kendo.stringify(values).replace(/\'/g,"&\\#39;")#\' data-#=ns#role="dropdownlist" data-#=ns#option-label="#=messages.selectValue#">' + "</select>" + "#}else{#" + '<input data-#=ns#bind="value: filters[1].value" class="k-textbox" type="text" #=role ? "data-" + ns + "role=\'" + role + "\'" : ""#/>' + "#}#" + "#}#" + "<div>" + '<button type="submit" class="k-button">#=messages.filter#</button>' + '<button type="reset" class="k-button">#=messages.clear#</button>' + "</div>" + "</div>";
function removeFiltersForField(expression, field) {
if (expression.filters) {
expression.filters = $.grep(expression.filters, function(filter) {
removeFiltersForField(filter, field);
if (filter.filters) {
return filter.filters.length;
} else {
return filter.field != field;
}
});
}
}
function convertItems(items) {
var idx, length, item, value, text, result;
if (items && items.length) {
result = [];
for (idx = 0, length = items.length; idx < length; idx++) {
item = items[idx];
text = item.text || item.value || item;
value = item.value == null ? item.text || item : item.value;
result[idx] = {
text: text,
value: value
};
}
}
return result;
}
var FilterMenu = Widget.extend({
init: function(element, options) {
var that = this, type = "string", link, field;
Widget.fn.init.call(that, element, options);
that.operators = options.operators || {};
element = that.element;
options = that.options;
if (!options.appendToElement) {
link = element.addClass("k-filterable").find(".k-grid-filter");
if (!link[0]) {
link = element.prepend('<a class="k-grid-filter" href="#"><span class="k-icon k-filter"/></a>').find(".k-grid-filter");
}
link.attr("tabindex", -1).on("click" + NS, proxy(that._click, that));
}
that.link = link || $();
that.dataSource = options.dataSource;
that.field = options.field || element.attr(kendo.attr("field"));
that.model = that.dataSource.reader.model;
that._parse = function(value) {
return value + "";
};
if (that.model && that.model.fields) {
field = that.model.fields[that.field];
if (field) {
type = field.type || "string";
if (field.parse) {
that._parse = proxy(field.parse, field);
}
}
}
if (options.values) {
type = "enums";
}
that.type = type;
if (options.appendToElement) {
// force creation if used in column menu
that._init();
}
},
_init: function() {
var that = this, options = that.options, operators = that.operators || {}, initial, ui = options.ui, setUI = isFunction(ui), role, type = that.type;
that._refreshHandler = proxy(that.refresh, that);
that.dataSource.bind("change", that._refreshHandler);
operators = operators[type] || options.operators[type];
for (initial in operators) {
// get the first operator
break;
}
that._defaultFilter = function() {
return {
field: that.field,
operator: initial || "eq",
value: ""
};
};
if (!setUI) {
role = ui || roles[type];
}
that.form = $('<form class="k-filter-menu"/>').html(kendo.template(type === "boolean" ? booleanTemplate : defaultTemplate)({
field: that.field,
format: options.format,
ns: kendo.ns,
messages: options.messages,
extra: options.extra,
operators: operators,
type: type,
role: role,
values: convertItems(options.values)
})).on("keydown" + NS, proxy(that._keydown, that)).on("submit" + NS, proxy(that._submit, that)).on("reset" + NS, proxy(that._reset, that));
if (!options.appendToElement) {
that.popup = that.form[POPUP]({
anchor: that.link,
open: proxy(that._open, that),
activate: proxy(that._activate, that),
close: that.options.closeCallback
}).data(POPUP);
} else {
that.element.append(that.form);
that.popup = that.element.closest(".k-popup").data(POPUP);
}
if (setUI) {
that.form.find(".k-textbox").removeClass("k-textbox").each(function() {
ui($(this));
});
}
that.form.find("[" + kendo.attr("role") + "=numerictextbox]").removeClass("k-textbox").end().find("[" + kendo.attr("role") + "=datetimepicker]").removeClass("k-textbox").end().find("[" + kendo.attr("role") + "=timepicker]").removeClass("k-textbox").end().find("[" + kendo.attr("role") + "=datepicker]").removeClass("k-textbox");
that.refresh();
that.trigger(INIT, {
field: that.field,
container: that.form
});
},
refresh: function() {
var that = this, expression = that.dataSource.filter() || {
filters: [],
logic: "and"
};
that.filterModel = kendo.observable({
logic: "and",
filters: [ that._defaultFilter(), that._defaultFilter() ]
});
//NOTE: binding the form element directly causes weird error in IE when grid is bound through MVVM and column is sorted
kendo.bind(that.form.children().first(), that.filterModel);
if (that._bind(expression)) {
that.link.addClass("k-state-active");
} else {
that.link.removeClass("k-state-active");
}
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
if (that.form) {
kendo.unbind(that.form);
kendo.destroy(that.form);
that.form.unbind(NS);
that.popup.destroy();
}
that.link.unbind(NS);
that.dataSource.unbind("change", that._refreshHandler);
},
_bind: function(expression) {
var that = this, filters = expression.filters, idx, length, found = false, current = 0, filterModel = that.filterModel, currentFilter, filter;
for (idx = 0, length = filters.length; idx < length; idx++) {
filter = filters[idx];
if (filter.field == that.field) {
filterModel.set("logic", expression.logic);
currentFilter = filterModel.filters[current];
if (!currentFilter) {
filterModel.filters.push({
field: that.field
});
currentFilter = filterModel.filters[current];
}
currentFilter.set("value", that._parse(filter.value));
currentFilter.set("operator", filter.operator);
current++;
found = true;
} else if (filter.filters) {
found = found || that._bind(filter);
}
}
return found;
},
_merge: function(expression) {
var that = this, logic = expression.logic || "and", filters = expression.filters, filter, result = that.dataSource.filter() || {
filters: [],
logic: "and"
}, idx, length;
removeFiltersForField(result, that.field);
filters = $.grep(filters, function(filter) {
return filter.value !== "";
});
for (idx = 0, length = filters.length; idx < length; idx++) {
filter = filters[idx];
filter.value = that._parse(filter.value);
}
if (filters.length) {
if (result.filters.length) {
expression.filters = filters;
if (result.logic !== "and") {
result.filters = [ {
logic: result.logic,
filters: result.filters
} ];
result.logic = "and";
}
if (filters.length > 1) {
result.filters.push(expression);
} else {
result.filters.push(filters[0]);
}
} else {
result.filters = filters;
result.logic = logic;
}
}
return result;
},
filter: function(expression) {
expression = this._merge(expression);
if (expression.filters.length) {
this.dataSource.filter(expression);
}
},
clear: function() {
var that = this, expression = that.dataSource.filter() || {
filters: []
};
expression.filters = $.grep(expression.filters, function(filter) {
if (filter.filters) {
filter.filters = $.grep(filter.filters, function(expr) {
return expr.field != that.field;
});
return filter.filters.length;
}
return filter.field != that.field;
});
if (!expression.filters.length) {
expression = null;
}
that.dataSource.filter(expression);
},
_submit: function(e) {
var that = this;
e.preventDefault();
that.filter(that.filterModel.toJSON());
that.popup.close();
},
_reset: function() {
this.clear();
this.popup.close();
},
_click: function(e) {
e.preventDefault();
e.stopPropagation();
if (!this.popup) {
this._init();
}
this.popup.toggle();
},
_open: function() {
var popup;
$(".k-filter-menu").not(this.form).each(function() {
popup = $(this).data(POPUP);
if (popup) {
popup.close();
}
});
},
_activate: function() {
this.form.find(":focusable:first").focus();
},
_keydown: function(e) {
if (e.keyCode == kendo.keys.ESC) {
this.popup.close();
}
},
events: [ INIT ],
options: {
name: "FilterMenu",
extra: true,
appendToElement: false,
type: "string",
operators: {
string: {
eq: EQ,
neq: NEQ,
startswith: "Starts with",
contains: "Contains",
doesnotcontain: "Does not contain",
endswith: "Ends with"
},
number: {
eq: EQ,
neq: NEQ,
gte: "Is greater than or equal to",
gt: "Is greater than",
lte: "Is less than or equal to",
lt: "Is less than"
},
date: {
eq: EQ,
neq: NEQ,
gte: "Is after or equal to",
gt: "Is after",
lte: "Is before or equal to",
lt: "Is before"
},
enums: {
eq: EQ,
neq: NEQ
}
},
messages: {
info: "Show items with value that:",
isTrue: "is true",
isFalse: "is false",
filter: "Filter",
clear: "Clear",
and: "And",
or: "Or",
selectValue: "-Select value-"
}
}
});
ui.plugin(FilterMenu);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, keys = kendo.keys, extend = $.extend, each = $.each, template = kendo.template, Widget = ui.Widget, excludedNodesRegExp = /^(ul|a|div)$/i, NS = ".kendoPanelBar", IMG = "img", HREF = "href", LAST = "k-last", LINK = "k-link", LINKSELECTOR = "." + LINK, ERROR = "error", ITEM = ".k-item", GROUP = ".k-group", VISIBLEGROUP = GROUP + ":visible", IMAGE = "k-image", FIRST = "k-first", EXPAND = "expand", SELECT = "select", CONTENT = "k-content", ACTIVATE = "activate", COLLAPSE = "collapse", MOUSEENTER = "mouseenter", MOUSELEAVE = "mouseleave", CONTENTLOAD = "contentLoad", ACTIVECLASS = "k-state-active", GROUPS = "> .k-panel", CONTENTS = "> .k-content", FOCUSEDCLASS = "k-state-focused", DISABLEDCLASS = "k-state-disabled", SELECTEDCLASS = "k-state-selected", SELECTEDSELECTOR = "." + SELECTEDCLASS, HIGHLIGHTEDCLASS = "k-state-highlighted", ACTIVEITEMSELECTOR = ITEM + ":not(.k-state-disabled)", clickableItems = ACTIVEITEMSELECTOR + " > .k-link", disabledItems = ITEM + ".k-state-disabled > .k-link", selectableItems = "> li > " + SELECTEDSELECTOR + ", .k-panel > li > " + SELECTEDSELECTOR, defaultState = "k-state-default", ARIA_DISABLED = "aria-disabled", ARIA_EXPANDED = "aria-expanded", ARIA_HIDDEN = "aria-hidden", ARIA_SELECTED = "aria-selected", VISIBLE = ":visible", EMPTY = ":empty", SINGLE = "single", templates = {
content: template("<div role='region' class='k-content'#= contentAttributes(data) #>#= content(item) #</div>"),
group: template("<ul role='group' aria-hidden='true' class='#= groupCssClass(group) #'#= groupAttributes(group) #>" + "#= renderItems(data) #" + "</ul>"),
itemWrapper: template("<#= tag(item) # class='#= textClass(item, group) #' #= contentUrl(item) ##= textAttributes(item) #>" + "#= image(item) ##= sprite(item) ##= text(item) #" + "#= arrow(data) #" + "</#= tag(item) #>"),
item: template("<li role='menuitem' #=aria(item)#class='#= wrapperCssClass(group, item) #'>" + "#= itemWrapper(data) #" + "# if (item.items) { #" + "#= subGroup({ items: item.items, panelBar: panelBar, group: { expanded: item.expanded } }) #" + "# } else if (item.content || item.contentUrl) { #" + "#= renderContent(data) #" + "# } #" + "</li>"),
image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
arrow: template("<span class='#= arrowClass(item) #'></span>"),
sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
empty: template("")
}, rendering = {
aria: function(item) {
var attr = "";
if (item.items || item.content || item.contentUrl) {
attr += ARIA_EXPANDED + "='" + (item.expanded ? "true" : "false") + "' ";
}
if (item.enabled === false) {
attr += ARIA_DISABLED + "='true'";
}
return attr;
},
wrapperCssClass: function(group, item) {
var result = "k-item", index = item.index;
if (item.enabled === false) {
result += " " + DISABLEDCLASS;
} else if (item.expanded === true) {
result += " " + ACTIVECLASS;
} else {
result += " k-state-default";
}
if (index === 0) {
result += " k-first";
}
if (index == group.length - 1) {
result += " k-last";
}
if (item.cssClass) {
result += " " + item.cssClass;
}
return result;
},
textClass: function(item, group) {
var result = LINK;
if (group.firstLevel) {
result += " k-header";
}
return result;
},
textAttributes: function(item) {
return item.url ? " href='" + item.url + "'" : "";
},
arrowClass: function(item) {
var result = "k-icon";
result += item.expanded ? " k-i-arrow-n k-panelbar-collapse" : " k-i-arrow-s k-panelbar-expand";
return result;
},
text: function(item) {
return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
},
tag: function(item) {
return item.url ? "a" : "span";
},
groupAttributes: function(group) {
return group.expanded !== true ? " style='display:none'" : "";
},
groupCssClass: function() {
return "k-group k-panel";
},
contentAttributes: function(content) {
return content.item.expanded !== true ? " style='display:none'" : "";
},
content: function(item) {
return item.content ? item.content : item.contentUrl ? "" : " ";
},
contentUrl: function(item) {
return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
}
};
function updateArrow(items) {
items = $(items);
items.children(LINKSELECTOR).children(".k-icon").remove();
items.filter(":has(.k-panel),:has(.k-content)").children(".k-link:not(:has([class*=k-i-arrow]))").each(function() {
var item = $(this), parent = item.parent();
item.append("<span class='k-icon " + (parent.hasClass(ACTIVECLASS) ? "k-i-arrow-n k-panelbar-collapse" : "k-i-arrow-s k-panelbar-expand") + "'/>");
});
}
function updateFirstLast(items) {
items = $(items);
items.filter(".k-first:not(:first-child)").removeClass(FIRST);
items.filter(".k-last:not(:last-child)").removeClass(LAST);
items.filter(":first-child").addClass(FIRST);
items.filter(":last-child").addClass(LAST);
}
var PanelBar = Widget.extend({
init: function(element, options) {
var that = this, content;
Widget.fn.init.call(that, element, options);
element = that.wrapper = that.element.addClass("k-widget k-reset k-header k-panelbar");
options = that.options;
if (element[0].id) {
that._itemId = element[0].id + "_pb_active";
}
that._tabindex();
that._initData(options);
that._updateClasses();
that._animations(options);
element.on("click" + NS, clickableItems, function(e) {
if (that._click($(e.currentTarget))) {
e.preventDefault();
}
}).on(MOUSEENTER + NS + " " + MOUSELEAVE + NS, clickableItems, that._toggleHover).on("click" + NS, disabledItems, false).on("keydown" + NS, $.proxy(that._keydown, that)).on("focus" + NS, function() {
var item = that.select();
that._current(item[0] ? item : that._first());
}).on("blur" + NS, function() {
that._current(null);
}).attr("role", "menu");
/*if (options.contentUrls) {
element.find("> .k-item")
.each(function(index, item) {
var span = $(item).find(LINKSELECTOR);
span.replaceWith('<a class="k-link k-header" href="' + options.contentUrls[index] + '">' + span.html() + '</a>');
});
}*/
content = element.find("li." + ACTIVECLASS + " > ." + CONTENT);
if (content[0]) {
that.expand(content.parent(), false);
}
kendo.notify(that);
},
events: [ EXPAND, COLLAPSE, SELECT, ACTIVATE, ERROR, CONTENTLOAD ],
options: {
name: "PanelBar",
animation: {
expand: {
effects: "expand:vertical",
duration: 200
},
collapse: {
// if collapse animation effects are defined, they will be used instead of expand.reverse
duration: 200
}
},
expandMode: "multiple"
},
destroy: function() {
Widget.fn.destroy.call(this);
this.element.off(NS);
kendo.destroy(this.element);
},
_initData: function(options) {
var that = this;
if (options.dataSource) {
that.element.empty();
that.append(options.dataSource, that.element);
}
},
setOptions: function(options) {
var animation = this.options.animation;
this._animations(options);
options.animation = extend(true, animation, options.animation);
if ("dataSource" in options) {
this._initData(options);
}
Widget.fn.setOptions.call(this, options);
},
expand: function(element, useAnimation) {
var that = this, animBackup = {};
useAnimation = useAnimation !== false;
element = this.element.find(element);
element.each(function(index, item) {
item = $(item);
var groups = item.find(GROUPS).add(item.find(CONTENTS));
if (!item.hasClass(DISABLEDCLASS) && groups.length > 0) {
if (that.options.expandMode == SINGLE && that._collapseAllExpanded(item)) {
return that;
}
element.find("." + HIGHLIGHTEDCLASS).removeClass(HIGHLIGHTEDCLASS);
item.addClass(HIGHLIGHTEDCLASS);
if (!useAnimation) {
animBackup = that.options.animation;
that.options.animation = {
expand: {
effects: {}
},
collapse: {
hide: true,
effects: {}
}
};
}
if (!that._triggerEvent(EXPAND, item)) {
that._toggleItem(item, false);
}
if (!useAnimation) {
that.options.animation = animBackup;
}
}
});
return that;
},
collapse: function(element, useAnimation) {
var that = this, animBackup = {};
useAnimation = useAnimation !== false;
element = that.element.find(element);
element.each(function(index, item) {
item = $(item);
var groups = item.find(GROUPS).add(item.find(CONTENTS));
if (!item.hasClass(DISABLEDCLASS) && groups.is(VISIBLE)) {
item.removeClass(HIGHLIGHTEDCLASS);
if (!useAnimation) {
animBackup = that.options.animation;
that.options.animation = {
expand: {
effects: {}
},
collapse: {
hide: true,
effects: {}
}
};
}
if (!that._triggerEvent(COLLAPSE, item)) {
that._toggleItem(item, true);
}
if (!useAnimation) {
that.options.animation = animBackup;
}
}
});
return that;
},
_toggleDisabled: function(element, enable) {
element = this.element.find(element);
element.toggleClass(defaultState, enable).toggleClass(DISABLEDCLASS, !enable).attr(ARIA_DISABLED, !enable);
},
select: function(element) {
var that = this;
if (element === undefined) {
return that.element.find(selectableItems).parent();
}
that.element.find(element).each(function() {
var item = $(this), link = item.children(LINKSELECTOR);
if (item.hasClass(DISABLEDCLASS)) {
return that;
}
that._updateSelected(link);
});
return that;
},
enable: function(element, state) {
this._toggleDisabled(element, state !== false);
return this;
},
disable: function(element) {
this._toggleDisabled(element, false);
return this;
},
append: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.length ? referenceItem.find(GROUPS) : null);
each(inserted.items, function() {
inserted.group.append(this);
updateFirstLast(this);
});
updateArrow(referenceItem);
updateFirstLast(inserted.group.find(".k-first, .k-last"));
inserted.group.height("auto");
return this;
},
insertBefore: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.parent());
each(inserted.items, function() {
referenceItem.before(this);
updateFirstLast(this);
});
updateFirstLast(referenceItem);
inserted.group.height("auto");
return this;
},
insertAfter: function(item, referenceItem) {
referenceItem = this.element.find(referenceItem);
var inserted = this._insert(item, referenceItem, referenceItem.parent());
each(inserted.items, function() {
referenceItem.after(this);
updateFirstLast(this);
});
updateFirstLast(referenceItem);
inserted.group.height("auto");
return this;
},
remove: function(element) {
element = this.element.find(element);
var that = this, parent = element.parentsUntil(that.element, ITEM), group = element.parent("ul");
element.remove();
if (group && !group.hasClass("k-panelbar") && !group.children(ITEM).length) {
group.remove();
}
if (parent.length) {
parent = parent.eq(0);
updateArrow(parent);
updateFirstLast(parent);
}
return that;
},
reload: function(element) {
var that = this;
element = that.element.find(element);
element.each(function() {
var item = $(this);
that._ajaxRequest(item, item.children("." + CONTENT), !item.is(VISIBLE));
});
},
_first: function() {
return this.element.children(ACTIVEITEMSELECTOR).first();
},
_last: function() {
var item = this.element.children(ACTIVEITEMSELECTOR).last(), group = item.children(VISIBLEGROUP);
if (group[0]) {
return group.children(ACTIVEITEMSELECTOR).last();
}
return item;
},
_current: function(candidate) {
var that = this, focused = that._focused, id = that._itemId;
if (candidate === undefined) {
return focused;
}
that.element.removeAttr("aria-activedescendant");
if (focused) {
if (focused[0].id === id) {
focused.removeAttr("id");
}
focused.children(LINKSELECTOR).removeClass(FOCUSEDCLASS);
}
if (candidate) {
id = candidate[0].id || id;
candidate.attr("id", id).children(LINKSELECTOR).addClass(FOCUSEDCLASS);
that.element.attr("aria-activedescendant", id);
}
that._focused = candidate;
},
_keydown: function(e) {
var that = this, key = e.keyCode, current = that._current();
if (e.target != e.currentTarget) {
return;
}
if (key == keys.DOWN || key == keys.RIGHT) {
that._current(that._nextItem(current));
e.preventDefault();
} else if (key == keys.UP || key == keys.LEFT) {
that._current(that._prevItem(current));
e.preventDefault();
} else if (key == keys.ENTER || key == keys.SPACEBAR) {
that._click(current.children(LINKSELECTOR));
e.preventDefault();
} else if (key == keys.HOME) {
that._current(that._first());
e.preventDefault();
} else if (key == keys.END) {
that._current(that._last());
e.preventDefault();
}
},
_nextItem: function(item) {
if (!item) {
return this._first();
}
var group = item.children(VISIBLEGROUP), next = item.next();
if (group[0]) {
next = group.children("." + FIRST);
}
if (!next[0]) {
next = item.parent(VISIBLEGROUP).parent(ITEM).next();
}
if (!next[0] || !next.is(":visible")) {
next = this._first();
}
if (next.hasClass(DISABLEDCLASS)) {
next = this._nextItem(next);
}
return next;
},
_prevItem: function(item) {
if (!item) {
return this._last();
}
var prev = item.prev(), result;
if (!prev[0]) {
prev = item.parent(VISIBLEGROUP).parent(ITEM);
if (!prev[0]) {
prev = this._last();
}
} else {
result = prev;
while (result[0]) {
result = result.children(VISIBLEGROUP).children("." + LAST);
if (result[0]) {
prev = result;
}
}
}
if (prev.hasClass(DISABLEDCLASS)) {
prev = this._prevItem(prev);
}
return prev;
},
_insert: function(item, referenceItem, parent) {
var that = this, items, plain = $.isPlainObject(item), isReferenceItem = referenceItem && referenceItem[0], groupData;
if (!isReferenceItem) {
parent = that.element;
}
groupData = {
firstLevel: parent.hasClass("k-panelbar"),
expanded: parent.parent().hasClass(ACTIVECLASS),
length: parent.children().length
};
if (isReferenceItem && !parent.length) {
parent = $(PanelBar.renderGroup({
group: groupData
})).appendTo(referenceItem);
}
if (plain || $.isArray(item)) {
// is JSON
items = $.map(plain ? [ item ] : item, function(value, idx) {
if (typeof value === "string") {
return $(value);
} else {
return $(PanelBar.renderItem({
group: groupData,
item: extend(value, {
index: idx
})
}));
}
});
if (isReferenceItem) {
referenceItem.attr(ARIA_EXPANDED, false);
}
} else {
items = $(item);
that._updateItemsClasses(items);
}
return {
items: items,
group: parent
};
},
_toggleHover: function(e) {
var target = $(e.currentTarget);
if (!target.parents("li." + DISABLEDCLASS).length) {
target.toggleClass("k-state-hover", e.type == MOUSEENTER);
}
},
_updateClasses: function() {
var that = this, panels, items;
panels = that.element.find("li > ul").not(function() {
return $(this).parentsUntil(".k-panelbar", "div").length;
}).addClass("k-group k-panel").attr("role", "group");
panels.parent().attr(ARIA_EXPANDED, false).not("." + ACTIVECLASS).children("ul").attr(ARIA_HIDDEN, true).hide();
items = that.element.add(panels).children();
that._updateItemsClasses(items);
updateArrow(items);
updateFirstLast(items);
},
_updateItemsClasses: function(items) {
var length = items.length, idx = 0;
for (;idx < length; idx++) {
this._updateItemClasses(items[idx], idx);
}
},
_updateItemClasses: function(item, index) {
var selected = this._selected, contentUrls = this.options.contentUrls, url = contentUrls && contentUrls[index], root = this.element[0], wrapElement, link;
item = $(item).addClass("k-item").attr("role", "menuitem");
item.children(IMG).addClass(IMAGE);
link = item.children("a").addClass(LINK);
if (link[0]) {
link.attr("href", url);
//url can be undefined
link.children(IMG).addClass(IMAGE);
}
item.filter(":not([disabled]):not([class*=k-state])").addClass("k-state-default");
item.filter("li[disabled]").addClass("k-state-disabled").attr(ARIA_DISABLED, true).removeAttr("disabled");
item.children("div").addClass(CONTENT).attr("role", "region").attr(ARIA_HIDDEN, true).hide().parent().attr(ARIA_EXPANDED, false);
link = item.children(SELECTEDSELECTOR);
if (link[0]) {
if (selected) {
selected.removeAttr(ARIA_SELECTED).children(SELECTEDSELECTOR).removeClass(SELECTEDCLASS);
}
link.addClass(SELECTEDCLASS);
this._selected = item.attr(ARIA_SELECTED, true);
}
if (!item.children(LINKSELECTOR)[0]) {
wrapElement = "<span class='" + LINK + "'/>";
if (contentUrls && contentUrls[index] && item[0].parentNode == root) {
wrapElement = '<a class="k-link k-header" href="' + contentUrls[index] + '"/>';
}
item.contents().filter(function() {
return !this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !$.trim(this.nodeValue));
}).wrapAll(wrapElement);
}
if (item.parent(".k-panelbar")[0]) {
item.children(LINKSELECTOR).addClass("k-header");
}
},
_click: function(target) {
var that = this, element = that.element, prevent, contents, href, isAnchor;
if (target.parents("li." + DISABLEDCLASS).length) {
return;
}
if (target.closest(".k-widget")[0] != element[0]) {
return;
}
var link = target.closest(LINKSELECTOR), item = link.closest(ITEM);
that._updateSelected(link);
contents = item.find(GROUPS).add(item.find(CONTENTS));
href = link.attr(HREF);
isAnchor = href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1);
prevent = !!(isAnchor || contents.length);
if (contents.data("animating")) {
return prevent;
}
if (that._triggerEvent(SELECT, item)) {
prevent = true;
}
if (prevent === false) {
return;
}
if (that.options.expandMode == SINGLE) {
if (that._collapseAllExpanded(item)) {
return prevent;
}
}
if (contents.length) {
var visibility = contents.is(VISIBLE);
if (!that._triggerEvent(!visibility ? EXPAND : COLLAPSE, item)) {
prevent = that._toggleItem(item, visibility);
}
}
return prevent;
},
_toggleItem: function(element, isVisible) {
var that = this, childGroup = element.find(GROUPS), prevent, content;
if (childGroup.length) {
this._toggleGroup(childGroup, isVisible);
prevent = true;
} else {
content = element.children("." + CONTENT);
if (content.length) {
prevent = true;
if (!content.is(EMPTY)) {
that._toggleGroup(content, isVisible);
} else {
that._ajaxRequest(element, content, isVisible);
}
}
}
return prevent;
},
_toggleGroup: function(element, visibility) {
var that = this, animationSettings = that.options.animation, animation = animationSettings.expand, collapse = extend({}, animationSettings.collapse), hasCollapseAnimation = collapse && "effects" in collapse;
if (element.is(VISIBLE) != visibility) {
return;
}
element.parent().attr(ARIA_EXPANDED, !visibility).attr(ARIA_HIDDEN, visibility).toggleClass(defaultState, visibility).toggleClass(ACTIVECLASS, !visibility).find("> .k-link > .k-icon").toggleClass("k-i-arrow-n", !visibility).toggleClass("k-panelbar-collapse", !visibility).toggleClass("k-i-arrow-s", visibility).toggleClass("k-panelbar-expand", visibility);
if (visibility) {
animation = extend(hasCollapseAnimation ? collapse : extend({
reverse: true
}, animation), {
hide: true
});
} else {
animation = extend({
complete: function(element) {
that._triggerEvent(ACTIVATE, element.closest(ITEM));
}
}, animation);
}
element.kendoStop(true, true).kendoAnimate(animation);
},
_collapseAllExpanded: function(item) {
var that = this, children, stopExpand = false;
if (item.children(LINKSELECTOR).hasClass("k-header")) {
var groups = item.find(GROUPS).add(item.find(CONTENTS));
if (groups.is(VISIBLE)) {
stopExpand = true;
}
if (!(groups.is(VISIBLE) || groups.length === 0)) {
children = $(that.element).children();
children.find(GROUPS).add(children.find(CONTENTS)).filter(function() {
return $(this).is(VISIBLE);
}).each(function(index, content) {
content = $(content);
stopExpand = that._triggerEvent(COLLAPSE, content.closest(ITEM));
if (!stopExpand) {
that._toggleGroup(content, true);
}
});
}
return stopExpand;
}
},
_ajaxRequest: function(element, contentElement, isVisible) {
var that = this, statusIcon = element.find(".k-panelbar-collapse, .k-panelbar-expand"), link = element.find(LINKSELECTOR), loadingIconTimeout = setTimeout(function() {
statusIcon.addClass("k-loading");
}, 100), data = {}, url = link.attr(HREF);
$.ajax({
type: "GET",
cache: false,
url: url,
dataType: "html",
data: data,
error: function(xhr, status) {
statusIcon.removeClass("k-loading");
if (that.trigger(ERROR, {
xhr: xhr,
status: status
})) {
this.complete();
}
},
complete: function() {
clearTimeout(loadingIconTimeout);
statusIcon.removeClass("k-loading");
},
success: function(data) {
try {
contentElement.html(data);
} catch (e) {
var console = window.console;
if (console && console.error) {
console.error(e.name + ": " + e.message + " in " + url);
}
this.error(this.xhr, "error");
}
that._toggleGroup(contentElement, isVisible);
that.trigger(CONTENTLOAD, {
item: element[0],
contentElement: contentElement[0]
});
}
});
},
_triggerEvent: function(eventName, element) {
var that = this;
return that.trigger(eventName, {
item: element[0]
});
},
_updateSelected: function(link) {
var that = this, element = that.element, item = link.parent(ITEM), selected = that._selected;
if (selected) {
selected.removeAttr(ARIA_SELECTED);
}
that._selected = item.attr(ARIA_SELECTED, true);
element.find(selectableItems).removeClass(SELECTEDCLASS);
element.find("> .k-state-highlighted, .k-panel > .k-state-highlighted").removeClass(HIGHLIGHTEDCLASS);
link.addClass(SELECTEDCLASS);
link.parentsUntil(element, ITEM).filter(":has(.k-header)").addClass(HIGHLIGHTEDCLASS);
that._current(item);
},
_animations: function(options) {
if (options && "animation" in options && !options.animation) {
options.animation = {
expand: {
effects: {}
},
collapse: {
hide: true,
effects: {}
}
};
}
}
});
// client-side rendering
extend(PanelBar, {
renderItem: function(options) {
options = extend({
panelBar: {},
group: {}
}, options);
var empty = templates.empty, item = options.item;
return templates.item(extend(options, {
image: item.imageUrl ? templates.image : empty,
sprite: item.spriteCssClass ? templates.sprite : empty,
itemWrapper: templates.itemWrapper,
renderContent: PanelBar.renderContent,
arrow: item.items || item.content || item.contentUrl ? templates.arrow : empty,
subGroup: PanelBar.renderGroup
}, rendering));
},
renderGroup: function(options) {
return templates.group(extend({
renderItems: function(options) {
var html = "", i = 0, items = options.items, len = items ? items.length : 0, group = extend({
length: len
}, options.group);
for (;i < len; i++) {
html += PanelBar.renderItem(extend(options, {
group: group,
item: extend({
index: i
}, items[i])
}));
}
return html;
}
}, options, rendering));
},
renderContent: function(options) {
return templates.content(extend(options, rendering));
}
});
kendo.ui.plugin(PanelBar);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, keys = kendo.keys, map = $.map, each = $.each, trim = $.trim, extend = $.extend, template = kendo.template, Widget = ui.Widget, excludedNodesRegExp = /^(a|div)$/i, NS = ".kendoTabStrip", IMG = "img", HREF = "href", PREV = "prev", LINK = "k-link", LAST = "k-last", CLICK = "click", ERROR = "error", EMPTY = ":empty", IMAGE = "k-image", FIRST = "k-first", SELECT = "select", ACTIVATE = "activate", CONTENT = "k-content", CONTENTURL = "contentUrl", MOUSEENTER = "mouseenter", MOUSELEAVE = "mouseleave", CONTENTLOAD = "contentLoad", DISABLEDSTATE = "k-state-disabled", DEFAULTSTATE = "k-state-default", ACTIVESTATE = "k-state-active", FOCUSEDSTATE = "k-state-focused", HOVERSTATE = "k-state-hover", TABONTOP = "k-tab-on-top", NAVIGATABLEITEMS = ".k-item:not(." + DISABLEDSTATE + ")", HOVERABLEITEMS = ".k-tabstrip-items > " + NAVIGATABLEITEMS + ":not(." + ACTIVESTATE + ")", templates = {
content: template("<div class='k-content'#= contentAttributes(data) # role='tabpanel'>#= content(item) #</div>"),
itemWrapper: template("<#= tag(item) # class='k-link'#= contentUrl(item) ##= textAttributes(item) #>" + "#= image(item) ##= sprite(item) ##= text(item) #" + "</#= tag(item) #>"),
item: template("<li class='#= wrapperCssClass(group, item) #' role='tab' #=item.active ? \"aria-selected='true'\" : ''#>" + "#= itemWrapper(data) #" + "</li>"),
image: template("<img class='k-image' alt='' src='#= imageUrl #' />"),
sprite: template("<span class='k-sprite #= spriteCssClass #'></span>"),
empty: template("")
}, rendering = {
wrapperCssClass: function(group, item) {
var result = "k-item", index = item.index;
if (item.enabled === false) {
result += " k-state-disabled";
} else {
result += " k-state-default";
}
if (index === 0) {
result += " k-first";
}
if (index == group.length - 1) {
result += " k-last";
}
return result;
},
textAttributes: function(item) {
return item.url ? " href='" + item.url + "'" : "";
},
text: function(item) {
return item.encoded === false ? item.text : kendo.htmlEncode(item.text);
},
tag: function(item) {
return item.url ? "a" : "span";
},
contentAttributes: function(content) {
return content.active !== true ? " style='display:none' aria-hidden='true' aria-expanded='false'" : "";
},
content: function(item) {
return item.content ? item.content : item.contentUrl ? "" : " ";
},
contentUrl: function(item) {
return item.contentUrl ? kendo.attr("content-url") + '="' + item.contentUrl + '"' : "";
}
};
function updateTabClasses(tabs) {
tabs.children(IMG).addClass(IMAGE);
tabs.children("a").addClass(LINK).children(IMG).addClass(IMAGE);
tabs.filter(":not([disabled]):not([class*=k-state-disabled])").addClass(DEFAULTSTATE);
tabs.filter("li[disabled]").addClass(DISABLEDSTATE).removeAttr("disabled");
tabs.filter(":not([class*=k-state])").children("a").filter(":focus").parent().addClass(ACTIVESTATE + " " + TABONTOP);
tabs.attr("role", "tab");
tabs.filter("." + ACTIVESTATE).attr("aria-selected", true);
tabs.each(function() {
var item = $(this);
if (!item.children("." + LINK).length) {
item.contents().filter(function() {
return !this.nodeName.match(excludedNodesRegExp) && !(this.nodeType == 3 && !trim(this.nodeValue));
}).wrapAll("<a class='" + LINK + "'/>");
}
});
}
function updateFirstLast(tabGroup) {
var tabs = tabGroup.children(".k-item");
tabs.filter(".k-first:not(:first-child)").removeClass(FIRST);
tabs.filter(".k-last:not(:last-child)").removeClass(LAST);
tabs.filter(":first-child").addClass(FIRST);
tabs.filter(":last-child").addClass(LAST);
}
var TabStrip = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._animations(that.options);
if (that.element.is("ul")) {
that.wrapper = that.element.wrapAll("<div />").parent();
} else {
that.wrapper = that.element;
}
options = that.options;
that._isRtl = kendo.support.isRtl(that.wrapper);
that._tabindex();
that._updateClasses();
that._dataSource();
if (options.dataSource) {
that.dataSource.fetch();
}
if (that.options.contentUrls) {
that.wrapper.find(".k-tabstrip-items > .k-item").each(function(index, item) {
$(item).find(">." + LINK).data(CONTENTURL, that.options.contentUrls[index]);
});
}
that.wrapper.on(MOUSEENTER + NS + " " + MOUSELEAVE + NS, HOVERABLEITEMS, that._toggleHover).on("keydown" + NS, $.proxy(that._keydown, that)).on("focus" + NS, $.proxy(that._active, that)).on("blur" + NS, function() {
that._current(null);
});
that.wrapper.children(".k-tabstrip-items").on(CLICK + NS, ".k-state-disabled .k-link", false).on(CLICK + NS, " > " + NAVIGATABLEITEMS, function(e) {
if (that._click($(e.currentTarget))) {
e.preventDefault();
}
});
var selectedItems = that.tabGroup.children("li." + ACTIVESTATE), content = that.contentHolder(selectedItems.index());
if (content.length > 0 && content[0].childNodes.length === 0) {
that.activateTab(selectedItems.eq(0));
}
that.element.attr("role", "tablist");
if (that.element[0].id) {
that._ariaId = that.element[0].id + "_ts_active";
}
kendo.notify(that);
},
_active: function() {
var item = this.tabGroup.children().filter("." + ACTIVESTATE);
item = item[0] ? item : this._endItem("first");
if (item[0]) {
this._current(item);
}
},
_endItem: function(action) {
return this.tabGroup.children(NAVIGATABLEITEMS)[action]();
},
_item: function(item, action) {
var endItem;
if (action === PREV) {
endItem = "last";
} else {
endItem = "first";
}
if (!item) {
return this._endItem(endItem);
}
item = item[action]();
if (!item[0]) {
item = this._endItem(endItem);
}
if (item.hasClass(DISABLEDSTATE)) {
item = this._item(item, action);
}
return item;
},
_current: function(candidate) {
var that = this, focused = that._focused, id = that._ariaId;
if (candidate === undefined) {
return focused;
}
if (focused) {
if (focused[0].id === id) {
focused.removeAttr("id");
}
focused.removeClass(FOCUSEDSTATE);
}
if (candidate) {
if (!candidate.hasClass(ACTIVESTATE)) {
candidate.addClass(FOCUSEDSTATE);
}
that.element.removeAttr("aria-activedescendant");
id = candidate[0].id || id;
if (id) {
candidate.attr("id", id);
that.element.attr("aria-activedescendant", id);
}
}
that._focused = candidate;
},
_keydown: function(e) {
var that = this, key = e.keyCode, current = that._current(), rtl = that._isRtl, action;
if (e.target != e.currentTarget) {
return;
}
if (key == keys.DOWN || key == keys.RIGHT) {
action = rtl ? PREV : "next";
} else if (key == keys.UP || key == keys.LEFT) {
action = rtl ? "next" : PREV;
} else if (key == keys.ENTER || key == keys.SPACEBAR) {
that._click(current);
e.preventDefault();
} else if (key == keys.HOME) {
that._click(that._endItem("first"));
e.preventDefault();
return;
} else if (key == keys.END) {
that._click(that._endItem("last"));
e.preventDefault();
return;
}
if (action) {
that._click(that._item(current, action));
e.preventDefault();
}
},
_dataSource: function() {
var that = this;
if (that.dataSource && that._refreshHandler) {
that.dataSource.unbind("change", that._refreshHandler);
} else {
that._refreshHandler = $.proxy(that.refresh, that);
}
that.dataSource = kendo.data.DataSource.create(that.options.dataSource).bind("change", that._refreshHandler);
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
dataSource.fetch();
},
_animations: function(options) {
if (options && "animation" in options && !options.animation) {
options.animation = {
open: {
effects: {}
},
close: {
effects: {}
}
};
}
},
refresh: function(e) {
var that = this, options = that.options, text = kendo.getter(options.dataTextField), content = kendo.getter(options.dataContentField), contentUrl = kendo.getter(options.dataContentUrlField), image = kendo.getter(options.dataImageUrlField), url = kendo.getter(options.dataUrlField), sprite = kendo.getter(options.dataSpriteCssClass), idx, tabs = [], tab, action, view = that.dataSource.view(), length;
e = e || {};
action = e.action;
if (action) {
view = e.items;
}
for (idx = 0, length = view.length; idx < length; idx++) {
tab = {
text: text(view[idx])
};
if (options.dataContentField) {
tab.content = content(view[idx]);
}
if (options.dataContentUrlField) {
tab.contentUrl = contentUrl(view[idx]);
}
if (options.dataUrlField) {
tab.url = url(view[idx]);
}
if (options.dataImageUrlField) {
tab.imageUrl = image(view[idx]);
}
if (options.dataSpriteCssClass) {
tab.spriteCssClass = sprite(view[idx]);
}
tabs[idx] = tab;
}
if (e.action == "add") {
if (e.index < that.tabGroup.children().length) {
that.insertBefore(tabs, that.tabGroup.children().eq(e.index));
} else {
that.append(tabs);
}
} else if (e.action == "remove") {
for (idx = 0; idx < view.length; idx++) {
that.remove(e.index);
}
} else if (e.action == "itemchange") {
idx = that.dataSource.view().indexOf(view[0]);
if (e.field === options.dataTextField) {
that.tabGroup.children().eq(idx).find(".k-link").text(view[0].get(e.field));
}
} else {
that.trigger("dataBinding");
that.remove("li");
that.append(tabs);
that.trigger("dataBound");
}
},
value: function(value) {
var that = this;
if (value !== undefined) {
if (value != that.value()) {
that.tabGroup.children().each(function() {
if ($.trim($(this).text()) == value) {
that.select(this);
}
});
}
} else {
return that.select().text();
}
},
items: function() {
return this.tabGroup[0].children;
},
setOptions: function(options) {
var animation = this.options.animation;
this._animations(options);
options.animation = extend(true, animation, options.animation);
Widget.fn.setOptions.call(this, options);
},
events: [ SELECT, ACTIVATE, ERROR, CONTENTLOAD, "change", "dataBinding", "dataBound" ],
options: {
name: "TabStrip",
dataTextField: "",
dataContentField: "",
dataImageUrlField: "",
dataUrlField: "",
dataSpriteCssClass: "",
dataContentUrlField: "",
animation: {
open: {
effects: "expand:vertical fadeIn",
duration: 200
},
close: {
// if close animation effects are defined, they will be used instead of open.reverse
duration: 200
}
},
collapsible: false
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
if (that._refreshHandler) {
that.dataSource.unbind("change", that._refreshHandler);
}
that.wrapper.off(NS);
kendo.destroy(that.wrapper);
},
select: function(element) {
var that = this;
if (arguments.length === 0) {
return that.tabGroup.children("li." + ACTIVESTATE);
}
if (!isNaN(element)) {
element = that.tabGroup.children().get(element);
}
element = that.tabGroup.find(element);
$(element).each(function(index, item) {
item = $(item);
if (!item.hasClass(ACTIVESTATE) && !that.trigger(SELECT, {
item: item[0],
contentElement: that.contentHolder(item.index())[0]
})) {
that.activateTab(item);
}
});
return that;
},
enable: function(element, state) {
this._toggleDisabled(element, state !== false);
return this;
},
disable: function(element) {
this._toggleDisabled(element, false);
return this;
},
reload: function(element) {
element = this.tabGroup.find(element);
var that = this;
element.each(function() {
var item = $(this), contentUrl = item.find("." + LINK).data(CONTENTURL), content = that.contentHolder(item.index());
if (contentUrl) {
that.ajaxRequest(item, content, null, contentUrl);
}
});
return that;
},
append: function(tab) {
var that = this, inserted = that._create(tab);
each(inserted.tabs, function(idx) {
that.tabGroup.append(this);
that.wrapper.append(inserted.contents[idx]);
});
updateFirstLast(that.tabGroup);
that._updateContentElements();
return that;
},
insertBefore: function(tab, referenceTab) {
var that = this, inserted = that._create(tab), referenceContent = $(that.contentElement(referenceTab.index()));
each(inserted.tabs, function(idx) {
referenceTab.before(this);
referenceContent.before(inserted.contents[idx]);
});
updateFirstLast(that.tabGroup);
that._updateContentElements();
return that;
},
insertAfter: function(tab, referenceTab) {
var that = this, inserted = that._create(tab), referenceContent = $(that.contentElement(referenceTab.index()));
each(inserted.tabs, function(idx) {
referenceTab.after(this);
referenceContent.after(inserted.contents[idx]);
});
updateFirstLast(that.tabGroup);
that._updateContentElements();
return that;
},
remove: function(elements) {
var that = this, type = typeof elements, contents = $();
if (type === "string") {
elements = that.tabGroup.find(elements);
} else if (type === "number") {
elements = that.tabGroup.children().eq(elements);
}
elements.each(function() {
contents.push(that.contentElement($(this).index()));
});
elements.remove();
contents.remove();
that._updateContentElements();
return that;
},
_create: function(tab) {
var plain = $.isPlainObject(tab), that = this, tabs, contents;
if (plain || $.isArray(tab)) {
tab = $.isArray(tab) ? tab : [ tab ];
tabs = map(tab, function(value, idx) {
return $(TabStrip.renderItem({
group: that.tabGroup,
item: extend(value, {
index: idx
})
}));
});
contents = map(tab, function(value, idx) {
if (value.content || value.contentUrl) {
return $(TabStrip.renderContent({
item: extend(value, {
index: idx
})
}));
}
});
} else {
tabs = $(tab);
contents = $("<div class='" + CONTENT + "'/>");
updateTabClasses(tabs);
}
return {
tabs: tabs,
contents: contents
};
},
_toggleDisabled: function(element, enable) {
element = this.tabGroup.find(element);
element.each(function() {
$(this).toggleClass(DEFAULTSTATE, enable).toggleClass(DISABLEDSTATE, !enable);
});
},
_updateClasses: function() {
var that = this, tabs, activeItem, activeTab;
that.wrapper.addClass("k-widget k-header k-tabstrip");
that.tabGroup = that.wrapper.children("ul").addClass("k-tabstrip-items k-reset");
if (!that.tabGroup[0]) {
that.tabGroup = $("<ul class='k-tabstrip-items k-reset'/>").appendTo(that.wrapper);
}
tabs = that.tabGroup.find("li").addClass("k-item");
if (tabs.length) {
activeItem = tabs.filter("." + ACTIVESTATE).index();
activeTab = activeItem >= 0 ? activeItem : undefined;
that.tabGroup.contents().filter(function() {
return this.nodeType == 3 && !trim(this.nodeValue);
}).remove();
}
if (activeItem >= 0) {
tabs.eq(activeItem).addClass(TABONTOP);
}
that.contentElements = that.wrapper.children("div");
that.contentElements.addClass(CONTENT).eq(activeTab).addClass(ACTIVESTATE).css({
display: "block"
});
if (tabs.length) {
updateTabClasses(tabs);
updateFirstLast(that.tabGroup);
that._updateContentElements();
}
},
_updateContentElements: function() {
var that = this, contentUrls = that.options.contentUrls || [], tabStripID = that.element.attr("id"), contentElements = that.wrapper.children("div");
that.tabGroup.find(".k-item").each(function(idx) {
var currentContent = contentElements.eq(idx), id = tabStripID + "-" + (idx + 1);
this.setAttribute("aria-controls", id);
if (!currentContent.length && contentUrls[idx]) {
$("<div id='" + id + "' class='" + CONTENT + "'/>").appendTo(that.wrapper);
} else {
currentContent.attr("id", id);
}
currentContent.attr("role", "tabpanel");
currentContent.filter(":not(." + ACTIVESTATE + ")").attr("aria-hidden", true).attr("aria-expanded", false);
currentContent.filter("." + ACTIVESTATE).attr("aria-expanded", true);
});
that.contentElements = that.contentAnimators = that.wrapper.children("div");
// refresh the contents
if (kendo.kineticScrollNeeded && kendo.mobile.ui.Scroller) {
kendo.touchScroller(that.contentElements);
that.contentElements = that.contentElements.children(".km-scroll-container");
}
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVERSTATE, e.type == MOUSEENTER);
},
_click: function(item) {
var that = this, link = item.find("." + LINK), href = link.attr(HREF), collapse = that.options.collapsible, contentHolder = that.contentHolder(item.index()), prevent, isAnchor;
if (item.closest(".k-widget")[0] != that.wrapper[0]) {
return;
}
if (item.is("." + DISABLEDSTATE + (!collapse ? ",." + ACTIVESTATE : ""))) {
return true;
}
isAnchor = link.data(CONTENTURL) || href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1);
prevent = !href || isAnchor;
if (that.tabGroup.children("[data-animating]").length) {
return prevent;
}
if (that.trigger(SELECT, {
item: item[0],
contentElement: contentHolder[0]
})) {
return true;
}
if (prevent === false) {
return;
}
if (collapse && item.is("." + ACTIVESTATE)) {
that.deactivateTab(item);
return true;
}
if (that.activateTab(item)) {
prevent = true;
}
return prevent;
},
deactivateTab: function(item) {
var that = this, animationSettings = that.options.animation, animation = animationSettings.open, close = extend({}, animationSettings.close), hasCloseAnimation = close && "effects" in close;
item = that.tabGroup.find(item);
close = extend(hasCloseAnimation ? close : extend({
reverse: true
}, animation), {
hide: true
});
if (kendo.size(animation.effects)) {
item.kendoAddClass(DEFAULTSTATE, {
duration: animation.duration
});
item.kendoRemoveClass(ACTIVESTATE, {
duration: animation.duration
});
} else {
item.addClass(DEFAULTSTATE);
item.removeClass(ACTIVESTATE);
}
item.removeAttr("aria-selected");
that.contentAnimators.filter("." + ACTIVESTATE).kendoStop(true, true).kendoAnimate(close).removeClass(ACTIVESTATE).attr("aria-hidden", true);
},
activateTab: function(item) {
item = this.tabGroup.find(item);
var that = this, animationSettings = that.options.animation, animation = animationSettings.open, close = extend({}, animationSettings.close), hasCloseAnimation = close && "effects" in close, neighbours = item.parent().children(), oldTab = neighbours.filter("." + ACTIVESTATE), itemIndex = neighbours.index(item);
close = extend(hasCloseAnimation ? close : extend({
reverse: true
}, animation), {
hide: true
});
// deactivate previously active tab
if (kendo.size(animation.effects)) {
oldTab.kendoRemoveClass(ACTIVESTATE, {
duration: close.duration
});
item.kendoRemoveClass(HOVERSTATE, {
duration: close.duration
});
} else {
oldTab.removeClass(ACTIVESTATE);
item.removeClass(HOVERSTATE);
}
// handle content elements
var contentAnimators = that.contentAnimators;
if (item.data("in-request")) {
that.xhr.abort();
item.removeAttr("data-in-request");
}
if (contentAnimators.length === 0) {
oldTab.removeClass(TABONTOP);
item.addClass(TABONTOP).css("z-index");
item.addClass(ACTIVESTATE);
that._current(item);
that.trigger("change");
return false;
}
var visibleContents = contentAnimators.filter("." + ACTIVESTATE), contentHolder = that.contentHolder(itemIndex), contentElement = contentHolder.closest(".k-content");
if (contentHolder.length === 0) {
visibleContents.removeClass(ACTIVESTATE).attr("aria-hidden", true).kendoStop(true, true).kendoAnimate(close);
return false;
}
item.attr("data-animating", true);
var isAjaxContent = (item.children("." + LINK).data(CONTENTURL) || false) && contentHolder.is(EMPTY), showContentElement = function() {
oldTab.removeClass(TABONTOP);
item.addClass(TABONTOP).css("z-index");
if (kendo.size(animation.effects)) {
oldTab.kendoAddClass(DEFAULTSTATE, {
duration: animation.duration
});
item.kendoAddClass(ACTIVESTATE, {
duration: animation.duration
});
} else {
oldTab.addClass(DEFAULTSTATE);
item.addClass(ACTIVESTATE);
}
oldTab.removeAttr("aria-selected");
item.attr("aria-selected", true);
that._current(item);
contentElement.addClass(ACTIVESTATE).removeAttr("aria-hidden").kendoStop(true, true).attr("aria-expanded", true).kendoAnimate(extend({
init: function() {
that.trigger(ACTIVATE, {
item: item[0],
contentElement: contentHolder[0]
});
}
}, animation, {
complete: function() {
item.removeAttr("data-animating");
}
}));
}, showContent = function() {
if (!isAjaxContent) {
showContentElement();
that.trigger("change");
} else {
item.removeAttr("data-animating");
that.ajaxRequest(item, contentHolder, function() {
item.attr("data-animating", true);
showContentElement();
that.trigger("change");
});
}
};
visibleContents.removeClass(ACTIVESTATE);
visibleContents.attr("aria-hidden", true);
visibleContents.attr("aria-expanded", false);
if (visibleContents.length) {
visibleContents.kendoStop(true, true).kendoAnimate(extend({
complete: showContent
}, close));
} else {
showContent();
}
return true;
},
contentElement: function(itemIndex) {
if (isNaN(itemIndex - 0)) {
return undefined;
}
var contentElements = this.contentElements && this.contentElements[0] && !kendo.kineticScrollNeeded ? this.contentElements : this.contentAnimators, idTest = new RegExp("-" + (itemIndex + 1) + "$");
if (contentElements) {
for (var i = 0, len = contentElements.length; i < len; i++) {
if (idTest.test(contentElements.closest(".k-content")[i].id)) {
return contentElements[i];
}
}
}
return undefined;
},
contentHolder: function(itemIndex) {
var contentElement = $(this.contentElement(itemIndex)), scrollContainer = contentElement.children(".km-scroll-container");
return kendo.support.touch && scrollContainer[0] ? scrollContainer : contentElement;
},
ajaxRequest: function(element, content, complete, url) {
element = this.tabGroup.find(element);
if (element.find(".k-loading").length) {
return;
}
var that = this, link = element.find("." + LINK), data = {}, statusIcon = null, loadingIconTimeout = setTimeout(function() {
statusIcon = $("<span class='k-icon k-loading'/>").prependTo(link);
}, 100);
url = url || link.data(CONTENTURL) || link.attr(HREF);
element.attr("data-in-request", true);
that.xhr = $.ajax({
type: "GET",
cache: false,
url: url,
dataType: "html",
data: data,
error: function(xhr, status) {
if (that.trigger("error", {
xhr: xhr,
status: status
})) {
this.complete();
}
},
complete: function() {
element.removeAttr("data-in-request");
clearTimeout(loadingIconTimeout);
if (statusIcon !== null) {
statusIcon.remove();
}
},
success: function(data) {
try {
content.html(data);
} catch (e) {
var console = window.console;
if (console && console.error) {
console.error(e.name + ": " + e.message + " in " + url);
}
this.error(this.xhr, "error");
}
if (complete) {
complete.call(that, content);
}
that.trigger(CONTENTLOAD, {
item: element[0],
contentElement: content[0]
});
}
});
}
});
// client-side rendering
extend(TabStrip, {
renderItem: function(options) {
options = extend({
tabStrip: {},
group: {}
}, options);
var empty = templates.empty, item = options.item;
return templates.item(extend(options, {
image: item.imageUrl ? templates.image : empty,
sprite: item.spriteCssClass ? templates.sprite : empty,
itemWrapper: templates.itemWrapper
}, rendering));
},
renderContent: function(options) {
return templates.content(extend(options, rendering));
}
});
kendo.ui.plugin(TabStrip);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, keys = kendo.keys, activeElement = kendo._activeElement, extractFormat = kendo._extractFormat, support = kendo.support, browser = support.browser, ui = kendo.ui, Widget = ui.Widget, OPEN = "open", CLOSE = "close", CHANGE = "change", ns = ".kendoTimePicker", CLICK = "click" + ns, DEFAULT = "k-state-default", DISABLED = "disabled", READONLY = "readonly", LI = "li", SPAN = "<span/>", FOCUSED = "k-state-focused", HOVER = "k-state-hover", HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, MOUSEDOWN = "mousedown" + ns, MS_PER_MINUTE = 6e4, MS_PER_DAY = 864e5, SELECTED = "k-state-selected", STATEDISABLED = "k-state-disabled", ARIA_SELECTED = "aria-selected", ARIA_EXPANDED = "aria-expanded", ARIA_HIDDEN = "aria-hidden", ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", ARIA_ACTIVEDESCENDANT = "aria-activedescendant", ID = "id", isArray = $.isArray, extend = $.extend, proxy = $.proxy, DATE = Date, TODAY = new DATE();
TODAY = new DATE(TODAY.getFullYear(), TODAY.getMonth(), TODAY.getDate(), 0, 0, 0);
var TimeView = function(options) {
var that = this, id = options.id;
that.options = options;
that.ul = $('<ul tabindex="-1" role="listbox" aria-hidden="true" unselectable="on" class="k-list k-reset"/>').css({
overflow: support.kineticScrollNeeded ? "" : "auto"
}).on(CLICK, LI, proxy(that._click, that)).on("mouseenter" + ns, LI, function() {
$(this).addClass(HOVER);
}).on("mouseleave" + ns, LI, function() {
$(this).removeClass(HOVER);
});
that.list = $("<div class='k-list-container'/>").append(that.ul).on(MOUSEDOWN, preventDefault);
if (id) {
that._timeViewID = id + "_timeview";
that._optionID = id + "_option_selected";
that.ul.attr(ID, that._timeViewID);
}
that._popup();
that.template = kendo.template('<li tabindex="-1" role="option" class="k-item" unselectable="on">#=data#</li>', {
useWithBlock: false
});
};
TimeView.prototype = {
current: function(candidate) {
var that = this, active = that.options.active;
if (candidate !== undefined) {
if (that._current) {
that._current.removeClass(SELECTED).removeAttr(ARIA_SELECTED).removeAttr(ID);
}
if (candidate) {
candidate = $(candidate).addClass(SELECTED).attr(ID, that._optionID).attr(ARIA_SELECTED, true);
that.scroll(candidate[0]);
}
that._current = candidate;
if (active) {
active(candidate);
}
} else {
return that._current;
}
},
close: function() {
this.popup.close();
},
destroy: function() {
var that = this;
that.ul.off(ns);
that.list.off(ns);
that.popup.destroy();
},
open: function() {
var that = this;
if (!that.ul[0].firstChild) {
that.bind();
}
that.popup.open();
if (that._current) {
that.scroll(that._current[0]);
}
},
dataBind: function(dates) {
var that = this, options = that.options, format = options.format, toString = kendo.toString, template = that.template, length = dates.length, idx = 0, date, html = "";
for (;idx < length; idx++) {
date = dates[idx];
if (isInRange(date, options.min, options.max)) {
html += template(toString(date, format, options.culture));
}
}
that._html(html, length);
},
refresh: function() {
var that = this, options = that.options, format = options.format, offset = dst(), ignoreDST = offset < 0, min = options.min, max = options.max, msMin = getMilliseconds(min), msMax = getMilliseconds(max), msInterval = options.interval * MS_PER_MINUTE, toString = kendo.toString, template = that.template, start = new DATE(+min), startDay = start.getDate(), msStart, lastIdx, idx = 0, length, html = "";
if (ignoreDST) {
length = (MS_PER_DAY + offset * MS_PER_MINUTE) / msInterval;
} else {
length = MS_PER_DAY / msInterval;
}
if (msMin != msMax) {
if (msMin > msMax) {
msMax += MS_PER_DAY;
}
length = (msMax - msMin) / msInterval + 1;
}
lastIdx = parseInt(length, 10);
for (;idx < length; idx++) {
if (idx) {
setTime(start, msInterval, ignoreDST);
}
if (msMax && lastIdx == idx) {
msStart = getMilliseconds(start);
if (startDay < start.getDate()) {
msStart += MS_PER_DAY;
}
if (msStart > msMax) {
start = new DATE(+max);
}
}
html += template(toString(start, format, options.culture));
}
that._html(html, length);
},
bind: function() {
var that = this, dates = that.options.dates;
if (dates && dates[0]) {
that.dataBind(dates);
} else {
that.refresh();
}
},
_html: function(html, length) {
var that = this;
that.ul[0].innerHTML = html;
that._height(length);
that.current(null);
that.select(that._value);
},
scroll: function(item) {
if (!item) {
return;
}
var ul = this.ul[0], itemOffsetTop = item.offsetTop, itemOffsetHeight = item.offsetHeight, ulScrollTop = ul.scrollTop, ulOffsetHeight = ul.clientHeight, bottomDistance = itemOffsetTop + itemOffsetHeight;
ul.scrollTop = ulScrollTop > itemOffsetTop ? itemOffsetTop : bottomDistance > ulScrollTop + ulOffsetHeight ? bottomDistance - ulOffsetHeight : ulScrollTop;
},
select: function(li) {
var that = this, options = that.options, current = that._current;
if (li instanceof Date) {
li = kendo.toString(li, options.format, options.culture);
}
if (typeof li === "string") {
if (!current || current.text() !== li) {
li = $.grep(that.ul[0].childNodes, function(node) {
return (node.textContent || node.innerText) == li;
});
li = li[0] ? li : null;
} else {
li = current;
}
}
that.current(li);
},
toggle: function() {
var that = this;
if (that.popup.visible()) {
that.close();
} else {
that.open();
}
},
value: function(value) {
var that = this;
that._value = value;
if (that.ul[0].firstChild) {
that.select(value);
}
},
_click: function(e) {
var that = this, li = $(e.currentTarget);
if (!e.isDefaultPrevented()) {
that.select(li);
that.options.change(li.text(), true);
that.close();
}
},
_height: function(length) {
if (length) {
var that = this, list = that.list, parent = list.parent(".k-animation-container"), height = that.options.height;
list.add(parent).show().height(that.ul[0].scrollHeight > height ? height : "auto").hide();
}
},
_parse: function(value) {
var that = this, options = that.options, current = that._value || TODAY;
if (value instanceof DATE) {
return value;
}
value = kendo.parseDate(value, options.parseFormats, options.culture);
if (value) {
value = new DATE(current.getFullYear(), current.getMonth(), current.getDate(), value.getHours(), value.getMinutes(), value.getSeconds(), value.getMilliseconds());
}
return value;
},
_adjustListWidth: function() {
var list = this.list, width = list[0].style.width, wrapper = this.options.anchor, computedStyle, computedWidth;
if (!list.data("width") && width) {
return;
}
computedStyle = window.getComputedStyle ? window.getComputedStyle(wrapper[0], null) : 0;
computedWidth = computedStyle ? parseFloat(computedStyle.width) : wrapper.outerWidth();
if (computedStyle && (browser.mozilla || browser.msie)) {
// getComputedStyle returns different box in FF and IE.
computedWidth += parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight) + parseFloat(computedStyle.borderLeftWidth) + parseFloat(computedStyle.borderRightWidth);
}
width = computedWidth - (list.outerWidth() - list.width());
list.css({
fontFamily: wrapper.css("font-family"),
width: width
}).data("width", width);
},
_popup: function() {
var that = this, list = that.list, options = that.options, anchor = options.anchor;
that.popup = new ui.Popup(list, extend(options.popup, {
anchor: anchor,
open: options.open,
close: options.close,
animation: options.animation,
isRtl: support.isRtl(options.anchor)
}));
kendo.touchScroller(that.popup.element);
},
move: function(e) {
var that = this, key = e.keyCode, ul = that.ul[0], current = that._current, down = key === keys.DOWN;
if (key === keys.UP || down) {
if (e.altKey) {
that.toggle(down);
return;
} else if (down) {
current = current ? current[0].nextSibling : ul.firstChild;
} else {
current = current ? current[0].previousSibling : ul.lastChild;
}
if (current) {
that.select(current);
}
that.options.change(that._current.text());
e.preventDefault();
} else if (key === keys.ENTER || key === keys.TAB || key === keys.ESC) {
e.preventDefault();
if (current) {
that.options.change(current.text(), true);
}
that.close();
}
}
};
function setTime(date, time, ignoreDST) {
var offset = date.getTimezoneOffset(), offsetDiff;
date.setTime(date.getTime() + time);
if (!ignoreDST) {
offsetDiff = date.getTimezoneOffset() - offset;
date.setTime(date.getTime() + offsetDiff * MS_PER_MINUTE);
}
}
function dst() {
var today = new DATE(), midnight = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0), noon = new DATE(today.getFullYear(), today.getMonth(), today.getDate(), 12, 0, 0);
return -1 * (midnight.getTimezoneOffset() - noon.getTimezoneOffset());
}
function getMilliseconds(date) {
return date.getHours() * 60 * MS_PER_MINUTE + date.getMinutes() * MS_PER_MINUTE + date.getSeconds() * 1e3 + date.getMilliseconds();
}
function isInRange(value, min, max) {
var msMin = getMilliseconds(min), msMax = getMilliseconds(max), msValue;
if (!value || msMin == msMax) {
return true;
}
msValue = getMilliseconds(value);
if (msMin > msValue) {
msValue += MS_PER_DAY;
}
if (msMax < msMin) {
msMax += MS_PER_DAY;
}
return msValue >= msMin && msValue <= msMax;
}
TimeView.getMilliseconds = getMilliseconds;
kendo.TimeView = TimeView;
var TimePicker = Widget.extend({
init: function(element, options) {
var that = this, ul, timeView, disabled;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
normalize(options);
that._wrapper();
that.timeView = timeView = new TimeView(extend({}, options, {
id: element.attr(ID),
anchor: that.wrapper,
format: options.format,
change: function(value, trigger) {
if (trigger) {
that._change(value);
} else {
element.val(value);
}
},
open: function(e) {
that.timeView._adjustListWidth();
if (that.trigger(OPEN)) {
e.preventDefault();
} else {
element.attr(ARIA_EXPANDED, true);
ul.attr(ARIA_HIDDEN, false);
}
},
close: function(e) {
if (that.trigger(CLOSE)) {
e.preventDefault();
} else {
element.attr(ARIA_EXPANDED, false);
ul.attr(ARIA_HIDDEN, true);
}
},
active: function(current) {
element.removeAttr(ARIA_ACTIVEDESCENDANT);
if (current) {
element.attr(ARIA_ACTIVEDESCENDANT, timeView._optionID);
}
}
}));
ul = timeView.ul;
that._icon();
that._reset();
element[0].type = "text";
element.addClass("k-input").attr({
role: "textbox",
"aria-haspopup": true,
"aria-expanded": false,
"aria-owns": timeView._timeViewID
});
disabled = element.is("[disabled]");
if (disabled) {
that.enable(false);
} else {
that.readonly(element.is("[readonly]"));
}
that.value(options.value || element.val());
kendo.notify(that);
},
options: {
name: "TimePicker",
min: TODAY,
max: TODAY,
format: "",
dates: [],
parseFormats: [],
value: null,
interval: 30,
height: 200,
animation: {}
},
events: [ OPEN, CLOSE, CHANGE ],
setOptions: function(options) {
var that = this, timeView = that.timeView, timeViewOptions = timeView.options;
Widget.fn.setOptions.call(that, options);
normalize(that.options);
timeView.options = extend(timeViewOptions, that.options, {
active: timeViewOptions.active,
change: timeViewOptions.change,
close: timeViewOptions.close,
open: timeViewOptions.open
});
timeView.ul[0].innerHTML = "";
},
dataBind: function(dates) {
if (isArray(dates)) {
this.timeView.dataBind(dates);
}
},
_editable: function(options) {
var that = this, disable = options.disable, readonly = options.readonly, arrow = that._arrow.off(ns), element = that.element.off(ns), wrapper = that._inputWrapper.off(ns);
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
element.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false).on("keydown" + ns, proxy(that._keydown, that)).on("blur" + ns, proxy(that._blur, that)).on("focus" + ns, function() {
that._inputWrapper.addClass(FOCUSED);
});
arrow.on(CLICK, proxy(that._click, that)).on(MOUSEDOWN, preventDefault);
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
element.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
readonly: function(readonly) {
this._editable({
readonly: readonly === undefined ? true : readonly,
disable: false
});
},
enable: function(enable) {
this._editable({
readonly: false,
disable: !(enable = enable === undefined ? true : enable)
});
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.timeView.destroy();
that.element.off(ns);
that._arrow.off(ns);
that._inputWrapper.off(ns);
if (that._form) {
that._form.off("reset", that._resetHandler);
}
},
close: function() {
this.timeView.close();
},
open: function() {
this.timeView.open();
},
min: function(value) {
return this._option("min", value);
},
max: function(value) {
return this._option("max", value);
},
value: function(value) {
var that = this;
if (value === undefined) {
return that._value;
}
that._old = that._update(value);
if (that._old === null) {
that.element.val("");
}
that._oldText = that.element.val();
},
_blur: function() {
var that = this, value = that.element.val();
that.close();
if (value !== that._oldText) {
that._change(value);
}
that._inputWrapper.removeClass(FOCUSED);
},
_click: function() {
var that = this, element = that.element;
that.timeView.toggle();
if (!support.touch && element[0] !== activeElement()) {
element.focus();
}
},
_change: function(value) {
var that = this;
value = that._update(value);
if (+that._old != +value) {
that._old = value;
that._oldText = that.element.val();
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_icon: function() {
var that = this, element = that.element, arrow;
arrow = element.next("span.k-select");
if (!arrow[0]) {
arrow = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-clock">select</span></span>').insertAfter(element);
}
that._arrow = arrow.attr({
role: "button",
"aria-controls": that.timeView._timeViewID
});
},
_keydown: function(e) {
var that = this, key = e.keyCode, timeView = that.timeView, value = that.element.val();
if (timeView.popup.visible() || e.altKey) {
timeView.move(e);
} else if (key === keys.ENTER && value !== that._oldText) {
that._change(value);
}
},
_option: function(option, value) {
var that = this, options = that.options;
if (value === undefined) {
return options[option];
}
value = that.timeView._parse(value);
if (!value) {
return;
}
value = new DATE(+value);
options[option] = value;
that.timeView.options[option] = value;
that.timeView.bind();
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_update: function(value) {
var that = this, options = that.options, timeView = that.timeView, date = timeView._parse(value);
if (!isInRange(date, options.min, options.max)) {
date = null;
}
that._value = date;
that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
timeView.value(date);
return date;
},
_wrapper: function() {
var that = this, element = that.element, wrapper;
wrapper = element.parents(".k-timepicker");
if (!wrapper[0]) {
wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
wrapper = wrapper.wrap(SPAN).parent();
}
wrapper[0].style.cssText = element[0].style.cssText;
that.wrapper = wrapper.addClass("k-widget k-timepicker k-header").addClass(element[0].className);
element.css({
width: "100%",
height: element[0].style.height
});
that._inputWrapper = $(wrapper[0].firstChild);
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
that.value(element[0].defaultValue);
};
that._form = form.on("reset", that._resetHandler);
}
}
});
function normalize(options) {
var parseFormats = options.parseFormats;
options.format = extractFormat(options.format || kendo.getCulture(options.culture).calendars.standard.patterns.t);
parseFormats = isArray(parseFormats) ? parseFormats : [ parseFormats ];
parseFormats.splice(0, 0, options.format);
options.parseFormats = parseFormats;
}
function preventDefault(e) {
e.preventDefault();
}
ui.plugin(TimePicker);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, TimeView = kendo.TimeView, parse = kendo.parseDate, activeElement = kendo._activeElement, extractFormat = kendo._extractFormat, calendar = kendo.calendar, isInRange = calendar.isInRange, restrictValue = calendar.restrictValue, isEqualDatePart = calendar.isEqualDatePart, getMilliseconds = TimeView.getMilliseconds, ui = kendo.ui, Widget = ui.Widget, OPEN = "open", CLOSE = "close", CHANGE = "change", ns = ".kendoDateTimePicker", CLICK = "click" + ns, DISABLED = "disabled", READONLY = "readonly", DEFAULT = "k-state-default", FOCUSED = "k-state-focused", HOVER = "k-state-hover", STATEDISABLED = "k-state-disabled", HOVEREVENTS = "mouseenter" + ns + " mouseleave" + ns, MOUSEDOWN = "mousedown" + ns, MONTH = "month", SPAN = "<span/>", ARIA_ACTIVEDESCENDANT = "aria-activedescendant", ARIA_EXPANDED = "aria-expanded", ARIA_HIDDEN = "aria-hidden", ARIA_OWNS = "aria-owns", ARIA_DISABLED = "aria-disabled", ARIA_READONLY = "aria-readonly", DATE = Date, MIN = new DATE(1900, 0, 1), MAX = new DATE(2099, 11, 31), dateViewParams = {
view: "date"
}, timeViewParams = {
view: "time"
}, extend = $.extend;
var DateTimePicker = Widget.extend({
init: function(element, options) {
var that = this, disabled;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
normalize(options);
that._wrapper();
that._views();
that._icons();
that._reset();
that._template();
element[0].type = "text";
element.addClass("k-input").attr({
role: "textbox",
"aria-haspopup": true,
"aria-expanded": false
});
that._midnight = getMilliseconds(options.min) + getMilliseconds(options.max) === 0;
disabled = element.is("[disabled]");
if (disabled) {
that.enable(false);
} else {
that.readonly(element.is("[readonly]"));
}
that.value(options.value || element.val());
kendo.notify(that);
},
options: {
name: "DateTimePicker",
value: null,
format: "",
timeFormat: "",
culture: "",
parseFormats: [],
dates: [],
min: new DATE(MIN),
max: new DATE(MAX),
interval: 30,
height: 200,
footer: "",
start: MONTH,
depth: MONTH,
animation: {},
month: {},
ARIATemplate: 'Current focused date is #=kendo.toString(data.current, "G")#'
},
events: [ OPEN, CLOSE, CHANGE ],
setOptions: function(options) {
var that = this, dateViewOptions = that.dateView.options, timeViewOptions = that.timeView.options;
Widget.fn.setOptions.call(that, options);
normalize(that.options);
options = that.options;
extend(dateViewOptions, options, {
change: dateViewOptions.change,
close: dateViewOptions.close,
open: dateViewOptions.open
});
extend(timeViewOptions, options, {
format: options.timeFormat,
active: timeViewOptions.active,
change: timeViewOptions.change,
close: timeViewOptions.close,
open: timeViewOptions.open
});
that.timeView.ul[0].innerHTML = "";
},
_editable: function(options) {
var that = this, element = that.element.off(ns), dateIcon = that._dateIcon.off(ns), timeIcon = that._timeIcon.off(ns), wrapper = that._inputWrapper.off(ns), readonly = options.readonly, disable = options.disable;
if (!readonly && !disable) {
wrapper.addClass(DEFAULT).removeClass(STATEDISABLED).on(HOVEREVENTS, that._toggleHover);
element.removeAttr(DISABLED).removeAttr(READONLY).attr(ARIA_DISABLED, false).attr(ARIA_READONLY, false).on("keydown" + ns, $.proxy(that._keydown, that)).on("focus" + ns, function() {
that._inputWrapper.addClass(FOCUSED);
}).on("blur" + ns, function() {
that._inputWrapper.removeClass(FOCUSED);
if (element.val() !== that._oldText) {
that._change(element.val());
}
that.close("date");
that.close("time");
});
dateIcon.on(MOUSEDOWN, preventDefault).on(CLICK, function() {
that.toggle("date");
if (!kendo.support.touch && element[0] !== activeElement()) {
element.focus();
}
});
timeIcon.on(MOUSEDOWN, preventDefault).on(CLICK, function() {
that.toggle("time");
if (!kendo.support.touch && element[0] !== activeElement()) {
element.focus();
}
});
} else {
wrapper.addClass(disable ? STATEDISABLED : DEFAULT).removeClass(disable ? DEFAULT : STATEDISABLED);
element.attr(DISABLED, disable).attr(READONLY, readonly).attr(ARIA_DISABLED, disable).attr(ARIA_READONLY, readonly);
}
},
readonly: function(readonly) {
this._editable({
readonly: readonly === undefined ? true : readonly,
disable: false
});
},
enable: function(enable) {
this._editable({
readonly: false,
disable: !(enable = enable === undefined ? true : enable)
});
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.dateView.destroy();
that.timeView.destroy();
that.element.off(ns);
that._dateIcon.off(ns);
that._timeIcon.off(ns);
that._inputWrapper.off(ns);
if (that._form) {
that._form.off("reset", that._resetHandler);
}
},
close: function(view) {
if (view !== "time") {
view = "date";
}
this[view + "View"].close();
},
open: function(view) {
if (view !== "time") {
view = "date";
}
this[view + "View"].open();
},
min: function(value) {
return this._option("min", value);
},
max: function(value) {
return this._option("max", value);
},
toggle: function(view) {
var secondView = "timeView";
if (view !== "time") {
view = "date";
} else {
secondView = "dateView";
}
this[view + "View"].toggle();
this[secondView].close();
},
value: function(value) {
var that = this;
if (value === undefined) {
return that._value;
}
that._old = that._update(value);
if (that._old === null) {
that.element.val("");
}
that._oldText = that.element.val();
},
_change: function(value) {
var that = this;
value = that._update(value);
if (+that._old != +value) {
that._old = value;
that._oldText = that.element.val();
that.trigger(CHANGE);
// trigger the DOM change event so any subscriber gets notified
that.element.trigger(CHANGE);
}
},
_option: function(option, value) {
var that = this, options = that.options, timeView = that.timeView, timeViewOptions = timeView.options, current = that._value || that._old;
if (value === undefined) {
return options[option];
}
value = parse(value, options.parseFormats, options.culture);
if (!value) {
return;
}
options[option] = new DATE(+value);
that.dateView[option](value);
that._midnight = getMilliseconds(options.min) + getMilliseconds(options.max) === 0;
if (current && isEqualDatePart(value, current)) {
if (that._midnight && option == "max") {
timeViewOptions[option] = MAX;
timeView.dataBind([ MAX ]);
return;
}
timeViewOptions[option] = value;
} else {
timeViewOptions.max = MAX;
timeViewOptions.min = MIN;
}
timeView.bind();
},
_toggleHover: function(e) {
$(e.currentTarget).toggleClass(HOVER, e.type === "mouseenter");
},
_update: function(value) {
var that = this, options = that.options, min = options.min, max = options.max, dates = options.dates, timeView = that.timeView, date = parse(value, options.parseFormats, options.culture), rebind, timeViewOptions, old, skip, formattedValue;
if (+date === +that._value) {
formattedValue = kendo.toString(date, options.format, options.culture);
if (formattedValue !== value) {
that.element.val(date === null ? value : formattedValue);
}
return date;
}
if (date !== null && isEqualDatePart(date, min)) {
date = restrictValue(date, min, max);
} else if (!isInRange(date, min, max)) {
date = null;
}
that._value = date;
timeView.value(date);
that.dateView.value(date);
if (date) {
old = that._old;
timeViewOptions = timeView.options;
if (dates[0]) {
dates = $.grep(dates, function(d) {
return isEqualDatePart(date, d);
});
if (dates[0]) {
timeView.dataBind(dates);
skip = true;
}
}
if (!skip) {
if (isEqualDatePart(date, min)) {
timeViewOptions.min = min;
timeViewOptions.max = MAX;
rebind = true;
}
if (isEqualDatePart(date, max)) {
if (that._midnight) {
timeView.dataBind([ MAX ]);
skip = true;
} else {
timeViewOptions.max = max;
if (!rebind) {
timeViewOptions.min = MIN;
}
rebind = true;
}
}
}
if (!skip && (!old && rebind || old && !isEqualDatePart(old, date))) {
if (!rebind) {
timeViewOptions.max = MAX;
timeViewOptions.min = MIN;
}
timeView.bind();
}
}
that.element.val(date ? kendo.toString(date, options.format, options.culture) : value);
that._updateARIA(date);
return date;
},
_keydown: function(e) {
var that = this, dateView = that.dateView, timeView = that.timeView, value = that.element.val(), isDateViewVisible = dateView.popup.visible();
if (e.altKey && e.keyCode === kendo.keys.DOWN) {
that.toggle(isDateViewVisible ? "time" : "date");
} else if (isDateViewVisible) {
dateView.move(e);
that._updateARIA(dateView._current);
} else if (timeView.popup.visible()) {
timeView.move(e);
} else if (e.keyCode === kendo.keys.ENTER && value !== that._oldText) {
that._change(value);
}
},
_views: function() {
var that = this, element = that.element, options = that.options, id = element.attr("id"), dateView, timeView, div, ul, date;
that.dateView = dateView = new kendo.DateView(extend({}, options, {
id: id,
anchor: that.wrapper,
change: function() {
var value = dateView.calendar.value(), msValue = +value, msMin = +options.min, msMax = +options.max, current;
if (msValue === msMin || msValue === msMax) {
current = new DATE(+that._value);
current.setFullYear(value.getFullYear());
current.setMonth(value.getMonth());
current.setDate(value.getDate());
if (isInRange(current, msMin, msMax)) {
value = current;
}
}
that._change(value);
that.close("date");
},
close: function(e) {
if (that.trigger(CLOSE, dateViewParams)) {
e.preventDefault();
} else {
element.attr(ARIA_EXPANDED, false);
div.attr(ARIA_HIDDEN, true);
if (!timeView.popup.visible()) {
element.removeAttr(ARIA_OWNS);
}
}
},
open: function(e) {
if (that.trigger(OPEN, dateViewParams)) {
e.preventDefault();
} else {
if (that.element.val() !== that._oldText) {
date = parse(element.val(), options.parseFormats, options.culture);
if (!date) {
that.dateView.value(date);
} else {
that.dateView._current = date;
that.dateView.calendar._focus(date);
}
}
div.attr(ARIA_HIDDEN, false);
element.attr(ARIA_EXPANDED, true).attr(ARIA_OWNS, dateView._dateViewID);
}
}
}));
div = dateView.div;
that.timeView = timeView = new TimeView({
id: id,
value: options.value,
anchor: that.wrapper,
animation: options.animation,
format: options.timeFormat,
culture: options.culture,
height: options.height,
interval: options.interval,
min: new DATE(MIN),
max: new DATE(MAX),
parseFormats: options.parseFormats,
change: function(value, trigger) {
value = timeView._parse(value);
if (value < options.min) {
value = new DATE(+options.min);
timeView.options.min = value;
} else if (value > options.max) {
value = new DATE(+options.max);
timeView.options.max = value;
}
if (trigger) {
that._timeSelected = true;
that._change(value);
} else {
element.val(kendo.toString(value, options.format, options.culture));
dateView.value(value);
that._updateARIA(value);
}
},
close: function(e) {
if (that.trigger(CLOSE, timeViewParams)) {
e.preventDefault();
} else {
ul.attr(ARIA_HIDDEN, true);
element.attr(ARIA_EXPANDED, false);
if (!dateView.popup.visible()) {
element.removeAttr(ARIA_OWNS);
}
}
},
open: function(e) {
timeView._adjustListWidth();
if (that.trigger(OPEN, timeViewParams)) {
e.preventDefault();
} else {
ul.attr(ARIA_HIDDEN, false);
element.attr(ARIA_EXPANDED, true).attr(ARIA_OWNS, timeView._timeViewID);
}
},
active: function(current) {
element.removeAttr(ARIA_ACTIVEDESCENDANT);
if (current) {
element.attr(ARIA_ACTIVEDESCENDANT, timeView._optionID);
}
}
});
ul = timeView.ul;
},
_icons: function() {
var that = this, element = that.element, icons;
icons = element.next("span.k-select");
if (!icons[0]) {
icons = $('<span unselectable="on" class="k-select"><span unselectable="on" class="k-icon k-i-calendar">select</span><span unselectable="on" class="k-icon k-i-clock">select</span></span>').insertAfter(element);
}
icons = icons.children();
that._dateIcon = icons.eq(0).attr({
role: "button",
"aria-controls": that.dateView._dateViewID
});
that._timeIcon = icons.eq(1).attr({
role: "button",
"aria-controls": that.timeView._timeViewID
});
},
_wrapper: function() {
var that = this, element = that.element, wrapper;
wrapper = element.parents(".k-datetimepicker");
if (!wrapper[0]) {
wrapper = element.wrap(SPAN).parent().addClass("k-picker-wrap k-state-default");
wrapper = wrapper.wrap(SPAN).parent();
}
wrapper[0].style.cssText = element[0].style.cssText;
element.css({
width: "100%",
height: element[0].style.height
});
that.wrapper = wrapper.addClass("k-widget k-datetimepicker k-header").addClass(element[0].className);
that._inputWrapper = $(wrapper[0].firstChild);
},
_reset: function() {
var that = this, element = that.element, form = element.closest("form");
if (form[0]) {
that._resetHandler = function() {
that.value(element[0].defaultValue);
};
that._form = form.on("reset", that._resetHandler);
}
},
_template: function() {
this._ariaTemplate = kendo.template(this.options.ARIATemplate);
},
_updateARIA: function(date) {
this.element.attr("aria-label", this._ariaTemplate({
current: date
}));
}
});
function preventDefault(e) {
e.preventDefault();
}
function normalize(options) {
var patterns = kendo.getCulture(options.culture).calendars.standard.patterns, timeFormat;
options.format = extractFormat(options.format || patterns.g);
options.timeFormat = timeFormat = extractFormat(options.timeFormat || patterns.t);
kendo.DateView.normalize(options);
if ($.inArray(timeFormat, options.parseFormats) === -1) {
options.parseFormats.splice(1, 0, timeFormat);
}
}
ui.plugin(DateTimePicker);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, data = kendo.data, extend = $.extend, template = kendo.template, isArray = $.isArray, Widget = ui.Widget, HierarchicalDataSource = data.HierarchicalDataSource, proxy = $.proxy, keys = kendo.keys, NS = ".kendoTreeView", SELECT = "select", NAVIGATE = "navigate", EXPAND = "expand", CHANGE = "change", CHECKED = "checked", COLLAPSE = "collapse", DRAGSTART = "dragstart", DRAG = "drag", DROP = "drop", DRAGEND = "dragend", DATABOUND = "dataBound", CLICK = "click", VISIBILITY = "visibility", UNDEFINED = "undefined", KSTATEHOVER = "k-state-hover", KTREEVIEW = "k-treeview", VISIBLE = ":visible", NODE = ".k-item", STRING = "string", ARIASELECTED = "aria-selected", ARIADISABLED = "aria-disabled", TreeView, subGroup, nodeContents, nodeIcon, bindings = {
text: "dataTextField",
url: "dataUrlField",
spriteCssClass: "dataSpriteCssClassField",
imageUrl: "dataImageUrlField"
}, isDomElement = function(o) {
return typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName === STRING;
};
function contentChild(filter) {
return function(node) {
var result = node.children(".k-animation-container");
if (!result.length) {
result = node;
}
return result.children(filter);
};
}
subGroup = contentChild(".k-group");
nodeContents = contentChild(".k-group,.k-content");
nodeIcon = function(node) {
return node.children("div").children(".k-icon");
};
function checkboxes(node) {
return node.children("div").find(".k-checkbox:first :checkbox");
}
function updateNodeHtml(node) {
var wrapper = node.children("div"), group = node.children("ul"), toggleButton = wrapper.children(".k-icon"), checkbox = node.children(":checkbox"), innerWrapper = wrapper.children(".k-in"), currentNode, tmp;
if (node.hasClass("k-treeview")) {
return;
}
if (!wrapper.length) {
wrapper = $("<div />").prependTo(node);
}
if (!toggleButton.length && group.length) {
toggleButton = $("<span class='k-icon' />").prependTo(wrapper);
} else if (!group.length || !group.children().length) {
toggleButton.remove();
group.remove();
}
if (checkbox.length) {
$("<span class='k-checkbox' />").appendTo(wrapper).append(checkbox);
}
if (!innerWrapper.length) {
innerWrapper = $("<span class='k-in' />").appendTo(wrapper)[0];
// move all non-group content in the k-in container
currentNode = wrapper[0].nextSibling;
innerWrapper = wrapper.find(".k-in")[0];
while (currentNode && currentNode.nodeName.toLowerCase() != "ul") {
tmp = currentNode;
currentNode = currentNode.nextSibling;
if (tmp.nodeType == 3) {
tmp.nodeValue = $.trim(tmp.nodeValue);
}
innerWrapper.appendChild(tmp);
}
}
}
TreeView = Widget.extend({
init: function(element, options) {
var that = this, dataInit, inferred = false;
if (isArray(options)) {
dataInit = true;
options = {
dataSource: options
};
}
if (options && typeof options.loadOnDemand == UNDEFINED && isArray(options.dataSource)) {
options.loadOnDemand = false;
}
Widget.prototype.init.call(that, element, options);
element = that.element;
options = that.options;
inferred = element.is("ul") || element.hasClass(KTREEVIEW);
if (inferred) {
options.dataSource.list = element.is("ul") ? element : element.children("ul");
}
that._animation();
that._accessors();
that._templates();
// render treeview if it's not already rendered
if (!element.hasClass(KTREEVIEW)) {
that._wrapper();
if (inferred) {
that.root = element;
that._group(that.wrapper);
}
} else {
// otherwise just initialize properties
that.wrapper = element;
that.root = element.children("ul").eq(0);
}
that._tabindex();
if (!that.wrapper.filter("[role=tree]").length) {
that.wrapper.attr("role", "tree");
}
that._dataSource(inferred);
that._attachEvents();
if (options.dragAndDrop) {
that.dragging = new TreeViewDragAndDrop(that);
}
if (!inferred) {
if (options.autoBind) {
that._progress(true);
that.dataSource.fetch();
}
} else {
that._attachUids();
}
if (options.checkboxes && options.checkboxes.checkChildren) {
that._updateIndeterminateInitial(that.wrapper);
}
if (that.element[0].id) {
that._ariaId = kendo.format("{0}_tv_active", that.element[0].id);
}
},
_attachEvents: function() {
var that = this, clickableItems = ".k-in:not(.k-state-selected,.k-state-disabled)", MOUSEENTER = "mouseenter";
that.wrapper.on(MOUSEENTER + NS, ".k-in.k-state-selected", function(e) {
e.preventDefault();
}).on(MOUSEENTER + NS, clickableItems, function() {
$(this).addClass(KSTATEHOVER);
}).on("mouseleave" + NS, clickableItems, function() {
$(this).removeClass(KSTATEHOVER);
}).on(CLICK + NS, clickableItems, proxy(that._click, that)).on("dblclick" + NS, ".k-in:not(.k-state-disabled)", proxy(that._toggleButtonClick, that)).on(CLICK + NS, ".k-plus,.k-minus", proxy(that._toggleButtonClick, that)).on("keydown" + NS, proxy(that._keydown, that)).on("focus" + NS, proxy(that._focus, that)).on("blur" + NS, proxy(that._blur, that)).on("mousedown" + NS, ".k-in,.k-checkbox :checkbox,.k-plus,.k-minus", proxy(that._mousedown, that)).on("change" + NS, ".k-checkbox :checkbox", proxy(that._checkboxChange, that)).on("click" + NS, ".k-checkbox :checkbox", proxy(that._checkboxClick, that)).on("click" + NS, function(e) {
if (!$(e.target).is(":focusable")) {
that.focus();
}
});
},
_checkboxClick: function(e) {
var checkbox = $(e.target);
if (checkbox.data("indeterminate")) {
checkbox.data("indeterminate", false).prop("indeterminate", false).prop(CHECKED, true);
this._checkboxChange(e);
}
},
_attachUids: function(root, dataSource) {
var that = this, data, uidAttr = kendo.attr("uid");
root = root || that.root;
dataSource = dataSource || that.dataSource;
data = dataSource.view();
root.children("li").each(function(index, item) {
item = $(item).attr(uidAttr, data[index].uid);
item.attr("role", "treeitem");
that._attachUids(item.children("ul"), data[index].children);
});
},
_animation: function() {
var options = this.options, animationOptions = options.animation;
if (animationOptions === false) {
animationOptions = {
expand: {
effects: {}
},
collapse: {
hide: true,
effects: {}
}
};
} else if (!animationOptions.collapse || !("effects" in animationOptions.collapse)) {
animationOptions.collapse = extend({
reverse: true
}, animationOptions.expand);
}
extend(animationOptions.collapse, {
hide: true
});
options.animation = animationOptions;
},
_templates: function() {
var that = this, options = that.options, fieldAccessor = proxy(that._fieldAccessor, that);
if (options.template && typeof options.template == STRING) {
options.template = template(options.template);
} else if (!options.template) {
options.template = template("# var text = " + fieldAccessor("text") + "(item); #" + "# if (typeof item.encoded != 'undefined' && item.encoded === false) {#" + "#= text #" + "# } else { #" + "#: text #" + "# } #");
}
that._checkboxes();
that.templates = {
wrapperCssClass: function(group, item) {
var result = "k-item", index = item.index;
if (group.firstLevel && index === 0) {
result += " k-first";
}
if (index == group.length - 1) {
result += " k-last";
}
return result;
},
cssClass: function(group, item) {
var result = "", index = item.index, groupLength = group.length - 1;
if (group.firstLevel && index === 0) {
result += "k-top ";
}
if (index === 0 && index != groupLength) {
result += "k-top";
} else if (index == groupLength) {
result += "k-bot";
} else {
result += "k-mid";
}
return result;
},
textClass: function(item) {
var result = "k-in";
if (item.enabled === false) {
result += " k-state-disabled";
}
if (item.selected === true) {
result += " k-state-selected";
}
return result;
},
toggleButtonClass: function(item) {
var result = "k-icon";
if (item.expanded !== true) {
result += " k-plus";
} else {
result += " k-minus";
}
if (item.enabled === false) {
result += "-disabled";
}
return result;
},
groupAttributes: function(group) {
return group.expanded !== true ? " style='display:none'" : "";
},
groupCssClass: function(group) {
var cssClass = "k-group";
if (group.firstLevel) {
cssClass += " k-treeview-lines";
}
return cssClass;
},
dragClue: template("<div class='k-header k-drag-clue'>" + "<span class='k-icon k-drag-status'></span>" + "#= treeview.template(data) #" + "</div>"),
group: template("<ul class='#= r.groupCssClass(group) #'#= r.groupAttributes(group) # role='group'>" + "#= renderItems(data) #" + "</ul>"),
itemContent: template("# var imageUrl = " + fieldAccessor("imageUrl") + "(item); #" + "# var spriteCssClass = " + fieldAccessor("spriteCssClass") + "(item); #" + "# if (imageUrl) { #" + "<img class='k-image' alt='' src='#= imageUrl #'>" + "# } #" + "# if (spriteCssClass) { #" + "<span class='k-sprite #= spriteCssClass #'></span>" + "# } #" + "#= treeview.template(data) #"),
itemElement: template("# var url = " + fieldAccessor("url") + "(item); #" + "<div class='#= r.cssClass(group, item) #'>" + "# if (item.hasChildren) { #" + "<span class='#= r.toggleButtonClass(item) #' role='presentation'></span>" + "# } #" + "# if (treeview.checkboxes) { #" + "<span class='k-checkbox' role='presentation'>" + "#= treeview.checkboxes.template(data) #" + "</span>" + "# } #" + "# var tag = url ? 'a' : 'span'; #" + "# var textAttr = url ? ' href=\\'' + url + '\\'' : ''; #" + "<#=tag# class='#= r.textClass(item) #'#= textAttr #>" + "#= r.itemContent(data) #" + "</#=tag#>" + "</div>"),
item: template("<li role='treeitem' class='#= r.wrapperCssClass(group, item) #'" + " " + kendo.attr("uid") + "='#= item.uid #'" + "#=item.selected ? \"aria-selected='true'\" : ''#" + "#=item.enabled === false ? \"aria-disabled='true'\" : ''#" + ">" + "#= r.itemElement(data) #" + "</li>"),
loading: template("<div class='k-icon k-loading' /> Loading...")
};
},
items: function() {
return this.element.find(".k-item");
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
this.dataSource.fetch();
},
_dataSource: function(silentRead) {
var that = this, options = that.options, dataSource = options.dataSource;
function recursiveRead(data) {
for (var i = 0; i < data.length; i++) {
data[i]._initChildren();
data[i].children.fetch();
recursiveRead(data[i].children.view());
}
}
dataSource = isArray(dataSource) ? {
data: dataSource
} : dataSource;
if (that.dataSource && that._refreshHandler) {
that.dataSource.unbind(CHANGE, that._refreshHandler);
} else {
that._refreshHandler = proxy(that.refresh, that);
}
if (!dataSource.fields) {
dataSource.fields = [ {
field: "text"
}, {
field: "url"
}, {
field: "spriteCssClass"
}, {
field: "imageUrl"
} ];
}
that.dataSource = HierarchicalDataSource.create(dataSource);
if (silentRead) {
that.dataSource.fetch();
recursiveRead(that.dataSource.view());
}
that.dataSource.bind(CHANGE, that._refreshHandler);
},
events: [ DRAGSTART, DRAG, DROP, DRAGEND, DATABOUND, EXPAND, COLLAPSE, SELECT, CHANGE, NAVIGATE ],
options: {
name: "TreeView",
dataSource: {},
animation: {
expand: {
effects: "expand:vertical",
duration: 200
},
collapse: {
duration: 100
}
},
dragAndDrop: false,
checkboxes: false,
autoBind: true,
loadOnDemand: true,
template: "",
dataTextField: null
},
_accessors: function() {
var that = this, options = that.options, i, field, textField, element = that.element;
for (i in bindings) {
field = options[bindings[i]];
textField = element.attr(kendo.attr(i + "-field"));
if (!field && textField) {
field = textField;
}
if (!field) {
field = i;
}
if (!isArray(field)) {
field = [ field ];
}
options[bindings[i]] = field;
}
},
_fieldAccessor: function(fieldName) {
var fieldBindings = this.options[bindings[fieldName]], count = fieldBindings.length, result = "(function(item) {";
if (count === 0) {
result += "return item['" + fieldName + "'];";
} else {
result += "var level = item.level();" + "var levels = [" + $.map(fieldBindings, function(x) {
return "function(d){ return " + kendo.expr(x) + "}";
}).join(",") + "];";
// generates levels[level < 3 ? level : 2](item);
result += "return levels[Math.min(level, " + count + "-1)](item)";
}
result += "})";
return result;
},
setOptions: function(options) {
var that = this;
if ("dragAndDrop" in options && options.dragAndDrop && !that.options.dragAndDrop) {
that.dragging = new TreeViewDragAndDrop(that);
}
Widget.fn.setOptions.call(that, options);
that._animation();
that._templates();
},
_trigger: function(eventName, node) {
return this.trigger(eventName, {
node: node.closest(NODE)[0]
});
},
_setIndeterminate: function(node) {
var group = subGroup(node), siblings, length, all = true, i;
if (!group.length) {
return;
}
siblings = checkboxes(group.children());
length = siblings.length;
if (length > 1) {
for (i = 1; i < length; i++) {
if (siblings[i].checked != siblings[i - 1].checked || siblings[i].indeterminate || siblings[i - 1].indeterminate) {
all = false;
break;
}
}
} else {
all = !siblings[0].indeterminate;
}
checkboxes(node).data("indeterminate", !all).prop("indeterminate", !all).prop(CHECKED, all && siblings[0].checked);
},
_updateIndeterminateInitial: function(node) {
var subnodes = subGroup(node).children(), i;
if (subnodes.length) {
for (i = 0; i < subnodes.length; i++) {
this._updateIndeterminateInitial(subnodes.eq(i));
}
this._setIndeterminate(node);
}
},
_updateIndeterminate: function(node) {
var parentNode = this.parent(node), checkbox;
if (parentNode.length) {
this._setIndeterminate(parentNode);
checkbox = parentNode.children("div").find(".k-checkbox > :checkbox");
if (checkbox.prop("indeterminate") === false) {
this.dataItem(parentNode).set(CHECKED, checkbox.prop(CHECKED));
} else {
this.dataItem(parentNode).checked = false;
}
this._updateIndeterminate(parentNode);
}
},
_checkboxChange: function(e) {
var checkbox = $(e.target), isChecked = checkbox.prop(CHECKED), node = checkbox.closest(NODE), that = this;
that.dataItem(node).set(CHECKED, isChecked);
},
_toggleButtonClick: function(e) {
this.toggle($(e.target).closest(NODE));
},
_mousedown: function(e) {
var node = $(e.currentTarget).closest(NODE);
this._clickTarget = node;
this.current(node);
},
_focusable: function(node) {
return node && node.length && node.is(":visible") && !node.find(".k-in:first").hasClass("k-state-disabled");
},
_focus: function() {
var current = this.select(), clickTarget = this._clickTarget;
// suppress initial focus state on touch devices (until keyboard is used)
if (kendo.support.touch) {
return;
}
if (clickTarget && clickTarget.length) {
current = clickTarget;
}
if (!this._focusable(current)) {
current = this.current();
}
if (!this._focusable(current)) {
current = this._nextVisible($());
}
this.current(current);
},
focus: function() {
var wrapper = this.wrapper, scrollContainer = wrapper[0], scrollTop, body = document.body;
do {
scrollContainer = scrollContainer.parentNode;
} while (scrollContainer.scrollHeight <= scrollContainer.clientHeight && scrollContainer != body);
scrollTop = scrollContainer.scrollTop;
wrapper.focus();
scrollContainer.scrollTop = scrollTop;
},
_blur: function() {
this.current().find(".k-in:first").removeClass("k-state-focused");
},
_enabled: function(node) {
return !node.children("div").children(".k-in").hasClass("k-state-disabled");
},
parent: function(node) {
var wrapperRe = /\bk-treeview\b/, itemRe = /\bk-item\b/, result, skipSelf;
if (typeof node == STRING) {
node = this.element.find(node);
}
if (!isDomElement(node)) {
node = node[0];
}
skipSelf = itemRe.test(node.className);
do {
node = node.parentNode;
if (itemRe.test(node.className)) {
if (skipSelf) {
result = node;
} else {
skipSelf = true;
}
}
} while (!wrapperRe.test(node.className) && !result);
return $(result);
},
_nextVisible: function(node) {
var that = this, expanded = that._expanded(node), result;
if (!node.length || !node.is(":visible")) {
result = that.root.children().eq(0);
} else if (expanded) {
result = subGroup(node).children().first();
} else {
while (node.length && !node.next().length) {
node = that.parent(node);
}
if (node.next().length) {
result = node.next();
} else {
result = node;
}
}
if (!that._enabled(result)) {
result = that._nextVisible(result);
}
return result;
},
_previousVisible: function(node) {
var that = this, result;
if (!node.length || node.prev().length) {
if (node.length) {
result = node.prev();
} else {
result = that.root.children().last();
}
while (that._expanded(result)) {
result = subGroup(result).children().last();
}
} else {
result = that.parent(node) || node;
}
if (!that._enabled(result)) {
result = that._previousVisible(result);
}
return result;
},
_keydown: function(e) {
var that = this, key = e.keyCode, target, focused = that.current(), expanded = that._expanded(focused), checkbox = focused.find(".k-checkbox:first :checkbox"), rtl = kendo.support.isRtl(that.element);
if (e.target != e.currentTarget) {
return;
}
if (!rtl && key == keys.RIGHT || rtl && key == keys.LEFT) {
if (expanded) {
target = that._nextVisible(focused);
} else {
that.expand(focused);
}
} else if (!rtl && key == keys.LEFT || rtl && key == keys.RIGHT) {
if (expanded) {
that.collapse(focused);
} else {
target = that.parent(focused);
if (!that._enabled(target)) {
target = undefined;
}
}
} else if (key == keys.DOWN) {
target = that._nextVisible(focused);
} else if (key == keys.UP) {
target = that._previousVisible(focused);
} else if (key == keys.HOME) {
target = that._nextVisible($());
} else if (key == keys.END) {
target = that._previousVisible($());
} else if (key == keys.ENTER) {
if (!focused.find(".k-in:first").hasClass("k-state-selected")) {
if (!that._trigger(SELECT, focused)) {
that.select(focused);
}
}
} else if (key == keys.SPACEBAR && checkbox.length) {
checkbox.prop(CHECKED, !checkbox.prop(CHECKED)).data("indeterminate", false).prop("indeterminate", false);
that._checkboxChange({
target: checkbox
});
target = focused;
}
if (target) {
e.preventDefault();
if (focused[0] != target[0]) {
that._trigger(NAVIGATE, target);
that.current(target);
}
}
},
_click: function(e) {
var that = this, node = $(e.target), contents = nodeContents(node.closest(NODE)), href = node.attr("href"), shouldNavigate;
if (href) {
shouldNavigate = href == "#" || href.indexOf("#" + this.element.id + "-") >= 0;
} else {
shouldNavigate = contents.length && !contents.children().length;
}
if (shouldNavigate) {
e.preventDefault();
}
if (!node.hasClass(".k-state-selected") && !that._trigger(SELECT, node)) {
that.select(node);
}
},
_wrapper: function() {
var that = this, element = that.element, wrapper, root, wrapperClasses = "k-widget k-treeview";
if (element.is("div")) {
wrapper = element;
root = wrapper.children("ul").eq(0);
} else {
// element is ul
wrapper = element.wrap("<div />").parent();
root = element;
}
that.wrapper = wrapper.addClass(wrapperClasses);
that.root = root;
},
_group: function(item) {
var that = this, firstLevel = item.hasClass(KTREEVIEW), group = {
firstLevel: firstLevel,
expanded: firstLevel || that._expanded(item)
}, groupElement = item.children("ul");
groupElement.addClass(that.templates.groupCssClass(group)).css("display", group.expanded ? "" : "none");
that._nodes(groupElement, group);
},
_nodes: function(groupElement, groupData) {
var that = this, nodes = groupElement.children("li"), nodeData;
groupData = extend({
length: nodes.length
}, groupData);
nodes.each(function(i, node) {
node = $(node);
nodeData = {
index: i,
expanded: that._expanded(node)
};
updateNodeHtml(node);
that._updateNodeClasses(node, groupData, nodeData);
// iterate over child nodes
that._group(node);
});
},
_checkboxes: function() {
var options = this.options, checkboxOptions = options.checkboxes, checkboxTemplate;
if (checkboxOptions || options.checkboxTemplate) {
if (options.checkboxTemplate) {
checkboxTemplate = options.checkboxTemplate;
} else {
checkboxTemplate = "<input type='checkbox' #= (item.enabled === false) ? 'disabled' : '' # #= item.checked ? 'checked' : '' #";
if (checkboxOptions.name) {
checkboxTemplate += " name='" + checkboxOptions.name + "'";
}
checkboxTemplate += " />";
}
checkboxOptions = extend({
template: checkboxTemplate
}, options.checkboxes);
if (typeof checkboxOptions.template == STRING) {
checkboxOptions.template = template(checkboxOptions.template);
}
options.checkboxes = checkboxOptions;
}
},
_updateNodeClasses: function(node, groupData, nodeData) {
var wrapper = node.children("div"), group = node.children("ul"), templates = this.templates;
if (node.hasClass("k-treeview")) {
return;
}
nodeData = nodeData || {};
nodeData.expanded = typeof nodeData.expanded != UNDEFINED ? nodeData.expanded : this._expanded(node);
nodeData.index = typeof nodeData.index != UNDEFINED ? nodeData.index : node.index();
nodeData.enabled = typeof nodeData.enabled != UNDEFINED ? nodeData.enabled : !wrapper.children(".k-in").hasClass("k-state-disabled");
groupData = groupData || {};
groupData.firstLevel = typeof groupData.firstLevel != UNDEFINED ? groupData.firstLevel : node.parent().parent().hasClass(KTREEVIEW);
groupData.length = typeof groupData.length != UNDEFINED ? groupData.length : node.parent().children().length;
// li
node.removeClass("k-first k-last").addClass(templates.wrapperCssClass(groupData, nodeData));
// div
wrapper.removeClass("k-top k-mid k-bot").addClass(templates.cssClass(groupData, nodeData));
// span
wrapper.children(".k-in").removeClass("k-in k-state-default k-state-disabled").addClass(templates.textClass(nodeData));
// toggle button
if (group.length || node.attr("data-hasChildren") == "true") {
wrapper.children(".k-icon").removeClass("k-plus k-minus k-plus-disabled k-minus-disabled").addClass(templates.toggleButtonClass(nodeData));
group.addClass("k-group");
}
},
_processNodes: function(nodes, callback) {
var that = this;
that.element.find(nodes).each(function(index, item) {
callback.call(that, index, $(item).closest(NODE));
});
},
dataItem: function(node) {
var uid = $(node).closest(NODE).attr(kendo.attr("uid")), dataSource = this.dataSource;
return dataSource && dataSource.getByUid(uid);
},
_insertNode: function(nodeData, index, parentNode, insertCallback, collapsed) {
var that = this, group = subGroup(parentNode), updatedGroupLength = group.children().length + 1, childrenData, groupData = {
firstLevel: parentNode.hasClass(KTREEVIEW),
expanded: !collapsed,
length: updatedGroupLength
}, node, i, item, nodeHtml = "", append = function(item, group) {
item.appendTo(group);
};
for (i = 0; i < nodeData.length; i++) {
item = nodeData[i];
item.index = index + i;
nodeHtml += that._renderItem({
group: groupData,
item: item
});
}
node = $(nodeHtml);
if (!node.length) {
return;
}
if (!group.length) {
group = $(that._renderGroup({
group: groupData
})).appendTo(parentNode);
}
insertCallback(node, group);
if (parentNode.hasClass("k-item")) {
updateNodeHtml(parentNode);
that._updateNodeClasses(parentNode);
}
that._updateNodeClasses(node.prev().first());
that._updateNodeClasses(node.next().last());
// render sub-nodes
for (i = 0; i < nodeData.length; i++) {
item = nodeData[i];
if (item.hasChildren) {
childrenData = item.children.data();
if (childrenData.length) {
that._insertNode(childrenData, item.index, node.eq(i), append, !that._expanded(node.eq(i)));
}
}
}
return node;
},
_updateNode: function(field, items) {
var that = this, i, node, item, isChecked, isCollapsed, context = {
treeview: that.options,
item: item
};
function setChecked() {
that.dataItem(this).set(CHECKED, isChecked);
}
if (field == "selected") {
item = items[0];
node = that.findByUid(item.uid).find(".k-in:first").removeClass("k-state-hover").toggleClass("k-state-selected", item[field]).end();
if (item[field]) {
that.current(node);
node.attr(ARIASELECTED, true);
} else {
node.attr(ARIASELECTED, false);
}
} else {
for (i = 0; i < items.length; i++) {
context.item = item = items[i];
if (field == "spriteCssClass" || field == "imageUrl" || $.inArray(field, that.options.dataTextField) >= 0) {
that.findByUid(item.uid).find(">div>.k-in").html(that.templates.itemContent(context));
} else if (field == CHECKED) {
node = that.findByUid(item.uid);
isChecked = item[field];
node.children("div").find(".k-checkbox :checkbox").prop(CHECKED, item[field]).data("indeterminate", false).prop("indeterminate", false);
if (that.options.checkboxes.checkChildren) {
node.find(".k-checkbox :checkbox").each(setChecked);
that._updateIndeterminate(node);
}
} else if (field == "expanded") {
that._toggle(that.findByUid(item.uid), item, item[field]);
} else if (field == "enabled") {
node = that.findByUid(item.uid);
node.find(".k-checkbox :checkbox").prop("disabled", !item[field]);
isCollapsed = !nodeContents(node).is(VISIBLE);
node.removeAttr(ARIADISABLED);
if (!item[field]) {
if (item.selected) {
item.set("selected", false);
}
if (item.expanded) {
item.set("expanded", false);
}
isCollapsed = true;
node.removeAttr(ARIASELECTED).attr(ARIADISABLED, true);
}
that._updateNodeClasses(node, {}, {
enabled: item[field],
expanded: !isCollapsed
});
}
}
}
},
refresh: function(e) {
var that = this, parentNode = that.wrapper, node = e.node, action = e.action, items = e.items, index = e.index, options = that.options, loadOnDemand = options.loadOnDemand, checkChildren = options.checkboxes && options.checkboxes.checkChildren, i;
function append(items, parentNode) {
var group = subGroup(parentNode), children = group.children(), collapsed = !that._expanded(parentNode);
if (typeof index == UNDEFINED) {
index = children.length;
}
that._insertNode(items, index, parentNode, function(item, group) {
// insert node into DOM
if (index == children.length) {
item.appendTo(group);
} else {
item.insertBefore(children.eq(index));
}
}, collapsed);
if (that._expanded(parentNode)) {
that._updateNodeClasses(parentNode);
subGroup(parentNode).css("display", "block");
}
}
if (e.field) {
return that._updateNode(e.field, items);
}
if (node) {
parentNode = that.findByUid(node.uid);
that._progress(parentNode, false);
}
if (checkChildren && action != "remove" && node && node.checked) {
for (i = 0; i < items.length; i++) {
items[i].checked = true;
}
}
if (action == "add") {
append(items, parentNode);
} else if (action == "remove") {
that._remove(that.findByUid(items[0].uid), false);
} else {
if (node) {
subGroup(parentNode).empty();
append(items, parentNode);
} else {
that.root = that.wrapper.html(that._renderGroup({
items: items,
group: {
firstLevel: true,
expanded: true
}
})).children("ul");
}
}
for (i = 0; i < items.length; i++) {
if (!loadOnDemand || items[i].expanded) {
items[i].load();
}
}
that.trigger(DATABOUND, {
node: node ? parentNode : undefined
});
},
expand: function(nodes) {
this._processNodes(nodes, function(index, item) {
this.toggle(item, true);
});
},
collapse: function(nodes) {
this._processNodes(nodes, function(index, item) {
this.toggle(item, false);
});
},
enable: function(nodes, enable) {
enable = arguments.length == 2 ? !!enable : true;
this._processNodes(nodes, function(index, item) {
this.dataItem(item).set("enabled", enable);
});
},
current: function(node) {
var that = this, current = that._current, element = that.element, id = that._ariaId;
if (arguments.length > 0 && node && node.length) {
if (current) {
if (current[0].id === id) {
current.removeAttr("id");
}
current.find(".k-in:first").removeClass("k-state-focused");
}
current = that._current = $(node, element).closest(NODE);
current.find(".k-in:first").addClass("k-state-focused");
id = current[0].id || id;
if (id) {
that.wrapper.removeAttr("aria-activedescendant");
current.attr("id", id);
that.wrapper.attr("aria-activedescendant", id);
}
return;
}
if (!current) {
current = that._nextVisible($());
}
return current;
},
select: function(node) {
var that = this, element = that.element;
if (!arguments.length) {
return element.find(".k-state-selected").closest(NODE);
}
node = $(node, element).closest(NODE);
element.find(".k-state-selected").each(function() {
var dataItem = that.dataItem(this);
dataItem.set("selected", false);
delete dataItem.selected;
});
if (node.length) {
that.dataItem(node).set("selected", true);
}
that.trigger(CHANGE);
},
_toggle: function(node, dataItem, expand) {
var that = this, options = that.options, contents = nodeContents(node), direction = expand ? "expand" : "collapse", animation = options.animation[direction], loaded;
if (contents.data("animating")) {
return;
}
if (!that._trigger(direction, node)) {
that._expanded(node, expand);
loaded = dataItem && dataItem.loaded();
if (loaded && contents.children().length > 0) {
that._updateNodeClasses(node, {}, {
expanded: expand
});
if (contents.css("display") == (expand ? "block" : "none")) {
return;
}
if (!expand) {
contents.css("height", contents.height()).css("height");
}
contents.kendoStop(true, true).kendoAnimate(extend({
reset: true
}, animation, {
complete: function() {
if (expand) {
contents.css("height", "");
}
}
}));
} else if (!loaded || loaded && expand) {
if (options.loadOnDemand) {
that._progress(node, true);
}
contents.remove();
dataItem.load();
}
}
},
toggle: function(node, expand) {
node = $(node);
if (!nodeIcon(node).is(".k-minus,.k-plus,.k-minus-disabled,.k-plus-disabled")) {
return;
}
if (arguments.length == 1) {
expand = !this._expanded(node);
}
this._expanded(node, expand);
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.element.off(NS);
if (that.dragging) {
that.dragging.destroy();
}
kendo.destroy(that.element);
},
_expanded: function(node, value) {
var expandedAttr = kendo.attr("expanded"), dataItem = this.dataItem(node);
if (arguments.length == 1) {
return node.attr(expandedAttr) === "true" || dataItem && dataItem.expanded;
}
if (nodeContents(node).data("animating")) {
return;
}
if (dataItem) {
dataItem.set("expanded", value);
}
if (value) {
node.attr(expandedAttr, "true");
node.attr("aria-expanded", "true");
} else {
node.removeAttr(expandedAttr);
node.attr("aria-expanded", "false");
}
},
_progress: function(node, showProgress) {
var element = this.element;
if (arguments.length == 1) {
showProgress = node;
if (showProgress) {
element.html(this.templates.loading);
} else {
element.empty();
}
} else {
nodeIcon(node).toggleClass("k-loading", showProgress);
}
},
text: function(node, text) {
var dataItem = this.dataItem(node), fieldBindings = this.options[bindings.text], level = dataItem.level(), length = fieldBindings.length, field = fieldBindings[Math.min(level, length - 1)];
if (text) {
dataItem.set(field, text);
} else {
return dataItem[field];
}
},
_objectOrSelf: function(node) {
return $(node).closest("[data-role=treeview]").data("kendoTreeView") || this;
},
_dataSourceMove: function(nodeData, group, parentNode, callback) {
var referenceDataItem, destTreeview = this._objectOrSelf(parentNode || group), destDataSource = destTreeview.dataSource;
if (parentNode && parentNode[0] != destTreeview.element[0]) {
referenceDataItem = destTreeview.dataItem(parentNode);
if (!referenceDataItem.loaded()) {
destTreeview._progress(parentNode, true);
referenceDataItem.load();
}
if (parentNode != this.root) {
destDataSource = referenceDataItem.children;
if (!destDataSource || !(destDataSource instanceof HierarchicalDataSource)) {
referenceDataItem._initChildren();
destDataSource = referenceDataItem.children;
}
}
}
nodeData = this._toObservableData(nodeData);
return callback.call(this, destDataSource, nodeData);
},
_toObservableData: function(node) {
var dataItem = node, dataSource, uid;
if (node instanceof window.jQuery || isDomElement(node)) {
dataSource = this._objectOrSelf(node).dataSource, uid = $(node).attr(kendo.attr("uid"));
dataItem = dataSource.getByUid(uid);
if (dataItem) {
dataItem = dataSource.remove(dataItem);
}
}
return dataItem;
},
_insert: function(data, model, index) {
if (!(model instanceof kendo.data.ObservableArray)) {
if (!isArray(model)) {
model = [ model ];
}
} else {
// items will be converted to new Node instances
model = model.toJSON();
}
data.splice.apply(data, [ index, 0 ].concat(model));
return this.findByUid(data[index].uid);
},
insertAfter: function(nodeData, referenceNode) {
var group = referenceNode.parent(), parentNode;
if (group.parent().is("li")) {
parentNode = group.parent();
}
return this._dataSourceMove(nodeData, group, parentNode, function(dataSource, model) {
return this._insert(dataSource.data(), model, referenceNode.index() + 1);
});
},
insertBefore: function(nodeData, referenceNode) {
var group = referenceNode.parent(), parentNode;
if (group.parent().is("li")) {
parentNode = group.parent();
}
return this._dataSourceMove(nodeData, group, parentNode, function(dataSource, model) {
return this._insert(dataSource.data(), model, referenceNode.index());
});
},
append: function(nodeData, parentNode) {
var that = this, group = that.root;
if (parentNode) {
group = subGroup(parentNode);
}
return that._dataSourceMove(nodeData, group, parentNode, function(dataSource, model) {
function add() {
var data = dataSource.data(), index = Math.max(data.length, 0);
if (parentNode) {
that._expanded(parentNode, true);
}
return that._insert(data, model, index);
}
if (!dataSource.data()) {
dataSource.one(CHANGE, add);
return null;
} else {
return add();
}
});
},
_remove: function(node, keepData) {
var that = this, parentNode, prevSibling, nextSibling;
node = $(node, that.element);
parentNode = node.parent().parent();
prevSibling = node.prev();
nextSibling = node.next();
node[keepData ? "detach" : "remove"]();
if (parentNode.hasClass("k-item")) {
updateNodeHtml(parentNode);
that._updateNodeClasses(parentNode);
}
that._updateNodeClasses(prevSibling);
that._updateNodeClasses(nextSibling);
return node;
},
remove: function(node) {
var dataItem = this.dataItem(node);
if (dataItem) {
this.dataSource.remove(dataItem);
}
},
detach: function(node) {
return this._remove(node, true);
},
findByText: function(text) {
return $(this.element).find(".k-in").filter(function(i, element) {
return $(element).text() == text;
}).closest(NODE);
},
findByUid: function(uid) {
return this.element.find(".k-item[" + kendo.attr("uid") + "=" + uid + "]");
},
_renderItem: function(options) {
if (!options.group) {
options.group = {};
}
options.treeview = this.options;
options.r = this.templates;
return this.templates.item(options);
},
_renderGroup: function(options) {
var that = this;
options.renderItems = function(options) {
var html = "", i = 0, items = options.items, len = items ? items.length : 0, group = options.group;
group.length = len;
for (;i < len; i++) {
options.group = group;
options.item = items[i];
options.item.index = i;
html += that._renderItem(options);
}
return html;
};
options.r = that.templates;
return that.templates.group(options);
}
});
function TreeViewDragAndDrop(treeview) {
var that = this;
that.treeview = treeview;
that.hovered = treeview.element;
that._draggable = new ui.Draggable(treeview.element, {
filter: "div:not(.k-state-disabled) .k-in",
hint: function(node) {
return treeview.templates.dragClue({
item: treeview.dataItem(node),
treeview: treeview.options
});
},
cursorOffset: {
left: 10,
top: kendo.support.touch || kendo.support.pointers ? -40 / kendo.support.zoomLevel() : 10
},
dragstart: proxy(that.dragstart, that),
dragcancel: proxy(that.dragcancel, that),
drag: proxy(that.drag, that),
dragend: proxy(that.dragend, that)
});
}
TreeViewDragAndDrop.prototype = {
_removeTouchHover: function() {
var that = this;
if (kendo.support.touch && that.hovered) {
that.hovered.find("." + KSTATEHOVER).removeClass(KSTATEHOVER);
that.hovered = false;
}
},
_hintStatus: function(newStatus) {
var statusElement = this._draggable.hint.find(".k-drag-status")[0];
if (newStatus) {
statusElement.className = "k-icon k-drag-status " + newStatus;
} else {
return $.trim(statusElement.className.replace(/k-(icon|drag-status)/g, ""));
}
},
dragstart: function(e) {
var that = this, treeview = that.treeview, sourceNode = that.sourceNode = e.currentTarget.closest(NODE);
if (treeview.trigger(DRAGSTART, {
sourceNode: sourceNode[0]
})) {
e.preventDefault();
}
that.dropHint = $("<div class='k-drop-hint' />").css(VISIBILITY, "hidden").appendTo(treeview.element);
},
drag: function(e) {
var that = this, treeview = that.treeview, sourceNode = that.sourceNode, dropTarget = that.dropTarget = $(kendo.eventTarget(e)), statusClass, closestTree = dropTarget.closest(".k-treeview"), hoveredItem, hoveredItemPos, itemHeight, itemTop, itemContent, delta, insertOnTop, insertOnBottom, addChild;
if (!closestTree.length) {
// dragging node outside of treeview
statusClass = "k-denied";
that._removeTouchHover();
} else if ($.contains(sourceNode[0], dropTarget[0])) {
// dragging node within itself
statusClass = "k-denied";
} else {
// moving or reordering node
statusClass = "k-insert-middle";
hoveredItem = dropTarget.closest(".k-top,.k-mid,.k-bot");
if (hoveredItem.length) {
itemHeight = hoveredItem.outerHeight();
itemTop = kendo.getOffset(hoveredItem).top;
itemContent = dropTarget.closest(".k-in");
delta = itemHeight / (itemContent.length > 0 ? 4 : 2);
insertOnTop = e.y.location < itemTop + delta;
insertOnBottom = itemTop + itemHeight - delta < e.y.location;
that._removeTouchHover();
addChild = itemContent.length && !insertOnTop && !insertOnBottom;
that.hovered = addChild ? closestTree : false;
that.dropHint.css(VISIBILITY, addChild ? "hidden" : "visible");
itemContent.toggleClass(KSTATEHOVER, addChild);
if (addChild) {
statusClass = "k-add";
} else {
hoveredItemPos = hoveredItem.position();
hoveredItemPos.top += insertOnTop ? 0 : itemHeight;
that.dropHint.css(hoveredItemPos)[insertOnTop ? "prependTo" : "appendTo"](dropTarget.closest(NODE).children("div:first"));
if (insertOnTop && hoveredItem.hasClass("k-top")) {
statusClass = "k-insert-top";
}
if (insertOnBottom && hoveredItem.hasClass("k-bot")) {
statusClass = "k-insert-bottom";
}
}
} else if (dropTarget[0] != that.dropHint[0]) {
if (closestTree[0] != treeview.element[0]) {
// moving node to different treeview without children
statusClass = "k-add";
} else {
statusClass = "k-denied";
}
}
}
treeview.trigger(DRAG, {
sourceNode: sourceNode[0],
dropTarget: dropTarget[0],
pageY: e.y.location,
pageX: e.x.location,
statusClass: statusClass.substring(2),
setStatusClass: function(value) {
statusClass = value;
}
});
if (statusClass.indexOf("k-insert") !== 0) {
that.dropHint.css(VISIBILITY, "hidden");
}
that._hintStatus(statusClass);
},
dragcancel: function() {
this.dropHint.remove();
},
dragend: function() {
var that = this, treeview = that.treeview, dropPosition = "over", sourceNode = that.sourceNode, destinationNode, dropHint = that.dropHint, dropTarget = that.dropTarget, valid, dropPrevented;
if (dropHint.css(VISIBILITY) == "visible") {
dropPosition = dropHint.prevAll(".k-in").length > 0 ? "after" : "before";
destinationNode = dropHint.closest(NODE);
} else if (dropTarget) {
destinationNode = dropTarget.closest(NODE);
// moving node to root element
if (!destinationNode.length) {
destinationNode = dropTarget.closest(".k-treeview");
}
}
valid = that._hintStatus() != "k-denied";
dropPrevented = treeview.trigger(DROP, {
sourceNode: sourceNode[0],
destinationNode: destinationNode[0],
valid: valid,
setValid: function(newValid) {
valid = newValid;
},
dropTarget: dropTarget[0],
dropPosition: dropPosition
});
dropHint.remove();
that._removeTouchHover();
if (!valid || dropPrevented) {
that._draggable.dropped = valid;
return;
}
that._draggable.dropped = true;
// perform reorder / move
if (dropPosition == "over") {
sourceNode = treeview.append(sourceNode, destinationNode);
} else if (dropPosition == "before") {
sourceNode = treeview.insertBefore(sourceNode, destinationNode);
} else if (dropPosition == "after") {
sourceNode = treeview.insertAfter(sourceNode, destinationNode);
}
treeview.trigger(DRAGEND, {
sourceNode: sourceNode && sourceNode[0],
destinationNode: destinationNode[0],
dropPosition: dropPosition
});
},
destroy: function() {
this._draggable.destroy();
}
};
ui.plugin(TreeView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, Draggable = kendo.ui.Draggable, extend = $.extend, format = kendo.format, parse = kendo.parseFloat, proxy = $.proxy, isArray = $.isArray, math = Math, support = kendo.support, pointers = support.pointers, CHANGE = "change", SLIDE = "slide", NS = ".slider", MOUSE_DOWN = "touchstart" + NS + " mousedown" + NS, TRACK_MOUSE_DOWN = pointers ? "MSPointerDown" + NS : "mousedown" + NS + " touchstart" + NS, MOUSE_UP = "touchend" + NS + " mouseup" + NS, MOVE_SELECTION = "moveSelection", KEY_DOWN = "keydown" + NS, CLICK = "click" + NS, MOUSE_OVER = "mouseover" + NS, FOCUS = "focus" + NS, BLUR = "blur" + NS, DRAG_HANDLE = ".k-draghandle", TRACK_SELECTOR = ".k-slider-track", TICK_SELECTOR = ".k-tick", STATE_SELECTED = "k-state-selected", STATE_DEFAULT = "k-state-default", STATE_DISABLED = "k-state-disabled", PRECISION = 3, DISABLED = "disabled", UNDEFINED = "undefined", TABINDEX = "tabindex", getTouches = kendo.getTouches;
var SliderBase = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
options = that.options;
that._distance = options.max - options.min;
that._isHorizontal = options.orientation == "horizontal";
that._isRtl = that._isHorizontal && kendo.support.isRtl(element);
that._position = that._isHorizontal ? "left" : "bottom";
that._size = that._isHorizontal ? "width" : "height";
that._outerSize = that._isHorizontal ? "outerWidth" : "outerHeight";
options.tooltip.format = options.tooltip.enabled ? options.tooltip.format || "{0}" : "{0}";
that._createHtml();
that.wrapper = that.element.closest(".k-slider");
that._trackDiv = that.wrapper.find(TRACK_SELECTOR);
that._setTrackDivWidth();
that._maxSelection = that._trackDiv[that._size]();
var sizeBetweenTicks = that._maxSelection / ((options.max - options.min) / options.smallStep);
var pixelWidths = that._calculateItemsWidth(math.floor(that._distance / options.smallStep));
if (options.tickPlacement != "none" && sizeBetweenTicks >= 2) {
that._trackDiv.before(createSliderItems(options, that._distance));
that._setItemsWidth(pixelWidths);
that._setItemsTitle();
that._setItemsLargeTick();
}
that._calculateSteps(pixelWidths);
that._tabindex(that.wrapper.find(DRAG_HANDLE));
that[options.enabled ? "enable" : "disable"]();
var rtlDirectionSign = kendo.support.isRtl(that.wrapper) ? -1 : 1;
that._keyMap = {
37: step(-1 * rtlDirectionSign * options.smallStep),
// left arrow
40: step(-options.smallStep),
// down arrow
39: step(+1 * rtlDirectionSign * options.smallStep),
// right arrow
38: step(+options.smallStep),
// up arrow
35: setValue(options.max),
// end
36: setValue(options.min),
// home
33: step(+options.largeStep),
// page up
34: step(-options.largeStep)
};
kendo.notify(that);
},
events: [ CHANGE, SLIDE ],
options: {
enabled: true,
min: 0,
max: 10,
smallStep: 1,
largeStep: 5,
orientation: "horizontal",
tickPlacement: "both",
tooltip: {
enabled: true,
format: "{0}"
}
},
_setTrackDivWidth: function() {
var that = this, trackDivPosition = parseFloat(that._trackDiv.css(that._isRtl ? "right" : that._position), 10) * 2;
that._trackDiv[that._size](that.wrapper[that._size]() - 2 - trackDivPosition);
},
_setItemsWidth: function(pixelWidths) {
var that = this, options = that.options, first = 0, last = pixelWidths.length - 1, items = that.wrapper.find(TICK_SELECTOR), i, paddingTop = 0, bordersWidth = 2, count = items.length, selection = 0;
for (i = 0; i < count - 2; i++) {
$(items[i + 1])[that._size](pixelWidths[i]);
}
if (that._isHorizontal) {
$(items[first]).addClass("k-first")[that._size](pixelWidths[last - 1]);
$(items[last]).addClass("k-last")[that._size](pixelWidths[last]);
} else {
$(items[last]).addClass("k-first")[that._size](pixelWidths[last]);
$(items[first]).addClass("k-last")[that._size](pixelWidths[last - 1]);
}
if (that._distance % options.smallStep !== 0 && !that._isHorizontal) {
for (i = 0; i < pixelWidths.length; i++) {
selection += pixelWidths[i];
}
paddingTop = that._maxSelection - selection;
paddingTop += parseFloat(that._trackDiv.css(that._position), 10) + bordersWidth;
that.wrapper.find(".k-slider-items").css("padding-top", paddingTop);
}
},
_setItemsTitle: function() {
var that = this, options = that.options, items = that.wrapper.find(TICK_SELECTOR), titleNumber = options.min, count = items.length, i = that._isHorizontal && !that._isRtl ? 0 : count - 1, limit = that._isHorizontal && !that._isRtl ? count : -1, increment = that._isHorizontal && !that._isRtl ? 1 : -1;
for (;i - limit !== 0; i += increment) {
$(items[i]).attr("title", format(options.tooltip.format, round(titleNumber)));
titleNumber += options.smallStep;
}
},
_setItemsLargeTick: function() {
var that = this, options = that.options, i, items = that.wrapper.find(TICK_SELECTOR), item = {}, step = round(options.largeStep / options.smallStep);
if (1e3 * options.largeStep % (1e3 * options.smallStep) === 0) {
if (that._isHorizontal && !that._isRtl) {
for (i = 0; i < items.length; i = round(i + step)) {
item = $(items[i]);
item.addClass("k-tick-large").html("<span class='k-label'>" + item.attr("title") + "</span>");
}
} else {
for (i = items.length - 1; i >= 0; i = round(i - step)) {
item = $(items[i]);
item.addClass("k-tick-large").html("<span class='k-label'>" + item.attr("title") + "</span>");
if (!that._isRtl) {
if (i !== 0 && i !== items.length - 1) {
item.css("line-height", item[that._size]() + "px");
}
}
}
}
}
},
_calculateItemsWidth: function(itemsCount) {
var that = this, options = that.options, trackDivSize = parseFloat(that._trackDiv.css(that._size)) + 1, pixelStep = trackDivSize / that._distance, itemWidth, pixelWidths, i;
if (that._distance / options.smallStep - math.floor(that._distance / options.smallStep) > 0) {
trackDivSize -= that._distance % options.smallStep * pixelStep;
}
itemWidth = trackDivSize / itemsCount;
pixelWidths = [];
for (i = 0; i < itemsCount - 1; i++) {
pixelWidths[i] = itemWidth;
}
pixelWidths[itemsCount - 1] = pixelWidths[itemsCount] = itemWidth / 2;
return that._roundWidths(pixelWidths);
},
_roundWidths: function(pixelWidthsArray) {
var balance = 0, count = pixelWidthsArray.length, i;
for (i = 0; i < count; i++) {
balance += pixelWidthsArray[i] - math.floor(pixelWidthsArray[i]);
pixelWidthsArray[i] = math.floor(pixelWidthsArray[i]);
}
balance = math.round(balance);
return this._addAdditionalSize(balance, pixelWidthsArray);
},
_addAdditionalSize: function(additionalSize, pixelWidthsArray) {
if (additionalSize === 0) {
return pixelWidthsArray;
}
//set step size
var step = parseFloat(pixelWidthsArray.length - 1) / parseFloat(additionalSize == 1 ? additionalSize : additionalSize - 1), i;
for (i = 0; i < additionalSize; i++) {
pixelWidthsArray[parseInt(math.round(step * i), 10)] += 1;
}
return pixelWidthsArray;
},
_calculateSteps: function(pixelWidths) {
var that = this, options = that.options, val = options.min, selection = 0, itemsCount = math.ceil(that._distance / options.smallStep), i = 1, lastItem;
itemsCount += that._distance / options.smallStep % 1 === 0 ? 1 : 0;
pixelWidths.splice(0, 0, pixelWidths[itemsCount - 2] * 2);
pixelWidths.splice(itemsCount - 1, 1, pixelWidths.pop() * 2);
that._pixelSteps = [ selection ];
that._values = [ val ];
if (itemsCount === 0) {
return;
}
while (i < itemsCount) {
selection += (pixelWidths[i - 1] + pixelWidths[i]) / 2;
that._pixelSteps[i] = selection;
that._values[i] = val += options.smallStep;
i++;
}
lastItem = that._distance % options.smallStep === 0 ? itemsCount - 1 : itemsCount;
that._pixelSteps[lastItem] = that._maxSelection;
that._values[lastItem] = options.max;
if (that._isRtl) {
that._pixelSteps.reverse();
that._values.reverse();
}
},
_getValueFromPosition: function(mousePosition, dragableArea) {
var that = this, options = that.options, step = math.max(options.smallStep * (that._maxSelection / that._distance), 0), position = 0, halfStep = step / 2, i;
if (that._isHorizontal) {
position = mousePosition - dragableArea.startPoint;
if (that._isRtl) {
position = that._maxSelection - position;
}
} else {
position = dragableArea.startPoint - mousePosition;
}
if (that._maxSelection - (parseInt(that._maxSelection % step, 10) - 3) / 2 < position) {
return options.max;
}
for (i = 0; i < that._pixelSteps.length; i++) {
if (math.abs(that._pixelSteps[i] - position) - 1 <= halfStep) {
return round(that._values[i]);
}
}
},
_getFormattedValue: function(val, drag) {
var that = this, html = "", tooltip = that.options.tooltip, tooltipTemplate, selectionStart, selectionEnd;
if (isArray(val)) {
selectionStart = val[0];
selectionEnd = val[1];
} else if (drag && drag.type) {
selectionStart = drag.selectionStart;
selectionEnd = drag.selectionEnd;
}
if (drag) {
tooltipTemplate = drag.tooltipTemplate;
}
if (!tooltipTemplate && tooltip.template) {
tooltipTemplate = kendo.template(tooltip.template);
}
if (isArray(val) || drag && drag.type) {
if (tooltipTemplate) {
html = tooltipTemplate({
selectionStart: selectionStart,
selectionEnd: selectionEnd
});
} else {
selectionStart = format(tooltip.format, selectionStart);
selectionEnd = format(tooltip.format, selectionEnd);
html = selectionStart + " - " + selectionEnd;
}
} else {
if (drag) {
drag.val = val;
}
if (tooltipTemplate) {
html = tooltipTemplate({
value: val
});
} else {
html = format(tooltip.format, val);
}
}
return html;
},
_getDraggableArea: function() {
var that = this, offset = kendo.getOffset(that._trackDiv);
return {
startPoint: that._isHorizontal ? offset.left : offset.top + that._maxSelection,
endPoint: that._isHorizontal ? offset.left + that._maxSelection : offset.top
};
},
_createHtml: function() {
var that = this, element = that.element, options = that.options, inputs = element.find("input");
if (inputs.length == 2) {
inputs.eq(0).val(options.selectionStart);
inputs.eq(1).val(options.selectionEnd);
} else {
element.val(options.value);
}
element.wrap(createWrapper(options, element, that._isHorizontal)).hide();
if (options.showButtons) {
element.before(createButton(options, "increase", that._isHorizontal)).before(createButton(options, "decrease", that._isHorizontal));
}
element.before(createTrack(options, element));
},
_focus: function(e) {
var that = this, target = e.target, val = that.value(), drag = that._drag;
if (!drag) {
if (target == that.wrapper.find(DRAG_HANDLE).eq(0)[0]) {
drag = that._firstHandleDrag;
that._activeHandle = 0;
} else {
drag = that._lastHandleDrag;
that._activeHandle = 1;
}
val = val[that._activeHandle];
}
$(target).addClass(STATE_SELECTED);
if (drag) {
that._activeHandleDrag = drag;
drag.selectionStart = that.options.selectionStart;
drag.selectionEnd = that.options.selectionEnd;
drag._updateTooltip(val);
}
},
_focusWithMouse: function(e) {
var that = this, target = $(e.target), idx = target.is(DRAG_HANDLE) ? target.index() : 0;
window.setTimeout(function() {
that.wrapper.find(DRAG_HANDLE)[idx == 2 ? 1 : 0].focus();
}, 1);
that._setTooltipTimeout();
},
_blur: function(e) {
var that = this, drag = that._activeHandleDrag;
$(e.target).removeClass(STATE_SELECTED);
if (drag) {
drag._removeTooltip();
delete that._activeHandleDrag;
delete that._activeHandle;
}
},
_setTooltipTimeout: function() {
var that = this;
that._tooltipTimeout = window.setTimeout(function() {
var drag = that._drag || that._activeHandleDrag;
if (drag) {
drag._removeTooltip();
}
}, 300);
},
_clearTooltipTimeout: function() {
var that = this;
window.clearTimeout(this._tooltipTimeout);
var drag = that._drag || that._activeHandleDrag;
if (drag && drag.tooltipDiv) {
drag.tooltipDiv.stop(true, false).css("opacity", 1);
}
}
});
function createWrapper(options, element, isHorizontal) {
var orientationCssClass = isHorizontal ? " k-slider-horizontal" : " k-slider-vertical", style = options.style ? options.style : element.attr("style"), cssClasses = element.attr("class") ? " " + element.attr("class") : "", tickPlacementCssClass = "";
if (options.tickPlacement == "bottomRight") {
tickPlacementCssClass = " k-slider-bottomright";
} else if (options.tickPlacement == "topLeft") {
tickPlacementCssClass = " k-slider-topleft";
}
style = style ? " style='" + style + "'" : "";
return "<div class='k-widget k-slider" + orientationCssClass + cssClasses + "'" + style + ">" + "<div class='k-slider-wrap" + (options.showButtons ? " k-slider-buttons" : "") + tickPlacementCssClass + "'></div></div>";
}
function createButton(options, type, isHorizontal) {
var buttonCssClass = "";
if (type == "increase") {
buttonCssClass = isHorizontal ? "k-i-arrow-e" : "k-i-arrow-n";
} else {
buttonCssClass = isHorizontal ? "k-i-arrow-w" : "k-i-arrow-s";
}
return "<a class='k-button k-button-" + type + "'><span class='k-icon " + buttonCssClass + "' title='" + options[type + "ButtonTitle"] + "'>" + options[type + "ButtonTitle"] + "</span></a>";
}
function createSliderItems(options, distance) {
var result = "<ul class='k-reset k-slider-items'>", count = math.floor(round(distance / options.smallStep)) + 1, i;
for (i = 0; i < count; i++) {
result += "<li class='k-tick' role='presentation'> </li>";
}
result += "</ul>";
return result;
}
function createTrack(options, element) {
var dragHandleCount = element.is("input") ? 1 : 2, firstDragHandleTitle = dragHandleCount == 2 ? options.leftDragHandleTitle : options.dragHandleTitle;
return "<div class='k-slider-track'><div class='k-slider-selection'><!-- --></div>" + "<a href='#' class='k-draghandle' title='" + firstDragHandleTitle + "' role='slider' aria-valuemin='" + options.min + "' aria-valuemax='" + options.max + "' aria-valuenow='" + (dragHandleCount > 1 ? options.selectionStart || options.min : options.value || options.min) + "'>Drag</a>" + (dragHandleCount > 1 ? "<a href='#' class='k-draghandle' title='" + options.rightDragHandleTitle + "'role='slider' aria-valuemin='" + options.min + "' aria-valuemax='" + options.max + "' aria-valuenow='" + (options.selectionEnd || options.max) + "'>Drag</a>" : "") + "</div>";
}
function step(stepValue) {
return function(value) {
return value + stepValue;
};
}
function setValue(value) {
return function() {
return value;
};
}
function formatValue(value) {
return (value + "").replace(".", kendo.cultures.current.numberFormat["."]);
}
function round(value) {
value = parseFloat(value, 10);
var power = math.pow(10, PRECISION || 0);
return math.round(value * power) / power;
}
function parseAttr(element, name) {
var value = parse(element.getAttribute(name));
if (value === null) {
value = undefined;
}
return value;
}
function defined(value) {
return typeof value !== UNDEFINED;
}
var Slider = SliderBase.extend({
init: function(element, options) {
var that = this, dragHandle;
element.type = "text";
options = extend({}, {
value: parseAttr(element, "value"),
min: parseAttr(element, "min"),
max: parseAttr(element, "max"),
smallStep: parseAttr(element, "step")
}, options);
element = $(element);
if (options && options.enabled === undefined) {
options.enabled = !element.is("[disabled]");
}
SliderBase.fn.init.call(that, element, options);
options = that.options;
if (!defined(options.value) || options.value === null) {
options.value = options.min;
element.val(options.min);
}
options.value = math.max(math.min(options.value, options.max), options.min);
dragHandle = that.wrapper.find(DRAG_HANDLE);
new Slider.Selection(dragHandle, that, options);
that._drag = new Slider.Drag(dragHandle, "", that, options);
},
options: {
name: "Slider",
showButtons: true,
increaseButtonTitle: "Increase",
decreaseButtonTitle: "Decrease",
dragHandleTitle: "drag",
tooltip: {
format: "{0}"
},
value: null
},
enable: function(enable) {
var that = this, options = that.options, clickHandler, move;
that.disable();
if (enable === false) {
return;
}
that.wrapper.removeClass(STATE_DISABLED).addClass(STATE_DEFAULT);
that.wrapper.find("input").removeAttr(DISABLED);
clickHandler = function(e) {
var touch = getTouches(e)[0];
if (!touch) {
return;
}
var mousePosition = that._isHorizontal ? touch.location.pageX : touch.location.pageY, dragableArea = that._getDraggableArea(), target = $(e.target);
if (target.hasClass("k-draghandle")) {
target.addClass(STATE_SELECTED);
return;
}
that._update(that._getValueFromPosition(mousePosition, dragableArea));
that._focusWithMouse(e);
that._drag.dragstart(e);
e.preventDefault();
};
that.wrapper.find(TICK_SELECTOR + ", " + TRACK_SELECTOR).on(TRACK_MOUSE_DOWN, clickHandler).end().on(TRACK_MOUSE_DOWN, function() {
$(document.documentElement).one("selectstart", kendo.preventDefault);
});
that.wrapper.find(DRAG_HANDLE).attr(TABINDEX, 0).on(MOUSE_UP, function() {
that._setTooltipTimeout();
}).on(CLICK, function(e) {
that._focusWithMouse(e);
e.preventDefault();
}).on(FOCUS, proxy(that._focus, that)).on(BLUR, proxy(that._blur, that));
move = proxy(function(sign) {
var newVal = that._nextValueByIndex(that._valueIndex + sign * 1);
that._setValueInRange(newVal);
that._drag._updateTooltip(newVal);
}, that);
if (options.showButtons) {
var mouseDownHandler = proxy(function(e, sign) {
this._clearTooltipTimeout();
if (e.which === 1 || support.touch && e.which === 0) {
move(sign);
this.timeout = setTimeout(proxy(function() {
this.timer = setInterval(function() {
move(sign);
}, 60);
}, this), 200);
}
}, that);
that.wrapper.find(".k-button").on(MOUSE_UP, proxy(function(e) {
this._clearTimer();
that._focusWithMouse(e);
}, that)).on(MOUSE_OVER, function(e) {
$(e.currentTarget).addClass("k-state-hover");
}).on("mouseout" + NS, proxy(function(e) {
$(e.currentTarget).removeClass("k-state-hover");
this._clearTimer();
}, that)).eq(0).on(MOUSE_DOWN, proxy(function(e) {
mouseDownHandler(e, 1);
}, that)).click(false).end().eq(1).on(MOUSE_DOWN, proxy(function(e) {
mouseDownHandler(e, -1);
}, that)).click(kendo.preventDefault);
}
that.wrapper.find(DRAG_HANDLE).off(KEY_DOWN, false).on(KEY_DOWN, proxy(this._keydown, that));
options.enabled = true;
},
disable: function() {
var that = this;
that.wrapper.removeClass(STATE_DEFAULT).addClass(STATE_DISABLED);
$(that.element).prop(DISABLED, DISABLED);
that.wrapper.find(".k-button").off(MOUSE_DOWN).on(MOUSE_DOWN, kendo.preventDefault).off(MOUSE_UP).on(MOUSE_UP, kendo.preventDefault).off("mouseleave" + NS).on("mouseleave" + NS, kendo.preventDefault).off(MOUSE_OVER).on(MOUSE_OVER, kendo.preventDefault);
that.wrapper.find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(TRACK_MOUSE_DOWN);
that.wrapper.find(DRAG_HANDLE).attr(TABINDEX, -1).off(MOUSE_UP).off(KEY_DOWN).off(CLICK).off(FOCUS).off(BLUR);
that.options.enabled = false;
},
_update: function(val) {
var that = this, change = that.value() != val;
that.value(val);
if (change) {
that.trigger(CHANGE, {
value: that.options.value
});
}
},
value: function(value) {
var that = this, options = that.options;
value = round(value);
if (isNaN(value)) {
return options.value;
}
if (value >= options.min && value <= options.max) {
if (options.value != value) {
that.element.prop("value", formatValue(value));
options.value = value;
that._refreshAriaAttr(value);
that._refresh();
}
}
},
_refresh: function() {
this.trigger(MOVE_SELECTION, {
value: this.options.value
});
},
_refreshAriaAttr: function(value) {
var that = this, drag = that._drag, formattedValue;
if (drag && drag._tooltipDiv) {
formattedValue = drag._tooltipDiv.text();
} else {
formattedValue = that._getFormattedValue(value, null);
}
this.wrapper.find(DRAG_HANDLE).attr("aria-valuenow", value).attr("aria-valuetext", formattedValue);
},
_clearTimer: function() {
clearTimeout(this.timeout);
clearInterval(this.timer);
},
_keydown: function(e) {
var that = this;
if (e.keyCode in that._keyMap) {
that._clearTooltipTimeout();
that._setValueInRange(that._keyMap[e.keyCode](that.options.value));
that._drag._updateTooltip(that.value());
e.preventDefault();
}
},
_setValueInRange: function(val) {
var that = this, options = that.options;
val = round(val);
if (isNaN(val)) {
that._update(options.min);
return;
}
val = math.max(math.min(val, options.max), options.min);
that._update(val);
},
_nextValueByIndex: function(index) {
var count = this._values.length;
if (this._isRtl) {
index = count - 1 - index;
}
return this._values[math.max(0, math.min(index, count - 1))];
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.wrapper.off(NS).find(".k-button").off(NS).end().find(DRAG_HANDLE).off(NS).end().find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(NS).end();
that._drag.draggable.destroy();
}
});
Slider.Selection = function(dragHandle, that, options) {
function moveSelection(val) {
var selectionValue = val - options.min, index = that._valueIndex = math.ceil(round(selectionValue / options.smallStep)), selection = parseInt(that._pixelSteps[index], 10), selectionDiv = that._trackDiv.find(".k-slider-selection"), halfDragHanndle = parseInt(dragHandle[that._outerSize]() / 2, 10), rtlCorrection = that._isRtl ? 2 : 0;
selectionDiv[that._size](that._isRtl ? that._maxSelection - selection : selection);
dragHandle.css(that._position, selection - halfDragHanndle - rtlCorrection);
}
moveSelection(options.value);
that.bind([ CHANGE, SLIDE, MOVE_SELECTION ], function(e) {
moveSelection(parseFloat(e.value, 10));
});
};
Slider.Drag = function(dragHandle, type, owner, options) {
var that = this;
that.owner = owner;
that.options = options;
that.dragHandle = dragHandle;
that.dragHandleSize = dragHandle[owner._outerSize]();
that.type = type;
that.draggable = new Draggable(dragHandle, {
distance: 0,
dragstart: proxy(that._dragstart, that),
drag: proxy(that.drag, that),
dragend: proxy(that.dragend, that),
dragcancel: proxy(that.dragcancel, that)
});
dragHandle.click(false);
};
Slider.Drag.prototype = {
dragstart: function(e) {
// HACK to initiate click on the line
this.draggable.userEvents._start(e);
},
_dragstart: function(e) {
var that = this, owner = that.owner, options = that.options;
if (!options.enabled) {
e.preventDefault();
return;
}
owner.element.off(MOUSE_OVER);
that.dragHandle.addClass(STATE_SELECTED);
$(document.documentElement).css("cursor", "pointer");
that.dragableArea = owner._getDraggableArea();
that.step = math.max(options.smallStep * (owner._maxSelection / owner._distance), 0);
if (that.type) {
that.selectionStart = options.selectionStart;
that.selectionEnd = options.selectionEnd;
owner._setZIndex(that.type);
} else {
that.oldVal = that.val = options.value;
}
that._removeTooltip(true);
that._createTooltip();
},
_createTooltip: function() {
var that = this, owner = that.owner, tooltip = that.options.tooltip, html = "", wnd = $(window), tooltipTemplate, colloutCssClass;
if (!tooltip.enabled) {
return;
}
if (tooltip.template) {
tooltipTemplate = that.tooltipTemplate = kendo.template(tooltip.template);
}
$(".k-slider-tooltip").remove();
// if user changes window while tooltip is visible, a second one will be created
that.tooltipDiv = $("<div class='k-widget k-tooltip k-slider-tooltip'><!-- --></div>").appendTo(document.body);
html = owner._getFormattedValue(that.val || owner.value(), that);
if (!that.type) {
colloutCssClass = "k-callout-" + (owner._isHorizontal ? "s" : "e");
that.tooltipInnerDiv = "<div class='k-callout " + colloutCssClass + "'><!-- --></div>";
html += that.tooltipInnerDiv;
}
that.tooltipDiv.html(html);
that._scrollOffset = {
top: wnd.scrollTop(),
left: wnd.scrollLeft()
};
that.moveTooltip();
},
drag: function(e) {
var that = this, owner = that.owner, x = e.x.location, y = e.y.location, startPoint = that.dragableArea.startPoint, endPoint = that.dragableArea.endPoint, slideParams;
e.preventDefault();
if (owner._isHorizontal) {
if (owner._isRtl) {
that.val = that.constrainValue(x, startPoint, endPoint, x < endPoint);
} else {
that.val = that.constrainValue(x, startPoint, endPoint, x >= endPoint);
}
} else {
that.val = that.constrainValue(y, endPoint, startPoint, y <= endPoint);
}
if (that.oldVal != that.val) {
that.oldVal = that.val;
if (that.type) {
if (that.type == "firstHandle") {
if (that.val < that.selectionEnd) {
that.selectionStart = that.val;
} else {
that.selectionStart = that.selectionEnd = that.val;
}
} else {
if (that.val > that.selectionStart) {
that.selectionEnd = that.val;
} else {
that.selectionStart = that.selectionEnd = that.val;
}
}
slideParams = {
values: [ that.selectionStart, that.selectionEnd ],
value: [ that.selectionStart, that.selectionEnd ]
};
} else {
slideParams = {
value: that.val
};
}
owner.trigger(SLIDE, slideParams);
}
that._updateTooltip(that.val);
},
_updateTooltip: function(val) {
var that = this, options = that.options, tooltip = options.tooltip, html = "";
if (!tooltip.enabled) {
return;
}
if (!that.tooltipDiv) {
that._createTooltip();
}
html = that.owner._getFormattedValue(round(val), that);
if (!that.type) {
html += that.tooltipInnerDiv;
}
that.tooltipDiv.html(html);
that.moveTooltip();
},
dragcancel: function() {
this.owner._refresh();
$(document.documentElement).css("cursor", "");
return this._end();
},
dragend: function() {
var that = this, owner = that.owner;
$(document.documentElement).css("cursor", "");
if (that.type) {
owner._update(that.selectionStart, that.selectionEnd);
} else {
owner._update(that.val);
}
return that._end();
},
_end: function() {
var that = this, owner = that.owner;
owner._focusWithMouse({
target: that.dragHandle[0]
});
owner.element.on(MOUSE_OVER);
return false;
},
_removeTooltip: function(noAnimation) {
var that = this, owner = that.owner;
if (that.tooltipDiv && owner.options.tooltip.enabled && owner.options.enabled) {
if (noAnimation) {
that.tooltipDiv.remove();
that.tooltipDiv = null;
} else {
that.tooltipDiv.fadeOut("slow", function() {
$(this).remove();
that.tooltipDiv = null;
});
}
}
},
moveTooltip: function() {
var that = this, owner = that.owner, top = 0, left = 0, dragHandle = that.dragHandle, offset = kendo.getOffset(dragHandle), margin = 8, viewport = $(window), callout = that.tooltipDiv.find(".k-callout"), width = that.tooltipDiv.outerWidth(), height = that.tooltipDiv.outerHeight(), dragHandles, sdhOffset, diff, anchorSize;
if (that.type) {
dragHandles = owner.wrapper.find(DRAG_HANDLE);
offset = kendo.getOffset(dragHandles.eq(0));
sdhOffset = kendo.getOffset(dragHandles.eq(1));
if (owner._isHorizontal) {
top = sdhOffset.top;
left = offset.left + (sdhOffset.left - offset.left) / 2;
} else {
top = offset.top + (sdhOffset.top - offset.top) / 2;
left = sdhOffset.left;
}
anchorSize = dragHandles.eq(0).outerWidth() + 2 * margin;
} else {
top = offset.top;
left = offset.left;
anchorSize = dragHandle.outerWidth() + 2 * margin;
}
if (owner._isHorizontal) {
left -= parseInt((width - dragHandle[owner._outerSize]()) / 2, 10);
top -= height + callout.height() + margin;
} else {
top -= parseInt((height - dragHandle[owner._outerSize]()) / 2, 10);
left -= width + callout.width() + margin;
}
if (owner._isHorizontal) {
diff = that._flip(top, height, anchorSize, viewport.outerHeight() + that._scrollOffset.top);
top += diff;
left += that._fit(left, width, viewport.outerWidth() + that._scrollOffset.left);
} else {
diff = that._flip(left, width, anchorSize, viewport.outerWidth() + that._scrollOffset.left);
top += that._fit(top, height, viewport.outerHeight() + that._scrollOffset.top);
left += diff;
}
if (diff > 0 && callout) {
callout.removeClass();
callout.addClass("k-callout k-callout-" + (owner._isHorizontal ? "n" : "w"));
}
that.tooltipDiv.css({
top: top,
left: left
});
},
_fit: function(position, size, viewPortEnd) {
var output = 0;
if (position + size > viewPortEnd) {
output = viewPortEnd - (position + size);
}
if (position < 0) {
output = -position;
}
return output;
},
_flip: function(offset, size, anchorSize, viewPortEnd) {
var output = 0;
if (offset + size > viewPortEnd) {
output += -(anchorSize + size);
}
if (offset + output < 0) {
output += anchorSize + size;
}
return output;
},
constrainValue: function(position, min, max, maxOverflow) {
var that = this, val = 0;
if (min < position && position < max) {
val = that.owner._getValueFromPosition(position, that.dragableArea);
} else {
if (maxOverflow) {
val = that.options.max;
} else {
val = that.options.min;
}
}
return val;
}
};
kendo.ui.plugin(Slider);
var RangeSlider = SliderBase.extend({
init: function(element, options) {
var that = this, inputs = $(element).find("input"), firstInput = inputs.eq(0)[0], secondInput = inputs.eq(1)[0];
firstInput.type = "text";
secondInput.type = "text";
options = extend({}, {
selectionStart: parseAttr(firstInput, "value"),
min: parseAttr(firstInput, "min"),
max: parseAttr(firstInput, "max"),
smallStep: parseAttr(firstInput, "step")
}, {
selectionEnd: parseAttr(secondInput, "value"),
min: parseAttr(secondInput, "min"),
max: parseAttr(secondInput, "max"),
smallStep: parseAttr(secondInput, "step")
}, options);
if (options && options.enabled === undefined) {
options.enabled = !inputs.is("[disabled]");
}
SliderBase.fn.init.call(that, element, options);
options = that.options;
if (!defined(options.selectionStart) || options.selectionStart === null) {
options.selectionStart = options.min;
inputs.eq(0).val(options.min);
}
if (!defined(options.selectionEnd) || options.selectionEnd === null) {
options.selectionEnd = options.max;
inputs.eq(1).val(options.max);
}
var dragHandles = that.wrapper.find(DRAG_HANDLE);
new RangeSlider.Selection(dragHandles, that, options);
that._firstHandleDrag = new Slider.Drag(dragHandles.eq(0), "firstHandle", that, options);
that._lastHandleDrag = new Slider.Drag(dragHandles.eq(1), "lastHandle", that, options);
},
options: {
name: "RangeSlider",
leftDragHandleTitle: "drag",
rightDragHandleTitle: "drag",
tooltip: {
format: "{0}"
},
selectionStart: null,
selectionEnd: null
},
enable: function(enable) {
var that = this, options = that.options, clickHandler;
that.disable();
if (enable === false) {
return;
}
that.wrapper.removeClass(STATE_DISABLED).addClass(STATE_DEFAULT);
that.wrapper.find("input").removeAttr(DISABLED);
clickHandler = function(e) {
var touch = getTouches(e)[0];
if (!touch) {
return;
}
var mousePosition = that._isHorizontal ? touch.location.pageX : touch.location.pageY, dragableArea = that._getDraggableArea(), val = that._getValueFromPosition(mousePosition, dragableArea), target = $(e.target), idx;
if (target.hasClass("k-draghandle")) {
target.addClass(STATE_SELECTED);
return;
}
if (val < options.selectionStart) {
that._setValueInRange(val, options.selectionEnd);
that._firstHandleDrag.dragstart(e);
idx = 0;
} else if (val > that.selectionEnd) {
that._setValueInRange(options.selectionStart, val);
that._lastHandleDrag.dragstart(e);
idx = 1;
} else {
if (val - options.selectionStart <= options.selectionEnd - val) {
that._setValueInRange(val, options.selectionEnd);
that._firstHandleDrag.dragstart(e);
idx = 0;
} else {
that._setValueInRange(options.selectionStart, val);
that._lastHandleDrag.dragstart(e);
idx = 1;
}
}
that._focusWithMouse({
target: that.wrapper.find(DRAG_HANDLE)[idx]
});
};
that.wrapper.find(TICK_SELECTOR + ", " + TRACK_SELECTOR).on(TRACK_MOUSE_DOWN, clickHandler).end().on(TRACK_MOUSE_DOWN, function() {
$(document.documentElement).one("selectstart", kendo.preventDefault);
});
that.wrapper.find(DRAG_HANDLE).attr(TABINDEX, 0).on(MOUSE_UP, function() {
that._setTooltipTimeout();
}).on(CLICK, function(e) {
that._focusWithMouse(e);
e.preventDefault();
}).on(FOCUS, proxy(that._focus, that)).on(BLUR, proxy(that._blur, that));
that.wrapper.find(DRAG_HANDLE).off(KEY_DOWN, kendo.preventDefault).eq(0).on(KEY_DOWN, proxy(function(e) {
this._keydown(e, "firstHandle");
}, that)).end().eq(1).on(KEY_DOWN, proxy(function(e) {
this._keydown(e, "lastHandle");
}, that));
that.options.enabled = true;
},
disable: function() {
var that = this;
that.wrapper.removeClass(STATE_DEFAULT).addClass(STATE_DISABLED);
that.wrapper.find("input").prop(DISABLED, DISABLED);
that.wrapper.find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(TRACK_MOUSE_DOWN);
that.wrapper.find(DRAG_HANDLE).attr(TABINDEX, -1).off(MOUSE_UP).off(KEY_DOWN).off(CLICK).off(FOCUS).off(BLUR);
that.options.enabled = false;
},
_keydown: function(e, handle) {
var that = this, selectionStartValue = that.options.selectionStart, selectionEndValue = that.options.selectionEnd, dragSelectionStart, dragSelectionEnd, activeHandleDrag;
if (e.keyCode in that._keyMap) {
that._clearTooltipTimeout();
if (handle == "firstHandle") {
activeHandleDrag = that._activeHandleDrag = that._firstHandleDrag;
selectionStartValue = that._keyMap[e.keyCode](selectionStartValue);
if (selectionStartValue > selectionEndValue) {
selectionEndValue = selectionStartValue;
}
} else {
activeHandleDrag = that._activeHandleDrag = that._lastHandleDrag;
selectionEndValue = that._keyMap[e.keyCode](selectionEndValue);
if (selectionStartValue > selectionEndValue) {
selectionStartValue = selectionEndValue;
}
}
that._setValueInRange(selectionStartValue, selectionEndValue);
dragSelectionStart = Math.max(selectionStartValue, that.options.selectionStart);
dragSelectionEnd = Math.min(selectionEndValue, that.options.selectionEnd);
activeHandleDrag.selectionEnd = Math.max(dragSelectionEnd, that.options.selectionStart);
activeHandleDrag.selectionStart = Math.min(dragSelectionStart, that.options.selectionEnd);
activeHandleDrag._updateTooltip(that.value()[that._activeHandle]);
e.preventDefault();
}
},
_update: function(selectionStart, selectionEnd) {
var that = this, values = that.value();
var change = values[0] != selectionStart || values[1] != selectionEnd;
that.value([ selectionStart, selectionEnd ]);
if (change) {
that.trigger(CHANGE, {
values: [ selectionStart, selectionEnd ],
value: [ selectionStart, selectionEnd ]
});
}
},
value: function(value) {
if (value && value.length) {
return this._value(value[0], value[1]);
} else {
return this._value();
}
},
_value: function(start, end) {
var that = this, options = that.options, selectionStart = options.selectionStart, selectionEnd = options.selectionEnd;
if (isNaN(start) && isNaN(end)) {
return [ selectionStart, selectionEnd ];
} else {
start = round(start);
end = round(end);
}
if (start >= options.min && start <= options.max && end >= options.min && end <= options.max && start <= end) {
if (selectionStart != start || selectionEnd != end) {
that.element.find("input").eq(0).prop("value", formatValue(start)).end().eq(1).prop("value", formatValue(end));
options.selectionStart = start;
options.selectionEnd = end;
that._refresh();
that._refreshAriaAttr(start, end);
}
}
},
values: function(start, end) {
if (isArray(start)) {
return this._value(start[0], start[1]);
} else {
return this._value(start, end);
}
},
_refresh: function() {
var that = this, options = that.options;
that.trigger(MOVE_SELECTION, {
values: [ options.selectionStart, options.selectionEnd ],
value: [ options.selectionStart, options.selectionEnd ]
});
if (options.selectionStart == options.max && options.selectionEnd == options.max) {
that._setZIndex("firstHandle");
}
},
_refreshAriaAttr: function(start, end) {
var that = this, dragHandles = that.wrapper.find(DRAG_HANDLE), drag = that._activeHandleDrag, formattedValue;
formattedValue = that._getFormattedValue([ start, end ], drag);
dragHandles.eq(0).attr("aria-valuenow", start);
dragHandles.eq(1).attr("aria-valuenow", end);
dragHandles.attr("aria-valuetext", formattedValue);
},
_setValueInRange: function(selectionStart, selectionEnd) {
var options = this.options;
selectionStart = math.max(math.min(selectionStart, options.max), options.min);
selectionEnd = math.max(math.min(selectionEnd, options.max), options.min);
if (selectionStart == options.max && selectionEnd == options.max) {
this._setZIndex("firstHandle");
}
this._update(math.min(selectionStart, selectionEnd), math.max(selectionStart, selectionEnd));
},
_setZIndex: function(type) {
this.wrapper.find(DRAG_HANDLE).each(function(index) {
$(this).css("z-index", type == "firstHandle" ? 1 - index : index);
});
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that.wrapper.off(NS).find(TICK_SELECTOR + ", " + TRACK_SELECTOR).off(NS).end().find(DRAG_HANDLE).off(NS);
that._firstHandleDrag.draggable.destroy();
that._lastHandleDrag.draggable.destroy();
}
});
RangeSlider.Selection = function(dragHandles, that, options) {
function moveSelection(value) {
value = value || [];
var selectionStartValue = value[0] - options.min, selectionEndValue = value[1] - options.min, selectionStartIndex = math.ceil(round(selectionStartValue / options.smallStep)), selectionEndIndex = math.ceil(round(selectionEndValue / options.smallStep)), selectionStart = that._pixelSteps[selectionStartIndex], selectionEnd = that._pixelSteps[selectionEndIndex], halfHandle = parseInt(dragHandles.eq(0)[that._outerSize]() / 2, 10), rtlCorrection = that._isRtl ? 2 : 0;
dragHandles.eq(0).css(that._position, selectionStart - halfHandle - rtlCorrection).end().eq(1).css(that._position, selectionEnd - halfHandle - rtlCorrection);
makeSelection(selectionStart, selectionEnd);
}
function makeSelection(selectionStart, selectionEnd) {
var selection, selectionPosition, selectionDiv = that._trackDiv.find(".k-slider-selection");
selection = math.abs(selectionStart - selectionEnd);
selectionDiv[that._size](selection);
if (that._isRtl) {
selectionPosition = math.max(selectionStart, selectionEnd);
selectionDiv.css("right", that._maxSelection - selectionPosition - 1);
} else {
selectionPosition = math.min(selectionStart, selectionEnd);
selectionDiv.css(that._position, selectionPosition - 1);
}
}
moveSelection(that.value());
that.bind([ CHANGE, SLIDE, MOVE_SELECTION ], function(e) {
moveSelection(e.values);
});
};
kendo.ui.plugin(RangeSlider);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.ui, keys = kendo.keys, extend = $.extend, proxy = $.proxy, Widget = ui.Widget, pxUnitsRegex = /^\d+(\.\d+)?px$/i, percentageUnitsRegex = /^\d+(\.\d+)?%$/i, NS = ".kendoSplitter", EXPAND = "expand", COLLAPSE = "collapse", CONTENTLOAD = "contentLoad", RESIZE = "resize", LAYOUTCHANGE = "layoutChange", HORIZONTAL = "horizontal", VERTICAL = "vertical", MOUSEENTER = "mouseenter", CLICK = "click", PANE = "pane", MOUSELEAVE = "mouseleave", FOCUSED = "k-state-focused", KPANE = "k-" + PANE, PANECLASS = "." + KPANE;
function isPercentageSize(size) {
return percentageUnitsRegex.test(size);
}
function isPixelSize(size) {
return pxUnitsRegex.test(size) || /^\d+$/.test(size);
}
function isFluid(size) {
return !isPercentageSize(size) && !isPixelSize(size);
}
function panePropertyAccessor(propertyName, triggersResize) {
return function(pane, value) {
var paneConfig = this.element.find(pane).data(PANE);
if (arguments.length == 1) {
return paneConfig[propertyName];
}
paneConfig[propertyName] = value;
if (triggersResize) {
var splitter = this.element.data("kendoSplitter");
splitter.trigger(RESIZE);
}
};
}
var Splitter = Widget.extend({
init: function(element, options) {
var that = this, isHorizontal;
Widget.fn.init.call(that, element, options);
that.wrapper = that.element;
isHorizontal = that.options.orientation.toLowerCase() != VERTICAL;
that.orientation = isHorizontal ? HORIZONTAL : VERTICAL;
that._dimension = isHorizontal ? "width" : "height";
that._keys = {
decrease: isHorizontal ? keys.LEFT : keys.UP,
increase: isHorizontal ? keys.RIGHT : keys.DOWN
};
that._resizeStep = 10;
that.bind(RESIZE, proxy(that._resize, that));
that._marker = kendo.guid().substring(0, 8);
that._initPanes();
that._resizeHandler = function() {
that.trigger(RESIZE);
};
that._attachEvents();
$(window).on("resize", that._resizeHandler);
that.resizing = new PaneResizing(that);
that.element.triggerHandler("init.kendoSplitter");
},
events: [ EXPAND, COLLAPSE, CONTENTLOAD, RESIZE, LAYOUTCHANGE ],
_attachEvents: function() {
var that = this, orientation = that.options.orientation, splitbarDraggableSelector = "> .k-splitbar-draggable-" + orientation;
// do not use delegated events to increase performance of nested elements
that.element.find(splitbarDraggableSelector).on("keydown" + NS, $.proxy(that._keydown, that)).on("mousedown" + NS, function(e) {
e.currentTarget.focus();
}).on("focus" + NS, function(e) {
$(e.currentTarget).addClass(FOCUSED);
}).on("blur" + NS, function(e) {
$(e.currentTarget).removeClass(FOCUSED);
that.resizing.end();
}).on(MOUSEENTER + NS, function() {
$(this).addClass("k-splitbar-" + that.orientation + "-hover");
}).on(MOUSELEAVE + NS, function() {
$(this).removeClass("k-splitbar-" + that.orientation + "-hover");
}).on("mousedown" + NS, function() {
that._panes().append("<div class='k-splitter-overlay k-overlay' />");
}).on("mouseup" + NS, function() {
that._panes().children(".k-splitter-overlay").remove();
}).end().on(CLICK + NS, ".k-splitbar .k-collapse-next, .k-splitbar .k-collapse-prev", that._arrowClick(COLLAPSE)).on(CLICK + NS, ".k-splitbar .k-expand-next, .k-splitbar .k-expand-prev", that._arrowClick(EXPAND)).on("dblclick" + NS, ".k-splitbar", proxy(that._togglePane, that)).parent().closest(".k-splitter").each(function() {
var parentSplitter = $(this), splitter = parentSplitter.data("kendoSplitter");
if (splitter) {
splitter.bind(RESIZE, that._resizeHandler);
} else {
parentSplitter.one("init" + NS, function() {
$(this).data("kendoSplitter").bind(RESIZE, that._resizeHandler);
that._resizeHandler();
});
}
});
},
options: {
name: "Splitter",
orientation: HORIZONTAL,
panes: []
},
destroy: function() {
var that = this, orientation = that.options.orientation, splitbarDraggableSelector = "> .k-splitbar-draggable-" + orientation;
Widget.fn.destroy.call(that);
that.element.off(NS).find(splitbarDraggableSelector).off(NS);
that.resizing.destroy();
$(window).off("resize", that._resizeHandler);
kendo.destroy(that.element);
},
_keydown: function(e) {
var that = this, key = e.keyCode, resizing = that.resizing, target = $(e.currentTarget), navigationKeys = that._keys, increase = key === navigationKeys.increase, decrease = key === navigationKeys.decrease, pane;
if (increase || decrease) {
if (e.ctrlKey) {
pane = target[decrease ? "next" : "prev"]();
if (resizing.isResizing()) {
resizing.end();
}
if (!pane[that._dimension]()) {
that._triggerAction(EXPAND, pane);
} else {
that._triggerAction(COLLAPSE, target[decrease ? "prev" : "next"]());
}
} else {
resizing.move((decrease ? -1 : 1) * that._resizeStep, target);
}
e.preventDefault();
} else if (key === keys.ENTER) {
resizing.end();
e.preventDefault();
}
},
_initPanes: function() {
var that = this, panesConfig = that.options.panes || [];
that.element.addClass("k-widget").addClass("k-splitter").children().each(function(index, pane) {
var config = panesConfig && panesConfig[index];
pane = $(pane).attr("role", "group").addClass(KPANE);
pane.data(PANE, config ? config : {}).toggleClass("k-scrollable", config ? config.scrollable !== false : true);
that.ajaxRequest(pane);
}).end();
that.trigger(RESIZE);
},
ajaxRequest: function(pane, url, data) {
var that = this, paneConfig;
pane = that.element.find(pane);
paneConfig = pane.data(PANE);
url = url || paneConfig.contentUrl;
if (url) {
pane.append("<span class='k-icon k-loading k-pane-loading' />");
if (kendo.isLocalUrl(url)) {
jQuery.ajax({
url: url,
data: data || {},
type: "GET",
dataType: "html",
success: function(data) {
pane.html(data);
that.trigger(CONTENTLOAD, {
pane: pane[0]
});
}
});
} else {
pane.removeClass("k-scrollable").html("<iframe src='" + url + "' frameborder='0' class='k-content-frame'>" + "This page requires frames in order to show content" + "</iframe>");
}
}
},
_triggerAction: function(type, pane) {
if (!this.trigger(type, {
pane: pane[0]
})) {
this[type](pane[0]);
}
},
_togglePane: function(e) {
var that = this, target = $(e.target), arrow;
if (target.closest(".k-splitter")[0] != that.element[0]) {
return;
}
arrow = target.children(".k-icon:not(.k-resize-handle)");
if (arrow.length !== 1) {
return;
}
if (arrow.is(".k-collapse-prev")) {
that._triggerAction(COLLAPSE, target.prev());
} else if (arrow.is(".k-collapse-next")) {
that._triggerAction(COLLAPSE, target.next());
} else if (arrow.is(".k-expand-prev")) {
that._triggerAction(EXPAND, target.prev());
} else if (arrow.is(".k-expand-next")) {
that._triggerAction(EXPAND, target.next());
}
},
_arrowClick: function(arrowType) {
var that = this;
return function(e) {
var target = $(e.target), pane;
if (target.closest(".k-splitter")[0] != that.element[0]) {
return;
}
if (target.is(".k-" + arrowType + "-prev")) {
pane = target.parent().prev();
} else {
pane = target.parent().next();
}
that._triggerAction(arrowType, pane);
};
},
_updateSplitBar: function(splitbar, previousPane, nextPane) {
var catIconIf = function(iconType, condition) {
return condition ? "<div class='k-icon " + iconType + "' />" : "";
}, orientation = this.orientation, draggable = previousPane.resizable !== false && nextPane.resizable !== false, prevCollapsible = previousPane.collapsible, prevCollapsed = previousPane.collapsed, nextCollapsible = nextPane.collapsible, nextCollapsed = nextPane.collapsed;
splitbar.addClass("k-splitbar k-state-default k-splitbar-" + orientation).attr("role", "separator").attr("aria-expanded", !(prevCollapsed || nextCollapsed)).removeClass("k-splitbar-" + orientation + "-hover").toggleClass("k-splitbar-draggable-" + orientation, draggable && !prevCollapsed && !nextCollapsed).toggleClass("k-splitbar-static-" + orientation, !draggable && !prevCollapsible && !nextCollapsible).html(catIconIf("k-collapse-prev", prevCollapsible && !prevCollapsed && !nextCollapsed) + catIconIf("k-expand-prev", prevCollapsible && prevCollapsed && !nextCollapsed) + catIconIf("k-resize-handle", draggable) + catIconIf("k-collapse-next", nextCollapsible && !nextCollapsed && !prevCollapsed) + catIconIf("k-expand-next", nextCollapsible && nextCollapsed && !prevCollapsed));
},
_updateSplitBars: function() {
var that = this;
this.element.children(".k-splitbar").each(function() {
var splitbar = $(this), previousPane = splitbar.prev(PANECLASS).data(PANE), nextPane = splitbar.next(PANECLASS).data(PANE);
if (!nextPane) {
return;
}
that._updateSplitBar(splitbar, previousPane, nextPane);
});
},
_panes: function() {
return this.element.children(PANECLASS);
},
_resize: function() {
var that = this, element = that.element, panes = element.children(":not(.k-splitbar)"), isHorizontal = that.orientation == HORIZONTAL, splitBars = element.children(".k-splitbar"), splitBarsCount = splitBars.length, sizingProperty = isHorizontal ? "width" : "height", totalSize = element[sizingProperty]();
if (splitBarsCount === 0) {
splitBarsCount = panes.length - 1;
panes.slice(0, splitBarsCount).after("<div tabindex='0' class='k-splitbar' data-marker='" + that._marker + "' />");
that._updateSplitBars();
splitBars = element.children(".k-splitbar");
} else {
that._updateSplitBars();
}
// discard splitbar sizes from total size
splitBars.each(function() {
totalSize -= this[isHorizontal ? "offsetWidth" : "offsetHeight"];
});
var sizedPanesWidth = 0, sizedPanesCount = 0, freeSizedPanes = $();
panes.css({
position: "absolute",
top: 0
})[sizingProperty](function() {
var config = $(this).data(PANE) || {}, size;
if (config.collapsed) {
size = 0;
$(this).css("overflow", "hidden");
} else if (isFluid(config.size)) {
freeSizedPanes = freeSizedPanes.add(this);
return;
} else {
// sized in px/%, not collapsed
size = parseInt(config.size, 10);
if (isPercentageSize(config.size)) {
size = Math.floor(size * totalSize / 100);
}
}
sizedPanesCount++;
sizedPanesWidth += size;
return size;
});
totalSize -= sizedPanesWidth;
var freeSizePanesCount = freeSizedPanes.length, freeSizePaneWidth = Math.floor(totalSize / freeSizePanesCount);
freeSizedPanes.slice(0, freeSizePanesCount - 1).css(sizingProperty, freeSizePaneWidth).end().eq(freeSizePanesCount - 1).css(sizingProperty, totalSize - (freeSizePanesCount - 1) * freeSizePaneWidth);
// arrange panes
var sum = 0, alternateSizingProperty = isHorizontal ? "height" : "width", positioningProperty = isHorizontal ? "left" : "top", sizingDomProperty = isHorizontal ? "offsetWidth" : "offsetHeight";
if (freeSizePanesCount === 0) {
var lastNonCollapsedPane = panes.filter(function() {
return !($(this).data(PANE) || {}).collapsed;
}).last();
lastNonCollapsedPane[sizingProperty](totalSize + lastNonCollapsedPane[0][sizingDomProperty]);
}
element.children().css(alternateSizingProperty, element[alternateSizingProperty]()).each(function(i, child) {
child.style[positioningProperty] = Math.floor(sum) + "px";
sum += child[sizingDomProperty];
});
that.trigger(LAYOUTCHANGE);
},
toggle: function(pane, expand) {
var paneConfig;
pane = this.element.find(pane);
paneConfig = pane.data(PANE);
if (!expand && !paneConfig.collapsible) {
return;
}
if (arguments.length == 1) {
expand = paneConfig.collapsed === undefined ? false : paneConfig.collapsed;
}
paneConfig.collapsed = !expand;
if (paneConfig.collapsed) {
pane.css("overflow", "hidden");
} else {
pane.css("overflow", "");
}
this.trigger(RESIZE);
this.resizing.destroy();
this.resizing = new PaneResizing(this);
},
collapse: function(pane) {
this.toggle(pane, false);
},
expand: function(pane) {
this.toggle(pane, true);
},
size: panePropertyAccessor("size", true),
min: panePropertyAccessor("min"),
max: panePropertyAccessor("max")
});
ui.plugin(Splitter);
var verticalDefaults = {
sizingProperty: "height",
sizingDomProperty: "offsetHeight",
alternateSizingProperty: "width",
positioningProperty: "top",
mousePositioningProperty: "pageY"
};
var horizontalDefaults = {
sizingProperty: "width",
sizingDomProperty: "offsetWidth",
alternateSizingProperty: "height",
positioningProperty: "left",
mousePositioningProperty: "pageX"
};
function PaneResizing(splitter) {
var that = this, orientation = splitter.orientation;
that.owner = splitter;
that._element = splitter.element;
that.orientation = orientation;
extend(that, orientation === HORIZONTAL ? horizontalDefaults : verticalDefaults);
that._resizable = new kendo.ui.Resizable(splitter.element, {
orientation: orientation,
handle: ".k-splitbar-draggable-" + orientation + "[data-marker=" + splitter._marker + "]",
hint: proxy(that._createHint, that),
start: proxy(that._start, that),
max: proxy(that._max, that),
min: proxy(that._min, that),
invalidClass: "k-restricted-size-" + orientation,
resizeend: proxy(that._stop, that)
});
}
PaneResizing.prototype = {
press: function(target) {
this._resizable.press(target);
},
move: function(delta, target) {
if (!this.pressed) {
this.press(target);
this.pressed = true;
}
if (!this._resizable.target) {
this._resizable.press(target);
}
this._resizable.move(delta);
},
end: function() {
this._resizable.end();
this.pressed = false;
},
destroy: function() {
this._resizable.destroy();
},
isResizing: function() {
return this._resizable.resizing;
},
_createHint: function(handle) {
var that = this;
return $("<div class='k-ghost-splitbar k-ghost-splitbar-" + that.orientation + " k-state-default' />").css(that.alternateSizingProperty, handle[that.alternateSizingProperty]());
},
_start: function(e) {
var that = this, splitbar = $(e.currentTarget), previousPane = splitbar.prev(), nextPane = splitbar.next(), previousPaneConfig = previousPane.data(PANE), nextPaneConfig = nextPane.data(PANE), prevBoundary = parseInt(previousPane[0].style[that.positioningProperty], 10), nextBoundary = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - splitbar[0][that.sizingDomProperty], totalSize = parseInt(that._element.css(that.sizingProperty), 10), toPx = function(value) {
var val = parseInt(value, 10);
return (isPixelSize(value) ? val : totalSize * val / 100) || 0;
}, prevMinSize = toPx(previousPaneConfig.min), prevMaxSize = toPx(previousPaneConfig.max) || nextBoundary - prevBoundary, nextMinSize = toPx(nextPaneConfig.min), nextMaxSize = toPx(nextPaneConfig.max) || nextBoundary - prevBoundary;
that.previousPane = previousPane;
that.nextPane = nextPane;
that._maxPosition = Math.min(nextBoundary - nextMinSize, prevBoundary + prevMaxSize);
that._minPosition = Math.max(prevBoundary + prevMinSize, nextBoundary - nextMaxSize);
},
_max: function() {
return this._maxPosition;
},
_min: function() {
return this._minPosition;
},
_stop: function(e) {
var that = this, splitbar = $(e.currentTarget), owner = that.owner;
owner._panes().children(".k-splitter-overlay").remove();
if (e.keyCode !== kendo.keys.ESC) {
var ghostPosition = e.position, previousPane = splitbar.prev(), nextPane = splitbar.next(), previousPaneConfig = previousPane.data(PANE), nextPaneConfig = nextPane.data(PANE), previousPaneNewSize = ghostPosition - parseInt(previousPane[0].style[that.positioningProperty], 10), nextPaneNewSize = parseInt(nextPane[0].style[that.positioningProperty], 10) + nextPane[0][that.sizingDomProperty] - ghostPosition - splitbar[0][that.sizingDomProperty], fluidPanesCount = that._element.children(PANECLASS).filter(function() {
return isFluid($(this).data(PANE).size);
}).length;
if (!isFluid(previousPaneConfig.size) || fluidPanesCount > 1) {
if (isFluid(previousPaneConfig.size)) {
fluidPanesCount--;
}
previousPaneConfig.size = previousPaneNewSize + "px";
}
if (!isFluid(nextPaneConfig.size) || fluidPanesCount > 1) {
nextPaneConfig.size = nextPaneNewSize + "px";
}
owner.trigger(RESIZE);
}
return false;
}
};
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, logToConsole = kendo.logToConsole, rFileExtension = /\.([^\.]+)$/, NS = ".kendoUpload", SELECT = "select", UPLOAD = "upload", SUCCESS = "success", ERROR = "error", COMPLETE = "complete", CANCEL = "cancel", PROGRESS = "progress", REMOVE = "remove";
var Upload = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.name = element.name;
that.multiple = that.options.multiple;
that.localization = that.options.localization;
var activeInput = that.element;
that.wrapper = activeInput.closest(".k-upload");
if (that.wrapper.length === 0) {
that.wrapper = that._wrapInput(activeInput);
}
that._activeInput(activeInput);
that.toggle(that.options.enabled);
var ns = that._ns = NS + "-" + kendo.guid();
activeInput.closest("form").on("submit" + ns, $.proxy(that._onParentFormSubmit, that)).on("reset" + ns, $.proxy(that._onParentFormReset, that));
if (that.options.async.saveUrl) {
that._module = that._supportsFormData() ? new formDataUploadModule(that) : new iframeUploadModule(that);
that._async = true;
} else {
that._module = new syncUploadModule(that);
}
if (that._supportsDrop()) {
that._setupDropZone();
}
that.wrapper.delegate(".k-upload-action", "click", $.proxy(that._onFileAction, that)).delegate(".k-upload-selected", "click", $.proxy(that._onUploadSelected, that)).delegate(".k-file", "t:progress", $.proxy(that._onFileProgress, that)).delegate(".k-file", "t:upload-success", $.proxy(that._onUploadSuccess, that)).delegate(".k-file", "t:upload-error", $.proxy(that._onUploadError, that));
},
events: [ SELECT, UPLOAD, SUCCESS, ERROR, COMPLETE, CANCEL, PROGRESS, REMOVE ],
options: {
name: "Upload",
enabled: true,
multiple: true,
showFileList: true,
async: {
removeVerb: "POST",
autoUpload: true
},
localization: {
select: "Select...",
cancel: "Cancel",
retry: "Retry",
remove: "Remove",
uploadSelectedFiles: "Upload files",
dropFilesHere: "drop files here to upload",
statusUploading: "uploading",
statusUploaded: "uploaded",
statusFailed: "failed"
}
},
setOptions: function(options) {
var that = this, activeInput = that.element;
Widget.fn.setOptions.call(that, options);
that.multiple = that.options.multiple;
activeInput.attr("multiple", that._supportsMultiple() ? that.multiple : false);
that.toggle(that.options.enabled);
},
enable: function(enable) {
enable = typeof enable === "undefined" ? true : enable;
this.toggle(enable);
},
disable: function() {
this.toggle(false);
},
toggle: function(enable) {
enable = typeof enable === "undefined" ? enable : !enable;
this.wrapper.toggleClass("k-state-disabled", enable);
},
destroy: function() {
var that = this;
$(document).add($(".k-dropzone", that.wrapper)).add(that.wrapper.closest("form")).off(that._ns);
Widget.fn.destroy.call(that);
},
_addInput: function(input) {
var that = this;
input.insertAfter(that.element).data("kendoUpload", that);
$(that.element).hide().removeAttr("id");
that._activeInput(input);
},
_activeInput: function(input) {
var that = this, wrapper = that.wrapper;
that.element = input;
input.attr("multiple", that._supportsMultiple() ? that.multiple : false).attr("autocomplete", "off").click(function(e) {
if (wrapper.hasClass("k-state-disabled")) {
e.preventDefault();
}
}).change($.proxy(that._onInputChange, that));
},
_onInputChange: function(e) {
var upload = this, input = $(e.target), prevented = upload.trigger(SELECT, {
files: inputFiles(input)
});
if (prevented) {
upload._addInput(input.clone().val(""));
input.remove();
} else {
input.trigger("t:select");
}
},
_onDrop: function(e) {
var dt = e.originalEvent.dataTransfer, that = this, droppedFiles = dt.files;
stopEvent(e);
if (droppedFiles.length > 0) {
var prevented = that.trigger(SELECT, {
files: getAllFileInfo(droppedFiles)
});
if (!prevented) {
$(".k-dropzone", that.wrapper).trigger("t:select", [ droppedFiles ]);
}
}
},
_enqueueFile: function(name, data) {
var that = this, existingFileEntries, fileEntry, fileList = $(".k-upload-files", that.wrapper);
if (fileList.length === 0) {
fileList = $("<ul class='k-upload-files k-reset'></ul>").appendTo(that.wrapper);
if (!that.options.showFileList) {
fileList.hide();
}
}
existingFileEntries = $(".k-file", fileList);
fileEntry = $("<li class='k-file'><span class='k-filename' title='" + name + "'>" + name + "</span></li>").appendTo(fileList).data(data);
if (that._async) {
fileEntry.prepend("<span class='k-icon'></span>");
}
if (!that.multiple) {
existingFileEntries.trigger("t:remove");
}
return fileEntry;
},
_removeFileEntry: function(fileEntry) {
var fileList = fileEntry.closest(".k-upload-files"), allFiles;
fileEntry.remove();
allFiles = $(".k-file", fileList);
if (allFiles.find("> .k-fail").length === allFiles.length) {
this._hideUploadButton();
}
if (allFiles.length === 0) {
fileList.remove();
}
},
_fileAction: function(fileElement, actionKey) {
var classDictionary = {
remove: "k-delete",
cancel: "k-cancel",
retry: "k-retry"
};
if (!classDictionary.hasOwnProperty(actionKey)) {
return;
}
this._clearFileAction(fileElement);
fileElement.append(this._renderAction(classDictionary[actionKey], this.localization[actionKey]).addClass("k-upload-action"));
},
_fileState: function(fileEntry, stateKey) {
var localization = this.localization, states = {
uploading: {
cssClass: "k-loading",
text: localization.statusUploading
},
uploaded: {
cssClass: "k-success",
text: localization.statusUploaded
},
failed: {
cssClass: "k-fail",
text: localization.statusFailed
}
}, currentState = states[stateKey];
if (currentState) {
var icon = fileEntry.children(".k-icon").text(currentState.text);
icon[0].className = "k-icon " + currentState.cssClass;
}
},
_renderAction: function(actionClass, actionText) {
if (actionClass !== "") {
return $("<button type='button' class='k-button k-button-icontext'>" + "<span class='k-icon " + actionClass + "'></span>" + actionText + "</button>");
} else {
return $("<button type='button' class='k-button'>" + actionText + "</button>");
}
},
_clearFileAction: function(fileElement) {
fileElement.find(".k-upload-action").remove();
},
_onFileAction: function(e) {
var that = this;
if (!that.wrapper.hasClass("k-state-disabled")) {
var button = $(e.target).closest(".k-upload-action"), icon = button.find(".k-icon"), fileEntry = button.closest(".k-file"), eventArgs = {
files: fileEntry.data("fileNames")
};
if (icon.hasClass("k-delete")) {
if (!that.trigger(REMOVE, eventArgs)) {
fileEntry.trigger("t:remove", eventArgs.data);
}
} else if (icon.hasClass("k-cancel")) {
that.trigger(CANCEL, eventArgs);
fileEntry.trigger("t:cancel");
this._checkAllComplete();
} else if (icon.hasClass("k-retry")) {
fileEntry.trigger("t:retry");
}
}
return false;
},
_onUploadSelected: function() {
this.wrapper.trigger("t:saveSelected");
return false;
},
_onFileProgress: function(e, percentComplete) {
var progressBar = $(".k-progress-status", e.target);
if (progressBar.length === 0) {
progressBar = $("<span class='k-progress'><span class='k-state-selected k-progress-status' style='width: 0;'></span></span>").appendTo($(".k-filename", e.target)).find(".k-progress-status");
}
progressBar.width(percentComplete + "%");
this.trigger(PROGRESS, {
files: getFileEntry(e).data("fileNames"),
percentComplete: percentComplete
});
},
_onUploadSuccess: function(e, response, xhr) {
var fileEntry = getFileEntry(e);
this._fileState(fileEntry, "uploaded");
this.trigger(SUCCESS, {
files: fileEntry.data("fileNames"),
response: response,
operation: "upload",
XMLHttpRequest: xhr
});
if (this._supportsRemove()) {
this._fileAction(fileEntry, REMOVE);
} else {
this._clearFileAction(fileEntry);
}
this._checkAllComplete();
},
_onUploadError: function(e, xhr) {
var fileEntry = getFileEntry(e);
this._fileState(fileEntry, "failed");
this._fileAction(fileEntry, "retry");
this.trigger(ERROR, {
operation: "upload",
files: fileEntry.data("fileNames"),
XMLHttpRequest: xhr
});
logToConsole("Server response: " + xhr.responseText);
this._checkAllComplete();
},
_showUploadButton: function() {
var uploadButton = $(".k-upload-selected", this.wrapper);
if (uploadButton.length === 0) {
uploadButton = this._renderAction("", this.localization.uploadSelectedFiles).addClass("k-upload-selected");
}
this.wrapper.append(uploadButton);
},
_hideUploadButton: function() {
$(".k-upload-selected", this.wrapper).remove();
},
_onParentFormSubmit: function() {
var upload = this, element = upload.element;
element.trigger("t:abort");
if (!element.value) {
var input = $(element);
// Prevent submitting an empty input
input.attr("disabled", "disabled");
window.setTimeout(function() {
// Restore the input so the Upload remains functional
// in case the user cancels the form submit
input.removeAttr("disabled");
}, 0);
}
},
_onParentFormReset: function() {
$(".k-upload-files", this.wrapper).remove();
},
_supportsFormData: function() {
return typeof FormData != "undefined";
},
_supportsMultiple: function() {
var windows = this._userAgent().indexOf("Windows") > -1;
return !kendo.support.browser.opera && !(kendo.support.browser.safari && windows);
},
_supportsDrop: function() {
var userAgent = this._userAgent().toLowerCase(), isChrome = /chrome/.test(userAgent), isSafari = !isChrome && /safari/.test(userAgent), isWindowsSafari = isSafari && /windows/.test(userAgent);
return !isWindowsSafari && this._supportsFormData() && this.options.async.saveUrl;
},
_userAgent: function() {
return navigator.userAgent;
},
_setupDropZone: function() {
var that = this;
$(".k-upload-button", this.wrapper).wrap("<div class='k-dropzone'></div>");
var ns = that._ns;
var dropZone = $(".k-dropzone", that.wrapper).append($("<em>" + that.localization.dropFilesHere + "</em>")).on("dragenter" + ns, stopEvent).on("dragover" + ns, function(e) {
e.preventDefault();
}).on("drop" + ns, $.proxy(this._onDrop, this));
bindDragEventWrappers(dropZone, ns, function() {
dropZone.addClass("k-dropzone-hovered");
}, function() {
dropZone.removeClass("k-dropzone-hovered");
});
bindDragEventWrappers($(document), ns, function() {
dropZone.addClass("k-dropzone-active");
}, function() {
dropZone.removeClass("k-dropzone-active");
});
},
_supportsRemove: function() {
return !!this.options.async.removeUrl;
},
_submitRemove: function(fileNames, data, onSuccess, onError) {
var upload = this, removeField = upload.options.async.removeField || "fileNames", params = $.extend(data, getAntiForgeryTokens());
params[removeField] = fileNames;
jQuery.ajax({
type: this.options.async.removeVerb,
dataType: "json",
url: this.options.async.removeUrl,
traditional: true,
data: params,
success: onSuccess,
error: onError
});
},
_wrapInput: function(input) {
input.wrap("<div class='k-widget k-upload'><div class='k-button k-upload-button'></div></div>");
input.closest(".k-button").append("<span>" + this.localization.select + "</span>");
return input.closest(".k-upload");
},
_checkAllComplete: function() {
if ($(".k-file .k-icon.k-loading", this.wrapper).length === 0) {
this.trigger(COMPLETE);
}
}
});
// Synchronous upload module
var syncUploadModule = function(upload) {
this.name = "syncUploadModule";
this.element = upload.wrapper;
this.upload = upload;
this.element.bind("t:select", $.proxy(this.onSelect, this)).bind("t:remove", $.proxy(this.onRemove, this)).closest("form").attr("enctype", "multipart/form-data").attr("encoding", "multipart/form-data");
};
syncUploadModule.prototype = {
onSelect: function(e) {
var upload = this.upload;
var sourceInput = $(e.target);
upload._addInput(sourceInput.clone().val(""));
var file = upload._enqueueFile(getFileName(sourceInput), {
relatedInput: sourceInput,
fileNames: inputFiles(sourceInput)
});
upload._fileAction(file, REMOVE);
},
onRemove: function(e) {
var fileEntry = getFileEntry(e);
fileEntry.data("relatedInput").remove();
this.upload._removeFileEntry(fileEntry);
}
};
// Iframe upload module
var iframeUploadModule = function(upload) {
this.name = "iframeUploadModule";
this.element = upload.wrapper;
this.upload = upload;
this.iframes = [];
this.element.bind("t:select", $.proxy(this.onSelect, this)).bind("t:cancel", $.proxy(this.onCancel, this)).bind("t:retry", $.proxy(this.onRetry, this)).bind("t:remove", $.proxy(this.onRemove, this)).bind("t:saveSelected", $.proxy(this.onSaveSelected, this)).bind("t:abort", $.proxy(this.onAbort, this));
};
Upload._frameId = 0;
iframeUploadModule.prototype = {
onSelect: function(e) {
var upload = this.upload, sourceInput = $(e.target);
var fileEntry = this.prepareUpload(sourceInput);
if (upload.options.async.autoUpload) {
this.performUpload(fileEntry);
} else {
if (upload._supportsRemove()) {
this.upload._fileAction(fileEntry, REMOVE);
}
upload._showUploadButton();
}
},
prepareUpload: function(sourceInput) {
var upload = this.upload;
var activeInput = $(upload.element);
var name = upload.options.async.saveField || sourceInput.attr("name");
upload._addInput(sourceInput.clone().val(""));
sourceInput.attr("name", name);
var iframe = this.createFrame(upload.name + "_" + Upload._frameId++);
this.registerFrame(iframe);
var form = this.createForm(upload.options.async.saveUrl, iframe.attr("name")).append(activeInput);
var fileEntry = upload._enqueueFile(getFileName(sourceInput), {
frame: iframe,
relatedInput: activeInput,
fileNames: inputFiles(sourceInput)
});
iframe.data({
form: form,
file: fileEntry
});
return fileEntry;
},
performUpload: function(fileEntry) {
var e = {
files: fileEntry.data("fileNames")
}, iframe = fileEntry.data("frame"), upload = this.upload;
if (!upload.trigger(UPLOAD, e)) {
upload._hideUploadButton();
iframe.appendTo(document.body);
var form = iframe.data("form").appendTo(document.body);
e.data = $.extend({}, e.data, getAntiForgeryTokens());
for (var key in e.data) {
var dataInput = form.find("input[name='" + key + "']");
if (dataInput.length === 0) {
dataInput = $("<input>", {
type: "hidden",
name: key
}).appendTo(form);
}
dataInput.val(e.data[key]);
}
upload._fileAction(fileEntry, CANCEL);
upload._fileState(fileEntry, "uploading");
iframe.one("load", $.proxy(this.onIframeLoad, this));
form[0].submit();
} else {
upload._removeFileEntry(iframe.data("file"));
this.cleanupFrame(iframe);
this.unregisterFrame(iframe);
}
},
onSaveSelected: function() {
var module = this;
$(".k-file", this.element).each(function() {
var fileEntry = $(this), started = isFileUploadStarted(fileEntry);
if (!started) {
module.performUpload(fileEntry);
}
});
},
onIframeLoad: function(e) {
var iframe = $(e.target), responseText;
try {
responseText = iframe.contents().text();
} catch (ex) {
responseText = "Error trying to get server response: " + ex;
}
this.processResponse(iframe, responseText);
},
processResponse: function(iframe, responseText) {
var fileEntry = iframe.data("file"), module = this, fakeXHR = {
responseText: responseText
};
responseText = responseText || "null";
tryParseJSON(responseText, function(jsonResult) {
$.extend(fakeXHR, {
statusText: "OK",
status: "200"
});
fileEntry.trigger("t:progress", [ 100 ]);
fileEntry.trigger("t:upload-success", [ jsonResult, fakeXHR ]);
module.cleanupFrame(iframe);
module.unregisterFrame(iframe);
}, function() {
$.extend(fakeXHR, {
statusText: "error",
status: "500"
});
fileEntry.trigger("t:upload-error", [ fakeXHR ]);
});
},
onCancel: function(e) {
var iframe = $(e.target).data("frame");
this.stopFrameSubmit(iframe);
this.cleanupFrame(iframe);
this.unregisterFrame(iframe);
this.upload._removeFileEntry(iframe.data("file"));
},
onRetry: function(e) {
var fileEntry = getFileEntry(e);
this.performUpload(fileEntry);
},
onRemove: function(e, data) {
var fileEntry = getFileEntry(e);
var iframe = fileEntry.data("frame");
if (iframe) {
this.unregisterFrame(iframe);
this.upload._removeFileEntry(fileEntry);
this.cleanupFrame(iframe);
} else {
removeUploadedFile(fileEntry, this.upload, data);
}
},
onAbort: function() {
var element = this.element, module = this;
$.each(this.iframes, function() {
$("input", this.data("form")).appendTo(element);
module.stopFrameSubmit(this[0]);
this.data("form").remove();
this.remove();
});
this.iframes = [];
},
createFrame: function(id) {
return $("<iframe" + " name='" + id + "'" + " id='" + id + "'" + " style='display:none;' />");
},
createForm: function(action, target) {
return $("<form enctype='multipart/form-data' method='POST'" + " action='" + action + "'" + " target='" + target + "'" + "/>");
},
stopFrameSubmit: function(frame) {
if (typeof frame.stop != "undefined") {
frame.stop();
} else if (frame.document) {
frame.document.execCommand("Stop");
}
},
registerFrame: function(frame) {
this.iframes.push(frame);
},
unregisterFrame: function(frame) {
this.iframes = $.grep(this.iframes, function(value) {
return value.attr("name") != frame.attr("name");
});
},
cleanupFrame: function(frame) {
var form = frame.data("form");
frame.data("file").data("frame", null);
setTimeout(function() {
form.remove();
frame.remove();
}, 1);
}
};
// FormData upload module
var formDataUploadModule = function(upload) {
this.name = "formDataUploadModule";
this.element = upload.wrapper;
this.upload = upload;
this.element.bind("t:select", $.proxy(this.onSelect, this)).bind("t:cancel", $.proxy(this.onCancel, this)).bind("t:remove", $.proxy(this.onRemove, this)).bind("t:retry", $.proxy(this.onRetry, this)).bind("t:saveSelected", $.proxy(this.onSaveSelected, this)).bind("t:abort", $.proxy(this.onAbort, this));
};
formDataUploadModule.prototype = {
onSelect: function(e, rawFiles) {
var upload = this.upload, module = this, sourceElement = $(e.target), files = rawFiles ? getAllFileInfo(rawFiles) : this.inputFiles(sourceElement), fileEntries = this.prepareUpload(sourceElement, files);
$.each(fileEntries, function() {
if (upload.options.async.autoUpload) {
module.performUpload(this);
} else {
if (upload._supportsRemove()) {
upload._fileAction(this, REMOVE);
}
upload._showUploadButton();
}
});
},
prepareUpload: function(sourceElement, files) {
var fileEntries = this.enqueueFiles(files);
if (sourceElement.is("input")) {
$.each(fileEntries, function() {
$(this).data("relatedInput", sourceElement);
});
sourceElement.data("relatedFileEntries", fileEntries);
this.upload._addInput(sourceElement.clone().val(""));
}
return fileEntries;
},
enqueueFiles: function(files) {
var upload = this.upload, name, i, filesLength = files.length, currentFile, fileEntry, fileEntries = [];
if (upload.options.async.batch === true) {
name = $.map(files, function(file) {
return file.name;
}).join(", ");
fileEntry = upload._enqueueFile(name, {
fileNames: files
});
fileEntry.data("files", files);
fileEntries.push(fileEntry);
} else {
for (i = 0; i < filesLength; i++) {
currentFile = files[i];
name = currentFile.name;
fileEntry = upload._enqueueFile(name, {
fileNames: [ currentFile ]
});
fileEntry.data("files", [ currentFile ]);
fileEntries.push(fileEntry);
}
}
return fileEntries;
},
inputFiles: function(sourceInput) {
return inputFiles(sourceInput);
},
performUpload: function(fileEntry) {
var upload = this.upload, formData = this.createFormData(fileEntry.data("files")), e = {
files: fileEntry.data("fileNames")
};
if (!upload.trigger(UPLOAD, e)) {
upload._fileAction(fileEntry, CANCEL);
upload._hideUploadButton();
e.data = $.extend({}, e.data, getAntiForgeryTokens());
for (var key in e.data) {
formData.append(key, e.data[key]);
}
upload._fileState(fileEntry, "uploading");
this.postFormData(this.upload.options.async.saveUrl, formData, fileEntry);
} else {
this.removeFileEntry(fileEntry);
}
},
onSaveSelected: function() {
var module = this;
$(".k-file", this.element).each(function() {
var fileEntry = $(this), started = isFileUploadStarted(fileEntry);
if (!started) {
module.performUpload(fileEntry);
}
});
},
onCancel: function(e) {
var fileEntry = getFileEntry(e);
this.stopUploadRequest(fileEntry);
this.removeFileEntry(fileEntry);
},
onRetry: function(e) {
var fileEntry = getFileEntry(e);
this.performUpload(fileEntry);
},
onRemove: function(e, data) {
var fileEntry = getFileEntry(e);
if (fileEntry.children(".k-icon").is(".k-success")) {
removeUploadedFile(fileEntry, this.upload, data);
} else {
this.removeFileEntry(fileEntry);
}
},
postFormData: function(url, data, fileEntry) {
var xhr = new XMLHttpRequest(), module = this;
fileEntry.data("request", xhr);
xhr.addEventListener("load", function(e) {
module.onRequestSuccess.call(module, e, fileEntry);
}, false);
xhr.addEventListener(ERROR, function(e) {
module.onRequestError.call(module, e, fileEntry);
}, false);
xhr.upload.addEventListener("progress", function(e) {
module.onRequestProgress.call(module, e, fileEntry);
}, false);
xhr.open("POST", url);
xhr.withCredentials = "true";
xhr.send(data);
},
createFormData: function(files) {
var formData = new FormData(), upload = this.upload, i, length = files.length;
for (i = 0; i < length; i++) {
formData.append(upload.options.async.saveField || upload.name, files[i].rawFile);
}
return formData;
},
onRequestSuccess: function(e, fileEntry) {
var xhr = e.target, module = this;
function raiseError() {
fileEntry.trigger("t:upload-error", [ xhr ]);
}
if (xhr.status >= 200 && xhr.status <= 299) {
tryParseJSON(xhr.responseText, function(jsonResult) {
fileEntry.trigger("t:progress", [ 100 ]);
fileEntry.trigger("t:upload-success", [ jsonResult, xhr ]);
module.cleanupFileEntry(fileEntry);
}, raiseError);
} else {
raiseError();
}
},
onRequestError: function(e, fileEntry) {
var xhr = e.target;
fileEntry.trigger("t:upload-error", [ xhr ]);
},
cleanupFileEntry: function(fileEntry) {
var relatedInput = fileEntry.data("relatedInput"), uploadComplete = true;
if (relatedInput) {
$.each(relatedInput.data("relatedFileEntries") || [], function() {
// Exclude removed file entries and self
if (this.parent().length > 0 && this[0] != fileEntry[0]) {
uploadComplete = uploadComplete && this.children(".k-icon").is(".k-success");
}
});
if (uploadComplete) {
relatedInput.remove();
}
}
},
removeFileEntry: function(fileEntry) {
this.cleanupFileEntry(fileEntry);
this.upload._removeFileEntry(fileEntry);
},
onRequestProgress: function(e, fileEntry) {
var percentComplete = Math.round(e.loaded * 100 / e.total);
fileEntry.trigger("t:progress", [ percentComplete ]);
},
stopUploadRequest: function(fileEntry) {
fileEntry.data("request").abort();
}
};
// Helper functions
function getFileName(input) {
return $.map(inputFiles(input), function(file) {
return file.name;
}).join(", ");
}
function inputFiles($input) {
var input = $input[0];
if (input.files) {
return getAllFileInfo(input.files);
} else {
return [ {
name: stripPath(input.value),
extension: getFileExtension(input.value),
size: null
} ];
}
}
function getAllFileInfo(rawFiles) {
return $.map(rawFiles, function(file) {
return getFileInfo(file);
});
}
function getFileInfo(rawFile) {
// Older Firefox versions (before 3.6) use fileName and fileSize
var fileName = rawFile.name || rawFile.fileName;
return {
name: fileName,
extension: getFileExtension(fileName),
size: rawFile.size || rawFile.fileSize,
rawFile: rawFile
};
}
function getFileExtension(fileName) {
var matches = fileName.match(rFileExtension);
return matches ? matches[0] : "";
}
function stripPath(name) {
var slashIndex = name.lastIndexOf("\\");
return slashIndex != -1 ? name.substr(slashIndex + 1) : name;
}
function removeUploadedFile(fileEntry, upload, data) {
if (!upload._supportsRemove()) {
return;
}
var files = fileEntry.data("fileNames");
var fileNames = $.map(files, function(file) {
return file.name;
});
upload._submitRemove(fileNames, data, function onSuccess(data, textStatus, xhr) {
upload._removeFileEntry(fileEntry);
upload.trigger(SUCCESS, {
operation: "remove",
files: files,
response: data,
XMLHttpRequest: xhr
});
}, function onError(xhr) {
upload.trigger(ERROR, {
operation: "remove",
files: files,
XMLHttpRequest: xhr
});
logToConsole("Server response: " + xhr.responseText);
});
}
function tryParseJSON(input, onSuccess, onError) {
var success = false, json = "";
try {
json = $.parseJSON(input);
success = true;
} catch (e) {
onError();
}
if (success) {
onSuccess(json);
}
}
function stopEvent(e) {
e.stopPropagation();
e.preventDefault();
}
function bindDragEventWrappers(element, namespace, onDragEnter, onDragLeave) {
var hideInterval, lastDrag;
element.on("dragenter" + namespace, function() {
onDragEnter();
lastDrag = new Date();
if (!hideInterval) {
hideInterval = setInterval(function() {
var sinceLastDrag = new Date() - lastDrag;
if (sinceLastDrag > 100) {
onDragLeave();
clearInterval(hideInterval);
hideInterval = null;
}
}, 100);
}
}).on("dragover" + namespace, function() {
lastDrag = new Date();
});
}
function isFileUploadStarted(fileEntry) {
return fileEntry.children(".k-icon").is(".k-loading, .k-success, .k-fail");
}
function getFileEntry(e) {
return $(e.target).closest(".k-file");
}
function getAntiForgeryTokens() {
var tokens = {}, csrf_token = $("meta[name=csrf-token]").attr("content"), csrf_param = $("meta[name=csrf-param]").attr("content");
$("input[name^='__RequestVerificationToken']").each(function() {
tokens[this.name] = this.value;
});
if (csrf_param !== undefined && csrf_token !== undefined) {
tokens[csrf_param] = csrf_token;
}
return tokens;
}
kendo.ui.plugin(Upload);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, Draggable = kendo.ui.Draggable, isPlainObject = $.isPlainObject, activeElement = kendo._activeElement, proxy = $.proxy, extend = $.extend, each = $.each, template = kendo.template, BODY = "body", templates, NS = ".kendoWindow", // classNames
KWINDOW = ".k-window", KWINDOWTITLE = ".k-window-title", KWINDOWTITLEBAR = KWINDOWTITLE + "bar", KWINDOWCONTENT = ".k-window-content", KWINDOWRESIZEHANDLES = ".k-resize-handle", KOVERLAY = ".k-overlay", KCONTENTFRAME = "k-content-frame", LOADING = "k-loading", KHOVERSTATE = "k-state-hover", KFOCUSEDSTATE = "k-state-focused", // constants
VISIBLE = ":visible", HIDDEN = "hidden", CURSOR = "cursor", // events
OPEN = "open", ACTIVATE = "activate", DEACTIVATE = "deactivate", CLOSE = "close", REFRESH = "refresh", RESIZE = "resize", DRAGSTART = "dragstart", DRAGEND = "dragend", ERROR = "error", OVERFLOW = "overflow", ZINDEX = "zIndex", MINIMIZE_MAXIMIZE = ".k-window-actions .k-i-minimize,.k-window-actions .k-i-maximize", TITLEBAR_BUTTONS = ".k-window-titlebar .k-window-action", isLocalUrl = kendo.isLocalUrl;
function defined(x) {
return typeof x != "undefined";
}
function constrain(value, low, high) {
return Math.max(Math.min(value, high), low);
}
function windowObject(element, name) {
var contentElement = element.children(KWINDOWCONTENT);
return contentElement.data("kendoWindow") || contentElement.data("kendo" + name);
}
function openedModalWindows(name) {
return $(KWINDOW).filter(function() {
var wnd = $(this), winObj = windowObject(wnd, name);
return winObj.options.modal && wnd.is(VISIBLE) && winObj.options.visible;
}).sort(function(a, b) {
return +$(a).css("zIndex") - +$(b).css("zIndex");
});
}
function sizingAction(actionId, callback) {
return function() {
var that = this, wrapper = that.wrapper, style = wrapper[0].style, options = that.options;
if (options.isMaximized || options.isMinimized) {
return;
}
that.restoreOptions = {
width: style.width,
height: style.height
};
wrapper.find(KWINDOWRESIZEHANDLES).hide().end().find(MINIMIZE_MAXIMIZE).parent().hide().eq(0).before(templates.action({
name: "Restore"
}));
callback.call(that);
return that;
};
}
var Window = Widget.extend({
init: function(element, options) {
var that = this, wrapper, offset, visibility, display, isVisible = false, content, windowContent, id;
Widget.fn.init.call(that, element, options);
options = that.options;
element = that.element;
content = options.content;
that.appendTo = $(options.appendTo || document.body);
that._animations();
if (content && !isPlainObject(content)) {
content = options.content = {
url: content
};
}
// remove script blocks to prevent double-execution
element.find("script").filter(function() {
return !this.type || this.type.toLowerCase().indexOf("script") >= 0;
}).remove();
if (!element.parent().is(that.appendTo)) {
if (element.is(VISIBLE)) {
offset = element.offset();
isVisible = true;
} else {
visibility = element.css("visibility");
display = element.css("display");
element.css({
visibility: HIDDEN,
display: ""
});
offset = element.offset();
element.css({
visibility: visibility,
display: display
});
}
}
if (!defined(options.visible) || options.visible === null) {
options.visible = element.is(VISIBLE);
}
wrapper = that.wrapper = element.closest(KWINDOW);
if (!element.is(".k-content") || !wrapper[0]) {
element.addClass("k-window-content k-content");
that._createWindow(element, options);
wrapper = that.wrapper = element.closest(KWINDOW);
that._dimensions();
}
if (offset) {
wrapper.css({
top: offset.top,
left: offset.left
});
}
if (content) {
that.refresh(content);
}
if (options.visible) {
that.toFront();
}
windowContent = wrapper.children(KWINDOWCONTENT);
that._tabindex(windowContent);
if (options.visible && options.modal) {
that._overlay(wrapper.is(VISIBLE)).css({
opacity: .5
});
}
wrapper.on("mouseenter" + NS, TITLEBAR_BUTTONS, function() {
$(this).addClass(KHOVERSTATE);
}).on("mouseleave" + NS, TITLEBAR_BUTTONS, function() {
$(this).removeClass(KHOVERSTATE);
}).on("click" + NS, TITLEBAR_BUTTONS, proxy(that._windowActionHandler, that));
windowContent.on("keydown" + NS, proxy(that._keydown, that)).on("focus" + NS, function() {
wrapper.addClass(KFOCUSEDSTATE);
}).on("blur" + NS, function() {
wrapper.removeClass(KFOCUSEDSTATE);
});
if (options.resizable) {
wrapper.on("dblclick" + NS, KWINDOWTITLEBAR, function(e) {
if (!$(e.target).closest(".k-window-action").length) {
that.toggleMaximization();
}
return this;
});
each("n e s w se sw ne nw".split(" "), function(index, handler) {
wrapper.append(templates.resizeHandle(handler));
});
that.resizing = new WindowResizing(that);
}
if (options.draggable) {
that.dragging = new WindowDragging(that);
}
id = element.attr("id");
if (id) {
id = id + "_wnd_title";
wrapper.find(KWINDOWTITLEBAR).children(KWINDOWTITLE).attr("id", id);
windowContent.attr({
role: "dialog",
"aria-labelledby": id
});
}
wrapper.add(wrapper.find(".k-resize-handle,.k-window-titlebar")).on("mousedown" + NS, proxy(that.toFront, that));
that.touchScroller = kendo.touchScroller(element);
that._resizeHandler = function(e) {
return that._onDocumentResize(e);
};
$(window).on("resize", that._resizeHandler);
if (options.visible) {
that.trigger(OPEN);
that.trigger(ACTIVATE);
}
kendo.notify(that);
},
_dimensions: function() {
var that = this, wrapper = that.wrapper, element = that.element, options = that.options;
that.title(options.title);
each([ "minWidth", "minHeight", "maxWidth", "maxHeight" ], function(_, prop) {
var value = options[prop];
if (value && value != Infinity) {
element.css(prop, value);
}
});
if (options.width) {
wrapper.width(constrain(parseInt(options.width, 10), options.minWidth, options.maxWidth));
}
if (options.height) {
wrapper.height(constrain(parseInt(options.height, 10), options.minHeight, options.maxHeight));
}
if (!options.visible) {
wrapper.hide();
}
},
_animations: function() {
var options = this.options;
if (options.animation === false) {
options.animation = {
open: {
effects: {}
},
close: {
hide: true,
effects: {}
}
};
}
},
setOptions: function(options) {
Widget.fn.setOptions.call(this, options);
this._animations();
this._dimensions();
},
events: [ OPEN, ACTIVATE, DEACTIVATE, CLOSE, REFRESH, RESIZE, DRAGSTART, DRAGEND, ERROR ],
options: {
name: "Window",
animation: {
open: {
effects: {
zoom: {
direction: "in"
},
fade: {
direction: "in"
}
},
duration: 350
},
close: {
effects: {
zoom: {
direction: "out",
properties: {
scale: .7
}
},
fade: {
direction: "out"
}
},
duration: 350,
hide: true
}
},
title: "",
actions: [ "Close" ],
modal: false,
resizable: true,
draggable: true,
minWidth: 90,
minHeight: 50,
maxWidth: Infinity,
maxHeight: Infinity,
visible: null,
height: null,
width: null
},
_closable: function() {
return $.inArray("close", $.map(this.options.actions, function(x) {
return x.toLowerCase();
})) > -1;
},
_keydown: function(e) {
var that = this, options = that.options, keys = kendo.keys, keyCode = e.keyCode, wrapper = that.wrapper, offset, handled, distance = 10, isMaximized = that.options.isMaximized, newWidth, newHeight;
if (e.target != e.currentTarget) {
return;
}
if (keyCode == keys.ESC && that._closable()) {
that._close(true);
}
if (options.draggable && !e.ctrlKey && !isMaximized) {
offset = kendo.getOffset(wrapper);
if (keyCode == keys.UP) {
handled = wrapper.css("top", offset.top - distance);
} else if (keyCode == keys.DOWN) {
handled = wrapper.css("top", offset.top + distance);
} else if (keyCode == keys.LEFT) {
handled = wrapper.css("left", offset.left - distance);
} else if (keyCode == keys.RIGHT) {
handled = wrapper.css("left", offset.left + distance);
}
}
if (options.resizable && e.ctrlKey && !isMaximized) {
if (keyCode == keys.UP) {
handled = true;
newHeight = wrapper.height() - distance;
} else if (keyCode == keys.DOWN) {
handled = true;
newHeight = wrapper.height() + distance;
}
if (keyCode == keys.LEFT) {
handled = true;
newWidth = wrapper.width() - distance;
} else if (keyCode == keys.RIGHT) {
handled = true;
newWidth = wrapper.width() + distance;
}
if (handled) {
wrapper.css({
width: constrain(newWidth, options.minWidth, options.maxWidth),
height: constrain(newHeight, options.minHeight, options.maxHeight)
});
that.trigger(RESIZE);
}
}
if (handled) {
e.preventDefault();
}
},
_overlay: function(visible) {
var overlay = this.appendTo.children(".k-overlay"), wrapper = this.wrapper;
if (!overlay.length) {
overlay = $("<div class='k-overlay' />");
}
overlay.insertBefore(wrapper[0]).toggle(visible).css(ZINDEX, parseInt(wrapper.css(ZINDEX), 10) - 1);
return overlay;
},
_windowActionHandler: function(e) {
var target = $(e.target).closest(".k-window-action").find(".k-icon"), that = this;
if (that._closing) {
return;
}
each({
"k-i-close": function() {
that._close(true);
},
"k-i-maximize": that.maximize,
"k-i-minimize": that.minimize,
"k-i-restore": that.restore,
"k-i-refresh": that.refresh
}, function(commandName, handler) {
if (target.hasClass(commandName)) {
e.preventDefault();
handler.call(that);
return false;
}
});
},
center: function() {
var wrapper = this.wrapper, documentWindow = $(window);
wrapper.css({
left: documentWindow.scrollLeft() + Math.max(0, (documentWindow.width() - wrapper.width()) / 2),
top: documentWindow.scrollTop() + Math.max(0, (documentWindow.height() - wrapper.height()) / 2)
});
return this;
},
title: function(text) {
var that = this, wrapper = that.wrapper, options = that.options, titleBar = wrapper.find(KWINDOWTITLEBAR), title = titleBar.children(KWINDOWTITLE), titleBarHeight = titleBar.outerHeight();
if (!arguments.length) {
return title.text();
}
if (text === false) {
wrapper.addClass("k-window-titleless");
titleBar.remove();
} else {
if (!titleBar.length) {
wrapper.prepend(templates.titlebar(extend(templates, options)));
}
wrapper.css("padding-top", titleBarHeight);
titleBar.css("margin-top", -titleBarHeight);
}
title.text(text);
return that;
},
content: function(html) {
var content = this.wrapper.children(KWINDOWCONTENT), scrollContainer = content.children(".km-scroll-container");
content = scrollContainer[0] ? scrollContainer : content;
if (!html) {
return content.html();
}
content.html(html);
return this;
},
open: function() {
var that = this, wrapper = that.wrapper, options = that.options, showOptions = options.animation.open, contentElement = wrapper.children(KWINDOWCONTENT), initialOverflow = contentElement.css(OVERFLOW), overlay;
if (!that.trigger(OPEN)) {
that._closing = false;
that.toFront();
that.element.focus();
options.visible = true;
if (options.modal) {
overlay = that._overlay(false);
if (showOptions.duration) {
overlay.kendoStop().kendoAnimate({
effects: "fade:in",
duration: showOptions.duration
});
} else {
overlay.css("opacity", .5).show();
}
}
if (!wrapper.is(VISIBLE)) {
contentElement.css(OVERFLOW, HIDDEN);
wrapper.show().kendoStop().kendoAnimate({
effects: showOptions.effects,
duration: showOptions.duration,
complete: function() {
that.element.focus();
that.trigger(ACTIVATE);
contentElement.css(OVERFLOW, initialOverflow);
}
});
}
}
if (options.isMaximized) {
that._documentScrollTop = $(document).scrollTop();
$("html, body").css(OVERFLOW, HIDDEN);
}
return that;
},
_close: function(userTriggered) {
var that = this, wrapper = that.wrapper, options = that.options, showOptions = options.animation.open, hideOptions = options.animation.close, modalWindows, shouldHideOverlay, overlay;
if (wrapper.is(VISIBLE) && !that.trigger(CLOSE, {
userTriggered: !!userTriggered
})) {
that._closing = true;
options.visible = false;
$(KWINDOW).each(function(i, element) {
var windowObject = $(element), contentElement = windowObject.find(KWINDOWCONTENT);
// Remove overlay set by toFront
if (element != wrapper && contentElement.find("> ." + KCONTENTFRAME).length > 0) {
contentElement.children(".k-overlay").remove();
}
});
modalWindows = openedModalWindows(options.name);
shouldHideOverlay = options.modal && !modalWindows.length;
overlay = options.modal ? that._overlay(true) : $(undefined);
if (shouldHideOverlay) {
if (hideOptions.duration) {
overlay.kendoStop().kendoAnimate({
effects: "fade:out",
duration: hideOptions.duration,
hide: true
});
} else {
overlay.hide();
}
} else if (modalWindows.length) {
windowObject(modalWindows.eq(modalWindows.length - 1), options.name)._overlay(true);
}
wrapper.kendoStop().kendoAnimate({
effects: hideOptions.effects || showOptions.effects,
reverse: hideOptions.reverse === true,
duration: hideOptions.duration,
complete: function() {
wrapper.hide();
that.trigger(DEACTIVATE);
}
});
}
if (that.options.isMaximized) {
$("html, body").css(OVERFLOW, "");
if (that._documentScrollTop && that._documentScrollTop > 0) {
$(document).scrollTop(that._documentScrollTop);
}
}
},
close: function() {
this._close(false);
return this;
},
toFront: function(e) {
var that = this, wrapper = that.wrapper, currentWindow = wrapper[0], zIndex = +wrapper.css(ZINDEX), originalZIndex = zIndex, active = activeElement(), winElement = that.element, target = e && e.target ? e.target : null;
$(KWINDOW).each(function(i, element) {
var windowObject = $(element), zIndexNew = windowObject.css(ZINDEX), contentElement = windowObject.find(KWINDOWCONTENT);
if (!isNaN(zIndexNew)) {
zIndex = Math.max(+zIndexNew, zIndex);
}
// Add overlay to windows with iframes and lower z-index to prevent
// trapping of events when resizing / dragging
if (element != currentWindow && contentElement.find("> ." + KCONTENTFRAME).length > 0) {
contentElement.append(templates.overlay);
}
});
if (zIndex == 10001 || originalZIndex < zIndex) {
wrapper.css(ZINDEX, zIndex + 2);
}
that.element.find("> .k-overlay").remove();
if (!$(active).is(winElement) && !$(target).is(TITLEBAR_BUTTONS + "," + TITLEBAR_BUTTONS + " .k-icon,:input") && (!winElement.find(active).length || !winElement.find(target).length)) {
winElement.focus();
var scrollTop = $(window).scrollTop(), windowTop = parseInt(that.wrapper.position().top, 10);
if (windowTop > 0 && windowTop - scrollTop < 0) {
if (scrollTop > 0) {
$(window).scrollTop(windowTop);
} else {
that.wrapper.css("top", scrollTop);
}
}
}
return that;
},
toggleMaximization: function() {
if (this._closing) {
return this;
}
return this[this.options.isMaximized ? "restore" : "maximize"]();
},
restore: function() {
var that = this, options = that.options, restoreOptions = that.restoreOptions;
if (!options.isMaximized && !options.isMinimized) {
return;
}
that.wrapper.css({
position: "absolute",
left: restoreOptions.left,
top: restoreOptions.top,
width: restoreOptions.width,
height: restoreOptions.height
}).find(".k-window-content,.k-resize-handle").show().end().find(".k-window-titlebar .k-i-restore").parent().remove().end().end().find(MINIMIZE_MAXIMIZE).parent().show();
$("html, body").css(OVERFLOW, "");
if (this._documentScrollTop && this._documentScrollTop > 0) {
$(document).scrollTop(this._documentScrollTop);
}
options.isMaximized = options.isMinimized = false;
that.trigger(RESIZE);
return that;
},
maximize: sizingAction("maximize", function() {
var that = this, wrapper = that.wrapper, position = wrapper.position();
extend(that.restoreOptions, {
left: position.left,
top: position.top
});
wrapper.css({
left: 0,
top: 0,
position: "fixed"
});
this._documentScrollTop = $(document).scrollTop();
$("html, body").css(OVERFLOW, HIDDEN);
that.options.isMaximized = true;
that._onDocumentResize();
}),
minimize: sizingAction("minimize", function() {
var that = this;
that.wrapper.css("height", "");
that.element.hide();
that.options.isMinimized = true;
}),
_onDocumentResize: function() {
var that = this, wrapper = that.wrapper, wnd = $(window);
if (!that.options.isMaximized) {
return;
}
wrapper.css({
width: wnd.width(),
height: wnd.height() - parseInt(wrapper.css("padding-top"), 10)
});
that.trigger(RESIZE);
},
refresh: function(options) {
var that = this, initOptions = that.options, element = $(that.element), iframe, showIframe, url;
if (!isPlainObject(options)) {
options = {
url: options
};
}
options = extend({}, initOptions.content, options);
showIframe = defined(initOptions.iframe) ? initOptions.iframe : options.iframe;
url = options.url;
if (url) {
if (!defined(showIframe)) {
showIframe = !isLocalUrl(url);
}
if (!showIframe) {
// perform AJAX request
that._ajaxRequest(options);
} else {
iframe = element.find("." + KCONTENTFRAME)[0];
if (iframe) {
// refresh existing iframe
iframe.src = url || iframe.src;
} else {
// render new iframe
element.html(templates.contentFrame(extend({}, initOptions, {
content: options
})));
}
element.find("." + KCONTENTFRAME).unbind("load" + NS).on("load" + NS, function() {
that.trigger(REFRESH);
});
}
} else {
if (options.template) {
// refresh template
that.content(template(options.template)({}));
}
that.trigger(REFRESH);
}
return that;
},
_ajaxRequest: function(options) {
var that = this, contentTemplate = options.template, refreshIcon = that.wrapper.find(".k-window-titlebar .k-i-refresh"), loadingIconTimeout = setTimeout(function() {
refreshIcon.addClass(LOADING);
}, 100);
$.ajax(extend({
type: "GET",
dataType: "html",
cache: false,
error: proxy(function(xhr, status) {
that.trigger(ERROR, {
status: status,
xhr: xhr
});
}, that),
complete: function() {
clearTimeout(loadingIconTimeout);
refreshIcon.removeClass(LOADING);
},
success: proxy(function(data) {
if (contentTemplate) {
data = template(contentTemplate)(data || {});
}
that.content(data);
that.element.prop("scrollTop", 0);
that.trigger(REFRESH);
}, that)
}, options));
},
destroy: function() {
var that = this, modalWindows, shouldHideOverlay;
Widget.fn.destroy.call(that);
kendo.destroy(that.wrapper);
if (that.resizing) {
that.resizing.destroy();
}
if (that.dragging) {
that.dragging.destroy();
}
that.element.children("iframe").remove();
that.wrapper.remove().add(that.wrapper.find(".k-resize-handle,.k-window-titlebar")).off(NS);
$(window).off("resize", that._resizeHandler);
modalWindows = openedModalWindows();
shouldHideOverlay = that.options.modal && !modalWindows.length;
if (shouldHideOverlay) {
that._overlay(false).remove();
} else if (modalWindows.length > 0) {
windowObject(modalWindows.eq(modalWindows.length - 1), that.options.name)._overlay(true);
}
},
_createWindow: function() {
var that = this, contentHtml = that.element, options = that.options, iframeSrcAttributes, wrapper, isRtl = kendo.support.isRtl(contentHtml);
if (options.scrollable === false) {
contentHtml.attr("style", "overflow:hidden;");
}
wrapper = $(templates.wrapper(options));
if (options.title !== false) {
wrapper.append(templates.titlebar(extend(templates, options)));
}
// Collect the src attributes of all iframes and then set them to empty string.
// This seems to fix this IE9 "feature": http://msdn.microsoft.com/en-us/library/gg622929%28v=VS.85%29.aspx?ppud=4
iframeSrcAttributes = contentHtml.find("iframe:not(.k-content)").map(function() {
var src = this.getAttribute("src");
this.src = "";
return src;
});
// Make sure the wrapper is appended to the body only once. IE9+ will throw exceptions if you move iframes in DOM
wrapper.toggleClass("k-rtl", isRtl).appendTo(that.appendTo).append(contentHtml).find("iframe:not(.k-content)").each(function(index) {
// Restore the src attribute of the iframes when they are part of the live DOM tree
this.src = iframeSrcAttributes[index];
});
wrapper.find(".k-window-title").css(isRtl ? "left" : "right", wrapper.find(".k-window-actions").outerWidth() + 10);
contentHtml.show();
}
});
templates = {
wrapper: template("<div class='k-widget k-window' />"),
action: template("<a role='button' href='\\#' class='k-window-action k-link'>" + "<span role='presentation' class='k-icon k-i-#= name.toLowerCase() #'>#= name #</span>" + "</a>"),
titlebar: template("<div class='k-window-titlebar k-header'> " + "<span class='k-window-title'>#= title #</span>" + "<div class='k-window-actions'>" + "# for (var i = 0; i < actions.length; i++) { #" + "#= action({ name: actions[i] }) #" + "# } #" + "</div>" + "</div>"),
overlay: "<div class='k-overlay' />",
contentFrame: template("<iframe frameborder='0' title='#= title #' class='" + KCONTENTFRAME + "' " + "src='#= content.url #'>" + "This page requires frames in order to show content" + "</iframe>"),
resizeHandle: template("<div class='k-resize-handle k-resize-#= data #'></div>")
};
function WindowResizing(wnd) {
var that = this;
that.owner = wnd;
that._draggable = new Draggable(wnd.wrapper, {
filter: KWINDOWRESIZEHANDLES,
group: wnd.wrapper.id + "-resizing",
dragstart: proxy(that.dragstart, that),
drag: proxy(that.drag, that),
dragend: proxy(that.dragend, that)
});
}
WindowResizing.prototype = {
dragstart: function(e) {
var that = this, wnd = that.owner, wrapper = wnd.wrapper;
that.elementPadding = parseInt(wnd.wrapper.css("padding-top"), 10);
that.initialCursorPosition = kendo.getOffset(wrapper, "position");
that.resizeDirection = e.currentTarget.prop("className").replace("k-resize-handle k-resize-", "");
that.initialSize = {
width: wrapper.width(),
height: wrapper.height()
};
that.containerOffset = kendo.getOffset(wnd.appendTo);
wrapper.append(templates.overlay).find(KWINDOWRESIZEHANDLES).not(e.currentTarget).hide();
$(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
},
drag: function(e) {
var that = this, wnd = that.owner, wrapper = wnd.wrapper, options = wnd.options, direction = that.resizeDirection, containerOffset = that.containerOffset, initialPosition = that.initialCursorPosition, initialSize = that.initialSize, newWidth, newHeight, windowBottom, windowRight, x = e.x.location, y = e.y.location;
if (direction.indexOf("e") >= 0) {
newWidth = x - initialPosition.left;
wrapper.width(constrain(newWidth, options.minWidth, options.maxWidth));
} else if (direction.indexOf("w") >= 0) {
windowRight = initialPosition.left + initialSize.width;
newWidth = constrain(windowRight - x, options.minWidth, options.maxWidth);
wrapper.css({
left: windowRight - newWidth - containerOffset.left,
width: newWidth
});
}
if (direction.indexOf("s") >= 0) {
newHeight = y - initialPosition.top - that.elementPadding;
wrapper.height(constrain(newHeight, options.minHeight, options.maxHeight));
} else if (direction.indexOf("n") >= 0) {
windowBottom = initialPosition.top + initialSize.height;
newHeight = constrain(windowBottom - y, options.minHeight, options.maxHeight);
wrapper.css({
top: windowBottom - newHeight - containerOffset.top,
height: newHeight
});
}
wnd.trigger(RESIZE);
},
dragend: function(e) {
var that = this, wnd = that.owner, wrapper = wnd.wrapper;
wrapper.find(KOVERLAY).remove().end().find(KWINDOWRESIZEHANDLES).not(e.currentTarget).show();
$(BODY).css(CURSOR, "");
if (wnd.touchScroller) {
wnd.touchScroller.reset();
}
if (e.keyCode == 27) {
wrapper.css(that.initialCursorPosition).css(that.initialSize);
}
return false;
},
destroy: function() {
this._draggable.destroy();
}
};
function WindowDragging(wnd) {
var that = this;
that.owner = wnd;
that._draggable = new Draggable(wnd.wrapper, {
filter: KWINDOWTITLEBAR,
group: wnd.wrapper.id + "-moving",
dragstart: proxy(that.dragstart, that),
drag: proxy(that.drag, that),
dragend: proxy(that.dragend, that),
dragcancel: proxy(that.dragcancel, that)
});
}
WindowDragging.prototype = {
dragstart: function(e) {
var wnd = this.owner, element = wnd.element, actions = element.find(".k-window-actions"), containerOffset = kendo.getOffset(wnd.appendTo);
wnd.trigger(DRAGSTART);
wnd.initialWindowPosition = kendo.getOffset(wnd.wrapper, "position");
wnd.startPosition = {
left: e.x.client - wnd.initialWindowPosition.left,
top: e.y.client - wnd.initialWindowPosition.top
};
if (actions.length > 0) {
wnd.minLeftPosition = actions.outerWidth() + parseInt(actions.css("right"), 10) - element.outerWidth();
} else {
wnd.minLeftPosition = 20 - element.outerWidth();
}
wnd.minLeftPosition -= containerOffset.left;
wnd.minTopPosition = -containerOffset.top;
wnd.wrapper.append(templates.overlay).find(KWINDOWRESIZEHANDLES).hide();
$(BODY).css(CURSOR, e.currentTarget.css(CURSOR));
},
drag: function(e) {
var wnd = this.owner, coordinates = {
left: Math.max(e.x.client - wnd.startPosition.left, wnd.minLeftPosition),
top: Math.max(e.y.client - wnd.startPosition.top, wnd.minTopPosition)
};
$(wnd.wrapper).css(coordinates);
},
_finishDrag: function() {
var wnd = this.owner;
wnd.wrapper.find(KWINDOWRESIZEHANDLES).toggle(!wnd.options.isMinimized).end().find(KOVERLAY).remove();
$(BODY).css(CURSOR, "");
},
dragcancel: function(e) {
this._finishDrag();
e.currentTarget.closest(KWINDOW).css(this.owner.initialWindowPosition);
},
dragend: function() {
this._finishDrag();
this.owner.trigger(DRAGEND);
return false;
},
destroy: function() {
this._draggable.destroy();
}
};
kendo.ui.plugin(Window);
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var doc = document, kendo = window.kendo, dataviz = kendo.dataviz = {}, Class = kendo.Class, template = kendo.template, map = $.map, noop = $.noop, indexOf = $.inArray, math = Math, deepExtend = kendo.deepExtend;
var renderTemplate = function(definition) {
return template(definition, {
useWithBlock: false,
paramName: "d"
});
};
var CSS_PREFIX = "k-";
// Constants ==============================================================
var ANIMATION_STEP = 10, AXIS_LABEL_CLICK = "axisLabelClick", BASELINE_MARKER_SIZE = 1, BLACK = "#000", BOTTOM = "bottom", CENTER = "center", COORD_PRECISION = 3, CLIP = "clip", DEFAULT_FONT = "12px sans-serif", DEFAULT_HEIGHT = 400, DEFAULT_PRECISION = 6, DEFAULT_WIDTH = 600, DEGREE = math.PI / 180, FADEIN = "fadeIn", FORMAT_REGEX = /\{\d+:?/, HEIGHT = "height", ID_PREFIX = "k", ID_POOL_SIZE = 1e3, ID_START = 1e4, INITIAL_ANIMATION_DURATION = 600, LEFT = "left", LINEAR = "linear", MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, NONE = "none", OUTSIDE = "outside", RADIAL = "radial", RIGHT = "right", SWING = "swing", TOP = "top", UNDEFINED = "undefined", UPPERCASE_REGEX = /([A-Z])/g, WIDTH = "width", WHITE = "#fff", X = "x", Y = "y", ZERO_THRESHOLD = .2;
function getSpacing(value) {
var spacing = {
top: 0,
right: 0,
bottom: 0,
left: 0
};
if (typeof value === "number") {
spacing[TOP] = spacing[RIGHT] = spacing[BOTTOM] = spacing[LEFT] = value;
} else {
spacing[TOP] = value[TOP] || 0;
spacing[RIGHT] = value[RIGHT] || 0;
spacing[BOTTOM] = value[BOTTOM] || 0;
spacing[LEFT] = value[LEFT] || 0;
}
return spacing;
}
// Geometric primitives ===================================================
var Point2D = function(x, y) {
var point = this;
if (!(point instanceof Point2D)) {
return new Point2D(x, y);
}
point.x = round(x || 0, COORD_PRECISION);
point.y = round(y || 0, COORD_PRECISION);
};
Point2D.fn = Point2D.prototype = {
clone: function() {
var point = this;
return new Point2D(point.x, point.y);
}
};
var Box2D = function(x1, y1, x2, y2) {
var box = this;
if (!(box instanceof Box2D)) {
return new Box2D(x1, y1, x2, y2);
}
box.x1 = x1 || 0;
box.x2 = x2 || 0;
box.y1 = y1 || 0;
box.y2 = y2 || 0;
};
Box2D.fn = Box2D.prototype = {
width: function() {
return this.x2 - this.x1;
},
height: function() {
return this.y2 - this.y1;
},
translate: function(dx, dy) {
var box = this;
box.x1 += dx;
box.x2 += dx;
box.y1 += dy;
box.y2 += dy;
return box;
},
move: function(x, y) {
var box = this, height = box.height(), width = box.width();
box.x1 = x;
box.y1 = y;
box.x2 = box.x1 + width;
box.y2 = box.y1 + height;
return box;
},
wrap: function(targetBox) {
var box = this;
box.x1 = math.min(box.x1, targetBox.x1);
box.y1 = math.min(box.y1, targetBox.y1);
box.x2 = math.max(box.x2, targetBox.x2);
box.y2 = math.max(box.y2, targetBox.y2);
return box;
},
wrapPoint: function(point) {
this.wrap(new Box2D(point.x, point.y, point.x, point.y));
return this;
},
snapTo: function(targetBox, axis) {
var box = this;
if (axis == X || !axis) {
box.x1 = targetBox.x1;
box.x2 = targetBox.x2;
}
if (axis == Y || !axis) {
box.y1 = targetBox.y1;
box.y2 = targetBox.y2;
}
return box;
},
alignTo: function(targetBox, anchor) {
var box = this, height = box.height(), width = box.width(), axis = anchor == TOP || anchor == BOTTOM ? Y : X, offset = axis == Y ? height : width;
if (anchor === CENTER) {
var targetCenter = targetBox.center();
var center = box.center();
box.x1 += targetCenter.x - center.x;
box.y1 += targetCenter.y - center.y;
} else if (anchor === TOP || anchor === LEFT) {
box[axis + 1] = targetBox[axis + 1] - offset;
} else {
box[axis + 1] = targetBox[axis + 2];
}
box.x2 = box.x1 + width;
box.y2 = box.y1 + height;
return box;
},
shrink: function(dw, dh) {
var box = this;
box.x2 -= dw;
box.y2 -= dh;
return box;
},
expand: function(dw, dh) {
this.shrink(-dw, -dh);
return this;
},
pad: function(padding) {
var box = this, spacing = getSpacing(padding);
box.x1 -= spacing.left;
box.x2 += spacing.right;
box.y1 -= spacing.top;
box.y2 += spacing.bottom;
return box;
},
unpad: function(padding) {
var box = this, spacing = getSpacing(padding);
spacing.left = -spacing.left;
spacing.top = -spacing.top;
spacing.right = -spacing.right;
spacing.bottom = -spacing.bottom;
return box.pad(spacing);
},
clone: function() {
var box = this;
return new Box2D(box.x1, box.y1, box.x2, box.y2);
},
center: function() {
var box = this;
return {
x: box.x1 + box.width() / 2,
y: box.y1 + box.height() / 2
};
},
containsPoint: function(point) {
var box = this;
return point.x >= box.x1 && point.x <= box.x2 && point.y >= box.y1 && point.y <= box.y2;
},
points: function() {
var box = this;
return [ new Point2D(box.x1, box.y1), new Point2D(box.x2, box.y1), new Point2D(box.x2, box.y2), new Point2D(box.x1, box.y2) ];
},
getHash: function() {
var box = this;
return [ box.x1, box.y1, box.x2, box.y2 ].join(",");
}
};
var Ring = Class.extend({
init: function(center, innerRadius, radius, startAngle, angle) {
var ring = this;
ring.c = center;
ring.ir = innerRadius;
ring.r = radius;
ring.startAngle = startAngle;
ring.angle = angle;
},
clone: function() {
var r = this;
return new Ring(r.c, r.ir, r.r, r.startAngle, r.angle);
},
middle: function() {
return this.startAngle + this.angle / 2;
},
radius: function(newRadius, innerRadius) {
var that = this;
if (innerRadius) {
that.ir = newRadius;
} else {
that.r = newRadius;
}
return that;
},
point: function(angle, innerRadius) {
var ring = this, radianAngle = angle * DEGREE, ax = math.cos(radianAngle), ay = math.sin(radianAngle), radius = innerRadius ? ring.ir : ring.r, x = ring.c.x - ax * radius, y = ring.c.y - ay * radius;
return new Point2D(x, y);
},
getBBox: function() {
var ring = this, box = new Box2D(MAX_VALUE, MAX_VALUE, MIN_VALUE, MIN_VALUE), sa = round(ring.startAngle % 360), ea = round((sa + ring.angle) % 360), innerRadius = ring.ir, allAngles = [ 0, 90, 180, 270, sa, ea ].sort(numericComparer), saIndex = indexOf(sa, allAngles), eaIndex = indexOf(ea, allAngles), angles, i, point;
if (sa == ea) {
angles = allAngles;
} else {
if (saIndex < eaIndex) {
angles = allAngles.slice(saIndex, eaIndex + 1);
} else {
angles = [].concat(allAngles.slice(0, eaIndex + 1), allAngles.slice(saIndex, allAngles.length));
}
}
for (i = 0; i < angles.length; i++) {
point = ring.point(angles[i]);
box.wrapPoint(point);
box.wrapPoint(point, innerRadius);
}
if (!innerRadius) {
box.wrapPoint(ring.c);
}
return box;
},
expand: function(value) {
this.r += value;
return this;
}
});
var Sector = Ring.extend({
init: function(center, radius, startAngle, angle) {
Ring.fn.init.call(this, center, 0, radius, startAngle, angle);
},
expand: function(value) {
return Ring.fn.expand.call(this, value);
},
clone: function() {
var sector = this;
return new Sector(sector.c, sector.r, sector.startAngle, sector.angle);
},
radius: function(newRadius) {
return Ring.fn.radius.call(this, newRadius);
},
point: function(angle) {
return Ring.fn.point.call(this, angle);
}
});
var Pin = Class.extend({
init: function(options) {
deepExtend(this, {
height: 40,
rotation: 90,
radius: 10,
arcAngle: 10
}, options);
}
});
// View-Model primitives ==================================================
var ChartElement = Class.extend({
init: function(options) {
var element = this;
element.children = [];
element.options = deepExtend({}, element.options, options);
},
reflow: function(targetBox) {
var element = this, children = element.children, box, i, currentChild;
for (i = 0; i < children.length; i++) {
currentChild = children[i];
currentChild.reflow(targetBox);
box = box ? box.wrap(currentChild.box) : currentChild.box.clone();
}
element.box = box || targetBox;
},
getViewElements: function(view) {
var element = this, options = element.options, modelId = options.modelId, viewElements = [], root, children = element.children, i, child, childrenCount = children.length;
for (i = 0; i < childrenCount; i++) {
child = children[i];
if (!child.discoverable) {
child.options = child.options || {};
child.options.modelId = modelId;
}
viewElements.push.apply(viewElements, child.getViewElements(view));
}
if (element.discoverable) {
root = element.getRoot();
if (root) {
root.modelMap[modelId] = element;
}
}
return viewElements;
},
enableDiscovery: function() {
var element = this, options = element.options;
options.modelId = IDPool.current.alloc();
element.discoverable = true;
},
destroy: function() {
var element = this, children = element.children, root = element.getRoot(), modelId = element.options.modelId, i;
if (root && modelId) {
if (root.modelMap[modelId]) {
IDPool.current.free(modelId);
}
root.modelMap[modelId] = undefined;
}
for (i = 0; i < children.length; i++) {
children[i].destroy();
}
},
getRoot: function() {
var parent = this.parent;
return parent ? parent.getRoot() : null;
},
translateChildren: function(dx, dy) {
var element = this, children = element.children, childrenCount = children.length, i;
for (i = 0; i < childrenCount; i++) {
children[i].box.translate(dx, dy);
}
},
append: function() {
var element = this, i, length = arguments.length;
append(element.children, arguments);
for (i = 0; i < length; i++) {
arguments[i].parent = element;
}
}
});
var RootElement = ChartElement.extend({
init: function(options) {
var root = this;
// Logical tree ID to element map
root.modelMap = {};
ChartElement.fn.init.call(root, options);
},
options: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
background: WHITE,
border: {
color: BLACK,
width: 0
},
margin: getSpacing(5),
zIndex: -2
},
reflow: function() {
var root = this, options = root.options, children = root.children, currentBox = new Box2D(0, 0, options.width, options.height);
root.box = currentBox.unpad(options.margin);
for (var i = 0; i < children.length; i++) {
children[i].reflow(currentBox);
currentBox = boxDiff(currentBox, children[i].box);
}
},
getViewElements: function(view) {
var root = this, options = root.options, border = options.border || {}, box = root.box.clone().pad(options.margin).unpad(border.width), elements = [ view.createRect(box, {
stroke: border.width ? border.color : "",
strokeWidth: border.width,
dashType: border.dashType,
fill: options.background,
fillOpacity: options.opacity,
zIndex: options.zIndex
}) ];
return elements.concat(ChartElement.fn.getViewElements.call(root, view));
},
getRoot: function() {
return this;
}
});
var BoxElement = ChartElement.extend({
init: function(options) {
ChartElement.fn.init.call(this, options);
},
options: {
align: LEFT,
vAlign: TOP,
margin: {},
padding: {},
border: {
color: BLACK,
width: 0
},
background: "",
shrinkToFit: false,
width: 0,
height: 0,
visible: true
},
reflow: function(targetBox) {
var element = this, box, contentBox, options = element.options, margin = getSpacing(options.margin), padding = getSpacing(options.padding), borderWidth = options.border.width;
function reflowPaddingBox() {
element.align(targetBox, X, options.align);
element.align(targetBox, Y, options.vAlign);
element.paddingBox = box.clone().unpad(margin).unpad(borderWidth);
}
ChartElement.fn.reflow.call(element, targetBox);
if (options.width && options.height) {
box = element.box = new Box2D(0, 0, options.width, options.height);
} else {
box = element.box;
}
if (options.shrinkToFit) {
reflowPaddingBox();
contentBox = element.contentBox = element.paddingBox.clone().unpad(padding);
} else {
contentBox = element.contentBox = box.clone();
box.pad(padding).pad(borderWidth).pad(margin);
reflowPaddingBox();
}
element.translateChildren(box.x1 - contentBox.x1 + margin.left + borderWidth + padding.left, box.y1 - contentBox.y1 + margin.top + borderWidth + padding.top);
},
align: function(targetBox, axis, alignment) {
var element = this, box = element.box, c1 = axis + 1, c2 = axis + 2, sizeFunc = axis === X ? WIDTH : HEIGHT, size = box[sizeFunc]();
if (inArray(alignment, [ LEFT, TOP ])) {
box[c1] = targetBox[c1];
box[c2] = box[c1] + size;
} else if (inArray(alignment, [ RIGHT, BOTTOM ])) {
box[c2] = targetBox[c2];
box[c1] = box[c2] - size;
} else if (alignment == CENTER) {
box[c1] = targetBox[c1] + (targetBox[sizeFunc]() - size) / 2;
box[c2] = box[c1] + size;
}
},
hasBox: function() {
var options = this.options;
return options.border.width || options.background;
},
getViewElements: function(view, renderOptions) {
var boxElement = this, options = boxElement.options, elements = [];
if (!options.visible) {
return [];
}
if (boxElement.hasBox()) {
elements.push(view.createRect(boxElement.paddingBox, deepExtend(boxElement.elementStyle(), renderOptions)));
}
return elements.concat(ChartElement.fn.getViewElements.call(boxElement, view));
},
elementStyle: function() {
var boxElement = this, options = boxElement.options, border = options.border || {};
return {
id: options.id,
stroke: border.width ? border.color : "",
strokeWidth: border.width,
dashType: border.dashType,
strokeOpacity: options.opacity,
fill: options.background,
fillOpacity: options.opacity,
animation: options.animation,
zIndex: options.zIndex,
data: {
modelId: options.modelId
}
};
}
});
var Text = ChartElement.extend({
init: function(content, options) {
var text = this;
ChartElement.fn.init.call(text, options);
// Calculate size
text.content = content;
text.reflow(new Box2D());
},
options: {
font: DEFAULT_FONT,
color: BLACK,
align: LEFT,
vAlign: ""
},
reflow: function(targetBox) {
var text = this, options = text.options, size, margin;
size = options.size = measureText(text.content, {
font: options.font
}, options.rotation);
text.baseline = size.baseline;
if (options.align == LEFT) {
text.box = new Box2D(targetBox.x1, targetBox.y1, targetBox.x1 + size.width, targetBox.y1 + size.height);
} else if (options.align == RIGHT) {
text.box = new Box2D(targetBox.x2 - size.width, targetBox.y1, targetBox.x2, targetBox.y1 + size.height);
} else if (options.align == CENTER) {
margin = (targetBox.width() - size.width) / 2;
text.box = new Box2D(round(targetBox.x1 + margin, COORD_PRECISION), targetBox.y1, round(targetBox.x2 - margin, COORD_PRECISION), targetBox.y1 + size.height);
}
if (options.vAlign == CENTER) {
margin = (targetBox.height() - size.height) / 2;
text.box = new Box2D(text.box.x1, targetBox.y1 + margin, text.box.x2, targetBox.y2 - margin);
} else if (options.vAlign == BOTTOM) {
text.box = new Box2D(text.box.x1, targetBox.y2 - size.height, text.box.x2, targetBox.y2);
} else if (options.vAlign == TOP) {
text.box = new Box2D(text.box.x1, targetBox.y1, text.box.x2, targetBox.y1 + size.height);
}
},
getViewElements: function(view) {
var text = this, options = text.options;
ChartElement.fn.getViewElements.call(this, view);
return [ view.createText(text.content, deepExtend({}, options, {
x: text.box.x1,
y: text.box.y1,
baseline: text.baseline,
data: {
modelId: options.modelId
}
})) ];
}
});
var TextBox = BoxElement.extend({
init: function(content, options) {
var textBox = this, text;
BoxElement.fn.init.call(textBox, options);
options = textBox.options;
text = new Text(content, deepExtend({}, options, {
align: LEFT,
vAlign: TOP
}));
textBox.append(text);
if (textBox.hasBox()) {
text.options.id = uniqueId();
}
// Calculate size
textBox.reflow(new Box2D());
}
});
var Title = ChartElement.extend({
init: function(options) {
var title = this;
ChartElement.fn.init.call(title, options);
options = title.options;
title.append(new TextBox(options.text, deepExtend({}, options, {
vAlign: options.position
})));
},
options: {
color: BLACK,
position: TOP,
align: CENTER,
margin: getSpacing(5),
padding: getSpacing(5)
},
reflow: function(targetBox) {
var title = this;
ChartElement.fn.reflow.call(title, targetBox);
title.box.snapTo(targetBox, X);
}
});
Title.buildTitle = function(options, parent, defaultOptions) {
var title;
if (typeof options === "string") {
options = {
text: options
};
}
options = deepExtend({
visible: true
}, defaultOptions, options);
if (options && options.visible && options.text) {
title = new Title(options);
parent.append(title);
}
return title;
};
var AxisLabel = TextBox.extend({
init: function(value, index, dataItem, options) {
var label = this, text = value;
if (options.template) {
label.template = template(options.template);
text = label.template({
value: value,
dataItem: dataItem
});
} else if (options.format) {
text = label.formatValue(value, options);
}
label.text = text;
label.value = value;
label.index = index;
label.dataItem = dataItem;
TextBox.fn.init.call(label, text, deepExtend({
id: uniqueId()
}, options));
label.enableDiscovery();
},
formatValue: function(value, options) {
return autoFormat(options.format, value);
},
click: function(widget, e) {
var label = this;
widget.trigger(AXIS_LABEL_CLICK, {
element: $(e.target),
value: label.value,
text: label.text,
index: label.index,
dataItem: label.dataItem,
axis: label.parent.options
});
}
});
var Axis = ChartElement.extend({
init: function(options) {
var axis = this;
ChartElement.fn.init.call(axis, options);
if (!axis.options.visible) {
axis.options = deepExtend({}, axis.options, {
labels: {
visible: false
},
line: {
visible: false
},
margin: 0,
majorTickSize: 0,
minorTickSize: 0
});
}
axis.options.minorTicks = deepExtend({}, {
color: axis.options.line.color,
width: axis.options.line.width,
visible: axis.options.minorTickType != NONE
}, axis.options.minorTicks, {
size: axis.options.minorTickSize,
align: axis.options.minorTickType
});
axis.options.majorTicks = deepExtend({}, {
color: axis.options.line.color,
width: axis.options.line.width,
visible: axis.options.majorTickType != NONE
}, axis.options.majorTicks, {
size: axis.options.majorTickSize,
align: axis.options.majorTickType
});
axis.createLabels();
axis.createTitle();
},
options: {
labels: {
visible: true,
rotation: 0,
mirror: false,
step: 1,
skip: 0
},
line: {
width: 1,
color: BLACK,
visible: true
},
title: {
visible: true,
position: CENTER
},
majorTicks: {
align: OUTSIDE,
size: 4
},
minorTicks: {
align: OUTSIDE,
size: 3
},
axisCrossingValue: 0,
majorTickType: OUTSIDE,
minorTickType: NONE,
minorGridLines: {
visible: false,
width: 1,
color: BLACK
},
// TODO: Move to line or labels options
margin: 5,
visible: true,
reverse: false,
justified: true,
_alignLines: true
},
// abstract labelsCount(): Number
// abstract createAxisLabel(index, options): AxisLabel
createLabels: function() {
var axis = this, options = axis.options, align = options.vertical ? RIGHT : CENTER, labelOptions = deepExtend({}, options.labels, {
align: align,
zIndex: options.zIndex,
modelId: options.modelId
}), step = labelOptions.step;
axis.labels = [];
if (labelOptions.visible) {
var labelsCount = axis.labelsCount(), label, i;
for (i = labelOptions.skip; i < labelsCount; i += step) {
label = axis.createAxisLabel(i, labelOptions);
axis.append(label);
axis.labels.push(label);
}
}
},
destroy: function() {
var axis = this, labels = axis.labels, i;
for (i = 0; i < labels.length; i++) {
labels[i].destroy();
}
ChartElement.fn.destroy.call(axis);
},
lineBox: function() {
var axis = this, options = axis.options, box = axis.box, vertical = options.vertical, labels = axis.labels, labelSize = vertical ? HEIGHT : WIDTH, justified = options.justified, mirror = options.labels.mirror, axisX = mirror ? box.x1 : box.x2, axisY = mirror ? box.y2 : box.y1, startMargin = 0, endMargin = options.line.width;
if (justified && labels.length > 1) {
startMargin = labels[0].box[labelSize]() / 2;
endMargin = last(labels).box[labelSize]() / 2;
}
return vertical ? new Box2D(axisX, box.y1 + startMargin, axisX, box.y2 - endMargin) : new Box2D(box.x1 + startMargin, axisY, box.x2 - endMargin, axisY);
},
createTitle: function() {
var axis = this, options = axis.options, titleOptions = deepExtend({
rotation: options.vertical ? -90 : 0,
text: "",
zIndex: 1
}, options.title), title;
if (titleOptions.visible && titleOptions.text) {
title = new TextBox(titleOptions.text, titleOptions);
axis.append(title);
axis.title = title;
}
},
renderTicks: function(view) {
var axis = this, ticks = [], options = axis.options, lineBox = axis.lineBox(), mirror = options.labels.mirror, tickX, tickY, pos, lineOptions;
function render(tickPositions, unit, tick, visible, skipUnit) {
var skip = skipUnit / unit, i, count = tickPositions.length;
if (visible) {
for (i = 0; i < count; i++) {
if (i % skip === 0) {
continue;
}
tickX = mirror ? lineBox.x2 : lineBox.x2 - tick.size;
tickY = mirror ? lineBox.y1 - tick.size : lineBox.y1;
pos = tickPositions[i];
lineOptions = {
strokeWidth: tick.width,
stroke: tick.color,
align: options._alignLines
};
if (options.vertical) {
ticks.push(view.createLine(tickX, pos, tickX + tick.size, pos, lineOptions));
} else {
ticks.push(view.createLine(pos, tickY, pos, tickY + tick.size, lineOptions));
}
}
}
}
render(axis.getMajorTickPositions(), options.majorUnit, options.majorTicks, options.majorTicks.visible);
render(axis.getMinorTickPositions(), options.minorUnit, options.minorTicks, options.minorTicks.visible, options.majorTicks.visible ? options.majorUnit : 0);
return ticks;
},
getViewElements: function(view) {
var axis = this, options = axis.options, line = options.line, lineBox = axis.lineBox(), childElements = ChartElement.fn.getViewElements.call(axis, view), lineOptions;
if (line.width > 0 && line.visible) {
lineOptions = {
strokeWidth: line.width,
stroke: line.color,
dashType: line.dashType,
zIndex: line.zIndex,
align: options._alignLines
};
childElements.push(view.createLine(lineBox.x1, lineBox.y1, lineBox.x2, lineBox.y2, lineOptions));
append(childElements, axis.renderTicks(view));
}
append(childElements, axis.renderPlotBands(view));
return childElements;
},
getActualTickSize: function() {
var axis = this, options = axis.options, tickSize = 0;
if (options.majorTicks.visible && options.minorTicks.visible) {
tickSize = math.max(options.majorTicks.size, options.minorTicks.size);
} else if (options.majorTicks.visible) {
tickSize = options.majorTicks.size;
} else if (options.minorTicks.visible) {
tickSize = options.minorTicks.size;
}
return tickSize;
},
renderPlotBands: function(view) {
var axis = this, options = axis.options, plotBands = options.plotBands || [], vertical = options.vertical, result = [], plotArea = axis.plotArea, slotX, slotY, from, to;
if (plotBands.length) {
result = map(plotBands, function(item) {
from = defined(item.from) ? item.from : MIN_VALUE;
to = defined(item.to) ? item.to : MAX_VALUE;
if (vertical) {
slotX = plotArea.axisX.lineBox();
slotY = axis.getSlot(item.from, item.to);
} else {
slotX = axis.getSlot(item.from, item.to);
slotY = plotArea.axisY.lineBox();
}
return view.createRect(new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2), {
fill: item.color,
fillOpacity: item.opacity,
zIndex: -1
});
});
}
return result;
},
renderGridLines: function(view, altAxis) {
var axis = this, modelId = axis.plotArea.options.modelId, options = axis.options, vertical = options.vertical, lineBox = altAxis.lineBox(), lineStart = lineBox[vertical ? "x1" : "y1"], lineEnd = lineBox[vertical ? "x2" : "y2"], majorTicks = axis.getMajorTickPositions(), gridLines = [], gridLine = function(pos, options) {
return {
pos: pos,
options: options
};
};
if (options.majorGridLines.visible) {
gridLines = map(majorTicks, function(pos) {
return gridLine(pos, options.majorGridLines);
});
}
if (options.minorGridLines.visible) {
gridLines = gridLines.concat(map(axis.getMinorTickPositions(), function(pos) {
if (options.majorGridLines.visible) {
if (!inArray(pos, majorTicks)) {
return gridLine(pos, options.minorGridLines);
}
} else {
return gridLine(pos, options.minorGridLines);
}
}));
}
return map(gridLines, function(line) {
var gridLineOptions = {
data: {
modelId: modelId
},
strokeWidth: line.options.width,
stroke: line.options.color,
dashType: line.options.dashType,
zIndex: -1
}, linePos = round(line.pos), altAxisBox = altAxis.lineBox();
if (vertical) {
if (!altAxis.options.line.visible || altAxisBox.y1 !== linePos) {
return view.createLine(lineStart, linePos, lineEnd, linePos, gridLineOptions);
}
} else {
if (!altAxis.options.line.visible || altAxisBox.x1 !== linePos) {
return view.createLine(linePos, lineStart, linePos, lineEnd, gridLineOptions);
}
}
});
},
reflow: function(box) {
var axis = this, options = axis.options, vertical = options.vertical, labels = axis.labels, count = labels.length, space = axis.getActualTickSize() + options.margin, maxLabelHeight = 0, maxLabelWidth = 0, title = axis.title, label, i;
for (i = 0; i < count; i++) {
label = labels[i];
maxLabelHeight = math.max(maxLabelHeight, label.box.height());
maxLabelWidth = math.max(maxLabelWidth, label.box.width());
}
if (title) {
if (vertical) {
maxLabelWidth += title.box.width();
} else {
maxLabelHeight += title.box.height();
}
}
if (vertical) {
axis.box = new Box2D(box.x1, box.y1, box.x1 + maxLabelWidth + space, box.y2);
} else {
axis.box = new Box2D(box.x1, box.y1, box.x2, box.y1 + maxLabelHeight + space);
}
axis.arrangeTitle();
axis.arrangeLabels(maxLabelWidth, maxLabelHeight);
},
arrangeLabels: function() {
var axis = this, options = axis.options, labelOptions = options.labels, labels = axis.labels, labelsBetweenTicks = !options.justified, vertical = options.vertical, lineBox = axis.lineBox(), mirror = options.labels.mirror, tickPositions = axis.getMajorTickPositions(), labelOffset = axis.getActualTickSize() + options.margin, labelBox, labelY, i;
for (i = 0; i < labels.length; i++) {
var label = labels[i], tickIx = labelOptions.skip + labelOptions.step * i, labelSize = vertical ? label.box.height() : label.box.width(), labelPos = tickPositions[tickIx] - labelSize / 2, firstTickPosition, nextTickPosition, middle, labelX;
if (vertical) {
if (labelsBetweenTicks) {
firstTickPosition = tickPositions[tickIx];
nextTickPosition = tickPositions[tickIx + 1];
middle = firstTickPosition + (nextTickPosition - firstTickPosition) / 2;
labelPos = middle - labelSize / 2;
}
labelX = lineBox.x2;
if (mirror) {
labelX += labelOffset;
} else {
labelX -= labelOffset + label.box.width();
}
labelBox = label.box.move(labelX, labelPos);
} else {
if (labelsBetweenTicks) {
firstTickPosition = tickPositions[tickIx];
nextTickPosition = tickPositions[tickIx + 1];
} else {
firstTickPosition = labelPos;
nextTickPosition = labelPos + labelSize;
}
labelY = lineBox.y1;
if (mirror) {
labelY -= labelOffset + label.box.height();
} else {
labelY += labelOffset;
}
labelBox = new Box2D(firstTickPosition, labelY, nextTickPosition, labelY + label.box.height());
}
label.reflow(labelBox);
}
},
arrangeTitle: function() {
var axis = this, options = axis.options, mirror = options.labels.mirror, vertical = options.vertical, title = axis.title;
if (title) {
if (vertical) {
title.options.align = mirror ? RIGHT : LEFT;
title.options.vAlign = title.options.position;
} else {
title.options.align = title.options.position;
title.options.vAlign = mirror ? TOP : BOTTOM;
}
title.reflow(axis.box);
}
},
alignTo: function(secondAxis) {
var axis = this, lineBox = secondAxis.lineBox(), vertical = axis.options.vertical, pos = vertical ? Y : X;
axis.box.snapTo(lineBox, pos);
if (vertical) {
axis.box.shrink(0, axis.lineBox().height() - lineBox.height());
} else {
axis.box.shrink(axis.lineBox().width() - lineBox.width(), 0);
}
axis.box[pos + 1] -= axis.lineBox()[pos + 1] - lineBox[pos + 1];
axis.box[pos + 2] -= axis.lineBox()[pos + 2] - lineBox[pos + 2];
}
});
var NumericAxis = Axis.extend({
init: function(seriesMin, seriesMax, options) {
var axis = this, defaultOptions = axis.initDefaults(seriesMin, seriesMax, options);
Axis.fn.init.call(axis, defaultOptions);
},
options: {
type: "numeric",
min: 0,
max: 1,
vertical: true,
majorGridLines: {
visible: true,
width: 1,
color: BLACK
},
zIndex: 1
},
initDefaults: function(seriesMin, seriesMax, options) {
var axis = this, narrowRange = options.narrowRange, autoMin = axis.autoAxisMin(seriesMin, seriesMax, narrowRange), autoMax = axis.autoAxisMax(seriesMin, seriesMax, narrowRange), majorUnit = autoMajorUnit(autoMin, autoMax), autoOptions = {
majorUnit: majorUnit
}, userSetLimits;
if (autoMin < 0) {
autoMin -= majorUnit;
}
if (autoMax > 0) {
autoMax += majorUnit;
}
autoOptions.min = floor(autoMin, majorUnit);
autoOptions.max = ceil(autoMax, majorUnit);
if (options) {
userSetLimits = defined(options.min) || defined(options.max);
if (userSetLimits) {
if (options.min === options.max) {
if (options.min > 0) {
options.min = 0;
} else {
options.max = 1;
}
}
}
if (options.majorUnit) {
autoOptions.min = floor(autoOptions.min, options.majorUnit);
autoOptions.max = ceil(autoOptions.max, options.majorUnit);
} else if (userSetLimits) {
options = deepExtend(autoOptions, options);
// Determine an auto major unit after min/max have been set
autoOptions.majorUnit = autoMajorUnit(options.min, options.max);
}
}
autoOptions.minorUnit = (options.majorUnit || autoOptions.majorUnit) / 5;
return deepExtend(autoOptions, options);
},
range: function() {
var options = this.options;
return {
min: options.min,
max: options.max
};
},
autoAxisMax: function(min, max, narrow) {
var axisMax, diff;
if (!min && !max) {
return 1;
}
if (min <= 0 && max <= 0) {
max = min == max ? 0 : max;
diff = math.abs((max - min) / max);
if (!narrow && diff > ZERO_THRESHOLD) {
return 0;
}
axisMax = math.min(0, max - (min - max) / 2);
} else {
min = min == max ? 0 : min;
axisMax = max;
}
return axisMax;
},
autoAxisMin: function(min, max, narrow) {
var axisMin, diff;
if (!min && !max) {
return 0;
}
if (min >= 0 && max >= 0) {
min = min == max ? 0 : min;
diff = (max - min) / max;
if (!narrow && diff > ZERO_THRESHOLD) {
return 0;
}
axisMin = math.max(0, min - (max - min) / 2);
} else {
max = min == max ? 0 : max;
axisMin = min;
}
return axisMin;
},
getDivisions: function(stepValue) {
var options = this.options, range = options.max - options.min;
return math.floor(round(range / stepValue, COORD_PRECISION)) + 1;
},
getTickPositions: function(stepValue) {
var axis = this, options = axis.options, vertical = options.vertical, reverse = options.reverse, lineBox = axis.lineBox(), lineSize = vertical ? lineBox.height() : lineBox.width(), range = options.max - options.min, scale = lineSize / range, step = stepValue * scale, divisions = axis.getDivisions(stepValue), dir = (vertical ? -1 : 1) * (reverse ? -1 : 1), startEdge = dir === 1 ? 1 : 2, pos = lineBox[(vertical ? Y : X) + startEdge], positions = [], i;
for (i = 0; i < divisions; i++) {
positions.push(round(pos, COORD_PRECISION));
pos = pos + step * dir;
}
return positions;
},
getMajorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.majorUnit);
},
getMinorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.minorUnit);
},
getSlot: function(a, b) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineSize = vertical ? lineBox.height() : lineBox.width(), dir = reverse ? -1 : 1, step = dir * (lineSize / (options.max - options.min)), p1, p2, slotBox = new Box2D(lineBox.x1, lineBox.y1, lineBox.x1, lineBox.y1);
if (!defined(a)) {
a = b || 0;
}
if (!defined(b)) {
b = a || 0;
}
a = math.max(math.min(a, options.max), options.min);
b = math.max(math.min(b, options.max), options.min);
if (vertical) {
p1 = options.max - math.max(a, b);
p2 = options.max - math.min(a, b);
} else {
p1 = math.min(a, b) - options.min;
p2 = math.max(a, b) - options.min;
}
slotBox[valueAxis + 1] = lineStart + step * (reverse ? p2 : p1);
slotBox[valueAxis + 2] = lineStart + step * (reverse ? p1 : p2);
return slotBox;
},
getValue: function(point) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, max = options.max * 1, min = options.min * 1, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineSize = vertical ? lineBox.height() : lineBox.width(), dir = reverse ? -1 : 1, offset = dir * (point[valueAxis] - lineStart), step = (max - min) / lineSize, valueOffset = offset * step, value;
if (offset < 0 || offset > lineSize) {
return null;
}
value = vertical ? max - valueOffset : min + valueOffset;
return round(value, DEFAULT_PRECISION);
},
translateRange: function(delta) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), vertical = options.vertical, reverse = options.reverse, size = vertical ? lineBox.height() : lineBox.width(), range = options.max - options.min, scale = size / range, offset = round(delta / scale, DEFAULT_PRECISION);
if ((vertical || reverse) && !(vertical && reverse)) {
offset = -offset;
}
return {
min: options.min + offset,
max: options.max + offset
};
},
scaleRange: function(delta) {
var axis = this, options = axis.options, offset = -delta * options.majorUnit;
return {
min: options.min - offset,
max: options.max + offset
};
},
labelsCount: function() {
return this.getDivisions(this.options.majorUnit);
},
createAxisLabel: function(index, labelOptions) {
var axis = this, options = axis.options, value = round(options.min + index * options.majorUnit, DEFAULT_PRECISION);
return new AxisLabel(value, index, null, labelOptions);
}
});
// View base classes ======================================================
var ViewElement = Class.extend({
init: function(options) {
var element = this;
element.children = [];
element.options = deepExtend({}, element.options, options);
},
render: function() {
return this.template(this);
},
renderContent: function() {
var element = this, output = "", sortedChildren = element.sortChildren(), childrenCount = sortedChildren.length, i;
for (i = 0; i < childrenCount; i++) {
output += sortedChildren[i].render();
}
return output;
},
sortChildren: function() {
var element = this, children = element.children, length, i;
for (i = 0, length = children.length; i < length; i++) {
children[i]._childIndex = i;
}
return children.slice(0).sort(element.compareChildren);
},
refresh: $.noop,
compareChildren: function(a, b) {
var aValue = a.options.zIndex || 0, bValue = b.options.zIndex || 0;
if (aValue !== bValue) {
return aValue - bValue;
}
return a._childIndex - b._childIndex;
},
renderId: function() {
var element = this, result = "";
if (element.options.id) {
result = element.renderAttr("id", element.options.id);
}
return result;
},
renderAttr: function(name, value) {
return defined(value) ? " " + name + "='" + value + "' " : "";
},
renderDataAttributes: function() {
var element = this, data = element.options.data, key, attr, output = "";
for (key in data) {
attr = "data-" + key.replace(UPPERCASE_REGEX, "-$1").toLowerCase();
output += element.renderAttr(attr, data[key]);
}
return output;
}
});
var ViewBase = ViewElement.extend({
init: function(options) {
var view = this;
ViewElement.fn.init.call(view, options);
view.definitions = {};
view.decorators = [];
view.animations = [];
},
destroy: function() {
var animations = this.animations;
while (animations.length > 0) {
animations.shift().destroy();
}
},
renderDefinitions: function() {
var definitions = this.definitions, definitionId, output = "";
for (definitionId in definitions) {
if (definitions.hasOwnProperty(definitionId)) {
output += definitions[definitionId].render();
}
}
return output;
},
decorate: function(element) {
var decorators = this.decorators, i, length = decorators.length, currentDecorator;
for (i = 0; i < length; i++) {
currentDecorator = decorators[i];
this._decorateChildren(currentDecorator, element);
element = currentDecorator.decorate.call(currentDecorator, element);
}
return element;
},
_decorateChildren: function(decorator, element) {
var view = this, children = element.children, i, length = children.length;
for (i = 0; i < length; i++) {
view._decorateChildren(decorator, children[i]);
children[i] = decorator.decorate.call(decorator, children[i]);
}
},
setupAnimations: function() {
for (var i = 0; i < this.animations.length; i++) {
this.animations[i].setup();
}
},
playAnimations: function() {
for (var i = 0; i < this.animations.length; i++) {
this.animations[i].play();
}
},
buildGradient: function(options) {
var view = this, cache = view._gradientCache, hashCode, overlay, definition;
if (!cache) {
cache = view._gradientCache = [];
}
if (options) {
hashCode = getHash(options);
overlay = cache[hashCode];
definition = dataviz.Gradients[options.gradient];
if (!overlay && definition) {
overlay = deepExtend({
id: uniqueId()
}, definition, options);
cache[hashCode] = overlay;
}
}
return overlay;
},
setDefaults: function(options) {
var viewOptions = this.options;
options = options || {};
if (!defined(options.inline)) {
options.inline = viewOptions.inline;
}
if (!defined(options.align)) {
options.align = viewOptions.align;
}
return options;
}
});
dataviz.Gradients = {
glass: {
type: LINEAR,
rotation: 0,
stops: [ {
offset: 0,
color: WHITE,
opacity: 0
}, {
offset: .25,
color: WHITE,
opacity: .3
}, {
offset: 1,
color: WHITE,
opacity: 0
} ]
},
sharpBevel: {
type: RADIAL,
stops: [ {
offset: 0,
color: WHITE,
opacity: .55
}, {
offset: .65,
color: WHITE,
opacity: 0
}, {
offset: .95,
color: WHITE,
opacity: .25
} ]
},
roundedBevel: {
type: RADIAL,
stops: [ {
offset: .33,
color: WHITE,
opacity: .06
}, {
offset: .83,
color: WHITE,
opacity: .2
}, {
offset: .95,
color: WHITE,
opacity: 0
} ]
},
roundedGlass: {
type: RADIAL,
supportVML: false,
stops: [ {
offset: 0,
color: WHITE,
opacity: 0
}, {
offset: .5,
color: WHITE,
opacity: .3
}, {
offset: .99,
color: WHITE,
opacity: 0
} ]
},
sharpGlass: {
type: RADIAL,
supportVML: false,
stops: [ {
offset: 0,
color: WHITE,
opacity: .2
}, {
offset: .15,
color: WHITE,
opacity: .15
}, {
offset: .17,
color: WHITE,
opacity: .35
}, {
offset: .85,
color: WHITE,
opacity: .05
}, {
offset: .87,
color: WHITE,
opacity: .15
}, {
offset: .99,
color: WHITE,
opacity: 0
} ]
}
};
// Animations =============================================================
var ElementAnimation = Class.extend({
init: function(element, options) {
var anim = this;
anim.options = deepExtend({}, anim.options, options);
anim.element = element;
},
options: {
duration: INITIAL_ANIMATION_DURATION,
easing: SWING
},
play: function() {
var anim = this, options = anim.options, element = anim.element, elementId = element.options.id, domElement, delay = options.delay || 0, start = +new Date() + delay, duration = options.duration, finish = start + duration, easing = $.easing[options.easing], wallTime, time, pos, easingPos;
setTimeout(function() {
var loop = function() {
if (anim._stopped) {
return;
}
wallTime = +new Date();
time = math.min(wallTime - start, duration);
pos = time / duration;
easingPos = easing(pos, time, 0, 1, duration);
anim.step(easingPos);
if (!domElement || detached(domElement)) {
domElement = getElement(elementId);
}
element.refresh(domElement);
if (wallTime < finish) {
dataviz.requestFrame(loop);
} else {
anim.destroy();
}
};
loop();
}, delay);
},
abort: function() {
this._stopped = true;
},
destroy: function() {
this.abort();
},
setup: noop,
step: noop
});
var FadeAnimation = ElementAnimation.extend({
options: {
duration: 200,
easing: LINEAR
},
setup: function() {
var anim = this, options = anim.element.options;
anim.targetFillOpacity = options.fillOpacity;
anim.targetStrokeOpacity = options.strokeOpacity;
options.fillOpacity = options.strokeOpacity = 0;
},
step: function(pos) {
var anim = this, options = anim.element.options;
options.fillOpacity = pos * anim.targetFillOpacity;
options.strokeOpacity = pos * anim.targetStrokeOpacity;
}
});
var ExpandAnimation = ElementAnimation.extend({
options: {
size: 0,
easing: LINEAR
},
setup: function() {
var points = this.element.points;
points[1].x = points[2].x = points[0].x;
},
step: function(pos) {
var options = this.options, size = interpolateValue(0, options.size, pos), points = this.element.points;
// Expands rectangle to the right
points[1].x = points[2].x = points[0].x + size;
},
destroy: function() {
ElementAnimation.fn.destroy.call(this);
// Unwrap all child elements
this.element.destroy();
}
});
var RotationAnimation = ElementAnimation.extend({
options: {
easing: LINEAR,
duration: 900
},
setup: function() {
var anim = this, element = anim.element, elementOptions = element.options, options = anim.options, center = options.center, start, end;
if (elementOptions.rotation) {
start = options.startAngle;
end = elementOptions.rotation[0];
options.duration = math.max(math.abs(start - end) / options.speed * 1e3, 1);
anim.endState = end;
elementOptions.rotation = [ start, center.x, center.y ];
}
},
step: function(pos) {
var anim = this, element = anim.element;
if (element.options.rotation) {
element.options.rotation[0] = interpolateValue(anim.options.startAngle, anim.endState, pos);
}
}
});
var BarAnimation = ElementAnimation.extend({
options: {
easing: SWING
},
setup: function() {
var anim = this, element = anim.element, points = element.points, options = element.options, axis = options.vertical ? Y : X, stackBase = options.stackBase, aboveAxis = options.aboveAxis, startPosition, endState = anim.endState = {
top: points[0].y,
right: points[1].x,
bottom: points[3].y,
left: points[0].x
};
if (axis === Y) {
startPosition = defined(stackBase) ? stackBase : endState[aboveAxis ? BOTTOM : TOP];
} else {
startPosition = defined(stackBase) ? stackBase : endState[aboveAxis ? LEFT : RIGHT];
}
anim.startPosition = startPosition;
updateArray(points, axis, startPosition);
},
step: function(pos) {
var anim = this, startPosition = anim.startPosition, endState = anim.endState, element = anim.element, points = element.points;
if (element.options.vertical) {
points[0].y = points[1].y = interpolateValue(startPosition, endState.top, pos);
points[2].y = points[3].y = interpolateValue(startPosition, endState.bottom, pos);
} else {
points[0].x = points[3].x = interpolateValue(startPosition, endState.left, pos);
points[1].x = points[2].x = interpolateValue(startPosition, endState.right, pos);
}
}
});
var BarIndicatorAnimatin = ElementAnimation.extend({
options: {
easing: SWING,
duration: 1e3
},
setup: function() {
var anim = this, element = anim.element, points = element.points, options = element.options.animation, vertical = options.vertical, reverse = options.reverse, axis = anim.axis = vertical ? "y" : "x", start, end, pos, endPosition = anim.options.endPosition, initialState = anim.initialState = {
top: points[0].y,
right: points[1].x,
bottom: points[3].y,
left: points[0].x
}, initial = !defined(anim.options.endPosition);
if (vertical) {
pos = reverse ? "y2" : "y1";
start = initialState[initial && !reverse ? BOTTOM : TOP];
end = initial ? initialState[reverse ? BOTTOM : TOP] : endPosition[pos];
} else {
pos = reverse ? "x1" : "x2";
start = initialState[initial && !reverse ? LEFT : RIGHT];
end = initial ? initialState[reverse ? LEFT : RIGHT] : endPosition[pos];
}
anim.start = start;
anim.end = end;
if (initial) {
updateArray(points, axis, anim.start);
} else if (options.speed) {
anim.options.duration = math.max(math.abs(anim.start - anim.end) / options.speed * 1e3, 1);
}
},
step: function(pos) {
var anim = this, start = anim.start, end = anim.end, element = anim.element, points = element.points, axis = anim.axis;
if (element.options.animation.vertical) {
points[0][axis] = points[1][axis] = interpolateValue(start, end, pos);
} else {
points[1][axis] = points[2][axis] = interpolateValue(start, end, pos);
}
}
});
var ArrowAnimation = ElementAnimation.extend({
options: {
easing: SWING,
duration: 1e3
},
setup: function() {
var anim = this, element = anim.element, points = element.points, options = element.options.animation, vertical = options.vertical, reverse = options.reverse, axis = vertical ? "y" : "x", startPos = axis + (reverse ? "1" : "2"), endPos = axis + (reverse ? "2" : "1"), startPosition = options.startPosition[vertical ? startPos : endPos], halfSize = options.size / 2, count = points.length, initial = !defined(anim.options.endPosition), padding = halfSize, point, end, i;
anim.axis = axis;
anim.endPositions = [];
anim.startPositions = [];
if (!initial) {
startPosition = points[1][axis];
end = anim.options.endPosition[vertical ? endPos : startPos];
if (options.speed) {
anim.options.duration = math.max(math.abs(startPosition - end) / options.speed * 1e3, 1);
}
}
for (i = 0; i < count; i++) {
point = deepExtend({}, points[i]);
if (initial) {
anim.endPositions[i] = point[axis];
points[i][axis] = startPosition - padding;
} else {
anim.endPositions[i] = end - padding;
}
anim.startPositions[i] = points[i][axis];
padding -= halfSize;
}
},
step: function(pos) {
var anim = this, startPositions = anim.startPositions, endPositions = anim.endPositions, element = anim.element, points = element.points, axis = anim.axis, count = points.length, i;
for (i = 0; i < count; i++) {
points[i][axis] = interpolateValue(startPositions[i], endPositions[i], pos);
}
}
});
function animationDecorator(animationName, animationType) {
return Class.extend({
init: function(view) {
this.view = view;
},
decorate: function(element) {
var decorator = this, view = decorator.view, animation = element.options.animation, animationObject;
if (animation && animation.type === animationName && view.options.transitions) {
animationObject = element._animation = new animationType(element, animation);
view.animations.push(animationObject);
}
return element;
}
});
}
var FadeAnimationDecorator = animationDecorator(FADEIN, FadeAnimation);
// Helper functions========================================================
var Color = function(value) {
var color = this, formats = Color.formats, re, processor, parts, i, channels;
if (arguments.length === 1) {
value = color.resolveColor(value);
for (i = 0; i < formats.length; i++) {
re = formats[i].re;
processor = formats[i].process;
parts = re.exec(value);
if (parts) {
channels = processor(parts);
color.r = channels[0];
color.g = channels[1];
color.b = channels[2];
}
}
} else {
color.r = arguments[0];
color.g = arguments[1];
color.b = arguments[2];
}
color.r = color.normalizeByte(color.r);
color.g = color.normalizeByte(color.g);
color.b = color.normalizeByte(color.b);
};
Color.prototype = {
toHex: function() {
var color = this, pad = color.padDigit, r = color.r.toString(16), g = color.g.toString(16), b = color.b.toString(16);
return "#" + pad(r) + pad(g) + pad(b);
},
resolveColor: function(value) {
value = value || BLACK;
if (value.charAt(0) == "#") {
value = value.substr(1, 6);
}
value = value.replace(/ /g, "");
value = value.toLowerCase();
value = Color.namedColors[value] || value;
return value;
},
normalizeByte: function(value) {
return value < 0 || isNaN(value) ? 0 : value > 255 ? 255 : value;
},
padDigit: function(value) {
return value.length === 1 ? "0" + value : value;
},
brightness: function(value) {
var color = this, round = math.round;
color.r = round(color.normalizeByte(color.r * value));
color.g = round(color.normalizeByte(color.g * value));
color.b = round(color.normalizeByte(color.b * value));
return color;
},
percBrightness: function() {
var color = this;
return math.sqrt(.241 * color.r * color.r + .691 * color.g * color.g + .068 * color.b * color.b);
}
};
Color.formats = [ {
re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
process: function(parts) {
return [ parseInt(parts[1], 10), parseInt(parts[2], 10), parseInt(parts[3], 10) ];
}
}, {
re: /^(\w{2})(\w{2})(\w{2})$/,
process: function(parts) {
return [ parseInt(parts[1], 16), parseInt(parts[2], 16), parseInt(parts[3], 16) ];
}
}, {
re: /^(\w{1})(\w{1})(\w{1})$/,
process: function(parts) {
return [ parseInt(parts[1] + parts[1], 16), parseInt(parts[2] + parts[2], 16), parseInt(parts[3] + parts[3], 16) ];
}
} ];
Color.namedColors = {
aqua: "00ffff",
azure: "f0ffff",
beige: "f5f5dc",
black: "000000",
blue: "0000ff",
brown: "a52a2a",
coral: "ff7f50",
cyan: "00ffff",
darkblue: "00008b",
darkcyan: "008b8b",
darkgray: "a9a9a9",
darkgreen: "006400",
darkorange: "ff8c00",
darkred: "8b0000",
dimgray: "696969",
fuchsia: "ff00ff",
gold: "ffd700",
goldenrod: "daa520",
gray: "808080",
green: "008000",
greenyellow: "adff2f",
indigo: "4b0082",
ivory: "fffff0",
khaki: "f0e68c",
lightblue: "add8e6",
lightgrey: "d3d3d3",
lightgreen: "90ee90",
lightpink: "ffb6c1",
lightyellow: "ffffe0",
lime: "00ff00",
limegreen: "32cd32",
linen: "faf0e6",
magenta: "ff00ff",
maroon: "800000",
mediumblue: "0000cd",
navy: "000080",
olive: "808000",
orange: "ffa500",
orangered: "ff4500",
orchid: "da70d6",
pink: "ffc0cb",
plum: "dda0dd",
purple: "800080",
red: "ff0000",
royalblue: "4169e1",
salmon: "fa8072",
silver: "c0c0c0",
skyblue: "87ceeb",
slateblue: "6a5acd",
slategray: "708090",
snow: "fffafa",
steelblue: "4682b4",
tan: "d2b48c",
teal: "008080",
tomato: "ff6347",
turquoise: "40e0d0",
violet: "ee82ee",
wheat: "f5deb3",
white: "ffffff",
whitesmoke: "f5f5f5",
yellow: "ffff00",
yellowgreen: "9acd32"
};
var IDPool = Class.extend({
init: function(size, prefix, start) {
this._pool = [];
this._freed = {};
this._size = size;
this._id = start;
this._prefix = prefix;
},
alloc: function() {
var that = this, pool = that._pool, id;
if (pool.length > 0) {
id = pool.pop();
that._freed[id] = false;
} else {
id = that._prefix + that._id++;
}
return id;
},
free: function(id) {
var that = this, pool = that._pool, freed = that._freed;
if (pool.length < that._size && !freed[id]) {
pool.push(id);
freed[id] = true;
}
}
});
IDPool.current = new IDPool(ID_POOL_SIZE, ID_PREFIX, ID_START);
var LRUCache = Class.extend({
init: function(size) {
this._size = size;
this._length = 0;
this._map = {};
},
put: function(key, value) {
var lru = this, map = lru._map, entry = {
key: key,
value: value
};
map[key] = entry;
if (!lru._head) {
lru._head = lru._tail = entry;
} else {
lru._tail.newer = entry;
entry.older = lru._tail;
lru._tail = entry;
}
if (lru._length >= lru._size) {
map[lru._head.key] = null;
lru._head = lru._head.newer;
lru._head.older = null;
} else {
lru._length++;
}
},
get: function(key) {
var lru = this, entry = lru._map[key];
if (entry) {
if (entry === lru._head && entry !== lru._tail) {
lru._head = entry.newer;
lru._head.older = null;
}
if (entry !== lru._tail) {
if (entry.older) {
entry.older.newer = entry.newer;
entry.newer.older = entry.older;
}
entry.older = lru._tail;
entry.newer = null;
lru._tail.newer = entry;
lru._tail = entry;
}
return entry.value;
}
}
});
function measureText(text, style, rotation) {
var styleHash = getHash(style), cacheKey = text + styleHash + rotation, cachedResult = measureText.cache.get(cacheKey), size = {
width: 0,
height: 0,
baseline: 0
};
if (cachedResult) {
return cachedResult;
}
var measureBox = measureText.measureBox, baselineMarker = measureText.baselineMarker.cloneNode(false);
if (!measureBox) {
measureBox = measureText.measureBox = $("<div style='position: absolute; top: -4000px; left: -4000px;" + "line-height: normal; visibility: hidden;' />").appendTo(doc.body)[0];
}
for (var styleKey in style) {
measureBox.style[styleKey] = style[styleKey];
}
measureBox.innerHTML = text;
measureBox.appendChild(baselineMarker);
if ((text + "").length) {
size = {
width: measureBox.offsetWidth - BASELINE_MARKER_SIZE,
height: measureBox.offsetHeight,
baseline: baselineMarker.offsetTop + BASELINE_MARKER_SIZE
};
}
if (rotation) {
var width = size.width, height = size.height, cx = width / 2, cy = height / 2, r1 = rotatePoint(0, 0, cx, cy, rotation), r2 = rotatePoint(width, 0, cx, cy, rotation), r3 = rotatePoint(width, height, cx, cy, rotation), r4 = rotatePoint(0, height, cx, cy, rotation);
size.normalWidth = width;
size.normalHeight = height;
size.width = math.max(r1.x, r2.x, r3.x, r4.x) - math.min(r1.x, r2.x, r3.x, r4.x);
size.height = math.max(r1.y, r2.y, r3.y, r4.y) - math.min(r1.y, r2.y, r3.y, r4.y);
}
measureText.cache.put(cacheKey, size);
return size;
}
measureText.cache = new LRUCache(1e3);
measureText.baselineMarker = $("<div class='" + CSS_PREFIX + "baseline-marker' " + "style='display: inline-block; vertical-align: baseline;" + "width: " + BASELINE_MARKER_SIZE + "px; height: " + BASELINE_MARKER_SIZE + "px;" + "overflow: hidden;' />")[0];
function autoMajorUnit(min, max) {
var diff = max - min;
if (diff === 0) {
if (max === 0) {
return .1;
}
diff = math.abs(max);
}
var scale = math.pow(10, math.floor(math.log(diff) / math.log(10))), relativeValue = round(diff / scale, DEFAULT_PRECISION), scaleMultiplier = 1;
if (relativeValue < 1.904762) {
scaleMultiplier = .2;
} else if (relativeValue < 4.761904) {
scaleMultiplier = .5;
} else if (relativeValue < 9.523809) {
scaleMultiplier = 1;
} else {
scaleMultiplier = 2;
}
return round(scale * scaleMultiplier, DEFAULT_PRECISION);
}
function getHash(object) {
var hash = [];
for (var key in object) {
hash.push(key + object[key]);
}
return hash.sort().join(" ");
}
function uniqueId() {
return IDPool.current.alloc();
}
function rotatePoint(x, y, cx, cy, angle) {
var theta = angle * DEGREE;
return {
x: cx + (x - cx) * math.cos(theta) + (y - cy) * math.sin(theta),
y: cy - (x - cx) * math.sin(theta) + (y - cy) * math.cos(theta)
};
}
function boxDiff(r, s) {
if (r.x1 == s.x1 && r.y1 == s.y1 && r.x2 == s.x2 && r.y2 == s.y2) {
return s;
}
var a = math.min(r.x1, s.x1), b = math.max(r.x1, s.x1), c = math.min(r.x2, s.x2), d = math.max(r.x2, s.x2), e = math.min(r.y1, s.y1), f = math.max(r.y1, s.y1), g = math.min(r.y2, s.y2), h = math.max(r.y2, s.y2), result = [];
// X = intersection, 0-7 = possible difference areas
// h +-+-+-+
// . |5|6|7|
// g +-+-+-+
// . |3|X|4|
// f +-+-+-+
// . |0|1|2|
// e +-+-+-+
// . a b c d
// we'll always have rectangles 1, 3, 4 and 6
result[0] = Box2D(b, e, c, f);
result[1] = Box2D(a, f, b, g);
result[2] = Box2D(c, f, d, g);
result[3] = Box2D(b, g, c, h);
// decide which corners
if (r.x1 == a && r.y1 == e || s.x1 == a && s.y1 == e) {
// corners 0 and 7
result[4] = Box2D(a, e, b, f);
result[5] = Box2D(c, g, d, h);
} else {
// corners 2 and 5
result[4] = Box2D(c, e, d, f);
result[5] = Box2D(a, g, b, h);
}
return $.grep(result, function(box) {
return box.height() > 0 && box.width() > 0;
})[0];
}
function supportsSVG() {
return doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
}
var requestFrameFn = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
setTimeout(callback, ANIMATION_STEP);
};
dataviz.requestFrame = function(callback, delay) {
return requestFrameFn(callback, delay);
};
function inArray(value, array) {
return indexOf(value, array) != -1;
}
function last(array) {
return array[array.length - 1];
}
function append(first, second) {
[].push.apply(first, second);
}
function ceil(value, step) {
return round(math.ceil(value / step) * step, DEFAULT_PRECISION);
}
function floor(value, step) {
return round(math.floor(value / step) * step, DEFAULT_PRECISION);
}
function round(value, precision) {
var power = math.pow(10, precision || 0);
return math.round(value * power) / power;
}
function interpolateValue(start, end, progress) {
return round(start + (end - start) * progress, COORD_PRECISION);
}
function defined(value) {
return typeof value !== UNDEFINED;
}
function numericComparer(a, b) {
return a - b;
}
function updateArray(arr, prop, value) {
var i, length = arr.length;
for (i = 0; i < length; i++) {
arr[i][prop] = value;
}
}
function autoFormat(format, value) {
if (format.match(FORMAT_REGEX)) {
return kendo.format.apply(this, arguments);
}
return kendo.toString(value, format);
}
function getElement(modelId) {
return doc.getElementById(modelId);
}
function detached(element) {
var parent = element.parentNode;
while (parent && parent.parentNode) {
parent = parent.parentNode;
}
return parent !== doc;
}
// Exports ================================================================
/**
* @name kendo.dataviz
* @namespace Contains Kendo DataViz.
*/
deepExtend(kendo.dataviz, {
init: function(element) {
kendo.init(element, kendo.dataviz.ui);
},
/**
* @name kendo.dataviz.ui
* @namespace Contains Kendo DataViz UI widgets.
*/
ui: {
roles: {},
themes: {},
views: [],
defaultView: function() {
var i, views = dataviz.ui.views, length = views.length;
for (i = 0; i < length; i++) {
if (views[i].available()) {
return views[i];
}
}
kendo.logToConsole("Warning: KendoUI DataViz cannot render. Possible causes:\n" + "- The browser does not support SVG or VML. User agent: " + navigator.userAgent + "\n" + "- The kendo.dataviz.svg.js or kendo.dataviz.vml.js scripts are not loaded");
},
registerView: function(viewType) {
var defaultView = dataviz.ui.views[0];
if (!defaultView || viewType.preference > defaultView.preference) {
dataviz.ui.views.unshift(viewType);
} else {
dataviz.ui.views.push(viewType);
}
},
plugin: function(widget) {
kendo.ui.plugin(widget, dataviz.ui);
}
},
AXIS_LABEL_CLICK: AXIS_LABEL_CLICK,
COORD_PRECISION: COORD_PRECISION,
DEFAULT_PRECISION: DEFAULT_PRECISION,
DEFAULT_WIDTH: DEFAULT_WIDTH,
DEFAULT_HEIGHT: DEFAULT_HEIGHT,
DEFAULT_FONT: DEFAULT_FONT,
INITIAL_ANIMATION_DURATION: INITIAL_ANIMATION_DURATION,
CLIP: CLIP,
Axis: Axis,
AxisLabel: AxisLabel,
Box2D: Box2D,
BoxElement: BoxElement,
ChartElement: ChartElement,
Color: Color,
ElementAnimation: ElementAnimation,
ExpandAnimation: ExpandAnimation,
ArrowAnimation: ArrowAnimation,
BarAnimation: BarAnimation,
BarIndicatorAnimatin: BarIndicatorAnimatin,
FadeAnimation: FadeAnimation,
FadeAnimationDecorator: FadeAnimationDecorator,
IDPool: IDPool,
LRUCache: LRUCache,
NumericAxis: NumericAxis,
Point2D: Point2D,
Ring: Ring,
Pin: Pin,
RootElement: RootElement,
RotationAnimation: RotationAnimation,
Sector: Sector,
Text: Text,
TextBox: TextBox,
Title: Title,
ViewBase: ViewBase,
ViewElement: ViewElement,
animationDecorator: animationDecorator,
append: append,
autoFormat: autoFormat,
autoMajorUnit: autoMajorUnit,
boxDiff: boxDiff,
defined: defined,
getElement: getElement,
getSpacing: getSpacing,
inArray: inArray,
interpolateValue: interpolateValue,
last: last,
measureText: measureText,
rotatePoint: rotatePoint,
round: round,
ceil: ceil,
floor: floor,
supportsSVG: supportsSVG,
renderTemplate: renderTemplate,
uniqueId: uniqueId
});
})(window.kendo.jQuery);
(function() {
// Imports ================================================================
var kendo = window.kendo, ui = kendo.dataviz.ui, deepExtend = kendo.deepExtend;
// Constants ==============================================================
var BAR_GAP = 1.5, BAR_SPACING = .4, BLACK = "#000", SANS = "Arial,Helvetica,sans-serif", SANS11 = "11px " + SANS, SANS12 = "12px " + SANS, SANS16 = "16px " + SANS, WHITE = "#fff";
var chartBaseTheme = {
title: {
font: SANS16
},
legend: {
labels: {
font: SANS12
}
},
seriesDefaults: {
labels: {
font: SANS11
},
donut: {
margin: 1
},
line: {
width: 4
},
area: {
line: {
opacity: 1,
width: 0
}
},
candlestick: {
line: {
width: 1,
color: BLACK
},
border: {
width: 1,
_brightness: .8
},
gap: 1,
spacing: .3,
downColor: WHITE,
aggregate: {
open: "max",
high: "max",
low: "min",
close: "max"
},
highlight: {
line: {
width: 2
},
border: {
width: 2,
opacity: 1
}
}
},
ohlc: {
line: {
width: 1
},
gap: 1,
spacing: .3,
aggregate: {
open: "max",
high: "max",
low: "min",
close: "max"
},
highlight: {
line: {
width: 3,
opacity: 1
}
}
},
bubble: {
opacity: .6,
border: {
width: 0
},
labels: {
background: "transparent"
}
},
bar: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
column: {
gap: BAR_GAP,
spacing: BAR_SPACING
},
bullet: {
gap: BAR_GAP,
spacing: BAR_SPACING,
target: {
color: "#ff0000"
}
},
verticalBullet: {
gap: BAR_GAP,
spacing: BAR_SPACING,
target: {
color: "#ff0000"
}
}
},
categoryAxis: {
majorGridLines: {
visible: true
}
},
axisDefaults: {
labels: {
font: SANS12
},
title: {
font: SANS16,
margin: 5
},
crosshair: {
tooltip: {
font: SANS12
}
}
},
tooltip: {
font: SANS12
},
navigator: {
pane: {
height: 90,
margin: {
top: 10
}
}
}
};
var gaugeBaseTheme = {
scale: {
labels: {
font: SANS12
}
}
};
var themes = ui.themes, registerTheme = ui.registerTheme = function(themeName, options) {
var result = {};
// Apply base theme
result.chart = deepExtend({}, chartBaseTheme, options.chart);
result.gauge = deepExtend({}, gaugeBaseTheme, options.gauge);
// Copy the line/area chart settings for their vertical counterparts
var defaults = result.chart.seriesDefaults;
defaults.verticalLine = deepExtend({}, defaults.line);
defaults.verticalArea = deepExtend({}, defaults.area);
themes[themeName] = result;
};
registerTheme("black", {
chart: {
title: {
color: WHITE
},
legend: {
labels: {
color: WHITE
}
},
seriesDefaults: {
labels: {
color: WHITE
},
pie: {
overlay: {
gradient: "sharpBevel"
}
},
donut: {
overlay: {
gradient: "sharpGlass"
}
},
line: {
markers: {
background: "#3d3d3d"
}
},
scatter: {
markers: {
background: "#3d3d3d"
}
},
scatterLine: {
markers: {
background: "#3d3d3d"
}
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#555",
line: {
color: WHITE
},
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: WHITE,
opacity: .2
}
}
},
ohlc: {
line: {
color: WHITE
}
}
},
chartArea: {
background: "#3d3d3d"
},
seriesColors: [ "#0081da", "#3aafff", "#99c900", "#ffeb3d", "#b20753", "#ff4195" ],
axisDefaults: {
line: {
color: "#8e8e8e"
},
labels: {
color: WHITE
},
majorGridLines: {
color: "#545454"
},
minorGridLines: {
color: "#454545"
},
title: {
color: WHITE
},
crosshair: {
color: "#8e8e8e"
}
}
},
gauge: {
pointer: {
color: "#0070e4"
},
scale: {
rangePlaceholderColor: "#1d1d1d",
labels: {
color: WHITE
},
minorTicks: {
color: WHITE
},
majorTicks: {
color: WHITE
},
line: {
color: WHITE
}
}
}
});
registerTheme("blueopal", {
chart: {
title: {
color: "#293135"
},
legend: {
labels: {
color: "#293135"
}
},
seriesDefaults: {
labels: {
color: BLACK,
background: WHITE,
opacity: .5
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#c4d0d5",
line: {
color: "#9aabb2"
}
}
},
seriesColors: [ "#0069a5", "#0098ee", "#7bd2f6", "#ffb800", "#ff8517", "#e34a00" ],
axisDefaults: {
line: {
color: "#9aabb2"
},
labels: {
color: "#293135"
},
majorGridLines: {
color: "#c4d0d5"
},
minorGridLines: {
color: "#edf1f2"
},
title: {
color: "#293135"
},
crosshair: {
color: "#9aabb2"
}
}
},
gauge: {
pointer: {
color: "#005c83"
},
scale: {
rangePlaceholderColor: "#daecf4",
labels: {
color: "#293135"
},
minorTicks: {
color: "#293135"
},
majorTicks: {
color: "#293135"
},
line: {
color: "#293135"
}
}
}
});
registerTheme("highcontrast", {
chart: {
title: {
color: "#ffffff"
},
legend: {
labels: {
color: "#ffffff"
}
},
seriesDefaults: {
labels: {
color: "#ffffff"
},
pie: {
overlay: {
gradient: "sharpGlass"
}
},
donut: {
overlay: {
gradient: "sharpGlass"
}
},
line: {
markers: {
background: "#2c232b"
}
},
scatter: {
markers: {
background: "#2c232b"
}
},
scatterLine: {
markers: {
background: "#2c232b"
}
},
area: {
opacity: .5,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#664e62",
line: {
color: "#ffffff"
},
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: "#ffffff",
opacity: 1
}
}
},
ohlc: {
line: {
color: "#ffffff"
}
}
},
chartArea: {
background: "#2c232b"
},
seriesColors: [ "#a7008f", "#ffb800", "#3aafff", "#99c900", "#b20753", "#ff4195" ],
axisDefaults: {
line: {
color: "#ffffff"
},
labels: {
color: "#ffffff"
},
majorGridLines: {
color: "#664e62"
},
minorGridLines: {
color: "#4f394b"
},
title: {
color: "#ffffff"
},
crosshair: {
color: "#ffffff"
}
}
},
gauge: {
pointer: {
color: "#a7008f"
},
scale: {
rangePlaceholderColor: "#2c232b",
labels: {
color: "#ffffff"
},
minorTicks: {
color: "#2c232b"
},
majorTicks: {
color: "#664e62"
},
line: {
color: "#ffffff"
}
}
}
});
registerTheme("default", {
chart: {
title: {
color: "#8e8e8e"
},
legend: {
labels: {
color: "#232323"
}
},
seriesDefaults: {
labels: {
color: BLACK,
background: WHITE,
opacity: .5
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#dedede",
line: {
color: "#8d8d8d"
}
}
},
seriesColors: [ "#ff6800", "#a0a700", "#ff8d00", "#678900", "#ffb53c", "#396000" ],
axisDefaults: {
line: {
color: "#8e8e8e"
},
labels: {
color: "#232323"
},
minorGridLines: {
color: "#f0f0f0"
},
majorGridLines: {
color: "#dfdfdf"
},
title: {
color: "#232323"
},
crosshair: {
color: "#8e8e8e"
}
}
},
gauge: {
pointer: {
color: "#ea7001"
},
scale: {
rangePlaceholderColor: "#dedede",
labels: {
color: "#2e2e2e"
},
minorTicks: {
color: "#2e2e2e"
},
majorTicks: {
color: "#2e2e2e"
},
line: {
color: "#2e2e2e"
}
}
}
});
registerTheme("silver", {
chart: {
title: {
color: "#4e5968"
},
legend: {
labels: {
color: "#4e5968"
}
},
seriesDefaults: {
labels: {
color: "#293135",
background: "#eaeaec",
opacity: .5
},
line: {
markers: {
background: "#eaeaec"
}
},
scatter: {
markers: {
background: "#eaeaec"
}
},
scatterLine: {
markers: {
background: "#eaeaec"
}
},
pie: {
connectors: {
color: "#A6B1C0"
}
},
donut: {
connectors: {
color: "#A6B1C0"
}
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#a6afbe"
}
},
chartArea: {
background: "#eaeaec"
},
seriesColors: [ "#007bc3", "#76b800", "#ffae00", "#ef4c00", "#a419b7", "#430B62" ],
axisDefaults: {
line: {
color: "#a6b1c0"
},
labels: {
color: "#4e5968"
},
majorGridLines: {
color: "#dcdcdf"
},
minorGridLines: {
color: "#eeeeef"
},
title: {
color: "#4e5968"
},
crosshair: {
color: "#a6b1c0"
}
}
},
gauge: {
pointer: {
color: "#0879c0"
},
scale: {
rangePlaceholderColor: "#f3f3f4",
labels: {
color: "#515967"
},
minorTicks: {
color: "#515967"
},
majorTicks: {
color: "#515967"
},
line: {
color: "#515967"
}
}
}
});
registerTheme("metro", {
chart: {
title: {
color: "#777777"
},
legend: {
labels: {
color: "#777777"
}
},
seriesDefaults: {
labels: {
color: BLACK
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#c7c7c7",
line: {
color: "#787878"
}
},
overlay: {
gradient: "none"
},
border: {
_brightness: 1
}
},
seriesColors: [ "#8ebc00", "#309b46", "#25a0da", "#ff6900", "#e61e26", "#d8e404", "#16aba9", "#7e51a1", "#313131", "#ed1691" ],
axisDefaults: {
line: {
color: "#c7c7c7"
},
labels: {
color: "#777777"
},
minorGridLines: {
color: "#c7c7c7"
},
majorGridLines: {
color: "#c7c7c7"
},
title: {
color: "#777777"
},
crosshair: {
color: "#c7c7c7"
}
}
},
gauge: {
pointer: {
color: "#8ebc00"
},
scale: {
rangePlaceholderColor: "#e6e6e6",
labels: {
color: "#777"
},
minorTicks: {
color: "#777"
},
majorTicks: {
color: "#777"
},
line: {
color: "#777"
}
}
}
});
registerTheme("metroblack", {
chart: {
title: {
color: "#ffffff"
},
legend: {
labels: {
color: "#ffffff"
}
},
seriesDefaults: {
border: {
_brightness: 1
},
labels: {
color: "#ffffff"
},
line: {
markers: {
background: "#0e0e0e"
}
},
bubble: {
opacity: .6
},
scatter: {
markers: {
background: "#0e0e0e"
}
},
scatterLine: {
markers: {
background: "#0e0e0e"
}
},
area: {
opacity: .4,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#828282",
line: {
color: "#ffffff"
}
},
overlay: {
gradient: "none"
}
},
chartArea: {
background: "#0e0e0e"
},
seriesColors: [ "#00aba9", "#309b46", "#8ebc00", "#ff6900", "#e61e26", "#d8e404", "#25a0da", "#7e51a1", "#313131", "#ed1691" ],
axisDefaults: {
line: {
color: "#cecece"
},
labels: {
color: "#ffffff"
},
minorGridLines: {
color: "#2d2d2d"
},
majorGridLines: {
color: "#333333"
},
title: {
color: "#ffffff"
},
crosshair: {
color: "#cecece"
}
}
},
gauge: {
pointer: {
color: "#00aba9"
},
scale: {
rangePlaceholderColor: "#2d2d2d",
labels: {
color: "#ffffff"
},
minorTicks: {
color: "#333333"
},
majorTicks: {
color: "#cecece"
},
line: {
color: "#cecece"
}
}
}
});
registerTheme("moonlight", {
chart: {
title: {
color: "#ffffff"
},
legend: {
labels: {
color: "#ffffff"
}
},
seriesDefaults: {
labels: {
color: "#ffffff"
},
pie: {
overlay: {
gradient: "sharpBevel"
}
},
donut: {
overlay: {
gradient: "sharpGlass"
}
},
line: {
markers: {
background: "#212a33"
}
},
bubble: {
opacity: .6
},
scatter: {
markers: {
background: "#212a33"
}
},
scatterLine: {
markers: {
background: "#212a33"
}
},
area: {
opacity: .3,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#757d87",
line: {
color: "#ea9d06"
},
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: WHITE,
opacity: .2
}
}
},
ohlc: {
line: {
color: "#ea9d06"
}
}
},
chartArea: {
background: "#212a33"
},
seriesColors: [ "#ffca08", "#ff710f", "#ed2e24", "#ff9f03", "#e13c02", "#a00201" ],
axisDefaults: {
line: {
color: "#8c909e"
},
minorTicks: {
color: "#8c909e"
},
majorTicks: {
color: "#8c909e"
},
labels: {
color: "#ffffff"
},
majorGridLines: {
color: "#3e424d"
},
minorGridLines: {
color: "#2f3640"
},
title: {
color: "#ffffff"
},
crosshair: {
color: "#8c909e"
}
}
},
gauge: {
pointer: {
color: "#f4af03"
},
scale: {
rangePlaceholderColor: "#2f3640",
labels: {
color: WHITE
},
minorTicks: {
color: "#8c909e"
},
majorTicks: {
color: "#8c909e"
},
line: {
color: "#8c909e"
}
}
}
});
registerTheme("uniform", {
chart: {
title: {
color: "#686868"
},
legend: {
labels: {
color: "#686868"
}
},
seriesDefaults: {
labels: {
color: "#686868"
},
pie: {
overlay: {
gradient: "sharpBevel"
}
},
donut: {
overlay: {
gradient: "sharpGlass"
}
},
line: {
markers: {
background: "#ffffff"
}
},
bubble: {
opacity: .6
},
scatter: {
markers: {
background: "#ffffff"
}
},
scatterLine: {
markers: {
background: "#ffffff"
}
},
area: {
opacity: .3,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#cccccc",
line: {
color: "#cccccc"
},
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: "#cccccc",
opacity: .2
}
}
},
ohlc: {
line: {
color: "#cccccc"
}
}
},
chartArea: {
background: "#ffffff"
},
seriesColors: [ "#527aa3", "#6f91b3", "#8ca7c2", "#a8bdd1", "#c5d3e0", "#e2e9f0" ],
axisDefaults: {
line: {
color: "#9e9e9e"
},
minorTicks: {
color: "#aaaaaa"
},
majorTicks: {
color: "#888888"
},
labels: {
color: "#686868"
},
majorGridLines: {
color: "#dadada"
},
minorGridLines: {
color: "#e7e7e7"
},
title: {
color: "#686868"
},
crosshair: {
color: "#9e9e9e"
}
}
},
gauge: {
pointer: {
color: "#527aa3"
},
scale: {
rangePlaceholderColor: "#e7e7e7",
labels: {
color: "#686868"
},
minorTicks: {
color: "#aaaaaa"
},
majorTicks: {
color: "#888888"
},
line: {
color: "#9e9e9e"
}
}
}
});
registerTheme("bootstrap", {
chart: {
title: {
color: "#343434"
},
legend: {
labels: {
color: "#343434"
}
},
seriesDefaults: {
labels: {
color: "#343434"
},
pie: {
overlay: {
gradient: "sharpBevel"
}
},
donut: {
overlay: {
gradient: "sharpGlass"
}
},
line: {
markers: {
background: "#ffffff"
}
},
bubble: {
opacity: .6
},
scatter: {
markers: {
background: "#ffffff"
}
},
scatterLine: {
markers: {
background: "#ffffff"
}
},
area: {
opacity: .3,
markers: {
visible: false,
size: 6
}
},
candlestick: {
downColor: "#d0d0d0",
line: {
color: "#d0d0d0"
},
border: {
_brightness: 1.5,
opacity: 1
},
highlight: {
border: {
color: "#b8b8b8",
opacity: .2
}
}
},
ohlc: {
line: {
color: "#d0d0d0"
}
}
},
chartArea: {
background: "#ffffff"
},
seriesColors: [ "#006dcc", "#49AFCD", "#5BB75B", "#FAA732", "#DA4F49", "#363636" ],
axisDefaults: {
line: {
color: "#b8b8b8"
},
minorTicks: {
color: "#dddddd"
},
majorTicks: {
color: "#b8b8b8"
},
labels: {
color: "#343434"
},
majorGridLines: {
color: "#b8b8b8"
},
minorGridLines: {
color: "#dddddd"
},
title: {
color: "#343434"
},
crosshair: {
color: "#b8b8b8"
}
}
},
gauge: {
pointer: {
color: "#0044cc"
},
scale: {
rangePlaceholderColor: "#b8b8b8",
labels: {
color: "#343434"
},
minorTicks: {
color: "#dddddd"
},
majorTicks: {
color: "#b8b8b8"
},
line: {
color: "#b8b8b8"
}
}
}
});
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var each = $.each, isArray = $.isArray, map = $.map, math = Math, extend = $.extend, proxy = $.proxy, kendo = window.kendo, Class = kendo.Class, Observable = kendo.Observable, DataSource = kendo.data.DataSource, Widget = kendo.ui.Widget, template = kendo.template, deepExtend = kendo.deepExtend, getter = kendo.getter, dataviz = kendo.dataviz, Axis = dataviz.Axis, AxisLabel = dataviz.AxisLabel, BarAnimation = dataviz.BarAnimation, Box2D = dataviz.Box2D, BoxElement = dataviz.BoxElement, ChartElement = dataviz.ChartElement, Color = dataviz.Color, ElementAnimation = dataviz.ElementAnimation, NumericAxis = dataviz.NumericAxis, Point2D = dataviz.Point2D, RootElement = dataviz.RootElement, Ring = dataviz.Ring, Text = dataviz.Text, TextBox = dataviz.TextBox, Title = dataviz.Title, animationDecorator = dataviz.animationDecorator, append = dataviz.append, autoFormat = dataviz.autoFormat, defined = dataviz.defined, getElement = dataviz.getElement, getSpacing = dataviz.getSpacing, inArray = dataviz.inArray, interpolateValue = dataviz.interpolateValue, last = dataviz.last, round = dataviz.round, renderTemplate = dataviz.renderTemplate, uniqueId = dataviz.uniqueId;
// Constants ==============================================================
var NS = ".kendoChart", ABOVE = "above", AREA = "area", AUTO = "auto", FIT = "fit", AXIS_LABEL_CLICK = dataviz.AXIS_LABEL_CLICK, BAR = "bar", BAR_BORDER_BRIGHTNESS = .8, BELOW = "below", BLACK = "#000", BOTH = "both", BOTTOM = "bottom", BUBBLE = "bubble", BULLET = "bullet", CANDLESTICK = "candlestick", CATEGORY = "category", CENTER = "center", CHANGE = "change", CIRCLE = "circle", CLICK_NS = "click" + NS, CLIP = dataviz.CLIP, COLUMN = "column", COORD_PRECISION = dataviz.COORD_PRECISION, CSS_PREFIX = "k-", DATABOUND = "dataBound", DATE = "date", DATE_REGEXP = /^\/Date\((.*?)\)\/$/, DAYS = "days", DEFAULT_FONT = dataviz.DEFAULT_FONT, DEFAULT_HEIGHT = dataviz.DEFAULT_HEIGHT, DEFAULT_PRECISION = dataviz.DEFAULT_PRECISION, DEFAULT_WIDTH = dataviz.DEFAULT_WIDTH, DEGREE = math.PI / 180, DONUT = "donut", DONUT_SECTOR_ANIM_DELAY = 50, DRAG = "drag", DRAG_END = "dragEnd", DRAG_START = "dragStart", FADEIN = "fadeIn", GLASS = "glass", HOURS = "hours", INITIAL_ANIMATION_DURATION = dataviz.INITIAL_ANIMATION_DURATION, INSIDE_BASE = "insideBase", INSIDE_END = "insideEnd", INTERPOLATE = "interpolate", LEFT = "left", LINE = "line", LINE_MARKER_SIZE = 8, MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, MINUTES = "minutes", MONTHS = "months", MOUSELEAVE_NS = "mouseleave" + NS, MOUSEMOVE_TRACKING = "mousemove.tracking", MOUSEOVER_NS = "mouseover" + NS, MOUSEMOVE_NS = "mousemove" + NS, MOUSEMOVE_THROTTLE = 20, MOUSEWHEEL_DELAY = 150, MOUSEWHEEL_NS = "DOMMouseScroll" + NS + " mousewheel" + NS, OHLC = "ohlc", OUTSIDE_END = "outsideEnd", OUTLINE_SUFFIX = "_outline", PIE = "pie", PIE_SECTOR_ANIM_DELAY = 70, PLOT_AREA_CLICK = "plotAreaClick", RIGHT = "right", ROUNDED_BEVEL = "roundedBevel", ROUNDED_GLASS = "roundedGlass", SCATTER = "scatter", SCATTER_LINE = "scatterLine", SELECT_START = "selectStart", SELECT = "select", SELECT_END = "selectEnd", SERIES_CLICK = "seriesClick", SERIES_HOVER = "seriesHover", STRING = "string", TIME_PER_MINUTE = 6e4, TIME_PER_HOUR = 60 * TIME_PER_MINUTE, TIME_PER_DAY = 24 * TIME_PER_HOUR, TIME_PER_WEEK = 7 * TIME_PER_DAY, TIME_PER_MONTH = 31 * TIME_PER_DAY, TIME_PER_YEAR = 365 * TIME_PER_DAY, TIME_PER_UNIT = {
years: TIME_PER_YEAR,
months: TIME_PER_MONTH,
weeks: TIME_PER_WEEK,
days: TIME_PER_DAY,
hours: TIME_PER_HOUR,
minutes: TIME_PER_MINUTE
}, TOP = "top", TOOLTIP_ANIMATION_DURATION = 150, TOOLTIP_OFFSET = 5, TOOLTIP_SHOW_DELAY = 100, TOOLTIP_HIDE_DELAY = 100, TOOLTIP_INVERSE = "tooltip-inverse", TOUCH_START_NS = "touchstart" + NS, TRIANGLE = "triangle", VALUE = "value", VERTICAL_AREA = "verticalArea", VERTICAL_BULLET = "verticalBullet", VERTICAL_LINE = "verticalLine", WEEKS = "weeks", WHITE = "#fff", X = "x", Y = "y", YEARS = "years", ZERO = "zero", ZOOM_ACCELERATION = 3, ZOOM_START = "zoomStart", ZOOM = "zoom", ZOOM_END = "zoomEnd", CATEGORICAL_CHARTS = [ BAR, COLUMN, LINE, VERTICAL_LINE, AREA, VERTICAL_AREA, CANDLESTICK, OHLC, BULLET, VERTICAL_BULLET ], XY_CHARTS = [ SCATTER, SCATTER_LINE, BUBBLE ], BASE_UNITS = [ MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS ];
var DateLabelFormats = {
minutes: "HH:mm",
hours: "HH:mm",
days: "M/d",
weeks: "M/d",
months: "MMM 'yy",
years: "yyyy"
};
// Chart ==================================================================
var Chart = Widget.extend({
init: function(element, userOptions) {
var chart = this, options, themeOptions, themes = dataviz.ui.themes || {}, theme, themeName;
kendo.destroy(element);
Widget.fn.init.call(chart, element);
options = deepExtend({}, chart.options, userOptions);
chart.element.addClass(CSS_PREFIX + options.name.toLowerCase()).css("position", "relative");
// Used by the ThemeBuilder
chart._originalOptions = deepExtend({}, options);
themeName = options.theme;
theme = themes[themeName] || themes[themeName.toLowerCase()];
themeOptions = themeName && theme ? theme.chart : {};
resolveAxisAliases(options);
chart._applyDefaults(options, themeOptions);
chart.options = deepExtend({}, themeOptions, options);
applySeriesColors(chart.options);
chart.bind(chart.events, chart.options);
chart.wrapper = chart.element;
chart._initDataSource(userOptions);
kendo.notify(chart, dataviz.ui);
},
_initDataSource: function(userOptions) {
var chart = this, dataSourceOptions = (userOptions || {}).dataSource;
chart._dataChangeHandler = proxy(chart._onDataChanged, chart);
chart.dataSource = DataSource.create(dataSourceOptions).bind(CHANGE, chart._dataChangeHandler);
chart._redraw();
chart._attachEvents();
if (dataSourceOptions && chart.options.autoBind) {
chart.dataSource.fetch();
}
},
setDataSource: function(dataSource) {
var chart = this;
chart.dataSource.unbind(CHANGE, chart._dataChangeHandler);
chart.dataSource = dataSource;
dataSource.bind(CHANGE, chart._dataChangeHandler);
if (chart.options.autoBind) {
dataSource.fetch();
}
},
events: [ DATABOUND, SERIES_CLICK, SERIES_HOVER, AXIS_LABEL_CLICK, PLOT_AREA_CLICK, DRAG_START, DRAG, DRAG_END, ZOOM_START, ZOOM, ZOOM_END, SELECT_START, SELECT, SELECT_END ],
items: function() {
return $();
},
options: {
name: "Chart",
theme: "default",
chartArea: {},
legend: {
visible: true,
labels: {}
},
categoryAxis: {},
autoBind: true,
seriesDefaults: {
type: COLUMN,
data: [],
highlight: {
visible: true
},
groupNameTemplate: "#= group.value + (kendo.dataviz.defined(series.name) ? ': ' + series.name : '') #",
labels: {}
},
series: [],
tooltip: {
visible: false
},
transitions: true,
valueAxis: {},
plotArea: {},
title: {},
xAxis: {},
yAxis: {}
},
refresh: function() {
var chart = this;
chart._applyDefaults(chart.options);
delete chart._sourceSeries;
chart._onDataChanged();
},
redraw: function(paneName) {
var chart = this, pane, plotArea;
chart._applyDefaults(chart.options);
if (paneName) {
plotArea = chart._model._plotArea;
pane = plotArea.findPane(paneName);
plotArea.redraw(pane);
} else {
chart._redraw();
}
},
_redraw: function() {
var chart = this, model = chart._getModel(), viewType = dataviz.ui.defaultView(), view;
chart._destroyView();
chart._model = model;
chart._plotArea = model._plotArea;
if (viewType) {
view = chart._view = viewType.fromModel(model);
chart._viewElement = chart._renderView(view);
chart._tooltip = chart._createTooltip();
chart._highlight = new Highlight(view, chart._viewElement);
chart._setupSelection();
}
},
_sharedTooltip: function() {
var chart = this, options = chart.options;
return chart._plotArea instanceof CategoricalPlotArea && options.tooltip.shared;
},
_createTooltip: function() {
var chart = this, options = chart.options, element = chart.element, tooltip;
if (chart._sharedTooltip()) {
tooltip = new SharedTooltip(element, chart._plotArea, options.tooltip);
} else {
tooltip = new Tooltip(element, options.tooltip);
}
return tooltip;
},
_renderView: function() {
var chart = this;
return chart._view.renderTo(chart.element[0]);
},
svg: function() {
var model = this._getModel(), view = dataviz.SVGView.fromModel(model);
return view.render();
},
_applyDefaults: function(options, themeOptions) {
applyAxisDefaults(options, themeOptions);
applySeriesDefaults(options, themeOptions);
},
_getModel: function() {
var chart = this, options = chart.options, model = new RootElement(chart._modelOptions()), plotArea;
model.parent = chart;
Title.buildTitle(options.title, model);
plotArea = model._plotArea = chart._createPlotArea();
if (options.legend.visible) {
model.append(new Legend(plotArea.options.legend));
}
model.append(plotArea);
model.reflow();
return model;
},
_modelOptions: function() {
var chart = this, options = chart.options, element = chart.element, height = math.floor(element.height()), width = math.floor(element.width());
return deepExtend({
width: width || DEFAULT_WIDTH,
height: height || DEFAULT_HEIGHT,
transitions: options.transitions
}, options.chartArea);
},
_createPlotArea: function() {
var chart = this, options = chart.options, series = options.series, i, length = series.length, currentSeries, categoricalSeries = [], xySeries = [], pieSeries = [], donutSeries = [], bulletSeries = [], plotArea;
for (i = 0; i < length; i++) {
currentSeries = series[i];
if (inArray(currentSeries.type, CATEGORICAL_CHARTS)) {
categoricalSeries.push(currentSeries);
} else if (inArray(currentSeries.type, XY_CHARTS)) {
xySeries.push(currentSeries);
} else if (currentSeries.type === PIE) {
pieSeries.push(currentSeries);
} else if (currentSeries.type === DONUT) {
donutSeries.push(currentSeries);
} else if (currentSeries.type === BULLET) {
bulletSeries.push(currentSeries);
}
}
if (pieSeries.length > 0) {
plotArea = new PiePlotArea(pieSeries, options);
} else if (donutSeries.length > 0) {
plotArea = new DonutPlotArea(donutSeries, options);
} else if (xySeries.length > 0) {
plotArea = new XYPlotArea(xySeries, options);
} else {
plotArea = new CategoricalPlotArea(categoricalSeries, options);
}
return plotArea;
},
_setupSelection: function() {
var chart = this, plotArea = chart._plotArea, axes = plotArea.axes, selections = chart._selections = [], selection, i, axis, min, max, options;
if (!chart._selectStartHandler) {
chart._selectStartHandler = proxy(chart._selectStart, chart);
chart._selectHandler = proxy(chart._select, chart);
chart._selectEndHandler = proxy(chart._selectEnd, chart);
}
for (i = 0; i < axes.length; i++) {
axis = axes[i];
options = axis.options;
if (axis instanceof CategoryAxis && options.select && !options.vertical) {
min = 0;
max = options.categories.length - 1;
if (axis instanceof DateCategoryAxis) {
min = options.categories[min];
max = options.categories[max];
}
if (!options.justified) {
if (axis instanceof DateCategoryAxis) {
max = addDuration(max, 1, options.baseUnit, options.weekStartDay);
} else {
max++;
}
}
selection = new Selection(chart, axis, deepExtend({
min: min,
max: max
}, options.select));
selection.bind(SELECT_START, chart._selectStartHandler);
selection.bind(SELECT, chart._selectHandler);
selection.bind(SELECT_END, chart._selectEndHandler);
selections.push(selection);
}
}
},
_selectStart: function(e) {
return this.trigger(SELECT_START, e);
},
_select: function(e) {
return this.trigger(SELECT, e);
},
_selectEnd: function(e) {
return this.trigger(SELECT_END, e);
},
_attachEvents: function() {
var chart = this, element = chart.element;
element.on(CLICK_NS, proxy(chart._click, chart));
element.on(MOUSEOVER_NS, proxy(chart._mouseover, chart));
element.on(MOUSEWHEEL_NS, proxy(chart._mousewheel, chart));
element.on(TOUCH_START_NS, proxy(chart._tap, chart));
element.on(MOUSELEAVE_NS, proxy(chart._mouseleave, chart));
if (chart._plotArea.crosshairs.length || chart._tooltip && chart._sharedTooltip()) {
element.on(MOUSEMOVE_NS, proxy(chart._mousemove, chart));
}
if (kendo.UserEvents) {
chart._userEvents = new kendo.UserEvents(element, {
global: true,
threshold: 5,
filter: ":not(.k-selector)",
multiTouch: false,
start: proxy(chart._start, chart),
move: proxy(chart._move, chart),
end: proxy(chart._end, chart)
});
}
},
_start: function(e) {
var chart = this, events = chart._events;
if (defined(events[DRAG_START] || events[DRAG] || events[DRAG_END])) {
chart._startNavigation(e, DRAG_START);
}
},
_move: function(e) {
var chart = this, state = chart._navState, axes, ranges = {}, i, currentAxis, axisName, axis, delta;
if (state) {
e.preventDefault();
axes = state.axes;
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
axisName = currentAxis.options.name;
if (axisName) {
axis = currentAxis.options.vertical ? e.y : e.x;
delta = axis.startLocation - axis.location;
if (delta !== 0) {
ranges[currentAxis.options.name] = currentAxis.translateRange(delta);
}
}
}
state.axisRanges = ranges;
chart.trigger(DRAG, {
axisRanges: ranges,
originalEvent: e
});
}
},
_end: function(e) {
this._endNavigation(e, DRAG_END);
},
_mousewheel: function(e) {
var chart = this, origEvent = e.originalEvent, prevented, delta = mwDelta(e), totalDelta, state = chart._navState, axes, i, currentAxis, axisName, ranges = {};
if (!state) {
prevented = chart._startNavigation(origEvent, ZOOM_START);
if (!prevented) {
state = chart._navState;
}
}
if (state) {
totalDelta = state.totalDelta || delta;
state.totalDelta = totalDelta + delta;
axes = chart._navState.axes;
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
axisName = currentAxis.options.name;
if (axisName) {
ranges[axisName] = currentAxis.scaleRange(totalDelta);
}
}
chart.trigger(ZOOM, {
delta: delta,
axisRanges: ranges,
originalEvent: e
});
if (chart._mwTimeout) {
clearTimeout(chart._mwTimeout);
}
chart._mwTimeout = setTimeout(function() {
chart._endNavigation(e, ZOOM_END);
}, MOUSEWHEEL_DELAY);
}
},
_startNavigation: function(e, chartEvent) {
var chart = this, coords = chart._eventCoordinates(e), plotArea = chart._model._plotArea, pane = plotArea.findPointPane(coords), axes = plotArea.axes.slice(0), i, currentAxis, inAxis = false, prevented;
if (!pane) {
return;
}
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
if (currentAxis.box.containsPoint(coords)) {
inAxis = true;
break;
}
}
if (!inAxis && plotArea.backgroundBox().containsPoint(coords)) {
prevented = chart.trigger(chartEvent, {
axisRanges: axisRanges(axes),
originalEvent: e
});
if (prevented) {
chart._userEvents.cancel();
} else {
chart._suppressHover = true;
chart._unsetActivePoint();
chart._navState = {
pane: pane,
axes: axes
};
}
}
},
_endNavigation: function(e, chartEvent) {
var chart = this;
if (chart._navState) {
chart.trigger(chartEvent, {
axisRanges: chart._navState.axisRanges,
originalEvent: e
});
chart._suppressHover = false;
chart._navState = null;
}
},
_getChartElement: function(e) {
var chart = this, modelId = $(e.target).data("modelId"), model = chart._model, element;
if (modelId) {
element = model.modelMap[modelId];
}
if (element && element.aliasFor) {
element = element.aliasFor(e, chart._eventCoordinates(e));
}
return element;
},
_eventCoordinates: function(e) {
var chart = this, isTouch = defined((e.x || {}).client), clientX = isTouch ? e.x.client : e.clientX, clientY = isTouch ? e.y.client : e.clientY;
return chart._toModelCoordinates(clientX, clientY);
},
_toModelCoordinates: function(clientX, clientY) {
var element = this.element, offset = element.offset(), paddingLeft = parseInt(element.css("paddingLeft"), 10), paddingTop = parseInt(element.css("paddingTop"), 10), win = $(window);
return new Point2D(clientX - offset.left - paddingLeft + win.scrollLeft(), clientY - offset.top - paddingTop + win.scrollTop());
},
_click: function(e) {
var chart = this, element = chart._getChartElement(e);
while (element) {
if (element.click) {
element.click(chart, e);
}
element = element.parent;
}
},
_startHover: function(e) {
var chart = this, tooltip = chart._tooltip, highlight = chart._highlight, tooltipOptions = chart.options.tooltip, point;
if (chart._suppressHover || !highlight || inArray(e.target, highlight._overlays) || chart._sharedTooltip()) {
return;
}
point = chart._getChartElement(e);
if (point && point.hover) {
point.hover(chart, e);
chart._activePoint = point;
tooltipOptions = deepExtend({}, tooltipOptions, point.options.tooltip);
if (tooltipOptions.visible) {
tooltip.show(point);
}
highlight.show(point);
return true;
}
},
_mouseover: function(e) {
var chart = this;
if (chart._startHover(e)) {
$(document).on(MOUSEMOVE_TRACKING, proxy(chart._mouseMoveTracking, chart));
}
},
_mouseMoveTracking: function(e) {
var chart = this, options = chart.options, tooltip = chart._tooltip, highlight = chart._highlight, coords = chart._eventCoordinates(e), point = chart._activePoint, tooltipOptions, owner, seriesPoint;
if (chart._plotArea.box.containsPoint(coords)) {
if (point && point.series && (point.series.type === LINE || point.series.type === AREA)) {
owner = point.parent;
seriesPoint = owner.getNearestPoint(coords.x, coords.y, point.seriesIx);
if (seriesPoint && seriesPoint != point) {
seriesPoint.hover(chart, e);
chart._activePoint = seriesPoint;
tooltipOptions = deepExtend({}, options.tooltip, point.options.tooltip);
if (tooltipOptions.visible) {
tooltip.show(seriesPoint);
}
highlight.show(seriesPoint);
}
}
} else {
$(document).off(MOUSEMOVE_TRACKING);
chart._unsetActivePoint();
}
},
_mousemove: function(e) {
var chart = this, now = new Date(), timestamp = chart._mousemove.timestamp;
if (!timestamp || now - timestamp > MOUSEMOVE_THROTTLE) {
var coords = chart._eventCoordinates(e);
chart._trackCrosshairs(coords);
if (chart._sharedTooltip()) {
chart._trackSharedTooltip(coords);
}
chart._mousemove.timestamp = now;
}
},
_trackCrosshairs: function(coords) {
var crosshairs = this._plotArea.crosshairs, i, current;
for (i = 0; i < crosshairs.length; i++) {
current = crosshairs[i];
if (current.box.containsPoint(coords)) {
current.showAt(coords);
} else {
current.hide();
}
}
},
_trackSharedTooltip: function(coords) {
var chart = this, options = chart.options, plotArea = chart._plotArea, categoryAxis = plotArea.categoryAxis, tooltip = chart._tooltip, tooltipOptions = options.tooltip, highlight = chart._highlight, index, points;
index = categoryAxis.getCategoryIndex(coords);
if (index !== chart._tooltipCategoryIx) {
points = plotArea.pointsByCategoryIndex(index);
if (points.length > 0) {
if (tooltipOptions.visible) {
tooltip.showAt(points, coords);
}
highlight.show(points);
} else {
tooltip.hide();
}
chart._tooltipCategoryIx = index;
}
},
_mouseleave: function() {
var chart = this, plotArea = chart._plotArea, crosshairs = plotArea.crosshairs, tooltip = chart._tooltip, highlight = chart._highlight, i;
for (i = 0; i < crosshairs.length; i++) {
crosshairs[i].hide();
}
setTimeout(proxy(tooltip.hide, tooltip), TOOLTIP_HIDE_DELAY);
highlight.hide();
chart._tooltipCategoryIx = null;
},
_unsetActivePoint: function() {
var chart = this, tooltip = chart._tooltip, highlight = chart._highlight;
chart._activePoint = null;
if (tooltip) {
tooltip.hide();
}
if (highlight) {
highlight.hide();
}
},
_onDataChanged: function() {
var chart = this, options = chart.options, series = chart._sourceSeries || options.series, seriesIx, seriesLength = series.length, data = chart.dataSource.view(), grouped = (chart.dataSource.group() || []).length > 0, categoriesData = grouped ? (data[0] || []).items : data, processedSeries = [], currentSeries;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
currentSeries = series[seriesIx];
if (chart.isBindable(currentSeries)) {
if (currentSeries.autoBind !== false) {
currentSeries.data = data;
}
append(processedSeries, grouped ? chart._createGroupedSeries(currentSeries, data) : [ currentSeries ]);
} else {
processedSeries.push(currentSeries);
}
}
chart._sourceSeries = series;
options.series = processedSeries;
applySeriesColors(chart.options);
chart._bindCategories(categoriesData);
chart.trigger(DATABOUND);
chart._redraw();
},
_bindCategories: function(data) {
var chart = this, options = chart.options, definitions = [].concat(options.categoryAxis), axisIx, axis;
for (axisIx = 0; axisIx < definitions.length; axisIx++) {
axis = definitions[axisIx];
if (axis.autoBind !== false) {
chart._bindCategoryAxis(axis, data);
}
}
},
_bindCategoryAxis: function(axis, data) {
var categoryIx, category, row;
if (axis.field) {
axis.categories = [];
for (categoryIx = 0; categoryIx < data.length; categoryIx++) {
row = data[categoryIx];
category = getField(axis.field, row);
if (categoryIx === 0) {
axis.categories = [ category ];
axis.dataItems = [ row ];
} else {
axis.categories.push(category);
axis.dataItems.push(row);
}
}
}
},
isBindable: function(series) {
var valueFields = valueFieldsBySeriesType(series.type), result = true, field, i;
for (i = 0; i < valueFields.length; i++) {
field = valueFields[i];
if (field === VALUE) {
field = "field";
} else {
field = field + "Field";
}
if (!series[field]) {
result = false;
break;
}
}
return result;
},
_createGroupedSeries: function(series, data) {
var groupSeries = [], nameTemplate, group, groupIx, dataLength = data.length, seriesClone;
if (series.groupNameTemplate) {
nameTemplate = template(series.groupNameTemplate);
}
for (groupIx = 0; groupIx < dataLength; groupIx++) {
seriesClone = deepExtend({}, series);
seriesClone.color = undefined;
groupSeries.push(seriesClone);
group = data[groupIx];
seriesClone.data = group.items;
if (nameTemplate) {
seriesClone.name = nameTemplate({
series: seriesClone,
group: group
});
}
}
return groupSeries;
},
_tap: function(e) {
var chart = this;
if (!chart._startHover(e)) {
chart._unsetActivePoint();
}
chart._click(e);
},
destroy: function() {
var chart = this, dataSource = chart.dataSource;
chart.element.off(NS);
dataSource.unbind(CHANGE, chart._dataChangeHandler);
$(document).off(MOUSEMOVE_TRACKING);
if (chart._userEvents) {
chart._userEvents.destroy();
}
chart._destroyView();
Widget.fn.destroy.call(chart);
},
_destroyView: function() {
var chart = this, pool = dataviz.IDPool.current, model = chart._model, view = chart._view, viewElement = chart._viewElement, selections = chart._selections;
if (model) {
model.destroy();
}
if (view) {
view.destroy();
}
if (viewElement) {
$("[id]", viewElement).each(function() {
pool.free($(this).attr("id"));
});
}
if (selections) {
while (selections.length > 0) {
selections.shift().destroy();
}
}
}
});
var BarLabel = ChartElement.extend({
init: function(content, options) {
var barLabel = this;
ChartElement.fn.init.call(barLabel, options);
barLabel.append(new TextBox(content, barLabel.options));
},
options: {
position: OUTSIDE_END,
margin: getSpacing(3),
padding: getSpacing(4),
color: BLACK,
background: "",
border: {
width: 1,
color: ""
},
aboveAxis: true,
vertical: false,
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
},
zIndex: 1
},
reflow: function(targetBox) {
var barLabel = this, options = barLabel.options, vertical = options.vertical, aboveAxis = options.aboveAxis, text = barLabel.children[0], box = text.box, padding = text.options.padding;
text.options.align = vertical ? CENTER : LEFT;
text.options.vAlign = vertical ? TOP : CENTER;
if (options.position == INSIDE_END) {
if (vertical) {
text.options.vAlign = TOP;
if (!aboveAxis && box.height() < targetBox.height()) {
text.options.vAlign = BOTTOM;
}
} else {
text.options.align = aboveAxis ? RIGHT : LEFT;
}
} else if (options.position == CENTER) {
text.options.vAlign = CENTER;
text.options.align = CENTER;
} else if (options.position == INSIDE_BASE) {
if (vertical) {
text.options.vAlign = aboveAxis ? BOTTOM : TOP;
} else {
text.options.align = aboveAxis ? LEFT : RIGHT;
}
} else if (options.position == OUTSIDE_END) {
if (vertical) {
if (aboveAxis) {
targetBox = new Box2D(targetBox.x1, targetBox.y1 - box.height(), targetBox.x2, targetBox.y1);
} else {
targetBox = new Box2D(targetBox.x1, targetBox.y2, targetBox.x2, targetBox.y2 + box.height());
}
} else {
text.options.align = CENTER;
if (aboveAxis) {
targetBox = new Box2D(targetBox.x2 + box.width(), targetBox.y1, targetBox.x2, targetBox.y2);
} else {
targetBox = new Box2D(targetBox.x1 - box.width(), targetBox.y1, targetBox.x1, targetBox.y2);
}
}
}
if (vertical) {
padding.left = padding.right = (targetBox.width() - text.contentBox.width()) / 2;
} else {
padding.top = padding.bottom = (targetBox.height() - text.contentBox.height()) / 2;
}
text.reflow(targetBox);
}
});
var Legend = ChartElement.extend({
init: function(options) {
var legend = this;
ChartElement.fn.init.call(legend, options);
legend.createLabels();
},
options: {
position: RIGHT,
items: [],
labels: {},
offsetX: 0,
offsetY: 0,
margin: getSpacing(10),
padding: getSpacing(5),
border: {
color: BLACK,
width: 0
},
background: "",
zIndex: 1
},
createLabels: function() {
var legend = this, items = legend.options.items, count = items.length, label, name, i;
for (i = 0; i < count; i++) {
name = items[i].name;
label = new Text(name, legend.options.labels);
legend.append(label);
}
},
reflow: function(targetBox) {
var legend = this, options = legend.options, childrenCount = legend.children.length;
if (childrenCount === 0) {
legend.box = targetBox.clone();
return;
}
if (options.position == "custom") {
legend.customLayout(targetBox);
return;
}
if (options.position == TOP || options.position == BOTTOM) {
legend.horizontalLayout(targetBox);
} else {
legend.verticalLayout(targetBox);
}
},
getViewElements: function(view) {
var legend = this, children = legend.children, options = legend.options, items = options.items, count = items.length, markerSize = legend.markerSize(), group = view.createGroup({
zIndex: options.zIndex
}), border = options.border || {}, padding, markerBox, labelBox, color, label, box, i;
append(group.children, ChartElement.fn.getViewElements.call(legend, view));
for (i = 0; i < count; i++) {
color = items[i].color;
label = children[i];
markerBox = new Box2D();
box = label.box;
labelBox = labelBox ? labelBox.wrap(box) : box.clone();
markerBox.x1 = box.x1 - markerSize * 2;
markerBox.x2 = markerBox.x1 + markerSize;
if (options.position == TOP || options.position == BOTTOM) {
markerBox.y1 = box.y1 + markerSize / 2;
} else {
markerBox.y1 = box.y1 + (box.height() - markerSize) / 2;
}
markerBox.y2 = markerBox.y1 + markerSize;
group.children.push(view.createRect(markerBox, {
fill: color,
stroke: color
}));
}
if (children.length > 0) {
padding = getSpacing(options.padding);
padding.left += markerSize * 2;
labelBox.pad(padding);
group.children.unshift(view.createRect(labelBox, {
stroke: border.width ? border.color : "",
strokeWidth: border.width,
dashType: border.dashType,
fill: options.background
}));
}
return [ group ];
},
verticalLayout: function(targetBox) {
var legend = this, options = legend.options, children = legend.children, childrenCount = children.length, labelBox = children[0].box.clone(), offsetX, offsetY, margin = getSpacing(options.margin), markerSpace = legend.markerSize() * 2, label, i;
// Position labels below each other
for (i = 1; i < childrenCount; i++) {
label = legend.children[i];
label.box.alignTo(legend.children[i - 1].box, BOTTOM);
labelBox.wrap(label.box);
}
// Vertical center is calculated relative to the container, not the parent!
if (options.position == LEFT) {
offsetX = targetBox.x1 + markerSpace + margin.left;
offsetY = (targetBox.y2 - labelBox.height()) / 2;
labelBox.x2 += markerSpace + margin.left + margin.right;
} else {
offsetX = targetBox.x2 - labelBox.width() - margin.right;
offsetY = (targetBox.y2 - labelBox.height()) / 2;
labelBox.translate(offsetX, offsetY);
labelBox.x1 -= markerSpace + margin.left;
}
legend.translateChildren(offsetX + options.offsetX, offsetY + options.offsetY);
var labelBoxWidth = labelBox.width();
labelBox.x1 = math.max(targetBox.x1, labelBox.x1);
labelBox.x2 = labelBox.x1 + labelBoxWidth;
labelBox.y1 = targetBox.y1;
labelBox.y2 = targetBox.y2;
legend.box = labelBox;
},
horizontalLayout: function(targetBox) {
var legend = this, options = legend.options, children = legend.children, childrenCount = children.length, box = children[0].box.clone(), markerWidth = legend.markerSize() * 3, offsetX, offsetY, margin = getSpacing(options.margin), boxWidth = children[0].box.width() + markerWidth, plotAreaWidth = targetBox.width(), label, labelY = 0, i;
// Position labels next to each other
for (i = 1; i < childrenCount; i++) {
label = children[i];
boxWidth += label.box.width() + markerWidth;
if (boxWidth > plotAreaWidth - markerWidth) {
label.box = new Box2D(box.x1, box.y2, box.x1 + label.box.width(), box.y2 + label.box.height());
boxWidth = label.box.width() + markerWidth;
labelY = label.box.y1;
} else {
label.box.alignTo(children[i - 1].box, RIGHT);
label.box.y2 = labelY + label.box.height();
label.box.y1 = labelY;
label.box.translate(markerWidth, 0);
}
box.wrap(label.box);
}
offsetX = (targetBox.width() - box.width() + markerWidth) / 2;
if (options.position === TOP) {
offsetY = targetBox.y1 + margin.top;
box.y2 = targetBox.y1 + box.height() + margin.top + margin.bottom;
box.y1 = targetBox.y1;
} else {
offsetY = targetBox.y2 - box.height() - margin.bottom;
box.y1 = targetBox.y2 - box.height() - margin.top - margin.bottom;
box.y2 = targetBox.y2;
}
legend.translateChildren(offsetX + options.offsetX, offsetY + options.offsetY);
box.x1 = targetBox.x1;
box.x2 = targetBox.x2;
legend.box = box;
},
customLayout: function(targetBox) {
var legend = this, options = legend.options, children = legend.children, childrenCount = children.length, labelBox = children[0].box.clone(), markerWidth = legend.markerSize() * 2, i;
// Position labels next to each other
for (i = 1; i < childrenCount; i++) {
labelBox = legend.children[i].box;
labelBox.alignTo(legend.children[i - 1].box, BOTTOM);
labelBox.wrap(labelBox);
}
legend.translateChildren(options.offsetX + markerWidth, options.offsetY);
legend.box = targetBox;
},
markerSize: function() {
var legend = this, children = legend.children;
if (children.length > 0) {
return children[0].box.height() / 2;
} else {
return 0;
}
}
});
var CategoryAxis = Axis.extend({
init: function(options) {
var axis = this;
Axis.fn.init.call(axis, options);
options = axis.options;
options.categories = options.categories.slice(0);
},
options: {
type: CATEGORY,
categories: [],
vertical: false,
majorGridLines: {
visible: false,
width: 1,
color: BLACK
},
zIndex: 1,
justified: false
},
range: function() {
return {
min: 0,
max: this.options.categories.length
};
},
getTickPositions: function(itemsCount) {
var axis = this, options = axis.options, vertical = options.vertical, justified = options.justified, lineBox = axis.lineBox(), size = vertical ? lineBox.height() : lineBox.width(), intervals = itemsCount - (justified ? 1 : 0), step = size / intervals, dim = vertical ? Y : X, pos = lineBox[dim + 1], positions = [], i;
for (i = 0; i < itemsCount; i++) {
positions.push(round(pos, COORD_PRECISION));
pos += step;
}
if (!justified) {
positions.push(lineBox[dim + 2]);
}
return options.reverse ? positions.reverse() : positions;
},
getMajorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.categories.length);
},
getMinorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.categories.length * 2);
},
getSlot: function(from, to) {
var axis = this, options = axis.options, majorTicks = axis.getMajorTickPositions(), reverse = options.reverse, justified = options.justified, valueAxis = options.vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + (reverse ? 2 : 1)], lineEnd = lineBox[valueAxis + (reverse ? 1 : 2)], slotBox = lineBox.clone(), intervals = math.max(1, majorTicks.length - (justified ? 0 : 1)), p1, p2, slotSize;
from = defined(from) ? from : 0;
to = defined(to) ? to : from;
from = clipValue(from, 0, intervals);
to = clipValue(to - 1, from, intervals);
// Fixes transient bug caused by iOS 6.0 JIT
// (one can never be too sure)
to = math.max(from, to);
p1 = from === 0 ? lineStart : majorTicks[from];
p2 = justified ? p1 : majorTicks[to];
slotSize = to - from;
if (slotSize > 0 || from === to) {
p2 = majorTicks[to + 1] || lineEnd;
}
if (justified) {
if (from === intervals) {
p1 = p2;
} else {
p2 = p1;
}
}
slotBox[valueAxis + 1] = reverse ? p2 : p1;
slotBox[valueAxis + 2] = reverse ? p1 : p2;
return slotBox;
},
getCategoryIndex: function(point) {
var axis = this, options = axis.options, reverse = options.reverse, vertical = options.vertical, valueAxis = vertical ? Y : X, lineBox = axis.lineBox(), lineStart = lineBox[valueAxis + 1], lineEnd = lineBox[valueAxis + 2], pos = point[valueAxis], majorTicks = axis.getMajorTickPositions(), diff = MAX_VALUE, tickPos, nextTickPos, i, categoryIx;
if (pos < lineStart || pos > lineEnd) {
return null;
}
for (i = 0; i < majorTicks.length; i++) {
tickPos = majorTicks[i];
nextTickPos = majorTicks[i + 1];
if (!defined(nextTickPos)) {
nextTickPos = reverse ? lineStart : lineEnd;
}
if (reverse) {
tickPos = nextTickPos;
nextTickPos = majorTicks[i];
}
if (options.justified) {
if (pos === nextTickPos) {
categoryIx = math.max(0, vertical ? majorTicks.length - i - 1 : i + 1);
break;
}
if (math.abs(pos - tickPos) < diff) {
diff = pos - tickPos;
categoryIx = i;
}
} else {
if (pos >= tickPos && pos <= nextTickPos) {
categoryIx = i;
break;
}
}
}
return categoryIx;
},
getCategory: function(point) {
var index = this.getCategoryIndex(point);
if (index === null) {
return null;
}
return this.options.categories[index];
},
translateRange: function(delta) {
var axis = this, options = axis.options, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = options.categories.length, scale = size / range, offset = round(delta / scale, DEFAULT_PRECISION);
return {
min: offset,
max: range + offset
};
},
scaleRange: function(scale) {
var axis = this, options = axis.options, range = options.categories.length, delta = scale * range;
return {
min: -delta,
max: range + delta
};
},
labelsCount: function() {
return this.options.categories.length;
},
createAxisLabel: function(index, labelOptions) {
var axis = this, options = axis.options, dataItem = options.dataItems ? options.dataItems[index] : null, category = defined(options.categories[index]) ? options.categories[index] : "";
return new AxisLabel(category, index, dataItem, labelOptions);
}
});
var AxisDateLabel = AxisLabel.extend({
formatValue: function(value, options) {
return kendo.toString(value, options.format, options.culture);
}
});
var DateCategoryAxis = CategoryAxis.extend({
init: function(options) {
var axis = this, baseUnit, useDefault;
options = options || {};
options = deepExtend({
roundToBaseUnit: true
}, options, {
min: toDate(options.min),
max: toDate(options.max)
});
if (options.categories && options.categories.length > 0) {
baseUnit = (options.baseUnit || "").toLowerCase();
useDefault = baseUnit !== FIT && !inArray(baseUnit, BASE_UNITS);
if (useDefault) {
options.baseUnit = axis.defaultBaseUnit(options);
}
if (baseUnit === FIT || options.baseUnitStep === AUTO) {
axis.autoBaseUnit(options);
}
axis.groupCategories(options);
}
CategoryAxis.fn.init.call(axis, options);
},
options: {
type: DATE,
labels: {
dateFormats: DateLabelFormats
},
autoBaseUnitSteps: {
minutes: [ 1, 2, 5, 15, 30 ],
hours: [ 1, 2, 3 ],
days: [ 1, 2, 3 ],
weeks: [ 1, 2 ],
months: [ 1, 2, 3, 6 ],
years: [ 1, 2, 3, 5, 10, 25, 50 ]
},
maxDateGroups: 10
},
translateRange: function(delta) {
var axis = this, range = CategoryAxis.fn.translateRange.call(axis, delta), options = axis.options, baseUnit = options.baseUnit, offset = math.round(range.min), weekStartDay = options.weekStartDay;
return {
min: addDuration(options.min, offset, baseUnit, weekStartDay),
max: addDuration(options.max, offset, baseUnit, weekStartDay)
};
},
scaleRange: function(delta) {
var axis = this, options = axis.options, rounds = math.abs(delta), from = options.min, to = options.max, range, step;
while (rounds--) {
range = dateDiff(from, to);
step = math.round(range * .1);
if (delta < 0) {
from = addTicks(from, step);
to = addTicks(to, -step);
} else {
from = addTicks(from, -step);
to = addTicks(to, step);
}
}
return {
min: from,
max: to
};
},
defaultBaseUnit: function(options) {
var categories = options.categories, count = defined(categories) ? categories.length : 0, categoryIx, cat, diff, minDiff = MAX_VALUE, lastCat, unit;
for (categoryIx = 0; categoryIx < count; categoryIx++) {
cat = toDate(categories[categoryIx]);
if (cat && lastCat) {
diff = dateDiff(cat, lastCat);
if (diff > 0) {
minDiff = math.min(minDiff, diff);
if (minDiff >= TIME_PER_YEAR) {
unit = YEARS;
} else if (minDiff >= TIME_PER_MONTH - TIME_PER_DAY * 3) {
unit = MONTHS;
} else if (minDiff >= TIME_PER_WEEK) {
unit = WEEKS;
} else if (minDiff >= TIME_PER_DAY) {
unit = DAYS;
} else if (minDiff >= TIME_PER_HOUR) {
unit = HOURS;
} else {
unit = MINUTES;
}
}
}
lastCat = cat;
}
return unit || DAYS;
},
range: function(options) {
options = options || this.options;
var categories = toDate(options.categories), autoUnit = options.baseUnit === FIT, baseUnit = autoUnit ? BASE_UNITS[0] : options.baseUnit, min = toTime(options.min), max = toTime(options.max), categoryLimits = sparseArrayLimits(categories), minCategory = toTime(categoryLimits.min), maxCategory = toTime(categoryLimits.max);
if (options.roundToBaseUnit) {
return {
min: addDuration(min || minCategory, 0, baseUnit, options.weekStartDay),
max: addDuration(max || maxCategory, 1, baseUnit, options.weekStartDay)
};
} else {
return {
min: toDate(min || minCategory),
max: toDate(max || maxCategory)
};
}
},
autoBaseUnit: function(options) {
var axis = this, range = axis.range(options), autoUnit = options.baseUnit === FIT, autoUnitIx = 0, baseUnit = autoUnit ? BASE_UNITS[autoUnitIx++] : options.baseUnit, span = range.max - range.min, units = span / TIME_PER_UNIT[baseUnit], totalUnits = units, maxDateGroups = options.maxDateGroups || axis.options.maxDateGroups, autoBaseUnitSteps = deepExtend({}, axis.options.autoBaseUnitSteps, options.autoBaseUnitSteps), unitSteps, step, nextStep;
while (units > maxDateGroups) {
unitSteps = unitSteps || autoBaseUnitSteps[baseUnit].slice(0);
nextStep = unitSteps.shift();
if (nextStep) {
step = nextStep;
units = totalUnits / step;
} else if (autoUnit) {
baseUnit = BASE_UNITS[autoUnitIx++] || last(BASE_UNITS);
totalUnits = span / TIME_PER_UNIT[baseUnit];
unitSteps = null;
} else {
if (units > maxDateGroups) {
step = math.ceil(totalUnits / maxDateGroups);
}
break;
}
}
options.baseUnitStep = step;
options.baseUnit = baseUnit;
},
getMajorTickPositions: function() {
var axis = this, options = axis.options, categories = options.categories, positions = [];
if (options.roundToBaseUnit || categories.length === 0) {
positions = CategoryAxis.fn.getMajorTickPositions.call(axis);
} else {
var vertical = options.vertical, reverse = options.reverse, lineBox = axis.lineBox(), lineSize = vertical ? lineBox.height() : lineBox.width(), startTime = categories[0].getTime(), range = axis.range(axis.options), timeRange = range.max - range.min, scale = lineSize / timeRange, divisions = categories.length, dir = (vertical ? -1 : 1) * (reverse ? -1 : 1), startEdge = dir === 1 ? 1 : 2, endEdge = dir === 1 ? 2 : 1, startPos = lineBox[(vertical ? Y : X) + startEdge], endPos = lineBox[(vertical ? Y : X) + endEdge], pos = startPos, i, timePos;
for (i = 0; i < divisions; i++) {
timePos = categories[i] - startTime;
pos = startPos + timePos * scale * dir;
positions.push(round(pos, COORD_PRECISION));
}
if (last(positions) !== endPos) {
positions.push(endPos);
}
}
return positions;
},
groupCategories: function(options) {
var axis = this, categories = toDate(options.categories), baseUnit = options.baseUnit, baseUnitStep = options.baseUnitStep || 1, range = axis.range(options), round = options.roundToBaseUnit, end, date, nextDate, groups = [], categoryMap = axis.categoryMap = [], categoryIndicies, lastCategoryIndicies = [], categoryIx, categoryDate;
end = round ? addDuration(range.max, baseUnitStep - 1, baseUnit, options.weekStartDay) : range.max;
if (dateEquals(range.min, range.max)) {
end = toDate(toTime(end) + 1);
}
for (date = range.min; date < end; date = nextDate) {
nextDate = addDuration(date, baseUnitStep, baseUnit, options.weekStartDay);
groups.push(date);
categoryIndicies = [];
for (categoryIx = lteDateIndex(categories, date); categoryIx < categories.length; categoryIx++) {
categoryDate = categories[categoryIx];
if (categoryDate && categoryDate >= date) {
if (categoryDate < nextDate) {
if (options.justified && dateEquals(categoryDate, end)) {
lastCategoryIndicies.push(categoryIx);
} else {
categoryIndicies.push(categoryIx);
}
} else if (!round && dateEquals(nextDate, end)) {
lastCategoryIndicies.push(categoryIx);
} else {
break;
}
}
}
categoryMap.push(categoryIndicies);
}
if (lastCategoryIndicies.length) {
groups.push(end);
categoryMap.push(lastCategoryIndicies);
}
if (!options.max && (last(categoryMap) || []).length === 0) {
// Drop the last group if the user has not requested it
categoryMap.pop();
groups.pop();
}
options.min = groups[0];
options.max = round ? last(groups) : end;
options.categories = groups;
},
createAxisLabel: function(index, labelOptions) {
var options = this.options, dataItem = options.dataItems ? options.dataItems[index] : null, date = options.categories[index], baseUnit = options.baseUnit, visible = true, unitFormat = labelOptions.dateFormats[baseUnit];
if (options.justified) {
var roundedDate = floorDate(date, baseUnit, options.weekStartDay);
visible = dateEquals(roundedDate, date);
}
labelOptions = deepExtend({
format: unitFormat
}, labelOptions, {
visible: visible
});
return new AxisDateLabel(date, index, dataItem, labelOptions);
}
});
var DateValueAxis = Axis.extend({
init: function(seriesMin, seriesMax, options) {
var axis = this;
options = options || {};
deepExtend(options, {
min: toDate(options.min),
max: toDate(options.max),
axisCrossingValue: toDate(options.axisCrossingValues || options.axisCrossingValue)
});
options = axis.applyDefaults(toDate(seriesMin), toDate(seriesMax), options);
Axis.fn.init.call(axis, options);
},
options: {
type: DATE,
labels: {
dateFormats: DateLabelFormats
}
},
applyDefaults: function(seriesMin, seriesMax, options) {
var axis = this, min = options.min || seriesMin, max = options.max || seriesMax, baseUnit = options.baseUnit || axis.timeUnits(max - min), baseUnitTime = TIME_PER_UNIT[baseUnit], autoMin = floorDate(toTime(min) - 1, baseUnit) || toDate(max), autoMax = ceilDate(toTime(max) + 1, baseUnit), userMajorUnit = options.majorUnit ? options.majorUnit : undefined, majorUnit = userMajorUnit || dataviz.ceil(dataviz.autoMajorUnit(autoMin.getTime(), autoMax.getTime()), baseUnitTime) / baseUnitTime, actualUnits = duration(autoMin, autoMax, baseUnit), totalUnits = dataviz.ceil(actualUnits, majorUnit), unitsToAdd = totalUnits - actualUnits, head = math.floor(unitsToAdd / 2), tail = unitsToAdd - head;
if (!options.baseUnit) {
delete options.baseUnit;
}
return deepExtend({
baseUnit: baseUnit,
min: addDuration(autoMin, -head, baseUnit),
max: addDuration(autoMax, tail, baseUnit),
minorUnit: majorUnit / 5
}, options, {
majorUnit: majorUnit
});
},
range: function() {
var options = this.options;
return {
min: options.min,
max: options.max
};
},
getDivisions: function(stepValue) {
var options = this.options;
return math.floor(duration(options.min, options.max, options.baseUnit) / stepValue + 1);
},
getTickPositions: function(stepValue) {
var axis = this, options = axis.options, vertical = options.vertical, reverse = options.reverse, lineBox = axis.lineBox(), lineSize = vertical ? lineBox.height() : lineBox.width(), timeRange = duration(options.min, options.max, options.baseUnit), scale = lineSize / timeRange, step = stepValue * scale, divisions = axis.getDivisions(stepValue), dir = (vertical ? -1 : 1) * (reverse ? -1 : 1), startEdge = dir === 1 ? 1 : 2, pos = lineBox[(vertical ? Y : X) + startEdge], positions = [], i;
for (i = 0; i < divisions; i++) {
positions.push(round(pos, COORD_PRECISION));
pos = pos + step * dir;
}
return positions;
},
getMajorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.majorUnit);
},
getMinorTickPositions: function() {
var axis = this;
return axis.getTickPositions(axis.options.minorUnit);
},
getSlot: function(a, b) {
return NumericAxis.fn.getSlot.call(this, toDate(a), toDate(b));
},
getValue: function(point) {
var value = NumericAxis.fn.getValue.call(this, point);
return value !== null ? toDate(value) : null;
},
labelsCount: function() {
return this.getDivisions(this.options.majorUnit);
},
createAxisLabel: function(index, labelOptions) {
var options = this.options, offset = index * options.majorUnit, date = addDuration(options.min, offset, options.baseUnit), unitFormat = labelOptions.dateFormats[options.baseUnit];
labelOptions.format = labelOptions.format || unitFormat;
return new AxisDateLabel(date, index, null, labelOptions);
},
timeUnits: function(delta) {
var unit = HOURS;
if (delta >= TIME_PER_YEAR) {
unit = YEARS;
} else if (delta >= TIME_PER_MONTH) {
unit = MONTHS;
} else if (delta >= TIME_PER_WEEK) {
unit = WEEKS;
} else if (delta >= TIME_PER_DAY) {
unit = DAYS;
}
return unit;
},
translateRange: function(delta) {
var axis = this, options = axis.options, baseUnit = options.baseUnit, weekStartDay = options.weekStartDay, lineBox = axis.lineBox(), size = options.vertical ? lineBox.height() : lineBox.width(), range = axis.range(), scale = size / (range.max - range.min), offset = round(delta / scale, DEFAULT_PRECISION), from = addTicks(options.min, offset), to = addTicks(options.max, offset);
return {
min: addDuration(from, 0, baseUnit, weekStartDay),
max: addDuration(to, 0, baseUnit, weekStartDay)
};
},
scaleRange: function(delta) {
var axis = this, options = axis.options, baseUnit = options.baseUnit, weekStartDay = options.weekStartDay, rounds = math.abs(delta), from = options.min, to = options.max, range, step;
while (rounds--) {
range = dateDiff(from, to);
step = math.round(range * .1);
if (delta < 0) {
from = addTicks(from, step);
to = addTicks(to, -step);
} else {
from = addTicks(from, -step);
to = addTicks(to, step);
}
from = addDuration(from, 0, baseUnit, weekStartDay);
to = addDuration(to, 1, baseUnit, weekStartDay);
}
return {
min: from,
max: to
};
}
});
var ClusterLayout = ChartElement.extend({
init: function(options) {
var cluster = this;
ChartElement.fn.init.call(cluster, options);
},
options: {
vertical: false,
gap: 0,
spacing: 0
},
reflow: function(box) {
var cluster = this, options = cluster.options, vertical = options.vertical, axis = vertical ? Y : X, children = cluster.children, gap = options.gap, spacing = options.spacing, count = children.length, slots = count + gap + spacing * (count - 1), slotSize = (vertical ? box.height() : box.width()) / slots, position = box[axis + 1] + slotSize * (gap / 2), childBox, i;
for (i = 0; i < count; i++) {
childBox = (children[i].box || box).clone();
childBox[axis + 1] = position;
childBox[axis + 2] = position + slotSize;
children[i].reflow(childBox);
if (i < count - 1) {
position += slotSize * spacing;
}
position += slotSize;
}
}
});
var StackLayout = ChartElement.extend({
init: function(options) {
var stack = this;
ChartElement.fn.init.call(stack, options);
},
options: {
vertical: true,
isReversed: false
},
reflow: function(targetBox) {
var stack = this, options = stack.options, vertical = options.vertical, positionAxis = vertical ? X : Y, stackAxis = vertical ? Y : X, stackBase = targetBox[stackAxis + 2], children = stack.children, box = stack.box = new Box2D(), childrenCount = children.length, stackDirection, i;
if (options.isReversed) {
stackDirection = vertical ? BOTTOM : LEFT;
} else {
stackDirection = vertical ? TOP : RIGHT;
}
for (i = 0; i < childrenCount; i++) {
var currentChild = children[i], childBox = currentChild.box.clone();
childBox.snapTo(targetBox, positionAxis);
if (currentChild.options) {
currentChild.options.stackBase = stackBase;
}
if (i === 0) {
box = stack.box = childBox.clone();
} else {
childBox.alignTo(children[i - 1].box, stackDirection);
}
currentChild.reflow(childBox);
box.wrap(childBox);
}
}
});
var PointEventsMixin = {
click: function(chart, e) {
var point = this;
chart.trigger(SERIES_CLICK, {
value: point.value,
category: point.category,
series: point.series,
dataItem: point.dataItem,
element: $(e.target)
});
},
hover: function(chart, e) {
var point = this;
chart.trigger(SERIES_HOVER, {
value: point.value,
category: point.category,
series: point.series,
dataItem: point.dataItem,
element: $(e.target)
});
}
};
var Bar = ChartElement.extend({
init: function(value, options) {
var bar = this;
ChartElement.fn.init.call(bar, options);
bar.value = value;
if (value < 0 && options.negativeColor) {
bar.options.color = options.negativeColor;
}
bar.options.id = uniqueId();
bar.enableDiscovery();
},
options: {
color: WHITE,
border: {
width: 1
},
vertical: true,
overlay: {
gradient: GLASS
},
aboveAxis: true,
labels: {
visible: false
},
animation: {
type: BAR
},
opacity: 1
},
render: function() {
var bar = this, value = bar.value, options = bar.options, labels = options.labels, labelText = value, labelTemplate;
if (bar._rendered) {
return;
} else {
bar._rendered = true;
}
if (labels.visible && value) {
if (labels.template) {
labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: bar.dataItem,
category: bar.category,
value: bar.value,
series: bar.series
});
} else if (labels.format) {
labelText = autoFormat(labels.format, labelText);
}
bar.append(new BarLabel(labelText, deepExtend({
vertical: options.vertical,
id: uniqueId()
}, options.labels)));
}
},
reflow: function(targetBox) {
this.render();
var bar = this, options = bar.options, children = bar.children, label = children[0];
bar.box = targetBox;
if (label) {
label.options.aboveAxis = options.aboveAxis;
label.reflow(targetBox);
}
},
getViewElements: function(view) {
var bar = this, options = bar.options, vertical = options.vertical, border = options.border.width > 0 ? {
stroke: bar.getBorderColor(),
strokeWidth: options.border.width,
strokeOpacity: options.border.opacity,
dashType: options.border.dashType
} : {}, box = bar.box, rectStyle = deepExtend({
id: options.id,
fill: options.color,
fillOpacity: options.opacity,
strokeOpacity: options.opacity,
vertical: options.vertical,
aboveAxis: options.aboveAxis,
stackBase: options.stackBase,
animation: options.animation,
data: {
modelId: options.modelId
}
}, border), elements = [];
if (box.width() > 0 && box.height() > 0) {
if (options.overlay) {
rectStyle.overlay = deepExtend({
rotation: vertical ? 0 : 90
}, options.overlay);
}
elements.push(view.createRect(box, rectStyle));
}
append(elements, ChartElement.fn.getViewElements.call(bar, view));
return elements;
},
highlightOverlay: function(view, options) {
var bar = this, box = bar.box;
options = deepExtend({
data: {
modelId: bar.options.modelId
}
}, options);
options.id = null;
return view.createRect(box, options);
},
getBorderColor: function() {
var bar = this, options = bar.options, color = options.color, border = options.border, borderColor = border.color, brightness = border._brightness || BAR_BORDER_BRIGHTNESS;
if (!defined(borderColor)) {
borderColor = new Color(color).brightness(brightness).toHex();
}
return borderColor;
},
tooltipAnchor: function(tooltipWidth, tooltipHeight) {
var bar = this, options = bar.options, box = bar.box, vertical = options.vertical, aboveAxis = options.aboveAxis, x, y;
if (vertical) {
x = box.x2 + TOOLTIP_OFFSET;
y = aboveAxis ? box.y1 : box.y2 - tooltipHeight;
} else {
if (options.isStacked) {
x = aboveAxis ? box.x2 - tooltipWidth : box.x1;
y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
} else {
x = aboveAxis ? box.x2 + TOOLTIP_OFFSET : box.x1 - tooltipWidth - TOOLTIP_OFFSET;
y = box.y1;
}
}
return new Point2D(x, y);
},
formatValue: function(format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(Bar.fn, PointEventsMixin);
var CategoricalChart = ChartElement.extend({
init: function(plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.categoryAxis = plotArea.seriesCategoryAxis(options.series[0]);
// Value axis ranges grouped by axis name, e.g.:
// primary: { min: 0, max: 1 }
chart.valueAxisRanges = {};
chart.points = [];
chart.categoryPoints = [];
chart.seriesPoints = [];
chart.render();
},
options: {
series: [],
invertAxes: false,
isStacked: false
},
render: function() {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
addValue: function(data, category, categoryIx, series, seriesIx) {
var chart = this, value = data.value, point, categoryPoints = chart.categoryPoints[categoryIx], seriesPoints = chart.seriesPoints[seriesIx];
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
if (!seriesPoints) {
chart.seriesPoints[seriesIx] = seriesPoints = [];
}
chart.updateRange(value, categoryIx, series);
point = chart.createPoint(data, category, categoryIx, series, seriesIx);
if (point) {
point.category = category;
point.series = series;
point.seriesIx = seriesIx;
point.owner = chart;
point.dataItem = series.data[categoryIx];
}
chart.points.push(point);
seriesPoints.push(point);
categoryPoints.push(point);
},
updateRange: function(value, categoryIx, series) {
var chart = this, axisName = series.axis, axisRange = chart.valueAxisRanges[axisName];
if (defined(value) && !isNaN(value)) {
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange.min = math.min(axisRange.min, value);
axisRange.max = math.max(axisRange.max, value);
}
},
seriesValueAxis: function(series) {
var plotArea = this.plotArea, axisName = series.axis, axis = axisName ? plotArea.namedValueAxes[axisName] : plotArea.valueAxis;
if (!axis) {
throw new Error("Unable to locate value axis with name " + axisName);
}
return axis;
},
reflow: function(targetBox) {
var chart = this, options = chart.options, invertAxes = options.invertAxes, pointIx = 0, categorySlots = chart.categorySlots = [], chartPoints = chart.points, categoryAxis = chart.categoryAxis, valueAxis, axisCrossingValue, point;
chart.traverseDataPoints(function(data, category, categoryIx, currentSeries) {
var value = chart.pointValue(data);
valueAxis = chart.seriesValueAxis(currentSeries);
axisCrossingValue = chart.categoryAxisCrossingValue(valueAxis);
point = chartPoints[pointIx++];
if (point && point.plotValue) {
value = point.plotValue;
}
var categorySlot = chart.categorySlot(categoryAxis, categoryIx, valueAxis), valueSlot = chart.valueSlot(valueAxis, value, axisCrossingValue), slotX = invertAxes ? valueSlot : categorySlot, slotY = invertAxes ? categorySlot : valueSlot, pointSlot = new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2), aboveAxis = valueAxis.options.reverse ? value < axisCrossingValue : value >= axisCrossingValue;
if (point) {
point.options.aboveAxis = aboveAxis;
point.reflow(pointSlot);
}
if (!categorySlots[categoryIx]) {
categorySlots[categoryIx] = categorySlot;
}
});
chart.reflowCategories(categorySlots);
chart.box = targetBox;
},
categoryAxisCrossingValue: function(valueAxis) {
var categoryAxis = this.categoryAxis, options = valueAxis.options, crossingValues = [].concat(options.axisCrossingValues || options.axisCrossingValue);
return crossingValues[categoryAxis.axisIndex || 0] || 0;
},
reflowCategories: function() {},
valueSlot: function(valueAxis, value, axisCrossingValue) {
return valueAxis.getSlot(value, axisCrossingValue);
},
categorySlot: function(categoryAxis, categoryIx) {
return categoryAxis.getSlot(categoryIx);
},
traverseDataPoints: function(callback) {
var chart = this, options = chart.options, series = options.series, categories = chart.categoryAxis.options.categories || [], count = categoriesCount(series), bindableFields = chart.bindableFields(), categoryIx, seriesIx, pointData, currentCategory, currentSeries, seriesCount = series.length;
for (categoryIx = 0; categoryIx < count; categoryIx++) {
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentCategory = categories[categoryIx];
currentSeries = series[seriesIx];
pointData = bindPoint(currentSeries, categoryIx, bindableFields);
callback(pointData, currentCategory, categoryIx, currentSeries, seriesIx);
}
}
},
bindableFields: function() {
return [];
},
formatPointValue: function(point, format) {
return autoFormat(format, point.value);
},
pointValue: function(data) {
return data.value;
}
});
var BarChart = CategoricalChart.extend({
init: function(plotArea, options) {
var chart = this;
chart._groupTotals = {};
chart._groups = [];
CategoricalChart.fn.init.call(chart, plotArea, options);
},
render: function() {
var chart = this;
CategoricalChart.fn.render.apply(chart);
chart.computeAxisRanges();
},
createPoint: function(data, category, categoryIx, series) {
var barChart = this, value = data.value, options = barChart.options, children = barChart.children, isStacked = barChart.options.isStacked, labelOptions = deepExtend({}, series.labels), bar, cluster;
if (isStacked) {
if (labelOptions.position == OUTSIDE_END) {
labelOptions.position = INSIDE_END;
}
}
bar = new Bar(value, deepExtend({}, {
vertical: !options.invertAxes,
overlay: series.overlay,
labels: labelOptions,
isStacked: isStacked
}, series, {
color: data.fields.color || undefined
}));
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
barChart.append(cluster);
}
if (isStacked) {
var stackWrap = barChart.getStackWrap(series, cluster), positiveStack, negativeStack;
if (stackWrap.children.length === 0) {
positiveStack = new StackLayout({
vertical: !options.invertAxes
});
negativeStack = new StackLayout({
vertical: !options.invertAxes,
isReversed: true
});
stackWrap.append(positiveStack, negativeStack);
} else {
positiveStack = stackWrap.children[0];
negativeStack = stackWrap.children[1];
}
if (value > 0) {
positiveStack.append(bar);
} else {
negativeStack.append(bar);
}
} else {
cluster.append(bar);
}
return bar;
},
getStackWrap: function(series, cluster) {
var wraps = cluster.children, stackGroup = series.stack, stackWrap, i, length = wraps.length;
if (typeof stackGroup === STRING) {
for (i = 0; i < length; i++) {
if (wraps[i]._stackGroup === stackGroup) {
stackWrap = wraps[i];
break;
}
}
} else {
stackWrap = wraps[0];
}
if (!stackWrap) {
stackWrap = new ChartElement();
stackWrap._stackGroup = stackGroup;
cluster.append(stackWrap);
}
return stackWrap;
},
updateRange: function(value, categoryIx, series) {
var chart = this, isStacked = chart.options.isStacked, totals = chart.groupTotals(series.stack), positive = totals.positive, negative = totals.negative;
if (defined(value)) {
if (isStacked) {
incrementSlot(value > 0 ? positive : negative, categoryIx, value);
} else {
CategoricalChart.fn.updateRange.apply(chart, arguments);
}
}
},
computeAxisRanges: function() {
var chart = this, isStacked = chart.options.isStacked, axisName, categoryTotals;
if (isStacked) {
axisName = chart.options.series[0].axis;
categoryTotals = chart.categoryTotals();
chart.valueAxisRanges[axisName] = {
min: sparseArrayMin(categoryTotals.negative.concat(0)),
max: sparseArrayMax(categoryTotals.positive.concat(0))
};
}
},
seriesValueAxis: function(series) {
var chart = this, options = chart.options;
return CategoricalChart.fn.seriesValueAxis.call(chart, options.isStacked ? chart.options.series[0] : series);
},
valueSlot: function(valueAxis, value, axisCrossingValue) {
return valueAxis.getSlot(value, this.options.isStacked ? 0 : axisCrossingValue);
},
categorySlot: function(categoryAxis, categoryIx, valueAxis) {
var chart = this, options = chart.options, categorySlot = categoryAxis.getSlot(categoryIx), stackAxis, zeroSlot;
if (options.isStacked) {
zeroSlot = valueAxis.getSlot(0, 0);
stackAxis = options.invertAxes ? X : Y;
categorySlot[stackAxis + 1] = categorySlot[stackAxis + 2] = zeroSlot[stackAxis + 1];
}
return categorySlot;
},
reflow: function(targetBox) {
var chart = this;
chart.setStacksDirection();
CategoricalChart.fn.reflow.call(chart, targetBox);
},
setStacksDirection: function() {
var chart = this, options = chart.options, series = options.series, count = categoriesCount(series), clusters = chart.children, categoryIx, seriesIx, currentSeries, valueAxis, seriesCount = series.length;
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeries = series[seriesIx];
valueAxis = chart.seriesValueAxis(currentSeries);
for (categoryIx = 0; categoryIx < count; categoryIx++) {
var cluster = clusters[categoryIx], stackWrap = chart.getStackWrap(currentSeries, cluster), stacks = stackWrap.children, positiveStack = stacks[0], negativeStack = stacks[1];
if (positiveStack && negativeStack) {
positiveStack.options.isReversed = valueAxis.options.reverse;
negativeStack.options.isReversed = !valueAxis.options.reverse;
}
}
}
},
reflowCategories: function(categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
groupTotals: function(stackGroup) {
var chart = this, groupName = typeof stackGroup === STRING ? stackGroup : "default", totals = chart._groupTotals[groupName];
if (!totals) {
totals = chart._groupTotals[groupName] = {
positive: [],
negative: []
};
chart._groups.push(groupName);
}
return totals;
},
categoryTotals: function() {
var chart = this, groups = chart._groups, groupTotals = chart._groupTotals, name, totals, categoryTotals = {
positive: [],
negative: []
}, i, length = groups.length;
for (i = 0; i < length; i++) {
name = groups[i];
totals = groupTotals[name];
append(categoryTotals.positive, totals.positive);
append(categoryTotals.negative, totals.negative);
}
return categoryTotals;
},
bindableFields: function() {
return [ "color" ];
}
});
var BulletChart = CategoricalChart.extend({
init: function(plotArea, options) {
var chart = this;
chart.wrapData(options);
CategoricalChart.fn.init.call(chart, plotArea, options);
},
wrapData: function(options) {
var series = options.series, i, data, seriesItem;
for (i = 0; i < series.length; i++) {
seriesItem = series[i];
data = seriesItem.data;
if (data && !isArray(data[0]) && typeof data[0] != "object") {
seriesItem.data = [ data ];
}
}
},
addValue: function(data, category, categoryIx, series, seriesIx) {
var chart = this, categoryPoints = chart.categoryPoints[categoryIx], seriesPoints = chart.seriesPoints[seriesIx], point;
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
if (!seriesPoints) {
chart.seriesPoints[seriesIx] = seriesPoints = [];
}
chart.updateRange(data.value, categoryIx, series);
point = chart.createPoint(data.value, categoryIx, series);
if (point) {
point.category = category;
point.series = series;
point.seriesIx = seriesIx;
point.owner = chart;
point.dataItem = series.data[categoryIx];
}
chart.points.push(point);
seriesPoints.push(point);
categoryPoints.push(point);
},
reflowCategories: function(categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
createPoint: function(data, categoryIx, series) {
var chart = this, options = chart.options, children = chart.children, bullet, cluster;
bullet = new Bullet(data, deepExtend({}, {
vertical: !options.invertAxes,
overlay: series.overlay,
categoryIx: categoryIx,
invertAxes: options.invertAxes
}, series));
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
cluster.append(bullet);
return bullet;
},
updateRange: function(value, categoryIx, series) {
var chart = this, axisName = series.axis, current = value.current, target = value.target, axisRange = chart.valueAxisRanges[axisName];
if (defined(current) && !isNaN(current) && defined(target && !isNaN(target))) {
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange.min = math.min.apply(math, [ axisRange.min, current, target ]);
axisRange.max = math.max.apply(math, [ axisRange.max, current, target ]);
}
},
formatPointValue: function(point, format) {
return autoFormat(format, point.value.current, point.value.target);
},
pointValue: function(data) {
return data.value.current;
}
});
var Bullet = ChartElement.extend({
init: function(value, options) {
var bullet = this;
ChartElement.fn.init.call(bullet, options);
bullet.value = value;
bullet.options.id = uniqueId();
bullet.enableDiscovery();
bullet.render();
},
options: {
color: WHITE,
border: {
width: 1
},
vertical: false,
animation: {
type: BAR
},
opacity: 1,
target: {
shape: "",
border: {
width: 0,
color: "green"
},
line: {
width: 2
}
},
tooltip: {
format: "Current: {0}</br>Target: {1}"
}
},
render: function() {
var bullet = this, options = bullet.options;
bullet.target = new Target({
id: bullet.options.id,
type: options.target.shape,
background: options.target.color || options.color,
opacity: options.opacity,
zIndex: options.zIndex,
border: options.target.border,
vAlign: TOP,
align: RIGHT
});
bullet.append(bullet.target);
},
reflow: function(box) {
var bullet = this, options = bullet.options, chart = bullet.owner, target = bullet.target, invertAxes = options.invertAxes, valueAxis = chart.seriesValueAxis(bullet.options), axisCrossingValue = chart.categoryAxisCrossingValue(valueAxis), categorySlot = chart.categorySlot(chart.categoryAxis, options.categoryIx, valueAxis), targetValueSlot = chart.valueSlot(valueAxis, bullet.value.target, axisCrossingValue), targetSlotX = invertAxes ? targetValueSlot : categorySlot, targetSlotY = invertAxes ? categorySlot : targetValueSlot, targetSlot = new Box2D(targetSlotX.x1, targetSlotY.y1, targetSlotX.x2, targetSlotY.y2);
target.options.height = invertAxes ? targetSlot.height() : options.target.line.width;
target.options.width = invertAxes ? options.target.line.width : targetSlot.width();
target.reflow(targetSlot);
bullet.box = box;
},
getViewElements: function(view) {
var bullet = this, options = bullet.options, vertical = options.vertical, border = options.border.width > 0 ? {
stroke: options.border.color || options.color,
strokeWidth: options.border.width,
dashType: options.border.dashType
} : {}, box = bullet.box, rectStyle = deepExtend({
id: options.id,
fill: options.color,
fillOpacity: options.opacity,
strokeOpacity: options.opacity,
vertical: options.vertical,
aboveAxis: options.aboveAxis,
animation: options.animation,
data: {
modelId: options.modelId
}
}, border), elements = [];
if (box.width() > 0 && box.height() > 0) {
if (options.overlay) {
rectStyle.overlay = deepExtend({
rotation: vertical ? 0 : 90
}, options.overlay);
}
elements.push(view.createRect(box, rectStyle));
}
append(elements, ChartElement.fn.getViewElements.call(bullet, view));
return elements;
},
tooltipAnchor: function(tooltipWidth, tooltipHeight) {
var bar = this, options = bar.options, box = bar.box, vertical = options.vertical, aboveAxis = options.aboveAxis, x, y;
if (vertical) {
x = box.x2 + TOOLTIP_OFFSET;
y = aboveAxis ? box.y1 : box.y2 - tooltipHeight;
} else {
if (options.isStacked) {
x = aboveAxis ? box.x2 - tooltipWidth : box.x1;
y = box.y1 - tooltipHeight - TOOLTIP_OFFSET;
} else {
x = aboveAxis ? box.x2 + TOOLTIP_OFFSET : box.x1 - tooltipWidth - TOOLTIP_OFFSET;
y = box.y1;
}
}
return new Point2D(x, y);
},
highlightOverlay: function(view, options) {
var bullet = this, box = bullet.box;
options = deepExtend({
data: {
modelId: bullet.options.modelId
}
}, options);
options.id = null;
return view.createRect(box, options);
},
formatValue: function(format) {
var bullet = this;
return bullet.owner.formatPointValue(bullet, format);
}
});
deepExtend(Bullet.fn, PointEventsMixin);
var ShapeElement = BoxElement.extend({
options: {
type: CIRCLE,
align: CENTER,
vAlign: CENTER
},
getViewElements: function(view, renderOptions) {
var marker = this, options = marker.options, type = options.type, box = marker.paddingBox, element, elementOptions, halfWidth = box.width() / 2;
if (!options.visible || !marker.hasBox()) {
return [];
}
elementOptions = deepExtend(marker.elementStyle(), renderOptions);
if (type === TRIANGLE) {
element = view.createPolyline([ new Point2D(box.x1 + halfWidth, box.y1), new Point2D(box.x1, box.y2), new Point2D(box.x2, box.y2) ], true, elementOptions);
} else if (type === CIRCLE) {
element = view.createCircle(new Point2D(round(box.x1 + halfWidth, COORD_PRECISION), round(box.y1 + box.height() / 2, COORD_PRECISION)), halfWidth, elementOptions);
} else {
element = view.createRect(box, elementOptions);
}
return [ element ];
}
});
var Target = ShapeElement.extend();
deepExtend(Target.fn, PointEventsMixin);
var LinePoint = ChartElement.extend({
init: function(value, options) {
var point = this;
ChartElement.fn.init.call(point, options);
point.value = value;
point.options.id = uniqueId();
point.enableDiscovery();
},
options: {
aboveAxis: true,
vertical: true,
markers: {
visible: true,
background: WHITE,
size: LINE_MARKER_SIZE,
type: CIRCLE,
border: {
width: 2
},
opacity: 1
},
labels: {
visible: false,
position: ABOVE,
margin: getSpacing(3),
padding: getSpacing(4),
animation: {
type: FADEIN,
delay: INITIAL_ANIMATION_DURATION
}
}
},
render: function() {
var point = this, options = point.options, markers = options.markers, labels = options.labels, markerBackground = markers.background, markerBorder = deepExtend({}, markers.border), labelText = point.value;
if (point._rendered) {
return;
} else {
point._rendered = true;
}
if (!defined(markerBorder.color)) {
markerBorder.color = new Color(markerBackground).brightness(BAR_BORDER_BRIGHTNESS).toHex();
}
point.marker = new ShapeElement({
id: point.options.id,
visible: markers.visible && markers.size,
type: markers.type,
width: markers.size,
height: markers.size,
background: markerBackground,
border: markerBorder,
opacity: markers.opacity,
zIndex: markers.zIndex,
animation: markers.animation
});
point.append(point.marker);
if (labels.visible) {
if (labels.template) {
var labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: point.dataItem,
category: point.category,
value: point.value,
series: point.series
});
} else if (labels.format) {
labelText = point.formatValue(labels.format);
}
point.label = new TextBox(labelText, deepExtend({
id: uniqueId(),
align: CENTER,
vAlign: CENTER,
margin: {
left: 5,
right: 5
}
}, labels));
point.append(point.label);
}
},
markerBox: function() {
return this.marker.box;
},
reflow: function(targetBox) {
var point = this, options = point.options, vertical = options.vertical, aboveAxis = options.aboveAxis, childBox;
point.render();
point.box = targetBox;
childBox = targetBox.clone();
if (vertical) {
if (aboveAxis) {
childBox.y1 -= childBox.height();
} else {
childBox.y2 += childBox.height();
}
} else {
if (aboveAxis) {
childBox.x1 += childBox.width();
} else {
childBox.x2 -= childBox.width();
}
}
point.marker.reflow(childBox);
point.reflowLabel(childBox);
},
reflowLabel: function(box) {
var point = this, options = point.options, marker = point.marker, label = point.label, anchor = options.labels.position;
if (label) {
anchor = anchor === ABOVE ? TOP : anchor;
anchor = anchor === BELOW ? BOTTOM : anchor;
label.reflow(box);
label.box.alignTo(marker.box, anchor);
label.reflow(label.box);
}
},
highlightOverlay: function(view, options) {
var element = this, marker = element.marker;
options = deepExtend({
data: {
modelId: element.options.modelId
}
}, options);
return marker.getViewElements(view, deepExtend(options, {
id: null,
fill: marker.options.border.color,
fillOpacity: 1,
strokeOpacity: 0
}))[0];
},
tooltipAnchor: function(tooltipWidth, tooltipHeight) {
var point = this, markerBox = point.marker.box, aboveAxis = point.options.aboveAxis;
return new Point2D(markerBox.x2 + TOOLTIP_OFFSET, aboveAxis ? markerBox.y1 - tooltipHeight : markerBox.y2);
},
formatValue: function(format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(LinePoint.fn, PointEventsMixin);
var Bubble = LinePoint.extend({
init: function(value, options) {
var point = this;
LinePoint.fn.init.call(point, value, options);
point.category = value.category;
},
options: {
labels: {
position: CENTER
},
highlight: {
opacity: 1,
border: {
width: 1,
opacity: 1
}
}
},
highlightOverlay: function(view) {
var element = this, options = element.options, highlight = options.highlight, borderWidth = highlight.border.width, markers = options.markers, center = element.box.center(), radius = markers.size / 2 - borderWidth / 2, borderColor = highlight.border.color || new Color(markers.background).brightness(BAR_BORDER_BRIGHTNESS).toHex();
return view.createCircle(center, radius, {
id: null,
data: {
modelId: element.options.modelId
},
stroke: borderColor,
strokeWidth: borderWidth,
strokeOpacity: highlight.border.opacity
});
},
toggleHighlight: function(view) {
var element = this, opacity = element.options.highlight.opacity;
element.highlighted = !element.highlighted;
var marker = element.marker.getViewElements(view, {
fillOpacity: element.highlighted ? opacity : undefined
})[0];
marker.refresh(getElement(this.options.id));
}
});
var LineSegment = ChartElement.extend({
init: function(linePoints, series, seriesIx) {
var segment = this;
ChartElement.fn.init.call(segment);
segment.linePoints = linePoints;
segment.series = series;
segment.seriesIx = seriesIx;
segment.options.id = uniqueId();
segment.enableDiscovery();
},
options: {},
points: function(visualPoints) {
var segment = this, linePoints = segment.linePoints.concat(visualPoints || []), points = [], i, length = linePoints.length, pointCenter;
for (i = 0; i < length; i++) {
pointCenter = linePoints[i].markerBox().center();
points.push(new Point2D(pointCenter.x, pointCenter.y));
}
return points;
},
getViewElements: function(view) {
var segment = this, series = segment.series;
ChartElement.fn.getViewElements.call(segment, view);
return [ view.createPolyline(segment.points(), false, {
id: segment.options.id,
stroke: series.color,
strokeWidth: series.width,
strokeOpacity: series.opacity,
fill: "",
dashType: series.dashType,
data: {
modelId: segment.options.modelId
},
zIndex: -1
}) ];
},
aliasFor: function(e, coords) {
var segment = this, seriesIx = segment.seriesIx;
return segment.parent.getNearestPoint(coords.x, coords.y, seriesIx);
}
});
var LineChartMixin = {
renderSegments: function() {
var chart = this, options = chart.options, series = options.series, seriesPoints = chart.seriesPoints, currentSeries, seriesIx, seriesCount = seriesPoints.length, currentSeriesPoints, linePoints, point, pointIx, pointCount, segments = [];
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeriesPoints = seriesPoints[seriesIx];
pointCount = currentSeriesPoints.length;
currentSeries = series[seriesIx];
linePoints = [];
for (pointIx = 0; pointIx < pointCount; pointIx++) {
point = currentSeriesPoints[pointIx];
if (point) {
linePoints.push(point);
} else if (chart.seriesMissingValues(currentSeries) !== INTERPOLATE) {
if (linePoints.length > 1) {
segments.push(chart.createSegment(linePoints, currentSeries, seriesIx, last(segments)));
}
linePoints = [];
}
}
if (linePoints.length > 1) {
segments.push(chart.createSegment(linePoints, currentSeries, seriesIx, last(segments)));
}
}
chart._segments = segments;
chart.append.apply(chart, segments);
},
seriesMissingValues: function(series) {
var missingValues = series.missingValues, assumeZero = !missingValues && this.options.isStacked;
return assumeZero ? ZERO : missingValues;
},
createSegment: function(linePoints, currentSeries, seriesIx) {
return new LineSegment(linePoints, currentSeries, seriesIx);
},
getNearestPoint: function(x, y, seriesIx) {
var chart = this, invertAxes = chart.options.invertAxes, axis = invertAxes ? Y : X, pos = invertAxes ? y : x, points = chart.seriesPoints[seriesIx], nearestPointDistance = MAX_VALUE, pointsLength = points.length, currentPoint, pointBox, pointDistance, nearestPoint, i;
for (i = 0; i < pointsLength; i++) {
currentPoint = points[i];
if (currentPoint && defined(currentPoint.value) && currentPoint.value !== null) {
pointBox = currentPoint.box;
pointDistance = math.abs(pointBox.center()[axis] - pos);
if (pointDistance < nearestPointDistance) {
nearestPoint = currentPoint;
nearestPointDistance = pointDistance;
}
}
}
return nearestPoint;
}
};
var LineChart = CategoricalChart.extend({
init: function(plotArea, options) {
var chart = this;
chart._stackAxisRange = {
min: MAX_VALUE,
max: MIN_VALUE
};
chart._categoryTotals = [];
chart.enableDiscovery();
CategoricalChart.fn.init.call(chart, plotArea, options);
},
render: function() {
var chart = this;
CategoricalChart.fn.render.apply(chart);
chart.computeAxisRanges();
chart.renderSegments();
},
createPoint: function(data, category, categoryIx, series) {
var chart = this, value = data.value, options = chart.options, isStacked = options.isStacked, categoryPoints = chart.categoryPoints[categoryIx], missingValues = chart.seriesMissingValues(series), stackPoint, plotValue = 0, fields = data.fields;
if (!defined(value) || value === null) {
if (missingValues === ZERO) {
value = 0;
} else {
return null;
}
}
var point = new LinePoint(value, deepExtend({
vertical: !options.invertAxes,
markers: {
border: {
color: series.color
}
}
}, series, {
color: fields.color,
markers: {
border: {
color: fields.color
}
}
}));
if (isStacked) {
stackPoint = lastValue(categoryPoints);
if (stackPoint) {
plotValue = stackPoint.plotValue;
}
point.plotValue = value + plotValue;
}
chart.append(point);
return point;
},
updateRange: function(value, categoryIx) {
var chart = this, isStacked = chart.options.isStacked, stackAxisRange = chart._stackAxisRange, totals = chart._categoryTotals, totalsLimits;
if (defined(value)) {
if (isStacked) {
incrementSlot(totals, categoryIx, value);
totalsLimits = sparseArrayLimits(totals);
stackAxisRange.min = math.min(stackAxisRange.min, totalsLimits.min);
stackAxisRange.max = math.max(stackAxisRange.max, totalsLimits.max);
} else {
CategoricalChart.fn.updateRange.apply(chart, arguments);
}
}
},
computeAxisRanges: function() {
var chart = this, isStacked = chart.options.isStacked, axisName;
if (isStacked) {
axisName = chart.options.series[0].axis;
chart.valueAxisRanges[axisName] = chart._stackAxisRange;
}
},
getViewElements: function(view) {
var chart = this, elements = CategoricalChart.fn.getViewElements.call(chart, view), group = view.createGroup({
animation: {
type: CLIP
}
});
group.children = elements;
return [ group ];
},
bindableFields: function() {
return [ "color" ];
}
});
deepExtend(LineChart.fn, LineChartMixin);
var AreaSegment = LineSegment.extend({
init: function(linePoints, stackPoints, currentSeries, seriesIx) {
var segment = this;
segment.stackPoints = stackPoints;
LineSegment.fn.init.call(segment, linePoints, currentSeries, seriesIx);
},
points: function() {
var segment = this, chart = segment.parent, stack = chart.options.isStacked && segment.seriesIx > 0, plotArea = chart.plotArea, invertAxes = chart.options.invertAxes, valueAxis = chart.seriesValueAxis(segment.series), valueAxisLineBox = valueAxis.lineBox(), categoryAxis = plotArea.seriesCategoryAxis(segment.series), categoryAxisLineBox = categoryAxis.lineBox(), end = invertAxes ? categoryAxisLineBox.x1 : categoryAxisLineBox.y1, stackPoints = segment.stackPoints, points = LineSegment.fn.points.call(segment, stackPoints), firstPoint, lastPoint;
if (invertAxes) {
end = clipValue(end, valueAxisLineBox.x1, valueAxisLineBox.x2);
} else {
end = clipValue(end, valueAxisLineBox.y1, valueAxisLineBox.y2);
}
if (!stack && points.length > 1) {
firstPoint = points[0];
lastPoint = last(points);
if (invertAxes) {
points.unshift(new Point2D(end, firstPoint.y));
points.push(new Point2D(end, lastPoint.y));
} else {
points.unshift(new Point2D(firstPoint.x, end));
points.push(new Point2D(lastPoint.x, end));
}
}
return points;
},
getViewElements: function(view) {
var segment = this, series = segment.series, lineOptions = deepExtend({
color: series.color,
opacity: series.opacity
}, series.line), linePoints = LineSegment.fn.points.call(segment), areaPoints = segment.points();
ChartElement.fn.getViewElements.call(segment, view);
return [ view.createPolyline(areaPoints, false, {
id: segment.options.id,
fillOpacity: series.opacity,
fill: series.color,
stack: series.stack,
data: {
modelId: segment.options.modelId
},
zIndex: -1
}), view.createPolyline(linePoints, false, {
stroke: lineOptions.color,
strokeWidth: lineOptions.width,
strokeOpacity: lineOptions.opacity,
dashType: lineOptions.dashType,
data: {
modelId: segment.options.modelId
},
strokeLineCap: "butt",
zIndex: -1
}) ];
}
});
var AreaChart = LineChart.extend({
createSegment: function(linePoints, currentSeries, seriesIx, prevSegment) {
var chart = this, options = chart.options, stackPoints;
if (options.isStacked && seriesIx > 0 && prevSegment) {
stackPoints = prevSegment.linePoints.slice(0).reverse();
}
return new AreaSegment(linePoints, stackPoints, currentSeries, seriesIx);
},
seriesMissingValues: function(series) {
return series.missingValues || ZERO;
}
});
var ScatterChart = ChartElement.extend({
init: function(plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
// X and Y axis ranges grouped by name, e.g.:
// primary: { min: 0, max: 1 }
chart.xAxisRanges = {};
chart.yAxisRanges = {};
chart.points = [];
chart.seriesPoints = [];
chart.render();
},
options: {
series: [],
tooltip: {
format: "{0}, {1}"
},
labels: {
format: "{0}, {1}"
}
},
render: function() {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
addValue: function(value, fields) {
var chart = this, point, x = value.x, y = value.y, seriesIx = fields.seriesIx, seriesPoints = chart.seriesPoints[seriesIx];
chart.updateRange(value, fields.series);
if (defined(x) && x !== null && defined(y) && y !== null) {
point = chart.createPoint(value, fields.series, seriesIx, fields);
if (point) {
extend(point, fields);
}
}
chart.points.push(point);
seriesPoints.push(point);
},
updateRange: function(value, series) {
var chart = this, x = value.x, y = value.y, xAxisName = series.xAxis, yAxisName = series.yAxis, xAxisRange = chart.xAxisRanges[xAxisName], yAxisRange = chart.yAxisRanges[yAxisName];
if (defined(x) && x !== null) {
xAxisRange = chart.xAxisRanges[xAxisName] = xAxisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
if (typeof x === STRING) {
x = toDate(x);
}
xAxisRange.min = math.min(xAxisRange.min, x);
xAxisRange.max = math.max(xAxisRange.max, x);
}
if (defined(y) && y !== null) {
yAxisRange = chart.yAxisRanges[yAxisName] = yAxisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
if (typeof y === STRING) {
y = toDate(y);
}
yAxisRange.min = math.min(yAxisRange.min, y);
yAxisRange.max = math.max(yAxisRange.max, y);
}
},
createPoint: function(value, series, seriesIx, fields) {
var chart = this, point;
point = new LinePoint(value, deepExtend({
markers: {
border: {
color: series.color
},
opacity: series.opacity
},
tooltip: {
format: chart.options.tooltip.format
},
labels: {
format: chart.options.labels.format
}
}, series, {
color: fields.color,
markers: {
border: {
color: fields.color
}
}
}));
chart.append(point);
return point;
},
seriesAxes: function(series) {
var plotArea = this.plotArea, xAxisName = series.xAxis, xAxis = xAxisName ? plotArea.namedXAxes[xAxisName] : plotArea.axisX, yAxisName = series.yAxis, yAxis = yAxisName ? plotArea.namedYAxes[yAxisName] : plotArea.axisY;
if (!xAxis) {
throw new Error("Unable to locate X axis with name " + xAxisName);
}
if (!yAxis) {
throw new Error("Unable to locate Y axis with name " + yAxisName);
}
return {
x: xAxis,
y: yAxis
};
},
reflow: function(targetBox) {
var chart = this, chartPoints = chart.points, pointIx = 0, point, seriesAxes;
chart.traverseDataPoints(function(value, fields) {
point = chartPoints[pointIx++];
seriesAxes = chart.seriesAxes(fields.series);
var slotX = seriesAxes.x.getSlot(value.x, value.x), slotY = seriesAxes.y.getSlot(value.y, value.y), pointSlot = new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2);
if (point) {
point.reflow(pointSlot);
}
});
chart.box = targetBox;
},
getViewElements: function(view) {
var chart = this, elements = ChartElement.fn.getViewElements.call(chart, view), group = view.createGroup({
animation: {
type: CLIP
}
});
group.children = elements;
return [ group ];
},
traverseDataPoints: function(callback) {
var chart = this, options = chart.options, series = options.series, seriesPoints = chart.seriesPoints, bindableFields = chart.bindableFields(), pointIx, seriesIx, currentSeries, currentSeriesPoints, pointData, value, fields;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
currentSeries = series[seriesIx];
currentSeriesPoints = seriesPoints[seriesIx];
if (!currentSeriesPoints) {
seriesPoints[seriesIx] = [];
}
for (pointIx = 0; pointIx < currentSeries.data.length; pointIx++) {
pointData = bindPoint(currentSeries, pointIx, bindableFields);
value = pointData.value;
fields = pointData.fields;
callback(value, deepExtend({
pointIx: pointIx,
series: currentSeries,
seriesIx: seriesIx,
dataItem: currentSeries.data[pointIx],
owner: chart
}, fields));
}
}
},
bindableFields: function() {
return [ "color" ];
},
formatPointValue: function(point, format) {
var value = point.value;
return autoFormat(format, value.x, value.y);
}
});
var ScatterLineChart = ScatterChart.extend({
render: function() {
var chart = this;
ScatterChart.fn.render.call(chart);
chart.renderSegments();
}
});
deepExtend(ScatterLineChart.fn, LineChartMixin);
var BubbleChart = ScatterChart.extend({
options: {
tooltip: {
format: "{3}"
},
labels: {
format: "{3}"
}
},
addValue: function(value, fields) {
var chart = this, color, series = fields.series, negativeValues = series.negativeValues, seriesColors = chart.plotArea.options.seriesColors || [], visible = true;
color = fields.color || series.color || seriesColors[fields.pointIx % seriesColors.length];
if (value.size < 0) {
color = negativeValues.color || color;
visible = negativeValues.visible;
}
fields.color = color;
if (visible) {
ScatterChart.fn.addValue.call(this, value, fields);
}
},
reflow: function(box) {
var chart = this;
chart.updateBubblesSize(box);
ScatterChart.fn.reflow.call(chart, box);
},
createPoint: function(value, series, seriesIx, fields) {
var chart = this, point, pointsCount = series.data.length, delay = fields.pointIx * (INITIAL_ANIMATION_DURATION / pointsCount), animationOptions = {
delay: delay,
duration: INITIAL_ANIMATION_DURATION - delay,
type: BUBBLE
};
point = new Bubble(value, deepExtend({
tooltip: {
format: chart.options.tooltip.format
},
labels: {
format: chart.options.labels.format,
animation: animationOptions
}
}, series, {
color: fields.color,
markers: {
type: CIRCLE,
background: fields.color,
border: series.border,
opacity: series.opacity,
animation: animationOptions
}
}));
chart.append(point);
return point;
},
updateBubblesSize: function(box) {
var chart = this, options = chart.options, series = options.series, boxSize = math.min(box.width(), box.height()), seriesIx, pointIx;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
var currentSeries = series[seriesIx], seriesPoints = chart.seriesPoints[seriesIx], seriesMaxSize = chart.maxSize(seriesPoints), minSize = currentSeries.minSize || math.max(boxSize * .02, 10), maxSize = currentSeries.maxSize || boxSize * .2, minR = minSize / 2, maxR = maxSize / 2, minArea = math.PI * minR * minR, maxArea = math.PI * maxR * maxR, areaRange = maxArea - minArea, areaRatio = areaRange / seriesMaxSize;
for (pointIx = 0; pointIx < seriesPoints.length; pointIx++) {
var point = seriesPoints[pointIx], area = math.abs(point.value.size) * areaRatio, r = math.sqrt((minArea + area) / math.PI);
deepExtend(point.options, {
markers: {
size: r * 2,
zIndex: maxR - r
},
labels: {
zIndex: maxR - r + 1
}
});
}
}
},
maxSize: function(seriesPoints) {
var length = seriesPoints.length, max = 0, i, size;
for (i = 0; i < length; i++) {
size = seriesPoints[i].value.size;
max = math.max(max, math.abs(size));
}
return max;
},
bindableFields: function() {
return [ "color", "category", "visibleInLegend" ];
},
getViewElements: function(view) {
var chart = this, elements = ChartElement.fn.getViewElements.call(chart, view), group = view.createGroup();
group.children = elements;
return [ group ];
},
formatPointValue: function(point, format) {
var value = point.value;
return autoFormat(format, value.x, value.y, value.size, point.category);
}
});
var Candlestick = ChartElement.extend({
init: function(value, options) {
var point = this;
ChartElement.fn.init.call(point, options);
point.value = value;
point.options.id = uniqueId();
point.enableDiscovery();
},
options: {
border: {
_brightness: .8
},
line: {
width: 2
},
overlay: {
gradient: GLASS
},
tooltip: {
format: "<table style='text-align: left;'>" + "<th colspan='2'>{4:d}</th>" + "<tr><td>Open:</td><td>{0:C}</td></tr>" + "<tr><td>High:</td><td>{1:C}</td></tr>" + "<tr><td>Low:</td><td>{2:C}</td></tr>" + "<tr><td>Close:</td><td>{3:C}</td></tr>" + "</table>"
},
highlight: {
opacity: 1,
border: {
width: 1,
opacity: 1
},
line: {
width: 1,
opacity: 1
}
}
},
reflow: function(box) {
var point = this, options = point.options, chart = point.owner, value = point.value, valueAxis = chart.seriesValueAxis(options), points = [], mid, ocSlot, lhSlot;
ocSlot = valueAxis.getSlot(value.open, value.close);
lhSlot = valueAxis.getSlot(value.low, value.high);
ocSlot.x1 = lhSlot.x1 = box.x1;
ocSlot.x2 = lhSlot.x2 = box.x2;
point.realBody = ocSlot;
mid = lhSlot.center().x;
points.push([ new Point2D(mid, lhSlot.y1), new Point2D(mid, ocSlot.y1) ]);
points.push([ new Point2D(mid, ocSlot.y2), new Point2D(mid, lhSlot.y2) ]);
point.lowHighLinePoints = points;
point.box = lhSlot.clone().wrap(ocSlot);
},
getViewElements: function(view) {
var point = this, options = point.options, elements = [], border = options.border.width > 0 ? {
stroke: point.getBorderColor(),
strokeWidth: options.border.width,
dashType: options.border.dashType,
strokeOpacity: defined(options.border.opacity) ? options.border.opacity : options.opacity
} : {}, rectStyle = deepExtend({
fill: point.color,
fillOpacity: options.opacity
}, border), lineStyle = {
strokeOpacity: defined(options.line.opacity) ? options.line.opacity : options.opacity,
strokeWidth: options.line.width,
stroke: options.line.color || point.color,
dashType: options.line.dashType,
strokeLineCap: "butt"
}, group = view.createGroup({
animation: {
type: CLIP
}
});
if (options.overlay) {
rectStyle.overlay = deepExtend({
rotation: 0
}, options.overlay);
}
elements.push(view.createRect(point.realBody, rectStyle));
elements.push(view.createPolyline(point.lowHighLinePoints[0], false, lineStyle));
elements.push(view.createPolyline(point.lowHighLinePoints[1], false, lineStyle));
elements.push(point.createOverlayRect(view, options));
append(elements, ChartElement.fn.getViewElements.call(point, view));
group.children = elements;
return [ group ];
},
getBorderColor: function() {
var point = this, options = point.options, color = point.color, border = options.border, borderColor = border.color;
if (!defined(borderColor)) {
borderColor = new Color(color).brightness(border._brightness).toHex();
}
return borderColor;
},
createOverlayRect: function(view, options) {
return view.createRect(this.box, {
data: {
modelId: options.modelId
},
fill: "#fff",
fillOpacity: 0
});
},
highlightOverlay: function(view, options) {
var point = this, pointOptions = point.options, highlight = pointOptions.highlight, border = highlight.border, borderColor = point.getBorderColor(), line = highlight.line, data = {
data: {
modelId: pointOptions.modelId
}
}, rectStyle = deepExtend({}, data, options, {
stroke: borderColor,
strokeOpacity: border.opacity,
strokeWidth: border.width
}), lineStyle = deepExtend({}, data, {
stroke: line.color || borderColor,
strokeWidth: line.width,
strokeOpacity: line.opacity,
strokeLineCap: "butt"
}), group = view.createGroup();
group.children.push(view.createRect(point.realBody, rectStyle));
group.children.push(view.createPolyline(point.lowHighLinePoints[0], false, lineStyle));
group.children.push(view.createPolyline(point.lowHighLinePoints[1], false, lineStyle));
return group;
},
tooltipAnchor: function() {
var point = this, box = point.box;
return new Point2D(box.x2 + TOOLTIP_OFFSET, box.y1 + TOOLTIP_OFFSET);
},
formatValue: function(format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(Candlestick.fn, PointEventsMixin);
var CandlestickChart = CategoricalChart.extend({
options: {},
bindableFields: function() {
return [ "color", "downColor" ];
},
reflowCategories: function(categorySlots) {
var chart = this, children = chart.children, childrenLength = children.length, i;
for (i = 0; i < childrenLength; i++) {
children[i].reflow(categorySlots[i]);
}
},
addValue: function(data, category, categoryIx, series, seriesIx) {
var chart = this, options = chart.options, value = data.value, children = chart.children, pointColor = data.fields.color || series.color, valueParts = this.splitValue(value), hasValue = validNumbers(valueParts), categoryPoints = chart.categoryPoints[categoryIx], point, cluster;
if (!categoryPoints) {
chart.categoryPoints[categoryIx] = categoryPoints = [];
}
if (hasValue) {
point = chart.createPoint(value, series);
}
cluster = children[categoryIx];
if (!cluster) {
cluster = new ClusterLayout({
vertical: options.invertAxes,
gap: options.gap,
spacing: options.spacing
});
chart.append(cluster);
}
if (point) {
chart.updateRange(value, categoryIx, series);
cluster.append(point);
if (series.type == CANDLESTICK) {
if (value.open > value.close) {
pointColor = data.fields.downColor || series.downColor;
}
}
point.color = pointColor;
point.categoryIx = categoryIx;
point.category = category;
point.series = series;
point.seriesIx = seriesIx;
point.owner = chart;
point.dataItem = series.data[categoryIx];
}
chart.points.push(point);
categoryPoints.push(point);
},
createPoint: function(value, series) {
return new Candlestick(value, series);
},
splitValue: function(value) {
return [ value.low, value.open, value.close, value.high ];
},
updateRange: function(value, categoryIx, series) {
var chart = this, axisName = series.axis, axisRange = chart.valueAxisRanges[axisName], parts = chart.splitValue(value);
axisRange = chart.valueAxisRanges[axisName] = axisRange || {
min: MAX_VALUE,
max: MIN_VALUE
};
axisRange = chart.valueAxisRanges[axisName] = {
min: math.min.apply(math, parts.concat([ axisRange.min ])),
max: math.max.apply(math, parts.concat([ axisRange.max ]))
};
},
formatPointValue: function(point, format) {
var value = point.value;
return autoFormat(format, value.open, value.high, value.low, value.close, point.category);
}
});
var OHLCPoint = Candlestick.extend({
reflow: function(box) {
var point = this, options = point.options, chart = point.owner, value = point.value, valueAxis = chart.seriesValueAxis(options), oPoints = [], cPoints = [], lhPoints = [], mid, oSlot, cSlot, lhSlot;
lhSlot = valueAxis.getSlot(value.low, value.high);
oSlot = valueAxis.getSlot(value.open, value.open);
cSlot = valueAxis.getSlot(value.close, value.close);
oSlot.x1 = cSlot.x1 = lhSlot.x1 = box.x1;
oSlot.x2 = cSlot.x2 = lhSlot.x2 = box.x2;
mid = lhSlot.center().x;
oPoints.push(new Point2D(oSlot.x1, oSlot.y1));
oPoints.push(new Point2D(mid, oSlot.y1));
cPoints.push(new Point2D(mid, cSlot.y1));
cPoints.push(new Point2D(cSlot.x2, cSlot.y1));
lhPoints.push(new Point2D(mid, lhSlot.y1));
lhPoints.push(new Point2D(mid, lhSlot.y2));
point.oPoints = oPoints;
point.cPoints = cPoints;
point.lhPoints = lhPoints;
point.box = lhSlot.clone().wrap(oSlot.clone().wrap(cSlot));
},
getViewElements: function(view) {
var point = this, options = point.options, elements = [], lineStyle = {
strokeOpacity: options.opacity,
zIndex: -1,
strokeWidth: options.width,
stroke: point.color,
dashType: options.dashType
}, group = view.createGroup({
animation: {
type: CLIP
}
});
elements.push(point.createOverlayRect(view, options));
elements.push(view.createPolyline(point.oPoints, true, lineStyle));
elements.push(view.createPolyline(point.cPoints, true, lineStyle));
elements.push(view.createPolyline(point.lhPoints, true, lineStyle));
append(elements, ChartElement.fn.getViewElements.call(point, view));
group.children = elements;
return [ group ];
},
highlightOverlay: function(view) {
var point = this, pointOptions = point.options, highlight = pointOptions.highlight, data = {
data: {
modelId: pointOptions.modelId
}
}, lineStyle = deepExtend(data, {
strokeWidth: highlight.line.width,
strokeOpacity: highlight.line.opacity,
stroke: highlight.line.color || point.color
}), group = view.createGroup();
group.children.push(view.createPolyline(point.oPoints, true, lineStyle));
group.children.push(view.createPolyline(point.cPoints, true, lineStyle));
group.children.push(view.createPolyline(point.lhPoints, true, lineStyle));
return group;
}
});
var OHLCChart = CandlestickChart.extend({
createPoint: function(value, series) {
return new OHLCPoint(value, series);
},
bindableFields: function() {
return [ "color" ];
}
});
var PieSegment = ChartElement.extend({
init: function(value, sector, options) {
var segment = this;
segment.value = value;
segment.sector = sector;
segment.enableDiscovery();
ChartElement.fn.init.call(segment, options);
},
options: {
color: WHITE,
overlay: {
gradient: ROUNDED_BEVEL
},
border: {
width: .5
},
labels: {
visible: false,
distance: 35,
font: DEFAULT_FONT,
margin: getSpacing(.5),
align: CIRCLE,
zIndex: 1,
position: OUTSIDE_END
},
animation: {
type: PIE
},
highlight: {
visible: true,
border: {
width: 1
}
}
},
render: function() {
var segment = this, options = segment.options, labels = options.labels, labelText = segment.value, labelTemplate;
if (segment._rendered) {
return;
} else {
segment._rendered = true;
}
if (labels.template) {
labelTemplate = template(labels.template);
labelText = labelTemplate({
dataItem: segment.dataItem,
category: segment.category,
value: segment.value,
series: segment.series,
percentage: segment.percentage
});
} else if (labels.format) {
labelText = autoFormat(labels.format, labelText);
}
if (labels.visible && labelText) {
segment.label = new TextBox(labelText, deepExtend({}, labels, {
id: uniqueId(),
align: CENTER,
vAlign: "",
animation: {
type: FADEIN,
delay: segment.animationDelay
}
}));
segment.append(segment.label);
}
},
reflow: function(targetBox) {
var segment = this;
segment.render();
segment.box = targetBox;
segment.reflowLabel();
},
reflowLabel: function() {
var segment = this, sector = segment.sector.clone(), options = segment.options, label = segment.label, labelsOptions = options.labels, labelsDistance = labelsOptions.distance, angle = sector.middle(), lp, x1, labelWidth, labelHeight;
if (label) {
labelHeight = label.box.height();
labelWidth = label.box.width();
if (labelsOptions.position == CENTER) {
sector.r = math.abs((sector.r - labelHeight) / 2) + labelHeight;
lp = sector.point(angle);
label.reflow(new Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else if (labelsOptions.position == INSIDE_END) {
sector.r = sector.r - labelHeight / 2;
lp = sector.point(angle);
label.reflow(new Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else {
lp = sector.clone().expand(labelsDistance).point(angle);
if (lp.x >= sector.c.x) {
x1 = lp.x + labelWidth;
label.orientation = RIGHT;
} else {
x1 = lp.x - labelWidth;
label.orientation = LEFT;
}
label.reflow(new Box2D(x1, lp.y - labelHeight, lp.x, lp.y));
}
}
},
getViewElements: function(view) {
var segment = this, sector = segment.sector, options = segment.options, borderOptions = options.border || {}, border = borderOptions.width > 0 ? {
stroke: borderOptions.color,
strokeWidth: borderOptions.width,
strokeOpacity: borderOptions.opacity,
dashType: borderOptions.dashType
} : {}, elements = [], overlay = options.overlay;
if (overlay) {
overlay = deepExtend({}, options.overlay, {
r: sector.r,
ir: sector.ir,
cx: sector.c.x,
cy: sector.c.y,
bbox: sector.getBBox()
});
}
if (segment.value) {
elements.push(segment.createSegment(view, sector, deepExtend({
id: options.id,
fill: options.color,
overlay: overlay,
fillOpacity: options.opacity,
strokeOpacity: options.opacity,
animation: deepExtend(options.animation, {
delay: segment.animationDelay
}),
data: {
modelId: options.modelId
},
zIndex: options.zIndex,
singleSegment: (segment.options.data || []).length === 1
}, border)));
}
append(elements, ChartElement.fn.getViewElements.call(segment, view));
return elements;
},
createSegment: function(view, sector, options) {
if (options.singleSegment) {
return view.createCircle(sector.c, sector.r, options);
} else {
return view.createSector(sector, options);
}
},
highlightOverlay: function(view, options) {
var segment = this, highlight = segment.options.highlight || {}, border = highlight.border || {}, outlineId = segment.options.id + OUTLINE_SUFFIX, element;
options = deepExtend({}, options, {
id: outlineId
});
if (segment.value !== 0) {
element = segment.createSegment(view, segment.sector, deepExtend({}, options, {
fill: highlight.color,
fillOpacity: highlight.opacity,
strokeOpacity: border.opacity,
strokeWidth: border.width,
stroke: border.color,
id: null,
data: {
modelId: segment.options.modelId
}
}));
}
return element;
},
tooltipAnchor: function(tooltipWidth, tooltipHeight) {
var point = this, sector = point.sector.clone().expand(TOOLTIP_OFFSET), w = tooltipWidth / 2, h = tooltipHeight / 2, midAndle = sector.middle(), pointAngle = midAndle * DEGREE, lp = sector.point(midAndle), cx = lp.x - w, cy = lp.y - h, sa = math.sin(pointAngle), ca = math.cos(pointAngle);
if (math.abs(sa) < .9) {
cx += w * -ca / math.abs(ca);
}
if (math.abs(ca) < .9) {
cy += h * -sa / math.abs(sa);
}
return new Point2D(cx, cy);
},
formatValue: function(format) {
var point = this;
return point.owner.formatPointValue(point, format);
}
});
deepExtend(PieSegment.fn, PointEventsMixin);
var PieChart = ChartElement.extend({
init: function(plotArea, options) {
var chart = this;
ChartElement.fn.init.call(chart, options);
chart.plotArea = plotArea;
chart.segments = [];
chart.legendItems = [];
chart.render();
},
options: {
startAngle: 90,
connectors: {
width: 1,
color: "#939393",
padding: 4
}
},
render: function() {
var chart = this;
chart.traverseDataPoints(proxy(chart.addValue, chart));
},
traverseDataPoints: function(callback) {
var chart = this, options = chart.options, colors = chart.plotArea.options.seriesColors || [], colorsCount = colors.length, series = options.series, seriesCount = series.length, overlayId = uniqueId(), bindableFields = chart.bindableFields(), currentSeries, pointData, fields, seriesIx, angle, data, anglePerValue, value, explode, total, currentAngle, i;
for (seriesIx = 0; seriesIx < seriesCount; seriesIx++) {
currentSeries = series[seriesIx];
data = currentSeries.data;
total = chart.pointsTotal(currentSeries);
anglePerValue = 360 / total;
if (defined(currentSeries.startAngle)) {
currentAngle = currentSeries.startAngle;
} else {
currentAngle = options.startAngle;
}
if (seriesIx != seriesCount - 1) {
if (currentSeries.labels.position == OUTSIDE_END) {
currentSeries.labels.position = CENTER;
}
}
for (i = 0; i < data.length; i++) {
pointData = bindPoint(currentSeries, i, bindableFields);
value = pointData.value;
fields = pointData.fields;
angle = round(value * anglePerValue, DEFAULT_PRECISION);
explode = data.length != 1 && !!fields.explode;
currentSeries.color = fields.color || colors[i % colorsCount];
callback(value, new Ring(null, 0, 0, currentAngle, angle), {
owner: chart,
category: fields.category || "",
categoryIx: i,
series: currentSeries,
seriesIx: seriesIx,
dataItem: data[i],
percentage: value / total,
explode: explode,
visibleInLegend: fields.visibleInLegend,
overlay: {
id: overlayId + seriesIx
},
zIndex: seriesCount - seriesIx,
animationDelay: chart.animationDelay(i, seriesIx, seriesCount)
});
currentAngle += angle;
}
}
},
bindableFields: function() {
return [ "category", "color", "explode", "visibleInLegend" ];
},
addValue: function(value, sector, fields) {
var chart = this, segment;
chart.createLegendItem(value, fields);
if (!value) {
return;
}
segment = new PieSegment(value, sector, fields.series);
segment.options.id = uniqueId();
extend(segment, fields);
chart.append(segment);
chart.segments.push(segment);
},
createLegendItem: function(value, point) {
var chart = this, options = (chart.options.legend || {}).labels || {}, text, labelTemplate;
if (point && point.visibleInLegend !== false) {
text = point.category || "";
if ((options || {}).template) {
labelTemplate = template(options.template);
text = labelTemplate({
text: text,
series: point.series,
dataItem: point.dataItem,
percentage: point.percentage,
value: value
});
}
chart.legendItems.push({
name: text,
color: point.series.color
});
}
},
pointsTotal: function(series) {
var data = series.data, length = data.length, sum = 0, i;
for (i = 0; i < length; i++) {
sum += bindPoint(series, i).value;
}
return sum;
},
reflow: function(targetBox) {
var chart = this, options = chart.options, box = targetBox.clone(), space = 5, minWidth = math.min(box.width(), box.height()), halfMinWidth = minWidth / 2, defaultPadding = minWidth - minWidth * .85, padding = defined(options.padding) ? options.padding : defaultPadding, newBox = new Box2D(box.x1, box.y1, box.x1 + minWidth, box.y1 + minWidth), newBoxCenter = newBox.center(), seriesConfigs = chart.seriesConfigs || [], boxCenter = box.center(), segments = chart.segments, count = segments.length, seriesCount = options.series.length, leftSideLabels = [], rightSideLabels = [], seriesConfig, seriesIndex, label, segment, sector, r, i, c;
padding = padding > halfMinWidth - space ? halfMinWidth - space : padding;
newBox.translate(boxCenter.x - newBoxCenter.x, boxCenter.y - newBoxCenter.y);
r = halfMinWidth - padding;
c = new Point2D(r + newBox.x1 + padding, r + newBox.y1 + padding);
for (i = 0; i < count; i++) {
segment = segments[i];
sector = segment.sector;
sector.r = r;
sector.c = c;
seriesIndex = segment.seriesIx;
if (seriesConfigs.length) {
seriesConfig = seriesConfigs[seriesIndex];
sector.ir = seriesConfig.ir;
sector.r = seriesConfig.r;
}
if (seriesIndex == seriesCount - 1 && segment.explode) {
sector.c = sector.clone().radius(sector.r * .15).point(sector.middle());
}
segment.reflow(newBox);
label = segment.label;
if (label) {
if (label.options.position === OUTSIDE_END) {
if (seriesIndex == seriesCount - 1) {
if (label.orientation === RIGHT) {
rightSideLabels.push(label);
} else {
leftSideLabels.push(label);
}
}
}
}
}
if (leftSideLabels.length > 0) {
leftSideLabels.sort(chart.labelComparator(true));
chart.leftLabelsReflow(leftSideLabels);
}
if (rightSideLabels.length > 0) {
rightSideLabels.sort(chart.labelComparator(false));
chart.rightLabelsReflow(rightSideLabels);
}
chart.box = newBox;
},
leftLabelsReflow: function(labels) {
var chart = this, distances = chart.distanceBetweenLabels(labels);
chart.distributeLabels(distances, labels);
},
rightLabelsReflow: function(labels) {
var chart = this, distances = chart.distanceBetweenLabels(labels);
chart.distributeLabels(distances, labels);
},
distanceBetweenLabels: function(labels) {
var chart = this, segments = chart.segments, segment = segments[segments.length - 1], sector = segment.sector, firstBox = labels[0].box, count = labels.length - 1, lr = sector.r + segment.options.labels.distance, distances = [], secondBox, distance, i;
distance = round(firstBox.y1 - (sector.c.y - lr - firstBox.height() - firstBox.height() / 2));
distances.push(distance);
for (i = 0; i < count; i++) {
firstBox = labels[i].box;
secondBox = labels[i + 1].box;
distance = round(secondBox.y1 - firstBox.y2);
distances.push(distance);
}
distance = round(sector.c.y + lr - labels[count].box.y2 - labels[count].box.height() / 2);
distances.push(distance);
return distances;
},
distributeLabels: function(distances, labels) {
var chart = this, count = distances.length, remaining, left, right, i;
for (i = 0; i < count; i++) {
left = right = i;
remaining = -distances[i];
while (remaining > 0 && (left >= 0 || right < count)) {
remaining = chart._takeDistance(distances, i, --left, remaining);
remaining = chart._takeDistance(distances, i, ++right, remaining);
}
}
chart.reflowLabels(distances, labels);
},
_takeDistance: function(distances, anchor, position, amount) {
if (distances[position] > 0) {
var available = math.min(distances[position], amount);
amount -= available;
distances[position] -= available;
distances[anchor] += available;
}
return amount;
},
reflowLabels: function(distances, labels) {
var chart = this, segments = chart.segments, segment = segments[segments.length - 1], sector = segment.sector, labelsCount = labels.length, labelOptions = segment.options.labels, labelDistance = labelOptions.distance, boxY = sector.c.y - (sector.r + labelDistance) - labels[0].box.height(), label, boxX, box, i;
distances[0] += 2;
for (i = 0; i < labelsCount; i++) {
label = labels[i];
boxY += distances[i];
box = label.box;
boxX = chart.hAlignLabel(box.x2, sector.clone().expand(labelDistance), boxY, boxY + box.height(), label.orientation == RIGHT);
if (label.orientation == RIGHT) {
if (labelOptions.align !== CIRCLE) {
boxX = sector.r + sector.c.x + labelDistance;
}
label.reflow(new Box2D(boxX + box.width(), boxY, boxX, boxY));
} else {
if (labelOptions.align !== CIRCLE) {
boxX = sector.c.x - sector.r - labelDistance;
}
label.reflow(new Box2D(boxX - box.width(), boxY, boxX, boxY));
}
boxY += box.height();
}
},
getViewElements: function(view) {
var chart = this, options = chart.options, connectors = options.connectors, segments = chart.segments, connectorLine, lines = [], count = segments.length, space = 4, sector, angle, points, segment, seriesIx, label, i;
for (i = 0; i < count; i++) {
segment = segments[i];
sector = segment.sector;
angle = sector.middle();
label = segment.label;
seriesIx = {
seriesId: segment.seriesIx
};
if (label) {
points = [];
if (label.options.position === OUTSIDE_END && segment.value !== 0) {
var box = label.box, centerPoint = sector.c, start = sector.point(angle), middle = new Point2D(box.x1, box.center().y), sr, end, crossing;
start = sector.clone().expand(connectors.padding).point(angle);
points.push(start);
if (label.orientation == RIGHT) {
end = new Point2D(box.x1 - connectors.padding, box.center().y);
crossing = intersection(centerPoint, start, middle, end);
middle = new Point2D(end.x - space, end.y);
crossing = crossing || middle;
crossing.x = math.min(crossing.x, middle.x);
if (chart.pointInCircle(crossing, sector.c, sector.r + space) || crossing.x < sector.c.x) {
sr = sector.c.x + sector.r + space;
if (segment.options.labels.align !== COLUMN) {
if (sr < middle.x) {
points.push(new Point2D(sr, start.y));
} else {
points.push(new Point2D(start.x + space * 2, start.y));
}
} else {
points.push(new Point2D(sr, start.y));
}
points.push(new Point2D(middle.x, end.y));
} else {
crossing.y = end.y;
points.push(crossing);
}
} else {
end = new Point2D(box.x2 + connectors.padding, box.center().y);
crossing = intersection(centerPoint, start, middle, end);
middle = new Point2D(end.x + space, end.y);
crossing = crossing || middle;
crossing.x = math.max(crossing.x, middle.x);
if (chart.pointInCircle(crossing, sector.c, sector.r + space) || crossing.x > sector.c.x) {
sr = sector.c.x - sector.r - space;
if (segment.options.labels.align !== COLUMN) {
if (sr > middle.x) {
points.push(new Point2D(sr, start.y));
} else {
points.push(new Point2D(start.x - space * 2, start.y));
}
} else {
points.push(new Point2D(sr, start.y));
}
points.push(new Point2D(middle.x, end.y));
} else {
crossing.y = end.y;
points.push(crossing);
}
}
points.push(end);
connectorLine = view.createPolyline(points, false, {
id: uniqueId(),
stroke: connectors.color,
strokeWidth: connectors.width,
animation: {
type: FADEIN,
delay: segment.animationDelay
},
data: {
modelId: segment.options.modelId
}
});
lines.push(connectorLine);
}
}
}
append(lines, ChartElement.fn.getViewElements.call(chart, view));
return lines;
},
labelComparator: function(reverse) {
reverse = reverse ? -1 : 1;
return function(a, b) {
a = (a.parent.sector.middle() + 270) % 360;
b = (b.parent.sector.middle() + 270) % 360;
return (a - b) * reverse;
};
},
hAlignLabel: function(originalX, sector, y1, y2, direction) {
var cx = sector.c.x, cy = sector.c.y, r = sector.r, t = math.min(math.abs(cy - y1), math.abs(cy - y2));
if (t > r) {
return originalX;
} else {
return cx + math.sqrt(r * r - t * t) * (direction ? 1 : -1);
}
},
pointInCircle: function(point, c, r) {
return sqr(c.x - point.x) + sqr(c.y - point.y) < sqr(r);
},
formatPointValue: function(point, format) {
return autoFormat(format, point.value);
},
animationDelay: function(categoryIndex) {
return categoryIndex * PIE_SECTOR_ANIM_DELAY;
}
});
var DonutSegment = PieSegment.extend({
options: {
overlay: {
gradient: ROUNDED_GLASS
},
labels: {
position: CENTER
},
animation: {
type: PIE
}
},
reflowLabel: function() {
var segment = this, sector = segment.sector.clone(), options = segment.options, label = segment.label, labelsOptions = options.labels, lp, angle = sector.middle(), labelHeight;
if (label) {
labelHeight = label.box.height();
if (labelsOptions.position == CENTER) {
sector.r -= (sector.r - sector.ir) / 2;
lp = sector.point(angle);
label.reflow(new Box2D(lp.x, lp.y - labelHeight / 2, lp.x, lp.y));
} else {
PieSegment.fn.reflowLabel.call(segment);
}
}
},
createSegment: function(view, sector, options) {
return view.createRing(sector, options);
}
});
deepExtend(DonutSegment.fn, PointEventsMixin);
var DonutChart = PieChart.extend({
options: {
startAngle: 90,
connectors: {
width: 1,
color: "#939393",
padding: 4
}
},
addValue: function(value, sector, fields) {
var chart = this, segment;
chart.createLegendItem(value, fields);
if (!value) {
return;
}
segment = new DonutSegment(value, sector, fields.series);
segment.options.id = uniqueId();
extend(segment, fields);
chart.append(segment);
chart.segments.push(segment);
},
reflow: function(targetBox) {
var chart = this, options = chart.options, box = targetBox.clone(), space = 5, minWidth = math.min(box.width(), box.height()), halfMinWidth = minWidth / 2, defaultPadding = minWidth - minWidth * .85, padding = defined(options.padding) ? options.padding : defaultPadding, series = options.series, currentSeries, seriesCount = series.length, seriesWithoutSize = 0, holeSize, totalSize, size, margin = 0, i, r, ir = 0, currentSize = 0;
chart.seriesConfigs = [];
padding = padding > halfMinWidth - space ? halfMinWidth - space : padding, totalSize = halfMinWidth - padding;
for (i = 0; i < seriesCount; i++) {
currentSeries = series[i];
if (i === 0) {
if (defined(currentSeries.holeSize)) {
holeSize = currentSeries.holeSize;
totalSize -= currentSeries.holeSize;
}
}
if (defined(currentSeries.size)) {
totalSize -= currentSeries.size;
} else {
seriesWithoutSize++;
}
if (defined(currentSeries.margin) && i != seriesCount - 1) {
totalSize -= currentSeries.margin;
}
}
if (!defined(holeSize)) {
currentSize = (halfMinWidth - padding) / (seriesCount + .75);
holeSize = currentSize * .75;
totalSize -= holeSize;
}
ir = holeSize;
for (i = 0; i < seriesCount; i++) {
currentSeries = series[i];
size = defined(currentSeries.size) ? currentSeries.size : totalSize / seriesWithoutSize;
ir += margin;
r = ir + size;
chart.seriesConfigs.push({
ir: ir,
r: r
});
margin = currentSeries.margin || 0;
ir = r;
}
PieChart.fn.reflow.call(chart, targetBox);
},
animationDelay: function(categoryIndex, seriesIndex, seriesCount) {
return categoryIndex * DONUT_SECTOR_ANIM_DELAY + INITIAL_ANIMATION_DURATION * (seriesIndex + 1) / (seriesCount + 1);
}
});
var Pane = BoxElement.extend({
init: function(options) {
var pane = this;
BoxElement.fn.init.call(pane, options);
options = pane.options;
options.id = uniqueId();
pane.title = Title.buildTitle(options.title, pane, Pane.fn.options.title);
pane.content = new ChartElement();
pane.append(pane.content);
pane.axes = [];
pane.charts = [];
},
options: {
zIndex: -1,
shrinkToFit: true,
title: {
align: LEFT
},
visible: true
},
appendAxis: function(axis) {
var pane = this;
pane.content.append(axis);
pane.axes.push(axis);
axis.pane = pane;
},
appendChart: function(chart) {
var pane = this;
pane.charts.push(chart);
pane.content.append(chart);
chart.pane = pane;
},
empty: function() {
var pane = this, plotArea = pane.parent, i;
if (plotArea) {
for (i = 0; i < pane.axes.length; i++) {
plotArea.removeAxis(pane.axes[i]);
}
for (i = 0; i < pane.charts.length; i++) {
plotArea.removeChart(pane.charts[i]);
}
}
pane.axes = [];
pane.charts = [];
pane.content.destroy();
pane.content.children = [];
},
reflow: function(targetBox) {
var pane = this;
// Content (such as charts) is rendered, but excluded from reflows
if (last(pane.children) === pane.content) {
pane.children.pop();
}
BoxElement.fn.reflow.call(pane, targetBox);
if (pane.title) {
pane.contentBox.y1 += pane.title.box.height();
}
},
getViewElements: function(view) {
var pane = this, elements = CategoricalChart.fn.getViewElements.call(pane, view), group = view.createGroup({
id: pane.options.id
}), result = [];
group.children = elements.concat(pane.renderGridLines(view), pane.content.getViewElements(view));
pane.view = view;
if (pane.options.visible) {
result = [ group ];
}
return result;
},
renderGridLines: function(view) {
var pane = this, axes = pane.axes, allAxes = axes.concat(pane.parent.axes), vGridLines = [], hGridLines = [], gridLines, i, j, axis, vertical, altAxis;
for (i = 0; i < axes.length; i++) {
axis = axes[i];
vertical = axis.options.vertical;
gridLines = vertical ? vGridLines : hGridLines;
for (j = 0; j < allAxes.length; j++) {
if (gridLines.length === 0) {
altAxis = allAxes[j];
if (vertical !== altAxis.options.vertical) {
append(gridLines, axis.renderGridLines(view, altAxis, axis));
}
}
}
}
return vGridLines.concat(hGridLines);
},
refresh: function() {
var pane = this, view = pane.view, element = getElement(pane.options.id);
if (view && element) {
element.parentNode.replaceChild(view.renderElement(pane.getViewElements(view)[0]), element);
}
}
});
var PlotAreaBase = ChartElement.extend({
init: function(series, options) {
var plotArea = this;
ChartElement.fn.init.call(plotArea, options);
plotArea.series = series;
plotArea.charts = [];
plotArea.options.legend.items = [];
plotArea.axes = [];
plotArea.crosshairs = [];
plotArea.options.id = uniqueId();
plotArea.enableDiscovery();
plotArea.createPanes();
plotArea.render();
plotArea.createCrosshairs();
},
options: {
series: [],
plotArea: {
margin: {}
},
background: "",
border: {
color: BLACK,
width: 0
},
legend: {}
},
createPanes: function() {
var plotArea = this, panes = [], paneOptions = plotArea.options.panes || [], i, panesLength = math.max(paneOptions.length, 1), currentPane;
for (i = 0; i < panesLength; i++) {
currentPane = new Pane(paneOptions[i]);
currentPane.paneIndex = i;
panes.push(currentPane);
plotArea.append(currentPane);
}
plotArea.panes = panes;
},
destroy: function() {
var plotArea = this, charts = plotArea.charts, axes = plotArea.axes, i;
for (i = 0; i < charts.length; i++) {
charts[i].destroy();
}
for (i = 0; i < axes.length; i++) {
axes[i].destroy();
}
ChartElement.fn.destroy.call(plotArea);
},
createCrosshairs: function() {
var plotArea = this, panes = plotArea.panes, i, j, pane, axis, currentCrosshair;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
for (j = 0; j < pane.axes.length; j++) {
axis = pane.axes[j];
if (axis.options.crosshair && axis.options.crosshair.visible) {
currentCrosshair = new Crosshair(axis, axis.options.crosshair);
plotArea.crosshairs.push(currentCrosshair);
plotArea.append(currentCrosshair);
}
}
}
},
findPane: function(name) {
var plotArea = this, panes = plotArea.panes, i, matchingPane;
for (i = 0; i < panes.length; i++) {
if (panes[i].options.name === name) {
matchingPane = panes[i];
break;
}
}
return matchingPane || panes[0];
},
findPointPane: function(point) {
var plotArea = this, panes = plotArea.panes, i, matchingPane;
for (i = 0; i < panes.length; i++) {
if (panes[i].box.containsPoint(point)) {
matchingPane = panes[i];
break;
}
}
return matchingPane;
},
appendAxis: function(axis) {
var plotArea = this, pane = plotArea.findPane(axis.options.pane);
pane.appendAxis(axis);
plotArea.axes.push(axis);
axis.plotArea = plotArea;
},
removeAxis: function(axisToRemove) {
var plotArea = this, i, axis, filteredAxes = [];
for (i = 0; i < plotArea.axes.length; i++) {
axis = plotArea.axes[i];
if (axisToRemove !== axis) {
filteredAxes.push(axis);
}
}
plotArea.axes = filteredAxes;
},
appendChart: function(chart, pane) {
var plotArea = this;
plotArea.charts.push(chart);
plotArea.addToLegend(chart);
if (pane) {
pane.appendChart(chart);
} else {
plotArea.append(chart);
}
},
removeChart: function(chartToRemove) {
var plotArea = this, i, chart, filteredCharts = [];
for (i = 0; i < plotArea.charts.length; i++) {
chart = plotArea.charts[i];
if (chart !== chartToRemove) {
filteredCharts.push(chart);
}
}
plotArea.charts = filteredCharts;
},
addToLegend: function(chart) {
var series = chart.options.series, count = series.length, data = [], i, currentSeries, text, labelTemplate, labels = this.options.legend.labels || {};
if (chart.legendItems) {
data = chart.legendItems;
} else {
for (i = 0; i < count; i++) {
currentSeries = series[i];
if (currentSeries.visibleInLegend !== false) {
text = currentSeries.name || "";
if (labels.template) {
labelTemplate = template(labels.template);
text = labelTemplate({
text: text,
series: currentSeries
});
}
data.push({
name: text,
color: currentSeries.color
});
}
}
}
append(this.options.legend.items, data);
},
groupAxes: function(panes) {
var xAxes = [], yAxes = [], paneAxes, axis, paneIx, axisIx;
for (paneIx = 0; paneIx < panes.length; paneIx++) {
paneAxes = panes[paneIx].axes;
for (axisIx = 0; axisIx < paneAxes.length; axisIx++) {
axis = paneAxes[axisIx];
if (axis.options.vertical) {
yAxes.push(axis);
} else {
xAxes.push(axis);
}
}
}
return {
x: xAxes,
y: yAxes,
any: xAxes.concat(yAxes)
};
},
groupSeriesByPane: function() {
var plotArea = this, series = plotArea.series, seriesByPane = {}, i, pane, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
pane = plotArea.seriesPaneName(currentSeries);
if (seriesByPane[pane]) {
seriesByPane[pane].push(currentSeries);
} else {
seriesByPane[pane] = [ currentSeries ];
}
}
return seriesByPane;
},
filterSeriesByType: function(series, types) {
var i, currentSeries, result = [];
types = [].concat(types);
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (inArray(currentSeries.type, types)) {
result.push(currentSeries);
}
}
return result;
},
reflow: function(targetBox) {
var plotArea = this, options = plotArea.options.plotArea, panes = plotArea.panes, margin = getSpacing(options.margin);
plotArea.box = targetBox.clone().unpad(margin);
plotArea.reflowPanes();
plotArea.reflowAxes(panes);
plotArea.reflowCharts(panes);
},
redraw: function(panes) {
var plotArea = this, i;
panes = [].concat(panes);
for (i = 0; i < panes.length; i++) {
panes[i].empty();
}
plotArea.render(panes);
plotArea.reflowAxes(plotArea.panes);
plotArea.reflowCharts(panes);
for (i = 0; i < panes.length; i++) {
panes[i].refresh();
}
},
axisCrossingValues: function(axis, crossingAxes) {
var options = axis.options, crossingValues = [].concat(options.axisCrossingValues || options.axisCrossingValue), valuesToAdd = crossingAxes.length - crossingValues.length, defaultValue = crossingValues[0] || 0, i;
for (i = 0; i < valuesToAdd; i++) {
crossingValues.push(defaultValue);
}
return crossingValues;
},
alignAxisTo: function(axis, targetAxis, crossingValue, targetCrossingValue) {
var slot = axis.getSlot(crossingValue, crossingValue), slotEdge = axis.options.reverse ? 2 : 1, targetSlot = targetAxis.getSlot(targetCrossingValue, targetCrossingValue), targetEdge = targetAxis.options.reverse ? 2 : 1, axisBox = axis.box.translate(targetSlot[X + targetEdge] - slot[X + slotEdge], targetSlot[Y + targetEdge] - slot[Y + slotEdge]);
if (axis.pane !== targetAxis.pane) {
axisBox.translate(0, axis.pane.box.y1 - targetAxis.pane.box.y1);
}
axis.reflow(axisBox);
},
alignAxes: function(xAxes, yAxes) {
var plotArea = this, xAnchor = xAxes[0], yAnchor = yAxes[0], xAnchorCrossings = plotArea.axisCrossingValues(xAnchor, yAxes), yAnchorCrossings = plotArea.axisCrossingValues(yAnchor, xAxes), leftAnchors = {}, rightAnchors = {}, topAnchors = {}, bottomAnchors = {}, pane, paneId, axis, i;
for (i = 0; i < yAxes.length; i++) {
axis = yAxes[i];
pane = axis.pane;
paneId = pane.options.id;
plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().x1) === round(xAnchor.lineBox().x1)) {
if (leftAnchors[paneId]) {
axis.reflow(axis.box.alignTo(leftAnchors[paneId].box, LEFT).translate(-axis.options.margin, 0));
}
leftAnchors[paneId] = axis;
}
if (round(axis.lineBox().x2) === round(xAnchor.lineBox().x2)) {
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
plotArea.alignAxisTo(axis, xAnchor, yAnchorCrossings[i], xAnchorCrossings[i]);
if (rightAnchors[paneId]) {
axis.reflow(axis.box.alignTo(rightAnchors[paneId].box, RIGHT).translate(axis.options.margin, 0));
}
rightAnchors[paneId] = axis;
}
if (i !== 0 && yAnchor.pane === axis.pane) {
axis.alignTo(yAnchor);
}
}
for (i = 0; i < xAxes.length; i++) {
axis = xAxes[i];
pane = axis.pane;
paneId = pane.options.id;
plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().y1) === round(yAnchor.lineBox().y1)) {
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
plotArea.alignAxisTo(axis, yAnchor, xAnchorCrossings[i], yAnchorCrossings[i]);
if (topAnchors[paneId]) {
axis.reflow(axis.box.alignTo(topAnchors[paneId].box, TOP).translate(0, -axis.options.margin));
}
topAnchors[paneId] = axis;
}
if (round(axis.lineBox().y2, COORD_PRECISION) === round(yAnchor.lineBox().y2, COORD_PRECISION)) {
if (bottomAnchors[paneId]) {
axis.reflow(axis.box.alignTo(bottomAnchors[paneId].box, BOTTOM).translate(0, axis.options.margin));
}
bottomAnchors[paneId] = axis;
}
if (i !== 0) {
axis.alignTo(xAnchor);
}
}
},
shrinkAxisWidth: function(panes) {
var plotArea = this, axes = plotArea.groupAxes(panes).any, axisBox = axisGroupBox(axes), overflowX = 0, i, currentPane, currentAxis;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
if (currentPane.axes.length > 0) {
overflowX = math.max(overflowX, axisBox.width() - currentPane.contentBox.width());
}
}
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
if (!currentAxis.options.vertical) {
currentAxis.reflow(currentAxis.box.shrink(overflowX, 0));
}
}
},
shrinkAxisHeight: function(panes) {
var i, currentPane, axes, overflowY, j, currentAxis;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
axes = currentPane.axes, overflowY = math.max(0, axisGroupBox(axes).height() - currentPane.contentBox.height());
for (j = 0; j < axes.length; j++) {
currentAxis = axes[j];
if (currentAxis.options.vertical) {
currentAxis.reflow(currentAxis.box.shrink(0, overflowY));
}
}
}
},
fitAxes: function(panes) {
var plotArea = this, axes = plotArea.groupAxes(panes).any, offsetX = 0, paneAxes, paneBox, axisBox, offsetY, currentPane, currentAxis, i, j;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
paneAxes = currentPane.axes;
paneBox = currentPane.contentBox;
if (paneAxes.length > 0) {
axisBox = axisGroupBox(paneAxes);
// OffsetX is calculated and applied globally
offsetX = math.max(offsetX, paneBox.x1 - axisBox.x1);
// OffsetY is calculated and applied per pane
offsetY = math.max(paneBox.y1 - axisBox.y1, paneBox.y2 - axisBox.y2);
for (j = 0; j < paneAxes.length; j++) {
currentAxis = paneAxes[j];
currentAxis.reflow(currentAxis.box.translate(0, offsetY));
}
}
}
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
currentAxis.reflow(currentAxis.box.translate(offsetX, 0));
}
},
reflowAxes: function(panes) {
var plotArea = this, i, axes = plotArea.groupAxes(panes);
for (i = 0; i < panes.length; i++) {
plotArea.reflowPaneAxes(panes[i]);
}
if (axes.x.length > 0 && axes.y.length > 0) {
plotArea.alignAxes(axes.x, axes.y);
plotArea.shrinkAxisWidth(panes);
plotArea.alignAxes(axes.x, axes.y);
plotArea.shrinkAxisHeight(panes);
plotArea.alignAxes(axes.x, axes.y);
plotArea.fitAxes(panes);
}
},
reflowPaneAxes: function(pane) {
var axes = pane.axes, i, length = axes.length;
if (length > 0) {
for (i = 0; i < length; i++) {
axes[i].reflow(pane.contentBox);
}
}
},
reflowCharts: function(panes) {
var plotArea = this, charts = plotArea.charts, count = charts.length, box = plotArea.box, chartPane, i;
for (i = 0; i < count; i++) {
chartPane = charts[i].pane;
if (!chartPane || inArray(chartPane, panes)) {
charts[i].reflow(box);
}
}
},
reflowPanes: function() {
var plotArea = this, box = plotArea.box, panes = plotArea.panes, i, panesLength = panes.length, currentPane, paneBox, remainingHeight = box.height(), remainingPanes = panesLength, autoHeightPanes = 0, top = box.y1, height, percents;
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
height = currentPane.options.height;
currentPane.options.width = box.width();
if (!currentPane.options.height) {
autoHeightPanes++;
} else {
if (height.indexOf && height.indexOf("%")) {
percents = parseInt(height, 10) / 100;
currentPane.options.height = percents * box.height();
}
currentPane.reflow(box.clone());
remainingHeight -= currentPane.options.height;
}
}
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
if (!currentPane.options.height) {
currentPane.options.height = remainingHeight / autoHeightPanes;
}
}
for (i = 0; i < panesLength; i++) {
currentPane = panes[i];
paneBox = box.clone().move(box.x1, top);
currentPane.reflow(paneBox);
remainingPanes--;
top += currentPane.options.height;
}
},
backgroundBox: function() {
var plotArea = this, axes = plotArea.axes, axesCount = axes.length, lineBox, box, i, j, axisA, axisB;
for (i = 0; i < axesCount; i++) {
axisA = axes[i];
for (j = 0; j < axesCount; j++) {
axisB = axes[j];
if (axisA.options.vertical !== axisB.options.vertical) {
lineBox = axisA.lineBox().clone().wrap(axisB.lineBox());
if (!box) {
box = lineBox;
} else {
box = box.wrap(lineBox);
}
}
}
}
return box || plotArea.box;
},
getViewElements: function(view) {
var plotArea = this, bgBox = plotArea.backgroundBox(), options = plotArea.options, userOptions = options.plotArea, border = userOptions.border || {}, elements = ChartElement.fn.getViewElements.call(plotArea, view);
append(elements, [ view.createRect(bgBox, {
fill: userOptions.background,
fillOpacity: userOptions.opacity,
zIndex: -2,
strokeWidth: .1
}), view.createRect(bgBox, {
id: options.id,
data: {
modelId: options.modelId
},
stroke: border.width ? border.color : "",
strokeWidth: border.width,
fill: WHITE,
fillOpacity: 0,
zIndex: -1,
dashType: border.dashType
}) ]);
return elements;
},
pointsByCategoryIndex: function(categoryIndex) {
var charts = this.charts, result = [], i, j, points, point, chart;
if (categoryIndex !== null) {
for (i = 0; i < charts.length; i++) {
chart = charts[i];
if (chart.pane.options.name === "_navigator") {
continue;
}
points = charts[i].categoryPoints[categoryIndex];
if (points && points.length) {
for (j = 0; j < points.length; j++) {
point = points[j];
if (point && defined(point.value) && point.value !== null) {
result.push(point);
}
}
}
}
}
return result;
},
paneByPoint: function(point) {
var plotArea = this, panes = plotArea.panes, pane, i;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
if (pane.box.containsPoint(point)) {
return pane;
}
}
}
});
var CategoricalPlotArea = PlotAreaBase.extend({
init: function(series, options) {
var plotArea = this;
plotArea.namedCategoryAxes = {};
plotArea.namedValueAxes = {};
plotArea.valueAxisRangeTracker = new AxisGroupRangeTracker();
if (series.length > 0) {
plotArea.invertAxes = inArray(series[0].type, [ BAR, BULLET, VERTICAL_LINE, VERTICAL_AREA ]);
}
PlotAreaBase.fn.init.call(plotArea, series, options);
},
options: {
categoryAxis: {
categories: []
},
valueAxis: {}
},
render: function(panes) {
var plotArea = this;
panes = panes || plotArea.panes;
plotArea.createCategoryAxes(panes);
plotArea.aggregateDateSeries(panes);
plotArea.createCharts(panes);
plotArea.createValueAxes(panes);
},
removeAxis: function(axis) {
var plotArea = this, axisName = axis.options.name;
PlotAreaBase.fn.removeAxis.call(plotArea, axis);
if (axis instanceof CategoryAxis) {
delete plotArea.namedCategoryAxes[axisName];
} else {
plotArea.valueAxisRangeTracker.reset(axisName);
delete plotArea.namedValueAxes[axisName];
}
if (axis === plotArea.categoryAxis) {
delete plotArea.categoryAxis;
}
if (axis === plotArea.valueAxis) {
delete plotArea.valueAxis;
}
},
createCharts: function(panes) {
var plotArea = this, seriesByPane = plotArea.groupSeriesByPane(), i, pane, paneSeries;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
paneSeries = seriesByPane[pane.options.name || "default"];
if (!paneSeries) {
continue;
}
plotArea.createAreaChart(plotArea.filterSeriesByType(paneSeries, [ AREA, VERTICAL_AREA ]), pane);
plotArea.createBarChart(plotArea.filterSeriesByType(paneSeries, [ COLUMN, BAR ]), pane);
plotArea.createLineChart(plotArea.filterSeriesByType(paneSeries, [ LINE, VERTICAL_LINE ]), pane);
plotArea.createCandlestickChart(plotArea.filterSeriesByType(paneSeries, CANDLESTICK), pane);
plotArea.createOHLCChart(plotArea.filterSeriesByType(paneSeries, OHLC), pane);
plotArea.createBulletChart(plotArea.filterSeriesByType(paneSeries, [ BULLET, VERTICAL_BULLET ]), pane);
}
},
aggregateDateSeries: function(panes) {
var plotArea = this, series = plotArea.srcSeries || plotArea.series, processedSeries = [], categoryAxis, axisPane, categories, categoryMap, groupIx, categoryIndicies, seriesIx, currentSeries, seriesClone, srcData, data, srcValues, i, categoryIx, pointData, value, srcDataItems;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
currentSeries = series[seriesIx];
seriesClone = deepExtend({}, currentSeries);
categoryAxis = plotArea.seriesCategoryAxis(currentSeries);
axisPane = plotArea.findPane(categoryAxis.options.pane);
if (inArray(axisPane, panes) && equalsIgnoreCase(categoryAxis.options.type, DATE)) {
categories = categoryAxis.options.categories;
categoryMap = categoryAxis.categoryMap;
srcData = seriesClone.data;
seriesClone.data = data = [];
for (groupIx = 0; groupIx < categories.length; groupIx++) {
categoryIndicies = categoryMap[groupIx];
srcValues = [];
srcDataItems = [];
for (i = 0; i < categoryIndicies.length; i++) {
categoryIx = categoryIndicies[i];
pointData = bindPoint(currentSeries, categoryIx);
value = pointData.value;
if (defined(value)) {
srcValues.push(pointData.value);
}
srcDataItems.push(currentSeries.data[categoryIx]);
}
if (srcValues.length > 1) {
data[groupIx] = calculateAggregates(srcValues, currentSeries, srcDataItems);
} else {
data[groupIx] = srcData[categoryIndicies[0]];
}
}
}
processedSeries.push(seriesClone);
}
plotArea.srcSeries = series;
plotArea.series = processedSeries;
},
appendChart: function(chart, pane) {
var plotArea = this, series = chart.options.series, categoryAxis = plotArea.seriesCategoryAxis(series[0]), categories = categoryAxis.options.categories, categoriesToAdd = math.max(0, categoriesCount(series) - categories.length);
while (categoriesToAdd--) {
categories.push("");
}
plotArea.valueAxisRangeTracker.update(chart.valueAxisRanges);
PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
},
// TODO: Refactor, optionally use series.pane option
seriesPaneName: function(series) {
var plotArea = this, options = plotArea.options, axisName = series.axis, axisOptions = [].concat(options.valueAxis), axis = $.grep(axisOptions, function(a) {
return a.name === axisName;
})[0], panes = options.panes || [ {} ], defaultPaneName = (panes[0] || {}).name || "default", paneName = (axis || {}).pane || defaultPaneName;
return paneName;
},
seriesCategoryAxis: function(series) {
var plotArea = this, axisName = series.categoryAxis, axis = axisName ? plotArea.namedCategoryAxes[axisName] : plotArea.categoryAxis;
if (!axis) {
throw new Error("Unable to locate category axis with name " + axisName);
}
return axis;
},
createBarChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], barChart = new BarChart(plotArea, {
series: series,
invertAxes: plotArea.invertAxes,
isStacked: firstSeries.stack && series.length > 1,
gap: firstSeries.gap,
spacing: firstSeries.spacing
});
plotArea.appendChart(barChart, pane);
},
createBulletChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], bulletChart = new BulletChart(plotArea, {
series: series,
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
spacing: firstSeries.spacing
});
plotArea.appendChart(bulletChart, pane);
},
createLineChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], lineChart = new LineChart(plotArea, {
invertAxes: plotArea.invertAxes,
isStacked: firstSeries.stack && series.length > 1,
series: series
});
plotArea.appendChart(lineChart, pane);
},
createAreaChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], areaChart = new AreaChart(plotArea, {
invertAxes: plotArea.invertAxes,
isStacked: firstSeries.stack && series.length > 1,
series: series
});
plotArea.appendChart(areaChart, pane);
},
createOHLCChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], chart = new OHLCChart(plotArea, {
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
series: series,
spacing: firstSeries.spacing
});
plotArea.appendChart(chart, pane);
},
createCandlestickChart: function(series, pane) {
if (series.length === 0) {
return;
}
var plotArea = this, firstSeries = series[0], chart = new CandlestickChart(plotArea, {
invertAxes: plotArea.invertAxes,
gap: firstSeries.gap,
series: series,
spacing: firstSeries.spacing
});
plotArea.appendChart(chart, pane);
},
axisRequiresRounding: function(categoryAxisName, categoryAxisIndex) {
var plotArea = this, centeredSeries = plotArea.filterSeriesByType(plotArea.series, [ BAR, COLUMN, OHLC, CANDLESTICK ]), seriesIx, seriesAxis;
for (seriesIx = 0; seriesIx < centeredSeries.length; seriesIx++) {
seriesAxis = centeredSeries[seriesIx].categoryAxis || "";
if (seriesAxis === categoryAxisName || !seriesAxis && categoryAxisIndex === 0) {
return true;
}
}
},
createCategoryAxes: function(panes) {
var plotArea = this, invertAxes = plotArea.invertAxes, definitions = [].concat(plotArea.options.categoryAxis), i, axisOptions, axisPane, categories, type, name, dateCategory, categoryAxis, axes = [], primaryAxis;
for (i = 0; i < definitions.length; i++) {
axisOptions = definitions[i];
axisPane = plotArea.findPane(axisOptions.pane);
if (inArray(axisPane, panes)) {
name = axisOptions.name;
categories = axisOptions.categories || [];
dateCategory = categories[0] instanceof Date;
type = axisOptions.type || "";
axisOptions = deepExtend({
vertical: invertAxes,
axisCrossingValue: invertAxes ? MAX_VALUE : 0
}, axisOptions);
if (!defined(axisOptions.justified)) {
axisOptions.justified = plotArea.inJustified();
}
if (plotArea.axisRequiresRounding(name, i)) {
axisOptions.justified = false;
axisOptions.roundToBaseUnit = true;
}
if (!type && dateCategory || equalsIgnoreCase(type, DATE)) {
categoryAxis = new DateCategoryAxis(axisOptions);
} else {
categoryAxis = new CategoryAxis(axisOptions);
}
if (name) {
if (plotArea.namedCategoryAxes[name]) {
throw new Error("Category axis with name " + name + " is already defined");
}
plotArea.namedCategoryAxes[name] = categoryAxis;
}
categoryAxis.axisIndex = i;
axes.push(categoryAxis);
plotArea.appendAxis(categoryAxis);
}
}
primaryAxis = plotArea.categoryAxis || axes[0];
plotArea.categoryAxis = primaryAxis;
if (invertAxes) {
plotArea.axisY = primaryAxis;
} else {
plotArea.axisX = primaryAxis;
}
},
inJustified: function() {
var plotArea = this, series = plotArea.series, i, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (currentSeries.type !== AREA) {
return false;
}
}
return true;
},
createValueAxes: function(panes) {
var plotArea = this, tracker = plotArea.valueAxisRangeTracker, defaultRange = tracker.query(), definitions = [].concat(plotArea.options.valueAxis), invertAxes = plotArea.invertAxes, baseOptions = {
vertical: !invertAxes
}, axisOptions, axisPane, valueAxis, primaryAxis, axes = [], range, name, i;
for (i = 0; i < definitions.length; i++) {
axisOptions = definitions[i];
axisPane = plotArea.findPane(axisOptions.pane);
if (inArray(axisPane, panes)) {
name = axisOptions.name;
range = tracker.query(name) || defaultRange || {
min: 0,
max: 1
};
if (i === 0 && range && defaultRange) {
range.min = math.min(range.min, defaultRange.min);
range.max = math.max(range.max, defaultRange.max);
}
valueAxis = new NumericAxis(range.min, range.max, deepExtend({}, baseOptions, axisOptions));
if (name) {
if (plotArea.namedValueAxes[name]) {
throw new Error("Value axis with name " + name + " is already defined");
}
plotArea.namedValueAxes[name] = valueAxis;
}
valueAxis.axisIndex = i;
axes.push(valueAxis);
plotArea.appendAxis(valueAxis);
}
}
primaryAxis = plotArea.valueAxis || axes[0];
plotArea.valueAxis = primaryAxis;
if (invertAxes) {
plotArea.axisX = primaryAxis;
} else {
plotArea.axisY = primaryAxis;
}
},
click: function(chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), pane = plotArea.pointPane(point), allAxes, i, axis, categories = [], values = [];
if (!pane) {
return;
}
allAxes = pane.axes;
for (i = 0; i < allAxes.length; i++) {
axis = allAxes[i];
if (axis.getValue) {
appendIfNotNull(values, axis.getValue(point));
} else {
appendIfNotNull(categories, axis.getCategory(point));
}
}
if (categories.length === 0) {
appendIfNotNull(categories, plotArea.categoryAxis.getCategory(point));
}
if (categories.length > 0 && values.length > 0) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
category: singleItemOrArray(categories),
value: singleItemOrArray(values)
});
}
},
pointPane: function(point) {
var plotArea = this, panes = plotArea.panes, currentPane, i;
for (i = 0; i < panes.length; i++) {
currentPane = panes[i];
if (currentPane.contentBox.containsPoint(point)) {
return currentPane;
}
}
}
});
var AxisGroupRangeTracker = Class.extend({
init: function() {
var tracker = this;
tracker.axisRanges = {};
},
update: function(chartAxisRanges) {
var tracker = this, axisRanges = tracker.axisRanges, range, chartRange, axisName;
for (axisName in chartAxisRanges) {
range = axisRanges[axisName];
chartRange = chartAxisRanges[axisName];
axisRanges[axisName] = range = range || {
min: MAX_VALUE,
max: MIN_VALUE
};
range.min = math.min(range.min, chartRange.min);
range.max = math.max(range.max, chartRange.max);
}
},
reset: function(axisName) {
this.axisRanges[axisName] = undefined;
},
query: function(axisName) {
return this.axisRanges[axisName];
}
});
var XYPlotArea = PlotAreaBase.extend({
init: function(series, options) {
var plotArea = this;
plotArea.namedXAxes = {};
plotArea.namedYAxes = {};
plotArea.xAxisRangeTracker = new AxisGroupRangeTracker();
plotArea.yAxisRangeTracker = new AxisGroupRangeTracker();
PlotAreaBase.fn.init.call(plotArea, series, options);
},
options: {
xAxis: {},
yAxis: {}
},
render: function(panes) {
var plotArea = this, seriesByPane = plotArea.groupSeriesByPane(), i, pane, paneSeries;
panes = panes || plotArea.panes;
for (i = 0; i < panes.length; i++) {
pane = panes[i];
paneSeries = seriesByPane[pane.options.name || "default"];
if (!paneSeries) {
continue;
}
plotArea.createScatterChart(plotArea.filterSeriesByType(paneSeries, SCATTER), pane);
plotArea.createScatterLineChart(plotArea.filterSeriesByType(paneSeries, SCATTER_LINE), pane);
plotArea.createBubbleChart(plotArea.filterSeriesByType(paneSeries, BUBBLE), pane);
}
plotArea.createAxes(panes);
},
appendChart: function(chart, pane) {
var plotArea = this;
plotArea.xAxisRangeTracker.update(chart.xAxisRanges);
plotArea.yAxisRangeTracker.update(chart.yAxisRanges);
PlotAreaBase.fn.appendChart.call(plotArea, chart, pane);
},
removeAxis: function(axis) {
var plotArea = this, axisName = axis.options.name;
PlotAreaBase.fn.removeAxis.call(plotArea, axis);
if (axis.options.vertical) {
plotArea.yAxisRangeTracker.reset(axisName);
delete plotArea.namedYAxes[axisName];
} else {
plotArea.xAxisRangeTracker.reset(axisName);
delete plotArea.namedXAxes[axisName];
}
if (axis === plotArea.axisX) {
delete plotArea.axisX;
}
if (axis === plotArea.axisY) {
delete plotArea.axisY;
}
},
// TODO: Refactor, optionally use series.pane option
seriesPaneName: function(series) {
var plotArea = this, options = plotArea.options, xAxisName = series.xAxis, xAxisOptions = [].concat(options.xAxis), xAxis = $.grep(xAxisOptions, function(a) {
return a.name === xAxisName;
})[0], yAxisName = series.yAxis, yAxisOptions = [].concat(options.yAxis), yAxis = $.grep(yAxisOptions, function(a) {
return a.name === yAxisName;
})[0], panes = options.panes || [ {} ], defaultPaneName = panes[0].name || "default", paneName = (xAxis || {}).pane || (yAxis || {}).pane || defaultPaneName;
return paneName;
},
createScatterChart: function(series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new ScatterChart(plotArea, {
series: series
}), pane);
}
},
createScatterLineChart: function(series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new ScatterLineChart(plotArea, {
series: series
}), pane);
}
},
createBubbleChart: function(series, pane) {
var plotArea = this;
if (series.length > 0) {
plotArea.appendChart(new BubbleChart(plotArea, {
series: series
}), pane);
}
},
createXYAxis: function(options, vertical, axisIndex) {
var plotArea = this, axisName = options.name, namedAxes = vertical ? plotArea.namedYAxes : plotArea.namedXAxes, tracker = vertical ? plotArea.yAxisRangeTracker : plotArea.xAxisRangeTracker, defaultRange = tracker.query(), range = tracker.query(axisName) || defaultRange || {
min: 0,
max: 1
}, axisOptions = deepExtend({}, options, {
vertical: vertical
}), axis, seriesIx, series = plotArea.series, currentSeries, seriesAxisName, firstPointValue, typeSamples = [ axisOptions.min, axisOptions.max ], inferredDate, i;
for (seriesIx = 0; seriesIx < series.length; seriesIx++) {
currentSeries = series[seriesIx];
seriesAxisName = currentSeries[vertical ? "yAxis" : "xAxis"];
if (seriesAxisName == axisOptions.name || axisIndex === 0 && !seriesAxisName) {
firstPointValue = bindPoint(currentSeries, 0).value;
typeSamples.push(firstPointValue[vertical ? "y" : "x"]);
break;
}
}
if (axisIndex === 0 && defaultRange) {
range.min = math.min(range.min, defaultRange.min);
range.max = math.max(range.max, defaultRange.max);
}
for (i = 0; i < typeSamples.length; i++) {
if (typeSamples[i] instanceof Date) {
inferredDate = true;
break;
}
}
if (equalsIgnoreCase(axisOptions.type, DATE) || !axisOptions.type && inferredDate) {
axis = new DateValueAxis(range.min, range.max, axisOptions);
} else {
axis = new NumericAxis(range.min, range.max, axisOptions);
}
if (axisName) {
if (namedAxes[axisName]) {
throw new Error((vertical ? "Y" : "X") + " axis with name " + axisName + " is already defined");
}
namedAxes[axisName] = axis;
}
plotArea.appendAxis(axis);
return axis;
},
createAxes: function(panes) {
var plotArea = this, options = plotArea.options, axisPane, xAxesOptions = [].concat(options.xAxis), xAxes = [], yAxesOptions = [].concat(options.yAxis), yAxes = [];
each(xAxesOptions, function(i) {
axisPane = plotArea.findPane(this.pane);
if (inArray(axisPane, panes)) {
xAxes.push(plotArea.createXYAxis(this, false, i));
}
});
each(yAxesOptions, function(i) {
axisPane = plotArea.findPane(this.pane);
if (inArray(axisPane, panes)) {
yAxes.push(plotArea.createXYAxis(this, true, i));
}
});
plotArea.axisX = plotArea.axisX || xAxes[0];
plotArea.axisY = plotArea.axisY || yAxes[0];
},
click: function(chart, e) {
var plotArea = this, coords = chart._eventCoordinates(e), point = new Point2D(coords.x, coords.y), allAxes = plotArea.axes, i, length = allAxes.length, axis, xValues = [], yValues = [], currentValue, values;
for (i = 0; i < length; i++) {
axis = allAxes[i];
values = axis.options.vertical ? yValues : xValues;
currentValue = axis.getValue(point);
if (currentValue !== null) {
values.push(currentValue);
}
}
if (xValues.length > 0 && yValues.length > 0) {
chart.trigger(PLOT_AREA_CLICK, {
element: $(e.target),
x: singleItemOrArray(xValues),
y: singleItemOrArray(yValues)
});
}
}
});
var PiePlotArea = PlotAreaBase.extend({
render: function() {
var plotArea = this, series = plotArea.series;
plotArea.createPieChart(series);
},
createPieChart: function(series) {
var plotArea = this, firstSeries = series[0], pieChart = new PieChart(plotArea, {
series: series,
padding: firstSeries.padding,
startAngle: firstSeries.startAngle,
connectors: firstSeries.connectors,
legend: plotArea.options.legend
});
plotArea.appendChart(pieChart);
}
});
var DonutPlotArea = PiePlotArea.extend({
render: function() {
var plotArea = this, series = plotArea.series;
plotArea.createDonutChart(series);
},
createDonutChart: function(series) {
var plotArea = this, firstSeries = series[0], donutChart = new DonutChart(plotArea, {
series: series,
padding: firstSeries.padding,
connectors: firstSeries.connectors,
legend: plotArea.options.legend
});
plotArea.appendChart(donutChart);
}
});
var PieAnimation = ElementAnimation.extend({
options: {
easing: "easeOutElastic",
duration: INITIAL_ANIMATION_DURATION
},
setup: function() {
var element = this.element, sector = element.config, startRadius;
if (element.options.singleSegment) {
sector = element;
}
this.endRadius = sector.r;
startRadius = this.startRadius = sector.ir || 0;
sector.r = startRadius;
},
step: function(pos) {
var animation = this, element = animation.element, endRadius = animation.endRadius, sector = element.config, startRadius = animation.startRadius;
if (element.options.singleSegment) {
sector = element;
}
sector.r = interpolateValue(startRadius, endRadius, pos);
}
});
var BubbleAnimation = ElementAnimation.extend({
options: {
easing: "easeOutElastic",
duration: INITIAL_ANIMATION_DURATION
},
setup: function() {
var circle = this.element;
circle.endRadius = circle.radius;
circle.radius = 0;
},
step: function(pos) {
var circle = this.element, endRadius = circle.endRadius;
circle.radius = interpolateValue(0, endRadius, pos);
}
});
var BarAnimationDecorator = animationDecorator(BAR, BarAnimation), PieAnimationDecorator = animationDecorator(PIE, PieAnimation), BubbleAnimationDecorator = animationDecorator(BUBBLE, BubbleAnimation);
var Highlight = Class.extend({
init: function(view, viewElement, options) {
var highlight = this;
highlight.options = deepExtend({}, highlight.options, options);
highlight.view = view;
highlight.viewElement = viewElement;
highlight._overlays = [];
},
options: {
fill: WHITE,
fillOpacity: .2,
stroke: WHITE,
strokeWidth: 1,
strokeOpacity: .2
},
show: function(points) {
var highlight = this, view = highlight.view, viewElement = highlight.viewElement, overlay, overlays = highlight._overlays, overlayElement, i, point, pointOptions;
highlight.hide();
highlight._points = points = [].concat(points);
for (i = 0; i < points.length; i++) {
point = points[i];
pointOptions = point.options;
if (!pointOptions || pointOptions.highlight.visible) {
if (point.highlightOverlay) {
overlay = point.highlightOverlay(view, highlight.options);
if (overlay) {
overlayElement = view.renderElement(overlay);
viewElement.appendChild(overlayElement);
overlays.push(overlayElement);
}
}
if (point.toggleHighlight) {
point.toggleHighlight(view);
}
}
}
},
hide: function() {
var highlight = this, points = highlight._points, overlays = highlight._overlays, overlay, i, point, pointOptions;
while (overlays.length) {
overlay = highlight._overlays.pop();
overlay.parentNode.removeChild(overlay);
}
if (points) {
for (i = 0; i < points.length; i++) {
point = points[i];
pointOptions = point.options;
if (!pointOptions || pointOptions.highlight.visible) {
if (point.toggleHighlight) {
point.toggleHighlight(highlight.view);
}
}
}
}
highlight._points = [];
}
});
var BaseTooltip = Class.extend({
init: function(chartElement, options) {
var tooltip = this;
tooltip.options = deepExtend({}, tooltip.options, options);
tooltip.chartElement = chartElement;
tooltip.chartPadding = {
top: parseInt(chartElement.css("paddingTop"), 10),
left: parseInt(chartElement.css("paddingLeft"), 10)
};
tooltip.template = BaseTooltip.template;
if (!tooltip.template) {
tooltip.template = BaseTooltip.template = renderTemplate("<div class='" + CSS_PREFIX + "tooltip' " + "style='display:none; position: absolute; font: #= d.font #;" + "border: #= d.border.width #px solid;" + "opacity: #= d.opacity #; filter: alpha(opacity=#= d.opacity * 100 #);'>" + "</div>");
}
tooltip.element = $(tooltip.template(tooltip.options)).appendTo(chartElement);
tooltip._moveProxy = proxy(tooltip.move, tooltip);
},
options: {
border: {
width: 1
},
opacity: 1,
animation: {
duration: TOOLTIP_ANIMATION_DURATION
}
},
move: function() {
var tooltip = this, options = tooltip.options, element = tooltip.element, offset = tooltip._offset();
if (!tooltip.visible) {
element.css({
top: offset.top,
left: offset.left
});
}
element.stop(true, true).show().animate({
left: offset.left,
top: offset.top
}, options.animation.duration);
tooltip.visible = true;
},
_offset: function() {
var tooltip = this, element = tooltip.element, anchor = tooltip.anchor, chartPadding = tooltip.chartPadding, top = round(anchor.y + chartPadding.top), left = round(anchor.x + chartPadding.left), zoomLevel = kendo.support.zoomLevel(), viewport = $(window), offsetTop = window.pageYOffset || document.documentElement.scrollTop || 0, offsetLeft = window.pageXOffset || document.documentElement.scrollLeft || 0;
offsetTop = tooltip.chartElement.offset().top - offsetTop;
offsetLeft = tooltip.chartElement.offset().left - offsetLeft;
top += tooltip._currentPosition(top + offsetTop, element.outerHeight(), viewport.outerHeight() / zoomLevel);
left += tooltip._currentPosition(left + offsetLeft, element.outerWidth(), viewport.outerWidth() / zoomLevel);
return {
top: top,
left: left
};
},
setStyle: function(options) {
this.element.css({
backgroundColor: options.background,
borderColor: options.border.color || options.background,
font: options.font,
color: options.color,
opacity: options.opacity,
borderWidth: options.border.width
});
},
show: function() {
var tooltip = this;
tooltip.showTimeout = setTimeout(tooltip._moveProxy, TOOLTIP_SHOW_DELAY);
},
hide: function() {
var tooltip = this;
clearTimeout(tooltip.showTimeout);
if (tooltip.visible) {
tooltip._hideElement();
tooltip.point = null;
tooltip.visible = false;
tooltip.index = null;
}
},
_hideElement: function() {
this.element.fadeOut();
},
_pointContent: function(point) {
var tooltip = this, options = deepExtend({}, tooltip.options, point.options.tooltip), content, tooltipTemplate;
if (defined(point.value)) {
content = point.value.toString();
}
if (options.template) {
tooltipTemplate = template(options.template);
content = tooltipTemplate({
value: point.value,
category: point.category,
series: point.series,
dataItem: point.dataItem,
percentage: point.percentage
});
} else if (options.format) {
content = point.formatValue(options.format);
}
return content;
},
_pointAnchor: function(point) {
var tooltip = this, element = tooltip.element;
return point.tooltipAnchor(element.outerWidth(), element.outerHeight());
},
_currentPosition: function(offset, size, viewPortSize) {
var output = 0;
if (offset + size > viewPortSize) {
output = viewPortSize - (offset + size);
}
if (offset < 0) {
output = -offset;
}
return output;
},
_updateStyle: function(options, pointOptions) {
if (!defined(options.background)) {
options.background = pointOptions.color;
}
if (!defined(options.color)) {
var tooltip = this, element = tooltip.element, brightnessValue = new Color(options.background).percBrightness();
if (brightnessValue > 180) {
element.addClass(CSS_PREFIX + TOOLTIP_INVERSE);
} else {
element.removeClass(CSS_PREFIX + TOOLTIP_INVERSE);
}
}
}
});
var Tooltip = BaseTooltip.extend({
show: function(point) {
var tooltip = this, options = deepExtend({}, tooltip.options, point.options.tooltip);
if (!point) {
return;
}
tooltip.element.html(tooltip._pointContent(point));
tooltip.anchor = tooltip._pointAnchor(point);
tooltip._updateStyle(options, point.options);
tooltip.setStyle(options);
BaseTooltip.fn.show.call(tooltip, point);
}
});
var SharedTooltip = BaseTooltip.extend({
init: function(element, plotArea, options) {
var tooltip = this;
BaseTooltip.fn.init.call(tooltip, element, options);
tooltip.plotArea = plotArea;
},
options: {
sharedTemplate: "<table>" + "<th colspan='2'>#= categoryText #</th>" + "# for(var i = 0; i < points.length; i++) { #" + "# var point = points[i]; #" + "<tr>" + "# if(point.series.name) { #<td>#= point.series.name #:</td> # } #" + "<td>#= content(point) #</td>" + "</tr>" + "# } #" + "</table>",
categoryFormat: "{0:d}"
},
showAt: function(points, coords) {
var tooltip = this, options = tooltip.options, plotArea = tooltip.plotArea, axis = plotArea.categoryAxis, index = axis.getCategoryIndex(coords), category = axis.getCategory(coords), slot = axis.getSlot(index), content;
content = tooltip._content(points, category);
tooltip.element.html(content);
tooltip.anchor = tooltip._slotAnchor(coords, slot);
tooltip._updateStyle(options, points[0].options);
tooltip.setStyle(options);
BaseTooltip.fn.show.call(tooltip);
},
_slotAnchor: function(point, slot) {
var tooltip = this, plotArea = tooltip.plotArea, axis = plotArea.categoryAxis, anchor, hCenter = point.y - tooltip.element.height() / 2;
if (axis.options.vertical) {
anchor = Point2D(point.x, hCenter);
} else {
anchor = Point2D(slot.center().x, hCenter);
}
return anchor;
},
_content: function(points, category) {
var tooltip = this, template, content;
template = kendo.template(tooltip.options.sharedTemplate);
content = template({
points: points,
category: category,
categoryText: autoFormat(tooltip.options.categoryFormat, category),
content: tooltip._pointContent
});
return content;
}
});
var Crosshair = ChartElement.extend({
init: function(axis, options) {
var crosshair = this;
ChartElement.fn.init.call(crosshair, options);
crosshair.axis = axis;
if (!crosshair.options.id) {
crosshair.options.id = uniqueId();
}
crosshair._visible = false;
crosshair.stickyMode = axis instanceof CategoryAxis;
},
options: {
color: BLACK,
width: 1,
zIndex: -1,
tooltip: {
visible: false
}
},
repaint: function() {
var crosshair = this, element = crosshair.element;
crosshair.getViewElements(crosshair._view);
element = crosshair.element;
element.refresh(getElement(crosshair.options.id));
},
showAt: function(point) {
var crosshair = this;
crosshair.updateAxisReference();
crosshair._visible = true;
crosshair.point = point;
crosshair.repaint();
if (crosshair.options.tooltip.visible) {
if (!crosshair.tooltip) {
crosshair.tooltip = new CrosshairTooltip(crosshair, deepExtend({}, crosshair.options.tooltip, {
stickyMode: crosshair.stickyMode
}));
}
crosshair.tooltip.showAt(point);
}
},
hide: function() {
var crosshair = this;
if (crosshair._visible) {
crosshair._visible = false;
crosshair.repaint();
if (crosshair.tooltip) {
crosshair.tooltip.hide();
}
}
},
linePoints: function() {
var crosshair = this, axis = crosshair.axis, vertical = axis.options.vertical, box = crosshair.getBox(), point = crosshair.point, dim = vertical ? Y : X, slot, lineStart, lineEnd;
lineStart = Point2D(box.x1, box.y1);
if (vertical) {
lineEnd = Point2D(box.x2, box.y1);
} else {
lineEnd = Point2D(box.x1, box.y2);
}
if (point) {
if (crosshair.stickyMode) {
slot = axis.getSlot(axis.getCategoryIndex(point));
lineStart[dim] = lineEnd[dim] = slot.center()[dim];
} else {
lineStart[dim] = lineEnd[dim] = point[dim];
}
}
crosshair.box = box;
return [ lineStart, lineEnd ];
},
getBox: function() {
var crosshair = this, axis = crosshair.axis, axes = axis.pane.axes, length = axes.length, vertical = axis.options.vertical, box = axis.lineBox().clone(), dim = vertical ? X : Y, axisLineBox, currentAxis, i;
for (i = 0; i < length; i++) {
currentAxis = axes[i];
if (currentAxis.options.vertical != vertical) {
if (!axisLineBox) {
axisLineBox = currentAxis.lineBox().clone();
} else {
axisLineBox.wrap(currentAxis.lineBox());
}
}
}
box[dim + 1] = axisLineBox[dim + 1];
box[dim + 2] = axisLineBox[dim + 2];
return box;
},
getViewElements: function(view) {
var crosshair = this, options = crosshair.options, elements = [];
crosshair.points = crosshair.linePoints();
crosshair.element = view.createPolyline(crosshair.points, false, {
id: options.id,
stroke: options.color,
strokeWidth: options.width,
strokeOpacity: options.opacity,
dashType: options.dashType,
zIndex: options.zIndex,
visible: crosshair._visible
});
elements.push(crosshair.element);
crosshair._view = view;
append(elements, ChartElement.fn.getViewElements.call(crosshair, view));
return elements;
},
updateAxisReference: function() {
var crosshair = this, axis = crosshair.axis, plotArea = axis.plotArea, axes = plotArea.axes, currentAxis, i;
for (i = 0; i < axes.length; i++) {
currentAxis = axes[i];
if (defined(axis.axisIndex) && axis instanceof NumericAxis != currentAxis instanceof CategoryAxis && axis.axisIndex === currentAxis.axisIndex) {
crosshair.axis = currentAxis;
break;
}
}
}
});
var CrosshairTooltip = BaseTooltip.extend({
init: function(crosshair, options) {
var tooltip = this, chartElement = crosshair.axis.getRoot().parent.element;
tooltip.crosshair = crosshair;
BaseTooltip.fn.init.call(tooltip, chartElement, deepExtend({}, tooltip.options, {
background: crosshair.axis.plotArea.options.seriesColors[0]
}, options));
tooltip._updateStyle(tooltip.options, {});
tooltip.setStyle(tooltip.options);
},
options: {
padding: 10
},
showAt: function(point) {
var tooltip = this, element = tooltip.element;
tooltip.point = point;
tooltip.element.html(tooltip.content(point));
tooltip.anchor = tooltip.getAnchor(element.outerWidth(), element.outerHeight());
tooltip.move();
},
move: function() {
var tooltip = this, element = tooltip.element, offset = tooltip._offset();
element.css({
top: offset.top,
left: offset.left
}).show();
},
content: function(point) {
var tooltip = this, options = tooltip.options, axis = tooltip.crosshair.axis, axisOptions = axis.options, content, value, tooltipTemplate;
value = content = axis[options.stickyMode ? "getCategory" : "getValue"](point);
if (options.template) {
tooltipTemplate = template(options.template);
content = tooltipTemplate({
value: value
});
} else if (options.format) {
content = autoFormat(options.format, value);
} else {
if (axisOptions.type === DATE) {
content = autoFormat(axisOptions.labels.dateFormats[axisOptions.baseUnit], value);
}
}
return content;
},
getAnchor: function(width, height) {
var tooltip = this, options = tooltip.options, position = options.position, vertical = tooltip.crosshair.axis.options.vertical, points = tooltip.crosshair.points, fPoint = points[0], sPoint = points[1], halfWidth = width / 2, halfHeight = height / 2, padding = options.padding, x, y;
if (vertical) {
if (position === LEFT) {
x = fPoint.x - width - padding;
y = fPoint.y - halfHeight;
} else {
x = sPoint.x + padding;
y = sPoint.y - halfHeight;
}
} else {
if (position === BOTTOM) {
x = sPoint.x - halfWidth;
y = sPoint.y + padding;
} else {
x = fPoint.x - halfWidth;
y = fPoint.y - height - padding;
}
}
return Point2D(x, y);
},
hide: function() {
this.element.hide();
this.point = null;
}
});
var Aggregates = {
max: function(values) {
var result = math.max.apply(math, values);
if (isNaN(result)) {
return sparseArrayMax(values);
} else {
return result;
}
},
min: function(values) {
var result = math.min.apply(math, values);
if (isNaN(result)) {
return sparseArrayMin(values);
} else {
return result;
}
},
sum: function(values) {
var i, length = values.length, sum = 0, n;
for (i = 0; i < length; i++) {
n = values[i];
if (defined(n) && !isNaN(n)) {
sum += n;
}
}
return sum;
},
count: function(values) {
return values.length;
},
avg: function(values) {
return Aggregates.sum(values) / Aggregates.count(values);
}
};
var Selection = Observable.extend({
init: function(chart, categoryAxis, options) {
var that = this, chartElement = chart.element, categoryAxisLineBox = categoryAxis.lineBox(), valueAxis = that.getValueAxis(categoryAxis), valueAxisLineBox = valueAxis.lineBox(), selectorPrefix = "." + CSS_PREFIX, wrapper, padding;
Observable.fn.init.call(that);
that.options = deepExtend({}, that.options, options);
options = that.options;
that.chart = chart;
that.chartElement = chartElement;
that.categoryAxis = categoryAxis;
that._dateAxis = that.categoryAxis instanceof DateCategoryAxis, that.valueAxis = valueAxis;
if (that._dateAxis) {
deepExtend(options, {
min: toDate(options.min),
max: toDate(options.max),
from: toDate(options.from),
to: toDate(options.to)
});
}
that.template = Selection.template;
if (!that.template) {
that.template = Selection.template = renderTemplate("<div class='" + CSS_PREFIX + "selector' " + "style='width: #= d.width #px; height: #= d.height #px;" + " top: #= d.offset.top #px; left: #= d.offset.left #px;'>" + "<div class='" + CSS_PREFIX + "mask'></div>" + "<div class='" + CSS_PREFIX + "mask'></div>" + "<div class='" + CSS_PREFIX + "selection'>" + "<div class='" + CSS_PREFIX + "handle " + CSS_PREFIX + "leftHandle'><div></div></div>" + "<div class='" + CSS_PREFIX + "handle " + CSS_PREFIX + "rightHandle'><div></div></div>" + "</div></div>");
}
padding = {
left: parseInt(chartElement.css("paddingLeft"), 10),
right: parseInt(chartElement.css("paddingTop"), 10)
};
that.options = deepExtend({}, {
width: categoryAxisLineBox.width(),
height: valueAxisLineBox.height(),
padding: padding,
offset: {
left: valueAxisLineBox.x2 + padding.left,
top: valueAxisLineBox.y1 + padding.right
},
from: options.min,
to: options.max
}, options);
if (that.options.visible) {
that.wrapper = wrapper = $(that.template(that.options)).appendTo(chartElement);
that.selection = wrapper.find(selectorPrefix + "selection");
that.leftMask = wrapper.find(selectorPrefix + "mask").first();
that.rightMask = wrapper.find(selectorPrefix + "mask").last();
that.leftHandle = wrapper.find(selectorPrefix + "leftHandle");
that.rightHandle = wrapper.find(selectorPrefix + "rightHandle");
that.options.selection = {
border: {
left: parseFloat(that.selection.css("border-left-width"), 10),
right: parseFloat(that.selection.css("border-right-width"), 10)
}
};
that.leftHandle.css("top", (that.selection.height() - that.leftHandle.height()) / 2);
that.rightHandle.css("top", (that.selection.height() - that.rightHandle.height()) / 2);
that.set(that._index(options.from), that._index(options.to));
that.bind(that.events, that.options);
that.wrapper[0].style.cssText = that.wrapper[0].style.cssText;
that.wrapper.on(MOUSEWHEEL_NS, proxy(that._mousewheel, that));
if (kendo.UserEvents) {
that.userEvents = new kendo.UserEvents(that.wrapper, {
global: true,
threshold: 5,
stopPropagation: true,
multiTouch: true,
start: proxy(that._start, that),
move: proxy(that._move, that),
end: proxy(that._end, that),
tap: proxy(that._tap, that),
gesturestart: proxy(that._gesturechange, that),
gesturechange: proxy(that._gesturechange, that)
});
} else {
that.leftHandle.add(that.rightHandle).removeClass(CSS_PREFIX + "handle");
}
}
},
events: [ SELECT_START, SELECT, SELECT_END ],
options: {
visible: true,
mousewheel: {
zoom: BOTH
},
min: MIN_VALUE,
max: MAX_VALUE
},
destroy: function() {
var that = this, userEvents = that.userEvents;
if (userEvents) {
userEvents.destroy();
}
},
_rangeEventArgs: function(range) {
var that = this;
return {
axis: that.categoryAxis.options,
from: that._value(range.from),
to: that._value(range.to)
};
},
_start: function(e) {
var that = this, options = that.options, target = $(e.event.target), args;
if (that._state || !target) {
return;
}
that.chart._unsetActivePoint();
that._state = {
moveTarget: target.parents(".k-handle").add(target).first(),
startLocation: e.x ? e.x.location : 0,
range: {
from: that._index(options.from),
to: that._index(options.to)
}
};
args = that._rangeEventArgs({
from: that._index(options.from),
to: that._index(options.to)
});
if (that.trigger(SELECT_START, args)) {
that.userEvents.cancel();
that._state = null;
}
},
_move: function(e) {
if (!this._state) {
return;
}
var that = this, state = that._state, options = that.options, categories = that.categoryAxis.options.categories, from = that._index(options.from), to = that._index(options.to), min = that._index(options.min), max = that._index(options.max), delta = state.startLocation - e.x.location, range = state.range, oldRange = {
from: range.from,
to: range.to
}, span = range.to - range.from, target = state.moveTarget, scale = that.wrapper.width() / (categories.length - 1), offset = math.round(delta / scale);
if (!target) {
return;
}
e.preventDefault();
if (target.is(".k-selection")) {
range.from = math.min(math.max(min, from - offset), max - span);
range.to = math.min(range.from + span, max);
} else if (target.is(".k-leftHandle")) {
range.from = math.min(math.max(min, from - offset), max - 1);
range.to = math.max(range.from + 1, range.to);
} else if (target.is(".k-rightHandle")) {
range.to = math.min(math.max(min + 1, to - offset), max);
range.from = math.min(range.to - 1, range.from);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
that.move(range.from, range.to);
that.trigger(SELECT, that._rangeEventArgs(range));
}
},
_end: function() {
var that = this, range = that._state.range;
delete that._state;
that.set(range.from, range.to);
that.trigger(SELECT_END, that._rangeEventArgs(range));
},
_gesturechange: function(e) {
if (!this._state) {
return;
}
var that = this, chart = that.chart, state = that._state, options = that.options, categoryAxis = that.categoryAxis, range = state.range, p0 = chart._toModelCoordinates(e.touches[0].x.location).x, p1 = chart._toModelCoordinates(e.touches[1].x.location).x, left = math.min(p0, p1), right = math.max(p0, p1);
e.preventDefault();
state.moveTarget = null;
range.from = categoryAxis.getCategoryIndex(new dataviz.Point2D(left)) || options.min;
range.to = categoryAxis.getCategoryIndex(new dataviz.Point2D(right)) || options.max;
that.move(range.from, range.to);
},
_tap: function(e) {
var that = this, options = that.options, coords = that.chart._eventCoordinates(e), categoryAxis = that.categoryAxis, categoryIx = categoryAxis.getCategoryIndex(new dataviz.Point2D(coords.x, categoryAxis.box.y1)), from = that._index(options.from), to = that._index(options.to), min = that._index(options.min), max = that._index(options.max), span = to - from, mid = from + span / 2, offset = math.round(mid - categoryIx), range = {}, rightClick = e.event.which === 3;
if (that._state || rightClick) {
return;
}
e.preventDefault();
that.chart._unsetActivePoint();
if (!categoryAxis.options.justified) {
offset--;
}
range.from = math.min(math.max(min, from - offset), max - span);
range.to = math.min(range.from + span, max);
that._start(e);
if (that._state) {
that._state.range = range;
that.trigger(SELECT, that._rangeEventArgs(range));
that._end();
}
},
_mousewheel: function(e) {
var that = this, options = that.options, delta = mwDelta(e);
that._start({
event: {
target: that.selection
}
});
if (that._state) {
var range = that._state.range;
e.preventDefault();
e.stopPropagation();
if (math.abs(delta) > 1) {
delta *= ZOOM_ACCELERATION;
}
if (options.mousewheel.reverse) {
delta *= -1;
}
if (that.expand(delta)) {
that.trigger(SELECT, {
axis: that.categoryAxis.options,
delta: delta,
originalEvent: e,
from: that._value(range.from),
to: that._value(range.to)
});
}
if (that._mwTimeout) {
clearTimeout(that._mwTimeout);
}
that._mwTimeout = setTimeout(function() {
that._end();
}, MOUSEWHEEL_DELAY);
}
},
_index: function(value) {
var that = this, categoryAxis = that.categoryAxis, categories = categoryAxis.options.categories, index = value;
if (value instanceof Date) {
index = lteDateIndex(categories, value);
if (!categoryAxis.options.justified && value > last(categories)) {
index += 1;
}
}
return index;
},
_value: function(index) {
var that = this, categoryAxis = this.categoryAxis, categories = categoryAxis.options.categories, value = index;
if (that._dateAxis) {
if (index > categories.length - 1) {
value = that.options.max;
} else {
value = categories[index];
}
}
return value;
},
_slot: function(value) {
var that = this, categoryAxis = this.categoryAxis;
return categoryAxis.getSlot(that._index(value));
},
move: function(from, to) {
var that = this, options = that.options, offset = options.offset, padding = options.padding, border = options.selection.border, leftMaskWidth, rightMaskWidth, box, distance;
box = that._slot(from);
leftMaskWidth = round(box.x1 - offset.left + padding.left);
that.leftMask.width(leftMaskWidth);
that.selection.css("left", leftMaskWidth);
box = that._slot(to);
rightMaskWidth = round(options.width - (box.x1 - offset.left + padding.left));
that.rightMask.width(rightMaskWidth);
distance = options.width - rightMaskWidth;
if (distance != options.width) {
distance += border.right;
}
that.rightMask.css("left", distance);
that.selection.width(math.max(options.width - (leftMaskWidth + rightMaskWidth) - border.right, 0));
},
set: function(from, to) {
var that = this, options = that.options, min = that._index(options.min), max = that._index(options.max);
from = clipValue(that._index(from), min, max);
to = clipValue(that._index(to), from + 1, max);
if (options.visible) {
that.move(from, to);
}
options.from = that._value(from);
options.to = that._value(to);
},
expand: function(delta) {
var that = this, options = that.options, min = that._index(options.min), max = that._index(options.max), zDir = options.mousewheel.zoom, from = that._index(options.from), to = that._index(options.to), range = {
from: from,
to: to
}, oldRange = deepExtend({}, range);
if (that._state) {
range = that._state.range;
}
if (zDir !== RIGHT) {
range.from = clipValue(clipValue(from - delta, 0, to - 1), min, max);
}
if (zDir !== LEFT) {
range.to = clipValue(clipValue(to + delta, range.from + 1, max), min, max);
}
if (range.from !== oldRange.from || range.to !== oldRange.to) {
that.set(range.from, range.to);
return true;
}
},
getValueAxis: function(categoryAxis) {
var axes = categoryAxis.pane.axes, axesCount = axes.length, i, axis;
for (i = 0; i < axesCount; i++) {
axis = axes[i];
if (axis.options.vertical !== categoryAxis.options.vertical) {
return axis;
}
}
}
});
function calculateAggregates(values, series, dataItems) {
var aggregate = series.aggregate, result;
function execSimple(values, aggregate, series) {
var result, aggregateType = typeof aggregate;
if (aggregateType === STRING) {
result = Aggregates[aggregate](values);
} else if (aggregateType === "function") {
result = aggregate(values, series, dataItems);
} else {
result = Aggregates.max(values);
}
return result;
}
function execComposite(values, aggregate, series) {
var valueFields = valueFieldsBySeriesType(series.type), valueFieldsCount = valueFields.length, count = values.length, i, j, field, result = [], data = [];
for (i = 0; i < valueFieldsCount; i++) {
field = valueFields[i];
for (j = 0; j < count; j++) {
data.push(values[j][field]);
}
result.push(execSimple(data, aggregate[field], series));
data = [];
}
return result;
}
if (typeof aggregate === "object") {
result = execComposite(values, aggregate, series);
} else {
result = execSimple(values, aggregate, series);
}
return result;
}
function sparseArrayMin(arr) {
return sparseArrayLimits(arr).min;
}
function sparseArrayMax(arr) {
return sparseArrayLimits(arr).max;
}
function sparseArrayLimits(arr) {
var min = MAX_VALUE, max = MIN_VALUE, i, length = arr.length, n;
for (i = 0; i < length; i++) {
n = arr[i];
if (n !== null && isFinite(n)) {
min = math.min(min, n);
max = math.max(max, n);
}
}
return {
min: min === MAX_VALUE ? undefined : min,
max: max === MIN_VALUE ? undefined : max
};
}
function intersection(a1, a2, b1, b2) {
var result, ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y), ua;
if (u_b !== 0) {
ua = ua_t / u_b;
result = new Point2D(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y));
}
return result;
}
function applySeriesDefaults(options, themeOptions) {
var series = options.series, i, seriesLength = series.length, seriesType, seriesDefaults = options.seriesDefaults, commonDefaults = deepExtend({}, options.seriesDefaults), themeSeriesDefaults = themeOptions ? deepExtend({}, themeOptions.seriesDefaults) : {}, commonThemeDefaults = deepExtend({}, themeSeriesDefaults);
cleanupNestedSeriesDefaults(commonDefaults);
cleanupNestedSeriesDefaults(commonThemeDefaults);
for (i = 0; i < seriesLength; i++) {
seriesType = series[i].type || options.seriesDefaults.type;
series[i] = deepExtend({}, commonThemeDefaults, themeSeriesDefaults[seriesType], {
tooltip: options.tooltip
}, commonDefaults, seriesDefaults[seriesType], series[i]);
}
}
function cleanupNestedSeriesDefaults(seriesDefaults) {
delete seriesDefaults.bar;
delete seriesDefaults.column;
delete seriesDefaults.line;
delete seriesDefaults.verticalLine;
delete seriesDefaults.pie;
delete seriesDefaults.donut;
delete seriesDefaults.area;
delete seriesDefaults.verticalArea;
delete seriesDefaults.scatter;
delete seriesDefaults.scatterLine;
delete seriesDefaults.bubble;
delete seriesDefaults.candlestick;
delete seriesDefaults.ohlc;
delete seriesDefaults.bullet;
delete seriesDefaults.verticalBullet;
}
function applySeriesColors(options) {
var series = options.series, i, seriesLength = series.length, colors = options.seriesColors || [];
for (i = 0; i < seriesLength; i++) {
series[i].color = series[i].color || colors[i % colors.length];
}
}
function resolveAxisAliases(options) {
var alias;
each([ CATEGORY, VALUE, X, Y ], function() {
alias = this + "Axes";
if (options[alias]) {
options[this + "Axis"] = options[alias];
delete options[alias];
}
});
}
function applyAxisDefaults(options, themeOptions) {
var themeAxisDefaults = (themeOptions || {}).axisDefaults || {};
each([ CATEGORY, VALUE, X, Y ], function() {
var axisName = this + "Axis", axes = [].concat(options[axisName]), axisDefaults = options.axisDefaults || {};
axes = $.map(axes, function(axisOptions) {
var axisColor = (axisOptions || {}).color;
var result = deepExtend({}, themeAxisDefaults, themeAxisDefaults[axisName], axisDefaults, axisDefaults[axisName], {
line: {
color: axisColor
},
labels: {
color: axisColor
},
title: {
color: axisColor
}
}, axisOptions);
delete result[axisName];
return result;
});
options[axisName] = axes.length > 1 ? axes : axes[0];
});
}
function incrementSlot(slots, index, value) {
slots[index] = (slots[index] || 0) + value;
}
function categoriesCount(series) {
var seriesCount = series.length, categories = 0, i;
for (i = 0; i < seriesCount; i++) {
categories = math.max(categories, series[i].data.length);
}
return categories;
}
function sqr(value) {
return value * value;
}
extend($.easing, {
easeOutElastic: function(n, d, first, diff) {
var s = 1.70158, p = 0, a = diff;
if (n === 0) {
return first;
}
if (n === 1) {
return first + diff;
}
if (!p) {
p = .5;
}
if (a < math.abs(diff)) {
a = diff;
s = p / 4;
} else {
s = p / (2 * math.PI) * math.asin(diff / a);
}
return a * math.pow(2, -10 * n) * math.sin((n * 1 - s) * 1.1 * math.PI / p) + diff + first;
}
});
function getField(field, row) {
if (row === null) {
return row;
}
var get = getter(field, true);
return get(row);
}
function toDate(value) {
var result, aspDate, i;
if (value instanceof Date) {
result = value;
} else if (typeof value === STRING) {
aspDate = DATE_REGEXP.exec(value);
result = new Date(aspDate ? parseInt(aspDate[1], 10) : value);
} else if (value) {
if (value.length) {
result = [];
for (i = 0; i < value.length; i++) {
result.push(toDate(value[i]));
}
} else {
result = new Date(value);
}
}
return result;
}
function toTime(value) {
if (isArray(value)) {
return map(value, toTime);
} else if (value) {
return toDate(value).getTime();
}
}
function addDuration(date, value, unit, weekStartDay) {
var result = date;
if (date) {
date = toDate(date);
if (unit === YEARS) {
result = new Date(date.getFullYear() + value, 0, 1);
} else if (unit === MONTHS) {
result = new Date(date.getFullYear(), date.getMonth() + value, 1);
} else if (unit === WEEKS) {
result = addDuration(startOfWeek(date, weekStartDay), value * 7, DAYS);
} else if (unit === DAYS) {
result = new Date(date.getFullYear(), date.getMonth(), date.getDate() + value);
} else if (unit === HOURS) {
result = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours() + value);
if (value > 0 && dateEquals(date, result)) {
result = addDuration(date, value + 1, unit, weekStartDay);
}
} else if (unit === MINUTES) {
result = new Date(date.getTime() + value * TIME_PER_MINUTE);
result.setSeconds(0);
}
}
return result;
}
function startOfWeek(date, weekStartDay) {
var day = date.getDay(), daysToSubtract = 0;
weekStartDay = weekStartDay || 0;
while (day !== weekStartDay) {
if (day === 0) {
day = 6;
} else {
day--;
}
daysToSubtract++;
}
return addTicks(date, -daysToSubtract * TIME_PER_DAY);
}
function floorDate(date, unit, weekStartDay) {
date = toDate(date);
return addDuration(date, 0, unit, weekStartDay);
}
function ceilDate(date, unit, weekStartDay) {
date = toDate(date);
if (date && floorDate(date, unit, weekStartDay).getTime() === date.getTime()) {
return date;
}
return addDuration(date, 1, unit, weekStartDay);
}
function dateDiff(a, b) {
var diff = a.getTime() - b, offsetDiff = a.getTimezoneOffset() - b.getTimezoneOffset();
return diff - offsetDiff * TIME_PER_MINUTE;
}
function addTicks(date, ticks) {
var tzOffsetBefore = date.getTimezoneOffset(), result = new Date(date.getTime() + ticks), tzOffsetDiff = result.getTimezoneOffset() - tzOffsetBefore;
return new Date(result.getTime() + tzOffsetDiff * TIME_PER_MINUTE);
}
function duration(a, b, unit) {
var diff;
if (unit === YEARS) {
diff = b.getFullYear() - a.getFullYear();
} else if (unit === MONTHS) {
diff = duration(a, b, YEARS) * 12 + b.getMonth() - a.getMonth();
} else if (unit === DAYS) {
diff = math.floor(dateDiff(b, a) / TIME_PER_DAY);
} else {
diff = math.floor((b - a) / TIME_PER_UNIT[unit]);
}
return diff;
}
function valueFieldsBySeriesType(type) {
var result = [ VALUE ];
if (inArray(type, [ CANDLESTICK, OHLC ])) {
result = [ "open", "high", "low", "close" ];
} else if (inArray(type, [ BULLET, VERTICAL_BULLET ])) {
result = [ "current", "target" ];
} else if (inArray(type, XY_CHARTS)) {
result = [ X, Y ];
if (type === BUBBLE) {
result.push("size");
}
}
return result;
}
function bindPoint(series, pointIx, pointFields) {
var pointData = series.data[pointIx], fieldData, fields = {}, srcValueFields, srcPointFields, valueFields = valueFieldsBySeriesType(series.type), value, result = {
value: pointData
};
if (defined(pointData)) {
if (isArray(pointData)) {
fieldData = pointData.slice(valueFields.length);
value = bindFromArray(pointData, valueFields);
fields = bindFromArray(fieldData, pointFields);
} else if (typeof pointData === "object") {
srcValueFields = mapSeriesFields(series, valueFields);
srcPointFields = mapSeriesFields(series, pointFields);
value = bindFromObject(pointData, valueFields, srcValueFields);
fields = bindFromObject(pointData, pointFields, srcPointFields);
}
} else {
value = bindFromObject({}, valueFields);
}
if (defined(value)) {
if (valueFields.length === 1) {
value = value[valueFields[0]];
}
result.value = value;
}
result.fields = fields;
return result;
}
function bindFromArray(array, fields) {
var value = {}, i, length;
if (fields) {
length = math.min(fields.length, array.length);
for (i = 0; i < length; i++) {
value[fields[i]] = array[i];
}
}
return value;
}
function bindFromObject(object, fields, srcFields) {
var value = {}, i, length, fieldName, srcFieldName;
if (fields) {
length = fields.length;
srcFields = srcFields || fields;
for (i = 0; i < length; i++) {
fieldName = fields[i];
srcFieldName = srcFields[i];
value[fieldName] = getField(srcFieldName, object);
}
}
return value;
}
function mapSeriesFields(series, fields) {
var i, length, fieldName, sourceFields, sourceFieldName;
if (fields) {
length = fields.length;
sourceFields = [];
for (i = 0; i < length; i++) {
fieldName = fields[i];
sourceFieldName = fieldName === VALUE ? "field" : fieldName + "Field";
sourceFields.push(series[sourceFieldName] || fieldName);
}
}
return sourceFields;
}
function singleItemOrArray(array) {
return array.length === 1 ? array[0] : array;
}
function clipValue(value, min, max) {
return math.max(math.min(value, max), min);
}
function axisGroupBox(axes) {
var length = axes.length, box, i, axisBox;
if (length > 0) {
for (i = 0; i < length; i++) {
axisBox = axes[i].box;
if (!box) {
box = axisBox.clone();
} else {
box.wrap(axisBox);
}
}
}
return box || Box2D();
}
function equalsIgnoreCase(a, b) {
if (a && b) {
return a.toLowerCase() === b.toLowerCase();
}
return a === b;
}
function dateEquals(a, b) {
if (a && b) {
return toTime(a) === toTime(b);
}
return a === b;
}
function lastValue(array) {
var i = array.length, value;
while (i--) {
value = array[i];
if (defined(value) && value !== null) {
return value;
}
}
}
function appendIfNotNull(array, element) {
if (element !== null) {
array.push(element);
}
}
function lteDateIndex(sortedDates, date) {
var low = 0, high = sortedDates.length - 1, i, currentDate;
while (low <= high) {
i = math.floor((low + high) / 2);
currentDate = sortedDates[i];
if (currentDate < date) {
low = i + 1;
continue;
}
if (currentDate > date) {
high = i - 1;
continue;
}
return i;
}
if (sortedDates[i] <= date) {
return i;
} else {
return i - 1;
}
}
function validNumbers(values) {
var valid = true, i, val, length = values.length;
for (i = 0; i < length; i++) {
val = values[i];
if (typeof val !== "number" || isNaN(val)) {
valid = false;
break;
}
}
return valid;
}
function axisRanges(axes) {
var i, axis, axisName, ranges = {};
for (i = 0; i < axes.length; i++) {
axis = axes[i];
axisName = axis.options.name;
if (axisName) {
ranges[axisName] = axis.range();
}
}
return ranges;
}
function mwDelta(e) {
var origEvent = e.originalEvent, delta = 0;
if (origEvent.wheelDelta) {
delta = -origEvent.wheelDelta / 120;
delta = delta > 0 ? math.ceil(delta) : math.floor(delta);
}
if (origEvent.detail) {
delta = round(origEvent.detail / 3);
}
return delta;
}
// Exports ================================================================
dataviz.ui.plugin(Chart);
deepExtend(dataviz, {
Aggregates: Aggregates,
AreaChart: AreaChart,
Bar: Bar,
BarAnimationDecorator: BarAnimationDecorator,
BarChart: BarChart,
BarLabel: BarLabel,
BubbleAnimationDecorator: BubbleAnimationDecorator,
BubbleChart: BubbleChart,
CandlestickChart: CandlestickChart,
Candlestick: Candlestick,
CategoricalPlotArea: CategoricalPlotArea,
CategoryAxis: CategoryAxis,
ClusterLayout: ClusterLayout,
Crosshair: Crosshair,
CrosshairTooltip: CrosshairTooltip,
DateCategoryAxis: DateCategoryAxis,
DateValueAxis: DateValueAxis,
DonutChart: DonutChart,
DonutPlotArea: DonutPlotArea,
DonutSegment: DonutSegment,
Highlight: Highlight,
SharedTooltip: SharedTooltip,
Legend: Legend,
LineChart: LineChart,
LinePoint: LinePoint,
Pane: Pane,
PieAnimation: PieAnimation,
PieAnimationDecorator: PieAnimationDecorator,
PieChart: PieChart,
PiePlotArea: PiePlotArea,
PieSegment: PieSegment,
ScatterChart: ScatterChart,
ScatterLineChart: ScatterLineChart,
Selection: Selection,
ShapeElement: ShapeElement,
StackLayout: StackLayout,
Tooltip: Tooltip,
OHLCChart: OHLCChart,
OHLCPoint: OHLCPoint,
XYPlotArea: XYPlotArea,
addDuration: addDuration,
axisGroupBox: axisGroupBox,
validNumbers: validNumbers,
bindPoint: bindPoint,
categoriesCount: categoriesCount,
ceilDate: ceilDate,
duration: duration,
floorDate: floorDate,
lteDateIndex: lteDateIndex,
sparseArrayLimits: sparseArrayLimits,
toDate: toDate,
toTime: toTime
});
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var math = Math, kendo = window.kendo, Widget = kendo.ui.Widget, deepExtend = kendo.deepExtend, dataviz = kendo.dataviz, Axis = dataviz.Axis, Box2D = dataviz.Box2D, ChartElement = dataviz.ChartElement, NumericAxis = dataviz.NumericAxis, Pin = dataviz.Pin, Ring = dataviz.Ring, RootElement = dataviz.RootElement, RotationAnimation = dataviz.RotationAnimation, BarIndicatorAnimatin = dataviz.BarIndicatorAnimatin, ArrowAnimation = dataviz.ArrowAnimation, append = dataviz.append, animationDecorator = dataviz.animationDecorator, autoMajorUnit = dataviz.autoMajorUnit, getElement = dataviz.getElement, getSpacing = dataviz.getSpacing, defined = dataviz.defined, rotatePoint = dataviz.rotatePoint, Point2D = dataviz.Point2D, round = dataviz.round, uniqueId = dataviz.uniqueId;
// Constants ==============================================================
var ANGULAR_SPEED = 150, ARROW = "arrow", ARROW_POINTER = "arrowPointer", BAR_INDICATOR = "barIndicator", BLACK = "#000", CAP_SIZE = .05, COORD_PRECISION = dataviz.COORD_PRECISION, MAX_VALUE = Number.MAX_VALUE, MIN_VALUE = -Number.MAX_VALUE, DEFAULT_HEIGHT = 200, DEFAULT_LINE_WIDTH = .5, DEFAULT_WIDTH = 200, DEFAULT_MIN_WIDTH = 60, DEFAULT_MIN_HEIGHT = 60, DEGREE = math.PI / 180, INSIDE = "inside", NEEDLE = "needle", OUTSIDE = "outside", RADIAL_POINTER = "radialPointer", ROTATION_ORIGIN = 90;
// Gauge ==================================================================
var Pointer = ChartElement.extend({
init: function(scale, options) {
var pointer = this, scaleOptions = scale.options;
ChartElement.fn.init.call(pointer, options);
options = pointer.options;
if (!options.id) {
options.id = uniqueId();
}
options.fill = options.color;
pointer.scale = scale;
if (defined(options.value)) {
options.value = math.min(math.max(options.value, scaleOptions.min), scaleOptions.max);
} else {
options.value = scaleOptions.min;
}
},
options: {
color: BLACK
},
value: function(newValue) {
var pointer = this, options = pointer.options, value = options.value, scaleOptions = pointer.scale.options;
if (arguments.length === 0) {
return value;
}
options._oldValue = options.value;
options.value = math.min(math.max(newValue, scaleOptions.min), scaleOptions.max);
pointer.repaint();
}
});
var RadialPointer = Pointer.extend({
options: {
shape: NEEDLE,
cap: {
size: CAP_SIZE
},
arrow: {
width: 16,
height: 14
},
animation: {
type: RADIAL_POINTER,
speed: ANGULAR_SPEED
}
},
reflow: function() {
var pointer = this, options = pointer.options, scale = pointer.scale, ring = scale.ring, c = ring.c, capSize = ring.r * options.cap.size;
pointer.box = new Box2D(c.x - capSize, c.y - capSize, c.x + capSize, c.y + capSize);
},
repaint: function() {
var pointer = this, scale = pointer.scale, options = pointer.options, needle = pointer.elements[0], animationOptions = options.animation, minSlotAngle = scale.slotAngle(scale.options.min), oldAngle = scale.slotAngle(options._oldValue) - minSlotAngle, animation = needle._animation;
needle.options.rotation[0] = scale.slotAngle(options.value) - minSlotAngle;
if (animation) {
animation.abort();
}
if (animationOptions.transitions === false) {
needle.refresh(getElement(options.id));
} else {
animation = needle._animation = new RotationAnimation(needle, deepExtend(animationOptions, {
startAngle: oldAngle,
reverse: scale.options.reverse
}));
animation.setup();
animation.play();
}
},
_renderNeedle: function(view, box, center, pointRotation) {
var pointer = this, options = pointer.options, scale = pointer.scale, capSize = scale.ring.r * options.cap.size;
return [ view.createPolyline([ rotatePoint((box.x1 + box.x2) / 2, box.y1 + scale.options.minorTicks.size, center.x, center.y, pointRotation), rotatePoint(center.x - capSize / 2, center.y, center.x, center.y, pointRotation), rotatePoint(center.x + capSize / 2, center.y, center.x, center.y, pointRotation) ], true, options), view.createCircle(center, capSize, {
fill: options.cap.color || options.color
}) ];
},
_renderArrow: function(view, box, center, pointRotation) {
var pointer = this, options = pointer.options, scale = pointer.scale, ring = scale.ring.clone(), trackWidth = 5, arrowOptions = options.arrow, height = arrowOptions.height;
ring.ir = ring.r - trackWidth;
return [ view.createPin(new Pin({
origin: rotatePoint((box.x1 + box.x2) / 2, box.y1 + height, center.x, center.y, pointRotation),
height: arrowOptions.height,
radius: trackWidth,
rotation: pointRotation,
arcAngle: 180
}), options), view.createRing(ring, {
fill: options.color
}) ];
},
renderPointer: function(view) {
var pointer = this, scale = pointer.scale, ring = scale.ring, c = ring.c, r = ring.r, shape, options = pointer.options, box = new Box2D(c.x - r, c.y - r, c.x + r, c.y + r), center = box.center(), minAngle = scale.slotAngle(scale.options.min), pointRotation = ROTATION_ORIGIN - minAngle;
if (options.animation !== false) {
deepExtend(options.animation, {
startAngle: 0,
center: center,
reverse: scale.options.reverse
});
}
deepExtend(options, {
rotation: [ scale.slotAngle(options.value) - minAngle, center.x, center.y ]
});
if (options.shape == ARROW) {
shape = pointer._renderArrow(view, box, center, pointRotation);
} else {
shape = pointer._renderNeedle(view, box, center, pointRotation);
}
return shape;
},
getViewElements: function(view) {
var pointer = this, elements = pointer.renderPointer(view);
pointer.elements = elements;
return elements;
}
});
var RadialScale = NumericAxis.extend({
init: function(options) {
var scale = this;
scale.options = deepExtend({}, scale.options, options);
scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);
Axis.fn.init.call(scale, scale.options);
scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;
},
options: {
min: 0,
max: 100,
majorTicks: {
size: 15,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
minorTicks: {
size: 10,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
startAngle: -30,
endAngle: 210,
labels: {
position: INSIDE,
padding: 2
}
},
reflow: function(box) {
var scale = this, options = scale.options, center = box.center(), radius = math.min(box.height(), box.width()) / 2, ring = scale.ring || new dataviz.Ring(center, radius - options.majorTicks.size, radius, options.startAngle, options.endAngle - options.startAngle);
scale.ring = ring;
scale.box = ring.getBBox();
scale.arrangeLabels();
},
slotAngle: function(value) {
var options = this.options, startAngle = options.startAngle, reverse = options.reverse, angle = options.endAngle - startAngle, min = options.min, max = options.max, result;
if (reverse) {
result = options.endAngle - (value - min) / (max - min) * angle;
} else {
result = (value - min) / (max - min) * angle + startAngle;
}
return result;
},
renderTicks: function(view) {
var scale = this, ticks = [], majorTickRing = scale.ring, minorTickRing = majorTickRing.clone(), options = scale.options, minorTickSize = options.minorTicks.size;
function renderTickRing(ring, unit, tickOptions, visible, skipUnit) {
var tickAngles = scale.tickAngles(ring, unit), i, innerPoint, outerPoint, skip = skipUnit / unit, count = tickAngles.length;
if (visible) {
for (i = 0; i < count; i++) {
if (i % skip === 0) {
continue;
}
outerPoint = ring.point(tickAngles[i]);
innerPoint = ring.point(tickAngles[i], true);
ticks.push(view.createLine(innerPoint.x, innerPoint.y, outerPoint.x, outerPoint.y, {
align: false,
stroke: tickOptions.color,
strokeWidth: tickOptions.width
}));
}
}
}
renderTickRing(majorTickRing, options.majorUnit, options.majorTicks, options.majorTicks.visible);
if (options.labels.position == INSIDE) {
minorTickRing.radius(minorTickRing.r - minorTickSize, true);
} else {
minorTickRing.radius(minorTickRing.ir + minorTickSize);
}
renderTickRing(minorTickRing, options.minorUnit, options.minorTicks, options.minorTicks.visible, options.majorUnit);
return ticks;
},
arrangeLabels: function() {
var scale = this, options = scale.options, ring = scale.ring.clone(), tickAngels = scale.tickAngles(ring, options.majorUnit), labels = scale.labels, count = labels.length, labelsOptions = options.labels, padding = labelsOptions.padding, rangeDistance = ring.r * .05, rangeSize = options.rangeSize = options.rangeSize || ring.r * .1, ranges = options.ranges || [], halfWidth, halfHeight, labelAngle, angle, label, lp, i, cx, cy, isInside;
if (typeof scale.options.rangeDistance != "undefined") {
rangeDistance = scale.options.rangeDistance;
} else {
scale.options.rangeDistance = rangeDistance;
}
if (labelsOptions.position === INSIDE && ranges.length) {
ring.r -= rangeSize + rangeDistance;
ring.ir -= rangeSize + rangeDistance;
}
for (i = 0; i < count; i++) {
label = labels[i];
halfWidth = label.box.width() / 2;
halfHeight = label.box.height() / 2;
angle = tickAngels[i];
labelAngle = angle * DEGREE;
isInside = labelsOptions.position === INSIDE;
lp = ring.point(angle, isInside);
cx = lp.x + math.cos(labelAngle) * (halfWidth + padding) * (isInside ? 1 : -1);
cy = lp.y + math.sin(labelAngle) * (halfHeight + padding) * (isInside ? 1 : -1);
label.reflow(new Box2D(cx - halfWidth, cy - halfHeight, cx + halfWidth, cy + halfHeight));
scale.box.wrap(label.box);
}
},
tickAngles: function(ring, stepValue) {
var scale = this, options = scale.options, reverse = options.reverse, range = options.max - options.min, angle = ring.angle, pos = ring.startAngle, tickCount = range / stepValue, step = angle / tickCount, positions = [], i;
if (reverse) {
pos += angle;
step = -step;
}
for (i = 0; i < tickCount; i++) {
positions.push(round(pos, COORD_PRECISION));
pos += step;
}
if (round(pos) <= options.endAngle) {
positions.push(pos);
}
return positions;
},
renderRanges: function(view) {
var scale = this, result = [], from, to, segments = scale.rangeSegments(), segmentsCount = segments.length, reverse = scale.options.reverse, segment, ringRadius, i;
if (segmentsCount) {
ringRadius = scale.getRadius();
for (i = 0; i < segmentsCount; i++) {
segment = segments[i];
from = scale.slotAngle(segment[reverse ? "to" : "from"]);
to = scale.slotAngle(segment[!reverse ? "to" : "from"]);
if (to - from !== 0) {
result.push(view.createRing(new Ring(scale.ring.c, ringRadius.inner, ringRadius.outer, from, to - from), {
fill: segment.color,
fillOpacity: segment.opacity,
zIndex: -1
}));
}
}
}
return result;
},
rangeSegments: function() {
var gauge = this, options = gauge.options, ranges = options.ranges || [], count = ranges.length, range, segmentsCount, defaultColor = options.rangePlaceholderColor, segments = [], segment, min = options.min, max = options.max, i, j;
function rangeSegment(from, to, color) {
return {
from: from,
to: to,
color: color
};
}
if (count) {
segments.push(rangeSegment(min, max, defaultColor));
for (i = 0; i < count; i++) {
range = getRange(ranges[i], min, max);
segmentsCount = segments.length;
for (j = 0; j < segmentsCount; j++) {
segment = segments[j];
if (segment.from <= range.from && range.from <= segment.to) {
segments.push(rangeSegment(range.from, range.to, range.color));
if (segment.from <= range.to && range.to <= segment.to) {
segments.push(rangeSegment(range.to, segment.to, defaultColor));
}
segment.to = range.from;
break;
}
}
}
}
return segments;
},
getRadius: function() {
var scale = this, options = scale.options, rangeSize = options.rangeSize, rangeDistance = options.rangeDistance, ring = scale.ring, ir, r;
if (options.labels.position === OUTSIDE) {
r = ring.ir - rangeDistance;
ir = r - rangeSize;
} else {
r = ring.r;
ir = r - rangeSize;
// move the ticks with a range distance and a range size
ring.r -= rangeSize + rangeDistance;
ring.ir -= rangeSize + rangeDistance;
}
return {
inner: ir,
outer: r
};
},
getViewElements: function(view) {
var scale = this, childElements = ChartElement.fn.getViewElements.call(scale, view);
append(childElements, scale.renderRanges(view));
append(childElements, scale.renderTicks(view));
return childElements;
}
});
var RadialGaugePlotArea = ChartElement.extend({
init: function(options) {
ChartElement.fn.init.call(this, options);
this.render();
},
options: {
margin: {},
background: "",
border: {
color: BLACK,
width: 0
},
minorTicks: {
align: INSIDE
}
},
reflow: function(box) {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer, plotBox;
scale.reflow(box);
plotBox = scale.box.clone();
pointer.scale = scale;
pointer.reflow();
plotBox.wrap(pointer.box);
plotArea.box = plotBox;
plotArea.fitScale(box);
plotArea.alignScale(box);
},
alignScale: function(box) {
var plotArea = this, plotBoxCenter = plotArea.box.center(), boxCenter = box.center(), paddingX = plotBoxCenter.x - boxCenter.x, paddingY = plotBoxCenter.y - boxCenter.y, scale = plotArea.scale, pointer = plotArea.pointer;
scale.ring.c.x -= paddingX;
scale.ring.c.y -= paddingY;
scale.reflow(box);
pointer.reflow();
plotArea.box = scale.box.clone().wrap(pointer.box);
},
fitScale: function(box) {
var plotArea = this, scale = plotArea.scale, ring = scale.ring, plotAreaBox = plotArea.box, step = math.abs(plotArea.getDiff(plotAreaBox, box)), min = round(step, COORD_PRECISION), max = round(-step, COORD_PRECISION), minDiff, midDiff, maxDiff, mid, i = 0;
while (i < 100) {
i++;
if (min != mid) {
minDiff = plotArea.getPlotBox(min, box, ring);
if (0 <= minDiff && minDiff <= 2) {
break;
}
}
if (max != mid) {
maxDiff = plotArea.getPlotBox(max, box, ring);
if (0 <= maxDiff && maxDiff <= 2) {
break;
}
}
if (minDiff > 0 && maxDiff > 0) {
mid = min * 2;
} else if (minDiff < 0 && maxDiff < 0) {
mid = max * 2;
} else {
mid = round((min + max) / 2 || 1, COORD_PRECISION);
}
midDiff = plotArea.getPlotBox(mid, box, ring);
if (0 <= midDiff && midDiff <= 2) {
break;
}
if (midDiff > 0) {
max = mid;
maxDiff = midDiff;
} else {
min = mid;
minDiff = midDiff;
}
}
},
getPlotBox: function(step, box, ring) {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer;
ring = ring.clone();
ring.r += step;
ring.ir += step;
scale.ring = ring;
scale.reflow(box);
pointer.scale = scale;
pointer.reflow();
plotArea.box = scale.box.clone().wrap(pointer.box);
return plotArea.getDiff(plotArea.box, box);
},
getDiff: function(plotBox, box) {
return math.min(box.width() - plotBox.width(), box.height() - plotBox.height());
},
render: function() {
var plotArea = this, options = plotArea.options, scale;
scale = plotArea.scale = new RadialScale(options.scale);
plotArea.append(plotArea.scale);
plotArea.pointer = new RadialPointer(scale, deepExtend({}, options.pointer, {
animation: {
transitions: options.transitions
}
}));
plotArea.append(plotArea.pointer);
}
});
var LinearScale = NumericAxis.extend({
init: function(options) {
var scale = this;
scale.options = deepExtend({}, scale.options, options);
scale.options = deepExtend({}, scale.options, {
labels: {
mirror: scale.options.mirror
}
});
scale.options.majorUnit = scale.options.majorUnit || autoMajorUnit(scale.options.min, scale.options.max);
Axis.fn.init.call(scale, scale.options);
scale.options.minorUnit = scale.options.minorUnit || scale.options.majorUnit / 10;
},
options: {
min: 0,
max: 50,
majorTicks: {
size: 15,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
minorTicks: {
size: 10,
align: INSIDE,
color: BLACK,
width: DEFAULT_LINE_WIDTH,
visible: true
},
line: {
width: DEFAULT_LINE_WIDTH
},
labels: {
position: INSIDE,
padding: 2
},
mirror: false,
_alignLines: false
},
renderRanges: function(view) {
var scale = this, options = scale.options, min = options.min, max = options.max, ranges = options.ranges || [], vertical = options.vertical, mirror = options.labels.mirror, result = [], count = ranges.length, range, slotX, slotY, i, rangeSize = options.rangeSize || options.minorTicks.size / 2, slot;
if (count) {
for (i = 0; i < count; i++) {
range = getRange(ranges[i], min, max);
slot = scale.getSlot(range.from, range.to);
slotX = vertical ? scale.lineBox() : slot;
slotY = vertical ? slot : scale.lineBox();
if (vertical) {
slotX.x1 -= rangeSize * (mirror ? -1 : 1);
} else {
slotY.y2 += rangeSize * (mirror ? -1 : 1);
}
result.push(view.createRect(new Box2D(slotX.x1, slotY.y1, slotX.x2, slotY.y2), {
fill: range.color,
fillOpacity: range.opacity
}));
}
}
return result;
},
getViewElements: function(view) {
var scale = this, elements = NumericAxis.fn.getViewElements.call(scale, view);
append(elements, scale.renderRanges(view));
return elements;
}
});
var LinearPointer = Pointer.extend({
init: function(scale, options) {
var pointer = this;
Pointer.fn.init.call(pointer, scale, options);
pointer.options = deepExtend({
size: pointer.pointerSize(),
track: {
visible: defined(options.track)
}
}, pointer.options);
},
options: {
shape: BAR_INDICATOR,
track: {
border: {
width: 1
}
},
color: BLACK,
border: {
width: 1
},
opacity: 1,
margin: getSpacing(3),
animation: {
type: BAR_INDICATOR
},
visible: true
},
repaint: function() {
var pointer = this, scale = pointer.scale, options = pointer.options, element = pointer.element, animation = element._animation;
if (animation) {
animation.abort();
}
if (options.animation.transitions === false) {
pointer.getViewElements(pointer._view);
element.points = pointer.element.points;
element.refresh(getElement(options.id));
} else {
options.animation = deepExtend({}, options.animation, {
endPosition: scale.getSlot(scale.options.min, options.value),
reverse: scale.options.reverse
});
if (options.shape === ARROW) {
animation = element._animation = new ArrowAnimation(element, options.animation);
} else {
animation = element._animation = new BarIndicatorAnimatin(element, options.animation);
}
animation.setup();
animation.play();
}
},
reflow: function() {
var pointer = this, options = pointer.options, scale = pointer.scale, scaleLine = scale.lineBox(), trackSize = options.track.size || options.size, pointerHalfSize = options.size / 2, mirror = scale.options.mirror, margin = getSpacing(options.margin), vertical = scale.options.vertical, space = vertical ? margin[mirror ? "left" : "right"] : margin[mirror ? "bottom" : "top"], pointerBox, pointerRangeBox, trackBox;
space = mirror ? -space : space;
if (vertical) {
trackBox = new Box2D(scaleLine.x1 + space, scaleLine.y1, scaleLine.x1 + space, scaleLine.y2);
if (mirror) {
trackBox.x1 -= trackSize;
} else {
trackBox.x2 += trackSize;
}
if (options.shape !== BAR_INDICATOR) {
pointerRangeBox = new Box2D(scaleLine.x2 + space, scaleLine.y1 - pointerHalfSize, scaleLine.x2 + space, scaleLine.y2 + pointerHalfSize);
pointerBox = pointerRangeBox;
}
} else {
trackBox = new Box2D(scaleLine.x1, scaleLine.y1 - space, scaleLine.x2, scaleLine.y1 - space);
if (mirror) {
trackBox.y2 += trackSize;
} else {
trackBox.y1 -= trackSize;
}
if (options.shape !== BAR_INDICATOR) {
pointerRangeBox = new Box2D(scaleLine.x1 - pointerHalfSize, scaleLine.y1 - space, scaleLine.x2 + pointerHalfSize, scaleLine.y1 - space);
pointerBox = pointerRangeBox;
}
}
pointer.trackBox = trackBox;
pointer.pointerRangeBox = pointerRangeBox;
pointer.box = pointerBox || trackBox.clone().pad(options.border.width);
},
renderPointer: function(view) {
var pointer = this, scale = pointer.scale, options = pointer.options, border = defined(options.border) ? {
stroke: options.border.width ? options.border.color || options.color : "",
strokeWidth: options.border.width,
dashType: options.border.dashType
} : {}, element, elementOptions = deepExtend({
fill: options.color,
fillOpacity: options.opacity,
animation: deepExtend(options.animation, {
startPosition: scale.getSlot(scale.options.min, options.value),
size: options.size,
vertical: scale.options.vertical,
reverse: scale.options.reverse
}),
id: options.id,
zIndex: 2,
align: false
}, border), shape = pointer.pointerShape(options.value);
if (options.shape === ARROW) {
elementOptions.animation.type = ARROW_POINTER;
element = view.createPolyline(shape, true, elementOptions);
} else {
element = view.createRect(shape, elementOptions);
}
return element;
},
pointerShape: function(value) {
var pointer = this, options = pointer.options, scale = pointer.scale, slot = scale.getSlot(value, scale.options.min), size = options.size, pointerRangeBox = pointer.pointerRangeBox, vertical = scale.options.vertical, halfSize = size / 2, shape, sign = scale.options.mirror ? -1 : 1, reverse = scale.options.reverse, pos, trackBox;
if (options.shape == ARROW) {
if (vertical) {
pos = reverse ? "y2" : "y1";
shape = [ new Point2D(pointerRangeBox.x1, slot[pos] - halfSize), new Point2D(pointerRangeBox.x1 - sign * size, slot[pos]), new Point2D(pointerRangeBox.x1, slot[pos] + halfSize) ];
} else {
pos = reverse ? "x1" : "x2";
shape = [ new Point2D(slot[pos] - halfSize, pointerRangeBox.y2), new Point2D(slot[pos], pointerRangeBox.y2 + sign * size), new Point2D(slot[pos] + halfSize, pointerRangeBox.y2) ];
}
} else {
trackBox = pointer.trackBox;
if (vertical) {
shape = new Box2D(trackBox.x1, slot.y1, trackBox.x1 + size, slot.y2);
} else {
shape = new Box2D(slot.x1, trackBox.y1, slot.x2, trackBox.y1 + size);
}
}
return shape;
},
pointerSize: function() {
var pointer = this, options = pointer.options, scale = pointer.scale, tickSize = scale.options.majorTicks.size, size;
if (options.shape === ARROW) {
size = tickSize * .6;
} else {
size = tickSize * .3;
}
return round(size);
},
renderTrack: function(view) {
var pointer = this, options = pointer.options, trackOptions = options.track, border = trackOptions.border || {}, trackBox = pointer.trackBox.clone().pad(border.width || 0);
return view.createRect(trackBox, {
fill: trackOptions.color,
fillOpacity: trackOptions.opacity,
stroke: border.width ? border.color || trackOptions.color : "",
strokeWidth: border.width,
dashType: border.dashType,
align: false
});
},
getViewElements: function(view) {
var pointer = this, options = pointer.options, elements = [];
pointer.element = pointer.renderPointer(view);
elements.push(pointer.element);
if (options.track.visible && (options.shape === BAR_INDICATOR || options.shape === "")) {
elements.push(pointer.renderTrack(view));
}
pointer._view = view;
append(elements, Pointer.fn.getViewElements.call(pointer, view));
return elements;
}
});
var LinearGaugePlotArea = ChartElement.extend({
init: function(options) {
ChartElement.fn.init.call(this, options);
this.render();
},
options: {
plotArea: {
margin: {},
background: "",
border: {
color: BLACK,
width: 0
}
},
pointer: {},
scale: {}
},
reflow: function(box) {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer;
scale.reflow(box);
pointer.reflow(box);
plotArea.box = plotArea.getBox(box);
plotArea.alignElements();
plotArea.shrinkElements();
},
shrinkElements: function() {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer, scaleBox = scale.box.clone(), pointerBox = pointer.box, pos = scale.options.vertical ? "y" : "x";
scaleBox[pos + 1] += math.max(scaleBox[pos + 1] - pointerBox[pos + 1], 0);
scaleBox[pos + 2] -= math.max(pointerBox[pos + 2] - scaleBox[pos + 2], 0);
scale.reflow(scaleBox);
pointer.reflow(plotArea.box);
},
getBox: function(box) {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer, boxCenter = box.center(), plotAreaBox = pointer.box.clone().wrap(scale.box), size;
if (scale.options.vertical) {
size = plotAreaBox.width() / 2;
plotAreaBox = new Box2D(boxCenter.x - size, box.y1, boxCenter.x + size, box.y2);
} else {
size = plotAreaBox.height() / 2;
plotAreaBox = new Box2D(box.x1, boxCenter.y - size, box.x2, boxCenter.y + size);
}
return plotAreaBox;
},
alignElements: function() {
var plotArea = this, scale = plotArea.scale, pointer = plotArea.pointer, scaleBox = scale.box, box = pointer.box.clone().wrap(scale.box), plotAreaBox = plotArea.box, diff;
if (scale.options.vertical) {
diff = plotAreaBox.center().x - box.center().x;
scale.reflow(new Box2D(scaleBox.x1 + diff, plotAreaBox.y1, scaleBox.x2 + diff, plotAreaBox.y2));
} else {
diff = plotAreaBox.center().y - box.center().y;
scale.reflow(new Box2D(plotAreaBox.x1, scaleBox.y1 + diff, plotAreaBox.x2, scaleBox.y2 + diff));
}
pointer.reflow(plotArea.box);
},
render: function() {
var plotArea = this, options = plotArea.options, scale;
scale = plotArea.scale = new LinearScale(options.scale);
plotArea.append(plotArea.scale);
plotArea.pointer = new LinearPointer(scale, deepExtend({}, options.pointer, {
animation: {
transitions: options.transitions
}
}));
plotArea.append(plotArea.pointer);
},
getViewElements: function(view) {
var plotArea = this, options = plotArea.options.plotArea, childElements = ChartElement.fn.getViewElements.call(plotArea, view), border = options.border || {}, elements = [ view.createRect(plotArea.box, {
fill: options.background,
stroke: border.width ? border.color : "",
strokeWidth: border.width,
dashType: border.dashType
}) ];
append(elements, childElements);
return elements;
}
});
var Gauge = Widget.extend({
init: function(element, userOptions) {
var gauge = this, options, themeOptions, themeName, themes = dataviz.ui.themes || {}, theme;
Widget.fn.init.call(gauge, element);
gauge.wrapper = gauge.element;
gauge._originalOptions = deepExtend({}, userOptions);
options = deepExtend({}, gauge.options, userOptions);
themeName = options.theme;
theme = themes[themeName] || themes[themeName.toLowerCase()];
themeOptions = themeName && theme ? theme.gauge : {};
gauge.options = deepExtend({}, themeOptions, options);
gauge.element.addClass("k-gauge");
gauge.redraw();
},
options: {
plotArea: {},
theme: "default",
pointer: {},
scale: {},
gaugeArea: {}
},
value: function(value) {
if (arguments.length === 0) {
return this._pointers[0].value();
}
this._pointers[0].value(value);
},
redraw: function() {
var gauge = this, element = gauge.element, model = gauge._model = gauge._getModel(), viewType = dataviz.ui.defaultView(), view;
gauge._plotArea = model._plotArea;
if (viewType) {
view = gauge._view = viewType.fromModel(model);
element.css("position", "relative");
gauge._viewElement = view.renderTo(element[0]);
}
},
svg: function() {
var model = this._getModel(), view = dataviz.SVGView.fromModel(model);
return view.render();
},
_createModel: function() {
var gauge = this, options = gauge.options, size = gauge._getSize();
return new RootElement(deepExtend({
width: size.width,
height: size.height,
transitions: options.transitions
}, options.gaugeArea));
},
_getSize: function() {
var gauge = this, element = gauge.element, width = element.width(), height = element.height();
if (!width) {
width = DEFAULT_WIDTH;
}
if (!height) {
height = DEFAULT_HEIGHT;
}
return {
width: width,
height: height
};
}
});
var RadialGauge = Gauge.extend({
init: function(element, options) {
var radialGauge = this;
Gauge.fn.init.call(radialGauge, element, options);
kendo.notify(radialGauge, dataviz.ui);
},
options: {
name: "RadialGauge",
transitions: true,
gaugeArea: {
background: ""
}
},
_getModel: function() {
var gauge = this, options = gauge.options, model = gauge._createModel(), plotArea;
plotArea = model._plotArea = new RadialGaugePlotArea(options);
gauge._pointers = [ plotArea.pointer ];
model.append(plotArea);
model.reflow();
return model;
}
});
var LinearGauge = Gauge.extend({
init: function(element, options) {
var linearGauge = this;
Gauge.fn.init.call(linearGauge, element, options);
kendo.notify(linearGauge, dataviz.ui);
},
options: {
name: "LinearGauge",
transitions: true,
gaugeArea: {
background: ""
},
scale: {
vertical: true
}
},
_getModel: function() {
var gauge = this, options = gauge.options, model = gauge._createModel(), plotArea;
plotArea = model._plotArea = new LinearGaugePlotArea(options);
gauge._pointers = [ plotArea.pointer ];
model.append(plotArea);
model.reflow();
return model;
},
_getSize: function() {
var gauge = this, element = gauge.element, width = element.width(), height = element.height(), vertical = gauge.options.scale.vertical;
if (!width) {
width = vertical ? DEFAULT_MIN_WIDTH : DEFAULT_WIDTH;
}
if (!height) {
height = vertical ? DEFAULT_HEIGHT : DEFAULT_MIN_HEIGHT;
}
return {
width: width,
height: height
};
}
});
function getRange(range, min, max) {
var from = defined(range.from) ? range.from : MIN_VALUE, to = defined(range.to) ? range.to : MAX_VALUE;
range.from = math.max(math.min(to, from), min);
range.to = math.min(math.max(to, from), max);
return range;
}
var RadialPointerAnimationDecorator = animationDecorator(RADIAL_POINTER, RotationAnimation);
var ArrowPointerAnimationDecorator = animationDecorator(ARROW_POINTER, ArrowAnimation);
var BarIndicatorAnimationDecorator = animationDecorator(BAR_INDICATOR, BarIndicatorAnimatin);
// Exports ================================================================
dataviz.ui.plugin(RadialGauge);
dataviz.ui.plugin(LinearGauge);
deepExtend(dataviz, {
Gauge: Gauge,
RadialGaugePlotArea: RadialGaugePlotArea,
LinearGaugePlotArea: LinearGaugePlotArea,
RadialPointer: RadialPointer,
LinearPointer: LinearPointer,
LinearScale: LinearScale,
RadialScale: RadialScale,
RadialPointerAnimationDecorator: RadialPointerAnimationDecorator,
ArrowPointerAnimationDecorator: ArrowPointerAnimationDecorator,
BarIndicatorAnimationDecorator: BarIndicatorAnimationDecorator
});
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ================================================================
var kendo = window.kendo, Class = kendo.Class, Observable = kendo.Observable, deepExtend = kendo.deepExtend, math = Math, proxy = $.proxy, dataviz = kendo.dataviz, template = kendo.template, defined = dataviz.defined, Chart = dataviz.ui.Chart, Selection = dataviz.Selection, addDuration = dataviz.addDuration, duration = dataviz.duration, last = dataviz.last, renderTemplate = dataviz.renderTemplate, toDate = dataviz.toDate, toTime = dataviz.toTime;
// Constants =============================================================
var AUTO_CATEGORY_WIDTH = 28, CHANGE = "change", CSS_PREFIX = "k-", DRAG = "drag", DRAG_END = "dragEnd", NAVIGATOR_PANE = "_navigator", NAVIGATOR_AXIS = NAVIGATOR_PANE, ZOOM_ACCELERATION = 3, ZOOM = "zoom", ZOOM_END = "zoomEnd";
// Stock chart ===========================================================
var StockChart = Chart.extend({
init: function(element, userOptions) {
$(element).addClass(CSS_PREFIX + "chart");
Chart.fn.init.call(this, element, userOptions);
},
_applyDefaults: function(options, themeOptions) {
var chart = this, width = chart.element.width() || dataviz.DEFAULT_WIDTH;
var stockDefaults = {
axisDefaults: {
categoryAxis: {
name: "default",
field: options.dateField,
majorGridLines: {
visible: false
},
labels: {
step: 2
},
majorTicks: {
visible: false
},
maxDateGroups: math.floor(width / AUTO_CATEGORY_WIDTH)
}
}
};
if (themeOptions) {
themeOptions = deepExtend({}, themeOptions, stockDefaults);
}
if (!chart._navigator) {
Navigator.setup(options, themeOptions);
}
Chart.fn._applyDefaults.call(chart, options, themeOptions);
},
_initDataSource: function(userOptions) {
var options = userOptions || {}, dataSource = options.dataSource, hasServerFiltering = dataSource && dataSource.serverFiltering, mainAxis = [].concat(options.categoryAxis)[0], naviOptions = options.navigator || {}, select = naviOptions.select, hasSelect = select && select.from && select.to, filter, dummyAxis;
if (hasServerFiltering && hasSelect) {
filter = [].concat(dataSource.filter || []);
dummyAxis = new dataviz.DateCategoryAxis(deepExtend({
baseUnit: "fit"
}, mainAxis, {
categories: [ select.from, select.to ]
}));
dataSource.filter = Navigator.buildFilter(dummyAxis.options.min, select.to).concat(filter);
}
Chart.fn._initDataSource.call(this, userOptions);
},
options: {
name: "StockChart",
dateField: "date",
axisDefaults: {
categoryAxis: {
type: "date",
baseUnit: "fit",
justified: true
},
valueAxis: {
narrowRange: true,
labels: {
format: "C"
}
}
},
navigator: {
select: {},
seriesDefaults: {
markers: {
visible: false
},
tooltip: {
visible: true,
template: "#= kendo.toString(category, 'd') #"
},
line: {
width: 2
}
},
hint: {},
visible: true
},
tooltip: {
visible: true
},
legend: {
visible: false
}
},
_redraw: function() {
var chart = this, navigator = chart._navigator;
if (navigator && navigator.dataSource) {
navigator.redrawSlaves();
} else {
if (!navigator) {
navigator = chart._navigator = new Navigator(chart);
}
navigator.filterAxes();
Chart.fn._redraw.call(chart);
navigator.redraw();
}
},
_onDataChanged: function() {
var chart = this;
Chart.fn._onDataChanged.call(chart);
chart._dataBound = true;
},
_trackSharedTooltip: function(coords) {
var chart = this, plotArea = chart._plotArea, pane = plotArea.paneByPoint(coords);
if (pane && pane.options.name === NAVIGATOR_PANE) {
chart._unsetActivePoint();
} else {
Chart.fn._trackSharedTooltip.call(chart, coords);
}
},
destroy: function() {
var chart = this;
chart._navigator.destroy();
Chart.fn.destroy.call(chart);
}
});
var Navigator = Observable.extend({
init: function(chart) {
var navi = this;
navi.chart = chart;
navi.options = deepExtend({}, navi.options, chart.options.navigator);
navi._initDataSource();
if (!defined(navi.options.hint.visible)) {
navi.options.hint.visible = navi.options.visible;
}
chart.bind(DRAG, proxy(navi._drag, navi));
chart.bind(DRAG_END, proxy(navi._dragEnd, navi));
chart.bind(ZOOM, proxy(navi._zoom, navi));
chart.bind(ZOOM_END, proxy(navi._zoomEnd, navi));
},
options: {},
_initDataSource: function() {
var navi = this, options = navi.options, autoBind = options.autoBind, dsOptions = options.dataSource;
if (!defined(autoBind)) {
autoBind = navi.chart.options.autoBind;
}
navi._dataChangedHandler = proxy(navi._onDataChanged, navi);
if (dsOptions) {
navi.dataSource = kendo.data.DataSource.create(dsOptions).bind(CHANGE, navi._dataChangedHandler);
if (autoBind) {
navi.dataSource.fetch();
}
}
},
_onDataChanged: function() {
var navi = this, chart = navi.chart, series = chart.options.series, seriesIx, seriesLength = series.length, categoryAxes = chart.options.categoryAxis, axisIx, axesLength = categoryAxes.length, data = navi.dataSource.view(), currentSeries, currentAxis;
for (seriesIx = 0; seriesIx < seriesLength; seriesIx++) {
currentSeries = series[seriesIx];
if (currentSeries.axis == NAVIGATOR_AXIS && chart.isBindable(currentSeries)) {
currentSeries.data = data;
}
}
for (axisIx = 0; axisIx < axesLength; axisIx++) {
currentAxis = categoryAxes[axisIx];
if (currentAxis.pane == NAVIGATOR_PANE) {
chart._bindCategoryAxis(currentAxis, data);
}
}
if (chart._model) {
navi.redraw();
navi.filterAxes();
if (!chart.options.dataSource || chart.options.dataSource && chart._dataBound) {
navi.redrawSlaves();
}
}
},
destroy: function() {
var navi = this, dataSource = navi.dataSource;
if (dataSource) {
dataSource.unbind(CHANGE, navi._dataChangeHandler);
}
if (navi.selection) {
navi.selection.destroy();
}
},
redraw: function() {
this._redrawSelf();
var navi = this, chart = navi.chart, options = navi.options, axis = navi.mainAxis(), axisClone = clone(axis), groups = axis.options.categories, select = navi.options.select || {}, selection = navi.selection, min = groups[0], max = last(groups), from = select.from || min, to = select.to || max;
if (groups.length > 0) {
if (selection) {
selection.destroy();
selection.wrapper.remove();
}
// "Freeze" the selection axis position until the next redraw
axisClone.box = axis.box;
// TODO: Move selection initialization to PlotArea.redraw
selection = navi.selection = new Selection(chart, axisClone, {
min: min,
max: max,
from: from,
to: to,
selectStart: $.proxy(navi._selectStart, navi),
select: $.proxy(navi._select, navi),
selectEnd: $.proxy(navi._selectEnd, navi),
mousewheel: {
zoom: "left"
}
});
if (options.hint.visible) {
navi.hint = new NavigatorHint(chart.element, {
min: groups[0],
max: last(groups),
template: options.hint.template,
format: options.hint.format
});
}
}
},
_redrawSelf: function(silent) {
var plotArea = this.chart._plotArea;
if (plotArea) {
plotArea.redraw(last(plotArea.panes), silent);
}
},
redrawSlaves: function() {
var navi = this, chart = navi.chart, plotArea = chart._plotArea, slavePanes = plotArea.panes.slice(0, -1);
chart._plotArea.redraw(slavePanes);
},
_drag: function(e) {
var navi = this, chart = navi.chart, coords = chart._eventCoordinates(e.originalEvent), navigatorAxis = navi.mainAxis(), inNavigator = navigatorAxis.pane.box.containsPoint(coords), groups = navigatorAxis.options.categories, axis = chart._plotArea.categoryAxis, baseUnit = axis.options.baseUnit, range = e.axisRanges[axis.options.name], selection = navi.selection, selectionDuration = duration(axis.options.min, axis.options.max, axis.options.baseUnit), from, to;
if (!range || inNavigator) {
return;
}
from = toDate(math.min(math.max(groups[0], range.min), addDuration(dataviz.last(groups), -selectionDuration, baseUnit)));
to = toDate(math.min(addDuration(from, selectionDuration, baseUnit), dataviz.last(groups)));
navi.options.select = {
from: from,
to: to
};
if (navi._liveDrag()) {
navi.filterAxes();
navi.redrawSlaves();
}
selection.set(from, addDuration(from, selectionDuration + 1, baseUnit));
navi.showHint(from, to);
},
_dragEnd: function() {
var navi = this;
navi.filterAxes();
navi.filterDataSource();
navi.redrawSlaves();
if (navi.hint) {
navi.hint.hide();
}
},
_liveDrag: function() {
var support = kendo.support, isTouch = support.touch, browser = support.browser, isFirefox = browser.mozilla, isOldIE = browser.msie && browser.version < 9;
return !isTouch && !isFirefox && !isOldIE;
},
readSelection: function() {
var navi = this, selection = navi.selection, src = selection.options, dst = navi.options.select;
dst.from = src.from;
dst.to = src.to;
},
filterAxes: function() {
var navi = this, categories, select = navi.options.select || {}, chart = navi.chart, allAxes = chart.options.categoryAxis, from = select.from, to = select.to, min, max, i, axis;
for (i = 0; i < allAxes.length; i++) {
axis = allAxes[i];
if (axis.name === NAVIGATOR_AXIS) {
categories = axis.categories;
if (categories && categories.length > 0) {
min = toTime(categories[0]);
max = toTime(last(categories));
from = toTime(from);
if (from < min || from > max) {
from = min;
}
to = toTime(to);
if (to < min || to > max) {
to = max;
}
break;
}
}
}
for (i = 0; i < allAxes.length; i++) {
axis = allAxes[i];
if (axis.pane !== NAVIGATOR_PANE) {
axis.min = toDate(from);
axis.max = toDate(to);
}
}
},
filterDataSource: function() {
var navi = this, select = navi.options.select || {}, chart = navi.chart, chartDataSource = chart.dataSource, hasServerFiltering = chartDataSource && chartDataSource.options.serverFiltering, axisOptions;
if (navi.dataSource && hasServerFiltering) {
axisOptions = new dataviz.DateCategoryAxis(deepExtend({
baseUnit: "fit"
}, chart.options.categoryAxis[0], {
categories: [ select.from, select.to ]
})).options;
chartDataSource.filter(Navigator.buildFilter(addDuration(axisOptions.min, -axisOptions.baseUnitStep, axisOptions.baseUnit), addDuration(axisOptions.max, axisOptions.baseUnitStep, axisOptions.baseUnit)));
}
},
_zoom: function(e) {
var navi = this, chart = navi.chart, delta = e.delta, axis = chart._plotArea.categoryAxis, select = navi.options.select, selection = navi.selection;
e.originalEvent.preventDefault();
if (math.abs(delta) > 1) {
delta *= ZOOM_ACCELERATION;
}
if (selection.options.to - selection.options.from > 1) {
selection.expand(delta);
navi.readSelection();
} else {
axis.options.min = select.from;
select.from = axis.scaleRange(-e.delta).min;
}
if (!kendo.support.touch) {
navi.filterAxes();
navi.redrawSlaves();
}
selection.set(select.from, select.to);
navi.showHint(navi.options.select.from, navi.options.select.to);
},
_zoomEnd: function(e) {
this._dragEnd(e);
},
showHint: function(from, to) {
var navi = this, chart = navi.chart, plotArea = chart._plotArea;
if (navi.hint) {
navi.hint.show(from, to, plotArea.backgroundBox());
}
},
_selectStart: function(e) {
var chart = this.chart;
chart._selectStart.call(chart, e);
},
_select: function(e) {
var navi = this, chart = navi.chart;
navi.showHint(e.from, e.to);
chart._select.call(chart, e);
},
_selectEnd: function(e) {
var navi = this, chart = navi.chart;
if (navi.hint) {
navi.hint.hide();
}
navi.readSelection();
navi.filterAxes();
navi.filterDataSource();
navi.redrawSlaves();
chart._selectEnd.call(chart, e);
},
mainAxis: function() {
var plotArea = this.chart._plotArea;
if (plotArea) {
return plotArea.namedCategoryAxes[NAVIGATOR_AXIS];
}
}
});
Navigator.setup = function(options, themeOptions) {
options = options || {};
themeOptions = themeOptions || {};
var naviOptions = deepExtend({}, themeOptions.navigator, options.navigator), panes = options.panes = [].concat(options.panes), paneOptions = deepExtend({}, naviOptions.pane, {
name: NAVIGATOR_PANE
});
if (!naviOptions.visible) {
paneOptions.visible = false;
paneOptions.height = .1;
}
panes.push(paneOptions);
Navigator.attachAxes(options, naviOptions);
Navigator.attachSeries(options, naviOptions, themeOptions);
};
Navigator.attachAxes = function(options, naviOptions) {
var categoryAxes, valueAxes;
categoryAxes = options.categoryAxis = [].concat(options.categoryAxis);
valueAxes = options.valueAxis = [].concat(options.valueAxis);
var base = deepExtend({
type: "date",
pane: NAVIGATOR_PANE,
field: naviOptions.dateField,
roundToBaseUnit: false,
justified: true,
tooltip: {
visible: false
},
labels: {
step: 1
},
autoBind: !naviOptions.dataSource,
autoBaseUnitSteps: {
minutes: [ 1 ],
hours: [ 1 ],
days: [ 1 ],
weeks: [],
months: [ 1 ],
years: [ 1 ]
},
_overlap: false
}, naviOptions.categoryAxis);
categoryAxes.push(deepExtend({}, base, {
name: NAVIGATOR_AXIS,
baseUnit: "fit",
// TODO: Width based
maxDateGroups: 200,
baseUnitStep: "auto",
labels: {
visible: false
},
majorTicks: {
visible: false
}
}), deepExtend({}, base, {
name: NAVIGATOR_AXIS + "_labels",
// TODO: Width based
maxDateGroups: 20,
baseUnitStep: "auto",
autoBaseUnitSteps: {
minutes: []
},
majorTicks: {
visible: true
}
}), deepExtend({}, base, {
name: NAVIGATOR_AXIS + "_ticks",
// TODO: Width based
maxDateGroups: 200,
majorTicks: {
visible: true,
width: .5
},
labels: {
visible: false,
mirror: true
}
}));
valueAxes.push({
// TODO: Extend navigaor.valueAxis
name: NAVIGATOR_AXIS,
pane: NAVIGATOR_PANE,
majorGridLines: {
visible: false
},
visible: false
});
};
Navigator.attachSeries = function(options, naviOptions, themeOptions) {
var series = options.series = options.series || [], navigatorSeries = [].concat(naviOptions.series), seriesColors = themeOptions.seriesColors, defaults = naviOptions.seriesDefaults, i;
for (i = 0; i < navigatorSeries.length; i++) {
series.push(deepExtend({
color: seriesColors[i % seriesColors.length],
visibleInLegend: false,
tooltip: {
visible: false
}
}, defaults, navigatorSeries[i], {
axis: NAVIGATOR_AXIS,
categoryAxis: NAVIGATOR_AXIS,
autoBind: !naviOptions.dataSource
}));
}
};
Navigator.buildFilter = function(from, to) {
return [ {
field: "Date",
operator: "gte",
value: toDate(from)
}, {
field: "Date",
operator: "lt",
value: toDate(to)
} ];
};
var NavigatorHint = Class.extend({
init: function(container, options) {
var hint = this;
hint.options = deepExtend({}, hint.options, options);
hint.container = container;
hint.chartPadding = {
top: parseInt(container.css("paddingTop"), 10),
left: parseInt(container.css("paddingLeft"), 10)
};
hint.template = hint.template;
if (!hint.template) {
hint.template = hint.template = renderTemplate("<div class='" + CSS_PREFIX + "navigator-hint' " + "style='display: none; position: absolute; top: 1px; left: 1px;'>" + "<div class='" + CSS_PREFIX + "tooltip'> </div>" + "<div class='" + CSS_PREFIX + "scroll' />" + "</div>");
}
hint.element = $(hint.template()).appendTo(container);
},
options: {
format: "{0:d} - {1:d}",
hideDelay: 500
},
show: function(from, to, bbox) {
var hint = this, middle = toDate(toTime(from) + toTime(to - from) / 2), options = hint.options, text = kendo.format(hint.options.format, from, to), tooltip = hint.element.find("." + CSS_PREFIX + "tooltip"), scroll = hint.element.find("." + CSS_PREFIX + "scroll"), scrollWidth = bbox.width() * .4, minPos = bbox.center().x - scrollWidth, maxPos = bbox.center().x, posRange = maxPos - minPos, range = options.max - options.min, scale = posRange / range, offset = middle - options.min, hintTemplate;
if (hint._hideTimeout) {
clearTimeout(hint._hideTimeout);
}
if (!hint._visible) {
hint.element.stop(false, true).css("visibility", "hidden").show();
hint._visible = true;
}
if (options.template) {
hintTemplate = template(options.template);
text = hintTemplate({
from: from,
to: to
});
}
tooltip.text(text).css({
left: bbox.center().x - tooltip.outerWidth() / 2,
top: bbox.y1
});
scroll.css({
width: scrollWidth,
left: minPos + offset * scale,
top: bbox.y1 + parseInt(tooltip.css("margin-top"), 10) + parseInt(tooltip.css("border-top-width"), 10) + tooltip.height() / 2
});
hint.element.css("visibility", "visible");
},
hide: function() {
var hint = this;
if (hint._hideTimeout) {
clearTimeout(hint._hideTimeout);
}
hint._hideTimeout = setTimeout(function() {
hint._visible = false;
hint.element.fadeOut("slow");
}, hint.options.hideDelay);
}
});
function ClonedObject() {}
function clone(obj) {
ClonedObject.prototype = obj;
return new ClonedObject();
}
// Exports ================================================================
dataviz.ui.plugin(StockChart);
deepExtend(dataviz, {
Navigator: Navigator
});
})(window.kendo.jQuery);
(function($, undefined) {
// Imports ===============================================================
var kendo = window.kendo, dataviz = kendo.dataviz, Chart = dataviz.ui.Chart, ObservableArray = kendo.data.ObservableArray, SharedTooltip = dataviz.SharedTooltip, deepExtend = kendo.deepExtend, isArray = $.isArray, inArray = dataviz.inArray, math = Math;
// Constants =============================================================
var CSS_PREFIX = "k-", DEAULT_BAR_WIDTH = 150, DEAULT_BULLET_WIDTH = 150, BAR = "bar", BULLET = "bullet", PIE = "pie", NO_CROSSHAIR = [ BAR, BULLET ];
// Sparkline =============================================================
var Sparkline = Chart.extend({
init: function(element, userOptions) {
var chart = this, stage = chart.stage = $("<span />"), options = userOptions || {};
element = $(element).addClass(CSS_PREFIX + "sparkline").empty().append(stage);
chart._initialWidth = math.floor(element.width());
options = wrapNumber(options);
if (isArray(options) || options instanceof ObservableArray) {
options = {
seriesDefaults: {
data: options
}
};
}
if (!options.series) {
options.series = [ {
data: wrapNumber(options.data)
} ];
}
deepExtend(options, {
seriesDefaults: {
type: options.type
}
});
if (inArray(options.series[0].type, NO_CROSSHAIR) || inArray(options.seriesDefaults.type, NO_CROSSHAIR)) {
options = deepExtend({}, {
categoryAxis: {
crosshair: {
visible: false
}
}
}, options);
}
Chart.fn.init.call(chart, element, options);
},
options: {
name: "Sparkline",
chartArea: {
margin: 2
},
axisDefaults: {
visible: false,
majorGridLines: {
visible: false
},
valueAxis: {
narrowRange: true
}
},
seriesDefaults: {
type: "line",
area: {
line: {
width: .5
}
},
bar: {
stack: true
},
width: .5,
overlay: {
gradient: null
},
highlight: {
visible: false
},
border: {
width: 0
},
markers: {
size: 2,
visible: false
}
},
tooltip: {
visible: true,
shared: true
},
categoryAxis: {
crosshair: {
visible: true,
tooltip: {
visible: false
}
}
},
legend: {
visible: false
},
transitions: false,
pointWidth: 5
},
_modelOptions: function() {
var chart = this, chartOptions = chart.options, options, width = chart._initialWidth, stage = chart.stage;
chart.stage[0].innerHTML = " ";
options = deepExtend({
width: width ? width : chart._autoWidth(),
height: stage.height(),
transitions: chartOptions.transitions
}, chartOptions.chartArea, {
inline: true,
align: false
});
stage.css({
width: options.width,
height: options.height
});
return options;
},
_createTooltip: function() {
var chart = this, options = chart.options, element = chart.element, tooltip;
if (chart._sharedTooltip()) {
tooltip = new SparklineSharedTooltip(element, chart._plotArea, options.tooltip);
} else {
tooltip = Chart.fn._createTooltip.call(chart);
}
return tooltip;
},
_renderView: function() {
var chart = this;
chart.element.empty().append(chart.stage);
return chart._view.renderTo(chart.stage[0]);
},
_autoWidth: function() {
var chart = this, options = chart.options, margin = dataviz.getSpacing(options.chartArea.margin), series = options.series, dsTotal = chart.dataSource.total(), seriesTotal = 0, width, i, currentSeries;
for (i = 0; i < series.length; i++) {
currentSeries = series[i];
if (currentSeries.type === BAR) {
return DEAULT_BAR_WIDTH;
}
if (currentSeries.type === BULLET) {
return DEAULT_BULLET_WIDTH;
}
if (currentSeries.type === PIE) {
return chart.stage.height();
}
if (currentSeries.data) {
seriesTotal = math.max(seriesTotal, currentSeries.data.length);
}
}
width = math.max(dsTotal, seriesTotal) * options.pointWidth;
if (width > 0) {
width += margin.left + margin.right;
}
return width;
}
});
var SparklineSharedTooltip = SharedTooltip.extend({
options: {
animation: {
duration: 0
}
},
_anchor: function(point, slot) {
var anchor = SharedTooltip.fn._anchor.call(this, point, slot);
anchor.y = -this.element.height() - this.options.offset;
return anchor;
},
_hideElement: function() {
this.element.hide();
}
});
function wrapNumber(x) {
return typeof x === "number" ? [ x ] : x;
}
// Exports ================================================================
dataviz.ui.plugin(Sparkline);
deepExtend(dataviz, {});
})(window.kendo.jQuery);
(function() {
// Imports ================================================================
var $ = jQuery, doc = document, math = Math, kendo = window.kendo, Class = kendo.Class, dataviz = kendo.dataviz, Box2D = dataviz.Box2D, ExpandAnimation = dataviz.ExpandAnimation, Point2D = dataviz.Point2D, ViewBase = dataviz.ViewBase, ViewElement = dataviz.ViewElement, deepExtend = kendo.deepExtend, defined = dataviz.defined, round = dataviz.round, renderTemplate = dataviz.renderTemplate, rotatePoint = dataviz.rotatePoint, uniqueId = dataviz.uniqueId;
// Constants ==============================================================
var BUTT = "butt", CLIP = dataviz.CLIP, COORD_PRECISION = dataviz.COORD_PRECISION, DEFAULT_WIDTH = dataviz.DEFAULT_WIDTH, DEFAULT_HEIGHT = dataviz.DEFAULT_HEIGHT, DEFAULT_FONT = dataviz.DEFAULT_FONT, NONE = "none", RADIAL = "radial", SOLID = "solid", SQUARE = "square", SVG_NS = "http://www.w3.org/2000/svg", SVG_DASH_TYPE = {
dot: [ 1.5, 3.5 ],
dash: [ 4, 3.5 ],
longdash: [ 8, 3.5 ],
dashdot: [ 3.5, 3.5, 1.5, 3.5 ],
longdashdot: [ 8, 3.5, 1.5, 3.5 ],
longdashdotdot: [ 8, 3.5, 1.5, 3.5, 1.5, 3.5 ]
}, TRANSPARENT = "transparent", UNDEFINED = "undefined";
// View ===================================================================
var SVGView = ViewBase.extend({
init: function(options) {
var view = this;
ViewBase.fn.init.call(view, options);
view.decorators.push(new SVGOverlayDecorator(view), new SVGGradientDecorator(view));
if (dataviz.ui.Chart) {
view.decorators.push(new dataviz.BarAnimationDecorator(view), new dataviz.PieAnimationDecorator(view), new dataviz.BubbleAnimationDecorator(view));
}
view.decorators.push(new SVGClipAnimationDecorator(view), new dataviz.FadeAnimationDecorator(view));
if (dataviz.Gauge) {
view.decorators.push(new dataviz.RadialPointerAnimationDecorator(view), new dataviz.ArrowPointerAnimationDecorator(view), new dataviz.BarIndicatorAnimationDecorator(view));
}
view.defsId = uniqueId();
view.template = SVGView.template;
view.display = view.options.inline ? "inline" : "block";
if (!view.template) {
view.template = SVGView.template = renderTemplate("<?xml version='1.0' ?>" + "<svg xmlns='" + SVG_NS + "' version='1.1' " + "width='#= d.options.width #px' height='#= d.options.height #px' " + "style='position: relative; display: #= d.display #;'>" + "#= d.renderDefinitions() #" + "#= d.renderContent() #</svg>");
}
},
options: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT
},
renderTo: function(container) {
var view = this, viewElement;
view.setupAnimations();
dataviz.renderSVG(container, view.render());
viewElement = container.firstElementChild;
view.alignToScreen(viewElement);
view.playAnimations();
return viewElement;
},
renderDefinitions: function() {
var view = this, id = view.defsId, output = ViewBase.fn.renderDefinitions.call(view);
return "<defs id='" + id + "'>" + output + "</defs>";
},
renderElement: function(element) {
var view = this, container = doc.createElement("div"), defsCurrent = doc.getElementById(view.defsId), defsElement, domElement;
dataviz.renderSVG(container, "<?xml version='1.0' ?>" + "<svg xmlns='" + SVG_NS + "' version='1.1'>" + view.renderDefinitions() + element.render() + "</svg>");
defsElement = container.firstElementChild.firstChild;
domElement = container.firstElementChild.lastChild;
if (defsCurrent && defsCurrent.textContent !== defsElement.textContent) {
defsCurrent.parentNode.replaceChild(defsElement, defsCurrent);
}
return domElement;
},
createGroup: function(options) {
return this.decorate(new SVGGroup(options));
},
createText: function(content, options) {
return this.decorate(new SVGText(content, options));
},
createRect: function(box, style) {
return this.decorate(new SVGLine(box.points(), true, this.setDefaults(style)));
},
createLine: function(x1, y1, x2, y2, options) {
return this.decorate(new SVGLine([ new Point2D(x1, y1), new Point2D(x2, y2) ], false, this.setDefaults(options)));
},
createPolyline: function(points, closed, options) {
return this.decorate(new SVGLine(points, closed, this.setDefaults(options)));
},
createCircle: function(center, radius, options) {
return this.decorate(new SVGCircle(center, radius, options));
},
createSector: function(sector, options) {
return this.decorate(new SVGSector(sector, options));
},
createRing: function(ring, options) {
return this.decorate(new SVGRing(ring, options));
},
createPin: function(pin, options) {
return this.decorate(new SVGPin(pin, options));
},
createGradient: function(options) {
if (options.type === RADIAL) {
if (defined(options.ir)) {
return new SVGDonutGradient(options);
} else {
return new SVGRadialGradient(options);
}
} else {
return new SVGLinearGradient(options);
}
},
alignToScreen: function(element) {
var ctm;
try {
ctm = element.getScreenCTM ? element.getScreenCTM() : null;
} catch (e) {}
if (ctm) {
var left = -ctm.e % 1, top = -ctm.f % 1, style = element.style;
if (left !== 0 || top !== 0) {
style.left = left + "px";
style.top = top + "px";
}
}
}
});
SVGView.fromModel = function(model) {
var view = new SVGView(model.options);
[].push.apply(view.children, model.getViewElements(view));
return view;
};
SVGView.available = dataviz.supportsSVG;
SVGView.preference = 100;
dataviz.ui.registerView(SVGView);
var SVGText = ViewElement.extend({
init: function(content, options) {
var text = this;
ViewElement.fn.init.call(text, options);
text.content = content;
text.template = SVGText.template;
if (!text.template) {
text.template = SVGText.template = renderTemplate("<text #= d.renderId() # " + "#= d.renderDataAttributes() # " + "x='#= Math.round(d.options.x) #' " + "y='#= Math.round(d.options.y + d.options.baseline) #' " + "fill-opacity='#= d.options.fillOpacity #' " + "#= d.options.rotation ? d.renderRotation() : '' # " + "style='font: #= d.options.font #' fill='#= d.options.color #'>" + "#= d.content #</text>");
}
},
options: {
x: 0,
y: 0,
baseline: 0,
font: DEFAULT_FONT,
size: {
width: 0,
height: 0
},
fillOpacity: 1
},
refresh: function(domElement) {
var options = this.options;
$(domElement).attr({
"fill-opacity": options.fillOpacity
});
},
clone: function() {
var text = this;
return new SVGText(text.content, deepExtend({}, text.options));
},
renderRotation: function() {
var text = this, options = text.options, size = options.size, cx = round(options.x + size.normalWidth / 2, COORD_PRECISION), cy = round(options.y + size.normalHeight / 2, COORD_PRECISION), rcx = round(options.x + size.width / 2, COORD_PRECISION), rcy = round(options.y + size.height / 2, COORD_PRECISION), offsetX = round(rcx - cx, COORD_PRECISION), offsetY = round(rcy - cy, COORD_PRECISION);
return "transform='translate(" + offsetX + "," + offsetY + ") " + "rotate(" + options.rotation + "," + cx + "," + cy + ")'";
}
});
var SVGPath = ViewElement.extend({
init: function(options) {
var path = this;
ViewElement.fn.init.call(path, options);
path.template = SVGPath.template;
if (!path.template) {
path.template = SVGPath.template = renderTemplate("<path #= d.renderId() #" + "style='display: #= d.renderDisplay() #' " + "#= d.renderDataAttributes() # " + "d='#= d.renderPoints() #' " + '#= d.renderAttr("stroke", d.options.stroke) # ' + '#= d.renderAttr("stroke-width", d.options.strokeWidth) #' + "#= d.renderDashType() # " + "stroke-linecap='#= d.renderLinecap() #' " + "stroke-linejoin='round' " + "fill-opacity='#= d.options.fillOpacity #' " + "stroke-opacity='#= d.options.strokeOpacity #' " + "fill='#= d.renderFill() #'></path>");
}
},
options: {
fill: "",
fillOpacity: 1,
strokeOpacity: 1,
rotation: [ 0, 0, 0 ],
strokeLineCap: SQUARE,
visible: true
},
refresh: function(domElement) {
var options = this.options;
$(domElement).attr({
d: this.renderPoints(),
"fill-opacity": options.fillOpacity,
"stroke-opacity": options.strokeOpacity
}).css("display", this.renderDisplay());
},
clone: function() {
return new SVGPath(deepExtend({}, this.options));
},
renderPoints: function() {},
renderDashType: function() {
var path = this, options = path.options;
return renderSVGDash(options.dashType, options.strokeWidth);
},
renderLinecap: function() {
var options = this.options, dashType = options.dashType, strokeLineCap = options.strokeLineCap;
return dashType && dashType != SOLID ? BUTT : strokeLineCap;
},
renderFill: function() {
var fill = this.options.fill;
if (fill && fill !== TRANSPARENT) {
return fill;
}
return NONE;
},
renderDisplay: function() {
return this.options.visible ? "block" : "none";
},
destroy: function() {}
});
var SVGLine = SVGPath.extend({
init: function(points, closed, options) {
var line = this;
SVGPath.fn.init.call(line, options);
line.points = points;
line.closed = closed;
},
renderPoints: function() {
var line = this, points = line.points, i, count = points.length, rotate = function(point) {
var rotation = line.options.rotation;
return rotatePoint(point.x, point.y, rotation[1], rotation[2], -rotation[0]);
}, result = "M" + line._print(rotate(points[0]));
for (i = 1; i < count; i++) {
result += " " + line._print(rotate(points[i]));
}
if (line.closed) {
result += " z";
}
return result;
},
clone: function() {
var line = this;
return new SVGLine(deepExtend([], line.points), line.closed, deepExtend({}, line.options));
},
_print: function(point) {
var line = this, options = line.options, strokeWidth = options.strokeWidth, shouldAlign = options.align !== false && strokeWidth && strokeWidth % 2 !== 0, align = shouldAlign ? alignToPixel : round;
return align(point.x, COORD_PRECISION) + " " + align(point.y, COORD_PRECISION);
}
});
var SVGRing = SVGPath.extend({
init: function(config, options) {
var ring = this;
SVGPath.fn.init.call(ring, options);
ring.pathTemplate = SVGRing.pathTemplate;
if (!ring.pathTemplate) {
ring.pathTemplate = SVGRing.pathTemplate = renderTemplate("M #= d.firstOuterPoint.x # #= d.firstOuterPoint.y # " + "A#= d.r # #= d.r # " + "0 #= d.isReflexAngle ? '1' : '0' #,1 " + "#= d.secondOuterPoint.x # #= d.secondOuterPoint.y # " + "L #= d.secondInnerPoint.x # #= d.secondInnerPoint.y # " + "A#= d.ir # #= d.ir # " + "0 #= d.isReflexAngle ? '1' : '0' #,0 " + "#= d.firstInnerPoint.x # #= d.firstInnerPoint.y # z");
}
ring.config = config || {};
},
renderPoints: function() {
var ring = this, ringConfig = ring.config, startAngle = ringConfig.startAngle, endAngle = ringConfig.angle + startAngle, isReflexAngle = endAngle - startAngle > 180, r = math.max(ringConfig.r, 0), ir = math.max(ringConfig.ir, 0), center = ringConfig.c, firstOuterPoint = ringConfig.point(startAngle), firstInnerPoint = ringConfig.point(startAngle, true), secondOuterPoint, secondInnerPoint;
endAngle = endAngle - startAngle === 360 ? endAngle - .002 : endAngle;
secondOuterPoint = ringConfig.point(endAngle);
secondInnerPoint = ringConfig.point(endAngle, true);
return ring.pathTemplate({
firstOuterPoint: firstOuterPoint,
secondOuterPoint: secondOuterPoint,
isReflexAngle: isReflexAngle,
r: r,
ir: ir,
cx: center.x,
cy: center.y,
firstInnerPoint: firstInnerPoint,
secondInnerPoint: secondInnerPoint
});
},
clone: function() {
var ring = this;
return new SVGRing(deepExtend({}, ring.config), deepExtend({}, ring.options));
}
});
var SVGPin = SVGPath.extend({
init: function(config, options) {
var pin = this;
SVGPath.fn.init.call(pin, options);
pin.pathTemplate = SVGPin.pathTemplate;
if (!pin.pathTemplate) {
pin.pathTemplate = SVGPin.pathTemplate = renderTemplate("M #= d.origin.x # #= d.origin.y # " + "#= d.as.x # #= d.as.y # " + "A#= d.r # #= d.r # " + "0 #= d.isReflexAngle ? '1' : '0' #,0 " + "#= d.ae.x # #= d.ae.y # " + "z");
}
pin.config = config || new dataviz.Pin();
},
renderPoints: function() {
var pin = this, config = pin.config, r = config.radius, degrees = math.PI / 180, arcAngle = config.arcAngle, halfChordLength = r * math.sin(arcAngle * degrees / 2), height = config.height - r * (1 - math.cos(arcAngle * degrees / 2)), origin = config.origin, arcStart = {
x: origin.x + halfChordLength,
y: origin.y - height
}, arcEnd = {
x: origin.x - halfChordLength,
y: origin.y - height
}, rotate = function(point, inclinedPoint) {
var rotation = pin.options.rotation, inclination = config.rotation;
point = rotatePoint(point.x, point.y, rotation[1], rotation[2], -rotation[0]);
if (inclinedPoint) {
point = rotatePoint(point.x, point.y, origin.x, origin.y, inclination);
}
return point;
};
origin = rotate(origin);
return pin.pathTemplate({
origin: origin,
as: rotate(arcStart, true),
ae: rotate(arcEnd, true),
r: r,
isReflexAngle: arcAngle > 180
});
}
});
var SVGSector = SVGRing.extend({
init: function(config, options) {
var sector = this;
SVGRing.fn.init.call(sector, config, options);
sector.pathTemplate = SVGSector.pathTemplate;
if (!sector.pathTemplate) {
sector.pathTemplate = SVGSector.pathTemplate = renderTemplate("M #= d.firstOuterPoint.x # #= d.firstOuterPoint.y # " + "A#= d.r # #= d.r # " + "0 #= d.isReflexAngle ? '1' : '0' #,1 " + "#= d.secondOuterPoint.x # #= d.secondOuterPoint.y # " + "L #= d.cx # #= d.cy # z");
}
},
options: {
fill: "",
fillOpacity: 1,
strokeOpacity: 1,
strokeLineCap: SQUARE
},
clone: function() {
var sector = this;
return new SVGSector(deepExtend({}, sector.config), deepExtend({}, sector.options));
}
});
var SVGCircle = ViewElement.extend({
init: function(c, r, options) {
var circle = this;
ViewElement.fn.init.call(circle, options);
circle.c = c;
circle.r = r;
circle.template = SVGCircle.template;
if (!circle.template) {
circle.template = SVGCircle.template = renderTemplate("<circle #= d.renderId() # " + "#= d.renderDataAttributes() #" + "cx='#= d.c.x #' cy='#= d.c.y #' " + "r='#= d.r #' " + '#= d.renderAttr("stroke", d.options.stroke) # ' + '#= d.renderAttr("stroke-width", d.options.strokeWidth) #' + "fill-opacity='#= d.options.fillOpacity #' " + "stroke-opacity='#= d.options.strokeOpacity #' " + "fill='#= d.options.fill || \"none\" #'></circle>");
}
},
options: {
fill: "",
fillOpacity: 1,
strokeOpacity: 1
},
refresh: function(domElement) {
$(domElement).attr({
r: math.max(0, this.r),
"fill-opacity": this.options.fillOpacity
});
},
clone: function() {
var circle = this;
return new SVGCircle(deepExtend({}, circle.c), circle.r, deepExtend({}, circle.options));
}
});
var SVGGroup = ViewElement.extend({
init: function(options) {
var group = this;
ViewElement.fn.init.call(group, options);
group.template = SVGGroup.template;
if (!group.template) {
group.template = SVGGroup.template = renderTemplate("<g#= d.renderId() #" + "#= d.renderDataAttributes() #" + '#= d.renderAttr("clip-path", d.options.clipPath) #>' + "#= d.renderContent() #</g>");
}
}
});
var SVGClipPath = ViewElement.extend({
init: function(options) {
var clip = this;
ViewElement.fn.init.call(clip, options);
clip.template = SVGClipPath.template;
if (!clip.template) {
clip.template = SVGClipPath.template = renderTemplate('<clipPath#= d.renderAttr("id", d.options.id) #>' + "#= d.renderContent() #</clipPath>");
}
}
});
var SVGGradient = ViewElement.extend({
init: function(options) {
var gradient = this;
ViewElement.fn.init.call(gradient, options);
},
options: {
id: ""
},
renderStops: function() {
var gradient = this, stops = gradient.options.stops, stopTemplate = gradient.stopTemplate, i, length = stops.length, currentStop, output = "";
for (i = 0; i < length; i++) {
currentStop = stops[i];
output += stopTemplate(currentStop);
}
return output;
}
});
var SVGLinearGradient = SVGGradient.extend({
init: function(options) {
var gradient = this;
SVGGradient.fn.init.call(gradient, options);
gradient.template = SVGLinearGradient.template;
gradient.stopTemplate = SVGLinearGradient.stopTemplate;
if (!gradient.template) {
gradient.template = SVGLinearGradient.template = renderTemplate("<linearGradient id='#= d.options.id #' " + "gradientTransform='rotate(#= d.options.rotation #)'> " + "#= d.renderStops() #" + "</linearGradient>");
gradient.stopTemplate = SVGLinearGradient.stopTemplate = renderTemplate("<stop offset='#= Math.round(d.offset * 100) #%' " + "style='stop-color:#= d.color #;stop-opacity:#= d.opacity #' />");
}
},
options: {
rotation: 0
}
});
var SVGRadialGradient = SVGGradient.extend({
init: function(options) {
var gradient = this;
SVGGradient.fn.init.call(gradient, options);
gradient.template = SVGRadialGradient.template;
gradient.stopTemplate = SVGRadialGradient.stopTemplate;
if (!gradient.template) {
gradient.template = SVGRadialGradient.template = renderTemplate("<radialGradient id='#= d.options.id #' " + "cx='#= d.options.cx #' cy='#= d.options.cy #' " + "fx='#= d.options.cx #' fy='#= d.options.cy #' " + "r='#= d.options.r #' gradientUnits='userSpaceOnUse'>" + "#= d.renderStops() #" + "</radialGradient>");
gradient.stopTemplate = SVGRadialGradient.stopTemplate = renderTemplate("<stop offset='#= Math.round(d.offset * 100) #%' " + "style='stop-color:#= d.color #;stop-opacity:#= d.opacity #' />");
}
}
});
var SVGDonutGradient = ViewElement.extend({
init: function(options) {
var gradient = this;
ViewElement.fn.init.call(gradient, options);
gradient.template = SVGDonutGradient.template;
gradient.stopTemplate = SVGDonutGradient.stopTemplate;
if (!gradient.template) {
gradient.template = SVGDonutGradient.template = renderTemplate("<radialGradient id='#= d.options.id #' " + "cx='#= d.options.cx #' cy='#= d.options.cy #' " + "fx='#= d.options.cx #' fy='#= d.options.cy #' " + "r='#= d.options.r #' gradientUnits='userSpaceOnUse'>" + "#= d.renderStops() #" + "</radialGradient>");
gradient.stopTemplate = SVGDonutGradient.stopTemplate = renderTemplate("<stop offset='#= d.offset #%' " + "style='stop-color:#= d.color #;stop-opacity:#= d.opacity #' />");
}
},
options: {
id: ""
},
renderStops: function() {
var gradient = this, options = gradient.options, stops = options.stops, stopTemplate = gradient.stopTemplate, usedSpace = options.ir / options.r * 100, i, length = stops.length, currentStop, output = "";
currentStop = deepExtend({}, stops[0]);
currentStop.offset = usedSpace;
output += stopTemplate(currentStop);
for (i = 1; i < length; i++) {
currentStop = deepExtend({}, stops[i]);
currentStop.offset = currentStop.offset * (100 - usedSpace) + usedSpace;
output += stopTemplate(currentStop);
}
return output;
}
});
// Decorators =============================================================
function SVGOverlayDecorator(view) {
this.view = view;
}
SVGOverlayDecorator.prototype = {
decorate: function(element) {
var decorator = this, view = decorator.view, options = element.options, id = options.id, group, overlay;
if (options.overlay) {
element.options.id = uniqueId();
group = view.createGroup();
overlay = element.clone();
group.children.push(element, overlay);
overlay.options.id = id;
overlay.options.fill = options.overlay;
return group;
} else {
return element;
}
}
};
function SVGGradientDecorator(view) {
this.view = view;
}
SVGGradientDecorator.prototype = {
decorate: function(element) {
var decorator = this, options = element.options;
options.fill = decorator.getPaint(options.fill);
return element;
},
getPaint: function(paint) {
var decorator = this, view = decorator.view, definitions = view.definitions, overlay, overlayId, gradient;
if (paint && defined(paint.gradient)) {
overlay = view.buildGradient(paint);
if (overlay) {
overlayId = overlay.id;
gradient = definitions[overlayId];
if (!gradient) {
gradient = view.createGradient(overlay);
definitions[overlayId] = gradient;
}
return "url(" + decorator.baseUrl() + "#" + gradient.options.id + ")";
} else {
return NONE;
}
} else {
return paint;
}
},
baseUrl: function() {
var base = doc.getElementsByTagName("base")[0], baseUrl = "", href = doc.location.href, hashIndex = href.indexOf("#");
if (base && !kendo.support.browser.msie) {
if (hashIndex !== -1) {
href = href.substring(0, hashIndex);
}
baseUrl = href;
}
return baseUrl;
}
};
var SVGClipAnimationDecorator = Class.extend({
init: function(view) {
this.view = view;
},
decorate: function(element) {
var decorator = this, view = decorator.view, clipId = decorator.clipId, options = view.options, animation = element.options.animation, definitions = view.definitions, clipPath, clipRect;
if (animation && animation.type === CLIP && options.transitions) {
if (!clipId) {
decorator.clipId = clipId = uniqueId();
}
clipPath = definitions[clipId];
if (!clipPath) {
clipPath = new SVGClipPath({
id: clipId
});
clipRect = view.createRect(new Box2D(0, 0, options.width, options.height), {
id: uniqueId()
});
clipPath.children.push(clipRect);
definitions[clipId] = clipPath;
view.animations.push(new ExpandAnimation(clipRect, {
size: options.width
}));
}
element.options.clipPath = "url(#" + clipId + ")";
}
return element;
}
});
// Helpers ================================================================
function alignToPixel(coord) {
return math.round(coord) + .5;
}
function renderSVGDash(dashType, strokeWidth) {
var result = [], dashTypeArray, i;
dashType = dashType ? dashType.toLowerCase() : null;
if (dashType && dashType != SOLID) {
dashTypeArray = SVG_DASH_TYPE[dashType];
for (i = 0; i < dashTypeArray.length; i++) {
result.push(dashTypeArray[i] * (strokeWidth || 1));
}
return "stroke-dasharray='" + result.join(" ") + "' ";
}
return "";
}
var renderSVG = function(container, svg) {
container.innerHTML = svg;
};
(function() {
var testFragment = "<svg xmlns='" + SVG_NS + "'></svg>", testContainer = doc.createElement("div"), hasParser = typeof DOMParser != UNDEFINED;
testContainer.innerHTML = testFragment;
if (hasParser && testContainer.firstChild.namespaceURI != SVG_NS) {
renderSVG = function(container, svg) {
var parser = new DOMParser(), chartDoc = parser.parseFromString(svg, "text/xml"), importedDoc = doc.adoptNode(chartDoc.documentElement);
container.innerHTML = "";
container.appendChild(importedDoc);
};
}
})();
// Exports ================================================================
deepExtend(dataviz, {
renderSVG: renderSVG,
SVGCircle: SVGCircle,
SVGClipAnimationDecorator: SVGClipAnimationDecorator,
SVGClipPath: SVGClipPath,
SVGGradientDecorator: SVGGradientDecorator,
SVGGroup: SVGGroup,
SVGLine: SVGLine,
SVGLinearGradient: SVGLinearGradient,
SVGOverlayDecorator: SVGOverlayDecorator,
SVGPath: SVGPath,
SVGRadialGradient: SVGRadialGradient,
SVGDonutGradient: SVGDonutGradient,
SVGRing: SVGRing,
SVGSector: SVGSector,
SVGText: SVGText,
SVGView: SVGView
});
})(window.kendo.jQuery);
(function() {
// Imports ================================================================
var $ = jQuery, doc = document, math = Math, kendo = window.kendo, Class = kendo.Class, deepExtend = kendo.deepExtend, dataviz = kendo.dataviz, Color = dataviz.Color, Box2D = dataviz.Box2D, Point2D = dataviz.Point2D, ExpandAnimation = dataviz.ExpandAnimation, ViewBase = dataviz.ViewBase, ViewElement = dataviz.ViewElement, defined = dataviz.defined, renderTemplate = dataviz.renderTemplate, uniqueId = dataviz.uniqueId, rotatePoint = dataviz.rotatePoint, round = dataviz.round, supportsSVG = dataviz.supportsSVG;
// Constants ==============================================================
var BLACK = "#000", CLIP = dataviz.CLIP, COORD_PRECISION = dataviz.COORD_PRECISION, DEFAULT_WIDTH = dataviz.DEFAULT_WIDTH, DEFAULT_HEIGHT = dataviz.DEFAULT_HEIGHT, DEFAULT_FONT = dataviz.DEFAULT_FONT, OBJECT = "object", LINEAR = "linear", RADIAL = "radial", TRANSPARENT = "transparent";
// View ===================================================================
var VMLView = ViewBase.extend({
init: function(options) {
var view = this;
ViewBase.fn.init.call(view, options);
view.decorators.push(new VMLOverlayDecorator(view), new VMLGradientDecorator(view));
if (dataviz.ui.Chart) {
view.decorators.push(new dataviz.BarAnimationDecorator(view), new dataviz.PieAnimationDecorator(view), new dataviz.BubbleAnimationDecorator(view));
}
view.decorators.push(new VMLClipAnimationDecorator(view));
if (!isIE9CompatibilityView()) {
// Setting opacity on VML elements is broken in
// IE9 Compatibility View
view.decorators.push(new dataviz.FadeAnimationDecorator(view));
}
if (dataviz.Gauge) {
view.decorators.push(new dataviz.RadialPointerAnimationDecorator(view), new dataviz.ArrowPointerAnimationDecorator(view), new dataviz.BarIndicatorAnimationDecorator(view));
}
view.template = VMLView.template;
view.tagName = view.options.inline ? "span" : "div";
if (!view.template) {
view.template = VMLView.template = renderTemplate("<#= d.tagName # style='width:#= d.options.width #px; " + "height:#= d.options.height #px; " + "position: relative;'>" + "#= d.renderContent() #</#= d.tagName #>");
}
},
options: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT
},
renderTo: function(container) {
var view = this;
if (doc.namespaces) {
doc.namespaces.add("kvml", "urn:schemas-microsoft-com:vml", "#default#VML");
}
view.setupAnimations();
container.innerHTML = view.render();
view.playAnimations();
return container.firstChild;
},
renderElement: function(element) {
var container = doc.createElement("div"), domElement;
container.style.display = "none";
doc.body.appendChild(container);
container.innerHTML = element.render();
domElement = container.firstChild;
doc.body.removeChild(container);
return domElement;
},
createText: function(content, options) {
return this.decorate(options && options.rotation ? new VMLRotatedText(content, options) : new VMLText(content, options));
},
createRect: function(box, style) {
return this.decorate(new VMLLine(box.points(), true, this.setDefaults(style)));
},
createLine: function(x1, y1, x2, y2, options) {
return this.decorate(new VMLLine([ new Point2D(x1, y1), new Point2D(x2, y2) ], false, this.setDefaults(options)));
},
createPolyline: function(points, closed, options) {
return this.decorate(new VMLLine(points, closed, this.setDefaults(options)));
},
createCircle: function(center, radius, options) {
return this.decorate(new VMLCircle(center, radius, options));
},
createSector: function(sector, options) {
return this.decorate(new VMLSector(sector, options));
},
createRing: function(ring, options) {
return this.decorate(new VMLRing(ring, this.setDefaults(options)));
},
createGroup: function(options) {
return this.decorate(new VMLGroup(this.setDefaults(options)));
},
createGradient: function(options) {
var validRadial = defined(options.cx) && defined(options.cy) && defined(options.bbox);
if (options.type === RADIAL && validRadial) {
return new VMLRadialGradient(options);
} else if (options.type === LINEAR) {
return new VMLLinearGradient(options);
} else {
return BLACK;
}
}
});
VMLView.fromModel = function(model) {
var view = new VMLView(model.options);
[].push.apply(view.children, model.getViewElements(view));
return view;
};
VMLView.available = function() {
return kendo.support.browser.msie;
};
VMLView.preference = 50;
dataviz.ui.registerView(VMLView);
// Primitives =============================================================
var VMLText = ViewElement.extend({
init: function(content, options) {
var text = this;
ViewElement.fn.init.call(text, options);
text.content = content;
text.template = VMLText.template;
if (!text.template) {
text.template = VMLText.template = renderTemplate("<kvml:textbox #= d.renderId() # " + "#= d.renderDataAttributes() #" + "style='position: absolute; " + "left: #= d.options.x #px; top: #= d.options.y #px; " + "font: #= d.options.font #; color: #= d.options.color #; " + "visibility: #= d.renderVisibility() #; white-space: nowrap;'>" + "#= d.content #</kvml:textbox>");
}
},
options: {
x: 0,
y: 0,
font: DEFAULT_FONT,
color: BLACK,
fillOpacity: 1
},
refresh: function(domElement) {
$(domElement).css("visibility", this.renderVisibility());
},
clone: function() {
var text = this;
return new VMLText(text.content, deepExtend({}, text.options));
},
renderVisibility: function() {
return this.options.fillOpacity > 0 ? "visible" : "hidden";
}
});
var VMLRotatedText = ViewElement.extend({
init: function(content, options) {
var text = this;
ViewElement.fn.init.call(text, options);
text.content = content;
text.template = VMLRotatedText.template;
if (!text.template) {
text.template = VMLRotatedText.template = renderTemplate("<kvml:shape #= d.renderId() # " + "#= d.renderDataAttributes() #" + "style='position: absolute; top: 0px; left: 0px; " + "width: 1px; height: 1px;' stroked='false' coordsize='1,1'>" + "#= d.renderPath() #" + "<kvml:fill color='#= d.options.color #' />" + "<kvml:textpath on='true' style='font: #= d.options.font #;' " + "fitpath='false' string='#= d.content #' /></kvml:shape>");
}
},
options: {
x: 0,
y: 0,
font: DEFAULT_FONT,
color: BLACK,
size: {
width: 0,
height: 0
}
},
renderPath: function() {
var text = this, options = text.options, width = options.size.width, height = options.size.height, cx = options.x + width / 2, cy = options.y + height / 2, angle = -options.rotation, r1 = rotatePoint(options.x, cy, cx, cy, angle), r2 = rotatePoint(options.x + width, cy, cx, cy, angle);
return "<kvml:path textpathok='true' " + "v='m " + round(r1.x) + "," + round(r1.y) + " l " + round(r2.x) + "," + round(r2.y) + "' />";
}
});
var VMLStroke = ViewElement.extend({
init: function(options) {
var stroke = this;
ViewElement.fn.init.call(stroke, options);
stroke.template = VMLStroke.template;
if (!stroke.template) {
stroke.template = VMLStroke.template = renderTemplate("<kvml:stroke on='#= !!d.options.stroke #' " + '#= d.renderAttr("color", d.options.stroke) #' + '#= d.renderAttr("weight", d.options.strokeWidth) #' + '#= d.renderAttr("dashstyle", d.options.dashType) #' + '#= d.renderAttr("opacity", d.options.strokeOpacity) # />');
}
},
refresh: function(domElement) {
try {
domElement.opacity = this.options.strokeOpacity;
} catch (e) {}
}
});
var VMLFill = ViewElement.extend({
init: function(options) {
var stroke = this;
ViewElement.fn.init.call(stroke, options);
stroke.template = VMLFill.template;
if (!stroke.template) {
stroke.template = VMLFill.template = renderTemplate("<kvml:fill on='#= d.isEnabled() #' " + '#= d.renderAttr("color", d.options.fill) #' + '#= d.renderAttr("weight", d.options.fillWidth) #' + '#= d.renderAttr("opacity", d.options.fillOpacity) # />');
}
},
isEnabled: function() {
var fill = this.options.fill;
return !!fill && fill.toLowerCase() !== TRANSPARENT;
},
refresh: function(domElement) {
try {
domElement.opacity = this.options.fillOpacity;
} catch (e) {}
}
});
var VMLPath = ViewElement.extend({
init: function(options) {
var path = this;
ViewElement.fn.init.call(path, options);
path.template = VMLPath.template;
if (!path.template) {
path.template = VMLPath.template = renderTemplate("<kvml:shape #= d.renderId() # " + "#= d.renderDataAttributes() #" + "style='position:absolute; #= d.renderSize() # display:#= d.renderDisplay() #;' " + "coordorigin='0 0' #= d.renderCoordsize() #>" + "<kvml:path v='#= d.renderPoints() # e' />" + "#= d.fill.render() + d.stroke.render() #" + "</kvml:shape>");
}
path.stroke = new VMLStroke(path.options);
path.fill = new VMLFill(path.options);
},
options: {
fill: "",
fillOpacity: 1,
strokeOpacity: 1,
rotation: [ 0, 0, 0 ],
visible: true
},
renderCoordsize: function() {
var scale = this.options.align === false ? 1e4 : 1;
return "coordsize='" + scale + " " + scale + "'";
},
renderSize: function() {
var scale = this.options.align === false ? 100 : 1;
return "width:" + scale + "px; height:" + scale + "px;";
},
render: function() {
var path = this;
path.fill.options.fillOpacity = path.options.fillOpacity;
path.stroke.options.strokeOpacity = path.options.strokeOpacity;
return ViewElement.fn.render.call(path);
},
renderDisplay: function() {
return this.options.visible ? "block" : "none";
},
renderPoints: function() {},
refresh: function(domElement) {
if (!domElement) {
return;
}
var path = this, element = $(domElement), parentNode = element[0].parentNode, fill = path.fill, stroke = path.stroke;
if (parentNode) {
element.find("path")[0].v = this.renderPoints();
fill.options = stroke.options = path.options;
fill.refresh(element.find("fill")[0]);
stroke.refresh(element.find("stroke")[0]);
element.css("display", path.renderDisplay());
// Force redraw in order to remove artifacts in IE < 7
parentNode.style.cssText = parentNode.style.cssText;
}
}
});
var VMLLine = VMLPath.extend({
init: function(points, closed, options) {
var line = this;
VMLPath.fn.init.call(line, options);
line.points = points;
line.closed = closed;
},
renderPoints: function() {
var line = this, points = line.points, i, count = points.length, rotate = function(point) {
var rotation = line.options.rotation;
return rotatePoint(point.x, point.y, rotation[1], rotation[2], -rotation[0]);
}, result = "m " + line._print(rotate(points[0]));
if (count > 1) {
result += " l ";
for (i = 1; i < count; i++) {
result += line._print(rotate(points[i]));
if (i < count - 1) {
result += ", ";
}
}
}
if (line.closed) {
result += " x";
}
return result;
},
clone: function() {
var line = this;
return new VMLLine(deepExtend([], line.points), line.closed, deepExtend({}, line.options));
},
_print: function(point) {
var scale = this.options.align === false ? 100 : 1;
return math.round(point.x * scale) + "," + math.round(point.y * scale);
}
});
var VMLRing = VMLPath.extend({
init: function(config, options) {
var ring = this;
VMLPath.fn.init.call(ring, options);
ring.pathTemplate = VMLRing.pathTemplate;
if (!ring.pathTemplate) {
ring.pathTemplate = VMLRing.pathTemplate = renderTemplate("M #= d.osp.x #,#= d.osp.y # " + "WA #= d.obb.l #,#= d.obb.t # #= d.obb.r #,#= d.obb.b # " + "#= d.osp.x #,#= d.osp.y # #= d.oep.x #,#= d.oep.y # " + "L #= d.iep.x #,#= d.iep.y # " + "AT #= d.ibb.l #,#= d.ibb.t # #= d.ibb.r #,#= d.ibb.b # " + "#= d.iep.x #,#= d.iep.y # #= d.isp.x #,#= d.isp.y # " + "X E");
}
ring.config = config;
},
renderPoints: function() {
var ring = this, config = ring.config, r = math.max(round(config.r), 0), ir = math.max(round(config.ir), 0), cx = round(config.c.x), cy = round(config.c.y), startAngle = config.startAngle, endAngle = config.angle + startAngle, angle = endAngle - startAngle, outerBBox = {
l: cx - r,
t: cy - r,
r: cx + r,
b: cy + r
}, innerBBox = {
l: cx - ir,
t: cy - ir,
r: cx + ir,
b: cy + ir
}, outerStartPoint, innerStartPoint, innerEndPoint, outerEndPoint;
function roundPointCoordinates(point) {
return new Point2D(round(point.x), round(point.y));
}
if (angle <= 1) {
endAngle += 1 - angle;
} else if (angle > 359) {
endAngle -= 1 - angle;
}
outerStartPoint = roundPointCoordinates(config.point(startAngle)), innerStartPoint = roundPointCoordinates(config.point(startAngle, true)),
outerEndPoint = roundPointCoordinates(config.point(endAngle));
innerEndPoint = roundPointCoordinates(config.point(endAngle, true));
return ring.pathTemplate({
obb: outerBBox,
ibb: innerBBox,
osp: outerStartPoint,
isp: innerStartPoint,
oep: outerEndPoint,
iep: innerEndPoint,
cx: cx,
cy: cy
});
},
clone: function() {
var sector = this;
return new VMLRing(deepExtend({}, sector.config), deepExtend({}, sector.options));
}
});
var VMLSector = VMLRing.extend({
init: function(config, options) {
var sector = this;
VMLRing.fn.init.call(sector, config, options);
sector.pathTemplate = VMLSector.pathTemplate;
if (!sector.pathTemplate) {
sector.pathTemplate = VMLSector.pathTemplate = renderTemplate("M #= d.osp.x #,#= d.osp.y # " + "WA #= d.obb.l #,#= d.obb.t # #= d.obb.r #,#= d.obb.b # " + "#= d.osp.x #,#= d.osp.y # #= d.oep.x #,#= d.oep.y # " + "L #= d.cx #,#= d.cy # " + "X E");
}
},
clone: function() {
var sector = this;
return new VMLSector(deepExtend({}, sector.config), deepExtend({}, sector.options));
}
});
var VMLCircle = ViewElement.extend({
init: function(c, r, options) {
var circle = this;
ViewElement.fn.init.call(circle, options);
circle.c = c;
circle.r = r;
circle.template = VMLCircle.template;
if (!circle.template) {
circle.template = VMLCircle.template = renderTemplate("<kvml:oval #= d.renderId() # " + "#= d.renderDataAttributes() #" + "style='position:absolute; " + "width:#= d.r * 2 #px; height:#= d.r * 2 #px; " + "top:#= d.c.y - d.r #px; " + "left:#= d.c.x - d.r #px;'>" + "#= d.fill.render() + d.stroke.render() #" + "</kvml:oval>");
}
circle.stroke = new VMLStroke(circle.options);
circle.fill = new VMLFill(circle.options);
},
options: {
fill: "",
fillOpacity: 1
},
refresh: function(domElement) {
var circle = this, c = circle.c, r = math.max(0, circle.r), size = r * 2, element = $(domElement);
element.css({
width: size,
height: size,
top: c.y - r,
left: c.x - r
});
circle.fill.options = circle.options;
circle.fill.refresh(element.find("fill")[0]);
},
clone: function() {
var circle = this;
return new VMLCircle(deepExtend({}, circle.c), circle.r, deepExtend({}, circle.options));
}
});
var VMLGroup = ViewElement.extend({
init: function(options) {
var group = this;
ViewElement.fn.init.call(group, options);
group.tagName = group.options.inline ? "span" : "div";
group.template = VMLGroup.template;
if (!group.template) {
group.template = VMLGroup.template = renderTemplate("<#= d.tagName # #= d.renderId() #" + "#= d.renderDataAttributes() #" + "style='position: absolute; white-space: nowrap;'>" + "#= d.renderContent() #</#= d.tagName #>");
}
}
});
var VMLClipRect = ViewElement.extend({
init: function(box, options) {
var clipRect = this;
ViewElement.fn.init.call(clipRect, options);
clipRect.tagName = clipRect.options.inline ? "span" : "div";
clipRect.template = VMLClipRect.template;
clipRect.clipTemplate = VMLClipRect.clipTemplate;
if (!clipRect.template) {
clipRect.template = VMLClipRect.template = renderTemplate("<#= d.tagName # #= d.renderId() #" + "style='position:absolute; " + "width:#= d.box.width() #px; height:#= d.box.height() #px; " + "top:#= d.box.y1 #px; " + "left:#= d.box.x1 #px; " + "clip:#= d._renderClip() #;' >" + "#= d.renderContent() #</#= d.tagName #>");
clipRect.clipTemplate = VMLClipRect.clipTemplate = renderTemplate("rect(#= d.points[0].y #px #= d.points[1].x #px " + "#= d.points[2].y #px #= d.points[0].x #px)");
}
clipRect.box = box;
// Points defining the clipping rectangle
clipRect.points = box.points();
},
clone: function() {
var clipRect = this;
return new VMLClipRect(clipRect.box, deepExtend({}, clipRect.options));
},
refresh: function(domElement) {
if (domElement) {
domElement.style.clip = this._renderClip();
}
},
_renderClip: function() {
return this.clipTemplate(this);
},
destroy: function() {
$("#" + this.options.id + ">*").unwrap();
}
});
var VMLGradient = ViewElement.extend({
init: function(options) {
var gradient = this;
ViewElement.fn.init.call(gradient, options);
},
options: {
opacity: 1
},
renderColors: function() {
var gradient = this, options = gradient.options, stops = options.stops, currentStop, i, length = stops.length, output = [], round = math.round;
for (i = 0; i < length; i++) {
currentStop = stops[i];
output.push(round(currentStop.offset * 100) + "% " + currentStop.color);
}
return output.join(",");
}
});
var VMLLinearGradient = VMLGradient.extend({
init: function(options) {
var gradient = this;
VMLGradient.fn.init.call(gradient, options);
gradient.template = VMLLinearGradient.template;
if (!gradient.template) {
gradient.template = VMLLinearGradient.template = renderTemplate("<kvml:fill type='gradient' angle='#= 270 - d.options.rotation #' " + "colors='#= d.renderColors() #' opacity='#= d.options.opacity #' />");
}
},
options: {
rotation: 0
}
});
var VMLRadialGradient = VMLGradient.extend({
init: function(options) {
var gradient = this;
VMLGradient.fn.init.call(gradient, options);
gradient.template = VMLRadialGradient.template;
if (!gradient.template) {
gradient.template = VMLRadialGradient.template = renderTemplate("<kvml:fill type='gradienttitle' focus='100%' focusposition='#= d.focusPosition() #'" + "colors='#= d.renderColors() #' color='#= d.firstColor() #' color2='#= d.lastColor() #' opacity='#= d.options.opacity #' />");
}
},
focusPosition: function() {
var options = this.options, bbox = options.bbox, cx = options.cx, cy = options.cy, focusx = Math.max(0, Math.min(1, (cx - bbox.x1) / bbox.width())), focusy = Math.max(0, Math.min(1, (cy - bbox.y1) / bbox.height()));
return round(focusx, COORD_PRECISION) + " " + round(focusy, COORD_PRECISION);
},
firstColor: function() {
var stops = this.options.stops;
return stops[0].color;
},
lastColor: function() {
var stops = this.options.stops;
return stops[stops.length - 1].color;
}
});
// Decorators =============================================================
function VMLOverlayDecorator(view) {
this.view = view;
}
VMLOverlayDecorator.prototype = {
decorate: function(element) {
var options = element.options, view = this.view, overlay, bbox;
if (options.overlay) {
bbox = options.overlay.bbox;
overlay = view.buildGradient(deepExtend({}, options.overlay, {
// Make the gradient definition unique for this color
_overlayFill: options.fill,
// and for the radial gradient bounding box, if specified
_bboxHash: defined(bbox) ? bbox.getHash() : ""
}));
}
if (!overlay) {
return element;
}
delete options.overlay;
options.fill = deepExtend(blendGradient(options.fill, overlay), {
opacity: options.fillOpacity
});
return element;
}
};
function VMLGradientDecorator(view) {
this.view = view;
}
VMLGradientDecorator.prototype = {
decorate: function(element) {
var decorator = this, view = decorator.view, options = element.options, fill = options.fill;
if (fill && fill.supportVML !== false) {
if (fill.gradient) {
fill = view.buildGradient(fill);
}
if (typeof fill === OBJECT) {
element.fill = view.createGradient(fill);
}
}
return element;
}
};
var VMLClipAnimationDecorator = Class.extend({
init: function(view) {
this.view = view;
},
decorate: function(element) {
var decorator = this, view = decorator.view, options = view.options, animation = element.options.animation, clipRect;
if (animation && animation.type === CLIP && options.transitions) {
clipRect = new VMLClipRect(new Box2D(0, 0, options.width, options.height), {
id: uniqueId(),
inline: options.inline
});
view.animations.push(new ExpandAnimation(clipRect, {
size: options.width
}));
clipRect.children.push(element);
return clipRect;
} else {
return element;
}
}
});
// Helpers ================================================================
function isIE9CompatibilityView() {
return kendo.support.browser.msie && !supportsSVG() && typeof window.performance !== "undefined";
}
function blendColors(base, overlay, alpha) {
var baseColor = new Color(base), overlayColor = new Color(overlay), r = blendChannel(baseColor.r, overlayColor.r, alpha), g = blendChannel(baseColor.g, overlayColor.g, alpha), b = blendChannel(baseColor.b, overlayColor.b, alpha);
return new Color(r, g, b).toHex();
}
function blendChannel(a, b, alpha) {
return math.round(alpha * b + (1 - alpha) * a);
}
function blendGradient(color, gradient) {
var srcStops = gradient.stops, stopsLength = srcStops.length, result = deepExtend({}, gradient), i, stop, resultStop;
result.stops = [];
for (i = 0; i < stopsLength; i++) {
stop = srcStops[i];
resultStop = result.stops[i] = deepExtend({}, srcStops[i]);
resultStop.color = blendColors(color, stop.color, stop.opacity);
resultStop.opacity = 0;
}
return result;
}
// Exports ================================================================
deepExtend(dataviz, {
VMLCircle: VMLCircle,
VMLClipAnimationDecorator: VMLClipAnimationDecorator,
VMLClipRect: VMLClipRect,
VMLFill: VMLFill,
VMLGroup: VMLGroup,
VMLLine: VMLLine,
VMLLinearGradient: VMLLinearGradient,
VMLOverlayDecorator: VMLOverlayDecorator,
VMLPath: VMLPath,
VMLRadialGradient: VMLRadialGradient,
VMLRing: VMLRing,
VMLRotatedText: VMLRotatedText,
VMLSector: VMLSector,
VMLStroke: VMLStroke,
VMLText: VMLText,
VMLView: VMLView,
blendColors: blendColors,
blendGradient: blendGradient
});
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Widget = kendo.ui.Widget, proxy = $.proxy, abs = Math.abs, MAX_DOUBLE_TAP_DISTANCE = 20;
var Swipe = kendo.Class.extend({
init: function(element, callback, options) {
options = $.extend({
minXDelta: 30,
maxYDelta: 20,
maxDuration: 1e3
}, options);
new kendo.UserEvents(element, {
surface: options.surface,
allowSelection: true,
start: function(e) {
if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
e.sender.capture();
}
},
move: function(e) {
var touch = e.touch, duration = e.event.timeStamp - touch.startTime, direction = touch.x.initialDelta > 0 ? "right" : "left";
if (abs(touch.x.initialDelta) >= options.minXDelta && abs(touch.y.initialDelta) < options.maxYDelta && duration < options.maxDuration) {
callback({
direction: direction,
touch: touch,
target: touch.target
});
touch.cancel();
}
}
});
}
});
var Touch = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
options = that.options;
element = that.element;
function eventProxy(name) {
return function(e) {
that._triggerTouch(name, e);
};
}
function gestureEventProxy(name) {
return function(e) {
that.trigger(name, {
touches: e.touches,
distance: e.distance,
center: e.center,
event: e.event
});
};
}
that.events = new kendo.UserEvents(element, {
filter: options.filter,
surface: options.surface,
multiTouch: options.multiTouch,
allowSelection: true,
press: proxy(that, "_touchstart"),
tap: proxy(that, "_tap"),
gesturestart: gestureEventProxy("gesturestart"),
gesturechange: gestureEventProxy("gesturechange"),
gestureend: gestureEventProxy("gestureend")
});
if (options.enableSwipe) {
that.events.bind("start", proxy(that, "_swipestart"));
that.events.bind("move", proxy(that, "_swipemove"));
} else {
that.events.bind("start", proxy(that, "_dragstart"));
that.events.bind("move", eventProxy("drag"));
that.events.bind("end", eventProxy("dragend"));
}
kendo.notify(that);
},
events: [ "touchstart", "dragstart", "drag", "dragend", "tap", "doubletap", "hold", "swipe", "gesturestart", "gesturechange", "gestureend" ],
options: {
name: "Touch",
surface: null,
global: false,
multiTouch: false,
enableSwipe: false,
minXDelta: 30,
maxYDelta: 20,
maxDuration: 1e3,
minHold: 800,
doubleTapTimeout: 800
},
_cancelHold: function() {
clearTimeout(this.holdTimeout);
},
_triggerTouch: function(type, e) {
this.trigger(type, {
touch: e.touch,
event: e.event
});
},
_touchstart: function(e) {
var that = this;
that._triggerTouch("touchstart", e);
that._cancelHold();
that.holdTimeout = setTimeout(function() {
that._triggerTouch("hold", e);
}, that.options.minHold);
},
_tap: function(e) {
var that = this, lastTap = that.lastTap, touch = e.touch;
that._cancelHold();
if (lastTap && touch.endTime - lastTap.endTime < that.options.doubleTapTimeout && kendo.touchDelta(touch, lastTap).distance < MAX_DOUBLE_TAP_DISTANCE) {
that._triggerTouch("doubletap", e);
that.lastTap = null;
} else {
that._triggerTouch("tap", e);
that.lastTap = touch;
}
},
_dragstart: function(e) {
this._cancelHold();
this._triggerTouch("dragstart", e);
},
_swipestart: function(e) {
this._cancelHold();
if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
e.sender.capture();
}
},
_swipemove: function(e) {
var that = this, options = that.options, touch = e.touch, duration = e.event.timeStamp - touch.startTime, direction = touch.x.initialDelta > 0 ? "right" : "left";
that._cancelHold();
if (abs(touch.x.initialDelta) >= options.minXDelta && abs(touch.y.initialDelta) < options.maxYDelta && duration < options.maxDuration) {
that.trigger("swipe", {
direction: direction,
touch: e.touch
});
touch.cancel();
}
}
});
window.jQuery.fn.kendoMobileSwipe = function(callback, options) {
this.each(function() {
new Swipe(this, callback, options);
});
};
kendo.ui.plugin(Touch);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, SHOW = "show", HIDE = "hide", OPEN = "open", CLOSE = "close", WRAPPER = '<div class="km-popup-wrapper" />', ARROW = '<div class="km-popup-arrow" />', OVERLAY = '<div class="km-popup-overlay" />', DIRECTION_CLASSES = "km-up km-down km-left km-right", Widget = ui.Widget, DIRECTIONS = {
down: {
origin: "bottom center",
position: "top center"
},
up: {
origin: "top center",
position: "bottom center"
},
left: {
origin: "center left",
position: "center right",
collision: "fit flip"
},
right: {
origin: "center right",
position: "center left",
collision: "fit flip"
}
}, ANIMATION = {
animation: {
open: {
effects: "fade:in",
duration: 0
},
close: {
effects: "fade:out",
duration: 400
}
}
}, DIMENSIONS = {
horizontal: {
offset: "top",
size: "height"
},
vertical: {
offset: "left",
size: "width"
}
}, REVERSE = {
up: "down",
down: "up",
left: "right",
right: "left"
};
var Popup = Widget.extend({
init: function(element, options) {
var that = this, container = mobile.application.element, popupOptions = {
viewport: kendo.mobile.application.element,
open: function() {
that.overlay.show();
},
activate: $.proxy(that._activate, that),
deactivate: function() {
that.overlay.hide();
that.trigger(HIDE);
}
}, axis;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
element.wrap(WRAPPER).addClass("km-popup").show();
axis = that.options.direction.match(/left|right/) ? "horizontal" : "vertical";
that.dimensions = DIMENSIONS[axis];
that.wrapper = element.parent().css({
width: options.width,
height: options.height
}).addClass("km-popup-wrapper km-" + options.direction).hide();
that.arrow = $(ARROW).prependTo(that.wrapper).hide();
that.overlay = $(OVERLAY).appendTo(container).hide();
popupOptions.appendTo = that.overlay;
that.popup = new kendo.ui.Popup(that.wrapper, $.extend(true, popupOptions, ANIMATION, DIRECTIONS[options.direction]));
},
options: {
name: "Popup",
width: 240,
height: 320,
direction: "down"
},
events: [ SHOW, HIDE ],
show: function(target) {
var that = this, popup = that.popup;
popup.options.anchor = $(target);
popup.open();
},
target: function() {
return this.popup.options.anchor;
},
hide: function() {
this.popup.close();
},
destroy: function() {
Widget.fn.destroy.call(this);
this.popup.destroy();
},
_activate: function() {
var that = this, direction = that.options.direction, dimensions = that.dimensions, offset = dimensions.offset, popup = that.popup, anchor = popup.options.anchor, anchorOffset = $(anchor).offset(), elementOffset = $(popup.element).offset(), cssClass = popup.flipped ? REVERSE[direction] : direction, offsetAmount = anchorOffset[offset] - elementOffset[offset] + $(anchor)[dimensions.size]() / 2;
that.wrapper.removeClass(DIRECTION_CLASSES).addClass("km-" + cssClass);
that.arrow.css(offset, offsetAmount).show();
that.trigger(SHOW);
}
});
var PopOver = Widget.extend({
init: function(element, options) {
var that = this, popupOptions;
that.initialOpen = false;
Widget.fn.init.call(that, element, options);
options = that.options;
popupOptions = $.extend({
show: function() {
that.trigger(OPEN, {
target: that.popup.target()
});
},
hide: function() {
that.trigger(CLOSE);
}
}, this.options.popup);
that.popup = new Popup(that.element, popupOptions);
that.pane = new ui.Pane(that.element, this.options.pane);
kendo.notify(that, ui);
},
options: {
name: "PopOver",
popup: {},
pane: {}
},
events: [ OPEN, CLOSE ],
open: function(target) {
this.openFor(target);
},
openFor: function(target) {
this.popup.show(target);
if (!this.initialOpen) {
this.pane.navigate("");
this.initialOpen = true;
}
},
close: function() {
this.popup.hide();
},
destroy: function() {
Widget.fn.destroy.call(this);
this.pane.destroy();
this.popup.destroy();
kendo.destroy(this.element);
}
});
ui.plugin(Popup);
ui.plugin(PopOver);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, CAPTURE_EVENTS = $.map(kendo.eventMap, function(value) {
return value;
}).join(" ").split(" ");
var Loader = Widget.extend({
init: function(container, options) {
var that = this, element = $('<div class="km-loader"><span class="km-loading km-spin"></span></div>');
Widget.fn.init.call(that, element, options);
that.container = container;
that._attachCapture();
element.append(that.options.loading).hide().appendTo(container);
},
options: {
name: "Loader",
loading: "<h1>Loading...</h1>",
timeout: 100
},
show: function() {
var that = this;
clearTimeout(that._loading);
if (that.options.loading === false) {
return;
}
that._loading = setTimeout(function() {
that.element.show();
}, that.options.timeout);
},
hide: function() {
var that = this;
clearTimeout(that._loading);
that.element.hide();
},
transition: function() {
this.transitioning = true;
this.container.css("pointer-events", "none");
},
transitionDone: function() {
this.transitioning = false;
this.container.css("pointer-events", "");
},
_attachCapture: function() {
var that = this;
that.transitioning = false;
function capture(e) {
if (that.transitioning) {
e.stopPropagation();
}
}
for (var i = 0; i < CAPTURE_EVENTS.length; i++) {
that.container[0].addEventListener(CAPTURE_EVENTS[i], capture, true);
}
}
});
ui.plugin(Loader);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Popup = kendo.ui.Popup, SHIM = '<div class="km-shim"/>', Widget = ui.Widget;
var Shim = Widget.extend({
init: function(element, options) {
var that = this, app = kendo.mobile.application, osname = app ? app.os.name : kendo.support.mobileOS.name, ioswp = osname === "ios" || osname === "wp", align = options.align || (ioswp ? "bottom center" : "center center"), position = options.position || (ioswp ? "bottom center" : "center center"), effect = options.effect || (ioswp ? "slideIn:up" : "fade:in"), shim = $(SHIM).handler(that).hide();
Widget.fn.init.call(that, element, options);
that.shim = shim;
that.element = element;
if (!that.options.modal) {
that.shim.on("up", "hide");
}
(app ? app.element : $(document.body)).append(shim);
that.popup = new Popup(that.element, {
anchor: shim,
appendTo: shim,
origin: align,
position: position,
animation: {
open: {
effects: effect,
duration: that.options.duration
},
close: {
duration: that.options.duration
}
},
deactivate: function() {
shim.hide();
},
open: function() {
shim.show();
}
});
kendo.notify(that);
},
options: {
name: "Shim",
modal: true,
align: undefined,
position: undefined,
effect: undefined,
duration: 200
},
show: function() {
this.popup.open();
},
hide: function(e) {
if (!e || !$.contains(this.shim[0], e.target)) {
this.popup.close();
}
},
destroy: function() {
Widget.fn.destroy.call(this);
this.shim.kendoDestroy();
this.popup.destroy();
}
});
ui.plugin(Shim);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, attr = kendo.attr, Class = kendo.Class, Widget = ui.Widget, INIT = "init", SHOW = "show", BEFORE_SHOW = "beforeShow", AFTER_SHOW = "afterShow", HIDE = "hide", Z_INDEX = "z-index", attrValue = kendo.attrValue, roleSelector = kendo.roleSelector;
function initPopOvers(element) {
var popovers = element.find(roleSelector("popover")), idx, length, roles = ui.roles;
for (idx = 0, length = popovers.length; idx < length; idx++) {
kendo.initWidget(popovers[idx], {}, roles);
}
}
var View = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
that.params = {};
that.lastParams = {};
$.extend(that, options);
that._layout();
that._scroller();
that._model();
},
events: [ INIT, BEFORE_SHOW, SHOW, AFTER_SHOW, HIDE ],
options: {
name: "View",
title: "",
reload: false,
defaultTransition: "",
useNativeScrolling: false,
stretch: false,
zoom: false,
model: null
},
destroy: function() {
Widget.fn.destroy.call(this);
if (this.scroller) {
this.scroller.destroy();
}
kendo.destroy(this.element);
},
purge: function() {
this.destroy();
this.element.remove();
},
showStart: function() {
var that = this;
that.element.css("display", "");
if (!that.inited) {
that.inited = true;
that.trigger(INIT, {
view: that
});
}
if (that.layout) {
that.layout.attach(that);
}
that.trigger(SHOW, {
view: that
});
},
hideStart: function() {
var that = this;
if (that.layout) {
that.layout.detach(that);
}
},
hideComplete: function() {
var that = this;
that.element.hide();
that.trigger(HIDE, {
view: that
});
},
updateParams: function(params) {
var that = this;
if (that.trigger(BEFORE_SHOW, {
view: that
})) {
return;
}
that.lastParams = that.params;
that.params = params;
that.trigger(SHOW, {
view: that
});
},
switchWith: function(view, transition, params, callback) {
var that = this, complete = function() {
that.trigger(AFTER_SHOW, {
view: that
});
callback();
};
if (that.trigger(BEFORE_SHOW, {
view: that
})) {
return;
}
that.lastParams = that.params;
that.params = params;
if (view) {
// layout needs to be detached first, then reattached
view.hideStart();
that.showStart();
new ViewTransition({
current: view,
next: that,
transition: transition,
defaultTransition: view.options.defaultTransition,
complete: complete
});
} else {
that.showStart();
complete();
}
},
parallaxContents: function(other) {
var that = this, contents = that.content;
if (!other.header[0]) {
contents = contents.add(that.header);
}
if (!other.footer[0]) {
contents = contents.add(that.footer);
}
return contents;
},
contentElement: function() {
var that = this;
return that.options.stretch ? that.content : that.scrollerContent;
},
_scroller: function() {
var that = this;
if (that.options.stretch) {
that.content.addClass("km-stretched-view");
} else {
that.content.kendoMobileScroller({
zoom: that.options.zoom,
useNative: that.options.useNativeScrolling
});
that.scroller = that.content.data("kendoMobileScroller");
that.scrollerContent = that.scroller.scrollElement;
}
// prevent accidental address bar display when pulling the header
if (kendo.support.kineticScrollNeeded) {
$(that.element).on("touchmove", ".km-header", kendo.preventDefault);
}
},
_model: function() {
var that = this, element = that.element, model = that.options.model;
if (typeof model === "string") {
model = kendo.getter(model)(window);
}
that.model = model;
initPopOvers(element);
if (model) {
kendo.bind(element.children(), model, ui, kendo.ui, kendo.dataviz.ui);
} else {
mobile.init(element.children());
}
},
_layout: function() {
var that = this, contentSelector = roleSelector("content"), element = that.element;
element.data("kendoView", that).addClass("km-view");
that.transition = attrValue(element, "transition");
that.header = element.children(roleSelector("header")).addClass("km-header");
that.footer = element.children(roleSelector("footer")).addClass("km-footer");
if (!element.children(contentSelector)[0]) {
element.wrapInner("<div " + attr("role") + '="content"></div>');
}
that.content = element.children(roleSelector("content")).addClass("km-content");
that.element.prepend(that.header).append(that.footer);
that.id = attrValue(element, "url") || "#" + element.attr("id");
if (that.layout) {
that.layout.setup(that);
}
}
});
function fade(source, destination) {
if (source[0] && destination[0] && source[0] != destination[0]) {
source.kendoAnimateTo(destination, {
effects: "fade"
});
}
}
function initWidgets(collection) {
collection.each(function() {
kendo.initWidget($(this), {}, ui.roles);
});
}
var ViewTransition = Class.extend({
init: function(options) {
$.extend(this, options);
var that = this, current = that.current, next = that.next, currentContent = current.element, nextContent = next.element, upper = next, lower = current, transition = that._transition();
if (transition.reverse && !transition.parallax) {
upper = current;
lower = next;
}
upper.element.css(Z_INDEX, 1);
lower.element.css(Z_INDEX, 0);
if (transition.parallax) {
fade(current.footer, next.footer);
fade(current.header, next.header);
currentContent = current.parallaxContents(next);
nextContent = next.parallaxContents(current);
}
currentContent.kendoAnimateTo(nextContent, transition);
if (!that.back()) {
current.nextViewID = next.id;
current.backTransition = transition.transition;
}
},
_transition: function() {
var that = this, current = that.current, next = that.next, back = that.back(), complete = function() {
current.hideComplete();
that.complete();
}, viewTransition = back ? next.backTransition : next.transition, transition = that.transition || viewTransition || that.defaultTransition, animationData = transition.split(" "), animationType = animationData[0], parallax = /^slide/.test(animationType), reverse = animationData[1] === "reverse";
if (that.back() && !that.transition) {
reverse = !reverse;
}
return {
effects: animationType,
reverse: reverse,
parallax: parallax,
complete: complete,
transition: transition
};
},
back: function() {
var next = this.next, current = this.current;
return next.nextViewID && next.nextViewID === current.id && JSON.stringify(next.params) === JSON.stringify(next.lastParams);
}
});
var Layout = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
that.element = element.detach();
that.header = element.children(roleSelector("header")).addClass("km-header");
that.footer = element.children(roleSelector("footer")).addClass("km-footer");
that.elements = that.header.add(that.footer);
initPopOvers(element);
kendo.mobile.init(that.element.children());
that.trigger(INIT, {
layout: that
});
},
options: {
name: "Layout"
},
events: [ INIT, SHOW, HIDE ],
setup: function(view) {
if (!view.header[0]) {
view.header = this.header;
}
if (!view.footer[0]) {
view.footer = this.footer;
}
},
detach: function(view) {
var that = this;
if (view.header === that.header) {
view.element.prepend(that.header.detach().clone(true));
}
if (view.footer === that.footer) {
view.element.append(that.footer.detach().clone(true));
}
that.trigger(HIDE, {
layout: that,
view: view
});
},
attach: function(view) {
var that = this;
if (view.header === that.header) {
that.header.detach();
view.element.children(roleSelector("header")).remove();
view.element.prepend(that.header);
}
if (view.footer === that.footer) {
that.footer.detach();
view.element.children(roleSelector("footer")).remove();
view.element.append(that.footer);
}
that.trigger(SHOW, {
layout: that,
view: view
});
}
});
var Observable = kendo.Observable, BODY_REGEX = /<body[^>]*>(([\u000a\u000d\u2028\u2029]|.)*)<\/body>/i, LOAD_START = "loadStart", LOAD_COMPLETE = "loadComplete", SHOW_START = "showStart", VIEW_SHOW = "viewShow";
function urlParams(url) {
var queryString = url.split("?")[1] || "", params = {}, paramParts = queryString.split(/&|=/), length = paramParts.length, idx = 0;
for (;idx < length; idx += 2) {
params[paramParts[idx]] = paramParts[idx + 1];
}
return params;
}
var ViewEngine = Observable.extend({
init: function(options) {
var that = this, views, container;
Observable.fn.init.call(that);
$.extend(that, options);
that.sandbox = $("<div />");
container = that.container;
views = that._hideViews(container);
that.rootView = views.first();
if (!that.rootView[0]) {
throw new Error('Your kendo mobile application element does not contain any direct child elements with data-role="view" attribute set. Make sure that you instantiate the mobile application using the correct container.');
}
that._view = null;
that.layouts = {};
that._setupLayouts(container);
initWidgets(container.children(roleSelector("modalview")));
if (that.loader) {
that.bind(SHOW_START, function() {
that.loader.transition();
});
that.bind(LOAD_START, function() {
that.loader.show();
});
that.bind(LOAD_COMPLETE, function() {
that.loader.hide();
});
that.bind(VIEW_SHOW, function() {
that.loader.transitionDone();
});
}
},
view: function() {
return this._view;
},
showView: function(url, transition) {
if (url === this.url) {
return;
}
this.trigger(SHOW_START);
var that = this, showClosure = function(view) {
that._show(view, transition, urlParams(url));
}, element = that._findViewElement(url), view = element.data("kendoView");
that.url = url;
if (view && view.reload) {
view.purge();
element = [];
}
this.trigger("viewTypeDetermined", {
remote: element.length === 0,
url: url
});
if (element[0]) {
if (!view) {
view = that._createView(element);
}
showClosure(view);
} else {
that._loadView(url, showClosure);
}
},
_findViewElement: function(url) {
var element, urlPath = url.split("?")[0];
if (!url || url === "/") {
return this.rootView;
}
element = this.container.children("[" + attr("url") + "='" + urlPath + "']");
// do not try to search for "#/foo/bar" id, jQuery throws error
if (!element[0] && urlPath.indexOf("/") === -1) {
element = this.container.children(urlPath.charAt(0) === "#" ? urlPath : "#" + urlPath);
}
return element;
},
_createView: function(element) {
var that = this, viewOptions, layout = attrValue(element, "layout");
if (typeof layout === "undefined") {
layout = that.layout;
}
if (layout) {
layout = that.layouts[layout];
}
viewOptions = {
defaultTransition: that.transition,
loader: that.loader,
container: that.container,
layout: layout,
reload: attrValue(element, "reload")
};
return kendo.initWidget(element, viewOptions, ui.roles);
},
_loadView: function(url, callback) {
var that = this;
if (this.serverNavigation) {
location.href = url;
return;
}
if (that._xhr) {
that._xhr.abort();
}
that.trigger(LOAD_START);
that._xhr = $.get(url, function(html) {
that.trigger(LOAD_COMPLETE);
callback(that._createRemoteView(url, html));
}, "html").fail(function(request) {
that.trigger(LOAD_COMPLETE);
if (request.status === 0 && request.responseText) {
callback(that._createRemoteView(url, request.responseText));
}
});
},
_createRemoteView: function(url, html) {
var that = this, sandbox = that.sandbox, urlPath = url.split("?")[0], container = that.container, views, modalViews, view;
if (BODY_REGEX.test(html)) {
html = RegExp.$1;
}
sandbox[0].innerHTML = html;
container.append(sandbox.children("script, style"));
views = that._hideViews(sandbox);
view = views.first();
// Generic HTML content found as remote view - no remote view markers
if (!view.length) {
views = view = sandbox.wrapInner("<div data-role=view />").children();
}
view.hide().attr(attr("url"), urlPath);
that._setupLayouts(sandbox);
modalViews = sandbox.children(roleSelector("modalview"));
container.append(sandbox.children(roleSelector("layout modalview")).add(views));
// Initialize the modalviews after they have been appended to the final container
initWidgets(modalViews);
return that._createView(view);
},
_show: function(view, transition, params) {
var that = this;
if (that._view !== view) {
view.switchWith(that._view, transition, params, function() {
that._view = view;
that.trigger(VIEW_SHOW, {
view: view
});
});
} else {
that._view.updateParams(params);
that.trigger(VIEW_SHOW, {
view: view
});
}
},
_hideViews: function(container) {
return container.children(roleSelector("view splitview")).hide();
},
_setupLayouts: function(element) {
var that = this;
element.children(roleSelector("layout")).each(function() {
var layout = $(this), platform = attrValue(layout, "platform");
if (platform === undefined || platform === mobile.application.os.name) {
that.layouts[kendo.attrValue(layout, "id")] = kendo.initWidget(layout, {}, ui.roles);
}
});
}
});
kendo.mobile.ViewEngine = ViewEngine;
ui.plugin(View);
ui.plugin(Layout);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Shim = ui.Shim, Widget = ui.Widget, OPEN = "open", CLOSE = "close", INIT = "init", WRAP = '<div class="km-modalview-wrapper" />';
var ModalView = ui.View.extend({
init: function(element, options) {
var that = this, width, height;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
width = element[0].style.width || element.css("width");
height = element[0].style.height || element.css("height");
element.addClass("km-modalview").wrap(WRAP);
that.wrapper = element.parent().css({
width: options.width || width || 300,
height: options.height || height || 300
});
element.css({
width: "",
height: ""
});
that.shim = new Shim(that.wrapper, {
modal: options.modal,
position: "center center",
align: "center center",
effect: "fade:in"
});
that._layout();
that._scroller();
that._model();
that.trigger(INIT);
kendo.onResize(function() {
var positionedElement = that.wrapper.parent(), viewPort = positionedElement.parent();
positionedElement.css({
top: (viewPort.height() - positionedElement.height()) / 2 + "px",
left: (viewPort.width() - positionedElement.width()) / 2 + "px"
});
});
},
events: [ INIT, OPEN, CLOSE ],
options: {
name: "ModalView",
modal: true,
width: null,
height: null
},
destroy: function() {
Widget.fn.destroy.call(this);
this.shim.destroy();
},
open: function(target) {
var that = this;
that.target = $(target);
that.shim.show();
that.trigger("show", {
view: that
});
},
// Interface implementation, called from the pane click handlers
openFor: function(target) {
this.open(target);
this.trigger(OPEN, {
target: target
});
},
close: function() {
this.shim.hide();
this.trigger(CLOSE);
}
});
ui.plugin(ModalView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, View = ui.View;
var SplitView = View.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
$.extend(that, options);
that._layout();
that._style();
kendo.mobile.init(element.children(kendo.roleSelector("modalview")));
that.panes = [];
that.element.children(kendo.roleSelector("pane")).each(function() {
that.panes.push(kendo.initWidget(this, {}, ui.roles));
});
},
options: {
name: "SplitView",
style: "horizontal"
},
// Implement view interface
_layout: function() {
var that = this, element = that.element;
element.data("kendoView", that).addClass("km-view km-splitview");
that.transition = kendo.attrValue(element, "transition");
$.extend(that, {
header: [],
footer: [],
content: element
});
},
_style: function() {
var style = this.options.style, element = this.element, styles;
if (style) {
styles = style.split(" ");
$.each(styles, function() {
element.addClass("km-split-" + this);
});
}
},
showStart: function() {
var that = this;
that.element.css("display", "");
if (!that.inited) {
that.inited = true;
$.each(that.panes, function() {
this.navigate("");
});
that.trigger("init", {
view: that
});
}
that.trigger("show", {
view: that
});
}
});
ui.plugin(SplitView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, roleSelector = kendo.roleSelector, ui = mobile.ui, Widget = ui.Widget, ViewEngine = mobile.ViewEngine, Loader = mobile.ui.Loader, EXTERNAL = "external", HREF = "href", DUMMY_HREF = "#!", NAVIGATE = "navigate", VIEW_SHOW = "viewShow", WIDGET_RELS = /popover|actionsheet|modalview/, BACK = "#:back", attrValue = kendo.attrValue, // navigation element roles
buttonRoles = "button backbutton detailbutton listview-link", linkRoles = "tab";
function appLinkClick(e) {
if (attrValue($(e.currentTarget), "rel") != EXTERNAL) {
e.preventDefault();
}
}
var Pane = Widget.extend({
init: function(element, options) {
var that = this, initial;
Widget.fn.init.call(that, element, options);
element = that.element;
element.addClass("km-pane");
that.history = [];
that.loader = new Loader(element, {
loading: that.options.loading
});
that.viewEngine = new ViewEngine({
container: element,
transition: that.options.transition,
serverNavigation: that.options.serverNavigation,
layout: that.options.layout,
loader: that.loader
});
that.viewEngine.bind(VIEW_SHOW, function(e) {
that.trigger(VIEW_SHOW, e);
});
that.viewEngine.bind("viewTypeDetermined", function(e) {
if (!e.remote || !that.options.serverNavigation) {
that.trigger(NAVIGATE, {
url: e.url
});
}
});
initial = that.options.initial;
if (initial) {
that.navigate(initial);
}
that._setupAppLinks();
},
options: {
name: "Pane",
transition: "",
layout: "",
initial: undefined,
loading: undefined
},
events: [ NAVIGATE, VIEW_SHOW ],
destroy: function() {
Widget.fn.destroy.call(this);
kendo.destroy(this.element);
},
navigate: function(url, transition) {
var that = this, history = that.history;
if (url === BACK) {
history.pop();
url = history[history.length - 1];
} else {
that.history.push(url);
}
that.viewEngine.showView(url, transition);
},
hideLoading: function() {
this.loader.hide();
},
showLoading: function() {
this.loader.show();
},
view: function() {
return this.viewEngine.view();
},
_setupAppLinks: function() {
this.element.handler(this).on("down", roleSelector(linkRoles), "_mouseup").on("up", roleSelector(buttonRoles), "_mouseup").on("click", roleSelector(linkRoles + " " + buttonRoles), appLinkClick);
},
_mouseup: function(e) {
if (e.which > 1 || e.isDefaultPrevented()) {
return;
}
var link = $(e.currentTarget), transition = attrValue(link, "transition"), rel = attrValue(link, "rel") || "", target = attrValue(link, "target"), pane = this, href = link.attr(HREF);
if (rel === EXTERNAL || typeof href === "undefined" || href === DUMMY_HREF) {
return;
}
// Prevent iOS address bar progress display for in app navigation
link.attr(HREF, DUMMY_HREF);
setTimeout(function() {
link.attr(HREF, href);
});
if (rel.match(WIDGET_RELS)) {
kendo.widgetInstance($(href), ui).openFor(link);
e.stopPropagation();
} else {
if (target === "_top") {
pane = mobile.application.pane;
} else if (target) {
pane = $("#" + target).data("kendoMobilePane");
}
pane.navigate(href, transition);
}
e.preventDefault();
}
});
ui.plugin(Pane);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, support = kendo.support, Pane = mobile.ui.Pane, DEFAULT_OS = "ios", OS = support.mobileOS, BERRYPHONEGAP = OS.device == "blackberry" && OS.flatVersion >= 600 && OS.flatVersion < 1e3 && OS.appMode, VERTICAL = "km-vertical", HORIZONTAL = "km-horizontal", MOBILE_PLATFORMS = {
ios: {
ios: true,
appMode: false,
browser: "default",
device: "iphone",
flatVersion: "612",
majorVersion: "6",
minorVersion: "1.2",
name: "ios",
tablet: false
},
android: {
android: true,
appMode: false,
browser: "default",
device: "android",
flatVersion: "233",
majorVersion: "2",
minorVersion: "3.3",
name: "android",
tablet: false
},
blackberry: {
blackberry: true,
appMode: false,
browser: "default",
device: "blackberry",
flatVersion: "710",
majorVersion: "7",
minorVersion: "1.0",
name: "blackberry",
tablet: false
},
meego: {
meego: true,
appMode: false,
browser: "default",
device: "meego",
flatVersion: "850",
majorVersion: "8",
minorVersion: "5.0",
name: "meego",
tablet: false
},
wp: {
wp: true,
appMode: false,
browser: "default",
device: "wp",
flatVersion: "800",
majorVersion: "8",
minorVersion: "0.0",
name: "wp",
tablet: false
}
}, viewportTemplate = kendo.template('<meta content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no#=data.height#" name="viewport" />', {
usedWithBlock: false
}), systemMeta = '<meta name="apple-mobile-web-app-capable" content="yes" /> ' + '<meta name="apple-mobile-web-app-status-bar-style" content="black" /> ' + '<meta name="msapplication-tap-highlight" content="no" /> ', viewportMeta = viewportTemplate({
height: ""
}), iconMeta = kendo.template('<link rel="apple-touch-icon' + (OS.android ? "-precomposed" : "") + '" # if(data.size) { # sizes="#=data.size#" #}# href="#=data.icon#" />', {
usedWithBlock: false
}), HIDEBAR = (OS.device == "iphone" || OS.device == "ipod") && OS.browser == "mobilesafari", BARCOMPENSATION = 60, WINDOW = $(window), HEAD = $("head"), proxy = $.proxy;
function osCssClass(os) {
var classes = [];
classes.push("km-" + os.name);
if (OS) {
classes.push("km-on-" + OS.name);
}
classes.push("km-" + os.name + os.majorVersion);
classes.push("km-" + os.majorVersion);
classes.push("km-m" + (os.minorVersion ? os.minorVersion[0] : 0));
if (os.appMode) {
classes.push("km-app");
} else {
classes.push("km-web");
}
return classes.join(" ");
}
function isOrientationHorizontal(element) {
return OS.wp ? element.css("animation-name") == "-kendo-landscape" : Math.abs(window.orientation) / 90 == 1;
}
function getOrientationClass(element) {
return isOrientationHorizontal(element) ? HORIZONTAL : VERTICAL;
}
function applyViewportHeight() {
$("meta[name=viewport]").remove();
HEAD.append(viewportTemplate({
height: isOrientationHorizontal() ? ", height=" + window.innerHeight + "px" : support.mobileOS.flatVersion >= 600 && support.mobileOS.flatVersion < 700 ? ", height=" + window.innerWidth + "px" : ", height=device-height"
}));
}
var Application = kendo.Observable.extend({
init: function(element, options) {
var that = this;
mobile.application = that;
// global reference to current application
that.options = $.extend({
hideAddressBar: true,
transition: "",
updateDocumentTitle: true
}, options);
kendo.Observable.fn.init.call(that, that.options);
$(function() {
that.element = $(element ? element : document.body);
that._setupPlatform();
that._setupElementClass();
that._attachHideBarHandlers();
that.pane = new Pane(that.element, that.options);
that._attachMeta();
if (that.options.updateDocumentTitle) {
that._setupDocumentTitle();
}
that._startHistory();
});
},
navigate: function(url, transition) {
this.pane.navigate(url, transition);
},
scroller: function() {
return this.view().scroller;
},
hideLoading: function() {
this.pane.hideLoading();
},
showLoading: function() {
this.pane.showLoading();
},
view: function() {
return this.pane.view();
},
_setupPlatform: function() {
var that = this, wpThemeProxy, platform = that.options.platform, os = OS || MOBILE_PLATFORMS[DEFAULT_OS];
if (platform) {
if (typeof platform === "string") {
os = MOBILE_PLATFORMS[platform];
} else {
os = platform;
}
}
that.os = os;
that.osCssClass = osCssClass(that.os);
if (os.name == "wp") {
wpThemeProxy = proxy(that._setupWP8Theme, that);
$(window).on("focusin", wpThemeProxy);
// Restore theme on browser focus (requires click).
document.addEventListener("resume", wpThemeProxy);
// PhoneGap fires resume.
that._setupWP8Theme();
}
},
_setupWP8Theme: function() {
var that = this, element = $(that.element), bgColor;
if (!that._bgColorDiv) {
that._bgColorDiv = $("<div />").css({
background: "Background",
visibility: "hidden",
position: "absolute",
top: "-3333px"
}).appendTo(document.body);
}
bgColor = parseInt(that._bgColorDiv.css("background-color").split(",")[1], 10);
element.removeClass("km-wp-dark km-wp-light");
if (bgColor === 0) {
element.addClass("km-wp-dark");
} else {
element.addClass("km-wp-light");
}
element.parent().css("overflow", "hidden");
},
_startHistory: function() {
var that = this, initial = that.options.initial, router = new kendo.Router({
init: function(e) {
var url = e.url;
if (url === "/" && initial) {
router.navigate(initial, true);
e.preventDefault();
}
},
routeMissing: function(e) {
that.pane.navigate(e.url);
}
});
that.pane.bind("navigate", function(e) {
router.navigate(e.url, true);
});
router.start();
that.router = router;
},
_setupElementClass: function() {
var that = this, element = that.element;
element.parent().addClass("km-root km-" + (that.os.tablet ? "tablet" : "phone"));
element.addClass(that.osCssClass + " " + getOrientationClass(element));
if (BERRYPHONEGAP) {
applyViewportHeight();
}
kendo.onResize(function() {
element.removeClass("km-horizontal km-vertical").addClass(getOrientationClass(element));
if (BERRYPHONEGAP) {
applyViewportHeight();
}
});
},
_attachMeta: function() {
var icon = this.options.icon, size;
if (!BERRYPHONEGAP) {
HEAD.prepend(viewportMeta);
}
HEAD.prepend(systemMeta);
if (icon) {
if (typeof icon === "string") {
icon = {
"": icon
};
}
for (size in icon) {
HEAD.prepend(iconMeta({
icon: icon[size],
size: size
}));
}
}
},
_attachHideBarHandlers: function() {
var that = this, hideBar = proxy(that._hideBar, that);
if (support.mobileOS.appMode || !that.options.hideAddressBar) {
return;
}
that._initialHeight = {};
if (HIDEBAR) {
WINDOW.on("load", hideBar);
kendo.onResize(hideBar);
that.element[0].addEventListener("touchstart", hideBar, true);
}
},
_setupDocumentTitle: function() {
var that = this, defaultTitle = document.title;
that.pane.bind("viewShow", function(e) {
var title = e.view.title;
document.title = title !== undefined ? title : defaultTitle;
});
},
_hideBar: function() {
var that = this, element = that.element, orientation = window.orientation + "", initialHeight = that._initialHeight, newHeight;
if (!initialHeight[orientation]) {
initialHeight[orientation] = WINDOW.height();
}
newHeight = initialHeight[orientation] + BARCOMPENSATION;
if (newHeight != element.height()) {
element.height(newHeight);
}
setTimeout(window.scrollTo, 0, 0, 1);
}
});
kendo.mobile.Application = Application;
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Shim = ui.Shim, Popup = ui.Popup, Widget = ui.Widget, OPEN = "open", BUTTONS = "li>a", CONTEXT_DATA = "actionsheetContext", WRAP = '<div class="km-actionsheet-wrapper" />', cancelTemplate = kendo.template('<li class="km-actionsheet-cancel"><a href="\\#">#:cancel#</a></li>');
var ActionSheet = Widget.extend({
init: function(element, options) {
var that = this, os = kendo.support.mobileOS, ShimClass = os.tablet ? Popup : Shim;
Widget.fn.init.call(that, element, options);
element = that.element;
element.addClass("km-actionsheet").append(cancelTemplate({
cancel: that.options.cancel
})).wrap(WRAP).on("up", BUTTONS, "_click").on("click", BUTTONS, kendo.preventDefault);
that.wrapper = element.parent();
that.shim = new ShimClass(that.wrapper, $.extend({
modal: !(os.android || os.meego || os.wp)
}, that.options.popup));
kendo.notify(that, ui);
kendo.onResize(function() {
var positionedElement = that.wrapper.parent(), viewPort = positionedElement.parent();
positionedElement.css({
top: viewPort.height() - positionedElement.height() + "px",
width: viewPort.width() + "px"
});
});
},
events: [ OPEN ],
options: {
name: "ActionSheet",
cancel: "Cancel",
popup: {
height: "auto"
}
},
open: function(target, context) {
var that = this;
that.target = $(target);
that.context = context;
that.shim.show(target);
},
close: function() {
this.context = this.target = null;
this.shim.hide();
},
openFor: function(target) {
var that = this, context = target.data(CONTEXT_DATA);
that.open(target, context);
that.trigger(OPEN, {
target: target,
context: context
});
},
destroy: function() {
Widget.fn.destroy.call(this);
this.shim.destroy();
},
_click: function(e) {
if (e.isDefaultPrevented()) {
return;
}
var action = $(e.currentTarget).data("action");
if (action) {
kendo.getter(action)(window)({
target: this.target,
context: this.context
});
}
e.preventDefault();
this.close();
}
});
ui.plugin(ActionSheet);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, Widget = ui.Widget, support = kendo.support, os = support.mobileOS, ANDROID3UP = os.android && os.flatVersion >= 300, CLICK = "click";
function highlightButton(widget, event, highlight) {
$(event.target).closest(".km-button,.km-detail").toggleClass("km-state-active", highlight);
if (ANDROID3UP && widget.deactivateTimeoutID) {
clearTimeout(widget.deactivateTimeoutID);
widget.deactivateTimeoutID = 0;
}
}
var Button = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that._wrap();
that._style();
that.element.on("up", "_release").on("down", "_activate").on("up cancel", "_deactivate");
if (ANDROID3UP) {
that.element.on("move", "_timeoutDeactivate");
}
},
events: [ CLICK ],
options: {
name: "Button",
icon: "",
style: ""
},
_timeoutDeactivate: function(e) {
if (!this.deactivateTimeoutID) {
this.deactivateTimeoutID = setTimeout(highlightButton, 500, this, e, false);
}
},
_activate: function(e) {
var activeElement = document.activeElement, nodeName = activeElement ? activeElement.nodeName : "";
highlightButton(this, e, true);
if (nodeName == "INPUT" || nodeName == "TEXTAREA") {
activeElement.blur();
}
},
_deactivate: function(e) {
highlightButton(this, e, false);
},
_release: function(e) {
var that = this;
if (e.which > 1) {
return;
}
if (that.trigger(CLICK, {
target: $(e.target),
button: that.element
})) {
e.preventDefault();
}
},
_style: function() {
var style = this.options.style, element = this.element, styles;
if (style) {
styles = style.split(" ");
$.each(styles, function() {
element.addClass("km-" + this);
});
}
},
_wrap: function() {
var that = this, icon = that.options.icon, iconSpan = '<span class="km-icon km-' + icon, element = that.element.addClass("km-button"), span = element.children("span:not(.km-icon)").addClass("km-text"), image = element.find("img").addClass("km-image");
if (!span[0] && element.html()) {
span = element.wrapInner('<span class="km-text" />').children("span.km-text");
}
if (!image[0] && icon) {
if (!span[0]) {
iconSpan += " km-notext";
}
element.prepend($(iconSpan + '" />'));
}
}
});
var BackButton = Button.extend({
options: {
name: "BackButton",
style: "back"
},
init: function(element, options) {
var that = this;
Button.fn.init.call(that, element, options);
if (typeof that.element.attr("href") === "undefined") {
that.element.attr("href", "#:back");
}
}
});
var DetailButton = Button.extend({
options: {
name: "DetailButton",
style: ""
},
init: function(element, options) {
Button.fn.init.call(this, element, options);
},
_style: function() {
var style = this.options.style + " detail", element = this.element;
if (style) {
var styles = style.split(" ");
$.each(styles, function() {
element.addClass("km-" + this);
});
}
},
_wrap: function() {
var that = this, icon = that.options.icon, iconSpan = '<span class="km-icon km-' + icon, element = that.element, span = element.children("span"), image = element.find("img").addClass("km-image");
if (!image[0] && icon) {
if (!span[0]) {
iconSpan += " km-notext";
}
element.prepend($(iconSpan + '" />'));
}
}
});
ui.plugin(Button);
ui.plugin(BackButton);
ui.plugin(DetailButton);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, ACTIVE = "km-state-active", SELECT = "select", SELECTOR = "li:not(." + ACTIVE + ")";
var ButtonGroup = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.element.addClass("km-buttongroup").find("li").each(that._button);
that.element.on(that.options.selectOn, SELECTOR, "_select");
that.select(that.options.index);
},
events: [ SELECT ],
options: {
name: "ButtonGroup",
selectOn: "down",
index: -1
},
current: function() {
return this.element.find("." + ACTIVE);
},
select: function(li) {
var that = this, index = -1;
if (li === undefined || li === -1) {
return;
}
that.current().removeClass(ACTIVE);
if (typeof li === "number") {
index = li;
li = $(that.element[0].children[li]);
} else if (li.nodeType) {
li = $(li);
index = li.index();
}
li.addClass(ACTIVE);
that.selectedIndex = index;
},
_button: function() {
var button = $(this).addClass("km-button"), icon = kendo.attrValue(button, "icon"), span = button.children("span"), image = button.find("img").addClass("km-image");
if (!span[0]) {
span = button.wrapInner("<span/>").children("span");
}
span.addClass("km-text");
if (!image[0] && icon) {
button.prepend($('<span class="km-icon km-' + icon + '"/>'));
}
},
_select: function(e) {
if (e.which > 1 || e.isDefaultPrevented()) {
return;
}
var that = this;
that.select(e.currentTarget);
that.trigger(SELECT);
}
});
ui.plugin(ButtonGroup);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, Node = window.Node, mobile = kendo.mobile, ui = mobile.ui, DataSource = kendo.data.DataSource, Widget = ui.Widget, ITEM_SELECTOR = ".km-list > li, > li:not(.km-group-container)", HIGHLIGHT_SELECTOR = ".km-listview-link, .km-listview-label", proxy = $.proxy, attrValue = kendo.attrValue, GROUP_CLASS = "km-group-title", ACTIVE_CLASS = "km-state-active", GROUP_WRAPPER = '<div class="' + GROUP_CLASS + '"><div class="km-text"></div></div>', GROUP_TEMPLATE = kendo.template('<li><div class="' + GROUP_CLASS + '"><div class="km-text">#= this.headerTemplate(data) #</div></div><ul>#= kendo.render(this.template, data.items)#</ul></li>'), WRAPPER = '<div class="km-listview-wrapper" />', SEARCH_TEMPLATE = kendo.template('<form class="km-filter-form"><div class="km-filter-wrap"><input type="search" placeholder="#=placeholder#"/><a href="\\#" class="km-filter-reset" title="Clear"><span class="km-icon km-clear"></span><span class="km-text">Clear</span></a></div></form>'), NS = ".kendoMobileListView", LAST_PAGE_REACHED = "lastPageReached", CLICK = "click", CHANGE = "change", PROGRESS = "progress", FUNCTION = "function", whitespaceRegExp = /^\s+$/, buttonRegExp = /button/;
function whitespace() {
return this.nodeType === Node.TEXT_NODE && this.nodeValue.match(whitespaceRegExp);
}
function addIcon(item, icon) {
if (icon) {
item.prepend('<span class="km-icon km-' + icon + '"/>');
}
}
function enhanceItem(item) {
addIcon(item, attrValue(item, "icon"));
}
function enhanceLinkItem(item) {
var parent = item.parent(), itemAndDetailButtons = item.add(parent.children(kendo.roleSelector("detailbutton"))), otherNodes = parent.contents().not(itemAndDetailButtons).not(whitespace);
if (otherNodes.length) {
return;
}
item.addClass("km-listview-link").attr(kendo.attr("role"), "listview-link");
addIcon(item, attrValue(parent, "icon"));
}
function enhanceCheckBoxItem(label) {
if (!label.children("input[type=checkbox],input[type=radio]").length) {
return;
}
var item = label.parent();
if (item.contents().not(label).not(function() {
return this.nodeType == 3;
})[0]) {
return;
}
label.addClass("km-listview-label");
}
var ListView = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
options = that.options;
if (options.scrollTreshold) {
options.scrollThreshold = options.scrollTreshold;
}
that._dragged = false;
element.on("down", HIGHLIGHT_SELECTOR, "_highlight").on("move up cancel", HIGHLIGHT_SELECTOR, "_dim").on("down", ITEM_SELECTOR, function() {
that._dragged = false;
}).on("move", ITEM_SELECTOR, function() {
that._dragged = true;
}).on("up", ITEM_SELECTOR, "_click");
element.wrap(WRAPPER);
that.wrapper = that.element.parent();
that._footer();
that._dataSource();
that._bindScroller();
that._fixHeaders();
that._filterable();
if (options.dataSource && that.options.autoBind) {
that.dataSource.fetch();
} else {
that._style();
}
kendo.notify(that, ui);
},
events: [ CLICK, "dataBound", LAST_PAGE_REACHED ],
options: {
name: "ListView",
style: "",
type: "flat",
autoBind: true,
fixedHeaders: false,
template: "#:data#",
headerTemplate: '<span class="km-text">#:value#</span>',
appendOnRefresh: false,
loadMore: false,
loadMoreText: "Press to load more",
endlessScroll: false,
scrollThreshold: 30,
pullToRefresh: false,
pullTemplate: "Pull to refresh",
releaseTemplate: "Release to refresh",
refreshTemplate: "Refreshing",
pullOffset: 140,
filterable: false
},
setOptions: function(options) {
Widget.fn.setOptions.call(this, options);
},
setDataSource: function(dataSource) {
this.options.dataSource = dataSource;
this._dataSource();
if (this.options.autoBind) {
dataSource.fetch();
}
},
destroy: function() {
var that = this;
Widget.fn.destroy.call(that);
that._unbindDataSource();
that.stopEndlessScrolling();
that.stopLoadMore();
kendo.destroy(that.element);
},
refresh: function(e) {
e = e || {};
var that = this, element = that.element, options = that.options, dataSource = that.dataSource, view = dataSource.view(), loading = that.loading, appendMethod = "html", action = e.action, items = e.items, idx = 0, contents, groups, length, data, item;
if (action === "itemchange") {
data = items[0];
item = $(that.template(data));
element.find("[data-" + kendo.ns + "uid=" + data.uid + "]").replaceWith(item);
that.trigger("itemChange", {
item: item,
data: data,
ns: ui
});
that._style();
return;
} else if (action === "add") {
length = items.length;
for (;idx < length; idx++) {
element.append(that.template(items[idx]));
}
that._style();
return;
} else if (action === "remove") {
length = items.length;
for (;idx < length; idx++) {
element.find("[data-" + kendo.ns + "uid=" + items[idx].uid + "]").remove();
}
return;
}
if (!that.template) {
that._templates();
}
that._cacheDataItems(view);
that.trigger("dataBinding");
groups = dataSource.group();
if (groups && groups[0]) {
options.type = "group";
contents = kendo.render(that.groupTemplate, view);
} else {
contents = kendo.render(that.template, view);
}
if (loading) {
appendMethod = "append";
} else if (options.appendOnRefresh && !that._filter) {
appendMethod = "prepend";
}
that._filter = false;
contents = $(contents);
element[appendMethod](contents);
mobile.init(contents);
if (loading) {
that.loading = false;
that._calcThreshold();
that._toggleLoader(false);
}
if (options.pullToRefresh) {
that._scroller().pullHandled();
}
that._hideLoading();
that._shouldFixHeaders();
that._style();
that._invalidateLoadMore();
that.trigger("dataBound", {
ns: ui
});
},
_invalidateLoadMore: function() {
var that = this, options = that.options, dataSource = that.dataSource, shouldInit = that._stopLoadMore && (!dataSource.total() || dataSource.page() < dataSource.totalPages());
if (shouldInit) {
if (options.endlessScroll) {
that.initEndlessScrolling();
}
if (options.loadMore) {
that.initLoadMore();
}
}
},
_cacheDataItems: function(view) {
var that = this, item;
if (!that.element[0].firstChild) {
that._firstOrigin = that._first = view[0];
that._last = view[view.length - 1];
}
if (that._pulled) {
item = view[0];
that._pulled = false;
if (item) {
that._first = item;
}
}
if (that.loading) {
item = view[view.length - 1];
if (item) {
that._last = item;
}
}
},
items: function() {
if (this.options.type === "group") {
return this.element.find(".km-list").children();
} else {
return this.element.children();
}
},
initEndlessScrolling: function() {
this._stopLoadMore = false;
this._scroller().setOptions({
resize: this._scrollerResize,
scroll: this._scrollerScroll
});
},
stopEndlessScrolling: function() {
var that = this, scroller = that._scroller();
if (scroller && that._loadIcon) {
that.loading = false;
that._stopLoadMore = true;
that._loadIcon.parent().hide();
scroller.unbind("resize", that._scrollerResize).unbind("scroll", that._scrollerScroll);
that.trigger(LAST_PAGE_REACHED);
}
},
initLoadMore: function() {
var that = this;
that._stopLoadMore = false;
that._loadButton.autoApplyNS().on("up", proxy(that._nextPage, that));
},
stopLoadMore: function() {
var that = this;
if (that._loadButton) {
that._stopLoadMore = true;
that.loading = false;
that._loadButton.kendoDestroy().parent().hide();
that.trigger(LAST_PAGE_REACHED);
}
},
_dim: function(e) {
this._toggle(e, false);
},
_highlight: function(e) {
this._toggle(e, true);
},
_toggle: function(e, highlight) {
if (e.which > 1) {
return;
}
var clicked = $(e.currentTarget), item = clicked.parent(), role = attrValue(clicked, "role") || "", plainItem = !role.match(buttonRegExp), prevented = e.isDefaultPrevented();
if (plainItem) {
item.toggleClass(ACTIVE_CLASS, highlight && !prevented);
}
},
_unbindDataSource: function() {
var that = this;
that.dataSource.unbind(CHANGE, that._refreshHandler).unbind(PROGRESS, that._progressHandler);
},
_dataSource: function() {
var that = this, options = that.options;
if (that.dataSource && that._refreshHandler) {
that._unbindDataSource();
} else {
that._refreshHandler = proxy(that.refresh, that);
that._progressHandler = proxy(that._showLoading, that);
}
that.dataSource = DataSource.create(options.dataSource).bind(CHANGE, that._refreshHandler);
if (!options.pullToRefresh && !options.loadMore && !options.endlessScroll) {
that.dataSource.bind(PROGRESS, that._progressHandler);
}
},
_fixHeader: function(e) {
if (this.fixedHeaders) {
var i = 0, that = this, scroller = that._scroller(), scrollTop = e.scrollTop, headers = that.headers, headerPair, offset, header;
do {
headerPair = headers[i++];
if (!headerPair) {
header = $("<div />");
break;
}
offset = headerPair.offset;
header = headerPair.header;
} while (offset > scrollTop);
if (that.currentHeader != i) {
scroller.fixedContainer.html(header.clone());
that.currentHeader = i;
}
}
},
_shouldFixHeaders: function() {
this.fixedHeaders = this.options.type === "group" && this.options.fixedHeaders;
},
_cacheHeaders: function() {
var that = this, headers = [];
if (that.fixedHeaders) {
that.element.find("." + GROUP_CLASS).each(function(_, header) {
header = $(header);
headers.unshift({
offset: header.position().top,
header: header
});
});
that.headers = headers;
that._fixHeader({
scrollTop: 0
});
}
},
_fixHeaders: function() {
var that = this, scroller = that._scroller();
that._shouldFixHeaders();
if (scroller) {
kendo.onResize(function() {
that._cacheHeaders();
});
scroller.bind("scroll", function(e) {
that._fixHeader(e);
});
}
},
_bindScroller: function() {
var that = this, options = that.options, scroller = that._scroller();
if (!scroller) {
return;
}
if (options.pullToRefresh) {
scroller.setOptions({
pullToRefresh: true,
pull: function() {
var callback = options.pullParameters, params = {
page: 1
};
if (callback) {
params = callback.call(that, that._first);
}
that._pulled = true;
that.dataSource.read(params);
},
pullTemplate: options.pullTemplate,
releaseTemplate: options.releaseTemplate,
refreshTemplate: options.refreshTemplate
});
}
if (options.endlessScroll) {
that._scrollHeight = scroller.element.height();
that._scrollerResize = function() {
that._scrollHeight = scroller.element.height();
that._calcThreshold();
}, that._scrollerScroll = function(e) {
if (!that.loading && e.scrollTop + that._scrollHeight > that._threshold) {
that._nextPage();
}
};
that.initEndlessScrolling();
}
},
_calcThreshold: function() {
var that = this, scroller = that._scroller();
if (scroller) {
that._threshold = scroller.scrollHeight() - that.options.scrollThreshold;
}
},
_nextPage: function() {
var that = this, options = that.options, callback = options.endlessScrollParameters || options.loadMoreParameters, params;
that.loading = true;
that._toggleLoader(true);
if (callback) {
params = callback.call(that, that._firstOrigin, that._last);
}
if (!that.dataSource.next(params)) {
that.stopLoadMore();
that.stopEndlessScrolling();
}
},
_templates: function() {
var that = this, template = that.options.template, headerTemplate = that.options.headerTemplate, dataIDAttribute = ' data-uid="#=data.uid || ""#"', templateProxy = {}, groupTemplateProxy = {};
if (typeof template === FUNCTION) {
templateProxy.template = template;
template = "#=this.template(data)#";
}
groupTemplateProxy.template = that.template = proxy(kendo.template("<li" + dataIDAttribute + ">" + template + "</li>"), templateProxy);
if (typeof headerTemplate === FUNCTION) {
groupTemplateProxy._headerTemplate = headerTemplate;
headerTemplate = "#=this._headerTemplate(data)#";
}
groupTemplateProxy.headerTemplate = kendo.template(headerTemplate);
that.groupTemplate = proxy(GROUP_TEMPLATE, groupTemplateProxy);
},
_click: function(e) {
if (e.which > 1 || e.isDefaultPrevented()) {
return;
}
if (this._dragged) {
e.preventDefault();
return;
}
var that = this, dataItem, item = $(e.currentTarget), target = $(e.target), buttonElement = target.closest(kendo.roleSelector("button", "detailbutton", "backbutton")), button = kendo.widgetInstance(buttonElement, ui), id = item.attr(kendo.attr("uid"));
if (id) {
dataItem = that.dataSource.getByUid(id);
}
if (that.trigger(CLICK, {
target: target,
item: item,
dataItem: dataItem,
button: button
})) {
e.preventDefault();
}
},
_style: function() {
var that = this, options = that.options, grouped = options.type === "group", element = that.element, inset = options.style === "inset";
element.addClass("km-listview").toggleClass("km-list", !grouped).toggleClass("km-listinset", !grouped && inset).toggleClass("km-listgroup", grouped && !inset).toggleClass("km-listgroupinset", grouped && inset);
if (grouped) {
element.children().children("ul").addClass("km-list");
element.children("li").each(function() {
var li = $(this), groupHeader = li.contents().first();
li.addClass("km-group-container");
if (!groupHeader.is("ul") && !groupHeader.is("div." + GROUP_CLASS)) {
groupHeader.wrap(GROUP_WRAPPER);
}
});
}
that._enhanceItems();
if (!element.parents(".km-listview")[0]) {
element.closest(".km-content").toggleClass("km-insetcontent", inset);
}
that._cacheHeaders();
},
_enhanceItems: function() {
this.items().each(function() {
var item = $(this), child, enhanced = false;
item.children().each(function() {
child = $(this);
if (child.is("a")) {
enhanceLinkItem(child);
enhanced = true;
} else if (child.is("label")) {
enhanceCheckBoxItem(child);
enhanced = true;
}
});
if (!enhanced) {
enhanceItem(item);
}
});
},
_footer: function() {
var that = this, options = that.options, loadMore = options.loadMore, loadWrapper;
if (loadMore || options.endlessScroll) {
that._loadIcon = $('<span style="display:none" class="km-icon"></span>');
loadWrapper = $('<span class="km-load-more"></span>').append(that._loadIcon);
if (loadMore) {
that._loadButton = $('<button class="km-load km-button">' + options.loadMoreText + "</button>");
that.initLoadMore();
loadWrapper.append(that._loadButton);
}
that.wrapper.append(loadWrapper);
}
},
_toggleLoader: function(toggle) {
var that = this, icon = that._loadIcon, button = that._loadButton;
if (button) {
button.toggle(!toggle);
}
if (toggle) {
icon.parent().addClass("km-scroller-refresh");
icon.css("display", "block");
} else {
icon.hide();
icon.parent().removeClass("km-scroller-refresh");
}
},
_scroller: function() {
var that = this, view;
if (!that._scrollerInstance) {
view = that.view();
that._scrollerInstance = view && view.scroller;
}
return that._scrollerInstance;
},
_showLoading: function() {
var view = this.view();
if (view && view.loader) {
view.loader.show();
}
},
_hideLoading: function() {
var view = this.view();
if (view && view.loader) {
view.loader.hide();
}
},
_filterable: function() {
var that = this, filterable = that.options.filterable, events = "change paste";
if (filterable) {
that.element.before(SEARCH_TEMPLATE({
placeholder: filterable.placeholder || "Search..."
}));
if (filterable.autoFilter !== false) {
events += " keyup";
}
that.searchInput = that.wrapper.find("input[type=search]").closest("form").on("submit" + NS, function(e) {
e.preventDefault();
}).end().on("focus" + NS, function() {
that._oldFilter = that.searchInput.val();
}).on(events.split(" ").join(NS + " ") + NS, proxy(that._filterChange, that));
that.clearButton = that.wrapper.find(".km-filter-reset").on(CLICK, proxy(that._clearFilter, that)).hide();
}
},
_search: function(expr) {
this._filter = true;
this.clearButton[expr ? "show" : "hide"]();
this.dataSource.filter(expr);
},
_filterChange: function(e) {
var that = this;
if (e.type == "paste" && that.options.filterable.autoFilter !== false) {
setTimeout(function() {
that._applyFilter();
}, 1);
} else {
that._applyFilter();
}
},
_applyFilter: function() {
var that = this, filterable = that.options.filterable, value = that.searchInput.val(), expr = value.length ? {
field: filterable.field,
operator: filterable.operator || "startsWith",
ignoreCase: filterable.ignoreCase,
value: value
} : null;
if (value === that._oldFilter) {
return;
}
that._oldFilter = value;
that._search(expr);
},
_clearFilter: function(e) {
this.searchInput.val("");
this._search(null);
e.preventDefault();
}
});
ui.plugin(ListView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, roleSelector = kendo.roleSelector, Widget = ui.Widget;
function createContainer(align, element) {
var items = element.find("[" + kendo.attr("align") + "=" + align + "]");
if (items[0]) {
element.prepend($('<div class="km-' + align + 'item" />').append(items));
}
}
function toggleTitle(centerElement) {
var siblings = centerElement.siblings();
centerElement.toggleClass("km-show-title", !!siblings[0] && $.trim(centerElement.text()) === "");
centerElement.toggleClass("km-no-title", !!centerElement.children("ul")[0]);
centerElement.toggleClass("km-hide-title", centerElement.css("visibility") == "hidden" && !siblings.children().is(":visible"));
}
var NavBar = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
that.container().bind("show", $.proxy(this, "refresh"));
element.addClass("km-navbar").wrapInner($('<div class="km-view-title" />'));
createContainer("left", element);
createContainer("right", element);
that.centerElement = element.find(".km-view-title");
},
options: {
name: "NavBar"
},
title: function(value) {
this.element.find(roleSelector("view-title")).text(value);
toggleTitle(this.centerElement);
},
refresh: function(e) {
var view = e.view;
if (view.options.title) {
this.title(view.options.title);
} else {
toggleTitle(this.centerElement);
}
},
destroy: function() {
Widget.fn.destroy.call(this);
kendo.destroy(this.element);
}
});
ui.plugin(NavBar);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, mobile = kendo.mobile, ui = mobile.ui, proxy = $.proxy, Transition = kendo.fx.Transition, Pane = kendo.ui.Pane, PaneDimensions = kendo.ui.PaneDimensions, Widget = ui.Widget, // Math
math = Math, abs = math.abs, ceil = math.ceil, round = math.round, max = math.max, min = math.min, floor = math.floor, CHANGE = "change", CURRENT_PAGE_CLASS = "km-current-page";
var ScrollView = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
element = that.element;
element.wrapInner("<div/>").addClass("km-scrollview").append('<ol class="km-pages"/>');
that.inner = element.children().first();
that.pager = element.children().last();
that.page = 0;
that.inner.css("height", that.options.contentHeight);
that.container().bind("show", $.proxy(this, "viewShow")).bind("init", $.proxy(this, "viewInit"));
var movable, transition, userEvents, dimensions, dimension, pane;
movable = new kendo.ui.Movable(that.inner);
transition = new Transition({
axis: "x",
movable: movable,
onEnd: proxy(that._transitionEnd, that)
});
userEvents = new kendo.UserEvents(element, {
start: function(e) {
if (abs(e.x.velocity) * 2 >= abs(e.y.velocity)) {
userEvents.capture();
} else {
userEvents.cancel();
}
transition.cancel();
},
allowSelection: true,
end: proxy(that._dragEnd, that)
});
dimensions = new PaneDimensions({
element: that.inner,
container: that.element
});
dimension = dimensions.x;
dimension.bind(CHANGE, proxy(that.refresh, that));
pane = new Pane({
dimensions: dimensions,
userEvents: userEvents,
movable: movable,
elastic: true
});
$.extend(that, {
movable: movable,
transition: transition,
userEvents: userEvents,
dimensions: dimensions,
dimension: dimension,
pane: pane
});
that.page = that.options.page;
},
options: {
name: "ScrollView",
page: 0,
duration: 300,
velocityThreshold: .8,
contentHeight: "auto",
pageSize: 1,
bounceVelocityThreshold: 1.6
},
events: [ CHANGE ],
destroy: function() {
Widget.fn.destroy.call(this);
this.userEvents.destroy();
kendo.destroy(this.element);
},
viewInit: function() {
this.movable.moveAxis("x", -this.page * this.dimension.getSize());
},
viewShow: function() {
this.dimensions.refresh();
},
refresh: function() {
var that = this, pageHTML = "", dimension = that.dimension, width = dimension.getSize(), pages, pageElements = that.element.find("[data-role=page]");
pageElements.width(width);
dimension.update(true);
// if no pages present, try to retain the current position
if (!pageElements[0]) {
that.page = Math.floor(-that.movable.x / width);
}
that.scrollTo(that.page, true);
pages = that.pages = ceil(dimension.getTotal() / width);
that.minSnap = -(pages - 1) * width;
that.maxSnap = 0;
for (var idx = 0; idx < pages; idx++) {
pageHTML += "<li/>";
}
that.pager.html(pageHTML);
that._updatePager();
},
content: function(html) {
this.element.children().first().html(html);
this.dimensions.refresh();
},
scrollTo: function(page, instant) {
this.page = page;
this._moveTo(-page * this.dimension.getSize(), Transition.easeOutExpo, instant);
},
_moveTo: function(location, ease, instant) {
this.transition.moveTo({
location: location,
duration: instant ? 1 : this.options.duration,
ease: ease
});
},
_dragEnd: function(e) {
var that = this, velocity = e.x.velocity, width = that.dimension.size * that.options.pageSize, options = that.options, velocityThreshold = options.velocityThreshold, snap, approx = round, ease = Transition.easeOutExpo;
if (velocity > velocityThreshold) {
approx = ceil;
} else if (velocity < -velocityThreshold) {
approx = floor;
}
if (abs(velocity) > options.bounceVelocityThreshold) {
ease = Transition.easeOutBack;
}
snap = max(that.minSnap, min(approx(that.movable.x / width) * width, that.maxSnap));
this._moveTo(snap, ease);
},
_transitionEnd: function() {
var that = this, page = Math.round(-that.movable.x / that.dimension.size);
if (page != that.page) {
that.page = page;
that.trigger(CHANGE, {
page: page
});
that._updatePager();
}
},
_updatePager: function() {
this.pager.children().removeClass(CURRENT_PAGE_CLASS).eq(this.page).addClass(CURRENT_PAGE_CLASS);
}
});
ui.plugin(ScrollView);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, support = kendo.support, CHANGE = "change", SWITCHON = "km-switch-on", SWITCHOFF = "km-switch-off", MARGINLEFT = "margin-left", ACTIVE_STATE = "km-state-active", TRANSFORMSTYLE = support.transitions.css + "transform", proxy = $.proxy;
function limitValue(value, minLimit, maxLimit) {
return Math.max(minLimit, Math.min(maxLimit, value));
}
var Switch = Widget.extend({
init: function(element, options) {
var that = this, checked;
Widget.fn.init.call(that, element, options);
that._wrapper();
that._drag();
that._background();
that.origin = parseInt(that.background.css(MARGINLEFT), 10);
that._handle();
that.constrain = 0;
that.snapPoint = 0;
that.container().bind("show", $.proxy(this, "refresh"));
element = that.element[0];
element.type = "checkbox";
that._animateBackground = true;
checked = that.options.checked;
if (checked === null) {
checked = element.checked;
}
that.check(checked);
that.refresh();
kendo.notify(that, kendo.mobile.ui);
},
refresh: function() {
var that = this, width, handleWidth;
width = that.wrapper.width();
handleWidth = that.handle.outerWidth(true);
that.constrain = width - handleWidth;
that.snapPoint = width / 2 - handleWidth / 2;
that.background.data("origin", that.origin);
that.check(that.element[0].checked);
},
events: [ CHANGE ],
options: {
name: "Switch",
onLabel: "ON",
offLabel: "OFF",
checked: null
},
check: function(check) {
var that = this, element = that.element[0];
if (check === undefined) {
return element.checked;
}
that._position(check ? that.constrain : 0);
element.checked = check;
that.wrapper.toggleClass(SWITCHON, check).toggleClass(SWITCHOFF, !check);
},
destroy: function() {
Widget.fn.destroy.call(this);
this.userEvents.destroy();
},
toggle: function() {
var that = this;
that.check(!that.element[0].checked);
},
_move: function(e) {
var that = this;
e.preventDefault();
that._position(limitValue(that.position + e.x.delta, 0, that.constrain));
},
_position: function(position) {
var that = this;
that.position = position;
that.handle.css(TRANSFORMSTYLE, "translatex(" + position + "px)");
if (that._animateBackground) {
that.background.css(MARGINLEFT, that.origin + position);
}
},
_start: function() {
this.userEvents.capture();
this.handle.addClass(ACTIVE_STATE);
},
_stop: function() {
var that = this;
that.handle.removeClass(ACTIVE_STATE);
that._toggle(that.position > that.snapPoint);
},
_toggle: function(checked) {
var that = this, handle = that.handle, element = that.element[0], value = element.checked, duration = kendo.mobile.application && kendo.mobile.application.os.wp ? 100 : 200, distance;
that.wrapper.toggleClass(SWITCHON, checked).toggleClass(SWITCHOFF, !checked);
that.position = distance = checked * that.constrain;
if (that._animateBackground) {
that.background.kendoStop(true, true).kendoAnimate({
effects: "slideMargin",
offset: distance,
reset: true,
reverse: !checked,
axis: "left",
duration: duration
});
}
handle.kendoStop(true, true).kendoAnimate({
effects: "slideTo",
duration: duration,
offset: distance + "px,0",
reset: true,
complete: function() {
if (value !== checked) {
element.checked = checked;
that.trigger(CHANGE, {
checked: checked
});
}
}
});
},
_background: function() {
var that = this, background;
background = $("<span class='km-switch-wrapper'><span class='km-switch-background'></span></span>").appendTo(that.wrapper).children(".km-switch-background");
that.background = background;
},
_handle: function() {
var that = this, options = that.options;
that.handle = $("<span class='km-switch-container'><span class='km-switch-handle' /></span>").appendTo(that.wrapper).children(".km-switch-handle");
that.handle.append('<span class="km-switch-label-on">' + options.onLabel + '</span><span class="km-switch-label-off">' + options.offLabel + "</span>");
},
_wrapper: function() {
var that = this, element = that.element, wrapper = element.parent("span.km-switch");
if (!wrapper[0]) {
wrapper = element.wrap('<span class="km-switch"/>').parent();
}
that.wrapper = wrapper;
},
_drag: function() {
var that = this;
that.userEvents = new kendo.UserEvents(that.wrapper, {
tap: function() {
that._toggle(!that.element[0].checked);
},
start: proxy(that._start, that),
move: proxy(that._move, that),
end: proxy(that._stop, that)
});
}
});
ui.plugin(Switch);
})(window.kendo.jQuery);
(function($, undefined) {
var kendo = window.kendo, ui = kendo.mobile.ui, Widget = ui.Widget, ACTIVE_STATE_CLASS = "km-state-active", SELECT = "select";
var TabStrip = Widget.extend({
init: function(element, options) {
var that = this;
Widget.fn.init.call(that, element, options);
that.container().bind("show", $.proxy(this, "refresh"));
that.element.addClass("km-tabstrip").find("a").each(that._buildButton).eq(that.options.selectedIndex).addClass(ACTIVE_STATE_CLASS);
that.element.on("down", "a", "_release");
},
events: [ SELECT ],
switchTo: function(url) {
var tabs = this.element.find("a"), tab, path, idx = 0, length = tabs.length;
for (;idx < length; idx++) {
tab = tabs[idx];
path = tab.href.replace(/(\#.+)(\?.+)$/, "$1");
// remove the fragment query string - http://www.foo.com?foo#bar**?baz=qux**
if (path.indexOf(url, path.length - url.length) !== -1) {
this._setActiveItem($(tab));
return;
}
}
},
clear: function() {
this.currentItem().removeClass(ACTIVE_STATE_CLASS);
},
currentItem: function() {
return this.element.children("." + ACTIVE_STATE_CLASS);
},
_release: function(e) {
if (e.which > 1) {
return;
}
var that = this, item = $(e.currentTarget);
if (item[0] === that.currentItem()[0]) {
return;
}
if (that.trigger(SELECT, {
item: item
})) {
e.preventDefault();
} else {
that._setActiveItem(item);
}
},
_setActiveItem: function(item) {
if (!item[0]) {
return;
}
this.clear();
item.addClass(ACTIVE_STATE_CLASS);
},
_buildButton: function() {
var button = $(this), icon = kendo.attrValue(button, "icon"), image = button.find("img"), iconSpan = $('<span class="km-icon"/>');
button.addClass("km-button").attr(kendo.attr("role"), "tab").contents().not(image).wrapAll('<span class="km-text"/>');
if (image[0]) {
image.addClass("km-image");
} else {
button.prepend(iconSpan);
if (icon) {
iconSpan.addClass("km-" + icon);
}
}
},
refresh: function(e) {
this.switchTo(e.view.id);
},
destroy: function() {
Widget.fn.destroy.call(this);
},
options: {
name: "TabStrip",
selectedIndex: 0,
enable: true
}
});
ui.plugin(TabStrip);
})(window.kendo.jQuery);