A common use case is the integration of the configurator into a shop system or an existing website. As the design options of the built-in menu are limited, it is often necessary to create a separate menu to control the parameters of the configurator.
Examples
The following image shows the customised menu from the sample project. The options are generated dynamically based on the available config parameters and control the rendering of the configurator.
Setting the scene
For this example, we set up a test scene on the colormass platform. It consists of a chair and a configuration node to select from a list of materials for the front and back of the chair.
Adding the markup
Once the scene has been prepared, the configurator component can be added to the page.
We also add an element that functions as a container for the menu options.
<div class="menu" id="menu-container">
<!-- Container holds the dynamically rendered menu elements -->
</div>
Adding the scripts
We use the getParameterList API to get all available configuration parameters for the current template. For each parameter, we cycle through the available configuration values and create a button element. The button uses setParameter to update the rendering.
Note: we use a small helper function htmlToNode to create the elements to prevent repetition.
async function initializeMenu() {
// load all available parameters
parameterList = cmConfigurator.getParameterList()
for (const parameter of parameterList) {
// create a container element for each parameter
const parameterContainer = htmlToNode(`
<div class="menu-parameter-container">
<h3>${parameter.name}</h3>
<div class="menu-parameter-values-container"></div>
</div>
`)
for (const parameterValue of parameter.values) {
// create a button for each available parameter value
const parameterValueButton = htmlToNode(`
<button class="parameter-value ${parameterValue.id === parameter.value ? 'active' : ''}">
${parameterValue.name}
</button>
`)
// use `setParameter` to update the rendering
parameterValueButton.onclick = async (event) => {
await cmConfigurator.setParameter(parameter.id, parameter.parameterType, parameterValue.id)
}
parameterContainer.querySelector('.menu-parameter-values-container').appendChild(parameterValueButton)
}
menuContainer.appendChild(parameterContainer)
}
}
As soon as the configurator element has finished loading, we add two event listeners to call the above defined render function initializeMenu. The loadingCompleted event is triggered when the first loading of the configurator is complete. The configurationLoaded event is triggered when the render parameters have changed.
let cmConfigurator, menuContainer, parameterList
customElements.whenDefined("cm-configurator-main").then(() => {
cmConfigurator = document.querySelector("cm-configurator-main")
// Initialize UI elements
menuContainer = document.getElementById("menu-container")
// Once the configurator has loaded, the menu can be rendered based on the available parameters
cmConfigurator.addEventListener("loadingCompleted", initializeMenu)
// Update the menu when available parameter values changed.
cmConfigurator.addEventListener("configurationLoaded", initializeMenu);
})
Putting it all together
The complete example code can also be found in our sample repository.
<!DOCTYPE html>
<html style="height: 100%">
<head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link href="https://storage.googleapis.com/cm-platform-prod-static/fonts/fontawesome-pro-6.0.0-alpha3/css/all.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Roboto:200,300,400,500,600,700" rel="stylesheet" />
<!-- Load the colormass configurator web components -->
<script src="https://configurator.colormass.com/cm-configurator.js" type="module"></script>
<title>Custom Menu | Example</title>
<meta charset="UTF-8" />
<style>
body {
width: 100%;
height: 100%;
margin: 0;
}
:root {
--cm-menu-max-num-cols: 8;
--cm-menu-icon-size: 40px;
--cm-menu-background-color: rgba(255, 255, 255, 0);
font-family: "Roboto";
}
.container {
display: flex;
height: 100%;
overflow: hidden;
}
.menu {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding: 20px;
width: 300px;
background-color: #f4f4f4;
overflow: hidden;
}
.menu-parameter-values-container {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
button.parameter-value {
cursor: pointer;
}
button.parameter-value.active {
background-color: lightblue;
}
.configurator-container {
flex-grow: 1;
box-sizing: border-box;
}
</style>
<script>
let cmConfigurator, menuContainer, parameterList
customElements.whenDefined("cm-configurator-main").then(() => {
cmConfigurator = document.querySelector("cm-configurator-main")
// Initialize UI elements
menuContainer = document.getElementById("menu-container")
// Once the configurator has loaded, the menu can be rendered based on the available parameters
cmConfigurator.addEventListener("loadingCompleted", initializeMenu)
// Update the menu when available parameter values changed.
cmConfigurator.addEventListener("configurationLoaded", initializeMenu);
})
async function initializeMenu() {
// load all available parameters
parameterList = cmConfigurator.getParameterList()
for (const parameter of parameterList) {
// create a container element for each parameter
const parameterContainer = htmlToNode(`
<div class="menu-parameter-container">
<h3>${parameter.name}</h3>
<div class="menu-parameter-values-container"></div>
</div>
`)
for (const parameterValue of parameter.values) {
// create a button for each possible parameter value
const parameterValueButton = htmlToNode(`
<button class="parameter-value ${parameterValue.id === parameter.value ? 'active' : ''}">
${parameterValue.name}
</button>
`)
parameterValueButton.onclick = async (event) => {
updateParameterMenuActiveStates(event.currentTarget)
await cmConfigurator.setParameter(parameter.id, parameter.parameterType, parameterValue.id)
}
parameterContainer.querySelector('.menu-parameter-values-container').appendChild(parameterValueButton)
}
menuContainer.appendChild(parameterContainer)
}
}
function updateParameterMenuActiveStates(element) {
// remove active class from all siblings
const siblings = element.parentElement.children
for (const sibling of siblings) {
sibling.classList.remove("active")
}
// set active class for clicked element
element.classList.add("active")
}
function htmlToNode(html) {
const template = document.createElement("template")
template.innerHTML = html.trim()
return template.content.firstChild
}
</script>
</head>
<body>
<div class="container">
<div class="menu" id="menu-container">
<!-- Container holds the dynamically rendered menu elements -->
</div>
<div class="configurator-container">
<cm-configurator-main ui-style="default" template-uuid="fb825772-66cd-4001-ae46-d537f194d6a2" use-external-menu="true"></cm-configurator-main>
</div>
</div>
</body>
</html>
Sample configurator with a custom menu UI.
Config group and variants for the material applied to the seat.