Web Development

The Modal Mixin

WillowTree default article featured image

Today we’re following up on our earlier post about mixins, and discussing another mixin we’ve found useful. Before we dive in, here is a simple demo with all the relevant code under the javascript tab.

In many apps you will need to pop up a modal and block all user interaction until the user responds to the prompt. This is very similar to the native confirm command.

if (confirm('Continue?')) {
    // Yes
} else {
    // No
}

Here we present the user with a Yes / No dialog box and proceed depending on what the user selects. With this in mind, how could we go about doing this and what technologies / patterns should we use? For the purpose of this article we are going to use our mixin pattern to give any view the ability to invoke a modal window.

Furthermore, we are going to use Promises to simulate the blocking nature of the confirm command. We are going to also use Backbone along with Handlebars to render our modals to the screen.

Planning Our API

First things first, let’s envision the API that we wish to interact with. We want to be able to supply a title, message, and some number of buttons the user can interact with. There is the possibility of showing an arbitrary view within the modal; however, that changes the problem space, so we will limit this functionality to simple prompts. Here is an example use of our API:

var modal = this.modalCreate({
    title: 'The title',
    message: 'A simple message for the user',
    buttons: { no: 'No Thanks', yes: 'Ok' }
});


modal.then(success, fail);

The modalCreate method is the only method the view interacts with. You simply pass it the relevant fields you want to display and it returns a promise. You then can proceed depending on how the promise resolves. For the buttons, we are going to follow a simple convention where the key is the event name, and the value is the text of the button. yes and no will be special buttons signifying success and failures. Any other buttons will pass along their event to the view.

Divide and Conquer

Now that we have the API we can divide this problem into three chunks:

  • The mixin facilitates the interaction between the modal view and the view triggering the modal. It will handle the promise which houses the ultimate value of the modal.
  • The modal view handles events related to the modal, namely click events.
  • The modal template will render the modal to the screen.

The Template

The template for the view will be very simple. We will base the template off of our mock API.

```
<div class="modal">
    <h1>{{title}}</h1>


    <p>{{message}}</p>


    <div class="button-group">
        {{#each buttons}}
            <span class="btn {{@key}}" data-btn="{{@key}}">
                {{this}}
            </span>
        {{/each}}
    </div>
</div>
```

As you can see, no surprises here. The only interesting portion is how we display the buttons. We use the @key Handlebar helper to display the key of the object. In our case it will be yes in the case of {yes: 'Ok!'}. The this helper references the value of the key, 'Ok!'. The last thing to note is we also save the key to the data-btn attribute. We will pass this btn along through the modal view to indicate which option the user selected.

The View

With the template fleshed out our next step is to make it do something. We will create a simple Backbone view to accomplish this.

```
var ModalView = Backbone.View.extend({
    template: Handlebars.compile($('#template-modal').html()),


    className: 'modal-container',


    initialize: function(options) {
        this.model = new Backbone.Model(options);
    },


    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },


    events: {
        'click .btn': 'btnClick'
    },


    btnClick: function(e) {
        var btn = $(e.target).data('btn');


        var response = (btn !== 'no') ? btn : false;


        this.trigger('modal:response', response);
    }
});
```

There is a bit going on here so let’s break it down:

First, we reference the template we made earlier. We are not showing it here, but we are compiling it with Handlebars.

In the initialize method we create a simple Backbone Model for our view, this is done so our template has attributes to render.

Lastly we get to the click event for our buttons. Recall that we stored the key of the button in its data-btn attribute. The first line of the click event grabs this value. Next we check to see if the button was yes or no, if so we translate this value into true or false. If the button wasn’t either of those, we simply pass the btn value along. We will see how this simplifies things in the mixin itself. Finally, we trigger an event on our view object with the response of the modal.

The Mixin

At this point we have several moving parts in our system. We have the modal view which gets rendered to the screen. It processes the events within the modal (mainly clicking yes or no). We also have our instantiating view which needs to render a modal to the screen. So far there is no communication between these two entities – that’s where this mixin comes into play. Our mixin has two internal methods (prefixed by underscore) and one public method.

// { ...
modalCreate: function(options) {
    this._modalStatus = $.Deferred();


    this._modal = new ModalView(options);
    this.$el.after(this._modal.render().$el);


    this.listenTo(this._modal, {
        'modal:response': this._modalInput
    });


    return this._modalStatus.promise();
}
// ...}

modalCreate behaves as we would expect from the API we defined initially. The method immediately creates a promise which we return at the end. This promise allows the view to glance over all the details of the modal and just care about the end value.

Everything else in this method is pretty standard. We make a modal view which we attach to ourselves and then render it to the screen. Next we listen for the response event from our modal.

// { ...
_modalInput: function(result) {
    var self = this;
    setTimeout(function() {
        if (result) {
            self._modalStatus.resolve(result);
        } else {
            self._modalStatus.reject(result);
        }
    }, 0);


    self._modalDestroy();
}
// ...}

We next look at the first internal method _modalInput which gets triggered when the user interacts with the modal. One interesting thing to note is the use of setTimeout(/\*...\*/, 0). Let’s first tackle what is within the function and then ask why it is in a timeout in the first place.

Recall from our view that when a user clicks on a button we either pass a string or false depending on if the user selected the no button. We can take this response and either fulfil or reject the promise depending on the response. This will ensure the user will be able to use then for success and fail for failures.

That’s all well and good, but why bother setting everything in a timeout? This timeout allows us to finish cleaning up the modal window before the view’s callback executes. That allows the view to bring up another prompt without trashing the original modal.

```
// { ...
_modalDestroy: function() {
    this.stopListening(this._modal);
    this._modal.remove();
}
// ...}
```

Our last internal method is _modalDestroy which is fairly self explanatory. Here we are cleaning up our modal by cleaning up events and removing it from the screen. One thing worth noting is why we separated this out into a separate method. Simply, it gives the view more flexibility with how the modal is dismissed. Perhaps the modal should be dismissed on a timer rather than interaction with the buttons.

Tying it all together

So how can we use this thing? Let’s get to some demos!

  • Here is the simple example we linked to at the very top of today’s post: demo
  • Our second demo shows how you can chain modals together using then and fail.
  • Our third demo demonstrates how you can manually dismiss modals with _modalDestroy.

Conclusion

We have only just brushed the surface with this pattern. Although the focus of the article was exploring the mixin pattern, we also brushed upon the concept of promises wrapping views.

1200x628-banners-Single-Page-Web-App

Single Page Web Apps: Why building a web app means less… and more than it used to

The number of modern web technologies available to software engineers is mind boggling....

Read the article