Search code examples
javascriptjquerybackbone.js

How to check if a user confirmed a custom modal dialog


I'm trying to use backbone js to implement a Modal window, but when I run the code, it does the search every time. I thought that I could just create a variable and assign it a 1 or 0, and check for that in my code, but I can't seem to access it properly.

Here are my two files: ModalView.js

var modalTemplate = "<div id=\"pivotModal\" class=\"modal\">" +
        "<div class=\"modal-header\"><h3><%- title %></h3><button class=\"dtsBtn close\">Close</button></div>" +
    "<div class=\"modal-body\"><h3>Are you sure you want to remove <strong>all</strong> results from your query?</h3></div>" +
    "<div class=\"modal-footer\"><button " +
    "class=\"dtsBtn cancel\">Cancel</button><button " +
    "class=\"dtsBtn confirm\">Confirm</button></div>" +
    "</div>" +
    "<div class=\"modal-backdrop\"></div>";

var ModalView = Backbone.View.extend({

    defaults: {
        title: 'Not Set',
        isConfirmed: '0'
    },

    initialize: function (options) {
        this.options = options;
        this.options = _.extend({}, this.defaults, this.options);
        this.template = _.template(modalTemplate);
        console.log('Hello from Modal View: ' + this.options.title);
    },

    events: {
        'click .close': 'close',
        'click .cancel': 'cancel',
        'click .modal-backdrop': 'close',
        'click .confirm': 'confirm',
    },


    render: function () {
        var data = {title: this.options.title}
        this.$el.html(this.template(data));
        return this;
    },

    show: function () {
        $(document.body).append(this.render().el);
    },

    close: function () {
        console.log('Closed');
        this.unbind();
        this.remove();
    },

    cancel: function () {
        console.log('Cancelled');
        this.unbind();
        this.remove();
    },

    confirm: function () {
        console.log('Confirmed');
        this.options.isConfirmed = 1;
        console.log(this.options.isConfirmed)
        this.unbind();
        this.remove();
    }

});

modal_test.js (which runs the code above):

$('.clearAll').on("click", function(e) {
    e.preventDefault();
    var _title = "Confirm Action";
    //pass the value of the item we clicked on to the title variable
    var modal = new ModalView({ title : _title });
    modal.show();
    if (modal.isConfirmed === 1) {
        console.log("Confirmed equals 1")
        clearAllSearch.startSearch();
        masterSearch.startSearch();
    }
});

Everything works independently, but I can't get the if statement to run.


Solution

  • How to implement a custom confirmation modal

    A way to implement a modal is to pass a callback function to the view.

    ModalView.show({
        title: _title,
        callback: function(confirmed) {
            if (!confirmed) return; // early return
            console.log("User confirmed")
            clearAllSearch.startSearch();
            masterSearch.startSearch();
        }
    });
    

    Where the view would be implemented like this:

    var ModalView = Backbone.View.extend({
        template: _.template(modalTemplate),
        events: {
            'click .close': 'close',
            'click .cancel': 'close',
            'click .modal-backdrop': 'close',
            'click .confirm': 'confirm',
        },
        initialize: function (options) {
            this.options = _.extend({ title: 'Are you sure?' }, options);
        },
        render: function () {
            this.$el.html(this.template(this.options));
            return this;
        },
        show: function () {
            $(document.body).append(this.render().el);
            return this;
        },
        close: function () {
            this.remove();
            var cb = this.options.callback;
            if (cb) cb(false);
        },
    
        confirm: function () {
            this.remove();
            var cb = this.options.callback;
            if (cb) cb(true);
        }
    }, {
        // Static function
        show: function(options) {
            return new ModalView(options).show();
        }
    });
    

    Using promises

    Instead of a callback, you could also use a Promise but it's not available in all browsers yet. An easy alternative would be to use jQuery deferred, jQuery's implementation of promises.

        initialize: function(options) {
            this.options = _.extend({ title: 'Are you sure?' }, options);
            this.deferred = $.Deferred();
        },
        show: function () {
            $(document.body).append(this.render().el);
            return this.deferred;
        },
        close: function () {
            this.remove();
            this.deferred().reject();
        },
    
        confirm: function () {
            this.remove();
            this.deferred().resolve();
        }
    

    It would standardize the way the async confirmation is handled.

    ModalView.show({ title: _title }).then(function() {
        console.log("User confirmed")
        clearAllSearch.startSearch();
        masterSearch.startSearch();
    });
    

    What's not working in your code?

    It looks like you're calling modal.isConfirmed but the modal view class is putting isConfirmed into an options property.

    this.options = _.extend({}, this.defaults, this.options);
    

    It would probably return the correct value if you used modal.options.isConfirmed but you could possibly create a simple accessor function on the view.

    isConfirmed: function() {
        return this.options.isConfirmed;
    }
    

    And since isConfirmed is a boolean flag, you should set it to true, not an arbitrary integer like 0 or 1.

    Then you could call if (modal.isConfirmed()) and it would return the correct value.

    Though, you're having another problem: the moment the modal is shown and the moment the user has interacted with the modal are asynchronous.

    var modal = new ModalView({ title : _title });
    modal.show();
    // This will always be false since the user hasn't clicked anything yet.
    if (modal.isConfirmed === 1) {
    

    This is fixed with the callback technique shown first.


    Other possible improvements

    You're creating the template function each time you instantiate the modal view when you could do it once while defining the class.

    var ModalView = Backbone.View.extend({
        template: _.template(modalTemplate), // define the template here
        initialize: function (options) {
            // this.options = options; // useless line
            this.options = _.extend({}, this.defaults, options);
        },