Modals let you display information and forms outside and on top of the current main view, which makes it a useful tool for getting the user’s attention about essential information. They are useful for showing a response to an action, highlighting a form, and gaining confirmation for a serious action.

Modals are not to be confused models, which are data objects.

In The Old Days, it was common to use JavaScript alert dialogs to get the user’s attention — but nobody really liked them because they provided a bad user experience. Nowadays, it is much more common to create artificial alert dialogs, which have more functionality and allow for us to improve the user experience so that they can be a positive thing.

Still, a principle worth remembering is that they should only be used when a user’s attention is especially required and you do not want to divert the user away from their current location. Here are some example uses:

  1. When a user first visits the site, they are alerted about the cookies in use and their rights regarding them
  2. When a user is viewing a list of records, the edit and view buttons spawn modals to show the user a specific record’s full details
  3. When a user has added an item to the cart, a confirmation message is shown with buttons showing them their next steps

As you can see from these examples, there are, broadly speaking, three things we can use a modal for:

  1. Simple messaging
  2. To show an in-context form
  3. Confirmation messaging

We will talk about all three of these things, but first there are some basics we need to go through.

Mechanisms

We will look at the basics through the lens of creating a simple modal.

The core technology for modals is provided to NetSuite Commerce bundles via Bootstrap. We do, however, have proprietary code to spawn instances of modals, so you will need to get familiar with that.

When approaching creating a model, it’s worth knowing that the rendering of a modal is very similar to how we would normally render any other view. You still need to prepare a view and template like you normally would. Instead of rendering the view in a page layout or as a child view, Backbone is instead directed to render the view in a modal container instead.

Once you have your new view and template ready to go, you can then cause a view to render in a modal using one of two ways (depending on your code version):

  1. SuiteCommerce and SuiteCommerce Advanced 2018.2 and Newer — use the layout component’s showContent() method
  2. SuiteCommerce Advanced Aconcagua and Older — use the layout object’s showInModal() method

Both methods work very similarly, and it’s not necessarily bad practice to use the layout object in newer versions, but you should use the layout component where possible.

The layout component’s showContent() method is inherited by other visual components, such as checkout, PDP, PLP, etc, so you can use it on those methods directly, if you wish.

Before using in the follow sub-sections, note the following — they assume that you have already:

  1. Created your new view
  2. Created your new template
  3. Added the view as a dependency to your file
  4. Added Underscore as a dependency to your file
  5. Created a new instance of your view, named view
  6. Referenced the layout component or layout object, named Layout
  7. Decided the best way to trigger it

Use the Layout Component

Details of how to use the layout component’s showContent() method is in our extensibility API documentation, so be sure to read that for more information and examples.

In short, you need to run code like this to trigger the view:

Layout.showContent(view, {showInModal: true});

Use the Layout Object

The layout object is available almost globally, as it refers to the main parent view of the current application. In a lot of cases, it can be referenced with this.application.getLayout().

In short, the code looks like this:

Layout.showInModal(view);

Deciding How to Trigger the Modal

The code snippets above are short, and deliberately so, because they cannot tell the whole picture.

Before you create the modal, you need to decide when you want the modal to appear. For example, do you want it to appear when a promise is completed? Perhaps when a user clicks a button? How about when the page finishes loading?

Triggering the Modal after a Promise Resolves

This is good when a user completes an action and you want to show a confirmation after the asynchronous call completes. We do this when a user has added an item to their cart, for example. This can also be used when a fetch completes too.

// Using the Layout Component (Newer Sites)
showConfirmation: function showConfirmation (promise, application) {
    var Layout = application.getComponent('Layout');

    promise.done(function success (data) {
        var view = new MyNewModalView({
            model: data,
            application: application
        });
        Layout.showContent(view, {showInModal: true});
    })
}

// Using the Layout Object (Older Sites)
showConfirmation: function showConfirmation (promise, application) {
    var Layout = application.getLayout();

    promise.done(function success (data) {
        var view = new MyNewModalView({
            model: data,
            application: application
        });
        Layout.showInModal(view);
    })
}

For more information on promises, see Understand jQuery Promises and Deferred Objects.

Triggering the Modal on Page Load

This is a good fit, for example, if you want a message to appear when a user hits your homepage, and you want to show something directly in their face.

In order to do this, you will need to listen for when the view has rendered.

If You’re Writing a Custom Module

If you are doing this on a custom module you own, then you will need to modify the initialize() method of the parent view and do something like this:

// Using the Layout Component (Newer Sites)
initialize: function initialize (options) {
    this.application = options.application;
    var Layout = this.application.getComponent('Layout');

    this.on('afterViewRender', function () {
        var view = new MyNewModalView();
        Layout.showContent(view, {showInModal: true});
    });
}

In addition to my previous assumptions, this assumes you have also passed in the application object from your entry point file.

If you’re using the layout object, then your code will look slightly different:

// Using the Layout Object (Older Sites)
initialize: function initialize (options) {
    this.application = options.application;
    var Layout = this.application.getLayout();

    this.on('afterViewRender', function () {
        var view = new MyNewModalView();
        Layout.showInModal(view);
    });
}

In both of these examples, MyNewModalView refers to the name of the new modal view I have created, so swap the name with whatever you have named your modal view.

If You’re Customizing an Existing Module

It is also possible to modify the behavior of existing modules. Unfortunately, this sort of behavior is not easily achievable through the extensibility API alone, so the approach will be similar for all kinds of sites.

Accordingly, as we are going to modify the prototypes of core modules, you should exercise caution before putting them into extensions or using them on SuiteCommerce sites you do not own. Historically, this sort of customization can lead to breakages when upgrading, and this is why we recommend using the extensibility API.

In this example, I will extend the Home.View so that when it has rendered, my modal will show. For newer sites, it is more straight-forward.

For sites running SuiteCommerce or 2018.2 or newer:

// Using the Layout Component (Newer Sites)
mountToApp: function mountToApp (container) {
    var Layout = container.getComponent('Layout');

    HomeView.afterInitialize.install({
        name: 'showMyNewModal',
        priority: 99,
        execute: function () {
            this.on('afterViewRender', function () {
                var view = new MyNewModalView();
                Layout.showContent(view, {showInModal: true});
            });            
        }
    });
}

For newer sites, we can use a plugin method called afterInitialize() which is available on every view, and lets us run code after its initialize() method has been called. There is also a beforeInitialize(), if you want to run code before.

For older sites:

// Using the Layout Object (Older Sites)
mountToApp: function mountToApp (application) {
    var Layout = application.getLayout();

    _.extend(HomeView.prototype, {
        initialize: _.wrap(HomeView.prototype.initialize, function initialize (fn) {
            fn.apply(this, _.toArray(arguments).slice(1));
            this.on('afterViewRender', function () {
                var view = new MyNewModalView();
                Layout.showInModal(view);
            });
        })
    });
}

There is a fair bit of additional code here. So, let’s talk through some things.

As we are modifying an existing area of the site, we need to hook into that. Although it is generally not good practice to modify the prototype of a view, it is necessary in this specific instance.

We are first using Underscore’s extend() method, which takes an existing object and creates a copy of it with additional properties that we specify. As Home.View is an object, we will extend its prototype object, which is the underlying object used to create its properties.

So, after specifying the initialize property as the one we want to replace, but note that if we were to then just pass a function or value, it would replace the existing method entirely and cause the existing code to be lost (and thus the site to potentially malfunction). We don’t want to do that — we want to keep the existing code and run new code. Therefore, we then use wrap(), passing in the existing initialize property and a callback. This means that whenever the original method is called, it will call the callback we passed instead.

The good thing about wrap() is that it passes in the existing function as the first parameter, and arguments that was passed to it when it was called. Therefore, if we want to execute the existing function before doing anything else, we can just use apply() to do that.

After that, it’s up to us as to what happens next. In our case, we are setting an event listener for after the view has been rendered to trigger the creation of our view. We create a new instance of our new modal view, and then use the appropriate method on the Layout object or component.

This is a good fit, for example, if you have button in your page (such as a list view page) and you want to show the user more details about a specific thing, or perhaps to show a form associated with that particular record.

The following is not recommended for actions that delete a record; for that you should use a confirmation view, which is detailed further below.

The mechanism for this is actually very straightforward. It assumes that you have already set up your view and template so that it is navigable via a route (whether by router or page type).

Edit the template and find the link you want to trigger the modal. In the anchor tag, add the following parameter:

data-toggle="show-in-modal"

Note, however, that if you want to reload the list view so that it reflects the changes, then you will need to modify your files so that you have a callback bound to the list view’s model or collection that listens for change and then runs showContent() or render() on it.

For example:

this.model.on('change', this.showContent, this);

Also note that this may need to be something like self if you are working within the scope of a promise object or the callback itself, eg:

var self = this;
this.collection.on('reset sync add remove change destroy', function () {
    // Some code
    self.render();
});

Showing a Confirmation Modal

Confirmation modals are a special kind of modal. They are typically used when a user has performed an action, such as deleting a record, and we want to make sure that they want to complete the action.

Standardized confirmation modals are included in the NetSuite Commerce bundles, so you do not need to create your own. To use them, you will need to first add GlobalViews.Confirmation.View as a dependency to your file.

Then, you will need to determine what you want to happen when the user confirms their choice: this is a success callback. If you are dealing with records, then this will be the destruction of a model.

Thus, your list view might like this:

events: {
    'click button[data-action="remove"]': 'removeMyRecord'
},

removeModel: function removeModel (options) {
    var model = options.context.collection.get(options.id);
    model.destroy();
},

removeMyRecord: function removeMyRecord (e) {
    e.preventDefault();

    var view = new GlobalViewsConfirmationView({
        title: 'Delete Record',
        body: 'Are you sure you want to delete this record?',
        callBack: this.removeModel,
        callBackParameters: {
            context: this,
            id: jQuery(e.target).data('id')
        },
        autohide: true
    });
    this.application.getLayout.showInModal(view);
}

You can also pass in a cancelCallBack parameter to define a callback if a user clicks No, but note that this doesn’t trigger if a user dismisses the dialog by clicking the X button.