Sam Leurs
Sam Leurs

Reputation: 490

Same function in function, preserve variables/elements

I'm creating a javascript function which creates a modal. Here's the function:

    function createModal(options) {
        var self = this;
        modalHeaderText = options.header;
        modalBodyText = options.body;
        $modal = $('<div />').addClass('modal').appendTo('body');
        $modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
        $modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
        $modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
        $modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
        if (options.buttons) {
            $modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
            $.each(options.buttons, function(name, buttonOptions) {
                $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
                if(buttonOptions.callback) {
                    $modalButton.on('click', function() { 
                        buttonOptions.callback(); 
                    });
                } else {
                    $modalButton.on('click', function(e) { 
                        $modal.remove();
                    });
                };
            });
        };
        $modal.addClass('active');
        if (options.closeOnOverlayClick == true) {
            $modalOverlay.on('click', function(e) {
                $modal.remove();
            });
        };
    };

This works fine, but I want to be able to call the function within the same function, like this:

    $('#modal').on('click', function(e){
        e.preventDefault();
        createModal({
            header      : 'Enter your name',
            body        : '<input type="text" class="name" />',
            buttons     : {
                'OK'    : {
                    class : 'btn btn-success',
                    callback : function() {
                        var name = self.$modalBody.find('.name').val();
                        if (!name) {
                            createModal({
                                header      : 'Error',
                                body        : 'You must provide a name',
                                buttons     : {
                                    'OK'    : {
                                        class : 'btn'
                                    }
                                }
                            });
                        } else {
                            alert(name);
                        };
                    },
                },
                'Close' : {
                    class : 'btn btn-error'
                }
            }
        });
    });

What I want is the following: when someone clicks the button with ID "modal" (hence "#modal"), a modal is opened with a input. When the OK-button is pressed, it checks if the input ('name') has a value. If so, the value is shown in an alert. If not, a new modal is openend (over the current modal) with the text 'You must provide a name'.

If I enter a name, it works. The name is shown in an alert, and also the close button works. But if I do not enter a name, and the second modal is shown, all the variables in the function are overwritten.

How can I preserve the variables/elements from the first modal so that, after the second modal is shown (and cleared), the buttons from the first modal still work.

I've created a JSFiddle here: https://jsfiddle.net/6pq7ce0a/2/

You can test it like this:

1) click on 'open modal' 2) enter a name 3) click on 'ok' 4) the name is shown in an alert ==> this works

The problem is here:

1) click on 'open modal' 2) do NOT enter a name 3) click on 'ok' 4) a new modal is shown 5) click on 'ok' in the new (error) modal 6) the buttons from the first modal (with the input field) don't work anymore

Thanks in advance!

Update

If I change the function to the function below, the first modal does not work at all.

    function createModal(options) {
        var self = this;
        var modalHeaderText = options.header;
        var modalBodyText = options.body;
        var $modal = $('<div />').addClass('modal').appendTo('body');
        var $modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
        var $modalContainer = $('<div />').addClass('modal-container').appendTo($modal);
        var $modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
        var $modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
        if (options.buttons) {
            var $modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
            $.each(options.buttons, function(name, buttonOptions) {
                var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
                if(buttonOptions.callback) {
                    $modalButton.on('click', function() { 
                        buttonOptions.callback(); 
                    });
                } else {
                    $modalButton.on('click', function(e) { 
                        $modal.remove();
                    });
                };
            });
        };
        $modal.addClass('active');
        if (options.closeOnOverlayClick == true) {
            $modalOverlay.on('click', function(e) {
                $modal.remove();
            });
        };
    };

The problem is here:

var name = self.$modalBody.find('.name').val();

$modalBody is not defined if I add 'var' to all the elements.

Upvotes: 0

Views: 65

Answers (2)

KunalMZ
KunalMZ

Reputation: 331

Simply not using var in front of $modal variable causing it to be stored in window scope. When the next next $modal is closed, the variable is referencing to an already removed element, so nothing happens on first modal's Close button click.

Upvotes: 0

pxslip
pxslip

Reputation: 309

So in addition to the comments above regarding not declaring var you also are storing a reference to window in the self variable. To avoid all of that I went down the road in this fiddle: https://jsfiddle.net/10fanzw6/1/.

Quick explanation.

  • First don't assign this to self as this is window
  • Second assign everything to the empty self object as well as a local var (for better readability)
  • Third pass the self var back to any button callback giving you access to any part of the modal you may need.

For posterity, including the updated function here:

function createModal(options) {
  var self = {};
  var modalHeaderText = options.header;
  var modalBodyText = options.body;
  var $modal = self.$modal = $('<div />').addClass('modal').appendTo('body');
  var $modalOverlay = self.$modalOverlay = $('<div />').addClass('modal-overlay').appendTo($modal);
  var $modalContainer = self.$modalContainer = $('<div />').addClass('modal-container').appendTo(self.$modal);
  self.$modalHeader = $('<div />').addClass('modal-header').addClass(options.headerClass).html(modalHeaderText).appendTo($modalContainer);
  self.$modalBody = $('<div />').addClass('modal-body').addClass(options.bodyClass).html(modalBodyText).appendTo($modalContainer);
  if (options.buttons) {
    var $modalFooter = self.$modalFooter = $('<div />').addClass('modal-footer').appendTo($modalContainer);
    $.each(options.buttons, function(name, buttonOptions) {
      var $modalButton = $('<button />').addClass(buttonOptions.class).html(name).appendTo($modalFooter);
      if (buttonOptions.callback) {
        $modalButton.on('click', function() {
          buttonOptions.callback(self);
        });
      } else {
        $modalButton.on('click', function(e) {
          $modal.remove();
        });
      };
    });
  };
  $modal.addClass('active');
  if (options.closeOnOverlayClick == true) {
    $modalOverlay.on('click', function(e) {
      $modal.remove();
    });
  };
};
$('#modal').on('click', function(e) {
  e.preventDefault();
  createModal({
    header: 'Enter your name',
    body: '<input type="text" class="name" />',
    buttons: {
      'OK': {
        class: 'btn btn-success',
        callback: function(modal) {
          var name = modal.$modalBody.find('.name').val();
          if (!name) {
            createModal({
              header: 'Error',
              body: 'You must provide a name',
              buttons: {
                'OK': {
                  class: 'btn'
                }
              }
            });
          } else {
            alert(name);
          };
        },
      },
      'Close': {
        class: 'btn btn-error'
      }
    }
  });
});

Upvotes: 3

Related Questions