Skip to main content

UI Submodule

UI submodule has some helper functions to hide/show, set attribute/property like styling, detecting iframe load/unload etc.

Detecting when iframes have loaded and/or unloaded

Detecting iframe load and unload events is very straightforward through the help of the UI submodule functions onIframeLoaded and onIframeUnloaded.

Detecting whether iframe has loaded

window.hapi.ui.service.onIframeLoaded((iframeName) => {
console.log('HAPI Elements iframe with name has LOADED:', iframeName)
})

Detecting whether iframe has unloaded

window.hapi.ui.service.onIframeUnloaded((iframeName) => {
console.log('HAPI Elements iframe with name has UNLOADED:', iframeName)
})
note

You should place the onIframeLoaded and onIframeUnloaded events in the hapi:load:script event otherwise because you are not explicitly removing these events, if you add these events again, the callback provided will run N times the amount of times you have added the listeners.


Example with hapi:load:script:

const centralStateManagement = {
hasHapiElementsLoaded: false,
loadedIframeNames: [],
}

window.addEventListener("hapi:load:script", () => {
centralStateManagement.hasHapiElementsLoaded = true

window.hapi.ui.service.onIframeLoaded((iframeName) => {
centralStateManagement.loadedIframeNames.push(iframeName)
})

window.hapi.ui.service.onIframeUnloaded((iframeName) => {
centralStateManagement.loadedIframeNames = centralStateManagement
.loadedIframeNames
.filter(
loadedIframeName => loadedIframeName !== iframeName
)
})
})

Inspecting the iframe DOM

Let's consider you want to hide "Add to Campaign"/"Remove from Campaign" buttons. Most of the time, elements like these will have an id attribute which will contain some helper text to help you create your query selector.


First inspect the element you want to work with, within the iframe. Finding the Element Selector


The button has a long id value in which there is a unique ID belonging to that particular product/contract; however we only care about the -toggle-basket part. We can query custom element attributes that are suffixed with -toggle-basket with this query selector: [id$=-toggle-basket].


info

Below example would change based on the HTML Element you want to take action upon. In the case of the basket toggle button, it is suffixed with -toggle-basket thus we use the CSS Attribute Selector id$=. For an HTML Element that has the ID id="some-prefixed-id-98asd9f898" you would want to use id^=some-prefixed-id. Please refer to CSS Attribute Selectors documentation for more complex use cases.

The following examples demonstrate how to hide and show elements:

Hiding an element with query selector

window.hapi.ui.service.hideElement("[id$=-toggle-basket]")
info

hideElement actually adds an event listener so that when elements matching the query selector mounts to the DOM, they are hidden. This is to prevent race conditions where you try to hide an HTML Element but you can't because it has not been mounted to DOM yet.


showElement with same selector need to be used to bring the elements back when you are done hiding them so there are no unintended side effects.

Showing an element with query selector

window.hapi.ui.service.showElement("[id$=-toggle-basket]")
info

Please note that this would also hide "Add to Campaign" button on the product page. Below example shows how to bring the buttons back when the steps change:

Depending on your use case, for example let's say you want to hide these buttons based on active order journey step, you can do:

window.hapi.orderJourney.state.stepActive.onChange(
(step) => {
switch (step.key) {
case window.hapi.orderJourney.utils.stepKeys
.selectContracts: {
window.hapi.ui.service.hideElement(
"[id$=-toggle-basket]",
)
break
}
default: {
window.hapi.ui.service.showElement(
"[id$=-toggle-basket]",
)
break
}
}
},
)
info

showElement actually adds an event listener so that when elements matching the query selector mounts to the DOM, they are shown. This is to prevent race conditions where you try to show an HTML Element but you can't because it has not been mounted to DOM yet.


hideElement with same selector need to be used to hide the elements back when you are done showing them so there are no unintended side effects.

Setting styles of an element with query selector

Sometimes, the HAPI Elements JS API for Theming might not be enough. Because the elements are inside iframes, there is no native way to style an element however we provide an HAPI Elements JS API function so that you can still style a HTML Element that is inside an iframe:

const ourCustomStylesForToggleItemInBasketButton = {
backgroundColor: "yellow",
color: "black",
borderRadius: "50px"
}

window.hapi.ui.service.setStyle(
"[id$=-toggle-basket]",
ourCustomStylesForToggleItemInBasketButton,
)
info

setStyle actually adds an event listener so that when elements matching the query selector mounts to the DOM, their styles are updated. This is to prevent race conditions where you try to set style of an HTML Element but you can't because it has not been mounted to DOM yet.

Setting a particular style attribute of an element with query selector and the name of the style property

In addition to the UI Function Submodule function setStyle, you can also set the value of a particular style property:

const ourCustomBackgroundColorForToggleItemInBasketButton = "yellow"

window.hapi.ui.service.setStyleAttribute(
"[id$=-toggle-basket]",
"background",
ourCustomBackgroundColorForToggleItemInBasketButton,
)
info

setStyleAttribute actually adds an event listener so that when elements matching the query selector mounts to the DOM, their style property is updated. This is to prevent race conditions where you try to set style property of an HTML Element but you can't because it has not been mounted to DOM yet.

Adding and removing CSS classes of an element

HAPI Elements uses under the hood TailwindCSS which is a utility-based CSS framework. The CSS classes of TailwindCSS are prefixed with hapi-.


So for example:

  • mt-4 becomes hapi-mt-4
  • md:hidden becomes md:hapi-hidden
  • !px-3 which adds !important becomes !hapi-px-3

You can add and remove these TailwindCSS classes applied to an HTML element using UI submodule's functions: addClass and removeClass. Examples are given below on how to use them.

Setting a particular attribute of an element with query selector and the name of the attribute and value of the attribute

In addition to the UI Function Submodule function setAttribute, you can also set the value of a particular attribute:

const attributeName = "disabled"
const attributeValue = "true"

window.hapi.ui.service.setAttribute(
"#user-journey-bar-next-button",
attributeName,
attributeValue
)
info

setAttribute actually adds an event listener so that when elements matching the query selector mounts to the DOM, their attribute is updated. This is to prevent race conditions where you try to set attribute of an HTML Element but you can't because it has not been mounted to DOM yet.

Adding a TailwindCSS class to an element with query selector

window.hapi.ui.service.addClass(
"[id$=-toggle-basket]",
// prefixed with an exclamation mark so that it has !important added
// depending on the element, the exclamation mark might not be needed
"!hapi-bg-yellow-500"
)
info

addClass actually adds an event listener so that when elements matching the query selector mounts to the DOM, their class property is updated. This is to prevent race conditions where you try to add a class to an HTML Element but you can't because it has not been mounted to DOM yet.

Removing a TailwindCSS or CSS class from an element with query selector

window.hapi.ui.service.removeClass(
"[id$=-toggle-basket]",
// you can only remove classes that are already applied to the element
// if class is not found, it will ignore removeClass action
"hapi-w-full",
)
info

removeClass actually adds an event listener so that when elements matching the query selector mounts to the DOM, their class property is updated. This is to prevent race conditions where you try to remove a class from an HTML Element but you can't because it has not been mounted to DOM yet.

Appending and removing HTML Elements to a slot element

It is possible to add custom HTML elements and/or remove elements to/from a slot (different than hiding) using UI submodule appendChildToSlot and removeChildFromSlot functions. For certain areas, we have <slot></slot> elements that you can use to append and remove custom HTML Elements. To find the slots, you would inspect the DOM with the Developer Tools and get the id property of the slot then provide it to appendChildToSlot and removeChildFromSlot functions.

Appending a custom HTML Element to a slot element

const customButtonEl = document.createElement("button");
customButtonEl.id = "our-custom-id"; // useful when you want to remove it later
// you can add your own event listeners (please note that addEventListener will not work)
customButtonEl.onclick = () => { // there are no arguments returned
console.log("Custom Button Clicked")
}
customButtonEl.innerHTML = "<b>Some Bold Text</b>"
const customButtonElStyles = {
textTransform: "uppercase",
}
Object.entries(customButtonElStyles).forEach(([key, value]) => {
customButtonEl.style.setProperty(key, value)
})

window.hapi.ui.service.appendChildToSlot(
"product-landing--before", //name of the slot
customButtonEl, //your HTML Element
)
info

You can also add TailwindCSS classes to your custom elements: customButtonEl.classList.add("hapi-p-4", "hapi-bg-red-500")

And also you can use the Global CSS variables mentioned in Theming and Fonts > Global Theming section however these CSS variables get rendered in kebab-case prefixed with --hapi-global-. Some examples:

  • --hapi-global-primary-background-color
  • --hapi-global-accent-text-color

To utilize them in your custom component you would do:

customButtonEl.style.setProperty("background-color", "var(--hapi-global-primary-background-color)")
info

appendChildToSlot actually adds an event listener so that when elements matching the slot ID mounts to the DOM, the child is appended to the slot. This is to prevent race conditions where you try to add a custom HTML Element but you can't because slot has not been mounted to DOM yet.

Removing an HTML Element from a slot element

By default the slots will be empty until you add custom elements to them. Therefore there won't be HTML Elements to remove, unless you want to remove an element you have previously appended. Let's first append an element to the slot, then remove it:

const customButtonEl = document.createElement("button");
customButtonEl.id = "our-custom-id"; // useful when you want to remove it later
// you can add your own event listeners (please note that addEventListener will not work)
customButtonEl.onclick = () => { // there are no arguments returned
console.log("Custom Button Clicked")
}
customButtonEl.innerHTML = "<b>Some Bold Text</b>"
const customButtonElStyles = {
textTransform: "uppercase",
}
Object.entries(customButtonElStyles).forEach(([key, value]) => {
customButtonEl.style.setProperty(key, value)
})

window.hapi.ui.service.appendChildToSlot(
"product-landing--before", //name of the slot
customButtonEl, //your HTML Element
)

// Remove custom element from the same slot
window
.hapi
.ui
.service
.removeChildFromSlot(
"product-landing--before",
"#our-custom-id" // this argument is a query selector thus we have the hash/pound sign to select the ID
)
info

Contrary to other UI submodule functions removeChildFromSlot does not add an event listener. Removing an element from a slot would depend on your business logic thus the moment you call this function, the existing element you added to the slot before gets removed.