Maintaining chainability

Throughout this app you’ve made a massive use of jQuery chaining to perform several operations in one statement. Your methods don’t return a value, so undefined (which is the default) will be returned. Because of this, a developer using your extension can’t call any other jQuery method after calling jqiaContextMenu().

The change required to maintain chainability in a plugin is simple yet invaluable. What you need to do is to ensure that your methods always return the this keyword. Applying this change to the destroy() method, after the line

.off('. ' + namespace);

you’ll have

return this;

You should make the same change for the init() method of the plugin as well. Thanks to this change you maintain chainability, allowing anyone using Jqia Context Menu to continue to operate on the same set in a single statement.

Provide public access to default settings

Your extension isn’t very customizable, but as things become more complex, you may find yourself passing the same large subset of options over and over again to different elements.

An improvement you can make is to expose the default settings so that a developer using your plugin can override them. With this change, the developer only needs to pass an object with the different options at each call of the plugin.

To apply this improvement to your project you need to make two changes. The first change is made on the defaults variable. In order to expose it to the external world, you need to assign it to the $.fn property. To avoid going against the rule of not claiming more than one namespace, you’ll set the object containing the default values as a property of jqiaContextMenu. Therefore, you’ll turn

var defaults = {
   // Options here...
}

;

into

$.fn.jqiaContextMenu.defaults = {
   // Options here...
};

You also need to move the default configuration after the statement where you claim the namespace ($.fn.jqiaContextMenu = function(method) {).

With this update, the default values can’t be referenced anymore using the defaults variable. You have to replace each occurrence of defaults in your code with $.fn.jqiaContextMenu.defaults. In your project there’s only one occurrence, which resides inside the init() method, when you merge the options with the default ones. Therefore you have to change that statement as shown here:

options = $.extend(true, {}, $.fn.jqiaContextMenu.defaults, options);

With these changes in place, let’s look at how you can use the exposed default values.

Let’s say that you want to always bind the left click when calling Jqia Context Menu. You can write

$.fn.jqiaContextMenu.defaults.bindLeftClick = true;

Then you can call the plugin by passing only the idMenu property inside the object.

With this last change you’ve completed your project. The final code is shown in the following listing.

Listing 12.3. The final version of Jqia Context Menu
(function($) {
   var namespace = 'jqiaContextMenu';

   var methods = {
      init: function(options) {
         if (!options.idMenu) {
            $.error('No menu specified');
         } else if ($('#' + options.idMenu).length === 0) {
            $.error('The menu specified does not exist');
         }

         options = $.extend(
            true,
            {},
            $.fn.jqiaContextMenu.defaults,
            options
         );

         if (
            this.filter(function() {
               return $(this).data(namespace);

            }).length !== 0
            ) {
            $.error('The plugin has already been initialized');
         }

         this.data(namespace, options);

         $('html').on(
            'contextmenu.' + namespace + ' click.' + namespace,
            function() {
               $('#' + options.idMenu).hide();
            }
         );

         this.on(
            'contextmenu.' + namespace +
               (options.bindLeftClick ? ' click.' + namespace : ''),
            function(event) {
               event.preventDefault();
               event.stopPropagation();

               $('#' + options.idMenu)
                  .css({
                     top: event.pageY,
                     left: event.pageX
                  })
                  .show();
            }
         );

         return this;
      },
      destroy: function() {
         this
            .each(function() {
               var options = $(this).data(namespace);
               if (options !== undefined) {
                  $('#' + options.idMenu).hide();
               }
            })
            .removeData(namespace)
            .add('html')
            .off('.' + namespace);

         return this;
      }
   };

   $.fn.jqiaContextMenu = function(method) {
      if (methods[method]) {
         return methods[method].apply(
            this,
            Array.prototype.slice.call(arguments, 1)
         );
      } else if ($.type(method) === 'object') {
         return methods.init.apply(this, arguments);
      } else {
         $.error('Method ' + method +
            ' does not exist on jQuery.jqiaContextMenu'

         );
      }
   };

   $.fn.jqiaContextMenu.defaults = {
      idMenu: null,
      bindLeftClick: false
   };
})(jQuery);

This code is available in the file js/jquery.jqia.contextMenu.js in the source code of this app. To have this plugin completely working, you also need to set up an accompanying style sheet that you can find in css/jquery.jqia.contextMenu.css. We also provided a demo that you can find in the file lesson-12/jqia.contextMenu.html so that you can play with your plugin. Figure 12.1 shows the result of clicking the right mouse button on an element of the demo page that has been initialized by the Jqia Context Menu plugin.

Figure 12.1. The effect of clicking the right mouse button on an element that has been initialized by the Jqia Context Menu plugin

What do you think of the result? Are you happy? We hope so and that you had fun while developing this small project. In the next section we’ll develop a more complex jQuery plugin built according to the guidelines you just learned.


by

Tags:

Comments

Leave a Reply

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