# Embedding Integration

The embedding is used when you want to integrate the configurator "as it is" into your website or webshop. If you have massive adjustment wishes also consider using our SDK (opens new window)

# Questions

If you have troubles with your integration, feel free to ask us on Stackoverflow (opens new window). Just make sure you are using the tag roomle so we get a notification.

# The 10.000 feet view

At first, we want to have a quick look at the concepts and ideas behind the embedded Roomle Configurator.

On the one hand, there is your webshop and on the other hand, there is the Roomle Configurator. The configurator runs client-side which means there is no need for any server-side language like PHP etc. Everything just happens in plain old JavaScript.

To ease the communication between your website and the Roomle Configurator we provide a small JavaScript library that can be used to call methods of the configurator or to subscribe to events.

Those methods and events give you the ability to flexibly integrate the configurator as you need it.

This tutorial will guide you through some uses cases and should give you the knowledge to implement whatever you need.

# Getting started

As stated above the communication happens with the help of a JavaScript library. Therefore you need to integrate the library into your project. This can either happen via a package manager (e.g. npm, yarn...) or a simple HTML script tag.

All the needed files can be found here: https://www.npmjs.com/package/@roomle/embedding-lib (opens new window)

You can either download the tar.gz or if you use npm you could do the following:

npm install @roomle/embedding-lib --save

Always specify the correct configuratorId in the init options (more details on that later). The configuratorId is handed over to you by your Roomle Contact Person. The correct configuratorId prevents attackers from simply copying your code and use the configurator on their website. The only thing you have to do is, to specify the domains on which the Roomle Configurator should be available. For example: www.roomle.com. You can do this either in Rubens Admin or by telling your Roomle Contact Person. If something is wrongly configured you will see a message on the screen and further information in the JavaScript error console. If you can not figure out what is wrong please contact your Roomle Contact Person.

# Copy & Paste without package manager

This section shows a simple quick start. We always recommend using a package manager and a good built step but to quickly illustrate the idea of the Roomle Configurator these examples should be sufficient.

<html>
  <head>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
      }

      #configurator-container {
        width: 1200px;
        height: 675px;
      }
    </style>
  </head>

  <body>
    <div id="configurator-container"></div>
    <script src="./roomle-configurator-api.es.min.js" type="module"></script>
    <script type="module">
      import RoomleConfiguratorApi from './roomle-configurator-api.es.min.js';

      (async () => {
        const options = {
          id: 'usm:frame',
        };
        const configurator = await RoomleConfiguratorApi.createConfigurator(
          'demoConfigurator',
          document.getElementById('configurator-container'),
          options,
        );
      })();
    </script>
  </body>
</html>

To try it out just copy&paste the HTML snippet from above into a file called index.html and serve it from a web server. For quick experiments, the npm package http-server (opens new window) is very nice.

# Some notes on the example

# Browsers

As you can see the Roomle Configurator uses ES6 modules. We highly recommend creating a fallback for older browsers. Since the Roomle Configurator is a 3D tool written in web technologies a good user experience is only possible with modern browsers. For users with old browsers (e.g. Internet Explorer) we recommend creating a different sales funnel and therefore a different user journey. Since the alternatives differ widely based on the website and webshop Roomle can not provide a default fallback.

# Browser policy

We try our best to support as many browsers as possible. All supported browsers are listed in the System Requirements.

# Iframe

Yes, the Roomle Configurator is integrated as an iframe. This is per se nothing bad. Iframes have a bad reputation because they are also used for displaying ads etc. But the Roomle Configurator is not an annoying banner it is an essential part of the UX for a potential customer. Big platforms like Youtube, Vimeo, Paypal are also integrating their widgets as iframe. Of course, there are technologies like Web Components but the iframe approach has several advantages in the use case of Roomle Configurator, e.g.: each WebAssembly instance is in its own process, it's easier to react on resize events, etc.

"Iframes are bad for SEO they told me". Yes, that's true but only if you put essential data into the iframes. The Roomle Configurator only has a 3D scene that has basically no information for search engines. Therefore it makes sense to optimize your landing page on the website and treat the iframe just like you would treat an image.

Since the embedding webshop has no control over the iframe it also can not track what happens inside the iframe. Therefore we send important events to the webshop so it is possible to track events. More about that in the Recipes section.

# TypeScript

We provide typings for the embedding lib. At the time of writing it is necessary to use "skipLibCheck": true because we rely on things like WebAssembly and WebGL. Those typings are not available in all projects and therefore skipLibCheck is needed. We work on providing better shims so that this reliance is not necessary.

# The code

In the example, we set the width to 1200px and the height to 675px. This is on purpose to explain one concept we encountered during our extensive research on UX and customer behavior. When you try to configure something it makes sense to use as much space as possible on the screen of the user. Otherwise configuring becomes a pain. At the time of writing Roomle has a limit for 1024px width screens (be aware that this is just a random value and could change at any point in the future). This means if the container in which the configurator is placed smaller than this width the configurator is called in a "view only" mode. When the configurator is in "view only" mode a button in the right bottom is displayed. If the user clicks on this button the configuration starts and the configurator opens in "full page" mode. If you do not want to rely on this button you can implement your own logic. Either you keep the button visible or you can also hide it. You only need to pass the following to the init options: {buttons: {startconfigure: false}}. In the following paragraphs we outline a custom behaviour of the "start" button.

When you tell the configurator to start it will open in a "full page" mode. You can try this out by adding the following code to the example from above (the full example can be found in the file 01_small_screen.html):

<style>
  #configurator-container {
    width: 800px;
    height: 600px;
  }
</style>
<button>Start configure</button>
<script>
  const button = document.querySelector('button');
  button.addEventListener('click', () => configurator.ui.startConfiguring());
</script>

When you did everything correct you should see a button at the beginning of the page. We add an event listener to the button which calls startConfiguring on a click event.

This should open the configurator in "full page" mode and all the controls for configuring should be visible.

So it's up to the embedding website how to embed the configurator. If you want to start in "view only" just add the configurator to a small container. If you want to start directly in "configure mode" add it to a bigger container.

On mobile devices most screens will be too small to achieve the required width therefore the configurator is always opened in "full page" mode (otherwise configuring would be a pain for the user).

To only show the "start now button" you can listen to the onResize event. How this works will be illustrated in the following example (the full example can be found in the file 02_resize_callback.html):

<style>
  #configurator-container {
    width: 800px;
    height: 600px;
  }

  button {
    display: none;
  }
</style>
<button>Start configure</button>
<script>
  const button = document.querySelector('button');
  configurator.ui.callbacks.onResize = (isDesktop) => {
    if (!isDesktop) {
      button.style.display = 'block';
    } else {
      button.style.display = 'none';
    }
  };
  button.addEventListener('click', () => configurator.ui.startConfiguring());
</script>

Now you should see the "start" button only if needed. Because we hardcoded the container width to 800px you will always see it. If you play around with this value you will see how the display changes.

If you just want to start the configurator and do not want to bother about the screen sizes you could change the example to the following (the full example can be found in the file 03_instant_start.html):

<style>
  #configurator-container {
    width: 800px;
    height: 600px;
  }

  button {
    display: block;
  }
</style>
<button>Start configure</button>
<script>
  const button = document.querySelector('button');
  const options = {
    id: 'usm:frame',
  };
  button.addEventListener('click', async () => {
    const configurator = await RoomleConfiguratorApi.createConfigurator(
      'demoConfigurator',
      document.getElementById('configurator-container'),
      options,
    );
    configurator.ui.startConfiguring();
  });
</script>

Now, as soon as you click on the button the configurator is started and immediately and opened in the needed size.

All the currently available methods are available behind the ui object. So if you want to know what's possible you can either look on the TSDocs or just inspect the ui object.

You will recognize that there is also an entry for callbacks. This is basically the event system of the configurator. In the example, above we already outlined how to use the onResize event and the same applies to all the other events available on the ui.callbacks objects. All the details can be found in the TSDocs as well.

Now you should know the basics and the idea of how to use and integrate the Roomle Configurator. Now there is the time to play around and create great user experiences.

The next section will illustrate some "recipes"

# Upgrades

We follow the sematic versioning (opens new window) approach and to make sure no breaking change slips through we follow the Conventional Commits (opens new window) convention. Therefore always make sure to read through our changelog (opens new window) if you upgrade to a new major version of the embedding lib. All the other upgrades should be straight forward and you only need to increment the package version in your package manager.

# Migration guide

If you are comming from an old version of the Roomle Configurator the following migration guide gives you an overview what changed and why.

# Recipes

Here you will find some recipes for how you could implement certain use cases. This is not a complete list and there are no limits for your own creativity but these recipes should give you an idea of how some things could be implemented.

# Instantiation

We provide now two convenient ways to load a product into the configurator. Either you instantiate the Roomle Configurator without a product and use loadObject later or you pass in the Roomle unique product ID as init-option. Both ways have their pros and cons. Let's review them quickly.

When you have a detail-page of a product in your webshop or website it makes sense to pass the Roomle unique product ID as init-option (because you know which product your customer wants to see). This makes it possible that everything is loaded in parallel, the code of the configurator, and the content of your product. This speeds up the loading process. On the other hand, it makes no sense if you load some product when you do not know which product your customer wants to see in the configurator. This could be the case if the Roomle Configurator is opened on a list-view page where several products are listed. Here it makes sense to only instantiate the configurator. This only loads the code of the configurator. When the user then selects a product you can use the loadObject method to load this specific product.

const objectToLoadId = 'usm:frame:BB3BB3E7951BC15109B1FF86D78C95DE3FB46E9F78714C46FFA2DE91866A2C2B';
const configurator1 = await RoomleConfiguratorApi.createConfigurator(configuratorId, domElement1, {id: objectToLoadId, ...overrideServerOptions});
// instantiate the second configurator, because you can now :-) and wait with the load of the object
// until the user clicks on a specific product
const configurator2 = await RoomleConfiguratorApi.createConfigurator(configuratorId, domElement2, overrideServerOptions);
// load the object into the second configurator when the user clicks on a specific button
document.getElementById('button-to-load-product-x').addEventListener('click', () => {
  configurator2.ui.loadObject(objectToLoadId);
});

The full example can be found in the file 00_init.html).

# Calculate a price based on the current part list

There are two options for the prices, either you use the prices you have in your webshops database or all the prices are calculated with the Roomle Price Service. This recipe shows a pseudo-code implementation of how you could calculate prices based on your webshops database. Therefore the variable priceDataBase is just a fake implementation of your own database. You can also fetch prices asynchronously with the fetch-api (opens new window).

To get all the parts of a configuration you need to register to the onPartListUpdate event. Then with all the parts, you can do your price calculation. To show the price within the configurator you need to call the method setPrice. ( the full example can be found in the file 04_price.html)

// fake implementation of a database
// only for showcase purpose
const priceDataBase = {};
configurator.ui.callbacks.onPartListUpdate = (partList) => {
  const parts = partList.fullList;
  let priceSum = parts.reduce((sum, part) => {
    if (!priceDataBase[part.articleNr]) {
      priceDataBase[part.articleNr] = Math.random() * 10;
    }
    return sum += priceDataBase[part.articleNr];
  }, 0);
  const shippingCosts = 30;
  // Tell the Roomle Configurator to show the current price
  configurator.ui.setPrice('€', priceSum + shippingCosts);
};

# Notes about onPartListUpdate

The onPartListUpdate callback always fires when the user of your webshop changes the configuration. It is also called on the initial load so that your implementation always has the most recent information about the current state of the configuration. The callback is executed with two parameters, parts and hash. The parts parameter is an array of parts. Based on this array you can do whatever calculation you need to do. For example calculate a price, shipping costs or packaging information. Sometimes those calculations can be complex and therefore we provide a hash. This has is not an ID it is only a representation of the current state of the configuration. If you see the same hash again you could short cut possible expensive calculations. Use the hash only for "performance optimization" during runtime. The hash is not saved to our database. A possible use-case for the hash could be the following (pseudo-code):

const lastHash = null;
configurator.ui.callbacks.onPartListUpdate = (partList, hash) => {
  const parts = partList.fullList;
  if (lastHash === hash) {
    return;
  }
  lastHash = hash;
  const priceSum = veryExpensivePriceCalculation(parts);
  const shippingCosts = 30;
  // Tell the Roomle Configurator to show the current price
  configurator.ui.setPrice('€', priceSum + shippingCosts);
};

# Typical challenges with the part list

If your webshop and ERP system can already handle all article numbers which could arise during a configuration then everything is fine and things like price calculations are simple arithmetic operations. But especially webshop systems are not always designed to handle highly individually customized configurations of a product. Let's consider the following example with two products (one shelf-frame and a combinded shelf-frame):

Example USM Frame

You see two different shelfs. A very simple approach for the webshop would be to setup two products in the webshop backend. For a configurable product this is not sufficient because you would need to create a product in the webshop backend for every possible configuration. This is why the Roomle Configurator returns a part list with the smallest combinable and customizable pieces. In the case of this shelf the part list consists on all the construction elements. Let's see why we can not simply add two whole shelf-frames to the part list.

For example: one shelf-frame consists of 8 circle-connectors and 4 feets. If you would simple duplicate it if there is a second shelf-frame attached you would have 16 circle-connectors and 8 feets. But the construction logic for this specific product is different. Because some parts can be shared between the two shelf-frames if they are docked together, only 12 circle-connectors and 6 feets are needed.

The following graphic should give you a better idea:

Parts

How parts are combined and added is very special for every product and therefore it is important to identify which things are the smallest possible parts. If those questions are addressed during content setup and also during the concept phase on how to connect the Roomle Configurator to existing systems (webshop, ERP etc) it will be easy to do the actual implementation later.

Furthermore it is also sometimes challenging for certain webshop systems to add configured products to the shopping cart. To think how to adjust your webshop system to accept also configured products in the shopping cart is another important part.

# Use the Roomle Price Service

If you do not have a webshop or a price database it could make sense to use the Roomle Price Service. The first step to use the Roomle Price Service is to enable the service. Therefore please speak with your customer success manager. If everything is in place you only need to provide the correct init options. Here is an example:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options, usePriceService: true},
);

For more details about the Roomle Price Service you can read the scripting price docu (opens new window)

Please also have a look on the localization section on how to load different price lists based on the location of your webshop.

If you see the error message prices are empty on the Chrome Dev Tools console it is possible that your account setup depends on a deprecated parameter option. For more details read the migration guide

# React on analytics events

Currently we pass out certain events to the embedding webshop those events are based on Goolge Tag Manager. You can find all the information about gtag.js (opens new window) on the Google website.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  "demoConfigurator",
  document.getElementById("configurator-container"),
  options
);
await configurator.ui.loadObject("usm:frame");
configurator.analytics.callbacks.onGATracking = function(type, event, data) {
  if (event.includes("Parameter:ChangeEvent")) {
    const {event_label} = data;
    const parts = event_label.split("#");
    console.log(`user changed parameter ${parts[2]} to ${parts[3]}`);
  }
};

You can try and play around with this example her: Edit intelligent-merkle-s589m (opens new window)

Overview of the most important events:

event description
RequestProduct User has clicked the request product button
Parameter:ChangeEvent User has changed a parameter, like material or size
ARButtonClicked User has clicked the AR Button
TypeChangeEvent User has switched to another variant
Dock User has docked/added another part to the existing configuration

# Skinning

If you want to adjust the colors of the configurator there are two options:

  • Primary color: this is the main color which indicates highlightings
  • "Call to action" color: this color indicates special areas in the UI, things like active state (e.g.: are measurements visible) or conversion "points" like save draft

The Roomle Configurator will determine which color to apply "on" primary and "call to action" color. For example: if a box has the primary color the "on primary color" will define the color of the text. The same applies for "call to action" color and the "on call to action color".

The Roomle Configurator will decide between "black" or "white" for the "on" colors. If this does not suit your needs you can define your own "on" colors. If you want to rely on the Roomle selection for the "on" colors please only specify valid CSS RGB hex values, e.g.: #fff000 or #f30. Things like rgba(0,0,0,0.7) won't work and you have to specify the "on" colors yourself (the full example can be found in the file 06_skinning.html):

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {
    ...options,
    skin: {
      'primary-color': '#1d68bd', // optional but please use a CSS RGB hex like #ff00ff if you want to rely on the color detection see explaination above
      'color-on-primary': '#f4e440', // optional, Roomle can decide this for you
      'cta-color': '#980d3f',  // please use a CSS RGB hex like #ff00ff if you want to rely on the color detection see explaination above
      'color-on-cta': '#8e8e8e', // optional, Roomle can decide this for you
    },
  },
);

# React on button clicks

You can use the onButtonClicked callback to react on button clicks within the configurator UI. The parameter of the callback is the name of the button which has been clicked.

You can use the UI_BUTTON enum to get all available button names:

export enum UI_BUTTON {
  AR = 'ar',
  PARTLIST = 'partlist',
  MULTISELECT = 'multiselect',
  DIMENSIONS = 'dimensions',
  FULLSCREEN = 'fullscreen',
  RESETCAMERA = 'resetcamera',
  RENDERIMAGE = 'renderimage',
  ADDONS = 'addons',
  REQUESTPRODUCT = 'requestproduct',
  REQUESTPLAN = 'requestplan',
  SAVEDRAFT = 'savedraft',
  STARTCONFIGURE = 'startconfigure',
  PAUSECONFIGURE = 'pauseconfigure',
  EXPORT_3D = 'export3d',
  ROTATE = 'rotate',
  UNDO = 'undo',
  REDO = 'redo'
}

Example:

configurator.ui.callbacks.onButtonClicked = (name) => {
  console.log('Button clicked: ' + name);
};

It is possible to override the default behaviour of the button by returning true. For example if we want to override only the default behaviour of the Save Draft button we could write something like this:

configurator.ui.callbacks.onButtonClicked = (name) => {
  if (name === 'savedraft') {
    alert('Custom save draft!');
    return true;
  }
  return false;
};

# Show/hide certain buttons

In some situations, there is a need to hide certain buttons. For example, you want to hide the part list button because the part list contains info you do not want to show to everybody. To do this you need to pass in the correct init options:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options, buttons: {partlist: false}},
);

The key of the buttons hash in the options are defined by the UI_BUTTON enum which was shown one section above.

# Listen to onRequestProduct

To be notified when the user clicks on the checkout-call-to-action button in our iframe you need to define the onRequestProduct callback.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  options,
);
configurator.ui.callbacks.onRequestProduct = (configurationId, image, partlist, price, labels, configuration) => {
  console.log(configurationId, image, partlist, price, labels, configuration);
};

Of course there are situations where you want to trigger this action from outside. Therefore we provide the method triggerRequestProduct. When you call this method the same process starts as if the user would have clicked on the checkout-call-to-action button. This means that you only need to subscribe to the onRequestProduct callback to handle those situations.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  options,
);
configurator.ui.callbacks.onRequestProduct = (configurationId, image, partlist, price, labels, configuration) => {
  console.log(configurationId, image, partlist, price, labels, configuration);
};
document.getElementById('trigger-request').addEventListener('click', async () => {
  await configurator.ui.triggerRequestProduct();
  console.log('trigger-request-done');
});

When the onRequestProduct callback fires it is assured that the configuration is saved to the Roomle backend. The data which you get from the callback are therefore the same as in the Roomle backend. This enables you to do ceratin things like: reloading the configuration later based on the configuration ID. This is especially helpful if the user adds a configuration to the shopping cart and continues later (maybe next day or after a restart of the browser etc.). Furthermore the onRequestProduct callback can be used to calculate a conversion rate (e.g.: how many users who configured actually add something to the cart). As you can see this callback is a very imporant one and we highly recommend not working around this callback.

# Add product variants

If you have different variants of your product and want to show them to your webshop users it makes sense to activate the variants feature. Variants could be products with different materials but we advise to use the variants feature for structural different product configurations, e.g.: the customer can start with a L-sofa but you have various different types of sofas or you start with a lowboard shelf but the same collection also has a highboard.

In other words, the variants feature should help the user of your webshop to explore the collection of a product. Of course the user could configer from on variation to the next variation but this is time consuming and reduces the chance that the user discovers all meaningful options.

To enable the variation feature some steps are necessary:

The first thing involves data-setup in Rubens Admin (opens new window):

  • you need to create different variants of your base product
  • you need to assign the variants to a tag

After that you can create a "variants map", which is basically a JSON key/value pair. The key is the ID of the root component and the value is the ID of the tag, an example could look like:

const options = {
  variants: {
    'usm:frame': 'tag_id_usm',
    'vitra:chair': 'tag_id_vitra'
  }
};

Now everytime a configuration which is based on the root component usm:frame the variants from the tag tag_id_usm are loaded and displayed in the configurator. If you change to something which is based on vitra:chair the variants from tag_id_vitra are loaded. If you load something which is not based on one of them no variants are loaded.

This behaviour is needed that the configurator only shows relevant variants which have something in common with the initial loaded configuration.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'), {
    ...options,
    variants: {
      'usm:frame': 'tag_id_usm',
      'vitra:chair': 'tag_id_vitra'
    }
  },
);

The full example can be found in the file 08_variants.html

When changing a variation the global parameters are applied to the newly loaded variaton. You can try this out in the CodeSandbox below. Just set the color of the shelf to yellow and switch the variant. You will see that the loaded variant is also yellow.

You can achieve the same behavior when you add the option {applyCurrentGlobalParameters: true} to extended.loadConfigurableItemById, extended.loadConfigurationById or extended.loadConfiguration

You can test and play around with those settings in this CodeSandbox: Edit add-variations-cvfyw (opens new window)

# Embed without JS interaction

In case you only want to include the Roomle 3D scene without any interaction to your webshop you can simply do that by setting the src of an iframe. Therefore you only need to add the query param api=false. This means the JavaScript API is not loaded and therefore the Roomle 3D scene does not wait for instructions of your webshop (e.g.: which object to load). A simple example would consist out ouf the query params: id (what object to load) and api=false:

<iframe
  src="https://www.roomle.com/t/cp/?id=usm:frame&api=false"
  width="560"
  height="315"
></iframe>

We want to mention that we suggest to use the JavaScript API because it gives you more control about many things. Especially about versioning and settings. Furthermore you only have very limited options for customizing the Roomle Configurator and also you loose a lot of interaction possibilities which could lead to better user engagement. If you want to do customizations like skinning etc. you have to use the JavaScript API. Nevertheless in some cases the embedding without JavaScript API makes perfect sense and has a valid use-case.

# Parameter implementation outside of the configurator iFrame

# Intro

The Roomle Embedding API gives you the possibility to get and set parameters inside the Roomle Configurator from outside. This means your webshop is able to change parameters of the configuration based on some logic you can define.

# Who should use this functionality?

This functionality is useful for every webshop which has special logics when and how to set a given parameter. For example you could preset parameters based on the geo-location of the user. This can be helpful for things like power plugs. For US users you can set the parameter to something different than for EU users.

Another use case would be to change the UX slightly. Maybe there is some very important parameter which should be visible all the time no matter what the user is doing inside the configuration. Then you can set up the controls for these parameters outside of the configurator directly within your webshop. Ideally, you set these parameters to "hidden" inside the Roomle Configurator.

# How can this be implemented with API

It is important that the parameters you want to change from outside are "well-known". This means that you know the name of the parameters. The name of the parameters can be anything and are defined during scripting. For example, the width parameter can be named something like "shelfWidth". If the parameter name is calculated dynamically that's not possible.

# Code

The following code snippet should give you an idea of how to implement the above-described behavior:

(async function() {
  const minWidth = '10rem';
  // be aware that you use the "extended" object, this needs to be included in your licence
  // if you are unsure please ask your Roomle Rubens Contact person
  const params = await configurator1.extended.getParametersOfRootComponent();
  const viewParam = params.find(({key}) => key === 'door');
  if (!viewParam) {
    console.warn('Configuration has no door param');
    return;
  }
  const parent = document.querySelector('.configurator-container').parentNode;
  if (!parent) {
    console.warn('No Roomle Configurator found');
    return;
  }
  const wrapper = document.createElement('div');
  viewParam.validValues.forEach(({value, label}) => {
    const button = document.createElement('div');
    const span = document.createElement('span');
    span.innerText = label;

    span.classList.add('btn', 'btn-primary');
    span.style.minWidth = minWidth;
    span.style.marginBottom = '1rem';
    button.appendChild(span);
    // be aware that you use the "extended" object, this needs to be included in your licence
    // if you are unsure please ask your Roomle Rubens Contact person
    button.addEventListener('click', () => configurator1.extended.setParameterOfRootComponent(viewParam, value));
    wrapper.appendChild(button);
  });

  parent.style.display = 'grid';
  parent.style.gridTemplateColumns = '1fr ' + minWidth;
  parent.style.gridGap = '10px';

  parent.appendChild(wrapper);

}());

It is also possible to change the value of parameters which are currently not visible if the parameter key is known, for example:

RoomleConfigurator.setParameter({ key: 'width' }, '400');

The full example can be found in the file 13_parameters.html.

# Load different products into the configurator

Since the Roomle Configurator is a single-page-app it is very easy to load different products into the configurator. If you are unsure what a single-page-app (SPA) is then it makes sense to read through the various explainations on the internet for example like this discussion (opens new window). If the concept of a SPA makes sense to you, let's have a look on the code:

<div class="product" data-roomle-id="__SOME_ROOMLE_ID_1__">
  <div>Product 1</div>
</div>
<div class="product" data-roomle-id="__SOME_ROOMLE_ID_2__">
  <div>Product 2</div>
</div>
<div class="product" data-roomle-id="__SOME_ROOMLE_ID_3__">
  <div>Product 3</div>
</div>
const buttons = document.querySelectorAll('.product');
[...buttons].forEach((button) => {
  button.addEventListener('click', (productDomNode) => {
    const target = productDomNode.target;
    let roomleId = target.getAttribute('data-roomle-id') || target.parentElement.getAttribute('data-roomle-id');
    if (!roomleId) {
      return;
    }
    configurator.ui.loadObject(roomleId);
  });
});

The full example can be found in the file 09_different_products.html.

# Localization

There are two different parameters to set the localization of the Roomle Configurator. First there is locale and then there is overrideCountry. If they are not provided both of them are inferred by the Roomle Configurator based on the browser settings and the location of the user of your webshop. Most of the time this is not 100% accurate. Let's have a look why on the example of locale. To set the language for the configurator we use locale. If a user visits your German webshop but has the browser settings switched to Spanish the configurator would be in Spanish but your webshop is in German. Since the Roomle Configurator does not know that it is embedded on the German webshop it's the best guess we can make. But if the webshop sets the locale we can use the same language as in the webshop. Therefore we highly recommend using this parameter. The same challenge arises for overrideCountry. The country parameter is used for the Roomle Price service. Based on the country we decide which price list to load. For example the price list with pounds for the UK webshop and the euro prices for the webshop based in EU. For both parameters we use two digits codes for locale we use the ISO_639-1 and for country ISO_3166-1_alpha-2, you can find the list for languages for example on ISO_639-1 (opens new window) the same is true for the country codes ISO_3166-1_alpha-2 (opens new window).

Let's have a look on the following code:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options, locale: 'de', overrideCountry: 'at', usePriceService: true},
);

With this snippet the webshop tells the Roomle Configurator that it should use the language de and the country at. Which basically means: "show the configurator in German and with the prices for the Austrian market". As explained above the overrideCountry is only needed if your webshop does not maintain the prices and you rely on the Roomle Price Service.

To check whether your language is supported, take a look at the currently supported configurator languages (opens new window).

# Opening an existing configuration again

As a webshop you often want to load a specific configuration again. There could be different use-cases e.g.: sharing a configuration with other people or giving the user the chance to continue a configuration. We recommand to create a landing page which can handle Roomle Configuration IDs (be aware of the difference between a hash and a Roomle Configuration ID). Probably one of the easiest ways is, to pass the ID as query param. Let's see some code:

const urlParams = new URLSearchParams(window.location.search);
const roomleId = urlParams.get('roomleId');
if (!roomleId) {
  console.warn('Please add the query param roomleId to the URL, as test value you can use: usm:frame:68AB2631D0E02A9FBBCA1371621CD23BB68818685738868D7DEB5B830BD5CD2D');
  return;
}
configurator.ui.loadObject(roomleId);

You can test and play around with those settings in this CodeSandbox: Edit intelligent-merkle-s589m (opens new window)

# Using different light settings

We try to provide a default light setting which works with the majority of products. Nevertheless there are certain products which require a special tweaking of those light settings. We provide a set of pre-defined light settings. Currently we support:

  • sofa: we created this light setting especially for sofas
  • shelf: the shelf light setting is optimized for shelfs
  • shelf_front: the shelf_front is optimized for shelfs with deep drawers so that there is not too much shadow inside them
  • equal: is a balanced light
  • camera: light comes always from the camera perspective
  • baked: light is set accordingly to the baked shadows

To use a special light setting you only need to provide the ls parameter in the options. In code this looks like:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options, ls: 'sofa'},
);

To switch through the different light settings you could try the following example: 11_light_settings.html.

# Send saved configuration to the e-mail of the user

To send a saved configuration to the e-mail address of the user you need to the set parameter emails to true. By default we only show the link to the configuration. Without any configuration the e-mail will contain a link back to Roomle. If you want to link to your webshop please make sure that you have a page which can open existing configuration. How this works is explained in the section Opening an existing configuration again. If you setup this page and everything is in place then you can set the deeplink parameter. The deeplink parameter should be a URL to a page in your webshop and containing the following placeholder: #CONFIGURATIONID#. This placeholder is then replaced by the real Configuration ID. The following code snippet shows the setup for the configurator.

Important: please also make sure that the deeplink is also set in the database of Roomle and linked to the configuratorId you are using. If this is not done, the link in the e-mail won't be replaced. If you encounter any problems, please contact your Roomle Contact Person.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {
    ...options,
    emails: true, deeplink: 'https://www.roomle.com/t/cp/?id=#CONFIGURATIONID#&configuratorId=demoConfigurator'
  },
);

The full example can be found here: 12_email.html.

# Grouping the part list by main component

The part list is an object which has the following TypeScript interface:

interface PartList {
  originPart: Part;
  perMainComponent: PartList[];
  fullList: Part[];
}

For things like calculating a price etc most of the time the fullList is needed but for UI purposes it often make sense to group parts together so that the user understands better what she/he configured. To display the part list grouped just add the following option to the init parameters: groupPartList: true, for example like:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {
    ...options,
    groupPartList: true
  },
);

All the callbacks like onPartListUpdate will work the same as before. If you need access to the grouped part list just use the partList.perMainComponent inside the callback.

# Re-docking components on deletion

While using the configurator, the user may delete a component that is in between other components, the default behavior for this is to remove all surrounding components so you don't have components attached to nothing. You can allow the configuration to be rearranged when a component with multiple children is deleted so the components fall back into a plausible configuration.

Re-docking Example

For example, if you removed element B, element C2 will fall into the place where element B was.

You can enable this functionality via feature flag by passing the following into your URL query parameters.

?featureFlags.reDock=true

# Change share url

Before you change the share url make sure you read through the sections Send saved configuration to the e-mail of the user and Opening an existing configuration again.

If you want to change the share url to a link to your webshop please make sure that you have a page which can open existing configuration. How this works is explained in the section Opening an existing configuration again. If you setup this page and everything is in place then you can set the deeplink parameter. The deeplink parameter should be a URL to a page in your webshop and containing the following placeholder: #CONFIGURATIONID#. This placeholder is then replaced by the real Configuration ID. The following code snippet shows the setup for the configurator.

Important: please also make sure that the deeplink is also set in the database of Roomle and linked to the configuratorId you are using. If this is not done, the link in the e-mail won't be replaced. If you encounter any problems, please contact your Roomle Contact Person.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {
    ...options,
    emails: true, deeplink: 'https://www.roomle.com/t/cp/?id=#CONFIGURATIONID#&configuratorId=demoConfigurator'
  },
);

The full example is similar to the 12_email.html example and can be found there: 12_email.html.

The Roomle Rubens uses Google Analytics to improve UX and performance based on the anonymized data we collect. We do not collect any private or personalized data. We do not use data collected with Google Analytics for personalized marketing or spam. We follow the best practices to protect the data of the user. Nevertheless there are doubts if using Google Analytics is allowed without the consent of the user. Currently you have two options:

  • ask the user for consent before loading the Roomle Rubens configurator. This is similar how YouTube and Twitter embedings work.
  • if you prefere more fine grained control you can do the following:

First you need to initialize Roomle Rubens without the Google Analytics consent. Therefore you need to do the following:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options, gaConsent: false}
);

Now nothing will be tracked to Google Analytics. As described this is not the ideal case because we use this data for UX and performance improvements. If you collect the user consent later you can just call the method giveGaConsent. This works as follows:

configurator.ui.giveGaConsent();

If you never call this method no data will be sent to Google Analytics. But keep in mind that you will lose insights into the usability of your configurator.

The full example can be found here: 14_ga_consent.html.

# Change text/labels in configurator UI

It's possible to change certain labels for certain languages in the UI of the configurator.

For example if you want to change what's written on the Request Product button you can use the translations options to change it:

const options = {
  locale: 'en',
  translations: {
    en: {
      params: {
        'request-product': 'Add to cart',
      },
    },
  },
};

To see which labels exist you can take a look at the API documentation.

You can test and play around with those settings in this CodeSandbox: Edit intelligent-merkle-s589m (opens new window)

# Implementing a custom share pop up

If you want to fully customize the share experience of your users you can do this by implementing your own pop-up. Therefore you need to apply the knowledge you already have from the section "React on button clicks". In that specific case you need to wait for the click on the button savedraft. Since you want to implement your own share logic you just need to return true to indicate that you want to disable the default behavior. Now the event "click savedraft" is your hook to execute your own logic. This can vary widely depending on what you want to do but a very basic idea and gist is implemented inside the following CodeSandbox:

Edit custom-share-functionality-gou3u (opens new window)

# Customize/change the UI

Learn more about UI elements and how to change them in the configurator tutorial about UI Customization.

# Image of current configuration/product

There are two ways to get an image/render of the current configuration shown in the Rubens configurator:

  • 3D / Perspective Image
  • 2D / Top Image

If you are using the SDK you can use preparePerspectiveImage or prepareTopImage directly on the RoomleConfigurator instance.

In case you are using the EmbeddingLib, you can use those calls on the extended object of the interface. For example:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options}
);
const base64Image = await configurator.extended.preparePerspectiveImage({
  showDimensions: true
});

# Exit configurator and back to website

If you want to know when the user pauses configuring and exits the configurator, you need to define the onBackToWebsite callback.

In case you are using the EmbeddingLib, you have to override the onBackToWebsite callback on the ui object. For example:

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  {...options}
);
configurator.ui.callbacks.onBackToWebsite = () => {
  console.log("Back to the website");
};

You can try and play around with this example her: Edit intelligent-merkle-s589m (opens new window)


NOTE: This callback is currently implemented on mobile devices only. You can see it in the user interface when you click the X button to close the configuration/MOC and then click on the Exist button.


# Exporting the current camera view as an image

There is the possiblity to export the current camera view as an image. Therefore just set the render image button to visible. This can be done via buttons.renderimage=true.

# Changing thumbnail view

Currently the thumbnails of materials, addons, options etc are shown in a "small" layout. This keeps the design nice but sometimes you need to show more details in the thumbnails. You can change this behavior by setting the option thumbnails. Currently the following values are accepted:

  • small: shows the thumbnails in a small way to save space and create a clean concise layout
  • big: shows the thumbnail in a big way so that the user can see more details in the thumbnail
  • list: shows the thumbnails as a list view, this is handy if you need lot's of text to describe what's going on in the thumbnail

In the background the thumbnails parameter is converted to the following data structure:

type ViewTypeState = 'list' | 'small' | 'big';
interface ThumbnailsSettings {
  collapsed: { desktop: ViewTypeState; mobile: ViewTypeState };
  expanded: { desktop: ViewTypeState; mobile: ViewTypeState };
}

This gives you very fine grained control about how to show the thumbnails. You have three different ways to define the behavior:

  • ?thumbnails=big this sets all the values to "big"
  • ?thumbnails.collapsed=big&thumbnails.expanded=list this sets the collapsed state to "big" and the expanded state to "list"
  • Or you can specify the full object like: ?thumbnails.collapsed.desktop=big&thumbnails.collapsed.mobile=small&thumbnails.expanded.desktop=list&thumbnails.expanded.mobile=big

# Listen to onSaveDraft

To be informed when an action to save the draft is initiated, whether by the user inside the iframe or triggered externally, you should utilize the onSaveDraft callback.

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  options,
);
configurator.ui.callbacks.onSaveDraft = (id, image, url, { type, payload }) => {
  console.log(id, image, url, type, payload);
};

There are times when you wish to initiate the save draft process externally for example is we use elements.bottom_bar=false to use custom bottomBar or use custom saveDraft button. For such cases, we provide the method triggerSaveDraft

const configurator = await RoomleConfiguratorApi.createConfigurator(
  'demoConfigurator',
  document.getElementById('configurator-container'),
  options,
);
configurator.ui.callbacks.onSaveDraft = (id, image, url, { type, payload }) => {
  console.log(id, image, url, type, payload);
};
document.getElementById('trigger-save').addEventListener('click', async () => {
  await configurator.ui.triggerSaveDraft('user@example.com');
  console.log('save-draft-triggered');
});

onSaveDraft parameters:

id: A unique string identifier for the configuration Id or the plan Id. image: The image of the current configuration. url: The generated url from save draft. data: An object containing: type: It specifies the kind of draft. It can be either plan if you trigger save from the planner or configuration of you trigger save from the configurator. payload: The actual saved data, which could be the configuration object or plan snapshot data.

When you use the triggerSaveDraft method, it saves the current configuration and then activates the onSaveDraft callback after triggerSaveDraft is finished. If you want to monitor and respond to actions from the custom save draft button, you can set up the onSaveDraft callback and execute your custom code inside it. The returned data from this callback aligns with the saved data in Roomle's backend, facilitating functionalities like reloading the configuration based on the draft ID later on. This comes in handy when users wish to return to a previous configuration, be it after a short break, the following day, or even post browser restarts.

Using onSaveDraft, there is also discern patterns or statistics such as how many users who started a configuration opted to save their drafts. This metric offers insights into user behaviors, shedding light on potential improvements or user tendencies. Given the importance of this callback, we strongly advise incorporating it seamlessly into your

You can try and play around with this example her: Edit intelligent-merkle-s589m (opens new window)

# Setting up custom tutorials in the configurator URL

# Step 1: Activating the Tutorials Overlay

Once you're inside the Room Designer, ensure that the URL parameter helpcenter setting is enabled. This setting enables the Tutorials Overlay feature.

It is possible to enable the helpcenter both for either configurator or room designer separately.

For configurator you need to set helpcenter.configurator=true. For room designer you have to set helpcenter.roomdesigner=true.

Per default, you will see the standard tutorials provided by roomle.

# Step 2: Sharing Custom Tutorials

If you want to show custom tutorials, follow these steps:

First of all you need to define, for what language you want to provide a tutorial. We support the ISO 639 (opens new window) languages. Hence the list of following country codes are supported by our configurator.

Construct the URL with the custom tutorials parameters:

https://roomle.com/t/cp/?<YOUR_PARAMETERS_HERE>

Replace <YOUR_PARAMETERS_HERE> with the custom tutorials parameters. The parameter customTutorials should contain an array of values with the following structure. An array is a table-like structure where each dataset corresponds to one line in the table.

In those lines you can define:

  1. The link which refers to the tutorial content
  2. The label which corresponds to the label displayed in the bubble of the tutorials overlay
  3. The desc which corresponds to the description of the video (optional as it is not visible in the configurator UI)
  4. scope defines if the content should be shown in room designer (assigning roomdesigner) or configurator (assigning configurator)
  5. platform defines if the content to be displayed is mobile (assigning mobile) or desktop content (assigning desktop)

The first three points of the list above are all language specific, meaning that they can be optionally provided for each country code of the ISO 639 countries whereas english is mandatory and always the fallback. To provide the language information, you have to add the ISO 639 (opens new window) language country code before the link, label and description (like en.link, en.label or en.description but you can find an example further underneath).

# Step 3: Formatting the URL

When constructing the URL, ensure it follows this format:

&customTutorials[0].en.link=<ENGLISH_VIDEO_URL>&customTutorials[0].en.label=<VIDEO_TITLE_ENGLISH>&customTutorials[0].en.desc=<VIDEO_DESCRIPTION_ENGLISH>&customTutorials[0].de.link=<GERMAN_VIDEO_URL>&customTutorials[0].de.label=<VIDEO_TITLE_GERMAN>&customTutorials[0].de.desc=<VIDEO_DESCRIPTION_GERMAN>&customTutorials[0].scope=room-designer&customTutorials[0].platform=desktop

Replace <ENGLISH_VIDEO_URL>, <VIDEO_TITLE_GERMAN>, <VIDEO_TITLE_ENGLISH>, <GERMAN_VIDEO_URL>, <VIDEO_DESCRIPTION_GERMAN>, and <VIDEO_DESCRIPTION_ENGLISH> with your video's URL, titles, and descriptions in both German, English and optionally any other language you want to provide content in.

If you want to add further content, just add new entries increasing the value inside the brackets.

&customTutorials[1].en.link=<ENGLISH_VIDEO_URL>&customTutorials[1].en.label=<VIDEO_TITLE_ENGLISH>&customTutorials[1].en.desc=<VIDEO_DESCRIPTION_ENGLISH>&customTutorials[1].de.link=<GERMAN_VIDEO_URL>&customTutorials[1].de.label=<VIDEO_TITLE_GERMAN>&customTutorials[1].de.desc=<VIDEO_DESCRIPTION_GERMAN>&customTutorials[1].scope=room-designer&customTutorials[1].platform=desktop

# Step 4: Viewing Custom Tutorials

Once the URL with the custom tutorials parameters is constructed in the URL, the Room Designer will display bubbles in the tutorials overlay for each uploaded tutorial. Clicking on a bubble will display the corresponding tutorial video within the Room Designer interface.

# Step 5: Fallback Option

If you have activated the tutorials overlay feature but haven't uploaded any custom tutorials, the Room Designer will display existing tutorial content as a fallback.

# Setting up custom tutorials in Rubens Admin

You can also set the individual tutorial content for all the configurators at once without having to construct each configurator URL. This also brings the advantage that your configurator links are smaller than if you construct the custom tutorial content in the URL.

To do so, navigate to the tenant settings in Rubens Admin. In the Rubens settings JSON you can define global settings for all configurators of a configurationId. So if you have the same tutorial content for your configurator, it is the best option to setup the content for your tutorials there directly.

The procedure is similar to if you set it up in the URL. You have the same parameters available - the only difference is, that you have to bring the format into JSON format. PS: If you are not so familiar with that format, you can ask ChatGPT to convert the URL parameter structure to JSON format for. That works quite well.

{
  "customTutorials": [
    {
      "en": {
        "link": "<ENGLISH_VIDEO_URL_1>",
        "label": "<VIDEO_TITLE_ENGLISH_1>",
        "desc": "<VIDEO_DESCRIPTION_ENGLISH_1>"
      },
      "de": {
        "link": "<GERMAN_VIDEO_URL_1>",
        "label": "<VIDEO_TITLE_GERMAN_1>",
        "desc": "<VIDEO_DESCRIPTION_GERMAN_1>"
      },
      "scope": "room-designer",
      "platform": "desktop"
    },
    {
      "en": {
        "link": "<ENGLISH_VIDEO_URL_2>",
        "label": "<VIDEO_TITLE_ENGLISH_2>",
        "desc": "<VIDEO_DESCRIPTION_ENGLISH_2>"
      },
      "de": {
        "link": "<GERMAN_VIDEO_URL_2>",
        "label": "<VIDEO_TITLE_GERMAN_2>",
        "desc": "<VIDEO_DESCRIPTION_GERMAN_2>"
      },
      "scope": "room-designer",
      "platform": "desktop"
    }
  ]
}

Replace <GERMAN_VIDEO_URL>, <ENGLISH_VIDEO_URL>, <VIDEO_TITLE_GERMAN>, <VIDEO_TITLE_ENGLISH>, <VIDEO_DESCRIPTION_GERMAN>, and <VIDEO_DESCRIPTION_ENGLISH> with your actual video URLs, titles, and descriptions in both German, English and any other optional language you want to provide content in.

Once you are done editing the Rubens settings you can save your editing. All the configurators of the configuratorId should then have your individual content displayed in the rubens tutorials overlay without having to set them explicitly in the URL provided you enabled the rubens tutorials for your configurators.

You can try and play around with this example here: (opens new window (opens new window))

You can try and play around with this example here: Edit intelligent-merkle-s589m (opens new window)

That's it! You're now ready to customize your Rubens configurator/Room Designer experience with your own tutorials. If you encounter any issues or have questions, feel free to reach out to our service desk for assistance.