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.


GOVUK Modules pattern

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.

Usage

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)

Module structure

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)

Writing modules

Whilst this isn’t prescriptive, it helps if modules look and behave in a similar manner.

Use js- prefixed classes for interaction hooks

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>

Declare event listeners at the start

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>

Use data-attributes for configuration

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>

Include Jest tests

Modules should have their own tests.

Examples

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
⚠️ **GitHub.com Fallback** ⚠️