Managing DOM Widgets And Controls - MANAGING THE DOM - Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

Building Backbone Plugins: Eliminate The Boilerplate In Backbone.js Apps (2014)

PART 2: MANAGING THE DOM

With the ability to quickly and easily create new view types, the process of managing where and when the views are displayed now seems quite cumbersome. Having jQuery selectors riddled throughout the code makes it difficult to change the DOM structure without breaking a lot of the app. It also requires a lot of duplication of selectors.

Managing widgets and controls, and figuring out a consistent and simple way to place views in the DOM and remove them from the DOM is as important as the ability to define views. Part 2, then, will show you how to create a few simple objects to manage DOM references, and show and swap out views. Along the way, you will update your views with some new options, as well. You will also combine the ability to define views and manage the DOM, in to a single view type called a Layout.

Chapter 7: Managing DOM Widgets And Controls

Most Backbone applications either use jQuery or Zepto as their DOM manipulation of choice. I tend to use jQuery as it’s supported across more browsers and has more features – though it is a little heavier in terms of download size (and maybe performance). I also use a lot of jQuery plugins for various widgets and controls, to create specific effects, etc. It’s generally easy to do, as Backbone’s views provide direct access to a jQuery element as this.$el. From there, I can call standard jQuery code and plugins.

There are some common patterns I’ve noticed for doing this, too. Specifically, when to call a particular jQuery function or plugin largely depends on the purpose of that function and sometimes depends on how that function or plugin is built.

DOM Dependent/Independent

At a very basic level, jQuery manipulations of the DOM can fall in to one of two categories:

1. DOM-dependent

2. DOM-independent

Many of the things I do with jQuery code depend on the HTML that I’m manipulating being in the DOM already. For example, it doesn’t make much sense to call “.slideUp” if the HTML we’re manipulating isn’t currently displayed. The animation that this method causes would still run, but we wouldn’t see anything. The end result would not be visible and we would have wasted the browser’s execution time on this animation. I generally lump visual changes and animations in to the DOM-dependent category because it doesn’t make sense to use these methods if the HTML is not in the DOM yet.

On the other hand, some of the calls I make don’t need the HTML to be part of the DOM. In those cases, I can work with document fragments or even raw strings. I often call .hide() on HTML fragments before they are attached to the DOM, for example. I do this so that when the HTML fragments are finally added to the DOM, they won’t be visible to the user. Then, after I’ve ttached the HTML to the DOM, I can call .slideUp() or any of a number of other animation methods to cause the content to be displayed. I call these methods DOM-independent because they can be called whether we are working with a document fragment that only exists in memory, or working with an element that is already part of the visible DOM.

Of course there are a lot of methods in jQuery that don’t deal with HTML elements or the DOM at all. These would clearly fall in to the DOM-independent category, but I’m not going to address those right now. But even if these two categories are a simplified way to view jQuery, it is generally useful. It helps me understand when I should call certain methods vs others, and gives me a better idea of where to integrate jQuery calls with my Backbone applications.

Simple Manipulations And Events

Most of the simple manipulations that I perform with jQuery are DOM-independent. I can call them and manipulate a document fragment or a DOM element directly, whenever I want to. This includes not only showing / hiding HTML elements, but also adding and removing them, attaching events, and more.

Consider this example:

1 Backbone.View.extend({

2 // this is the default for backbone views

3 // tagName: "div"

4

5 render: function(){

6 var html = $("<ul>");

7

8 html.append("<li>foo</li>");

9 html.hide();

10

11 this.$el.html(html);

12 }

13 });

With Backbone.View, an HTML element is often created with a view instance. This elements becomes the this.$el attribute on the view. In the render method, then, I am using jQuery to create additional document fragments. For performance reasons (since it’s possible that the view’s $elwas attached to the DOM without the view knowing it), I’m populating the document fragment with content entirely in memory. This includes the addition of some data and calling .hide() on the fragment. I can manipulate the fragment with DOM-independent functions as much as I want at this point.

Once I’ve completed the rendering of the HTML content, I stuff it all in to the view’s $el. The $el is then attached to the DOM and I can begin using DOM-dependent methods.

I can also attach DOM level events to the fragments that I’ve generated. Since jQuery is turning our strings in to proper document fragments for me, DOM events are available. That means I can call methods like .click() and .blur() on the view’s $el.

1 Backbone.View.extend({

2 render: function(){

3 // ... build the $el here

4

5 this.$el.click(function(e){

6 // handle the click event here

7 });

8

9 }

10 });

But I generally consider this to be an anti-pattern. There may be some cases where I need to manually attach events, and it can be done here in the render method. For the most part, though, I should be using Backbone’s declarative events on my views.

DOM Events And Simple Animations

Backbone’s View object abstracts a little bit of jQuery’s event system for me through the use of the declarative events.

1 Backbone.View.extend({

2 events: {

3 "click": "showHide"

4 },

5

6 showHide: function(){

7 this.$("ul").slideToggle();

8 },

9

10 // render: function ...

11 });

Surprisingly, DOM events are partially DOM-independent. As I said above, I can add the events to the document fragments before they become part of the DOM. I can even trigger them manually without them being part of the DOM. In general, though, the DOM fires the events for me when the user interacts with the DOM in the manner that I’ve specified.

Having the user to fire DOM events through DOM interactions (clicks, blurs, changes, etc) is DOM-dependent, of course. The view must attach any HTML structure that it needs to the DOM before the user can interact with them, to fire these events. This is why I said that the events are partially DOM-independent. They can be attached to a document fragment, but they generally need to be in the real DOM for user interactions to occur.

Often when a user clicks or changes a DOM element, I want to respond to this by manipulating the DOM in a visual manner. For example, I might want to call .hide(‘slow’) on a portion of the view’s $el in order to hide some items on the screen with a simple animation. The above example shows this. When I click on the top level div, the child ul is shown or hidden using the “slideToggle” animation.

jQuery Plugins And UI Controls

There are a large number of jQuery UI controls available, including the popular jQueryUI suite, commercial products such as Kendo UI, and many open source projects that take advantage of jQuery’s infrastructure. I’ve used many different control suites and plugins with Backbone, and I have found that they generally integrate the same way.

Most jQuery based controls are partially DOM-independent. I can call the plugin method to get it started before the document fragment is attached to the DOM. Once the document fragment has been configured with the plugin’s code and additional structure, though, the plugin often becomes DOM-dependent. In addition to events, as discussed above, control suites and widgets are often visual in nature and an initialized plugin that is not attached to the DOM won’t be visible.

As an example of a control / plugin, I can convert a ul list in to a menu structure using Kendo UI. To do this, I can call .kendoMenu() during the render method of a view. Or, if I’m using a view layer such as the one developed in the previous chapters, in the onRender callback.

Given an HTML template like this:

1 <script type="text/html" id="menu-template">

2 <li>foo</li>

3 <li>bar</li>

4 <li>baz</li>

5 </script>

I can add the .kendoMenu() call like this:

1 var MyView = Backbone.View.extend({

2 tagName: "ul",

3 template: "#menu-template",

4

5 onRender: function(){

6 this.$el.kendoMenu();

7 }

8 });

9

10 // ... somewhere else in the app

11 var view = new MyView();

12 view.render();

13 $("#someDiv").html(view.el);

Once the Kendo menu is configured, the view’s $el has to be attached to the DOM (if it isn’t already). When that happens, I’ll see the menu structure on screen.

DOM-Dependent UI Controls

There are plenty of jQuery controls and plugins that are entirely DOM-dependent, as well. With these controls, I could not initialize the plugin prior to the view’s $el being added to the DOM.

The “easy” solution to this is to have the view attach it’s “el” directly to the DOM in some fashion. But as I’ve talked about before, this can be a bad idea. Instead, it only takes a few extra lines of code to allow a callback method that be called after the view has been added to the DOM.

1 var AnotherView = Backbone.View.extend({

2 onShow: function(){

3 this.$el.kendoSplitter({

4 // splitter options go here

5 });

6 }

7 });

In this example, I’ve added a method to the view called onShow. This method contains the call to the Kendo UI Splitter, which is DOM-dependent. It’s important to note that the onShow method doesn’t get called from within the view, directly. This is because it’s not the view’s responsibility for adding itself to the DOM. This is the responsibility of the code that needs the view. For example:

1 var view = new AnotherView();

2 view.render();

3 $("#someDiv").html(view.$el);

4

5 if (_.isFunction(view.onShow)){

6 view.onShow();

7 }

The code that needs the view will instantiate it, call render and attach the resulting view.$el to the DOM. Next, it checks for the existence of an onShow method in the view. If that method does exist, it gets called. Since the code that is using the view knows that it has already added the view to the DOM, it knows when to call the onShow method. This allows me to write code that relies on DOM-dependent functionality without the view having to know if it has been added to the DOM or not.

Extracting The onShow Method Call

I’ve written this onShow method and the code to call it more times than I can count. It has become such a ubiquitous part of my code that I added the check for and call to the function to the Region object in MarionetteJS. Adding it here allows me to take advantage of the onShow semantics while making it easy for me to know when and where this method will be called.

Lessons Learned

Working with jQuery plugins can be simple and it can be frustrating at times. There are some tricks that can be used to generally keep it on the simple side, though. A method such as onShow and proper timing of this method being called can reduce the amount of complexity in an application by standardizing when and where jQuery plugins are initialized.

Understand Interactions And Timing

The DOM in an odd beast at times, and working with it at the right or wrong moment can spell the difference between success and failure for an app. It is important to understand when to do certain things in apps, based on the needs of the DOM and interactions with it.

Context Is King

The patterns and implementations that I’ve shown here are entirely contextual. There will be scenarios where some DOM-independent code should not be called until after the view’s “el” is part of the DOM, for example. Use your judgement, experience and trial-and-error with the various methods and functions that you have to call to get your behavior correct.

The use of a Backbone.View with jQuery code that manipulates the DOM creates a situation where a line of code may work in one spot for one view but need to be moved to another spot for another view. There are no silver bullets in software development. The context of the code being written must be accounted for at all times.

Use Existing Abstractions When Possible

Backbone has a lot of great abstractions and helpers built in to it. Taking advantage of these will help to reduce the amount of code that is needed without having to write additional abstractions. Take the time to read and understand the Backbone source code, so that the existing abstractions can be used to their full capabilities.