JQuery

Facebook dropdown menu using Jdropdown

This now has a github home: here. I’m new to Github so sorry if it’s not done right 😛

Please Note: You must have the latest version of jQuery to use this plugin (v 1.7.x and above). Please get this before you attempt to use this plugin otherwise the plugin will not work.

Ok so here is an update to the article about facebook dropdowns.

This time I have made an actual plugin that can handle dropdown menus from a single anchor. I made this plugin since there was, tbh, no real decent plugin for it surprisingly. There are plugins for nav bars, select boxs, option boxes, etc but none for making a simple menu from a random anchor on your page.

So here it is, my plugin version of the facebook dropdown: facebook dropdown example. The first dropdown shows with items provided in the plugin declaration and the second shows the items provided in the html code of the anchor.

The page source code will show you everything you need to know about the how it is styled etc but I will walk you through a little of how the plugin works and how it actually does stuff.

So the plugin itself is called Jdropdown (can be written with upper and lowercase j), you can call the plugin on any element like so $(‘.someel’).jdropdown({ ‘container’: ‘somecontainer’ }) (And I mean almost any element, this thing has little restrictions) and it takes 3 params atm:

  • container – This is the jQuery selector (minus the $() around the selector since thats not actually part of the selector) for the item which the menu items will be put in.
  • orientation – This param tells the script whether to align the menu to the left or right of its anchor and it takes either “left” or “right” (will make this more comlpex soon but atm its just that).
  • items – This params is optional and it is to tell the widget what items will be in the menu. The value is an array of object arrays (i.e. [{//item one}, {//item two}]) and any data can be put into these items but there must be at least a “label” field if your not using your own renderItem function in which case anyhting can be done

Now the plugin has currently two functions you can override:

  • renderMenu – This basically gives off some html for the items to go into in the form $(‘<ul></ul>’)
  • renderItem – This works a lot like jQuery UI plugins and it just appends each item to the menu container

Please note: If you override the renderMenu function with complex html you may be required to also override the renderItem to append/prepend to the correct element within that menu.

Now the plugin also has a number of api functions which you can hook into:

  • open – is triggered when the menu opens. This has a $(this) of the anchor which holds all data in the data() function about the menu
  • selectItem – is triggered when an item in the menu is selected. It has a $(this) of the menu item , its parent <li/>
  • holds all information in its data() function about the item
  • close – This is triggered upon close the menu and because of its currently global scope, has no $(this)

Now for an example of how to use one of these events lets draw code from the example:

$(document).on('jdropdown.selectItem', '#fb_menu a', function(e){
    e.preventDefault();
    alert('You selected: '+$(this).parent().data('jdropdown.item').label);

    // Do some crazy AJAXy weirdy stuff here to make your app awesome
});

This function binds to document that when a item in the #fb_menu container is clicked it will run that function.

Ok so that is the basic description of the plugin. Now I will state a few last words and then leave any further definition upto comments.

  • As I said items is optional in the plugin definition this is because you can actually attach items to the anchors themselves allowing you to have the same styled menu with different options without having to have duplicated code etc etc. The simple way of doing this is to add a “data-items” attribute to your anchors html and make the value a (valid to: parseJSON Doc) JSON string. An example is shown in the example link at the top (its the second drop down, check its html).
  • Also note the item itself can take ANY data and the jdropdown plugin will just attach that data to the data() of the li for that item allowing you to access it when it is selected. So for example you can place “id” in there or “woop” with values of 3 and access it like so: $(this).parent().data(‘jdropdown.item’).id .
  • You must put the container into the HTML page first, best place is at the end of the doc just before the </body> tag
  • You can call this on almost any element so long as you have provided items in some manner by using (for example) $(‘.someel’).jdropdown({ ‘container’: ‘somecontainer’ })

I can’t think of anything else off the top my head. So I am gonna leave it at that for the minute.

The example shows two drop downs, top one with full constructor with items provided in it and the second having items in the html of the object.

Have fun and any questions or if you need clarification etc just post a comment.

P.S almost forgot the plugin source code:

/**
 * Jdropdown
 *
 * @author Sam Millman
 * @licence GNU or whatever
 *
 * This plugin basically allows you to connect a menu to an anchor.
 * This anchor can be literally anything, from a <div/> to an <a/> and even a <li/>.
 */
/**
 * Jdropdown
 *
 * @author Sam Millman
 * @licence GNU or whatever
 *
 * This plugin basically allows you to connect a menu to an anchor.
 * This anchor can be literally anything, from a <div/> to an <a/> and even a <li/>.
 */
(function($){
	var methods = {
		init: function(options){
			return this.each(function(){
				var $this = $(this),
				items = $this.data('items');

				if(!$this.data('jdropdown')){
					$(options.container).addClass("jdropdown-menu");
					$(this).addClass('jdropdown-anchor').data('jdropdown', {
						'items': typeof items === 'object' ? items : options.items,
						'anchor': $(this),
						'menu': $(options.container),
						'options': options
					}).on({ 'click': open });
				}
				return this;
			});
		},
		destroy: function(){}
	},
	open = function(event){
		event.preventDefault();
		if($(this).hasClass('jdropdown-active')){
			close();
			return;
		}else{
			close();
		}

		var data  = $(this).data('jdropdown'),
		offset = $(this).offset(),
		container = data.menu;
		container.data('jdropdown', data);
		container.empty();

		if($.isFunction(data.renderMenu)){
			if($.isFunction(data.renderItem)){
				var ul = data.renderItem(data.renderMenu(), data.items);
			}else{
				var ul = renderItem(data.renderMenu(), data.items);
			}
		}else{
			if($.isFunction(data.renderItem)){
				var ul = data.renderItem($( '<ul></ul>' ), data.items);
			}else{
				var ul = renderItem($( '<ul></ul>' ), data.items);
			}
		}
		ul.appendTo( container );

		if(data.options.orientation == 'left'){
			data.menu.css({
				'position': 'absolute',
				'left': offset.left,
				'top': (offset.top + $(this).outerHeight()),
				'display': 'block'
			});
		}else{
			data.menu.css({
				'position': 'absolute',
				'left': (offset.left - container.outerWidth()) + $(this).outerWidth(),
				'top': (offset.top + $(this).outerHeight()),
				'display': 'block'
			});
		}
		$(this).addClass('jdropdown-active').trigger('jdropdown.open');
	},
	renderItem = function(menu, items){
		$.each(items, function(i, item){
			$('<li></li>').data('jdropdown.item', item).append(
				$( "<a></a>" ).attr({
					'href': '#', 'class': item['class']
				}).text( item.label ).on({ 'click': selectItem })
			).appendTo( menu );
		});
		return menu;
	},
	selectItem = function(e){
		close();
		$(this).trigger('jdropdown.selectItem', e);
	},
	close = function(){
    	$('.jdropdown-menu').css({ 'display': 'none' }); //hide all drop downs
    	$('.jdropdown-anchor').removeClass("jdropdown-active");
		$(this).trigger('jdropdown.close');
	};

	$(document).on('click', function(e) {
	    // Lets hide the menu when the page is clicked anywhere but the menu.
	    var $clicked = $(e.target);
	    if (!$clicked.parents().hasClass("jdropdown-menu") && !$clicked.parents().hasClass("jdropdown-anchor") && !$clicked.hasClass("jdropdown-menu") && !$clicked.hasClass("jdropdown-anchor")){
	    	close();
		}
	});

	$(window).resize(function(){
		if($('.jdropdown-active').length > 0){
			var offset = $('.jdropdown-active').offset(),
				data  = $('.jdropdown-active').data('jdropdown'),
				container = data.menu;

			if(data.options.orientation == 'left'){
				data.menu.css({
					'position': 'absolute',
					'left': offset.left,
					'top': (offset.top + $('.jdropdown-active').outerHeight()),
					'display': 'block'
				});
			}else{
				data.menu.css({
					'position': 'absolute',
					'left': (offset.left - container.outerWidth()) + $('.jdropdown-active').outerWidth(),
					'top': (offset.top + $('.jdropdown-active').outerHeight()),
					'display': 'block'
				});
			}
		}
	});

	$.fn.jdropdown = function(method){
		// Method calling logic
		if ( methods[method] ) {
			return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.j_slider' );
		}
	};
})(jQuery);

Notes:

Added bug fix for resizing the window.

Advertisements

26 thoughts on “Facebook dropdown menu using Jdropdown

  1. Hey this plugin was exactly what I was looking for but I just have a quick question: I want to add a url for the href’s of each item so that when they are clicked each one will take the user to a different page. I saw that we can overwrite the renderItem method but I have no idea how to do this, or how to overwrite any JQuery plugins for that matter 😦

    I was thinking of just modifying that function directly, but what do you think would be better approach?

    1. You can do something like in your JS:

      $('someel').jdropdown({ 'container': '#somemenu' }).data('jdropdown').renderItem = function(menu, item){
      		$.each($items, function(i, item){
      			$('<li></li>').data('jdropdown.item', item).append(
      				$( "<a></a>" ).attr({
      					'href': item.href, 'class': item['class']
      				}).text( item.label ).on({ 'click': selectItem })
      			).appendTo( $menu );
      		});
      		return $menu;
      }
      

      And then you can do in your delcaration array:

      [{‘href’: ‘someurl’, ‘label’: ‘link_to_somewhere’}, //etc]

      Hope this helps.

      There shouldn’t be any bugs with the overriding but I must admit I have not tested the overriding atm so if you find bugs let me know and I’ll fix them asap.

      1. Thanks a lot, your explanation definitely helped, after I originally commented I was trying to experiement on my own but I was way off lol, I thought I had to put my own code in

        $.fn.jdropdown.renderItem = function($menu, $items) {...}

        but I get it now. That’s actually a pretty cool/non-invasive way to let people extend or edit plugin functionality.

        Few things I noticed and eventually fixed:
        – the second parameter to the overriden renderItem should be named items
        – the second line in your example I think you mistakenly had $items instead of items in the .each
        – had to change on({ ‘click’: selectItem }) to on({ ‘click’: this.selectItem })

        So far so good, it’s working, but I’ll let you know if I find any bugs.

    2. To explain what I did in my code a little better, since I realised you mentioned you do not know how to override functions in jQuery plugins, I will walk you through how the JS I supplied works:

      $('someel').jdropdown({ 'container': '#somemenu' }).data('jdropdown').renderItem
      

      Basically this line states:

      – Make a jdropdown on someel
      – Make the container #somemenu
      – Then go into the jdropdown data array (this is like a global settings array for the plugin which is persistent and can be called anywhere on the page even after plugin delcaration) and set the renderItem array index (since data() is an array, it translates much like the HTML5 data attribute: http://ejohn.org/blog/html-5-data-attributes/) to be a function of what we specify.
      – Then when the plugin comes to make the menu it detects this set array element in the data() array and will run that function instead of my own.

      If you need to read more jQuery UI uses the same approach, they have more info to read on their site about it: http://jqueryui.com/.

      That is the *basic* explanation of how overriding works and how to implement it for most well designed plugins. But this method only works for plugins designed to use this method, which most are now-a-days.

      1. Thanks a lot, your explanation definitely helped, after I originally commented I was trying to experiement on my own but I was way off lol, I thought I had to put my own code inside

        $.fn.jdropdown.renderItem = function($menu, $items) {...}

        but I get it now. That’s actually a pretty cool/non-invasive way to let people extend or edit plugin functionality.

        Few things I noticed and eventually fixed:
        – the second parameter to the overriden renderItem should be named items
        – the second line in your example I think you mistakenly had $items instead of items in the .each
        – had to change

        on({ ‘click’: selectItem })

        to

        on({ ‘click’: this.selectItem })

        So far so good, it’s working, but I’ll let you know if I find any bugs.

        PS: Ignore my previous comment, I think I clicked reply on the wrong comment, and I’m not sure if I can delete it

      2. Yea using this.selectItem does not work correctly for me as I thought. Are you sure that it is running the selectItem function?

    3. “- the second parameter to the overriden renderItem should be named items”

      You are right I should really take those dollars out.

      “- the second line in your example I think you mistakenly had $items instead of items in the .each”

      This is me and PHP I am so used to using PHP (you’ll notice this with other jQuery programmers too) that I can sometimes still use $ to prepend variables :))

      “- had to change”

      Weird. I’ll change that and test it. It shouldn’t need this. because this as a word don’t actually exist in that function only in the init() function. It might be that the new jQuery passes it down in some manner.

      Thanks 🙂

      1. Yeah I changed it to this.selectItem because originally I was getting an error saying that selectItem was undefinted, so I figured it probably just needed some context added using this, and it did get rid of the error but it doesn’t seem to be triggering the event unfortunately. I’m trying to debug and see if I can get it figured out.

    4. Weird cos as you can see in my example in the article it triggers fine.

      Can you paste the code your using to do the menu and I’ll go through it.

      1. I actually ended up just changing this line in the plugin

        'href': item.href

        so I could pass in my url, and that is working all good now, but here’s what I had before:

        var accountUrl =  Wummiz.Url.base + '#',
        	profileUrl = Wummiz.Url.base + '#',
        	logoutUrl = Wummiz.Url.base + 'logout';
        			
        // first check if the user is logged in i.e. if account-dropdown exists
        if($('#account-dropdown').length)
        {
        	$('#account-dropdown').jdropdown({
        		'container': '#account-menu',
        		'orientation': 'right',
        	'items': [{'label': 'Account Settings', 'url':accountUrl},{'label': 'Profile Settings', 'url':profileUrl},{'label': 'Logout', 'url':logoutUrl}]
        	}).data('jdropdown').renderItem = function(menu, items){
        		 $.each(items, function(i, item){
                    $('<li></li>').data('jdropdown.item', item).append(
                        $( "<a></a>" ).attr({
                            'href': item.url, 'class': item['class']
        				}).text( item.label ).on({ 'click': this.selectItem })
                    ).appendTo( menu );
                });
                return menu;
        	}
        	
        	$('#account-menu').on('jdropdown.selectItem', function(e){
        		e.preventDefault();
        		alert('You selected: '+$(this).parent().data('jdropdown.item').label);
        		// Do some crazy AJAXy weirdy stuff here to make your app awesome
        	});
        }
        

        If I left it as just ‘selectItem’ it tells me it’s not defined, I figure cause selectItem is in the scope of the plugin so I can’t reference it just like that.

    5. Ok kool but the evnet wasnt being called because:

      $(‘#account-menu’).on(‘jdropdown.selectItem’, function(e){

      You are binding to the menu itself. The event occurs on the <a/> within that menu and since the is <a/> dynamic you should bind to document on the anchor of the <a/> as I have shown above in my documentation :).

    6. Hey man,

      Just as an update if your still stuck on this a little, I added a new example to my examples page.

      The third example there shows using a url attribute to redirect the user away from that page to a specific site. All the code is in the source code of the page.

      Hope it helps,

    1. I’ll add a link example tonight. The above comments do talk about adding links to each item in the menu but I do realise they are not very easy to follow so I’ll put up a third example on that page using links on the menu items.

    2. Hey,

      Sorry I didn’t do it last night but it is done now. If you look at the example page the third example will be one of using a url to redirect the browser to other pages. The code you need should be easily gotten from the source code of the page. Let me know if you have any problems.

  2. Hey, I’ve noticed that the plugin functioning well but seems messy when you re-size the window. I think you must revised the plugin itself using
    $(window).bind(“resize”, function(){}); so when event triggers you need to find the right position of clicked element and re-position the container the contains menu. Good plugin anyway. 😉

    #i-am-not-genius-but-trying-to-be-one

    1. Indeed I found this out a while ago and have fixed it. I have a new version of this plugin that can do its current functionality along with Ajax sourcing and accepting a piece of prebuilt html.

      I need to iron out some of its bugs though before I post it :P.

      Thanks for trying it out,

    1. Ah yea lol. I keep forgetting to update this article.

      I’m gonna bore it into my head to do it tonight (hopefully) but it’s pretty simple.

      On bind of the document when window width and height have changed (resize: http://api.jquery.com/resize/) you get the active jdropdown (which has a specific class, I think it’s jdropdown-active) and get it’s new position on page and make the x and y of the menu equal that.

      It’s like 5 lines of code I’ll see if I have them (or can make them) on this computer.

    2. Ok so I have put my new version of this script on hold and decided to just recode this one a tad to fix that resize bug. The new example page and source code shown has an extra function to deal with resizing. Very quick bug fix so let me know if you still get problems and I’ll fix them.

      Hope it helps 🙂

    1. I do have most of it somewhere since I had to make the functionality for my own site too, however I keep getting set back by work and what not. I hope, now that some one has reminded me, that I can get something together in the next couple of days :).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s