Bootstrap modal dialogs in an ember application

When building an application, you will need to show messages to the user, deal with confirmations, cancellations, etc. Modal dialogs are frequently used for those purposes. People are used to them.

In this example, I’ll show how to set up and use bootstrap modal windows in an ember application.

Source code and live demo

Download the complete source code of this example here

https://github.com/digiquence/ember3-bootstrap4-modal-dialogs

Live Demo

https://digiquence.github.io/ember3-bootstrap4-modal-dialogs/#/modals

Installing a bootstrap template

In a previous post, I explained how to add a free bootstrap template to a new ember application. So, I will build this bootstrap modal dialogs example on top of that project.

You can follow the instructions on that post to create the base application step by step, or just clone the source code of that project and start from there.

$ git clone https://github.com/digiquence/ember3-bootstrap4-template.git ember3-bootstrap4-modal-dialogs
$ cd ember3-bootstrap4-modal-dialog
$ npm install

Install ember-modal-service

We will need the ember-cli addon ember-modal-service, let’s install it:

$ ember install ember-modal-service

This addon can be injected as a service and manages modal dialogs as promises.

Route and controller

Let’s create a new route and add a new menu item to test our modal dialogs.

$ ember g route modals

The ember project already has three menu items. Let’s add a fourth one and point it to the route we just created.

app/routes/application.js

allMenuItems.push(store.createRecord("menu-item", { id: 1, route: "forms", icon: "icon-flag", label: "Forms" }));
allMenuItems.push(store.createRecord("menu-item", { id: 2, route: "buttons", icon: "icon-grid", label: "Buttons" }));
allMenuItems.push(store.createRecord("menu-item", { id: 3, route: "typography", icon: "icon-pencil", label: "Typography" }));
allMenuItems.push(store.createRecord("menu-item", { id: 4, route: "modals", icon: "icon-loop", label: "Modals" }));

We will manage our modal dialogs in the corresponding controller. But, we have to create it first.

$ ember g controller modals

In this controller we inject the modal service

app/controllers/modals.js

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';

export default Controller.extend({
    modal: service(),
});


Modal container

According to the documentation of the addon, all the modal dialogs are shown in a modal container that we must add to the main template.

app/templates/application.hbs

{{modal-container}}

{{! MAIN CONTAINER START }}
<div class="container-scroller">
    ...
</div>
{{! MAIN CONTAINER END }}

A plain modal dialog

To create a modal dialog we must create a new component for it. Let’s create a component called modal-plain and add the code of a bootstrap modal to it.

$ ember g component modal-plain

app/templates/components/modal-plain.hbs

{{#bs-modal as |modal|}}
    {{#modal.header}}
        <h4 class="modal-title">
            Message
        </h4>
    {{/modal.header}}
    {{#modal.body}}
        Just a plain modal dialog
    {{/modal.body}}
    {{#modal.footer}}
        {{#bs-button type="primary" onClick=(action resolve)}}Close{{/bs-button}}
    {{/modal.footer}}
{{/bs-modal}}

A modal component must extend the provided ModalComponent.

app/components/modal-plain.js

import ModalComponent from 'ember-modal-service/components/modal';

export default ModalComponent.extend({
});

The base modal component provides two actions: resolve and reject. We assign the action resolve to the close button of our dialog.

In the modals template we add a button that triggers the modal dialog, calling the showPlainModal action that lives on the corresponding modals controller

app/templates/modals.hbs

<button type="submit" class="btn btn-xl btn-success mr-2" {{action "showPlainModal"}}>
    Show modal
</button>

app/controllers/modals.js

import Controller from '@ember/controller';
import { inject as service } from '@ember/service';

export default Controller.extend({
    modal: service(),
    actions: {
        showPlainModal() {
            this.get('modal').open('plain');
        },
    },
});

Inside this action we only need to execute the open method with the name of the modal as the only input parameter, in this case, the name is plain. We omit the prefix modal-.

The dialog looks like this:

Plain Modal
Plain modal dialog

The plain modal window now shows as expected when you click the Show modal button. However, there is a small problem with it.

If you click the Close button, the window goes away as expected. Then, you can open it again as many times as you want. But, if you click the X mark on the top-right corner, or click on the gray backdrop around the window, or press the Esc key, you’ll notice the modal dialog won’t show up again.

This happens because the resolve action is assigned only to the Close button. When you click it, the component closes the dialog. But, if you close the window by any other means, the dialog is just hidden, but not formally closed by the component. Let’s fix it now.

Closing behavior

The component provides a method to close all modals.

this.get('modal').close();

It is possible to close a single modal window by name if you need it.

this.get(‘modal’).close(‘name’, ‘plain’);

We need to catch all the possible modal closing scenarios and call that method to make sure the dialogs are correctly closed. Then, reopen them if we wish. Fortunately, ember-bootstrap makes this very easy with the onHidden event in {{bs-modal}}.

app/templates/components/modal-plain.hbs

{{#bs-modal message=model.options.message onHidden=(action "closeModal") as |modal|}}

closeModal action
app/components/modal-plain.js

import { inject as service } from '@ember/service';
import ModalComponent from 'ember-modal-service/components/modal';

export default ModalComponent.extend({
    modal: service(),
    actions: {
        closeModal() {
            this.get('modal').close();
            return false;
        },
    }
});

In some scenarios, you may find desirable to disable the X mark on the top right corner and clicking on the backdrop area to close the window. You can pass the following parameters to {{bs-modal}}.

backdropClose=false closeButton=false

Dynamic content

What if we want to change the contents of the modal dialog dynamically? The addon allows us to send any parameters we want to the modal. Let’s build a modal dialog that accepts the message to show as an input parameter.

$ ember g component modal-message

app/templates/modals.hbs

<button type="submit" class="btn btn-xl btn-success mr-2"
    {{action "showInputMessageModal" 1}}>
    Message 1
</button>
<button type="submit" class="btn btn-xl btn-success mr-2"
    {{action "showInputMessageModal" 2}}>
    Message 2
</button>
<button type="submit" class="btn btn-xl btn-success mr-2"
    {{action "showInputMessageModal" 3}}>
    Message 3
</button>

app/controllers/modals.js

showInputMessageModal(messageNumber) {
    this.get('modal').open('message', { message: "Message " + messageNumber });
}

The parameters we send to the model template are available from model.options

app/templates/components/modal-message.hbs

{{#bs-modal message=model.options.message onHidden=(action "closeModal") as |modal|}}
    {{#modal.header}}
        <h4 class="modal-title">
            Message
        </h4>
    {{/modal.header}}
    {{#modal.body}}
        {{model.options.message}}
    {{/modal.body}}
    {{#modal.footer}}
        {{#bs-button type="primary" onClick=(action resolve)}}Close{{/bs-button}}
    {{/modal.footer}}
{{/bs-modal}}
Dynamic message modal
Dynamic message modal dialog

You can now send dynamically anything to a modal: title, button labels, icons, colors, etc. See the live demo and the source code for more examples.

Modal with dynamic parameters
Modal with dynamic parameters

Promises

We have sent dynamic parameters successfully to our modal dialogs. But what about receiving feedback from them?

Let’s create a new modal dialog called modal-feedback1 to test this functionality.

$ ember g component modal-feedback1

app/components/modal-feedback1.js

import { inject as service } from '@ember/service';
import ModalComponent from 'ember-modal-service/components/modal';

export default ModalComponent.extend({
    modal: service(),
    actions: {
        closeModal() {
            this.get('modal').close();
            return false;
        },
    }
});

app/templates/components/modal-feedback1.hbs

{{#bs-modal message=model.options.message onHidden=(action "closeModal") as |modal|}}
    {{#modal.header}}
        <h4 class="modal-title">
            Message
        </h4>
    {{/modal.header}}
    {{#modal.body}}
        Do you want to accept?
    {{/modal.body}}
    {{#modal.footer}}
        {{#bs-button type="danger" onClick=(action reject)}}
            Reject
        {{/bs-button}}
        {{#bs-button type="primary" onClick=(action resolve)}}
            Accept
        {{/bs-button}}
    {{/modal.footer}}
{{/bs-modal}}

In app/controllers/modals.js we receive the feedback: the resolve and reject actions are captured by the promise.

showFeedback1Modal() {
    this.get('modal').open('feedback1').then(() => {
        this.set('feedback1', "Accepted");
    }).catch(() => {
        this.set('feedback1', "Rejected");
    });
},

In app/templates/modals.hbs we show the result from the feedback

<button type="submit" class="btn btn-xl btn-success mr-2"
    {{action "showFeedback1Modal"}}>
    Open modal to get feedback
</button>
<label class="col-sm-2 col-form-label">{{feedback1}}</label>

You are not restricted to only two options. That is to say, you can get feedback from multiple valid options. Check out the live demo and source code to see how this works.

Modal with multiple valid options

Conclusion

There are many combinations you can try to have useful modal dialogs in your application. We used Bootstrap styled modal dialogs, but you can use whatever style suits better your needs.

Leave a Reply

Your email address will not be published. Required fields are marked *