Forked from mhulse/javascript-plugin-patterns-FOUND-protoytpe-newed.js
Created
March 6, 2020 11:57
-
-
Save tpaksu/4713a5249bc2ca8d8a731a315fc13d83 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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