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








Monday, August 18, 2014

Add cache control headers to Spring MVC

Many times we want browsers and proxies and gateways to cache our static files. But not the data calls. Here's how to add cache control headers in Spring MVC. 

    <mvc:interceptors>
        <mvc:interceptor>
            <!-- Cache of HTML pages -->
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/images/**"  />
            <mvc:exclude-mapping path="/webjars/**"  />
            <bean
                    class="org.springframework.web.servlet.mvc.WebContentInterceptor">
                <property name="cacheSeconds" value="0"/>
                <property name="useExpiresHeader" value="true"/>
                <property name="useCacheControlHeader" value="true"/>
                <property name="useCacheControlNoStore" value="true"/>
            </bean>
        </mvc:interceptor>

        <!-- Cache of all images  -->
        <mvc:interceptor>
            <mvc:mapping path="/images/**"/>
            <bean
                    class="org.springframework.web.servlet.mvc.WebContentInterceptor">
                <property name="cacheSeconds" value="1800" />
                <property name="useExpiresHeader" value="true"/>
                <property name="useCacheControlHeader" value="true"/>
                <property name="useCacheControlNoStore" value="true"/>
            </bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/webjars/**"/>
            <bean
                    class="org.springframework.web.servlet.mvc.WebContentInterceptor">
                <property name="cacheSeconds" value="86400" />
                <property name="useExpiresHeader" value="true"/>
                <property name="useCacheControlHeader" value="true"/>
                <property name="useCacheControlNoStore" value="true"/>
            </bean>
        </mvc:interceptor>
    </mvc:interceptors>



This will create the below HTTP headers:

Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache
Cache-Control: no-store

This should take care of all the cache problems that we have.