Block component
A block component injects HTML at a specific location in Future Ordering UI. When registering a block component a location is specified which tells the UI where to place the HTML it generates. Multiple components can be registered to the same location.
How it works
Registering a component in its simplest form just needs two properties.
- location - where the HTML it produces should be placed (predefined list of locations)
- loadContent - a function that generates the HTML contents that is injected when component is executed.
Hello world example that registers component at the location "start-bottom". The loadContent function generates a div with a text.
export default async context => {
context.componentLoader.registerBlockComponent({
location: 'start-bottom',
loadContent: async ctx => {
const { shadow } = ctx;
const container = document.createElement('div');
container.innerText = 'Hello world';
shadow.appendChild(container);
},
});
};
List of supported block component locations
Loading content
Block components (and also page components) provides a property "shadow" in the context object. Shadow is a separate DOM specifically created for the component. The shadow DOM is separate from the parent page's DOM. Any HTML or CSS changes made in the component will not affect the parent page.
NOTE: This is the only way of loading HTML that is supported, the component should not access the page DOM to read and/or make changes. See plugin guidelines for more info.
Example of a component that displays a div.
export default async context => {
context.componentLoader.registerBlockComponent({
location: 'categories-main',
loadContent: async ctx => {
const { shadow } = ctx;
const container = document.createElement('div');
container.innerText = 'Hello world';
shadow.appendChild(container);
},
});
};
Component styling
Context object
See type reference for more information about context object
Conditional loading
A component can be loaded in certain contexts by utilizing the "shouldBeLoaded" function. This callback provided to the registerBlockComponent object is called each time a page with the current block component location is loaded to determine if the block component should be executed.
Example of a component that is only loaded if in a kiosk context.
export default async context => {
context.componentLoader.registerBlockComponent({
location: 'start-bottom',
shouldBeLoaded: ctx => ctx.kiosk.isKiosk(),
loadContent: async ctx => {},
});
};
Example of a component that is only loaded if the current user is logged in.
export default async context => {
context.componentLoader.registerBlockComponent({
location: 'start-bottom',
shouldBeLoaded: async ctx => Boolean(await ctx.user.getCurrentUser()),
loadContent: async ctx => {},
});
};
Example of a component that preloads data which is then used to determine whether to load the component.
export default async context => {
// load data from api directly on application load
// NOTE: This plugin is executed for each user visiting the Future Ordering UI,
// regardless of the page, this behavior might not be desirable
const promise = fetch('https://myservice.com/api');
context.componentLoader.registerBlockComponent({
location: 'start-bottom',
shouldBeLoaded: async ctx => {
// wait for response from api before determining if the component should be loaded
const response = await promise;
if (response.ok) {
const data = await response.json();
return data.someProperty;
}
return false;
},
loadContent: async ctx => {},
});
};
If shouldBeLoaded is omitted a component will always be loaded.
Reduce Unnecessary Component Width and Height Changes
When a component requests data from other APIs, it may take time for the page to fully load. During this process, the page's content is updated asynchronously as components finish loading. However, if components are not loaded in sync with the rest of the page content, users may begin interacting with the Future Ordering UI before all components are fully loaded. This can lead to a poor user experience, as users may find it difficult to interact with the page if content continues to move around as components load. This issue is particularly noticeable if a block component is loaded before other content on the page, or if multiple components are loaded at the same location.
It is therefore recommended that the component attempts to "reserve" the space it is expected to use before waiting for the content. An example of how this could be achieved is demonstrated below:
This plugin sets a height to the container element and is also helpful by displaying "Loading..." while it loads the data.
export default async context => {
context.componentLoader.registerBlockComponent({
location: 'categories-main',
loadContent: async ctx => {
const { shadow } = ctx;
// make the plugin take up 200px in height directly when inserted on the page
const css = new CSSStyleSheet();
await css.replace(`
div {
height: 200px;
}
`);
shadow.adoptedStyleSheets.push(css);
// show loading text
const container = document.createElement('div');
container.innerText = 'Loading data..';
shadow.appendChild(container);
// fetch data from other service
const res = await fetch('https://myservice/api');
if (res.ok) {
const data = await res.json();
// update the content
container.innerText = data.text;
}
else
container.innerText = 'Failed to load';
},
});
};
Component Removal from Page
The "disconnectedCallback" function is executed when the component is removed from the page. It is crucial for components to perform cleanup operations, such as clearing event listeners or resetting state, when they are removed. Below is an example of a component that performs cleanup tasks.
export default async context => {
let fetchDataInterval;
context.componentLoader.registerBlockComponent({
loadContent: async ctx => {
const { shadow } = ctx;
fetchDataInterval = setInterval(() => {
// fetch data
}, 10000);
},
disconnectedCallback: () => {
clearInterval(fetchDataInterval);
},
});
};