// Kendo-Backbone // -------------- // v0.0.6 // // Copyright (c)2013 Telerik. All Rights Reserved. // Distributed under Apache 2.0 license // // http://kendoui.com (function(global, kendo){ "use strict"; // add a backbone namespace from which // to hang everything kendo.Backbone = kendo.Backbone || {}; // Kendo-Backbone Model // -------------------- // // Wrap a Backbone.Model in a kendo.data.Model (function () { var Model = kendo.data.Model; function wrapBackboneModel(BackboneModel, fields) { return Model.define({ fields: fields, init: function(model) { if (!(model instanceof BackboneModel)) { model = new BackboneModel(model); } Model.fn.init.call(this, model.toJSON()); this.backbone = model; }, set: function(field, value) { Model.fn.set.call(this, field, value); this.backbone.set(field, value); } }); } kendo.backboneModel = wrapBackboneModel; })(); // Kendo-Backbone Collection // ------------------------- // // Wrap a Backbone.Collection in a kendo.data.ObservableArray (function () { var Model = kendo.data.Model, ObservableArray = kendo.data.ObservableArray; function wrapBackboneCollection(model) { return ObservableArray.extend( { init: function(collection) { ObservableArray.fn.init.call(this, collection.models, model); this.collection = collection; }, splice: function(index, howMany) { var itemsToInsert, removedItemx, idx, length; itemsToInsert = Array.prototype.slice.call(arguments, 2); var removedItems = kendo.data.ObservableArray.fn.splice.apply(this, arguments); if (removedItems.length) { for (idx = 0, length = removedItems.length; idx < length; idx++) { this.collection.remove(removedItems[idx].backbone); } } if (itemsToInsert.length) { for (idx = 0, length = itemsToInsert.length; idx < length; idx++) { this.collection.unshift(itemsToInsert[idx].backbone); } } return removedItems; } }); } kendo.backboneCollection = wrapBackboneCollection; })(); // BackboneTransport // ----------------- // // INTERNAL TYPE // // Define a transport that will move data between // the kendo DataSource and the Backbone Collection kendo.Backbone.BackboneTransport = (function(){ "use strict"; // Constructor Function function Transport(colWrap){ this.colWrap = colWrap; }; // Instance methods. // add basic CRUD operations to the transport _.extend(Transport.prototype, { create: function(options) { var data = options.data; // create the model in the collection this.colWrap.create(data, function(model){ // tell the DataSource we're done options.success(model); }); }, read: function(options) { options.success(this.colWrap.collection.toJSON()); }, update: function(options) { // find the model var model = this.colWrap.collection.get(options.data.id); // update the model model.set(options.data); // tell the DataSource we're done options.success(options.data); }, destroy: function(options) { // find the model var model = this.colWrap.collection.get(options.data.id); // remove the model this.colWrap.collection.remove(model); // tell the DataSource we're done options.success(options.data); } }); return Transport; })(); // Backbone.Collection Adapter // --------------------------- // // INTERNAL TYPE // // Wrap a Collection with DataSource configuration so that // the two-way integration can occur without infinite loops kendo.Backbone.CollectionAdapter = (function(){ "use strict"; // Constructor function function Adapter(collection, dataSource){ this.collection = collection; this.dataSource = dataSource; this.listenTo(this.collection, "add", this._addToDataSource); this.listenTo(this.collection, "remove", this._removeFromDataSource); this.listenTo(this.collection, "reset", this._resetDataSource); }; // Instance methods _.extend(Adapter.prototype, Backbone.Events, { create: function(data, cb){ if (this.addFromCol){ // gate the add that came through the collection directly cb(data); } else { this.addFromDS = true; // ensure the id is cleared out, not just // an empty string if (!data.id){data.id = null} // add the model to the collection, and // for the return so that we can get the // id from the new model, and send it to // the datasource this.collection.create(data, { wait: true, success: function(model){ cb(model.toJSON()); } }); this.addFromDS = false; } }, _addToDataSource: function(model){ // gate the add that came through the datasource directly if (!this.addFromDS){ this.addFromCol = true; var data = model.toJSON(); this.dataSource.add(data); this.addFromCol = false; } }, _removeFromDataSource: function(model){ var dsModel = this.dataSource.get(model.id); if (dsModel){ this.dataSource.remove(dsModel); } }, _resetDataSource: function(){ this.dataSource.read(); } }); return Adapter; })(); // Kendo UI: kendo.backbone.DataSource // ----------------------------------- // // An adapter that wraps around a // `Backbone.Collection` as the underlying data store and // transport for a `kendo.data.DataSource`. This will provide basic // data-binding functionality for Kendo UI widgets and controls, such // as grids, list views, etc. kendo.Backbone.DataSource = (function($, kendo, _) { "use strict"; // kendo.Backbone.DataSource // ----------------------------------- // Define the custom data source that uses a Backbone.Collection // as the underlying data store / transport var DataSource = kendo.data.DataSource.extend({ init: function(options) { // build a collection wrapper for the backbone.collection var collection = options.collection; var colWrap = new kendo.Backbone.CollectionAdapter(collection, this); // configure the Backbone transport with the collection var bbtrans = new kendo.Backbone.BackboneTransport(colWrap); _.defaults(options, { transport: bbtrans }); // initialize the datasource with the new configuration options = setupDefaultSchema(options, collection); kendo.data.DataSource.prototype.init.call(this, options); } }); // Setup default schema, if none is provided function setupDefaultSchema(target, collection){ // build the schema.model, one step at a time, // to ensure it is not replaced, and ensure it is // properly set up in case parts of a schema or model // are provided by the specific application needs _.defaults(target, { schema: {} }); _.defaults(target.schema, { model: {} }); // set an id field based on the collection's model.idAttribute, // or use the default "id" if none found _.defaults(target.schema.model, { id: getPrototypeOf(collection).model.prototype.idAttribute || "id" }); return target; } // shim for Object.getPrototypeOf // based on http://ejohn.org/blog/objectgetprototypeof/ function getPrototypeOf(target){ if (Object.getPrototypeOf){ return Object.getPrototypeOf(target); } else { return target.__proto__ || target.constructor.prototype; } } return DataSource; })($, kendo, _); // Kendo UI: kendo.Backbone.ViewEvents // ----------------------------------- // // Handle Kendo UI Widget and control events from a // Backbone.View instance. // // Configure event definitions using a `kendoUIEvents` // object, similar to the standard Backbone.View `events` // configuration. Then call the `kendo.Backbone.ViewEvents.delegate(view)` // method after the view has been rendered and the Kendo UI // controls have been instantiated. When the Backbone.View // instance is being cleaned up, call the // `kendo.Backbone.ViewEvents.undelegate(view)` method to // clean up the event handlers. kendo.Backbone.ViewEvents = (function($, kendo, Backbone, _) { var eventSplitter = /^(\S+)\s*(.*)$/; var eventConfigName = "kendoUIEvents"; var m; var ViewEvents = { delegate: function(view) { this._processEvents(view, function(widget, eventName, method){ m = method; widget.bind(eventName, method); }); }, undelegate: function(view){ this._processEvents(view, function(widget, eventName, method){ widget.unbind(eventName); }); }, _processEvents: function(view, cb){ var events = _.result(view, eventConfigName); if (!events){ return; } for (var key in events) { var method = events[key]; if (!_.isFunction(method)) { method = view[method]; } if (!method) { continue; } var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, view); var element = view.$(selector); var widget = kendo.widgetInstance(element, kendo.ui) || kendo.widgetInstance(element, kendo.mobile.ui) || kendo.widgetInstance(element, kendo.dataviz.ui); cb.call(this, widget, eventName, method); } } }; return ViewEvents; })(jQuery, kendo, Backbone, _); })(this, kendo);