Custom Menu

Goal

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.

Sample configurator with a custom menu UI.

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.

Config group and variants for the material applied to the seat.

Adding the markup

Once the scene has been prepared, the configurator component can be added to the page.

<cm-configurator-main ui-style="default" template-uuid="fb825772-66cd-4001-ae46-d537f194d6a2" use-external-menu="true"></cm-configurator-main>

We also add an element that functions as a container for the menu options.

<div class="menu-controls" 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() {
    // clear the menu container
    menuContainer.innerHTML = ""
    // 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-section">
                <h3>${parameter.name}</h3>
                <div class="parameter-flex" id="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="${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)
    }
}

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 changeCompleted 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)
    // Update the menu when available parameter values changed through the setParameter API
    cmConfigurator.addEventListener("changeCompleted", 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 rel="preconnect" href="https://configurator.colormass.com" />
        <!-- Load the colormass configurator styles -->
        <link href="https://configurator.colormass.com/styles.css" rel="stylesheet" />
        <!-- Load the colormass configurator web components -->
        <script src="https://configurator.colormass.com/cm-configurator.js" type="module"></script>

        <link rel="stylesheet" href="../styles/sample.css" />
        <link rel="stylesheet" href="../styles/ui.css" />

        <title>Custom Menu | Example</title>
        <meta charset="UTF-8" />
 
        <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)
                // Update the menu when available parameter values changed.
                cmConfigurator.addEventListener("changeCompleted", initializeMenu)
            })

            async function initializeMenu() {
                // clear the menu container
                menuContainer.innerHTML = ""
                // 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-section">
                            <h3>${parameter.name}</h3>
                            <div class="parameter-flex" id="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="${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">
                <div class="menu-controls" id="menu-container">
                    <!-- Container holds the dynamically rendered menu elements -->
                </div>
            </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>

Last updated