Telerik blogs

I see a lot of questions about using Backbone and various UI / widget frameworks with it. There seems to be a bit of a fear in some people's minds that they won't be able to use their widget framework of choice if they are using an MV* libraries or framework for building JavaScript apps. Fortunately, this is far from the truth. Backbone and other MV* libraries and frameworks provide a much needed layer of abstraction to separate data from view concerns, but they do not replace or obviate the need for widget frameworks like jQuery UI or Kendo UI. Quite the opposite, really.

Backbone, in particular, doesn't promote a specific widget framework of any kind. It does promote the use of jQuery or similar libraries (such as Zepto), though. This opens up a world of possibilities for widget frameworks that also use jQuery, such as Kendo UI.

There are a few tricks to making Backbone and Kendo UI play nicely together, though. Fortunately these are simple tricks that are easy to understand once you recognize how Backbone deals with DOM elements. But before we get into the details of wiring up Backbone and Kendo UI together, it's important to understand not only where that will happen, by how and why.

Backbone.View And DOM Elements

Backbone uses the concept of a "View" object to manage and manipulate DOM elements in the browser. There's some debate as to whether Backbone.View is acting like a "Controller" in an Smalltalk-80 style MVC framework, a "Presenter" in an MVP framework, or some other flavor of the MV* variations. But the difference between all of these and which one Backbone falls directly in to is less important than understanding that Backbone implements a style of separated presentation. This means that we keep our canonical data source and application logic out of the view layer. This allows the view to focus on it's specific tools and technologies for managing and manipulating what the user sees and how the user interacts with the application.

In the case of Backbone, the Backbone.View object is responsible for managing and manipulating the HTML and DOM that the browser renders on the screen. Anytime we need to update the DOM, read data from the DOM, enable or disable a Kendo UI widget or do any other work with the DOM, we do it through a View instance.

To attach itself to the DOM, every Backbone.View instance has a reference to a DOM element through two attributes: el and $el ("el" is short for "element" - a common naming convention for dealing with DOM elements in JavaScript code). The el attribute is either a string representation of the DOM elements that this view manages, or a jQuery selector that points to some existing DOM elements in the browser window. The $el, then, is a cached jQuery selector object for the view's el. This allows us to use jQuery methods like append and prepend, fadeIn and fadeOut or any other jQuery method we need when manipulating the DOM.

Specifying 'el' As A jQuery Selector

If a jQuery selector is specified as the el when defining the view, the resulting jQuery object will become the view's el and $el:

<body>

  <navigation class="menu">
    <ul>
      <li>Menu Item</li>
      <li>Another Item</li>
    </ul>
  </navigation>

  <header>
    <h2>Article Title</h2>
  </header>

  <article>
    <p>some article content</p>
  </article>​

</body>
<script>
var MyView = Backbone.View.extend({

  // specify a jQuery selector, such as a css class or id
  el: "navigation.menu"

});

// get a view instance
var view = new MyView();

// log the view's $el, the jQuery object for the <article> tag in the above HTML
console.log(view.$el) 
```
</script>

 

This is useful for attaching views to existing HTML that has been rendered on the server or by another framework or widget prior to our Backbone.View instance being created. We can generate our own HTML as well, which is more common when building Single-Page Applications (SPA's) with Backbone.

Generating Our Own 'el'

If we don't specify an el for the view definition, Backbone.View will generate an HTML tag for us - a <div> tag by default. We can specify any tag name we need using the tagName attribute, and any attributes for the tag using the attributes hash, css classes using the className attribute, and css id using the id attribute as well. Once the view instance is created, we have access to the view's el and $el. Then we can provide a render method on the view to generate all of the HTML contents for the view's $el.

var MyView = Backbone.View.extend({

  // generate a <ul> tag for this view
  tagName: "ul",

  // give this `$el` a css class
  className: "menu",

  // render additional HTML contents
  render: function(){
    var html = "<li>a menu item</li><li>another menu item</li>"
    this.$el.html(html);
  }

});

// get a view instance
var view = new MyView();

// render the view to populate it's `el` appropriately
view.render();

// log the view's $el, the jQuery object for the <article> tag in the above HTML
console.log(view.$el) 

// Attach the view's el to the DOM
$(".some-parent-element").empty().append(view.$el);

 

There's a lot that can be done with views, and many different ways to populate the el of the view. How we populate it also makes a difference in how we wire up our plugins and widgets, too. In the first example, the use of a jQuery selector means that we are attaching to DOM elements that already exist in the visible DOM. In the second example, though, we are not attaching the rendered el to the DOM until the very last line where we are using a jQuery selector to find the .some-parent-element, empty out the existing content of that element and then append the view's $el in to it. This is done because a Backbone.View instance is not attached to the visible DOM, by default. We have to attach the view to the DOM ourselves, by either using a jQuery selector for the el or by adding the generated el to the DOM like this example shows.

The need to attach the view to the DOM ourselves has several implications. It gives us a potential performance increase over manipulating existing DOM elements. But it also affects how and when we will initialize and use widget frameworks like Kendo UI.

For more information about the basics of working with Views, rendering with JavaScript template engines, using jQuery selectors for the el and information on structuring them, working with DOM events and more, see the following resources. For now, though, this basic understanding of how to either render a view or use a jQuery selector will be enough.

DOM Dependent / Independent Widgets

There are two basic types of HTML widgets, in my experience: those that are depending on the visible DOM and those that are not. DOM dependent widgets are those that need to know and / or manipulate the actual size or location and ordering relative to other DOM elements. These attributes are only available to the plugin when the element in question is attached to the visible DOM. DOM independent widgets don't need to know exact positioning on the screen, may not need to calculate sizes based on other surrounding or sibling elements, and can generally be set up without having to access information that is only available to visible elements.

This difference might seem arbitrary or unimportant at first glance, but it has a tremendous impact on when and where we wire up our Kendo UI widgets with our Backbone views.

When we build a Backbone.View that is attached to an existing DOM element, it doesn't make much difference when or where we call the .kendo methods. As long as the view is already attached, the Kendo widget will be able to process it correctly.

When we're dealing with a Backbone.View that renders its own content, though, then we have to think about whether or not the Kendo widget we want to use depends on the DOM or not. If it doesn't, then we can call the Kendo widget method any time after the view has been rendered. If the widget is dependent on the DOM, though, we have to wait until after the view's el has been added to the DOM before calling the Kendo widget method.

With that in mind, let's look at a few examples of integrating Kendo and Backbone with both DOM independent and DOM dependent widgets and controls.

Working With Kendo UI's Menu Widget

In the first view example, above, the view is defined with a jQuery selector. Since the HTML elements for this view already exist in the DOM, we could activate any Kendo UI plugin that we want, at any time. For example, if we want to turn the <ul> in to a Kendo Menu, we would only need to add the necessary call to .kendoMenu() on the view's $el. It doesn't make much difference when we call this method since the HTML that is being manipulated is already part of the DOM. We could even put the .kendoMenu() call in the view's initializer:

var MenuView = Backbone.View.extend({

  // select the menu from the DOM
  el: "navigation.menu",

  // The "initialize" method like a constructor. It is called when we call "new MenuView()".
  initialize: function(){

    // find the "<ul>" tag in this view's "el", and turn it in to a menu
    this.$("ul").kendoMenu();

  }

});

// Create the view, and turn the navigation element in to a menu.
var menu = new MenuView();

 

The result can be seen in this JSFiddle.

But the Kendo UI menu is not a DOM dependent widget. The processing it does in order to build the correct CSS and JavaScript for the menu does not need the <ul> elements in the DOM prior to processing it.

var MenuView = Backbone.View.extend({

  tagName: "ul",

  render: function(){

    // "render" a menu of list items
    var content = "<li>Menu Item</li><li>Another Item</li>";
    this.$el.html(content);

    // The rendered items are not yet in the DOM. They are only
    // attached to the view instances. But we can still call
    // KendoUI's ".kendoMenu()" because this widget is not
    // dependent on the visible DOM
    this.$el.kendoMenu();

  }        
});

// Create the view
var menu = new MenuView();

// Render it and convert it in to a Kendo UI menu
menu.render();

// Attach it to the DOM
$("navigation.menu").append(menu.$el);​

The result can be seen in this JSFiddle.

In this view instance, we are generating the menu items in the view's render method. After generating the items, we then call the kendoMenu() function on the view's $el directly. But In the first example, we called kendoMenu() on a jQuery selector that found the <ul> underneath of the view's $el. Why the difference? The answer is in the view's $el itself. In the first example, we are attaching the view directly to the <navigation class="menu"> element. Since Kendo's menu wants to work with a <ul>, we had to find that element within the view's el. In the second example, though, we are having Backbone.View generate a <ul> tag as the view's el directly. This means we don't need to find any child element for Kendo's menu. We can apply the menu directly to the view's el.

In both of these examples, the menu displays correctly because the Kendo Menu is not dependent on the visible DOM. There are some cases where the control or widget is dependent on the DOM though. And trying to display a DOM dependent widget before the view is in the DOM will cause all kinds of problems.

Working With Kendo UI Splitter

Unlike the Kendo Menu control, the Splitter control is DOM dependent. If we set up a Backbone.View that renders the necessary set of <div> elements (in this case, borrowed from the Splitter demo page and converted to an UnderscoreJS template) and try to immediately call the .kendoSplitter() method, the results will not be anywhere near what we expect.

<script type="text/html" id="layout-template">
        <div id="top-pane">
            <div id="horizontal" style="height: 100%; width: 100%;">
                <div id="left-pane">
                    <div class="pane-content">
                        <h3>Inner splitter / left pane</h3>
                        <p>Resizable and collapsible.</p>
                    </div>
                </div>
                <div id="center-pane">
                    <div class="pane-content">
                        <h3>Inner splitter / center pane</h3>
                        <p>Resizable only.</p>
                    </div>
                </div>
                <div id="right-pane">
                    <div class="pane-content">
                        <h3>Inner splitter / right pane</h3>
                        <p>Resizable and collapsible.</p>
                    </div>
                </div>
            </div>
        </div>
        <div id="middle-pane">
            <div class="pane-content">
                <h3>Outer splitter / middle pane</h3>
                <p>Resizable only.</p>
            </div>
        </div>
        <div id="bottom-pane">
            <div class="pane-content">
                <h3>Outer splitter / bottom pane</h3>
                <p>Non-resizable and non-collapsible.</p>
            </div>
        </div>

</script>

<div id="placeholder"></div>​

 

Sidebar: We're wrapping the necessary HTML in a <script> block and giving it a type attribute that the browser doesn't understand. This prevents the browser from trying to execute this script block as JavaScript. The addition of an id attribute allows us to use jQuery selectors to find it quickly, and pull the contents out for rendering in our template.

For information about Underscore.js templates, and why this HTML is wrapped in a <script> block, see the following resources:

The view, then, renders this template and calls the necessary .kendoSplitter methods:

var SplitterLayout = Backbone.View.extend({

    id: "vertical",

    render: function(){

        // compile the template using Underscore.js (a pre-requisite of Backbone)
        var template = $("#layout-template").html();
        var content = _.template(template, {});

        // Stuff the content in to the view
        this.$el.append(content);

        // The content is not yet in the DOM. We can try to call the
        // kendoSplitter function on it, but it won't turn out the way it should.
        this.$el.kendoSplitter({
            orientation: "vertical",
            panes: [
                { collapsible: false },
                { collapsible: false, size: "100px" },
                { collapsible: false, resizable: false, size: "100px" }
            ]
        });

        $("#horizontal").kendoSplitter({
            panes: [
                { collapsible: true, size: "220px" },
                { collapsible: false },
                { collapsible: true, size: "220px" }
            ]
        });
    }

});

// Create the view and render it
var splitter = new SplitterLayout();
splitter.render();

// Add it to the DOM
$("#placeholder").append(splitter.$el);

 

The failing result can be viewed at this JSFiddle.

This code fails miserably. We don't see any of the content. The splitter controls are not showing up correctly, and there is a general sense of something gone wrong, given the bleak visual output of this code.

Instead of trying to run the .kendoSplitter calls immediately after rendering, then, we need to wait until the view has been added to the DOM. Once we've done that, we can call the .kendoSplitter methods and display the splitter controls correctly.

To keep the code related to this view - including the use of the splitter - encapsulated within the view, we'll move the call to .kendoSplitter() to a method of it's own. Then after the view has been added to the DOM, we'll call that method.

var SplitterLayout = Backbone.View.extend({

    id: "vertical",

    render: function(){

        // compile the template using Underscore.js (a pre-requisite of Backbone)
        var template = $("#layout-template").html();
        var content = _.template(template, {});

        // Stuff the content in to the view
        this.$el.append(content);

    },

    showSplitter: function(){    
        // The content is in the DOM now. We can call the kendoSplitter function on it, 
        // and it will turn out the way we expect it to
        this.$el.kendoSplitter({
            orientation: "vertical",
            panes: [
                { collapsible: false },
                { collapsible: false, size: "100px" },
                { collapsible: false, resizable: false, size: "100px" }
            ]
        });

        $("#horizontal").kendoSplitter({
            panes: [
                { collapsible: true, size: "220px" },
                { collapsible: false },
                { collapsible: true, size: "220px" }
            ]
        });
    }

});


// Create the view and render it
var splitter = new SplitterLayout();
splitter.render();

// Add it to the DOM
$("#placeholder").append(splitter.$el);

// Call the view's "onShow" method because we have added it to the DOM,
// meaning it is now visible, or being shown in the browser
splitter.showSplitter();​

 

The working splitter can be viewed at this JSFiddle.

This produces the desired result - a set of splitter controls - and illustrates the problems that can be caused by trying to using a DOM dependent widget before the Backbone.View instance has been attached to the DOM.

Reducing Boilerplate Code

Moving the splitter code to its own method reduces the amount of code in the render method, and makes the setup of the splitter code reusable within the view. But we're left with a lot of boilerplate code for the view rendering, placement in the DOM, and calling of the code to set up the splitter. Any application of even minimal size will quickly run in to duplication of this code and logic, and either copy & paste a lot of the same code or be drawn to an existing application framework such as MarionetteJS to reduce the boilerplate.

The good news with Marionette - an open source application framework for scaling Backbone application with event-driven, modular and composite application architectures - is that it not only reduces the boilerplate for rendering, but also for the timing of when the view has been added to the DOM, to call DOM dependent code. We can take advantage of the code that is built in to the Region object to show a view in a specific DOM element and call an onShow method on the view automatically. We can then update the view to call the splitter code from onShow, take advantage of Marionette's built-in UnderscoreJS template rendering, and reduce the amount of code we need to write, significantly.

var SplitterLayout = Marionette.ItemView.extend({
    template: "#layout-template",

    id: "vertical",

    // The "onShow" method is called by Marionette's region, after the
    // view has been placed in the DOM
    onShow: function(){    

        // The content is in the DOM now. We can call the kendoSplitter function on it, 
        // and it will turn out the way we expect it to
        this.$el.kendoSplitter({
            orientation: "vertical",
            panes: [
                { collapsible: false },
                { collapsible: false, size: "100px" },
                { collapsible: false, resizable: false, size: "100px" }
            ]
        });

        this.$("#horizontal").kendoSplitter({
            panes: [
                { collapsible: true, size: "220px" },
                { collapsible: false },
                { collapsible: true, size: "220px" }
            ]
        });

    }

});


// Create a region to manage the content of the "#placeholder" element
var region = new Marionette.Region({
    el: "#placeholder"
});

// Create the view
var splitter = new SplitterLayout();

// Show the view in the region
region.show(splitter);

 

The results of this code can be found at this JSFiddle.

The use of Marionette provides several benefits in this case, but the one that we care about the most is the call to the view's onShow method after the view has been added to the DOM. It's the Marionette.Region object that handles calling onShow on the view, if that method exists, because it's the Region that manages pushing the view in to the DOM. This has effectively automated the code from the previous example.

For more information about MarionetteJS, Regions, and what it can do for your applications, see the following resources:

The examples I've shown so far are useful but also trivial. Generating or using a fixed set of HTML elements makes it easy to see how we can integrate Backbone and Kendo UI. Most developers won't find this terribly useful, beyond a simple academic exercise and basic example, though. When it comes down to it, applications run on data and that data is often used to build the HTML structure which our Kendo UI widgets will use. So, let's see a real example of how this would work - something worthy of being used in a production application.

Generating A PanelBar With Models, Collections And MarionetteJS

Backbone supports two basic types of data structures: Backbone.Model and Backbone.Collection. A model is used as the canonical data source for Backbone based application, and collections are just groups of similar or related models. When we get data from a server or read data from the DOM, we stuff it in to models and collections. Then when we need to update the DOM to show some data, we pass the models and collections to our views and have the views render templates that include the data from the models and collections. Using models and collections to store and retrieve data gives us a lot of flexibility in how we process the data, separate from how we process the user interface or application workflow. It also gives us an easy to way to integrate data in to Kendo UI widgets, through the Backbone.View objects.

For example, it's common to build lists of things with Backbone.Collection instances. Whether it's a menu view, a drop list or panel bar, Backbone.Collection can provide the data we need to build the HTML structure for our Kendo controls, easily.

To construct a Kendo PanelBar with Backbone, we need a few things:

  • A Collection of Models with our data
  • A way to group the models in to panels
  • A way to render the list of models as the appropriate HTML elements

This can be achieved through the use of a single Backbone.View, but it gets messy, quickly. The work that it takes to build the necessary hierarchy of HTML controls typically ends up in the view template, which pollutes the separation of Backbone.View logic vs HTML being generated. This makes it difficult to know where to look when something goes wrong, makes the templates difficult to understand, and generally makes it hard to work with the data and controls. Instead, we should look at creating a hierarchy of views that match our data hierarchy.

Backbone.View doesn't have any direct support for building a hierarchy of views, out of the box. But it also doesn't prevent that from happening, either. It's not terribly difficult to iterate through a collection within a view, and then render a new view instance for each item in the collection. But this code does get somewhat tedious, as building the necessary hierarchy involves iterating the collection, creating view instances, handing models to views, storing the view instance and later destroying the view instances in order to avoid memory leaks and clean up view zombies.

Rather than go through all of the trouble to do this ourselves, we can again use MarionetteJS to handle the bulk of the work for us. In this case, we'll use a more than one of built-in View types that Marionette provides:

  • A CompositeView to render an individual book category, and the list of books within the category
  • A CollectionView to render the list of categories
  • An ItemView to render each individual model from the collection

The HTML templates that we need for this setup are quite simple compared to the JavaScript that will be written. We only need two templates: one for the category view, which is used to create the panels; and one for the book view, which displays the book information.

<script type="text/html" id="book-view-template">
    <div><strong>Title:</strong> <%= title %></div>
    <div><strong>Author:</strong> <%= author %></div>
</script>

<script type="text/html" id="category-panel-template">
    <span><%= name %></span>
    <div class="book-list"></div>
</script>

<div id="placeholder"></div>

 

The JavaScript is more involved, though. We need to build a Marionette.CollectionView that will render the list of categories as <li> tags in to a <ul> for the PanelBar to use. Each of the category views will be populated with the #category-panel-template which contains a <span> that becomes the title of the panel, and a <div> which becomes the contents of the panel. Within the panel's contents, then, we need a book view that will render the information for each Book model.

// View Definitions
// ----------------

// Define an Book View to render the individual book
var BookView = Marionette.ItemView.extend({
    className: "book",

    template: "#book-view-template"
});

// Define a Category View which will render the book
// Categories as panels, rendering the collection of book
// for each category within that panel.
//
// A Marionette.CompositeView is a view that can render
// both a model with template, and a collection of models
// that get placed within the rendered template.
var CategoryView = Marionette.CompositeView.extend({

    // the HTML tag name for this view
    tagName: "li",

    // the template to render for the category
    template: "#category-panel-template",

    // the view type to render for the collection of books
    itemView: BookView,

    // where to place the list of books, within the rendered template
    itemViewContainer: ".book-list",

    initialize: function(){
        // get the list of books for this category and assign it to
        // this view's "collection" so that it will render as the
        // specified itemView instance
        this.collection = this.model.books;
    }

});

// Define a Category List view that will render the 
// collection of categories as CategoryView instances
var CategoryListView = Marionette.CollectionView.extend({

    // render each model in to a category view
    itemView: CategoryView,

    // tell to to render a "ul" tag as the wrapper
    tagName: "ul",

    // after it's all rendered and has been added to the DOM,
    // initialize the KendoUI PanelBar
    onShow: function(){
        this.$el.kendoPanelBar({
             expandMode: "single"
        });
    }

});


// Model And Collection Definitions
// --------------------------------

// Define a Book model to hold info about books
var Book = Backbone.Model.extend({});

// Define a collection of books
var BookCollection = Backbone.Collection.extend({

    // tell it to use the Book as the type of model
    model: Book

});

// Define a category model to hold a collection of books
var Category = Backbone.Model.extend({});

// Define a category collection to hold a list of categories
var CategoryCollection = Backbone.Collection.extend({
    model: Category 
});

// Book And Category Data Structure And Instances
// ----------------------------------------------

var scifiBooks = new BookCollection([
    { 
        id: 1,
        title: "Neuromancer",
        author: "William Gibson",
    },
    {
        id: 2,
        title: "Snow Crash",
        author: "Neal Stephenson"
    }
]);

var scifiCategory = new Category({name: "Science Fiction"});

// attach the scifi books to this category
scifiCategory.books = scifiBooks;


var vampireBooks = new BookCollection([
    {
        id: 3,
        title: "Abraham Lincon: Vampire Hunter",
        author: "Seth Grahame-Smith"
    },
    {
        id: 4,
        title: "Interview With A Vampire",
        author: "Ann Rice"
    }
]);

var vampireCategory = new Category({name: "Vampires"});

// attach the vampire books to the category
vampireCategory.books = vampireBooks;


// build a collection of categories
var categories = new CategoryCollection([scifiCategory, vampireCategory]);



// Application Initialization
// --------------------------

var categoryListView = new CategoryListView({
    collection: categories    
});

var region = new Marionette.Region({
    el: "#placeholder"
});

region.show(categoryListView);​

The result of this code can be viewed in this JSFiddle.

This example is hard-coding the data and structure to be used for the collection of book categories, and books. A production system would generally load this information from a back-end data source and use one of many available Backbone plugins to build the model and collection hierarchy. The use of MarionetteJS, though, would generally remain the same as it provides a significant reduction in the amount of code that it takes to produce a hierarchical rendering of data.

For more information on using Backbone.Model and Backbone.Collection, building hierarchies with these, and more, see the following resources:

KendoUI, Backbone And DOM Dependency/Independency

Understanding the difference between DOM dependent and DOM independent controls is critical to integration with Backbone. Equally as important, though, we need to understand how and when a Backbone.View is attached to the DOM so that we can determine the most appropriate time to call any DOM dependent code. We've seen example of the Menu control, Splitter control and PanelBar so far, showing examples of how to integrate a DOM independent and dependent widget in to a Backbone.View. But what about the remaining Kendo widgets and controls? It would be nice to know which of them are DOM dependent and which are DOM independent.

From my experience with Kendo, I think it's safe to say that most of them are DOM independent. I've successfully used many of the controls without having to display the contents in the DOM first. This obviously isn't true of all controls, though, and may even change within a given control, depending on the options used. The use of a separate method on the view to set up the DOM dependent code makes it easy to get the widgets to work correctly, and the introduction of MarionetteJS reduces the amount of work that we have to do. Given that, I generally take the approach of always putting my widget code in to the onShow method of my view. This prevents me from having to worry about whether or not the view is attached to the DOM or not. Of course, there are exceptions to this, as there are with any development technique. But this should get you up running with Backbone and Kendo pretty quickly.

About the Author
has been an independent consultant, developer, trainer, speaker and author for the last 2 years. Beginning in January of 2013, though, he will be joining the Kendo UI team as a Developer Advocate. 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 at DerickBailey.LosTechies.com, produces screencasts at WatchMeCode.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.