Skip to main content

Configuring JS layout

Fast Simon Shopper Assistant uses advanced view rendering to bring the most optimized user experience. To adjust elements or logic we provide unique hooks you can use to edit the elements we generate.

Getting Started

Shopper Assistant hook Flow

  1. Register a hook using a script tag located in the <head>
  2. Shopper Assistant will pickup the hook and will execute any code you write there
  3. Hook will run again on every life cycle update

Rules

  1. Registering multiple hooks with the same event will execute all registered callbacks in order.
  2. You should only manipulate the dom element provided by the hook.
  3. We only update the dom nodes that changed in each life cycle change, so make sure your code doesn't add the same elements twice (see examples)
  4. Hook runs per component, data will contain the specific component state

Register a new hook & execute it

<script>
function shopperAssistantHooks() {
window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.ChatHeader,
({element, title, subtitle, config}) => {
// Customize chat header
element.style.borderBottom = '2px solid gold';
}
);

window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.Message,
({element, content, messageType, products}) => {
// Customize individual messages
if (messageType === 'system') {
element.style.backgroundColor = '#f0f0f0';
}
}
);
}

// execution here
if (window.FastSimonChat) {
shopperAssistantHooks();
} else {
window.addEventListener('fast-chat-assistant-ready', function () {
shopperAssistantHooks();
});
}
</script>

Delete a registered hook

<script>
// To delete a hook, pass null as the callback
window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.ChatHeader,
null
);
</script>

Available Hooks

Event NameDescriptionHook ParamsReturn Params
"chat-header"Life cycle hook: every chat header updateobject: {element: HTMLElement, title: string, subtitle: string, config: ChatConfig}void
"chat-footer"Life cycle hook: every chat footer updateobject: {element: HTMLElement, currentInput: string, isMessageLoading: boolean}void
"chat-body"Life cycle hook: every messages container updateobject: {element: HTMLElement, messages: Message[], isMessageLoading: boolean}void
"message"Life cycle hook: every individual message updateobject: {element: HTMLElement, content: string, type: MessageType, messageType: 'user' | 'system', products?: ServerProduct[], messageId?: string}void

type references: Typescript Types

Hook Patterns

Working with Shadow DOM

The Shopper Assistant uses Shadow DOM in production builds. When customizing elements, make sure to:

  1. Use the provided element parameter to access the DOM
  2. Avoid using document.querySelector() - use element.querySelector() or element.querySelectorAll() instead
  3. Styles added via hooks must be inline or injected into the shadow root
window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.ChatBody,
({element, messages}) => {
// ✅ Correct: Use the provided element to query all message containers
const messageContainers = element.querySelectorAll('.messages-container > *');

messageContainers.forEach((container, index) => {
// Add message number badge
if (!container.querySelector('.message-number')) {
const badge = document.createElement('span');
badge.className = 'message-number';
badge.textContent = `#${index + 1}`;
badge.style.cssText = 'position: absolute; top: 5px; right: 5px; background: #ccc; padding: 2px 6px; font-size: 10px; border-radius: 10px;';
container.style.position = 'relative';
container.appendChild(badge);
}
});

// ❌ Incorrect: Don't use document directly
// const messageContainer = document.querySelector('.messages-container');
}
);

Avoiding Duplicate Elements

Since hooks run on every lifecycle update, ensure you don't add duplicate elements:

window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.ChatHeader,
({element}) => {
// Remove existing custom element first
const existing = element.querySelector('.my-custom-badge');
if (existing) {
existing.remove();
}

// Now add the new element
const badge = document.createElement('div');
badge.className = 'my-custom-badge';
badge.textContent = 'VIP Support';
element.appendChild(badge);
}
);
window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.Message,
({element, messageType, products}) => {
if (messageType === 'system' && products && products.length > 0) {
// Query all product carousel items
const productItems = element.querySelectorAll('.chat-product-carousel-item-container');

productItems.forEach((productItem, index) => {
// Find the price container
const priceContainer = productItem.querySelector('.product-price-container');
const priceElement = productItem.querySelector('.product-price');

if (priceContainer && priceElement) {
// Remove existing discount badge
const existingBadge = priceContainer.querySelector('.discount-badge');
if (existingBadge) {
existingBadge.remove();
}

// Get product data
const product = products[index];
if (product) {
const price = parseFloat(product.p);
const comparePrice = parseFloat(product.p_c);

// Calculate and show discount percentage
if (comparePrice > price) {
const discount = Math.round(((comparePrice - price) / comparePrice) * 100);
const badge = document.createElement('div');
badge.className = 'discount-badge';
badge.textContent = `-${discount}%`;
badge.style.cssText = 'background: #FF4444; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-left: 8px;';
priceContainer.appendChild(badge);
}
}
}
});
}
}
);

Conditional Logic Based on Message Type

window.FastSimonChat.registerHook(
window.FastSimonChat.CustomizationEvent.Message,
({element, messageType, products, content}) => {
if (messageType === 'system' && products && products.length > 0) {
// Add a custom badge for product recommendations
if (!element.querySelector('.products-badge')) {
const badge = document.createElement('span');
badge.className = 'products-badge';
badge.textContent = `${products.length} product${products.length > 1 ? 's' : ''} recommended`;
badge.style.cssText = 'background: #4CAF50; color: white; padding: 4px 10px; border-radius: 4px; font-size: 12px; display: inline-block; margin-bottom: 8px; font-weight: 500;';
element.prepend(badge);
}
}

if (messageType === 'user') {
// Customize user messages
element.style.backgroundColor = '#e3f2fd';
element.style.borderRadius = '12px';
element.style.padding = '10px 14px';
}
}
);