In my first post about learning kendo.data.DataSource , a commenter asked me about the differences between this and a Backbone.Collection. He also wondered if there would be any value in creating an adapter that could turn a Backbone.Collection in to a Kendo UI DataSource.

The value that a DataSource wrapper provides, beyond integrating Backbone and Kendo UI from a visual perspective, is in the data-binding and other logic that Kendo UI provides in it's widgets and control suite. I've integrated them in previous projects for clients, but it has always required custom code for each situation that I was tying together. I don't like repeating code and I don't like having to manually wire things together. So it makes sense to build an adapter between a Backbone.Collection and Kendo UI DataSource.

Building A Custom Transport

The DataSource object has a lot of potential entry points for extension and customization. This can make it difficult to know exactly what to start with and what direction to head. But in looking through the documentation and demos , I noticed that there's a trend in changing how the DataSource works with different types of back-end data: a transport object.

According to the documentation, a transport

Specifies the settings for loading and saving data. This can be a remote or local/in-memory data.

This sounds like exactly what I want. Rather than having to deal with customizing the data-binding and other aspects of creating a full fledged DataSource, I can stick with the core CRUD operations of workign with my data's "back-end". In this, I'll just be pushing everything to and from a Backbone.Collection.

To get started, I dug through the documentation for transports and looked at a few examples that some co-workers sent to me. It turns out there is very little that I really need to do, though there is a lot that I could do.

To build a completely custom transport, I only need to provide an object literal to my DataSource instance, with basic CRUD operation methods:

A Custom Transport

var dataStore = new kendo.data.DataStore({

  transport: {
    create: function(options){
      // create a new entity with the `options.data` here
      // call `options.success(entity)` when I'm done
    },

    read: function(options){
      // send back all data as a simple JSON document or object literal
      // options.success(myData);
    },

    update: function(options){
      // update an existing entity with the `options.data` here
      // call `options.success(entity)` when I'm done
    },

    destroy: function(options){
      // destroy an entity identified with the `options.data` here
      // call `options.success(entity)` when I'm done
    }
  }

});

It can be as simple as this, but I need a little more than providing an object literal for a transport in my DataSource. I want to encapsulate all of the core options that I need for my Backbone DataSource while still allowing things to be customized. I also need to keep track of the actual Backbone.Collection being used as the backing store for the DataSource. To do all of this, then, I decided to create a custom class called kendo.Backbone.DataStore.

An Outline For kendo.Backbone.DataSource

At a high level, my custom DataSource type largely looks like the transport that I showed above. But instead of creating an object literal for it, I'm creating a new constructor function that can be used to create instances of the transport.

Outline Of BackboneTransport

var BackboneTransport = function(collection){
  this._collection = collection;
};

_.extend(BackboneTransport.prototype, {
  create: function(options){ /* ... */ },

  read: function(options){ /* ... */ },

  update: function(options){ /* ... */ },

  destroy: function(options){ /* ... */ },
});

I'm creating a new constructor function because I need the transport to hold on to the Backbone.Collection reference that I send to it, and I want every instance of my BackboneTransport to have it's own reference to the correct Backbone.Collection.

Note that I'm also taking advantage of JavaScript's prototypal inheritance in adding my CRUD methods to the prototype. To do this, I'm using the underscore.js extend method. This method allows me to easily add methods and data to an existing object using an object literal. In this case, I'm adding my CRUD methods to the BackboneTransport prototype, making these methods available to every instance of BackboneTransport.

As I said before, I want to provide a few default options for my DataSource and not just provide a transport implementation. To do this, I'm creating a kendo.Backbone.DataSource object that extends from kendo.data.DataSource.

A Custom DataSource

kendo.Backbone = kendo.Backbone || {};

kendo.Backbone.DataSource = kendo.data.DataSource.extend({
  init: function(options) {
    var bbtrans = new BackboneTransport(options.collection);
    _.defaults(options, {transport: bbtrans, autoSync: true});

    kendo.data.DataSource.fn.init.call(this, options);
  }
}); 

Here, I'm creatiing a namespace for kendo.Backbone so that I can attach my DataSource object.

I'm then extending from kendo.data.DataSource to create a custom DataSource that inherits directly from the original. This works very similarly to Backbone's extend method, but is provided by Kendo UI's DataSource in this case.

Within the init function (the Kendo UI equivalent of Backbone's initialize function), I'm creating a new instance of my BackboneTransport. I'm using this to set up a few default options and then forwarding the options to the super-type's init function in order to get the DataSource completely up and running.

The use of underscore's defaults method allows me to specify values for my options that will be used if those values have not already been provided in the options object. In other words, I can override these using the constructor for my DataSource:

Override Default Options

new kendo.Backbone.DataSource({
  autoSync: false // override the default that I set
});

Normally, I won't need to override this option, though. In fact, I usually want the autoSync option set to true so that any change made to the DataSource will be pushed to my BackboneTransport instance immediately.

Now that we have the outline for the custom Transport and DataSource covered, let's look at what it takes to fill in the CRUD operations for the transport.

Create

Creating a new model is fairly easy. Backbone.Collection has an add method that we can use for now. There is a create method as well, but this method will push data back to the server. Right now, I want to ignore the server communication that Backbone.Collection provides, and stick with an in-memory collection.

One thing we do need, though, is a "id" field and incrementing value. To keep this simple, we'll use a simple integer value and increment it every time we create a new model.

Create

  create: function(options){
    // increment the id
    if (!this._currentId) { this._currentId = this._collection.length; }
    this._currentId += 1;

    // set the id on the data provided
    var data = options.data;
    data.id = this._currentId;

    // create the model in the collection
    this._collection.add(data);

    // tell the DataSource we're done
    options.success(data);
  }

Read

Reading data is the easiest of all the code we have to add to the custom transport. Backbone.Collection provides a toJSON method that returns the models as an array object object literals, which is what the DataSource wants.

Read

  read: function(options){
    options.success(this._collection.toJSON());
  }

Update

Updating a model is fairly simple as well, but it does need a few steps to get it done. First, we have to find the model that we're trying to update. Since we are handed an object literal as options.data, we need to use the id that we added to the data to find the model. Once we have that, we can update the model as we normally would with Backbone.

Update

  update: function(options){
    // find the model
    var model = this._collection.get(options.data.id);

    // update the model
    model.set(options.data);

    // tell the DataSource we're done
    options.success(options.data);
  }

Destroy

Destroying a model is also fairly simple. As I noted in the "create" function's description, I'm only concerned with an in memory representation of the Backbone.Collection right now. Considering this, I'll avoid using the methods that actually destroy through the Backbone.Collection server communications. Instead, I'll just use the remove method to remove the model from the Collection.

Once again, we have to find the model by id since we are handed an object literal representation of the model.

Destroy

  destroy: function(options){
    // find the model
    var model = this._collection.get(options.data.id);

    // remove the model
    this._collection.remove(model);

    // tell the DataSource we're done
    options.success(options.data);
  }

Putting It All Together: A Demo App

Now that we have our custom transport and DataSource built, it's time to put together a simple demo application. As with my first two blog posts on the DataSource, I'm going to create a very simple "to do" application.

To really illustrate the value of my custom DataSource, though, I'm not going to manually run methods and event handlers as I did previously. Instead, I'm going to stick to the default behaviors that the Kendo UI Grid provides. This will better illustrate how simple it was to provide the functionality that Kendo UI needs, when creating a custom DataSource.

(You can see the full code and the functioning demo app at this JSFiddle.)

As you can see, I didn't get the usability factor set quite right in this sample. I chose to leave the grid alone and not provide any custom usability enhancements, though. I wanted to illustrate how a few methods can be used to provide a completely new data transport for a Kendo UI DataSource, and show that it is possible, and fairly simple, to wrap a DataSource around a Backbone.Collection.

The First Steps Of A Long Journey

The code that I've shown here is far from complete and definitely not ready for production use. But it does illustrate some of the key concepts and shows that it is possible and some-what simple to do. It also paves the way for future work that I will be engaged in.

There is a lot of work left to do, though. Right now this only provides synchronization one-way: from the DataSource to the Backbone.Collection. But what happens when the Backbone.Collection is reset with new data? And what about persisting the data back to a server, through the Collection's "save" method? There's much work to be done, but this is a good first step.


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.

Related Posts

Comments

Comments are disabled in preview mode.