Tuesday, August 19, 2014

Programmatically Select row in Angular Kendo's Grid

Kendo UI has AngularJs integration. Its mostly working fine but with a few hiccups. One of these hiccups is trying to programmatically select a row in Kendo UI's data grid (call a "grid" in Kendo).

Problem
The Kendo grid has a method select(). Which according to the API:
Gets or sets the table rows (or cells) which are selected.
So I tried to call select in a ng-click directive, and I get an error:
$apply already in progress
Some of you AngularJs experts will already know that $apply() is called by AngularJs to apply changes in the model (aka presentation model), to the view (aka presentation). And it cannot be called recursively; ie. you cannot call $apply() while another $apply is already in progress.

So here's a how my code looked like (the code that gave the "$apply already in progress" error):

<button ng-click="selectRow()">test</button>

And in my controller:
$scope.selectRow = function () {
  // ... some code ...
  $scope.myGrid.select( aRow );
};

After some digging, I found out that almost all code that's in the controller will be in an $apply() call.

  • ng-click="..."
  • ng-blur="..."
  • $scope.$watch(...)
  • $scope.$on(...)
AngularJs docs blame the 3rd party code

Another Problem
And this is Kendo's grid does not have ng-model support. That means you can't do this:

<div kendo-grid
     ng-model="selectedRow"></div>

You have to do this:

<div kendo-grid
     k-on-change="selectedRow = data"></div>

So even if I workaround the first problem above, I can't simply do this in my controller and hope the grid selects the row itself:

$scope.selectRow = function () {
  // ... some code ...
  $scope.selectedRow = aRow;
};


Solution
Apparently the accepted way to do it is 
  1. Wrap the data in a kendo.data.ObservableArray object.
  2. Add the CSS class "k-state-selected" to the row.
  3. Manually update the model with the row.

1. Wrap the data in a kendo.data.ObservableArray object
What this does is to provide true binding of each element of the array to each row of the grid. But a secondary effect of this is to wrap each element of the array in a kendo.data.ObservableObject object. This causes each array element to have a uid field automatically generated. And when the Grid renders, it will add a data-uid attribute to the HTML of the Grid row:

<tr class="k-alt ng-scope" data-uid="ac5be667-5483-4c52-bf2a-3a8f85bb5eeb" role="row">
  ...
</tr>

Perfect.

2. Add the CSS class "k-state-selected" to the row

This is done using jQuery:
$scope.selectRow = function () {
    // ... some code
    // "item" now contains the kendo.data.ObservableObject 
    $('[data-uid=' + item.uid + ']').addClass('k-state-selected');
};


3. Manually update the model with the row

This is done like so

$scope.selectRow = function () {
    // ... some code ...
    $scope.selectedRow = item;
};

So our final selectRow function looks like this:

$scope.selectRow = function () {
    // ... some code ...
    // "item" now contains the kendo.data.ObservableObject 
    $('[data-uid=' + item.uid + ']').addClass('k-state-selected');
    $scope.workingScreenState.selectedRow = item;
};








1 comment:

  1. sounds like you need to create a directive for this and tell Kendo not to create half baked angular support :)

    ReplyDelete