Managing A Modal Dialog With Backbone And Marionette
My current client project is using Twitter Bootstrap, and I have to say I’m loving it. It provides a simple way to get started with common layout and design elements, and includes a handful of UI widgets and features that are easy to use as jQuery plugins. But enough about that… I want to manage a modal dialog that displays my Backbone view, which is a notoriously frustrating problem no matter what UI framework you’re using, it seems.
The Problem: Moving DOM Elements
The core of the problem that people run in to when using a modal dialog is that the modal plugin removes the DOM element that wraps the modal, from the DOM. It usually gets added to some special holding location where the modal plugin can guarantee that the element won’t be visible until the modal dialog is opened. I’m over-generalizing this a bit, but many of the modal dialogs work this way or in a similar manner.
The problem that this usually causes is that a Backbone view will lose it’s event handling when the DOM element gets moved around by the modal dialog. When the modal dialog removes the view’s `el` from the DOM, the `events` configuration is lost because the DOM element has been moved or removed from the DOM and jQuery had to let go of the events. When the `el` is re-added to the DOM for displaying it as a modal, then, the `events` are not re-attached.
This is usually caused by a developer using the Backbone view itself as the modal dialog – which is not what you want to do.
The Solution: Don’t Modal A Backbone View
No, really. That’s the solution.
I’m not saying you can’t use a Backbone view in a modal. I’m saying you shouldn’t try to modal the view itself. Instead, you should create a simple wrapper div for the modal plugin to use and then populate the contents of that div with the view’s `el`.
Let’s see an example of a Twitter Bootstrap modal dialog, displaying a Backbone view. First, the HTML and Underscore template:
Note that I have a “<div id=’modal’></div>” in there. This is explicitly for the Twitter Bootstrap modal dialog to use. This div is the modal. There’s no content in it, though. I will supply the content for the modal through a Backbone view, which looks like this:
How easy is that? I’m just rendering a view like I always do, stuffing the view’s `el` in to the “#modal” div, and then calling `.modal` on the jQuery selector object. And that’s it. My modal is done and my view’s `events` will still work.
Managing A Modal With Backbone.Marionette
Yes, it always comes back to my Marionette plugin, right? 🙂
In this case, I want to manage my modal dialogs as I do any other area of my screen. I want a single object that keeps track of what view is currently displayed within the modal, handles rendering it that view for me, calls the `.modal` dialog method for me, and correctly closes the modal when the “cancel” or “close” button on my view is clicked.
This largely sounds like a Region object in Backbone.Marionette: a way to manage the contents of a particular DOM element, by rendering and displaying a Backbone View in to that DOM element. Only in this case, it’s a very specialized Region that needs to manipulate the DOM element as a Twitter Bootstrap modal.
To do this, I’m going to take advantage of Marionette’s “Region” and extend it with the behavior that I need for managing the modal:
This is honestly more code than I wanted to write. There’s a few things that I need to clean up and add in my Region object to make this a little more elegant. But, it works! And that’s the important part.
The `constructor` function listens to the region’s “view”show” event, which is fired when a view’s contents are populated in to the `el` of the Region. In the event handler for this, I’m calling the `modal` plugin method on the `el` of the region.
I’m also setting up a “close” event handler on the view itself at this point. That way when I close the view from within my view’s code (a cancel button, or otherwise), the modal will shutdown. The handler for this simply hides the modal using the modal’s ‘hide’ option.
As I said before, I want to reduce the amount of code that is required to make this happen. But for now, this is a functional modal dialog region. I’m using it in my application, like this:
Note that I’m registering my ModalRegion object in my Marionette.Application with my other regions. Instead of passing in a DOM selector, though, I passing in the ModalRegion type.
More Than Just Twitter Bootstrap
The best part about this is that you’ll only need to modify the region object. You shouldn’t have to worry about changing your entire application to use a different modal system, because we’ve encapsulated the modal system in to our ModalRegion object, and our application makes use of that to display the modal dialog with any Backbone.View that we want.