<!DOCTYPE html>
<html ng-app="app">
<head>
<link rel="stylesheet" href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.common.min.css">
<link rel="stylesheet" href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.default.min.css">
<script src="http://code.jquery.com/jquery-2.0.1.js"></script>
<script src="http://code.angularjs.org/1.0.7/angular.js"></script>
<script src="http://cdn.kendostatic.com/2013.1.319/js/kendo.all.min.js"></script>
<script src="angular-kendo.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="myCtrl">
<h1>{{greeting}}</h1>
<div kendo-grid k-data-source="dataNow" k-sortable="true" />
<br/>
<input type="submit" ng-click="getDelayData()" value="get delay data"/>
<div kendo-grid k-data-source="dataDelay" />
<br/>
<ul>
<li ng-repeat="d in dataDelay">
{{d.id}} - {{d.name}}
</li>
</ul>
</body>
</html>
var app = angular.module('app', ['kendo.directives']);
app.controller('myCtrl', function($scope){
$scope.dataNow =[
{id: '1', name: 'dataNow1'},
{id: '2', name: 'dataNow2'},
{id: '3', name: 'dataNow3'},
];
//$scope.dataDelay = [];
$scope.greeting = 'Hello from myCtrl';
$scope.getDelayData = function()
{
setTimeout(function(){
$scope.dataDelay =[
{id: '1', name: 'dataDelay1'},
{id: '2', name: 'dataDelay2'},
{id: '3', name: 'dataDelay3'},
];
$scope.dataNow.push({id: '4',name: 'dataNow4'});
//alert('delay loaded');
$scope.$apply();
},2000);
};
});
'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', function($parse, $log) {
// k-* attributes that should not be $parsed or $evaluated by gatherOptions
var ignoredAttributes = {
kDataSource: true,
kOptions: true,
kRebind: true
};
// 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));
// 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 attrRE = /k(On)?([A-Z].*)/;
// Mixin the data from the element's k-* attributes in the options
angular.forEach(attrs, function(attValue, attName) {
// ignore attributes that do not map to widget configuration options
if( ignoredAttributes[attName] ) {
return;
}
var match = attName.match(attrRE), 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(attValue);
// 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(attValue));
if( options[optionName] === undefined && attValue.match(/^\w*$/) ) {
// if the user put a single word as the attribute value and the expression evaluates to undefined,
// he may have wanted to use a string literal.
$log.warn(kendoWidget + '\'s ' + attName + ' attribute resolved to undefined. Maybe you meant to use a string literal like: \'' + attValue + '\'?');
}
}
}
});
// 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;
// TODO: invoke controller.decorateOptions to allow other directives (or directive extensions)
// to modify the options before they get bound. This would provide an extention point for directives
// that require special processing like compiling nodes generated by kendo so that angular data binding
// can happen in kendo widget templates for example.
//controller.decorateOptions(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);
});
// TODO: add functions to allow other directives to register option decorators
}],
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);
};
// 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);
};
return {
// This is an attribute directive
restrict: 'A',
controller: ['$scope', '$attrs', '$element', function($scope, $attrs, $element) {
// 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($scope.$eval($attrs.kDataSource)));
// Keep the element's data up-to-date with changes.
$scope.$watch($attrs.kDataSource, function(newDS, oldDS) {
if( newDS !== oldDS ) {
$element.data('$kendoDataSource', toDataSource(newDS));
}
});
}]
};
}]);