Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mhulse/2abd5ffd828e0fae45cbeec86317b97d to your computer and use it in GitHub Desktop.
Save mhulse/2abd5ffd828e0fae45cbeec86317b97d to your computer and use it in GitHub Desktop.
Some of my favorite JavaScript plugin design patterns: The Facade Pattern, The Revealing Module Pattern, Immediately-invoked Function Expressions (IIFE)s, The Module Pattern imports and exports
// http://callmenick.com/post/slide-and-push-menus-with-css3-transitions
(function(window) {
'use strict';
/**
* Extend Object helper function.
*/
function extend(a, b) {
var key;
for (key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
return a;
}
/**
* Each helper function.
*/
function each(collection, callback) {
var i;
var item;
for (i = 0; i < collection.length; i++) {
item = collection[i];
callback(item);
}
}
/**
* Menu Constructor.
*/
function Menu(options) {
this.options = extend({}, this.options);
extend(this.options, options);
this._init();
}
/**
* Menu Options.
*/
Menu.prototype.options = {
wrapper: '#o-wrapper', // The content wrapper.
type: 'slide-left', // The menu type.
menuOpenerClass: '.c-button', // The menu opener class names (i.e. the buttons).
maskId: '#c-mask' // The ID of the mask.
};
/**
* Initialise Menu.
*/
Menu.prototype._init = function() {
this.body = document.body;
this.wrapper = document.querySelector(this.options.wrapper);
this.mask = document.querySelector(this.options.maskId);
this.menu = document.querySelector('#c-menu--' + this.options.type);
this.closeBtn = this.menu.querySelector('.c-menu__close');
this.menuOpeners = document.querySelectorAll(this.options.menuOpenerClass);
this._initEvents();
};
/**
* Initialise Menu Events.
*/
Menu.prototype._initEvents = function() {
// Event for clicks on the close button inside the menu.
this.closeBtn.addEventListener('click', function(e) {
e.preventDefault();
this.close();
}.bind(this));
// Event for clicks on the mask.
this.mask.addEventListener('click', function(e) {
e.preventDefault();
this.close();
}.bind(this));
};
/**
* Open Menu.
*/
Menu.prototype.open = function() {
this.body.classList.add('has-active-menu');
this.wrapper.classList.add('has-' + this.options.type);
this.menu.classList.add('is-active');
this.mask.classList.add('is-active');
this.disableMenuOpeners();
};
/**
* Close Menu.
*/
Menu.prototype.close = function() {
this.body.classList.remove('has-active-menu');
this.wrapper.classList.remove('has-' + this.options.type);
this.menu.classList.remove('is-active');
this.mask.classList.remove('is-active');
this.enableMenuOpeners();
};
/**
* Disable Menu Openers.
*/
Menu.prototype.disableMenuOpeners = function() {
each(this.menuOpeners, function(item) {
item.disabled = true;
});
};
/**
* Enable Menu Openers.
*/
Menu.prototype.enableMenuOpeners = function() {
each(this.menuOpeners, function(item) {
item.disabled = false;
});
};
/**
* Add to global namespace.
*/
window.Menu = Menu;
})(window);
var slideLeft = new Menu({
wrapper: '#o-wrapper',
type: 'slide-left',
menuOpenerClass: '.c-button',
maskId: '#c-mask'
});
var slideLeftBtn = document.querySelector('#c-button--slide-left');
slideLeftBtn.addEventListener('click', function(e) {
e.preventDefault();
slideLeft.open();
});
//----------------------------------------------------------------------------------------------
// https://addyosmani.com/resources/essentialjsdesignpatterns/book/
// http://www.phpied.com/3-ways-to-define-a-javascript-class/
// https://gist.github.com/mhulse/2abd5ffd828e0fae45cbeec86317b97d
// https://gist.github.com/mhulse/3068831
// http://stackoverflow.com/a/1535687/922323
// jQuery-centric (based on above example):
(function(namespace, $, undefined) {
'use strict';
var self;
// Constructor:
function Menu(options) {
// Private variable:
var privateVariable = 'foo';
console.log('instantiated');
// Public variable:
this.publicVariable = 'bar';
this.options = $.extend({}, this.defaults, options);
self = this;
this._init();
}
// Object default options:
Menu.prototype.defaults = {
billy: 'bobby'
};
// Static variable shared by all instances:
Menu.staticProperty = 'baz';
// Public property:
Menu.prototype.test = 'foo';
// Instance method will be available to all instances but only load once in memory:
Menu.prototype.publicMethod = function() {
alert(this.publicVariable);
};
// Private instance method:
Menu.prototype._init = function() {
console.log('_init', this.options);
this._private.foo();
};
Menu.prototype._private = {};
Menu.prototype._private.foo = function() {
console.log('_private', self.options.billy);
};
// Add to namespace global:
namespace.Menu = Menu;
})((window.FT = (window.FT || {})), jQuery);
var a = new FT.Menu();
a.test = 'baz';
var b = new FT.Menu({ billy: 'bubba' });
console.log(a.test, b.test);
// Output:
//
// instantiated
// _init Object {billy: "bobby"}
// _private bobby
// instantiated
// _init Object {billy: "bubba"}
// _private bubba
// baz foo
// https://addyosmani.com/resources/essentialjsdesignpatterns/book/
// http://benalman.com/news/2010/11/immediately-invoked-function-expression/
// https://carldanley.com/js-facade-pattern/
///
/// The Facade Pattern
/// https://carldanley.com/js-facade-pattern/
///
var MyModule = (function(window, undefined) {
function myMethod() {
alert('my method');
}
function myOtherMethod() {
alert('my other method');
}
// explicitly return public methods when this object is instantiated
return {
someMethod: myMethod,
someOtherMethod: myOtherMethod
};
})(window);
// example usage
MyModule.myMethod(); // undefined
MyModule.myOtherMethod(); // undefined
MyModule.someMethod(); // alerts "my method"
MyModule.someOtherMethod(); // alerts "my other method"
///
/// The Facade + Module Pattern Hybrid
/// https://addyosmani.com/resources/essentialjsdesignpatterns/book/#facadepatternjavascript
///
var module = (function() {
var _private = {
i: 5,
get: function() {
console.log("current value:" + this.i);
},
set: function(val) {
this.i = val;
},
run: function() {
console.log("running");
},
jump: function() {
console.log("jumping");
}
};
return {
facade: function(args) {
_private.set(args.val);
_private.get();
if (args.run) {
_private.run();
}
}
};
}());
// Outputs: "current value: 10" and "running"
module.facade({run: true, val: 10});
//------------------------------------------------------------------------------
///
/// The Revealing Module Pattern
/// https://carldanley.com/js-revealing-module-pattern/
///
var MyModule = (function(window, undefined) {
// revealing module pattern ftw
function MyModule() {
function someMethod() {
alert('some method');
}
function someOtherMethod() {
alert('some other method');
}
// expose publicly available methods
return {
// in our normal revealing module pattern, we'd do the following:
someMethod: someMethod,
// in the facade pattern, we mask the internals so no one has direct access by doing this:
someMethod: function() {
someMethod();
}
};
}
})(window);
//------------------------------------------------------------------------------
///
/// Immediately-invoked Function Expressions (IIFE)s
/// https://addyosmani.com/resources/essentialjsdesignpatterns/book/
/// NAMESPACE EXTENSION
///
// namespace (our namespace name) and undefined are passed here
// to ensure 1. namespace can be modified locally and isn't
// overwritten outside of our function context
// 2. the value of undefined is guaranteed as being truly
// undefined. This is to avoid issues with undefined being
// mutable pre-ES5.
;(function (namespace, undefined) {
// private properties
var foo = "foo";
var bar = "bar";
// public methods and properties
namespace.foobar = "foobar";
namespace.say = function (msg) {
speak(msg);
};
namespace.sayHello = function () {
namespace.say("hello world");
};
// private method
function speak(msg) {
console.log("You said: " + msg);
};
// check to evaluate whether "namespace" exists in the
// global namespace - if not, assign window.namespace an
// object literal
})(window.namespace = window.namespace || {});
// we can then test our properties and methods as follows
// public
// Outputs: foobar
console.log(namespace.foobar);
// Outputs: You said: hello world
namespace.sayHello();
// assigning new properties
namespace.foobar2 = "foobar";
// Outputs: foobar
console.log(namespace.foobar2);
///
/// Extensibility is of course key to any scalable namespacing pattern
///
// let's extend the namespace with new functionality
(function(namespace, undefined) {
// public method
namespace.sayGoodbye = function () {
namespace.say("goodbye");
}
})(window.namespace = window.namespace || {});
// Outputs: goodbye
namespace.sayGoodbye();
//------------------------------------------------------------------------------
///
/// The Module Pattern, variations
/// https://addyosmani.com/resources/essentialjsdesignpatterns/book/
/// IMPORTS
///
// Global module
var myModule = (function (jQ, _) {
function privateMethod1() {
jQ(".container").html("test");
}
function privateMethod2(){
console.log(_.min([10, 5, 100, 2, 1000]));
}
return{
publicMethod: function(){
privateMethod1();
}
};
})(jQuery, _); // Pull in jQuery and Underscore
myModule.publicMethod();
///
/// The Module Pattern, variations
/// EXPORTS
///
// Global module
var myModule = (function () {
// Module object
var module = {};
var privateVariable = "Hello World";
function privateMethod() {
// ...
}
module.publicProperty = "Foobar";
module.publicMethod = function () {
console.log(privateVariable);
};
return module;
})();
/* global jQuery, namespace */
///
/// Immediately-invoked Function Expressions (IIFE)s
/// Namespace Extension
/// Module Pattern with Imports and Exports
///
(function($public, $window, undefined) {
var _private = {};
_private.i = 5;
_private.get = function() {
console.log('current value:' + this.i);
};
_private.set = function(val) {
this.i = val;
};
_private.run = function() {
console.log('running');
};
_private.speak = function(msg) {
console.log('You said: ' + msg);
};
_private.jump = function() {
console.log('jumping');
};
$public.say = function(msg) {
_private.speak(msg);
};
$public.init = function(args) {
_private.set(args.val);
_private.get();
if (args.run) {
_private.run();
}
console.log(typeof $window);
};
}(window.namespace = (window.namespace || {}), window, jQuery));
window.addEventListener('DOMContentLoaded', function() {
namespace.init({
run: true,
val: 10
});
// Let’s extend the namespace with new functionality:
(function($public, undefined) {
$public.sayGoodbye = function() {
this.say('goodbye');
};
})(window.namespace = (window.namespace || {}));
namespace.sayGoodbye(); // goodbye
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment