snippetjavascriptMinor
jQuery plugin that helps create a responsive Menu
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:
Then, call it using jQuery:
There are some others options to customize the plugin:
Where
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
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
```
;(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.onFinalResContext
StackExchange Code Review Q#30397, answer score: 2
Revisions (0)
No revisions yet.