Converting jQuery Code to a Plugin

Posted by Martin Angelov on Feb 3rd, 2011 in

When it comes to efficiently organizing jQuery code, one of the best options is turning certain parts of it into a plugin. There are many benefits to this – your code becomes easier to modify and follow, and repetitive tasks are handled naturally. This also improves the speed with which you develop, as plugin organization promotes code reuse.

This is why today we are going to demonstrate the process of converting code to a plugin. We are going to take the code from our jQuery & CSS3 Select Replacement tutorial, and turn it into a ready for use jQuery plugin.

The Idea

Writing a jQuery plugin is not at all difficult. You need to extend the $.fn object with your own function. What is more difficult, however, is finding a way to properly structure your code so that your plugin is easy to embed and use, without dependencies.

Here are several problems that we need to solve when converting the tutorial code into a jQuery plugin:

  1. We need to give users the ability to control what markup is generated for the dropdown. For example the tutorial code relies heavily on the presence of data- attributes, which contain HTML markup. This is too specific to be included into a plugin, so we need to leave it out of the implementation;
  2. Because of the way plugins are called, we need to rewrite the code so it uses the “this” object that is passed to the plugin, instead of hard-coding a selector. This will also make it possible to convert more than one select element at once;
  3. We need to extract the JavaScript and CSS code of the plugin into separate files, so it is easy to embed and redistribute.

The Code

As you remember from the tutorial, our jQuery code scans the select’s option elements and builds an unordered list. In the process it also looks for a number of data- attributes in the options that contain an image URL and a description to use in the list.

This is, however, too specific for a plugin. We need to give users the ability to override this functionality. To solve the problem, we can allow users to pass a function as a parameter to the plugin, which will generate the markup instead. If such a parameter is not passed, we will use fall back to a default one, which basically takes the text of the option element and turn it directly into a list item.

Lets put this into code:

(function($){

	$.fn.tzSelect = function(options){
		options = $.extend({
			render : function(option){
				return $('<li>',{
					html : option.text()
				});
			},
			className : ''
		},options);

	// More code will be added here.

	}
})(jQuery);

The render function takes an option element (the kind that is contained in a select), and returns a li element, which is directly included in the drop down list by the plugin. This solves problem #1 outlined above.

jQuery & CSS3 Select Replacement Plugin

jQuery & CSS3 Select Replacement Plugin

Before we move on with solving problem #2, lets see how our plugin is going to be called:

$(document).ready(function(){
	$('select').tzSelect();
});

In the example code above, you can see that we are applying the plugin to every select element on the page. We can access these elements by traversing the “this” object that is passed to the plugin:

return this.each(function(){

			// The "this" points to the current select element:

			var select = $(this);

			var selectBoxContainer = $('<div>',{
				width		: select.outerWidth(),
				className	: 'tzSelect',
				html		: '<div class="selectBox"></div>'
			});

			var dropDown = $('<ul>',{className:'dropDown'});
			var selectBox = selectBoxContainer.find('.selectBox');

			// Looping though the options of the original select element

			if(options.className){
				dropDown.addClass(options.className);
			}

			select.find('option').each(function(i){
				var option = $(this);

				if(i==select.attr('selectedIndex')){
					selectBox.html(option.text());
				}

				// As of jQuery 1.4.3 we can access HTML5
				// data attributes with the data() method.

				if(option.data('skip')){
					return true;
				}

				// Creating a dropdown item according to the
				// data-icon and data-html-text HTML5 attributes:

				var li = options.render(option);

				li.click(function(){

					selectBox.html(option.text());
					dropDown.trigger('hide');

					// When a click occurs, we are also reflecting
					// the change on the original select element:
					select.val(option.val());

					return false;
				});

				dropDown.append(li);
			});

			selectBoxContainer.append(dropDown.hide());
			select.hide().after(selectBoxContainer);

			// Binding custom show and hide events on the dropDown:

			dropDown.bind('show',function(){

				if(dropDown.is(':animated')){
					return false;
				}

				selectBox.addClass('expanded');
				dropDown.slideDown();

			}).bind('hide',function(){

				if(dropDown.is(':animated')){
					return false;
				}

				selectBox.removeClass('expanded');
				dropDown.slideUp();

			}).bind('toggle',function(){
				if(selectBox.hasClass('expanded')){
					dropDown.trigger('hide');
				}
				else dropDown.trigger('show');
			});

			selectBox.click(function(){
				dropDown.trigger('toggle');
				return false;
			});

			// If we click anywhere on the page, while the
			// dropdown is shown, it is going to be hidden:

			$(document).click(function(){
				dropDown.trigger('hide');
			});

		});

The fragment above almost identical with the tutorial code we are converting today. One notable change is that we assign $(this) to the select variable (line 5), which used to be $('select.makeMeFancy') (a hardcoded selector), which significantly limited the scope of the code.

The other change is that instead of directly generating the drop down list, we are calling the render function that was passed as a parameter (line 51).

When we combine the above, we get the complete source code of the plugin:

tzSelect/jquery.tzSelect.js

(function($){

	$.fn.tzSelect = function(options){
		options = $.extend({
			render : function(option){
				return $('<li>',{
					html : option.text()
				});
			},
			className : ''
		},options);

		return this.each(function(){

			// The "this" points to the current select element:

			var select = $(this);

			var selectBoxContainer = $('<div>',{
				width		: select.outerWidth(),
				className	: 'tzSelect',
				html		: '<div class="selectBox"></div>'
			});

			var dropDown = $('<ul>',{className:'dropDown'});
			var selectBox = selectBoxContainer.find('.selectBox');

			// Looping though the options of the original select element

			if(options.className){
				dropDown.addClass(options.className);
			}

			select.find('option').each(function(i){
				var option = $(this);

				if(i==select.attr('selectedIndex')){
					selectBox.html(option.text());
				}

				// As of jQuery 1.4.3 we can access HTML5
				// data attributes with the data() method.

				if(option.data('skip')){
					return true;
				}

				// Creating a dropdown item according to the
				// data-icon and data-html-text HTML5 attributes:

				var li = options.render(option);

				li.click(function(){

					selectBox.html(option.text());
					dropDown.trigger('hide');

					// When a click occurs, we are also reflecting
					// the change on the original select element:
					select.val(option.val());

					return false;
				});

				dropDown.append(li);
			});

			selectBoxContainer.append(dropDown.hide());
			select.hide().after(selectBoxContainer);

			// Binding custom show and hide events on the dropDown:

			dropDown.bind('show',function(){

				if(dropDown.is(':animated')){
					return false;
				}

				selectBox.addClass('expanded');
				dropDown.slideDown();

			}).bind('hide',function(){

				if(dropDown.is(':animated')){
					return false;
				}

				selectBox.removeClass('expanded');
				dropDown.slideUp();

			}).bind('toggle',function(){
				if(selectBox.hasClass('expanded')){
					dropDown.trigger('hide');
				}
				else dropDown.trigger('show');
			});

			selectBox.click(function(){
				dropDown.trigger('toggle');
				return false;
			});

			// If we click anywhere on the page, while the
			// dropdown is shown, it is going to be hidden:

			$(document).click(function(){
				dropDown.trigger('hide');
			});

		});
	}

})(jQuery);

Placing this plugin in a separate file solves problem #3. However, as I mentioned previously, we intentionally left out the code that uses the data- attributes in order to make the plugin more portable. To compensate, we need to pass a custom render function when calling the plugin, as you can see below (this is also the code that is used in the demo).

script.js

$(document).ready(function(){

	$('select.makeMeFancy').tzSelect({
		render : function(option){
			return $('<li>',{
				html:	'<img src="'+option.data('icon')+'" /><span>'+
						option.data('html-text')+'</span>'
			});
		},
		className : 'hasDetails'
	});

	// Calling the default version of the dropdown
	$('select.regularSelect').tzSelect();

});

With this our jQuery plugin is complete!

You can use this plugin by simply dropping the tzSelect folder (available from the download button above) into your project root, and including jquery.tzSelect.css and jquery.tzSelect.js in your HTML documents.

Wrapping Up

Following these simple steps, you can easily turn a mess of jQuery code into a structured and ready for reuse plugin. Sure, it does take a bit of work, but the effort would pay out many times in the long run.

Sharing is caring

If you enjoyed this post, feel free to
share it with your friends.

Related Tutorials

  • How to use Geolocation and Yahoo's APIs to build a simple weather webapp
  • TouchTouch - A Touch Optimized Gallery Plugin
  • 5 Lightweight jQuery Alternatives for Mobile Development
  • Timeline Portfolio

19 Comments

  1. Miquel says:

    wooo... this is an awesome tutorial!

  2. Billy says:

    Amazing, good work.

  3. Colin says:

    Nicely done and worthy of a recommendation. You still rock with jQuery guides. Thanks.

  4. Codeforest says:

    Thanks for this. I was just looking into jQuery plugin development for a project and your tutorial really comes in handy.

  5. Bolarinwa Olakunle says:

    Yeah! Nice Tut! Now I need to go back and see how I can convert my jquery validaton script to a jquery plugin...

  6. Sagar says:

    Another awesome stuff! Thanks a lot!

  7. Beben Koben says:

    can be like that, its good, change automatic...ck ck ck

  8. Santosh says:

    Hats off guys. Every tutorial at tutorialzine is a million dollar stuff. Thanks for sharing.

  9. Love this script!

    I am hoping to use this to style product personalisation options on our site, I've now handed over to the developers who are currently trying to implement this on the site and we've hit a snag!

    We need to find a way to get the list to post back!
    Any ideas?

    Thanks in advance.

  10. cantoraz says:

    Awesome! It's very useful to me. Thanks a lot!

  11. John says:

    Very nice. I do have a suggested improvement to your plugin. In place of
    if(i==select.attr('selectedIndex')){
    selectBox.html(option.text());
    }

    you could substitute
    if(i == 0 || this.defaultSelected){
    selectBox.html(option.text());
    }

    Then the first <option> for each <select> can specify disabled='disabled' instead of selected='selected'. This way, if user turns off javascript, the first option isn't selected, but if javascript is on it will still be displayed by default.

  12. John says:

    One further suggestion I might make--as it stands, the demo's hover effect doesn't work on Internet explorer 7 or 8. jquery.tzSelect.css needs a fallback background rule for the hover. For example, adding the rule
    background:url('img/select_slice.png') repeat-x 0 -34px;
    to the
    .tzSelect .selectBox:hover,
    .tzSelect .selectBox.expanded{
    selector will make the menus turn blue in IE when the mouse enters the menu.

  13. Bugsy1 says:

    In order to take the value of the option and move to a link you add .trigger('change') (tzSelect.js file):

    select.val(option.val()).trigger(‘change’);

    I have two dropdown menus in the same page. On one I have to move to a link but not in the other. If I add a class to the script file and change the .trigger change:

    $('.mynewclass'}.val(option.val()).trigger(‘change’);

    The .trigger change applies to both. I tried to .unbind('change') but that didn't work. Is there a way to stop the .trigger('change') from the other menu?

    1. Bugsy1 says:

      I found the solution to having two dropdown menus in the same page with different functions. One has a link to the value.
      jquery.tzSelect.js before:

      select.val(option.val());

      Add:

      if dropDown.hasClass('dropDown mynewclass')){
      select.val(option.val()).trigger('change');
      }
      else

      On the script.js after:
      className : 'hasDetails'
      });

      Add your new class:

      //mynewclass menu class
      $('select.sortBy').tzSelect({
      render : function(option){
      return $('',{
      html: ''+
      option.text()+'
      '
      });
      },
      className : 'mynewclass'
      });

      You can modify the html to fit your needs. Replace mynewclass with you class name.
      Hope this help.

      1. Bugsy1 says:

        The html: didn't upload correctly, change it it fit your needs.

  14. Vini Dy says:

    If you've upgrade to jQuery 1.6, you need to change a little bit of code.

    From this:

    var selectBoxContainer = $('',{
    	width		: select.outerWidth(),
    	className	: 'tzSelect',
    	html		: ''
    });
    
    var dropDown = $('',{className:'dropDown'});
    

    To this:

    var selectBoxContainer = $('',{
    	width		: select.outerWidth(),
    	'class'   	: 'tzSelect',
    	html		: ''
    });
    
    var dropDown = $('',{'class':'dropDown'});
    

    jQuery decided to stop supporting the 'className' property. It's detailed here: http://bugs.jquery.com/ticket/9150

    1. Vini Dy says:

      Looks like some of the HTML tags above were stripped. Just change those two instances from 'className' to 'class' (with the apostrophes). Do not change 'className' in the 'options' object because that's not related to this bug. That's just a coincidence.

      1. zizinho says:

        hello i tried the plugin with the current Jquery 1.7.2 and with your changes it's working but the selected option, the one that is skipped in the dropdown, is not displayed. is there a workaround for this?

    2. energie says:

      Thanks for the tip. Was not working with 1.7.1

Subscribe for the comments on this postAdd Comment

Add a Reply

HTML is escaped automatically. Surround code blocks with <pre></pre> for readability.
Perks:   **bold**   __italics__   [link](http://example.com)