HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptMinor

jQuery plugin that helps create a responsive Menu

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
createhelpsresponsivepluginjquerythatmenu

Problem

I've just launched my first project on GitHub. It's a jQuery plugin that helps create a responsive menu. It deals with interactivity (essentially using toggle and some classes), and leaves all the presentation stuff for CSS.

Here is the basics for using the plugin:

First, create the structure with HTML:


Menu

    
        sample link
        sample link
        sample link
            
            Open Submenu
            
            
                sample link
                sample link
                sample link
            
        
    


Then, call it using jQuery:

$('#menu').responsiveMenu($('#menu-toggle'));


There are some others options to customize the plugin:

$('#menu').responsiveMenu({
    trigger: $('#menu-toggle'),
    activeClass: 'active',
    submenuTrigger: $('.submenu-toggle'),
    submenu: $('.submenu'),
    submenuActiveClass: 'open',
    breakpoint: 720
    moveCanvas: true,
    canvas: $('.canvas'),
  });


Where $('#menu') is the main wrapper of the menu, and $('#menu-toggle') is the button to activate the behavior of the plugin.

And, finally, the plugin code:

```
;(function ( $, window, document, undefined ) {

$.fn.responsiveMenu = function(settings){
var config = {
'trigger': null,
'activeClass': 'active',
'submenuTrigger': $('.sub-toggle'),
'submenu': false,
'submenuActiveClass': 'open',
'breakpoint': 720,
'timeOut': 100,
'moveCanvas': false,
'canvas': null,
};
if (settings){$.extend(config, settings);}

// declaring plugin variables
var mTrigger;
var menu = $(this);
var active = config.activeClass;
var button = config.trigger;
var bpoint = config.breakpoint;
var submTrigger = config.submenuTrigger;
var submenu = config.submenu;
var submenuClass = '.' + submenu.prop('class');
var submenuActive = config.submenuActiveClass;
var c

Solution

Don't be intimidated be the size of the book, I just like to write out and explain in as much detail as possible. Well anyways, I've pretty much re-wrote the whole plugin. I've applied a more object-oriented approach and re-factored the plugin into the Module Design pattern. It's now much easier to add functionality and debug code. I've also added in some safe guards to properly initiate the plugin as well. At the end of the code, and inside it, I've included a few links for reading and watching materials. I highly recommend you go through them as they can explain the concepts demonstrated here a lot better than I can.

```
;(function ( $, window, document, undefined ) {

"use strict";

var $window = $(window),
ResponsiveMenu = {
//This is your main object, it holds everything about your plugin
//This way you can easily add methods and functionality to your plugin
//It also provides an easy way to chain methods
//It makes it easy to spot bugs because the code is broken down into sections/methods

init: function(options, elem) {
//In your functions "this" refers to the ResponsiveMenu object
//To call your methods you can do "this.init()" or "this.options"

this.options = $.extend( {}, this.options, options ); //Basic options extend
this.elem = $(elem); //Here I cache the element that the plugin was called on $("menu")

if($window.width() > this.options.breakpoint) {
this.options.mTrigger = false;
}

this.bindEvents(); //Call my first method

return this; //Maintain chainable
},

options: { //Options
trigger: null,
activeClass: 'active',
submenuTrigger: $('.sub-toggle'),
submenu: false,
submenuActiveClass: 'open',
breakpoint: 720,
timeOut: 100,
moveCanvas: false,
canvas: null,
mTrigger: true,
callback: null
},

bindEvents: function() {
//Here I cache the reference to ResponsiveMenu
//I do this because inside event callback methods, "this" refers to the element the event was triggered from
//Now I can still refer to my main object and keep "this" inside the callbacks the same
var self = this;

this.options.trigger.on('click', function(evt) {
evt.preventDefault();
//As you see here I use "self" to refer to the main ResponsiveMenu object
//If I would have used "this" I would be referring to the "trigger" element
self.triggerMain(self); //From here we go to the "triggerMain" method
});

if(this.options.submenu){
this.options.submenuTrigger.on("click", function(evt) { //Same idea from the one above
evt.preventDefault();
self.triggerSubMenu(this, self);
});
}

//Here I put in a safeguard on your resize event
//As you probably know, the resize event fires every time there is a resize, even if you're not finished resizing
//To prevent that I added in a custom event
$window.on('resize', function() {
if(this.resizeTO) clearTimeout(this.resizeTO); //Here we see if there's a timeout open, if yes, cancel it

this.resizeTO = setTimeout(function() { //Set a time out so that the event is only triggered once
$(this).trigger('resizeEnd'); //Trigger the event
}, self.options.timeOut); //After "time"
});

//I set up the custom event here
//This will only be called once
$window.on('resizeEnd', this.onFinalResize(self)); //When this happens, we move down to "onFinalResize" method
},

triggerMain: function(self) {
//I pass "self" here because in here "this" still refers to the "trigger" element because this has the context of a callback method
var activeClass = self.options.activeClass;

if(self.options.mTrigger) {
self.elem.toggleClass(activeClass);
self.options.trigger.toggleClass(activeClass);

if(self.options.moveCanvas){
self.options.canvas.toggleClass(activeClass);
}
}
},

triggerSubMenu: function(elem, self) {
//Here I did things a bit differently to show you different ways to do the same thing
//As a rule of thumb, if you use a selection more than once, you should cache it
//So here we cache the clicked element
//You could use "this" instead, but I used "elem" so it would be easier to understand
var $elem = $(elem),
activeClass = self.options.activeClass,
subActiveClass = self.options.submenuActiveClass,
submenu = self.options.submenu;

if(self.options.mTrigger) {
if($elem.hasClass(activeClass)) {
$elem.removeClass(activeClass);
submenu.removeClass(subActiveClass);
} else {
$ele

Code Snippets

;(function ( $, window, document, undefined ) {

"use strict";

var $window = $(window),
    ResponsiveMenu = { 
    //This is your main object, it holds everything about your plugin
    //This way you can easily add methods and functionality to your plugin
    //It also provides an easy way to chain methods
    //It makes it easy to spot bugs because the code is broken down into sections/methods

    init: function(options, elem) {
        //In your functions "this" refers to the ResponsiveMenu object
        //To call your methods you can do "this.init()" or "this.options"

        this.options = $.extend( {}, this.options, options ); //Basic options extend
        this.elem = $(elem); //Here I cache the element that the plugin was called on $("menu")

        if($window.width() > this.options.breakpoint) {
            this.options.mTrigger = false;
        }

        this.bindEvents(); //Call my first method

        return this; //Maintain chainable
    },

    options: { //Options
        trigger: null,
        activeClass: 'active',
        submenuTrigger: $('.sub-toggle'),
        submenu: false,
        submenuActiveClass: 'open',
        breakpoint: 720,
        timeOut: 100,
        moveCanvas: false,
        canvas: null,
        mTrigger: true,
        callback: null
    },

    bindEvents: function() {
        //Here I cache the reference to ResponsiveMenu
        //I do this because inside event callback methods, "this" refers to the element the event was triggered from
        //Now I can still refer to my main object and keep "this" inside the callbacks the same
        var self = this;

        this.options.trigger.on('click', function(evt) {
            evt.preventDefault();
            //As you see here I use "self" to refer to the main ResponsiveMenu object
            //If I would have used "this" I would be referring to the "trigger" element
            self.triggerMain(self); //From here we go to the "triggerMain" method
        });

        if(this.options.submenu){
            this.options.submenuTrigger.on("click", function(evt) { //Same idea from the one above
                evt.preventDefault();
                self.triggerSubMenu(this, self);
            });
        }

        //Here I put in a safeguard on your resize event
        //As you probably know, the resize event fires every time there is a resize, even if you're not finished resizing
        //To prevent that I added in a custom event
        $window.on('resize', function() {
            if(this.resizeTO) clearTimeout(this.resizeTO); //Here we see if there's a timeout open, if yes, cancel it

            this.resizeTO = setTimeout(function() { //Set a time out so that the event is only triggered once
                $(this).trigger('resizeEnd'); //Trigger the event
            }, self.options.timeOut); //After "time"
        });

        //I set up the custom event here
        //This will only be called once
        $window.on('resizeEnd', this.onFinalRes

Context

StackExchange Code Review Q#30397, answer score: 2

Revisions (0)

No revisions yet.