<!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.default.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="//ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.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="script.js"></script>
  </head>

  <body ng-app="app" ng-controller="myCtrl">
    <h1>AngularJS + kendo</h1>
    <button ng-click="addRow()">Create</button>
    <button ng-click="saveRow()">Save</button>
  <div kendo-grid="theGrid" k-data-source="products"  
       k-height="150"
       k-columns='[
                      { "field": "ProductName", "title": "Name"}
                  ]'
        k-sortable="true" k-editable="true"
 >
 </div>

  </body>

</html>
// Code goes here

var app = angular.module('app', ['kendo.directives']); 

app.controller("myCtrl", function ($compile, $scope) {

  $scope.products = new kendo.data.DataSource({
    transport: {
        read:  {
           // url: crudServiceBaseUrl + "/Products",
            dataType: "jsonp"
        },
        update: {
           // url: crudServiceBaseUrl + "/Products/Update",
            dataType: "jsonp"
        },
        destroy: {
            //url: crudServiceBaseUrl + "/Products/Destroy",
            dataType: "jsonp"
        },
        create: {
            //url: crudServiceBaseUrl + "/Products/Create",
            dataType: "jsonp"
        },
        parameterMap: function(options, operation) {
            if (operation !== "read" && options.models) {
                return {models: kendo.stringify(options.models)};
            }
        }
    },
    schema : {
			model : {
				fields : {
					ProductName : {
						type : "string",
						validation: {
							required: true
					}
				}
			}
			  
			}
      
    }
  });


  $scope.addRow = function(){
    $scope.theGrid.addRow();
  }
  
  $scope.saveRow = function(){
    $scope.theGrid.saveRow();
  }
});



/* Styles go here */

html .k-edit-cell .k-tooltip, html .k-grid-edit-row .k-tooltip{
  min-height: 100px;
  min-width:100px;
}
'use strict';

// declare all the module
angular.module('kendo.directives', []);
angular.module('kendo.directives', [], 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').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
      };
  }];

}]);

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
  };

  // 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.
            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;

    // 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);
              };

              // 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) {
                scope.$apply(function() {
                  // Set the value on the scope to the widget value.
                  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));
        }
      });
    }]
  };

}]);
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');

    // 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) {
    // 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)));
      }

    }
  };

}]);

[{"ProductID":1,"ProductName":"Chai","Supplier":{"SupplierID":1,"SupplierName":"Exotic Liquids"},"Category":{"CategoryID":1,"CategoryName":"Beverages"},"UnitPrice":18.0,"UnitsInStock":39,"Discontinued":false},{"ProductID":2,"ProductName":"Chang","Supplier":{"SupplierID":1,"SupplierName":"Exotic Liquids"},"Category":{"CategoryID":1,"CategoryName":"Beverages"},"UnitPrice":19.0,"UnitsInStock":17,"Discontinued":false},{"ProductID":3,"ProductName":"Aniseed Syrup","Supplier":{"SupplierID":1,"SupplierName":"Exotic Liquids"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":10.0,"UnitsInStock":13,"Discontinued":false},{"ProductID":4,"ProductName":"Chef Anton\u0027s Cajun Seasoning","Supplier":{"SupplierID":2,"SupplierName":"New Orleans Cajun Delights"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":22.0,"UnitsInStock":53,"Discontinued":false},{"ProductID":5,"ProductName":"Chef Anton\u0027s Gumbo Mix","Supplier":{"SupplierID":2,"SupplierName":"New Orleans Cajun Delights"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":21.35,"UnitsInStock":0,"Discontinued":true},{"ProductID":6,"ProductName":"Grandma\u0027s Boysenberry Spread","Supplier":{"SupplierID":3,"SupplierName":"Grandma Kelly\u0027s Homestead"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":25.0,"UnitsInStock":120,"Discontinued":false},{"ProductID":7,"ProductName":"Uncle Bob\u0027s Organic Dried Pears","Supplier":{"SupplierID":3,"SupplierName":"Grandma Kelly\u0027s Homestead"},"Category":{"CategoryID":7,"CategoryName":"Produce"},"UnitPrice":30.0,"UnitsInStock":15,"Discontinued":false},{"ProductID":8,"ProductName":"Northwoods Cranberry Sauce","Supplier":{"SupplierID":3,"SupplierName":"Grandma Kelly\u0027s Homestead"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":40.0,"UnitsInStock":6,"Discontinued":false},{"ProductID":9,"ProductName":"Mishi Kobe Niku","Supplier":{"SupplierID":4,"SupplierName":"Tokyo Traders"},"Category":{"CategoryID":6,"CategoryName":"Meat/Poultry"},"UnitPrice":97.0,"UnitsInStock":29,"Discontinued":true},{"ProductID":10,"ProductName":"Ikura","Supplier":{"SupplierID":4,"SupplierName":"Tokyo Traders"},"Category":{"CategoryID":8,"CategoryName":"Seafood"},"UnitPrice":31.0,"UnitsInStock":31,"Discontinued":false},{"ProductID":11,"ProductName":"Queso Cabrales","Supplier":{"SupplierID":5,"SupplierName":"Cooperativa de Quesos \u0027Las Cabras\u0027"},"Category":{"CategoryID":4,"CategoryName":"Dairy Products"},"UnitPrice":21.0,"UnitsInStock":22,"Discontinued":false},{"ProductID":12,"ProductName":"Queso Manchego La Pastora","Supplier":{"SupplierID":5,"SupplierName":"Cooperativa de Quesos \u0027Las Cabras\u0027"},"Category":{"CategoryID":4,"CategoryName":"Dairy Products"},"UnitPrice":38.0,"UnitsInStock":86,"Discontinued":false},{"ProductID":13,"ProductName":"Konbu","Supplier":{"SupplierID":6,"SupplierName":"Mayumi\u0027s"},"Category":{"CategoryID":8,"CategoryName":"Seafood"},"UnitPrice":6.0,"UnitsInStock":24,"Discontinued":false},{"ProductID":14,"ProductName":"Tofu","Supplier":{"SupplierID":6,"SupplierName":"Mayumi\u0027s"},"Category":{"CategoryID":7,"CategoryName":"Produce"},"UnitPrice":23.25,"UnitsInStock":35,"Discontinued":false},{"ProductID":15,"ProductName":"Genen Shouyu","Supplier":{"SupplierID":6,"SupplierName":"Mayumi\u0027s"},"Category":{"CategoryID":2,"CategoryName":"Condiments"},"UnitPrice":15.5,"UnitsInStock":39,"Discontinued":false},{"ProductID":16,"ProductName":"Pavlova","Supplier":{"SupplierID":7,"SupplierName":"Pavlova, Ltd."},"Category":{"CategoryID":3,"CategoryName":"Confections"},"UnitPrice":17.45,"UnitsInStock":29,"Discontinued":false},{"ProductID":17,"ProductName":"Alice Mutton","Supplier":{"SupplierID":7,"SupplierName":"Pavlova, Ltd."},"Category":{"CategoryID":6,"CategoryName":"Meat/Poultry"},"UnitPrice":39.0,"UnitsInStock":0,"Discontinued":true},{"ProductID":18,"ProductName":"Carnarvon Tigers","Supplier":{"SupplierID":7,"SupplierName":"Pavlova, Ltd."},"Category":{"CategoryID":8,"CategoryName":"Seafood"},"UnitPrice":62.5,"UnitsInStock":42,"Discontinued":false},{"ProductID":19,"ProductName":"Teatime Chocolate Biscuits","Supplier":{"SupplierID":8,"SupplierName":"Specialty Biscuits, Ltd."},"Category":{"CategoryID":3,"CategoryName":"Confections"},"UnitPrice":9.2,"UnitsInStock":25,"Discontinued":false},{"ProductID":20,"ProductName":"Sir Rodney\u0027s Marmalade","Supplier":{"SupplierID":8,"SupplierName":"Specialty Biscuits, Ltd."},"Category":{"CategoryID":3,"CategoryName":"Confections"},"UnitPrice":81.0,"UnitsInStock":40,"Discontinued":false}]