6M Technical Reference
6M version | 2.2.0 |
---|
This document describes the rules by which TUI 6M components are to be developed. If you don’t know what 6M is, read the 6M Intro. If you are developing a 6M component for the first time, you may find the 6M tutorial handy.
The 6M reference describes only the mandatory traits of a 6M component. You are strongly encouraged to have a look at the best practices as well in order to develop a high-quality component.
The frontend
Web component
From the frontend perspective, a 6M component is a web component, which produces a custom element.
The names of the custom element must be prefixed with tui-
and must be unique. If you’re unsure if a name is already taken, look into the components catalog.
Tag and attribute naming
The initial configuration of the custom element must happen through HTML attributes. In order to allow the element to be used multiple times within the same context (i.e. on the same or in the same SPA), it must support a scope
attribute. If the scope
attribute is set, all event publishers and subscribers registered by the component must respect the scope.
The following naming rules for attributes apply:
- Do not use global attributes/properties such as
id
,class
,style
,is
,hidden
orslot
for element-specific features. - Do not use the
data-
prefix for attributes on custom elements. They are not needed and make code less readable. - The
locale
attribute must only be used for setting the component’s locale (see below). - The
scope
attribute must only be used for setting the component’s event scope (see below). - Attribute names must be lower case.
Inner/private web components
If your 6M component ships additional custom elements, e.g. to build its inner parts, remember to prefix the names of these inner elements with the main component’s tag name followed by two dashes. For example, if your component’s tag is tui-image-gallery
, and you define a button as an inner element, this element’s tag name should be something like tui-image-gallery--button
.
Attribute changes at runtime
At the moment, it is not mandatory that the component reacts to attribute changes which occur after their initialisation. But this may become a requirement in the future, therefore it is recommended to implement this feature, if possible.
Skeletons
The component must provide at least one skeleton placeholder for each of its custom elements. Skeletons are neccessary to show an early preview of the layout and reduce flickering on the screen during loading/rendering of a website.
In practice, the owner of a website will use a custom element together with one of its skeletons like this:
<tui-image-gallery hotel="ABC1234">
<!-- BEGIN SKELETON -->
<div style="background: #eee; padding: 30px; display: flex;">
<div style="background: #ddd; width: 110px; height: 110px; border-radius: 50%"></div>
<div style="flex: 1 0; margin-left: 30px">
<div style="background: #ddd; height: 30px; width: 100%; margin-bottom: 10px;"></div>
<div style="background: #ddd; height: 30px; width: 100%; margin-bottom: 10px;"></div>
<div style="background: #ddd; height: 30px;"></div>
</div>
</div>
<!-- END SKELETON -->
</tui-image-gallery>
The skeleton must be styled with inline CSS to have the exact size of the real element. It should also show the shapes of its primary content elements.
Each Skeleton template must be put into a separate HTML file.
Web components have a feature called Slots. Slots can be anonymous or named. If you are using slots in your 6M component, please keep in mind that a skeleton would be inserted into anonymous slots. Therefore it is mandatory to only use named slots in 6M components.
Styling and generic elements
Components should be usable in different branding contexts. For that purpose, TUI has developed a design system based on generic web components, called UI Elements. You can and should use them in your web component to reduce the resource footprint of your 6M component. To learn more about available elements and their usage, have a look at the Element section on this website.
The component must also support branding based on the color schemes of the different TUI brands and be aware of the brand
attribute on the host element, i.e. styling via :host([brand=…])
. Have a look at the design.tui documentation to learn more.
The JavaScript
It is entirely up to you how the JavaScript looks on the inside, just take care of the following:
- The component must run in all common browsers, mobile and desktop (yes, this includes IE 11).
- There are already polyfills for web components v1, provided through UI Elements, which are based on the Stencil project.
- Your code must not pollute the global (
window
) namespace, or at least take measures to avoid collisions. - It must not have any external or cross-dependencies, unless they are shipped as a part of the component. (Exceptions are UI Elements and the Cotton Ball.)
- Your code must not mess with JavaScript internals (i.e. no “monkey-patching”).
- While you are free to use any framework/libraries you want, you should prefer lightweight solutions where possible.
- Think about performance and memory footprint when writing your code.
Please have a look at the unified build process requirements, too. There are several rules regarding the built JavaScript.
Event Handler (the “Cotton Ball”)
6M components talk to each other through events managed by a Publish/Subscribe event handler which we call the Cotton Ball. The Cotton Ball is the only “hard” dependency of a 6M component, and as such will always be present in the context of a website, so you can expect it to be there.
Cotton Ball API
The Cotton Ball has the following methods:
/**
* Emits an event.
*
* @param string componentName The components own name.
* @param string scope The scope, as passed through the `scope` attribute.
* @param string eventName The name of the event.
* @param object payload The event data/payload.
*
* @return void
*/
tuiCottonBall.publish(componentName, scope, eventName, payload)
/**
* Emits an event.
*
* @param string componentName The name of the component from which an event is expected.
* @param string scope The scope in which to listen.
* @param string eventName The name of the event to listen on.
* @param function callback Callback
*
* @return function Callback to unregister the event
*/
tuiCottonBall.subscribe(componentName, scope, eventName, callback)
Event scoping
6M has the concept of scoping: A 6M component can be put into one or more scopes, thus grouping it together with other components. The components HTML tag must support the scope
attribute to which the maintainer of a host site can assign the scope(s).
When a component publishes, or subscribes to, an event, it must always pass the value of the scope
attribute. Usually the value will be a string, but it may also be undefined
if the host site maintainer does not assign a scope. The Cotton Ball will take care of distributing events into the right scopes.
Component and event names
The publish
and subscribe
methods have two name-related parameters: componentName
and eventName
. The componentName
is the root namespace, the eventName
is the specific name. Names are written in lowercase and may contain lowercase letters, dashes, underscores and numbers. Event names may additionaly contain dots for separating sub-namespaces.
const currentScope = this.getAttribute("scope") || "*"
tuiCottonBall.publish("image-gallery", currentScope, "init", {/* … */})
tuiCottonBall.publish("image-gallery", currentScope, "imgage-gallery.slider.open", {/* … */})
…
Avoiding collisions
Remember that your component runs in the same website context as other components. Wherever you occupy resources or assign names, remember to avoid collisions. Some areas to look out for are:
- Custom Element tags
- CSS class names (outside of Shadow DOM)
- Cookie names
- Local/Session Storage keys
Use appropriate measures, such as namespacing, to avoid collisions.
Localisation
All components must be multilingual by design, supporting the en-US
locale as fallback as well as at least one additional locale (usually de-DE
). Note that in JavaScript and HTML, the language and country parts are separated by a dash, not an underscore.
Locales are set through the locale
attribute on the element. If the component doesn’t ship localisation for the requested locale, it should try to use a similar one, or en-US
as fallback.
For example, if the locale
attribute requests the de-AT
locale, but the component only supports de-DE
, it should switch to that one. If the locale
attribute requests the pt-BR
locale, and the component doesn’t support Portuguese at all, it should switch to the en-US
locale.
You are free in your choice of localisation implementation, though you might want to have a look at the l10n library maintained by the TUI.com team. It is lightweight, can extract localized messages from your code and works well with web components.
UI locale vs. content locale
If the component is able to display content in a different locale than the UI (for example legal documents), it may offer the configuration this feature though an element attribute. Consider the following example:
<tui-document-viewer content-locale="fr-BE"></tui-document-viewer>
The 6m.json
file
A 6M component must have a 6m.json
file with 6M-specific meta information.
File location
The 6m.json
must be publicly available at runtime from your assets server. In fact, making this file available and submitting its URL to the 6M components registry is the official way to publish the component.
The files referenced within 6m.json
are expected to be available relative to the location of the 6m.json
file.
JSON properties
The file must contain a JSON object with the following properties:
name
A human readable name of the component, such as “Image Gallery”. To avoid redundancy, the name should not contain “TUI” or “6M”.
file
The location of the main JavaScript file for embedding the component. This can be either a path, in which case it is considered relative to the 6m.json
file. Or it can be a full URL. The file
field is optional. If it doesn’t exist, the file is assumed to be named main.js
, and to be in the same directory as the 6m.json
file.
icon
The location of an image file (PNG or JPG) with a resolution of 300x300 pixels. Expect the image to be scaled down to much lower dimensions.
screenshots
An optional array of objects, where every object has the keys location
and description
. The location
is the location of an image file (PNG or JPEG) showing a screenshot of the component. It is recommended to show the component with a width of 1024 pixels, though other sizes are allowed as well. The description
should contain a caption for the image.
maintainer
A string containing an e-mail address to contact the maintainer of this component.
NOTE: If the component is maintained by a team, or maintainers are changing, it is recommemded to set up an e-mail alias and forward it to the responsible person(s).
description
A short description of 30 to 50 words, e.g. “A component for displaying images of hotels or other services. Can be used as part of other components or on the root level of a host page.” The description could, for example, be displayed in a list of known 6M components.
documentation
The location of a Markdown document containing extended documentation of the component, relative to the 6m.json
file, e.g. docs/about.md
. Consider your target audience to be website owners who are interested in embedding your component into their site. In-depth technical documentation is not required here, however, you are welcome to give hints on the integration. Also, you don’t need to document your attributes or events here, because this documentation will be generated automatically.
6m-version
The version of the specification the 6M component implements. The version number of this specification is found at the top of this document.
legacy
Some components have been developed in the pre-webcomponent era. They are not embedded as custom elements, but as div
tags, where the component identifier is added as a class name and attributes have a data-
prefix. If this is such a component, the value of legacy
must be true
. Otherwise it can be omitted or be set to false
.
locales
Contains an array of supported locales, e.g. ["en-US", "de-DE"]
.
tag
Contains a string with the tag name of the custom element, e.g. tui-image-gallery
.
attributes
An array of one or more objects, where every object has the following structure:
{
"name" : "hotel",
"description" : "An ID of a hotel.",
"documentation" : "docs/attributes/hotel.md",
"schema" : {
"type": "string",
"pattern": "^[A-Z]{3}[0-9]{5}$"
},
"required" : true
}
- The
name
key contains the attribute’s name. - The
description
key contains a description of the attributes functionality. - The
documentation
field contains the location of a Markdown document with extended documentation. This field is OPTIONAL. - The
schema
key contains a JSON schema definition (draft-07 compatible). - The
required
key indicates if the attribute is mandatory.
NOTE: The locale
, scope
and brand
attributes do not have to be documented.
events
The events
key contains an object with two sub-keys, publish
and subscribe
. They each contain an array with one or more items, where each item is an object describing an event:
{
"name" : "image-gallery.slideshow.next",
"description" : "Triggers when the slideshow switches to the next element.",
"data" : [
{
"name" : "nextPos",
"description" : "The position of the next image in the set. Note that if the last image is reached, nextPos will be `0`, i.e. the first picture.",
"documentation" : "docs/events/image-gallery.slideshow.next.md",
"schema" : {
"type": "integer",
"minimum": 0,
"maximum": 999
}
}
]
}
- The
name
key contains the event’s name. - The
description
key contains the event’s description. - The
documentation
field contains the location of a Markdown document with extended documentation. This field is OPTIONAL. - The
data
key describes the payload object of the event. It is an array, where each item is an object describing a property of the payload object:- The
name
key contains the property’s name. - The
description
key contains the field’s description. - The
documentation
field contains the location of a Markdown document with extended documentation. This field is OPTIONAL. - The
type
key contains the data type of the property’s value. Possible values areinteger
,float
,string
,array
andobject
. - The
schema
key contains a JSON schema definition (draft-07 compatible).
- The
You only need to document events which are defined by the component itself. In situations where your component acts as a “client” of a different component, i.e. it publishes, or subscribes to, an event defined elsewhere, you don’t need to document that particular event.
skeletons
The skeletons
field is an array containing at least one object. Each object contains a description
key with a brief description of the skeleton, and a location
key with the relative location of the skeleton file.
[
{
"description": "A minimal, non-animated skeleton.",
"location" : "skeletons/minimal.html"
},
{
"description": "An animated skeleton with more detail.",
"location" : "skeletons/animated.html"
}
]
examples
The examples
field is optional, if set it is an array containing objects. Each object contains a description
key with a brief description of the example, and a attributes
key with a map of attributes to set on the example element.
[
{
"description": "Showing the images in the most crazy way possible.",
"attributes" : {
"some-attribute" : "some-value",
"some-other-attribute" : "some-other-value"
}
}
]
Examples help editors and product owners to see usage scenarios of a component. It is especially advised to have examples if your component has required attributes and will fail if embedded without them.
The Middle Layer
General considerations
The middle layer of a 6M component is a translation service into the world of backends. Common use cases for middle layers are:
- Backend access which requires secret credentials,
- Complex or aggregated backend calls,
- Complex business logic which is too heavy to be delivered to, or executed in, the browser,
- Business logic which must stay confidential,
- Lambda-style functions, e.g. image processing.
In any case, the middle layer must be stateless, i.e. it must not do things like persisting data or managing sessions. Caching and serving imported data are edge-cases; make sure not to run into concurrency issues when multiple middle layers run in parallel.
6M Components without a Middle Layer
It is possible that a 6M component doesn’t need to talk to any backends at all, and therefore doesn’t need a Middle Layer. However, if there is backend communication, a dedicated Middle Layer must be used rather than talking to fat backends directly.
Technology
Developers of 6M components are free to implement and operate their Middle Layers in any technology (e.g. NodeJS, Golang, Java, Python, PHP) and with any API paradigm (e.g. REST or GraphQL) they want. It is however recommended to choose languages and frameworks which are best suited for a lightweight, stateless service.
The 6M reference describes only the mandatory traits of a 6M component. You are strongly encouraged to have a look at the best practices as well in order to develop a high-quality component.