Before we get started, I recommend reading about the revealing module pattern and closure, if you’re not already familiar with them.
When you are building components for use in a CMS, it’s important to understand that you have less control over the use of these components than you may initially think. Programming these blocks in such a way that they operate independently and discretely becomes more of an issue than if you were building a static or informational site, where you might be able to exhibit more control over the usage and structure of the components.
A good way to combat this is by making sure that as you’re writing the functionality of all of these blocks, you are mindful of their “scope leak” and “clobbering.” By leveraging JavaScript scope and closure, and keeping best practices, we can be sure that our blocks play nice with others.
Scope Leak
In JavaScript, “scope leak” is the concept of where access to discrete pieces of code get defined globally. Variables become defined globally. Functions are placed on the window object. If this is done with little-to-no regard for future-proofing, the global namespace can get quite messy and unwieldy.
Example
var componentOptions = {...}; console.log(window.componentOptions);
In this example, the object componentOptions
was defined globally off of the window, and therefore is accessible from anywhere else in the code base.
Clobbering
When you don’t pay attention to your scoping, and you commit the various crimes of “scope leak,” there’s a chance that the functionality of one component will completely or partially override the functionality of another component.
Example
// Component 1 var componentOptions = {...}; function doSomething() {...} // Component 2 var componentOptions = {...}; function doSomething() {...}
In this example, both components’ code are defined globally off of window. Since both options objects and both doSomething
functions are named the same, whichever component is initialized last will overwrite the first component’s options and function.
Closure
In JavaScript, variables and functions are lexically scoped; when a variable or function is defined inside of a function, they are only available from within that function and any of its “children” functions. While this might be a strange concept for an entry level JS programmer to grasp, the subtle nuances of the language shine brightly in the concept of closure.
Example
function init(){ var localVar = true; console.log(localVar); } init(); console.log(localVar);
In this example, localVar
is defined within the init
function. When logged inside of init
, localVar
will be true
. However, when logged outside of init
, localVar
will be undefined. This phenomena allows us to use the revealing module pattern to discourage poor practices.
Revealing Module Pattern
When we create a component, if we create its discrete functionality within a function, its code will operate separately from other component code due to closure. In this way, we’re able to build blocks of code that are portable, reusable, and independent of the rest of the code base.
Example
var component = (function(){ var localVar = true; return { init: init }; function init(){ console.log(localVar); } }()); component.init();
In this example, we create a variable and set it equal to an IIFE (immediately invoked functional expression) that returns an object with a single method. The object reveals the init
function, which has access to all of the IIFE’s closure (in this case the localVar
), and now both the init
function and the localVar
function are protected against clobbering and are not leaking all over the global scope.
Using the Revealing Module Pattern in a CMS Environment
Here’s the fun part. If all your components are in the revealing module pattern, you have to scope all of your components to somewhere. We recommend namespacing a main container object, that in-turn contains a utils
and a components
object (with an optional pages
object). The issue with this is the global namespace object has to be defined off of window, and it has to be setup before you load any of your component code. Additionally, all your component codes should also guard against null errors.
Example
Script in the <head> tag
var PD = {};
Base script (that executes before any component/util script)
(function(){ PD = PD || {}; PD.components = PD.components || {}; PD.utils = PD.utils || {}; }();
Component script
PD = PD || {}; PD.components = PD.components || {}; PD.components.myComponent = (function(){...}());
In this multi-step example, we first need to define the namespaced object to make sure it’s there for future use. One of the first things we do in our external script files, before we even start defining components, we should write code making sure that the sub-objects exist. We also should write code before defining individual components, all of this is in an effort to avoid some kind of race condition where a component possibly gets defined before the component
object, causing a null reference error or accidentally overwriting your defined component with an empty object later. (Note the absence of the var
keyword in the second and third parts of this example; in this case, we do want to define the objects globally off of window e.g. window.PD.component
).
Initializing Components
When you use the revealing module pattern to create CMS-ready components, you have to come up with a way to initialize them. Traditionally, when you have control over the template or page, you are able to only initialize the components that you actually use on the page before the closing <body>
tag. When using a CMS, we are not afforded the ability to know when and where which specific components will be used or in what order or configuration. Therefore, it is important that we make sure each component is given a chance to initialize.
Example
Component script
PD = PD || {}; PD.components = PD.components || {}; PD.components.myComponent = (function(){ return { init: init }; function init(){...} }());
Base initialization script (after all components/utils have been defined)
(function(){ var component; if (PD.components) { for (component in PD.components) { if (PD.components.hasOwnProperty(component) && PD.components[component] && PD.components[component].init) { PD.components[component].init(); } } } }());
In this example, the components are defined individually and after all components are defined, the initialization code iterates through all components and initializes them at once. This will work well if both: all the components have a function named “init,” and if every init
function checks to see if the component is on the page before attempting to initialize it. For instance, you would not want a gallery to be initialized on every page if there was no gallery on that page to init.
Example
PD = PD || {}; PD.components = PD.components || {}; PD.components.gallery = (function(){ var $galleries = $(); return { init:init }; function init(){ $galleries.add($('.my-gallery-selector')); $galleries.each(function(){...}); } }());
In this example, the gallery’s initialization code will iterate through all the galleries that have been added to the jQuery collection of $galleries
and do something with each of them. If the jQuery collection is empty, nothing will happen.
It is also possible to initialize specific components instead of all components, by calling each component’s init function verbosely.
Example
Base initialization script (after all components/utils have been defined)</>
(function(){ PD.components.nav.init(); PD.components.videoModal.init(); PD.components.capabilities.init(); ... }());
In this example, instead of iterating through every component, we choose specific components to initialize in a specific order. There is no immediate downside to this, other than the fact that it is more manual and that initialization calls will have to be added to this list in the future as more components are created. The mass-initializing method works more “automagically.”
TL;DR
Use the revealing module pattern to avoid “scope leak” and “clobbering.” Utilize “closure” to globally define a namespace “container” for your components. Write your components in a way that they can be initialized in any order and irrespective of other components on the page.