GOVUK Modules pattern - alphagov/notifications-manuals GitHub Wiki
We will be moving away from this pattern soon.
As we no longer support legacy browsers (IE11 and lower), we will write our application Javascript in ES6 using ECMA Script (Javascript native) modules.
To initialise components we will continue to use data attributes, but will implicitly import and initialise each one.
The Admin app uses a module pattern for most of its JavaScript, taken from GOVUK and adapted. The pattern was designed to make it easy to write re-usable modular components, without having to worry about where and when the module should be instantiated.
Javascript modules can be specified in markup using data-
attributes:
<div data-notify-module="some-module">
<strong>Some other markup inside the module</strong>
</div>
Modules can be found and started by including govuk/modules.js
and running:
$(document).ready(function(){
GOVUK.notifyModules.start()
});
This will attempt to find and start all modules in the page. For the example above it will look for a module at GOVUK.NotifyModules.SomeModule
. Note the value of the data attribute has been converted to PascalCase.
The module will be instantiated and then its start
method called. The HTML element with the data-notify-module
attribute is passed as the first argument to the module. This limits modules to acting only within their containing elements.
module = new GOVUK.NotifyModules[type]()
module.start(element)
Running GOVUK.notifyModules.start()
multiple times will have no additional affect. When a module is started a flag is set on the element using the data attribute module-started
. data-module-started
is a reserved attribute. It can however be called with an element as the first argument, to allow modules to be started in dynamically loaded content:
var $container = $('.dynamic-content')
GOVUK.notifyModules.start($container)
A module must add its constructor to GOVUK.NotifyModules
and it must have a start
method.
The simplest module looks like:
;(function(Modules) {
'use strict'
Modules.SomeModule = function() {
this.start = function($element) {
// module code
}
}
})(window.GOVUK.NotifyModules)
Whilst this isn’t prescriptive, it helps if modules look and behave in a similar manner.
Make it clear where a javascript module will be applying behaviour:
<div data-notify-module="toggle-thing">
<a href="/" class="js-toggle">Toggle</a>
<div class="js-toggle-target">Target</div>
</div>
Beginning with a set of event listeners clearly indicates the module’s intentions.
this.start = function($element) {
$element.on('click', '.js-toggle', toggle)
$element.on('click', '.js-cancel', cancel)
}
Where possible, assign listeners to the module element to minimise the number of listeners and to allow for flexible markup:
<div data-notify-module="toggle-thing">
<a href="/" class="js-toggle">This toggles</a>
<div class="js-toggle-target">
<p>Some content</p>
<a href="/" class="js-toggle">This also toggles</a>
</div>
</div>
Keep modules flexible by moving configuration to data attributes on the module’s element:
<div
data-notify-module="html-stream"
data-url="/some/endpoint"
data-refresh-ms="5000">
<!-- updates with content from end point -->
</div>
Modules should have their own tests.
From the root of the Admin app repository:
app/assets/javascripts/fileUpload.js
app/assets/javascripts/copyToClipboard.js
app/assets/javascripts/radioSlider.js
app/assets/javascripts/cookieMessage.js
app/assets/javascripts/templateFolderForm.js
app/assets/javascripts/liveSearch.js
app/assets/javascripts/addEmailBrandingOptionsForm.js
app/assets/javascripts/radioSelect.js
app/assets/javascripts/enhancedTextbox.js
app/assets/javascripts/errorTracking.js
app/assets/javascripts/updateContent.js
app/assets/javascripts/collapsibleCheckboxes.js
app/assets/javascripts/main.js
app/assets/javascripts/modules.js
app/assets/javascripts/homepage.js
app/assets/javascripts/colourPreview.js
app/assets/javascripts/listEntry.js
app/assets/javascripts/registerSecurityKey.js
app/assets/javascripts/fullscreenTable.js
app/assets/javascripts/autofocus.js
app/assets/javascripts/updateStatus.js
app/assets/javascripts/cookieSettings.js
app/assets/javascripts/authenticateSecurityKey.js