JavaScript must be linted with ESLint.
Developers should stick to the Origami eslintrc config, since this represents a common standard across FT teams. Custom linting may be defined at the component level with a .eslintrc
file, or at the file level with a /*eslint ... */
comment.
In addition, Object properties must not be named after reserved words in the JavaScript language.
An Origami component must be a JavaScript class which should be named for the component, with the o-
prefix dropped.
i.e. a component named o-dear
should be defined in a class named Dear
.
browserFeatures.required
section of origami.json
.documentElement
.Origami components must do as little as possible as the page parses. Instead they should provide a static init
method which constructs instances of the component. The init
method should run when the o.DOMContentLoaded
or o.load
events are fired.
The init
method must be callable with no arguments. It may accept arguments, but if it does, all arguments must be optional (see issue 228).
If the init
method (or the components constructor) takes an element argument to identifying its owned DOM, it must accept the following types:
HTMLElement
objectquerySelector
expression, e.g. .main-content > [data-o-component~='o-share']
document.body
If the given element does not have the components data attribute data-o-component
, the init
function may traverse the subtree looking for elements which do. The init
function may create multiple instances and return them in an array. Where passed to a constructor, however, the component must only create one instance and return it. If no owned DOM is found, the component must not throw an error.
If a component’s JavaScript requires configuration, the following methods of passing that configuration must be supported.
The component must be configurable using data attributes on the component’s root HTML element. Data attributes must be named data-{componentname}-{key}
, e.g. data-o-share-id
.
Developers should avoid the temptation to name data attributes based on the same naming conventions as BEM in CSS. Data attributes are not subject to the same semantics as classes so BEM is not a great fit.
Where it is possible for multiple instances of a component to exist on a page and for the same configuration to apply to all of them, or where a component has no markup (e.g. o-tracking
or o-errors
), the component must support declarative configuration via JSON data placed within a <script>
block with a type='application/json'
and a data attribute in the component’s namespace with the key ‘config’ and no value, ie. data-{componentname}-config
. For example:
<script data-o-errors-config type='application/json'>
{
"sentryEndpoint": "https://....",
"application": {
"version": "1.2.3",
"name": "Foo Application"
}
}
</script>
Components must parse any such configuration using JSON.parse
and only in response to an event (such as o.DOMContentLoaded
) or function call. Components must not expect more than one global declarative configuration block in their namespace to be present on the page.
Any configuration option expecting a function
must not be defined in a declarative configuration block and must be optional, providing default behaviour where the imperative configuration using init()
is absent. If a configuration key is present in the declarative configuration block that expects a function
, an Error
should be thrown to warn the developer of the invalid configuration.
Global declarative configuration is not useful in situations where a developer chooses to call a component’s static init
method directly, since the configuration could be given as an argument. Component’s should support that, and consider throwing an error if declarative configuration exists when init
is called.
Components should provide a static destroy
method that reverts the component to a pre-init
state. This is not necessary for large components like o-header
where it doesn’t make sense for it to be reverted. Traces which can not be garbage collected should not remain after the destroy
method is called.
Components should use o-errors to report runtime JavaScript errors and exceptions, as well as log notices and other significant events, using the oErrors.log
custom event.
Where components do not explicitly convert exceptions into o-errors
events, any unhandled exceptions will still be caught and reported if o-errors
has been initalised on the page. However, the report will lack critical information about the DOM elements to which the error relates.
Components must not use JavaScript features that are not yet part of a finalised standard, or which are proprietary, even if polyfills for them are available. JavaScript in components may use ES6 syntax, which product developers are expected to transpile to be ES5 if required.
If you want to use a modern browser feature, you must:
origami.json
; orComponents may wish to communicate (or make communication possible) with other components of the same type, other components of different types, or non-component code in the host page. This should be accomplished with API methods (when invoking known dependencies) and DOM events (in all other cases).
Components may expose an external API via an ESModule export
. Components must not rely on the existence of any APIs other than those that are explicitly required dependencies.
Components may emit events to allow loose coupling with other components and the host page. In doing so, the component must:
details
property.oComponentName.eventName
dialogClose
, not dialogClosed
, and using camel-case.A valid example of a component emitting a DOM event is shown below:
this.dispatchEvent(new CustomEvent('oExampleComponent.exampleEvent', {
detail: {...},
bubbles: true
}));
Components may bind to events emitted by themselves, other components, the host page or the browser. In doing so, the component:
Components should handle events during the bubbling phase, not the capturing phase (unless the event has no bubbling phase)
If a module wishes to bind to the DOMContentLoaded
or load
browser events, it must prefix the event name with o.
, and must expose the function that it binds to the event via its external API, eg:
if (typeof document !== 'undefined') {
document.addEventListener('o.DOMContentLoaded', init);
}
export init;
Components may emit events defined by other components, using the other component’s namespace, but must only do so if:
details
payload; anddetails
payload.For the most part, use of this technique creates too much ‘magic’ behaviour that would not be expected by a product developer and should not be used. Except in some cases e.g. analytics, where the approach may be a reasonable compromise to enable loose coupling.
Components that store data on the client via user-agent APIs must encapsulate all the logic required to get and set that data and must remain compatible with the format of data that they store, unless the major version number of the component changes. In that case the component must not invalidate any existing data, and should provide advice in documentation on migrating user data from previous versions.
For viewport events that may fire several times in quick succession (scroll
, resize
and orientationchange
) it’s good practice to throttle listeners to these. o-viewport provides pre-throttled abstractions of these events and should be used by components that need to listen for changes to the viewport.
A component e.g. o-overlay
, may need to display some or all of its owned DOM outside of the normal content flow so that it obscures content outside its owned DOM. The component must bind to and fire o-layers
events on its closest parent with the class o-layers__context
, or body
if no such element exists. The component must use the custom events defined in o-layers
to:
o-layers__context
by other components that make use of the z-axis.Any component may use the o-layers__context
class to define a new region of the DOM that can handle new layers independently of other regions of the DOM (e.g. two graphs handling their own tooltips independently, a date-picker appearing within a modal dialog). To learn more see o-layers.