Imagine instead maintaining a clear separation of concerns between your view models
and your UI. Views models only need to collaborate with other view models, never with the DOM.

Keep your code clean and elegant - just like your application!

What you'll learn

This post will teach you how to use Twitter Bootstrap modals in a Knockout.js application,
while keeping your code clean and simple.

You'll learn how to use Knockout's built-in function, ko.renderTemplate,
to generate a modal dialog box UI, on-demand, from a template.

This post will also show how jQuery's Deferred object can neatly manage the asynchronous
nature of displaying a modal dialog and waiting for its result.

View model flow

In this post, we'll be discussing the interaction between an application's root view model,
which is data-bound to the page, and a separate view model, data-bound to a modal dialog.

The flow of interaction when displaying a modal is shown below.

The viewmodel/view interaction of a modal dialog

Twitter Bootstrap Modal

Twitter Bootstrap provides a very nice looking modal component.
However, using the simple $.fn.modal jQuery plugin
directly from a Knockout view model can result in DOM code
creeping in where it doesn't belong.

The Bootstrap modal plugin requires there to be an element in the
page which it will display as a modal dialog. This element will
look something like the following:

This view model provides a method that can be bound to the click event of a button or link.
It shows the modal dialog UI, which is data-bound to the given view model.
Then it waits for the modal to be closed with some result data.

We'll dive into the showModal function soon.
For now, note that it is returning deferred object that is resolved when the
modal has been closed. The modal is able to pass back a result, which in
this case the application view model is adding to its notes array.

Here's the AddNoteViewModel, which will be data bound to the
modal UI:

The AddNoteViewModel provides the name of the template to
render. This will be used by the showModal function.

The view model is also in control of when to close the modal.
It assumes that a modal property has been attached to itself.
This object contains a close method that is used to close the
modal and optionally pass a result.

After some standard pre-condition validation, the function starts the
process of showing the modal. The pipe method provides a nice
way to chain together a set of deferred operations.

First, the modal element creation is started. Once Knockout has finished
updating the DOM, the next operation in the pipeline runs.

The call to pipe($) is equivalent to:

pipe(function(modalElement) {
return $(modalElement);
})

Because this returns a non-deferred value, the next stage of the
pipeline executes immediately after.

The final stage of the pipline creates a new deferred object that will
contain the result of the modal when it is closed. A helper object is
attached to the view model so it is able to close the modal.

var addModalHelperToViewModel = function (viewModel, deferredModalResult, context) {
// Provide a way for the viewModel to close the modal and pass back a result.
viewModel.modal = {
close: function (result) {
if (typeof result !== "undefined") {
deferredModalResult.resolveWith(context, [result]);
} else {
// When result is undefined, we don't want any `done` callbacks of
// the deferred being called. So reject instead of resolve.
deferredModalResult.rejectWith(context, []);
}
}
};
};

The deferred object's resolveWith method is used to provide a
useful value for this in any callback functions.
The context value was provided to the showModal
function via its options argument.

The signature of resolveWith is similar to
Function.prototype.apply i.e. the arguments must be passed as
an array.

Sometimes a user will need to cancel a modal. To handle this case,
close can be called with an undefined result. The deferred
modal result is then rejected, instead of resolved. This means that the
"happy path" in the application view model doesn't need to check for a
cancelled modal. Cancelling can be detected by providing a fail
callback instead.

When a modal is closed, it is removed from the DOM to free up memory.
However, Twitter Bootstrap modal's hide method will animate
the hiding. Instant removal would prevent this nice effect.

A two-step approach is used to remove the modal once the deferred result
has been resolved or rejected. The Bootstrap modal "hide" method is called.
This will raise the "hidden" event once the animation has finished. At that
point we can remove the modal's DOM element.

Extensions and ideas

The use of ko.renderTemplate to dynamically generate UI can be
generalised for other components. For example,
Twitter Bootstrap Popovers.

Occasionally one modal will need to display another modal. There are two
possible flows to consider:

Close the first modal, then show the second.

Stack the second modal on top of the first.

Be careful, this is not always a great user experience.
Avoid stacking up lots of modals on top of each other!

In the first case, the modal's deferred result object must be passed onto
the second modal. The application view model shouldn't resume until the
second modal has been closed.

The modal helper object assigned to view models could be extended to
support this scenario. Here's a possible API:

FirstViewModel.prototype.showSecond = function() {
this.modal.closeCurrentAndShowModal(new SecondViewModel());
// The first modal is now removed from the DOM.
};

The second case is somewhat simpler. A separate deferred result object is
created for the second modal. The first modal remains in the background,
ready handle the result when the second modal is closed.

Again, the modal helper object could be enhanced to support this behavior:

FirstViewModel.prototype.showSecond = function() {
this.modal.showNestedModal(new SecondViewModel());
// The first modal is still loaded, but hidden until the second is closed.
};

To prevent Twitter Bootstrap adding multiple backdrop overlays to the page,
the first modal could be hidden, but not removed, while the second modal is
displayed.

Full sample code

To get the full sample code that accompanies this blog post, join my
web development mailing list. I'll give you access to a private GitHub repository
containing code samples, which you can copy and use in your own projects.

Thanks for reading

I hope you found this post useful. Let me know in the comments, or on twitter
@andrewdavey.
Feel free to copy, modify and use the code in your applications.

Would you like access to the sample code repository?

Subscribe to my web development mailing list.

Name

Email address

No spam, just occasional web development tips, posts and sample code.
Unsubscribe at any time.