Telerik blogs

Hot on the heels of my post on creating a Kendo UI DataSource for a Backbone.Collection, Brandon Satrom and I built a quick hack to see what it would take to integrate Breeze.js with our DataSource.

DataSource All The Things

If you're not familiar with Breeze.js, it describes itself by saying "Rich data for JavaScript apps is a Breeze". Basically, it's a framework to abstract away all of the complexities of calling data APIs through JavaScript. It takes the complexity of understanding not only XHR objects away, like jQuery's ajax method, but also understanding data formats, query parameter formats, integration with specific back-end data sources, and much more.

The basic transport code

Like the Backbone.Collection integration, I'm going to build a custom data transport for Breeze.js. Instead of focusing on the basic CRUD features, though, I want to show some of the more advanced features of reading data: sorting and paging.

To get started, I need a DataSource wrapper that allows me to pass in the needed Breeze.js objects. The end result will be a custom transport that calls through to the Breeze objects through four basic methods: create, read, update, and delete. If you need further review or information on this, be sure to read my previous post on building a DataSource for a Backbone.Collection, and check out the documentation for DataSource transport.

BreezeDataSource

(function ($, kendo, breeze) {
  'use strict';

  // namespace for the extensions
  kendo.data.extensions = kendo.data.extensions || {};

  // constructor function for the custom transport
  function BreezeTransport(entityManager, endpoint){
    this.entityManager = entityManager;
    this.endPoint = endPoint;
  }

  // instance methods for the custom transport
  $.extend(BreezeTransport.prototype, {
    read: function (options) {
      // ...
    },
    create: function (options) {
      // ...
    },
    update: function (options) {
      // ...
    },
    destroy: function (options) {
      // ...
    }
  });

  // Create the custom DataSource by extending a kendo.data.DataSource
  // and specify an init method that wires up needed functionality.
  kendo.data.extensions.BreezeDataSource = kendo.data.DataSource.extend({
    init: function (options) {
      // build the transport and final options objects
      var breezeTransport = new BreezeTransport(options.entityManager, options.endpoint);
      options = $.extend({}, { transport: breezeTransport }, options)

      // Call the "base" DataSource init function and provide our custom transport object
      kendo.data.DataSource.fn.init.call(this, options);
    }
  });
})($, kendo, breeze);

The Breeze EntityManager is the core of what we work with, and is where an app will configure it's back-end specifics. Passing this in to our custom DataSource will allow all of Breeze's flexibility, but still give us the ability to query the data based on the parameters that the Kendo UI DataSource provides to the read method.

Reading data with EntityQuery

Once we have the custom DataSource and transport outlined, we can focus on read data through the EntityQuery object.

This object allows us to form the query that will be sent back to the API, and includes methods three methods that we'll use for sorting and paging:

  • orderBy
  • skip
  • take

These are common method and attribute names used for sorting and paging. The orderBy method is fairly self-explanatory in name. It allows us to sort the results by the configuration we specify. The skip and take methods, in combination, allow us to facilitate paging. These two methods might not be as obvious, though.

Skip, and take a page

When building a paged data source of any kind, paging can be facilitated with two simple parameters: skip and take. Any system that needs to use paging, then, can provide these parameters and end up with the data set that they expect. When a DataGrid or other page-enabled control says it wants page 2 of a data set, and each page is 10 records long, this gets translated in to the skip and take settings.

Given a page length of 10 and a request for page 2, we can do simple math to get the starting point of the data we want to show from the overall result set. Since we want page 2 of data and the page length is 10, we know that we need to skip the first 10 records... the first page of data. This can be calculated with a little bit of code, quite easily:

Calculating skip

var requestedPageLength = 10;
var requestedPageNumber = 2;

var skip = requestedPageLength * (requestedPageNumber-1); // => 10

We're subtracting 1 from the page number so that we can get the starting point of the second page of data, not the end point.

Even when we request page #1, then we don't want to skip anything - we want to start at the beginning of the record set. The formula still works for this scenario: 10 * (1-1) = 0, which says "skip zero records" or "start at the very beginning."

The take parameter of the skip/take combination is the actual number of records that we want to get - the reuested page length. This parameter tells the data source how many records to return, after it has skipped the number that we said to skip.

Take is the page size

var take = requestedPageLength;

The end result of the skip / take combination is a paging system that can be used by nearly any data source and page-enabled control or other code.

Reading paged and sorted data is a Breeze

Integration between Breeze and the transport read method is easy from here. The options parameter that is passed through the method contains three attributes: sort, skip, and take. We can apply these to the Breeze EntityQuery to get the paged and sorted data that has been requested.

Paged and sorted data with Breeze

read: function(options){
  var orderVal = "",
      sortOps = options.data.sort;

  // build sorting the way breeze understands it
  if (sortOps) {
    orderVal = sortOps.field + " " + sortOps.dir;
  }

  // build a query to get the sorted and paged results
  var query = new breeze.EntityQuery(endpoint)
      .orderBy(options.data.sort)
      .skip(options.data.skip)
      .take(options.data.take);

  // ...
}

Note that we're not just grabbing the raw sort value from the options.data.sort. Instead, we are taking the field and dir attributes from this and creating an orderBy configuration that Breeze understands.

Once we have those values applied, we can execute the query using the EntityManager that was supplied to the DataSource.

Executing a Breeze query

read: function(options){
  // ...

  breezeEntityManager.executeQuery(query).then(function(xhr){
    options.success(xhr.results);
  });
}

Executing a Breeze Query will result in a q.js Promise object, which can be chained in to a then callback that is called when the data finally returns from the server. Once the data has returned, we call options.success and pass the resulting data.

Using the BreezeDataSource

With the read-only BreezeDataSource set up, we can use it as we would any other DataSource for a Kendo UI control. We only need to configure a Breeze EntityManager to work with our API, and then pass that to our BreezeDataSource.

Using the BreezeDataSource

  // create a Breeze EntityManager and configure
  // it for the API that we are connecting to
  var manager = new breeze.EntityManager({
    dataService: new breeze.DataService({
      serviceName: 'data/',
      hasServerMetadata: false
    })
  });

  // create a BreezeDataSource
  var ds = new kendo.data.extensions.BreezeDataSource({
    entityManager: manager,
    endpoint: "products.json",
    pageSize: 10,
    serverPaging: true,
    serverSorting: true
  });

  // assign the DataSource to the grid
  $("#grid").kendoGrid({
    columns: [
      { field: 'ProductName', title: 'Name'},
      { field: 'Supplier.SupplierName', title: 'Supplier'},
      { field: 'Category.CategoryName', title: 'Category' }
    ],
    dataSource: ds,
    pageable: true,
    sortable: true
  });

This will produce a Kendo UI Grid with the data being read through Breeze.js


About the Author

Derick Bailey

About the Author
Derick Bailey is a Developer Advocate for Kendo UI, a developer, speaker, trainer, screen-caster and much more. He's been slinging code since the late 80’s and doing it professionally since the mid 90's. These days, Derick spends his time primarily writing javascript with back-end languages of all types, including Ruby, NodeJS, .NET and more. Derick blogs atDerickBailey.LosTechies.com, produces screencasts atWatchMeCode.net, tweets as @derickbailey and provides support and assistance for JavaScript, BackboneJS,MarionetteJS and much more around the web.

Comments

Comments are disabled in preview mode.