Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find and auto-start JavaScript modules from markup #227

Merged
merged 9 commits into from Nov 13, 2015
Merged

Conversation

@fofr
Copy link
Contributor

@fofr fofr commented Nov 12, 2015

Find and auto-start modules specified using the data-module="" pattern in markup. Includes an example module for tracking Google Analytics events using markup, which will meet the need in this PR: alphagov/frontend#894

This is based on the same pattern used by govuk_admin_template and all our publishing tools:
alphagov/govuk_admin_template#3 and alphagov/transition#192

I've created an example of how this could be used on static:
https://github.com/alphagov/static/compare/example-module-usage

This addresses the problems of:

  • needing to include script tags within views for specific JavaScript to run
  • determining where and when to instantiate JavaScript ie in a base.js file, or in a view specific one
  • JavaScript acting on an entire page and having side effects eg acting on all .js-x classes on a page, rather than within a specific part of a page

This pattern should work well with components that are used on Static.

cc @robinwhittleton @quis @dsingleton @tombye @jamiecobbett
Also cc'ing @tyom, a non-GDS user of frontend toolkit.

From the documentation

Javascript modules can be specified in markup using data- attributes:

<div data-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.modules.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.Modules.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-module attribute is passed as the first argument to the module. This limits modules to acting only within their containing elements.

module = new GOVUK.Modules[type]();
module.start(element);

The simplest of modules looks like this:

(function(Modules) {
  "use strict";
  Modules.SomeModule = function() {
    this.start = function(element) {
      // module code
    }
  };
})(window.GOVUK.Modules);

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-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-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-module="html-stream"
  data-url="/some/endpoint"
  data-refresh-ms="5000">
  <!-- updates with content from end point -->
</div>

Include Jasmine specs

Modules should have their own tests, whether they’re being included with the gem or are app specific.

fofr added 8 commits Nov 10, 2015
Find and auto-start modules specified using the `data-module=""`
pattern in markup.

Each module must register a constructor with GOVUK.Modules and
have a `start` method. The start method receives the element that
the data attribute is defined on.

This makes it easier to limit modules to their containers and to
start modules within certain elements We can also remove inline
script tags and stop worrying about instantiation.

Based on the govuk_admin_template:
alphagov/govuk_admin_template#3
Ported from govuk_admin_template
Add a module that uses `data-` attributes to define events that can be
sent to Google Analytics.

This is useful for tracking things like the validation messages that
users see when interacting with forms.
The object conversion when running tests on CI revealed a deficiency in
the existing module assertions that attempted to use `toMatch` on HTML
objects.

Instead assert that the HTML we expect is within the jQuery object.

This unveiled a failing test which has also been fixed. The `push`
method for when a container is also a module should actually be `add`.
`start` suffices.
There’s a risk that `start` could get called multiple times and lead to
confusing and hard to debug behaviour. Protect against this by calling
start only once per module.
if (typeof GOVUK.Modules[type] === "function" && !started) {
module = new GOVUK.Modules[type]();
module.start(element);
element.data('module-started', true);

This comment has been minimized.

@quis

quis Nov 12, 2015
Member

I’d like to see this documented as a reserved attribute.

This comment has been minimized.

@fofr

fofr Nov 13, 2015
Author Contributor

Good point, I'll add this to the documentation.

This comment has been minimized.

@fofr

fofr Nov 13, 2015
Author Contributor

Updated docs in 2ab43b2

@quis
Copy link
Member

@quis quis commented Nov 12, 2015

Code thoughts: this is a good, solid foundation, and it’s not overengineered.

Higher level thoughts: standardising this wrapper will make it easier to contribute some of our team’s work to back to the toolkit.

Overall: 👍

@dsingleton
Copy link
Contributor

@dsingleton dsingleton commented Nov 12, 2015

👍 to the approach / pattern. Adds some simple structure / scoping to our JS. It's also a stepping stone to doing something more clever in the future with lazy loading / requirejs strategies.

We discussed naming a bit offline, in particular GOVUK.modules vs GOVUK.Modules and GOVUK.modules.start(container), but naming hard, and this certainly good enough to ship. If we decide to change it in the future we can with a major version bump.

Once this is merged it'd be great to use this in a few places, to make use of it, and so anyone wanting to use it has a few more points of reference. +💯 to using this with components, it's a great fit.


it('starts all modules that are on the page', function() {
var modules = $(
'<div data-module="test-alert-module"></div>\

This comment has been minimized.

@robinwhittleton

robinwhittleton Nov 13, 2015
Contributor

What’s our policy on dependency versions? If we’re happy with specifying a minimum Node version of 4.0 then we get ES6 template strings which are a lot nicer than the non-standard ‘\ = newline’ convention we’re following here. Minor nit though.

This comment has been minimized.

@fofr

fofr Nov 13, 2015
Author Contributor

I like to keep these specs backwards compatible with older browsers, so if necessary Jasmine tests can be run on older browsers to try and find problems.

This comment has been minimized.

@robinwhittleton

robinwhittleton Nov 13, 2015
Contributor

Good call.

@robinwhittleton
Copy link
Contributor

@robinwhittleton robinwhittleton commented Nov 13, 2015

Apart from that, only my own 👍 to add. Happy to merge if everyone else is.

* Address @quis comment about `data-module-started` being a reserved
attribute
* Add details about running start with a parameter
@fofr fofr changed the title [DISCUSS] Find and auto-start JavaScript modules from markup Find and auto-start JavaScript modules from markup Nov 13, 2015
dsingleton added a commit that referenced this pull request Nov 13, 2015
Find and auto-start JavaScript modules from markup
@dsingleton dsingleton merged commit 3bb2ac2 into master Nov 13, 2015
2 checks passed
2 checks passed
continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details
@dsingleton dsingleton deleted the js-modules branch Nov 13, 2015
fofr added a commit that referenced this pull request Nov 13, 2015
- Find and auto-start JavaScript modules from markup:
`data-module="module-name"`(PR #227)
@fofr fofr mentioned this pull request Nov 13, 2015
quis added a commit to alphagov/notifications-admin that referenced this pull request Dec 23, 2015
Users can add placeholders to their messages, eg

> …your vehicle ((registration number))

when the message is sent, this gets replaced with the data the user uploads, eg

> …your vehicle LC12 BFL

We reckon that it will be useful to see that the placeholder has been
recognised, ie that its syntax is correct, before uploading any data.

We reckon that the best way to do this is by styling it differently to the rest
of the text that the user types.

This is not a trivial problem. There are two possible ways to do it:

1 Write a Google Docs-style text rendering engine, which completely replaces
  the native HTML `<textarea>` with a custom control, and programme what should
  happen when the user types something that looks like a placeholder, or
  presses an arrow key, or makes a selection, or…
2 Leave the `<textarea>` in place, unmodified, and duplicate layers in front
  of/behind it to visually replace a placeholder with the blue lozenge

Unsurprisingly, this commit implements 2.

There are four layers. Each layer contains live-updated copy of the text in the
textbox, and each is styled differently:
- one layer behind the textbox to make the blue background
- the textbox itself
- a layer with the white text, which overlays the black text of the textbox
- a layer with an inner shadow to knock back the brackets

This is because of some interesting limitations:
- The text in the foreground and background must occupy the same physical space,
  so no deleting characters from the duplicated layers
- Words can’t be split up into multiple elements,
  eg `<span>((</span>regist…`:—this results in slightly different kerning to
  `((regis…`, which messes up the alignment of the layers
- The textbox can’t be completely overlapped with a block of colour, because
  the cursor disappears behind it. Trying to edit text when you can’t see the
  cursor is hard.

Implementation

Technically this makes use of Paul Hayes work on Javascript modules in the
GOV.UK frontend toolkit[1].

It also makes use of the `oninput` event to detect changes to the textbox’s
contents. This is much more performant than `onkeydown`, `onpaste`, etc. Without
it the delay between user input and the layers all updating is too slow and you
see misalignment of the layers.

1. alphagov/govuk_frontend_toolkit#227
quis added a commit to alphagov/notifications-admin that referenced this pull request Jan 2, 2016
Users can add placeholders to their messages, eg

> …your vehicle ((registration number))

when the message is sent, this gets replaced with the data the user uploads, eg

> …your vehicle LC12 BFL

We reckon that it will be useful to see that the placeholder has been
recognised, ie that its syntax is correct, before uploading any data.

We reckon that the best way to do this is by styling it differently to the rest
of the text that the user types.

This is not a trivial problem. There are two possible ways to do it:

1 Write a Google Docs-style text rendering engine, which completely replaces
  the native HTML `<textarea>` with a custom control, and programme what should
  happen when the user types something that looks like a placeholder, or
  presses an arrow key, or makes a selection, or…
2 Leave the `<textarea>` in place, unmodified, and duplicate layers in front
  of/behind it to visually replace a placeholder with the blue lozenge

Unsurprisingly, this commit implements 2.

There are four layers. Each layer contains live-updated copy of the text in the
textbox, and each is styled differently:
- one layer behind the textbox to make the blue background
- the textbox itself
- a layer with the white text, which overlays the black text of the textbox
- a layer with an inner shadow to knock back the brackets

This is because of some interesting limitations:
- The text in the foreground and background must occupy the same physical space,
  so no deleting characters from the duplicated layers
- Words can’t be split up into multiple elements,
  eg `<span>((</span>regist…`:—this results in slightly different kerning to
  `((regis…`, which messes up the alignment of the layers
- The textbox can’t be completely overlapped with a block of colour, because
  the cursor disappears behind it. Trying to edit text when you can’t see the
  cursor is hard.

Implementation

Technically this makes use of Paul Hayes work on Javascript modules in the
GOV.UK frontend toolkit[1].

It also makes use of the `oninput` event to detect changes to the textbox’s
contents. This is much more performant than `onkeydown`, `onpaste`, etc. Without
it the delay between user input and the layers all updating is too slow and you
see misalignment of the layers.

1. alphagov/govuk_frontend_toolkit#227
quis added a commit to alphagov/notifications-admin that referenced this pull request Jan 5, 2016
Users can add placeholders to their messages, eg

> …your vehicle ((registration number))

when the message is sent, this gets replaced with the data the user uploads, eg

> …your vehicle LC12 BFL

We reckon that it will be useful to see that the placeholder has been
recognised, ie that its syntax is correct, before uploading any data.

We reckon that the best way to do this is by styling it differently to the rest
of the text that the user types.

This is not a trivial problem. There are two possible ways to do it:

1 Write a Google Docs-style text rendering engine, which completely replaces
  the native HTML `<textarea>` with a custom control, and programme what should
  happen when the user types something that looks like a placeholder, or
  presses an arrow key, or makes a selection, or…
2 Leave the `<textarea>` in place, unmodified, and duplicate layers in front
  of/behind it to visually replace a placeholder with the blue lozenge

Unsurprisingly, this commit implements 2.

There are four layers. Each layer contains live-updated copy of the text in the
textbox, and each is styled differently:
- one layer behind the textbox to make the blue background
- the textbox itself
- a layer with the white text, which overlays the black text of the textbox
- a layer with an inner shadow to knock back the brackets

This is because of some interesting limitations:
- The text in the foreground and background must occupy the same physical space,
  so no deleting characters from the duplicated layers
- Words can’t be split up into multiple elements,
  eg `<span>((</span>regist…`:—this results in slightly different kerning to
  `((regis…`, which messes up the alignment of the layers
- The textbox can’t be completely overlapped with a block of colour, because
  the cursor disappears behind it. Trying to edit text when you can’t see the
  cursor is hard.

Implementation

Technically this makes use of Paul Hayes work on Javascript modules in the
GOV.UK frontend toolkit[1].

It also makes use of the `oninput` event to detect changes to the textbox’s
contents. This is much more performant than `onkeydown`, `onpaste`, etc. Without
it the delay between user input and the layers all updating is too slow and you
see misalignment of the layers.

1. alphagov/govuk_frontend_toolkit#227
quis added a commit to alphagov/notifications-admin that referenced this pull request Jan 5, 2016
Users can add placeholders to their messages, eg

> …your vehicle ((registration number))

when the message is sent, this gets replaced with the data the user uploads, eg

> …your vehicle LC12 BFL

We reckon that it will be useful to see that the placeholder has been
recognised, ie that its syntax is correct, before uploading any data.

We reckon that the best way to do this is by styling it differently to the rest
of the text that the user types.

This is not a trivial problem. There are two possible ways to do it:

1 Write a Google Docs-style text rendering engine, which completely replaces
  the native HTML `<textarea>` with a custom control, and programme what should
  happen when the user types something that looks like a placeholder, or
  presses an arrow key, or makes a selection, or…
2 Leave the `<textarea>` in place, unmodified, and duplicate layers in front
  of/behind it to visually replace a placeholder with the blue lozenge

Unsurprisingly, this commit implements 2.

There are four layers. Each layer contains live-updated copy of the text in the
textbox, and each is styled differently:
- one layer behind the textbox to make the blue background
- the textbox itself
- a layer with the white text, which overlays the black text of the textbox
- a layer with an inner shadow to knock back the brackets

This is because of some interesting limitations:
- The text in the foreground and background must occupy the same physical space,
  so no deleting characters from the duplicated layers
- Words can’t be split up into multiple elements,
  eg `<span>((</span>regist…`:—this results in slightly different kerning to
  `((regis…`, which messes up the alignment of the layers
- The textbox can’t be completely overlapped with a block of colour, because
  the cursor disappears behind it. Trying to edit text when you can’t see the
  cursor is hard.

Implementation

Technically this makes use of Paul Hayes work on Javascript modules in the
GOV.UK frontend toolkit[1].

It also makes use of the `oninput` event to detect changes to the textbox’s
contents. This is much more performant than `onkeydown`, `onpaste`, etc. Without
it the delay between user input and the layers all updating is too slow and you
see misalignment of the layers.

1. alphagov/govuk_frontend_toolkit#227
quis added a commit to alphagov/notifications-admin that referenced this pull request Jan 6, 2016
Users can add placeholders to their messages, eg

> …your vehicle ((registration number))

when the message is sent, this gets replaced with the data the user uploads, eg

> …your vehicle LC12 BFL

We reckon that it will be useful to see that the placeholder has been
recognised, ie that its syntax is correct, before uploading any data.

We reckon that the best way to do this is by styling it differently to the rest
of the text that the user types.

This is not a trivial problem. There are two possible ways to do it:

1 Write a Google Docs-style text rendering engine, which completely replaces
  the native HTML `<textarea>` with a custom control, and programme what should
  happen when the user types something that looks like a placeholder, or
  presses an arrow key, or makes a selection, or…
2 Leave the `<textarea>` in place, unmodified, and duplicate layers in front
  of/behind it to visually replace a placeholder with the blue lozenge

Unsurprisingly, this commit implements 2.

There are four layers. Each layer contains live-updated copy of the text in the
textbox, and each is styled differently:
- one layer behind the textbox to make the blue background
- the textbox itself
- a layer with the white text, which overlays the black text of the textbox
- a layer with an inner shadow to knock back the brackets

This is because of some interesting limitations:
- The text in the foreground and background must occupy the same physical space,
  so no deleting characters from the duplicated layers
- Words can’t be split up into multiple elements,
  eg `<span>((</span>regist…`:—this results in slightly different kerning to
  `((regis…`, which messes up the alignment of the layers
- The textbox can’t be completely overlapped with a block of colour, because
  the cursor disappears behind it. Trying to edit text when you can’t see the
  cursor is hard.

Implementation

Technically this makes use of Paul Hayes work on Javascript modules in the
GOV.UK frontend toolkit[1].

It also makes use of the `oninput` event to detect changes to the textbox’s
contents. This is much more performant than `onkeydown`, `onpaste`, etc. Without
it the delay between user input and the layers all updating is too slow and you
see misalignment of the layers.

1. alphagov/govuk_frontend_toolkit#227
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.