@roomle/embedding-lib 4.37.0 → 4.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/docs/__sidebar__.json +14 -0
  2. package/docs/api/classes/exposed_analytics_callbacks.ExposedAnalyticsCallbacks.md +1 -1
  3. package/docs/api/classes/exposed_api.ExposedApi.md +11 -11
  4. package/docs/api/classes/exposed_callbacks.ExposedCallbacks.md +31 -7
  5. package/docs/api/classes/roomle_configurator_api.default.md +9 -9
  6. package/docs/api/enums/types.UI_BUTTON.md +33 -22
  7. package/docs/api/interfaces/exposed_callbacks.Labels.md +2 -2
  8. package/docs/api/interfaces/exposed_callbacks.Price.md +2 -2
  9. package/docs/api/interfaces/roomle_configurator_api.RoomleEmbeddingApiKeys.md +4 -4
  10. package/docs/api/interfaces/types.ConfiguratorSettings.md +5 -5
  11. package/docs/api/interfaces/types.EmbeddingSkin.md +5 -5
  12. package/docs/api/interfaces/types.UiInitData.md +31 -16
  13. package/docs/api/modules/roomle_configurator_api.md +3 -3
  14. package/docs/examples/11_light_settings.html +90 -62
  15. package/docs/examples/roomle-configurator-api.es.min.js +254 -58
  16. package/docs/hsp.md +61 -0
  17. package/docs/integration.md +1101 -0
  18. package/docs/md/web/ui/EMBEDDING-CHANGELOG.md +14 -2
  19. package/docs/migration-guides/v2-to-v3.md +2 -2
  20. package/docs/moc/index.md +86 -0
  21. package/docs/simple.md +1 -1
  22. package/package.json +2 -2
  23. package/types/index.d.ts +53 -28
  24. package/types/src/common/business-logic/connector.d.ts +8 -0
  25. package/types/src/common/store/collection-view-state.d.ts +8 -3
  26. package/types/src/common/store/common-ui-state.d.ts +1 -1
  27. package/types/src/common/store/index.d.ts +0 -1
  28. package/types/src/common/utils/touch-drag.d.ts +6 -1
  29. package/types/src/configurator/business-logic/roomle-sdk-wrapper.d.ts +2 -0
  30. package/types/src/configurator/business-logic/sdk-connector-configurator.d.ts +3 -5
  31. package/types/src/configurator/business-logic/sdk-connector.d.ts +4 -7
  32. package/types/src/configurator/embedding/exposed-api.d.ts +1 -1
  33. package/types/src/configurator/embedding/exposed-callbacks.d.ts +7 -1
  34. package/types/src/configurator/embedding/types.d.ts +8 -1
  35. package/types/src/configurator/store/ui-state.d.ts +1 -0
  36. package/types/src/planner/business-logic/sdk-connector-planner.d.ts +3 -3
  37. package/types/src/planner/store/planner-ui-state.d.ts +3 -2
  38. package/types/src/planner/utils/planner-sidebar.d.ts +0 -1
  39. package/types/src/viewer/business-logic/sdk-connector-viewer.d.ts +2 -2
  40. package/types/tests/helpers/data/part-list.d.ts +16 -0
  41. package/types/tests/helpers/mocks/sdk-connector.d.ts +1 -0
  42. package/types/tests/integration/{configurator → common}/components/BottomBar.spec.d.ts +0 -0
  43. package/types/tests/integration/planner/components/{BottomBar.spec.d.ts → PlannerSidebar.spec.d.ts} +0 -0
  44. package/docs/index.md +0 -846
@@ -0,0 +1,1101 @@
1
+ # Embedding Integration
2
+
3
+ The embedding is used when you want to integrate the configurator "as it is" into your website or webshop. If you have
4
+ massive adjustment wishes also consider using our [SDK](https://www.roomle.com/t/docs/)
5
+
6
+ ## Questions
7
+
8
+ If you have troubles with your integration, feel free to ask us
9
+ on [Stackoverflow](https://stackoverflow.com/questions/ask?tags=roomle).
10
+ Just make sure you are using the tag `roomle` so we get a notification.
11
+
12
+ ## The 10.000 feet view
13
+
14
+ At first, we want to have a quick look at the concepts and ideas behind the embedded Roomle Configurator.
15
+
16
+ On the one hand, there is your webshop and on the other hand, there is the Roomle Configurator. The configurator runs
17
+ client-side which means there is no need for any server-side language like PHP etc. Everything just happens in plain old
18
+ JavaScript.
19
+
20
+ To ease the communication between your website and the Roomle Configurator we provide a small JavaScript library that
21
+ can be used to call methods of the configurator or to subscribe to events.
22
+
23
+ Those methods and events give you the ability to flexibly integrate the configurator as you need it.
24
+
25
+ This tutorial will guide you through some uses cases and should give you the knowledge to implement whatever you need.
26
+
27
+ ## Getting started
28
+
29
+ As stated above the communication happens with the help of a JavaScript library. Therefore you need to integrate the
30
+ library into your project. This can either happen via a package manager (e.g. npm, yarn...) or a simple HTML script tag.
31
+
32
+ All the needed files can be found
33
+ here: [https://www.npmjs.com/package/@roomle/embedding-lib](https://www.npmjs.com/package/@roomle/embedding-lib)
34
+
35
+ You can either download the `tar.gz` or if you use `npm` you could do the following:
36
+
37
+ ```bash
38
+ npm install @roomle/embedding-lib --save
39
+ ```
40
+
41
+ Always specify the correct `configuratorId` in the init options (more details on that later). The `configuratorId` is
42
+ handed over to you by your Roomle Contact Person. The correct `configuratorId` prevents attackers from simply copying
43
+ your code and use the configurator on their website. The only thing you have to do is, to specify the domains on which
44
+ the Roomle Configurator should be available. For example: www.roomle.com. You can do this either in Rubens Admin or by
45
+ telling your Roomle Contact Person. If something is wrongly configured you will see a message on the screen and further
46
+ information in the JavaScript error console. If you can not figure out what is wrong please contact your Roomle Contact
47
+ Person.
48
+
49
+ ### Copy & Paste without package manager
50
+
51
+ This section shows a simple quick start. We always recommend using a package manager and a good built step but to
52
+ quickly illustrate the idea of the Roomle Configurator these examples should be sufficient.
53
+
54
+ ```html
55
+
56
+ <html>
57
+ <head>
58
+ <style>
59
+ body {
60
+ margin: 0;
61
+ padding: 0;
62
+ overflow: hidden;
63
+ }
64
+
65
+ #configurator-container {
66
+ width: 1200px;
67
+ height: 675px;
68
+ }
69
+ </style>
70
+ </head>
71
+
72
+ <body>
73
+ <div id="configurator-container"></div>
74
+ <script src="./roomle-configurator-api.es.min.js" type="module"></script>
75
+ <script type="module">
76
+ import RoomleConfiguratorApi from './roomle-configurator-api.es.min.js';
77
+
78
+ (async () => {
79
+ const options = {
80
+ id: 'usm:frame',
81
+ };
82
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
83
+ 'demoConfigurator',
84
+ document.getElementById('configurator-container'),
85
+ options,
86
+ );
87
+ })();
88
+ </script>
89
+ </body>
90
+ </html>
91
+ ```
92
+
93
+ To try it out just copy&paste the HTML snippet from above into a file called `index.html` and serve it from a web
94
+ server. For quick experiments, the npm package [http-server](https://www.npmjs.com/package/http-server) is very nice.
95
+
96
+ ### Some notes on the example
97
+
98
+ #### Browsers
99
+
100
+ As you can see the Roomle Configurator uses ES6 modules. We highly recommend creating a fallback for older browsers.
101
+ Since the Roomle Configurator is a 3D tool written in web technologies a good user experience is only possible with
102
+ modern browsers. For users with old browsers (e.g. Internet Explorer) we recommend creating a different sales funnel and
103
+ therefore a different user journey. Since the alternatives differ widely based on the website and webshop Roomle can not
104
+ provide a default fallback.
105
+
106
+ #### Browser policy
107
+
108
+ We try our best to support as many browsers as possible. All supported browsers are listed in
109
+ the [System Requirements](../guides/sdk/system-requirements.html#supported-internet-browsers).
110
+
111
+ #### Iframe
112
+
113
+ Yes, the Roomle Configurator is integrated as an iframe. This is per se nothing bad. Iframes have a bad reputation
114
+ because they are also used for displaying ads etc. But the Roomle Configurator is not an annoying banner it is an
115
+ essential part of the UX for a potential customer. Big platforms like Youtube, Vimeo, Paypal are also integrating their
116
+ widgets as iframe. Of course, there are technologies like Web Components but the iframe approach has several advantages
117
+ in the use case of Roomle Configurator, e.g.: each WebAssembly instance is in its own process, it's easier to react on
118
+ resize events, etc.
119
+
120
+ "Iframes are bad for SEO they told me". Yes, that's true but only if you put essential data into the iframes. The Roomle
121
+ Configurator only has a 3D scene that has basically no information for search engines. Therefore it makes sense to
122
+ optimize your landing page on the website and treat the iframe just like you would treat an image.
123
+
124
+ Since the embedding webshop has no control over the iframe it also can not track what happens inside the iframe.
125
+ Therefore we send important events to the webshop so it is possible to track events. More about that in
126
+ the [Recipes section](#Recipes).
127
+
128
+ ### TypeScript
129
+
130
+ We provide typings for the embedding lib. At the time of writing it is necessary to use `"skipLibCheck": true` because
131
+ we rely on things like WebAssembly and WebGL. Those typings are not available in all projects and
132
+ therefore `skipLibCheck` is needed. We work on providing better shims so that this reliance is not necessary.
133
+
134
+ #### The code
135
+
136
+ In the example, we set the width to 1200px and the height to 675px. This is on purpose to explain one concept we
137
+ encountered during our extensive research on UX and customer behavior. When you try to configure something it makes
138
+ sense to use as much space as possible on the screen of the user. Otherwise configuring becomes a pain. At the time of
139
+ writing Roomle has a limit for 1024px width screens (be aware that this is just a random value and could change at any
140
+ point in the future). This means if the container in which the configurator is placed smaller than this width the
141
+ configurator is called in a "view only" mode. When the configurator is in "view only" mode a button in the right bottom
142
+ is displayed. If the user clicks on this button the configuration starts and the configurator opens in "full page" mode.
143
+ If you do not want to rely on this button you can implement your own logic. Either you keep the button visible or you
144
+ can also hide it. You only need to pass the following to the init options: `{buttons: {startconfigure: false}}`. In the
145
+ following paragraphs we outline a custom behaviour of the "start" button.
146
+
147
+ When you tell the configurator to start it will open in a "full page" mode. You can try this out by adding the following
148
+ code to the example from above (the full example can be found in the
149
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/01_small_screen.html">01_small_screen.html</a>):
150
+
151
+ ```html
152
+
153
+ <style>
154
+ #configurator-container {
155
+ width: 800px;
156
+ height: 600px;
157
+ }
158
+ </style>
159
+ <button>Start configure</button>
160
+ <script>
161
+ const button = document.querySelector('button');
162
+ button.addEventListener('click', () => configurator.ui.startConfiguring());
163
+ </script>
164
+ ```
165
+
166
+ When you did everything correct you should see a button at the beginning of the page. We add an event listener to the
167
+ button which calls `startConfiguring` on a click event.
168
+
169
+ This should open the configurator in "full page" mode and all the controls for configuring should be visible.
170
+
171
+ So it's up to the embedding website how to embed the configurator. If you want to start in "view only" just add the
172
+ configurator to a small container. If you want to start directly in "configure mode" add it to a bigger container.
173
+
174
+ On mobile devices most screens will be too small to achieve the required width therefore the configurator is always
175
+ opened in "full page" mode (otherwise configuring would be a pain for the user).
176
+
177
+ To only show the "start now button" you can listen to the `onResize` event. How this works will be illustrated in the
178
+ following example (the full example can be found in the
179
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/02_resize_callback.html">
180
+ 02_resize_callback.html</a>):
181
+
182
+ ```html
183
+
184
+ <style>
185
+ #configurator-container {
186
+ width: 800px;
187
+ height: 600px;
188
+ }
189
+
190
+ button {
191
+ display: none;
192
+ }
193
+ </style>
194
+ <button>Start configure</button>
195
+ <script>
196
+ const button = document.querySelector('button');
197
+ configurator.ui.callbacks.onResize = (isDesktop) => {
198
+ if (!isDesktop) {
199
+ button.style.display = 'block';
200
+ } else {
201
+ button.style.display = 'none';
202
+ }
203
+ };
204
+ button.addEventListener('click', () => configurator.ui.startConfiguring());
205
+ </script>
206
+ ```
207
+
208
+ Now you should see the "start" button only if needed. Because we hardcoded the container width to 800px you will always
209
+ see it. If you play around with this value you will see how the display changes.
210
+
211
+ If you just want to start the configurator and do not want to bother about the screen sizes you could change the example
212
+ to the following (the full example can be found in the
213
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/03_instant_start.html">03_instant_start.html</a>):
214
+
215
+ ```html
216
+
217
+ <style>
218
+ #configurator-container {
219
+ width: 800px;
220
+ height: 600px;
221
+ }
222
+
223
+ button {
224
+ display: block;
225
+ }
226
+ </style>
227
+ <button>Start configure</button>
228
+ <script>
229
+ const button = document.querySelector('button');
230
+ const options = {
231
+ id: 'usm:frame',
232
+ };
233
+ button.addEventListener('click', async () => {
234
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
235
+ 'demoConfigurator',
236
+ document.getElementById('configurator-container'),
237
+ options,
238
+ );
239
+ configurator.ui.startConfiguring();
240
+ });
241
+ </script>
242
+ ```
243
+
244
+ Now, as soon as you click on the button the configurator is started and immediately and opened in the needed size.
245
+
246
+ All the currently available methods are available behind the `ui` object. So if you want to know what's possible you can
247
+ either look on the TSDocs or just inspect the `ui` object.
248
+
249
+ You will recognize that there is also an entry for `callbacks`. This is basically the event system of the configurator.
250
+ In the example, above we already outlined how to use the `onResize` event and the same applies to all the other events
251
+ available on the `ui.callbacks` objects. All the details can be found in the TSDocs as well.
252
+
253
+ Now you should know the basics and the idea of how to use and integrate the Roomle Configurator. Now there is the time
254
+ to play around and create great user experiences.
255
+
256
+ The next section will illustrate some "recipes"
257
+
258
+ ## Upgrades
259
+
260
+ We follow the [sematic versioning](https://semver.org/) approach and to make sure no breaking change slips through we
261
+ follow the [Conventional Commits](https://www.conventionalcommits.org/) convention. Therefore always make sure to read
262
+ through our [changelog](https://docs.roomle.com/web/ui/CHANGELOG.html) if you upgrade to a new major version of the
263
+ embedding lib. All the other upgrades should be straight forward and you only need to increment the package version in
264
+ your package manager.
265
+
266
+ ## Migration guide
267
+
268
+ If you are comming from an old version of the Roomle Configurator
269
+ the [following migration](migration-guides/v2-to-v3.md) guide gives you an overview what changed and why.
270
+
271
+ ## Recipes
272
+
273
+ Here you will find some recipes for how you could implement certain use cases. This is not a complete list and there are
274
+ no limits for your own creativity but these recipes should give you an idea of how some things could be implemented.
275
+
276
+ ### Instantiation
277
+
278
+ We provide now two convenient ways to load a product into the configurator. Either you instantiate the Roomle
279
+ Configurator without a product and use `loadObject` later or you pass in the `Roomle unique product ID` as init-option.
280
+ Both ways have their pros and cons. Let's review them quickly.
281
+
282
+ When you have a detail-page of a product in your webshop or website it makes sense to pass
283
+ the `Roomle unique product ID` as init-option (because you know which product your customer wants to see). This makes it
284
+ possible that everything is loaded in parallel, the code of the configurator, and the content of your product. This
285
+ speeds up the loading process. On the other hand, it makes no sense if you load some product when you do not know which
286
+ product your customer wants to see in the configurator. This could be the case if the Roomle Configurator is opened on a
287
+ list-view page where several products are listed. Here it makes sense to only instantiate the configurator. This only
288
+ loads the code of the configurator. When the user then selects a product you can use the `loadObject` method to load
289
+ this specific product.
290
+
291
+ ```JavaScript
292
+ const objectToLoadId = 'usm:frame:BB3BB3E7951BC15109B1FF86D78C95DE3FB46E9F78714C46FFA2DE91866A2C2B';
293
+ const configurator1 = await RoomleConfiguratorApi.createConfigurator(configuratorId, domElement1, {id: objectToLoadId, ...overrideServerOptions});
294
+ // instantiate the second configurator, because you can now :-) and wait with the load of the object
295
+ // until the user clicks on a specific product
296
+ const configurator2 = await RoomleConfiguratorApi.createConfigurator(configuratorId, domElement2, overrideServerOptions);
297
+ // load the object into the second configurator when the user clicks on a specific button
298
+ document.getElementById('button-to-load-product-x').addEventListener('click', () => {
299
+ configurator2.ui.loadObject(objectToLoadId);
300
+ });
301
+ ```
302
+
303
+ The full example can be found in the file <a target="_blank" rel="noopener noreferrer" href="./examples/00_init.html">
304
+ 00_init.html</a>).
305
+
306
+ ### Calculate a price based on the current part list
307
+
308
+ There are two options for the prices, either you use the prices you have in your webshops database or all the prices are
309
+ calculated with the Roomle Price Service. This recipe shows a pseudo-code implementation of how you could calculate
310
+ prices based on your webshops database. Therefore the variable `priceDataBase` is just a fake implementation of your own
311
+ database. You can also fetch prices asynchronously with
312
+ the [fetch-api](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
313
+
314
+ To get all the parts of a configuration you need to register to the `onPartListUpdate` event. Then with all the parts,
315
+ you can do your price calculation. To show the price within the configurator you need to call the method `setPrice`. (
316
+ the full example can be found in the file <a target="_blank" rel="noopener noreferrer" href="./examples/04_price.html">
317
+ 04_price.html</a>)
318
+
319
+ ```JavaScript
320
+ // fake implementation of a database
321
+ // only for showcase purpose
322
+ const priceDataBase = {};
323
+ configurator.ui.callbacks.onPartListUpdate = (partList) => {
324
+ const parts = partList.fullList;
325
+ let priceSum = parts.reduce((sum, part) => {
326
+ if (!priceDataBase[part.articleNr]) {
327
+ priceDataBase[part.articleNr] = Math.random() * 10;
328
+ }
329
+ return sum += priceDataBase[part.articleNr];
330
+ }, 0);
331
+ const shippingCosts = 30;
332
+ // Tell the Roomle Configurator to show the current price
333
+ configurator.ui.setPrice('€', priceSum + shippingCosts);
334
+ };
335
+ ```
336
+
337
+ #### Notes about onPartListUpdate
338
+
339
+ The `onPartListUpdate` callback always fires when the user of your webshop changes the configuration. It is also called
340
+ on the initial load so that your implementation always has the most recent information about the current state of the
341
+ configuration. The callback is executed with two parameters, parts and hash. The parts parameter is an array of parts.
342
+ Based on this array you can do whatever calculation you need to do. For example calculate a price, shipping costs or
343
+ packaging information. Sometimes those calculations can be complex and therefore we provide a hash. This has is not an
344
+ ID it is only a representation of the current state of the configuration. If you see the same hash again you could short
345
+ cut possible expensive calculations. Use the hash only for "performance optimization" during runtime. The hash is not
346
+ saved to our database. A possible use-case for the hash could be the following (pseudo-code):
347
+
348
+ ```JavaScript
349
+ const lastHash = null;
350
+ configurator.ui.callbacks.onPartListUpdate = (partList, hash) => {
351
+ const parts = partList.fullList;
352
+ if (lastHash === hash) {
353
+ return;
354
+ }
355
+ lastHash = hash;
356
+ const priceSum = veryExpensivePriceCalculation(parts);
357
+ const shippingCosts = 30;
358
+ // Tell the Roomle Configurator to show the current price
359
+ configurator.ui.setPrice('€', priceSum + shippingCosts);
360
+ };
361
+ ```
362
+
363
+ ### Typical challenges with the part list
364
+
365
+ If your webshop and ERP system can already handle all article numbers which could arise during a configuration then
366
+ everything is fine and things like price calculations are simple arithmetic operations. But especially webshop systems
367
+ are not always designed to handle highly individually customized configurations of a product. Let's consider the
368
+ following example with two products (one shelf-frame and a combinded shelf-frame):
369
+
370
+ ![Example USM Frame](images/usm-frames.png "Example USM Frame")
371
+
372
+ You see two different shelfs. A very simple approach for the webshop would be to setup two products in the webshop
373
+ backend. For a configurable product this is not sufficient because you would need to create a product in the webshop
374
+ backend for every possible configuration. This is why the Roomle Configurator returns a part list with the smallest
375
+ combinable and customizable pieces. In the case of this shelf the part list consists on all the construction elements.
376
+ Let's see why we can not simply add two whole shelf-frames to the part list.
377
+
378
+ For example: one shelf-frame consists of 8 circle-connectors and 4 feets. If you would simple duplicate it if there is a
379
+ second shelf-frame attached you would have 16 circle-connectors and 8 feets. But the construction logic for this
380
+ specific product is different. Because some parts can be shared between the two shelf-frames if they are docked
381
+ together, only 12 circle-connectors and 6 feets are needed.
382
+
383
+ The following graphic should give you a better idea:
384
+
385
+ ![Parts](images/parts.png "Parts")
386
+
387
+ How parts are combined and added is very special for every product and therefore it is important to identify which
388
+ things are the smallest possible parts. If those questions are addressed during content setup and also during the
389
+ concept phase on how to connect the Roomle Configurator to existing systems (webshop, ERP etc) it will be easy to do the
390
+ actual implementation later.
391
+
392
+ Furthermore it is also sometimes challenging for certain webshop systems to add configured products to the shopping
393
+ cart. To think how to adjust your webshop system to accept also configured products in the shopping cart is another
394
+ important part.
395
+
396
+ ### Use the Roomle Price Service
397
+
398
+ 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
399
+ use the Roomle Price Service is to enable the service. Therefore please speak with your customer success manager. If
400
+ everything is in place you only need to provide the correct init options. Here is an example:
401
+
402
+ ```JavaScript
403
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
404
+ 'demoConfigurator',
405
+ document.getElementById('configurator-container'),
406
+ {...options, usePriceService: true},
407
+ );
408
+ ```
409
+
410
+ For more details about the Roomle Price Service you can read
411
+ the [scripting price docu](https://docs.roomle.com/scripting/resources/200_130_pricing.html#price-service)
412
+
413
+ Please also have a look on the [localization](#localization) section on how to load different price lists based on the
414
+ location of your webshop.
415
+
416
+ If you see the error message `prices are empty` on the Chrome Dev Tools console it is possible that your account setup
417
+ depends on a deprecated parameter option. For more details read
418
+ the [migration guide](migration-guides/v2-to-v3.md#price-service)
419
+
420
+ ### React on analytics events
421
+
422
+ Currently we pass out certain events to the embedding webshop those events are based on Goolge Tag Manager. You can find
423
+ all the information about [gtag.js](https://developers.google.com/analytics/devguides/collection/gtagjs) on the Google
424
+ website.
425
+
426
+ ```JavaScript
427
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
428
+ "demoConfigurator",
429
+ document.getElementById("configurator-container"),
430
+ options
431
+ );
432
+ await configurator.ui.loadObject("usm:frame");
433
+ configurator.analytics.callbacks.onGATracking = function(type, event, data) {
434
+ if (event.includes("Parameter:ChangeEvent")) {
435
+ const {event_label} = data;
436
+ const parts = event_label.split("#");
437
+ console.log(`user changed parameter ${parts[2]} to ${parts[3]}`);
438
+ }
439
+ };
440
+ ```
441
+
442
+ You can try and play around with this example
443
+ her: [![Edit intelligent-merkle-s589m](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/evaluate-analytics-events-cvfyw?fontsize=14&hidenavigation=1&theme=dark)
444
+
445
+ Overview of the most important events:
446
+
447
+ | event | description |
448
+ |-----------------------|------------------------------------------------------------------|
449
+ | RequestProduct | User has clicked the request product button |
450
+ | Parameter:ChangeEvent | User has changed a parameter, like material or size |
451
+ | ARButtonClicked | User has clicked the AR Button |
452
+ | TypeChangeEvent | User has switched to another variant |
453
+ | Dock | User has docked/added another part to the existing configuration |
454
+
455
+ ### Skinning
456
+
457
+ If you want to adjust the colors of the configurator there are two options:
458
+
459
+ * Primary color: this is the main color which indicates highlightings
460
+ * "Call to action" color: this color indicates special areas in the UI, things like active state (e.g.: are measurements
461
+ visible) or conversion "points" like save draft
462
+
463
+ The Roomle Configurator will determine which color to apply "on" primary and "call to action" color. For example: if a
464
+ box has the primary color the "on primary color" will define the color of the text. The same applies for "call to
465
+ action" color and the "on call to action color".
466
+
467
+ The Roomle Configurator will decide between "black" or "white" for the "on" colors. If this does not suit your needs you
468
+ can define your own "on" colors. If you want to rely on the Roomle selection for the "on" colors please only specify
469
+ 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"
470
+ colors yourself (the full example can be found in the
471
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/06_skinning.html">06_skinning.html</a>):
472
+
473
+ ```JavaScript
474
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
475
+ 'demoConfigurator',
476
+ document.getElementById('configurator-container'),
477
+ {
478
+ ...options,
479
+ skin: {
480
+ '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
481
+ 'color-on-primary': '#f4e440', // optional, Roomle can decide this for you
482
+ 'cta-color': '#980d3f', // please use a CSS RGB hex like #ff00ff if you want to rely on the color detection see explaination above
483
+ 'color-on-cta': '#8e8e8e', // optional, Roomle can decide this for you
484
+ },
485
+ },
486
+ );
487
+ ```
488
+
489
+ ### React on button clicks
490
+
491
+ You can use the onButtonClicked callback to react on button clicks within the configurator UI.
492
+ The parameter of the callback is the name of the button which has been clicked.
493
+
494
+ You can use the UI_BUTTON enum to get all available button names:
495
+
496
+ ```Typescript
497
+ export enum UI_BUTTON {
498
+ AR = 'ar',
499
+ PARTLIST = 'partlist',
500
+ MULTISELECT = 'multiselect',
501
+ DIMENSIONS = 'dimensions',
502
+ FULLSCREEN = 'fullscreen',
503
+ RESETCAMERA = 'resetcamera',
504
+ RENDERIMAGE = 'renderimage',
505
+ ADDONS = 'addons',
506
+ REQUESTPRODUCT = 'requestproduct',
507
+ REQUESTPLAN = 'requestplan',
508
+ SAVEDRAFT = 'savedraft',
509
+ STARTCONFIGURE = 'startconfigure',
510
+ PAUSECONFIGURE = 'pauseconfigure',
511
+ EXPORT_3D = 'export3d',
512
+ ROTATE = 'rotate',
513
+ UNDO = 'undo',
514
+ REDO = 'redo'
515
+ }
516
+ ```
517
+
518
+ Example:
519
+
520
+ ```JavaScript
521
+ configurator.ui.callbacks.onButtonClicked = (name) => {
522
+ console.log('Button clicked: ' + name);
523
+ };
524
+ ```
525
+
526
+ It is possible to override the default behaviour of the button by returning `true`.
527
+ For example if we want to override only the default behaviour of the `Save Draft` button we could write something like
528
+ this:
529
+
530
+ ```JavaScript
531
+ configurator.ui.callbacks.onButtonClicked = (name) => {
532
+ if (name === 'savedraft') {
533
+ alert('Custom save draft!');
534
+ return true;
535
+ }
536
+ return false;
537
+ };
538
+ ```
539
+
540
+ ### Show/hide certain buttons
541
+
542
+ In some situations, there is a need to hide certain buttons. For example, you want to hide the part list button because
543
+ the part list contains info you do not want to show to everybody. To do this you need to pass in the correct init
544
+ options:
545
+
546
+ ```JavaScript
547
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
548
+ 'demoConfigurator',
549
+ document.getElementById('configurator-container'),
550
+ {...options, buttons: {partlist: false}},
551
+ );
552
+ ```
553
+
554
+ The key of the `buttons` hash in the options are defined by the UI_BUTTON enum which was shown one section above.
555
+
556
+ ### Listen to onRequestProduct
557
+
558
+ To be notified when the user clicks on the checkout-call-to-action button in our iframe you need to define
559
+ the `onRequestProduct` callback.
560
+
561
+ ```JavaScript
562
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
563
+ 'demoConfigurator',
564
+ document.getElementById('configurator-container'),
565
+ options,
566
+ );
567
+ configurator.ui.callbacks.onRequestProduct = (configurationId, image, partlist, price, labels, configuration) => {
568
+ console.log(configurationId, image, partlist, price, labels, configuration);
569
+ };
570
+ ```
571
+
572
+ Of course there are situations where you want to trigger this action from outside. Therefore we provide the
573
+ method `triggerRequestProduct`. When you call this method the same process starts as if the user would have clicked on
574
+ the checkout-call-to-action button. This means that you only need to subscribe to the `onRequestProduct` callback to
575
+ handle those situations.
576
+
577
+ ```JavaScript
578
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
579
+ 'demoConfigurator',
580
+ document.getElementById('configurator-container'),
581
+ options,
582
+ );
583
+ configurator.ui.callbacks.onRequestProduct = (configurationId, image, partlist, price, labels, configuration) => {
584
+ console.log(configurationId, image, partlist, price, labels, configuration);
585
+ };
586
+ document.getElementById('trigger-request').addEventListener('click', async () => {
587
+ await configurator.ui.triggerRequestProduct();
588
+ console.log('trigger-request-done');
589
+ });
590
+ ```
591
+
592
+ When the `onRequestProduct` callback fires it is assured that the configuration is saved to the Roomle backend. The data
593
+ which you get from the callback are therefore the same as in the Roomle backend. This enables you to do ceratin things
594
+ like: reloading the configuration later based on the configuration ID. This is especially helpful if the user adds a
595
+ configuration to the shopping cart and continues later (maybe next day or after a restart of the browser etc.).
596
+ Furthermore the `onRequestProduct` callback can be used to calculate a conversion rate (e.g.: how many users who
597
+ configured actually add something to the cart). As you can see this callback is a very imporant one and we highly
598
+ recommend not working around this callback.
599
+
600
+ ### Add product variants
601
+
602
+ If you have different variants of your product and want to show them to your webshop users it makes sense to activate
603
+ the variants feature. Variants could be products with different materials but we advise to use the variants feature for
604
+ structural different product configurations, e.g.: the customer can start with a L-sofa but you have various different
605
+ types of sofas or you start with a lowboard shelf but the same collection also has a highboard.
606
+
607
+ In other words, the variants feature should help the user of your webshop to explore the collection of a product. Of
608
+ course the user could configer from on variation to the next variation but this is time consuming and reduces the chance
609
+ that the user discovers all meaningful options.
610
+
611
+ To enable the variation feature some steps are necessary:
612
+
613
+ The first thing involves data-setup in [Rubens Admin](https://admin.roomle.com/):
614
+
615
+ * you need to create different variants of your base product
616
+ * you need to assign the variants to a tag
617
+
618
+ After that you can create a "variants map", which is basically a JSON key/value pair. The key is the ID of the root
619
+ component and the value is the ID of the tag, an example could look like:
620
+
621
+ ```JavaScript
622
+ const options = {
623
+ variants: {
624
+ 'usm:frame': 'tag_id_usm',
625
+ 'vitra:chair': 'tag_id_vitra'
626
+ }
627
+ };
628
+ ```
629
+
630
+ Now everytime a configuration which is based on the root component `usm:frame` the variants from the tag `tag_id_usm`
631
+ are loaded and displayed in the configurator. If you change to something which is based on `vitra:chair` the variants
632
+ from `tag_id_vitra` are loaded. If you load something which is not based on one of them no variants are loaded.
633
+
634
+ This behaviour is needed that the configurator only shows relevant variants which have something in common with the
635
+ initial loaded configuration.
636
+
637
+ ```JavaScript
638
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
639
+ 'demoConfigurator',
640
+ document.getElementById('configurator-container'), {
641
+ ...options,
642
+ variants: {
643
+ 'usm:frame': 'tag_id_usm',
644
+ 'vitra:chair': 'tag_id_vitra'
645
+ }
646
+ },
647
+ );
648
+ ```
649
+
650
+ The full example can be found in the
651
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/08_variants.html">08_variants.html</a>
652
+
653
+ When changing a variation the global parameters are applied to the newly loaded variaton. You can try this out in the
654
+ CodeSandbox below. Just set the color of the shelf to yellow and switch the variant. You will see that the loaded
655
+ variant is also yellow.
656
+
657
+ You can achieve the same behavior when you add the option `{applyCurrentGlobalParameters: true}`
658
+ to `extended.loadConfigurableItemById`, `extended.loadConfigurationById` or `extended.loadConfiguration`
659
+
660
+ You can test and play around with those settings in this CodeSandbox:
661
+ [![Edit add-variations-cvfyw](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/add-variations-cvfyw?file=/index.js)
662
+
663
+ ### Embed without JS interaction
664
+
665
+ In case you only want to include the Roomle 3D scene without any interaction to your webshop you can simply do that by
666
+ setting the `src` of an iframe. Therefore you only need to add the query param `api=false`. This means the JavaScript
667
+ API is not loaded and therefore the Roomle 3D scene does not wait for instructions of your webshop (e.g.: which object
668
+ to load). A simple example would consist out ouf the query params: `id` (what object to load) and `api=false`:
669
+
670
+ ```html
671
+
672
+ <iframe src="https://www.roomle.com/t/cp/?id=usm:frame&api=false" width="560" height="315"></iframe>
673
+ ```
674
+
675
+ We want to mention that we suggest to use the JavaScript API because it gives you more control about many things.
676
+ Especially about versioning and settings. Furthermore you only have very limited options for customizing the Roomle
677
+ Configurator and also you loose a lot of interaction possibilities which could lead to better user engagement. If you
678
+ want to do customizations like skinning etc. you have to use the JavaScript API.
679
+ Nevertheless in some cases the embedding without JavaScript API makes perfect sense and has a valid use-case.
680
+
681
+ ### Parameter implementation outside of the configurator iFrame
682
+
683
+ #### Intro
684
+
685
+ The Roomle Embedding API gives you the possibility to get and set parameters inside the Roomle Configurator from
686
+ outside. This means your webshop is able to change parameters of the configuration based on some logic you can define.
687
+
688
+ #### Who should use this functionality?
689
+
690
+ This functionality is useful for every webshop which has special logics when and how to set a given parameter. For
691
+ example you could preset parameters based on the geo-location of the user. This can be helpful for things like power
692
+ plugs. For US users you can set the parameter to something different than for EU users.
693
+
694
+ Another use case would be to change the UX slightly. Maybe there is some very important parameter which should be
695
+ visible all the time no matter what the user is doing inside the configuration. Then you can set up the controls for
696
+ these parameters outside of the configurator directly within your webshop. Ideally, you set these parameters to "hidden"
697
+ inside the Roomle Configurator.
698
+
699
+ #### How can this be implemented with API
700
+
701
+ It is important that the parameters you want to change from outside are "well-known". This means that you know the name
702
+ of the parameters. The name of the parameters can be anything and are defined during scripting. For example, the width
703
+ parameter can be named something like "shelfWidth". If the parameter name is calculated dynamically that's not possible.
704
+
705
+ #### Code
706
+
707
+ The following code snippet should give you an idea of how to implement the above-described behavior:
708
+
709
+ ```JavaScript
710
+ (async function() {
711
+ const minWidth = '10rem';
712
+ // be aware that you use the "extended" object, this needs to be included in your licence
713
+ // if you are unsure please ask your Roomle Rubens Contact person
714
+ const params = await configurator1.extended.getParametersOfRootComponent();
715
+ const viewParam = params.find(({key}) => key === 'door');
716
+ if (!viewParam) {
717
+ console.warn('Configuration has no door param');
718
+ return;
719
+ }
720
+ const parent = document.querySelector('.configurator-container').parentNode;
721
+ if (!parent) {
722
+ console.warn('No Roomle Configurator found');
723
+ return;
724
+ }
725
+ const wrapper = document.createElement('div');
726
+ viewParam.validValues.forEach(({value, label}) => {
727
+ const button = document.createElement('div');
728
+ const span = document.createElement('span');
729
+ span.innerText = label;
730
+
731
+ span.classList.add('btn', 'btn-primary');
732
+ span.style.minWidth = minWidth;
733
+ span.style.marginBottom = '1rem';
734
+ button.appendChild(span);
735
+ // be aware that you use the "extended" object, this needs to be included in your licence
736
+ // if you are unsure please ask your Roomle Rubens Contact person
737
+ button.addEventListener('click', () => configurator1.extended.setParameterOfRootComponent(viewParam, value));
738
+ wrapper.appendChild(button);
739
+ });
740
+
741
+ parent.style.display = 'grid';
742
+ parent.style.gridTemplateColumns = '1fr ' + minWidth;
743
+ parent.style.gridGap = '10px';
744
+
745
+ parent.appendChild(wrapper);
746
+
747
+ }());
748
+ ```
749
+
750
+ It is also possible to change the value of parameters which are currently not visible if the parameter key is known, for
751
+ example:
752
+
753
+ ```typescript
754
+ RoomleConfigurator.setParameter({"key": "width"}, '400');
755
+ ```
756
+
757
+ The full example can be found in the
758
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/13_parameters.html">13_parameters.html</a>.
759
+
760
+ ### Load different products into the configurator
761
+
762
+ Since the Roomle Configurator is a single-page-app it is very easy to load different products into the configurator. If
763
+ you are unsure what a single-page-app (SPA) is then it makes sense to read through the various explainations on the
764
+ internet for example like [this discussion](https://softwareengineering.stackexchange.com/a/376880). If the concept of a
765
+ SPA makes sense to you, let's have a look on the code:
766
+
767
+ ```html
768
+
769
+ <div class="product" data-roomle-id="__SOME_ROOMLE_ID_1__">
770
+ <div>Product 1</div>
771
+ </div>
772
+ <div class="product" data-roomle-id="__SOME_ROOMLE_ID_2__">
773
+ <div>Product 2</div>
774
+ </div>
775
+ <div class="product" data-roomle-id="__SOME_ROOMLE_ID_3__">
776
+ <div>Product 3</div>
777
+ </div>
778
+ ```
779
+
780
+ ```JavaScript
781
+ const buttons = document.querySelectorAll('.product');
782
+ [...buttons].forEach((button) => {
783
+ button.addEventListener('click', (productDomNode) => {
784
+ const target = productDomNode.target;
785
+ let roomleId = target.getAttribute('data-roomle-id') || target.parentElement.getAttribute('data-roomle-id');
786
+ if (!roomleId) {
787
+ return;
788
+ }
789
+ configurator.ui.loadObject(roomleId);
790
+ });
791
+ });
792
+ ```
793
+
794
+ The full example can be found in the
795
+ file <a target="_blank" rel="noopener noreferrer" href="./examples/09_different_products.html">
796
+ 09_different_products.html</a>.
797
+
798
+ ### Localization
799
+
800
+ There are two different parameters to set the localization of the Roomle Configurator. First there is `locale` and then
801
+ there is `overrideCountry`. If they are not provided both of them are inferred by the Roomle Configurator based on the
802
+ browser settings and the location of the user of your webshop. Most of the time this is not 100% accurate. Let's have a
803
+ look why on the example of `locale`. To set the language for the configurator we use `locale`. If a user visits your
804
+ German webshop but has the browser settings switched to Spanish the configurator would be in Spanish but your webshop is
805
+ in German. Since the Roomle Configurator does not know that it is embedded on the German webshop it's the best guess we
806
+ can make. But if the webshop sets the `locale` we can use the same language as in the webshop. Therefore we highly
807
+ recommend using this parameter. The same challenge arises for `overrideCountry`. The country parameter is used for
808
+ the [Roomle Price service](#use-the-roomle-price-service). Based on the country we decide which price list to load. For
809
+ example the price list with pounds for the UK webshop and the euro prices for the webshop based in EU. For both
810
+ 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
811
+ list for languages for example on [ISO_639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) the same is true
812
+ for the country codes [ISO_3166-1_alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
813
+
814
+ Let's have a look on the following code:
815
+
816
+ ```JavaScript
817
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
818
+ 'demoConfigurator',
819
+ document.getElementById('configurator-container'),
820
+ {...options, locale: 'de', overrideCountry: 'at', usePriceService: true},
821
+ );
822
+ ```
823
+
824
+ With this snippet the webshop tells the Roomle Configurator that it should use the language `de` and the country `at`.
825
+ Which basically means: "show the configurator in German and with the prices for the Austrian market". As explained above
826
+ the `overrideCountry` is only needed if your webshop does not maintain the prices and you rely on the Roomle Price
827
+ Service.
828
+
829
+ To check whether your language is supported, take a look at
830
+ the [currently supported configurator languages](https://www.roomle.com/en/documentation/configurator-languages).
831
+
832
+ ### Opening an existing configuration again
833
+
834
+ As a webshop you often want to load a specific configuration again. There could be different use-cases e.g.: sharing a
835
+ configuration with other people or giving the user the chance to continue a configuration. We recommand to create a
836
+ landing page which can handle Roomle Configuration IDs (be aware of the difference between a hash and a Roomle
837
+ Configuration ID). Probably one of the easiest ways is, to pass the ID as query param. Let's see some code:
838
+
839
+ ```JavaScript
840
+ const urlParams = new URLSearchParams(window.location.search);
841
+ const roomleId = urlParams.get('roomleId');
842
+ if (!roomleId) {
843
+ console.warn('Please add the query param roomleId to the URL, as test value you can use: usm:frame:68AB2631D0E02A9FBBCA1371621CD23BB68818685738868D7DEB5B830BD5CD2D');
844
+ return;
845
+ }
846
+ configurator.ui.loadObject(roomleId);
847
+ ```
848
+
849
+ You can test and play around with those settings in this CodeSandbox:
850
+ [![Edit intelligent-merkle-s589m](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/opening-an-existing-configuration-again-lzukpn?file=/index.js)
851
+
852
+ ### Using different light settings
853
+
854
+ We try to provide a default light setting which works with the majority of products. Nevertheless there are certain
855
+ products which require a special tweaking of those light settings. We provide a set of pre-defined light settings.
856
+ Currently we support:
857
+
858
+ * `sofa`: we created this light setting especially for sofas
859
+ * `shelf`: the shelf light setting is optimized for shelfs
860
+ * `shelf_front`: the shelf_front is optimized for shelfs with deep drawers so that there is not too much shadow inside
861
+ them
862
+ * `equal`: is a balanced light
863
+ * `camera`: light comes always from the camera perspective
864
+ * `baked`: light is set accordingly to the baked shadows
865
+
866
+ To use a special light setting you only need to provide the `ls` parameter in the options. In code this looks like:
867
+
868
+ ```JavaScript
869
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
870
+ 'demoConfigurator',
871
+ document.getElementById('configurator-container'),
872
+ {...options, ls: 'sofa'},
873
+ );
874
+ ```
875
+
876
+ To switch through the different light settings you could try the following
877
+ example: <a target="_blank" rel="noopener noreferrer" href="./examples/11_light_settings.html">
878
+ 11_light_settings.html</a>.
879
+
880
+ ### Send saved configuration to the e-mail of the user
881
+
882
+ To send a saved configuration to the e-mail address of the user you need to the set parameter `emails` to `true`. By
883
+ default we only show the link to the configuration. Without any configuration the e-mail will contain a link back to
884
+ Roomle. If you want to link to your webshop please make sure that you have a page which can open existing configuration.
885
+ How this works is explained in the
886
+ section [Opening an existing configuration again](#opening-an-existing-configuration-again). If you setup this page and
887
+ everything is in place then you can set the `deeplink` parameter. The `deeplink` parameter should be a URL to a page in
888
+ your webshop and containing the following placeholder: `#CONFIGURATIONID#`. This placeholder is then replaced by the
889
+ real Configuration ID. The following code snippet shows the setup for the configurator.
890
+
891
+ **Important**: please also make sure that the deeplink is also set in the database of Roomle and linked to
892
+ the `configuratorId` you are using. If this is not done, the link in the e-mail won't be replaced. If you encounter any
893
+ problems, please contact your Roomle Contact Person.
894
+
895
+ ```JavaScript
896
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
897
+ 'demoConfigurator',
898
+ document.getElementById('configurator-container'),
899
+ {
900
+ ...options,
901
+ emails: true, deeplink: 'https://www.roomle.com/t/cp/?id=#CONFIGURATIONID#&configuratorId=demoConfigurator'
902
+ },
903
+ );
904
+ ```
905
+
906
+ The full example can be found here: <a target="_blank" rel="noopener noreferrer" href="./examples/12_email.html">
907
+ 12_email.html</a>.
908
+
909
+ ### Grouping the part list by main component
910
+
911
+ The part list is an object which has the following TypeScript interface:
912
+
913
+ ```typescript
914
+ interface PartList {
915
+ originPart: Part;
916
+ perMainComponent: PartList[];
917
+ fullList: Part[];
918
+ }
919
+ ```
920
+
921
+ For things like calculating a price etc most of the time the fullList is needed but for UI purposes it often make sense
922
+ to group parts together so that the user understands better what she/he configured. To display the part list grouped
923
+ just add the following option to the init parameters: `groupPartList: true`, for example like:
924
+
925
+ ```JavaScript
926
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
927
+ 'demoConfigurator',
928
+ document.getElementById('configurator-container'),
929
+ {
930
+ ...options,
931
+ groupPartList: true
932
+ },
933
+ );
934
+ ```
935
+
936
+ All the callbacks like `onPartListUpdate` will work the same as before. If you need access to the grouped part list just
937
+ use the `partList.perMainComponent` inside the callback.
938
+
939
+ ### Change share url
940
+
941
+ Before you change the share url make sure you read through the
942
+ sections [Send saved configuration to the e-mail of the user](#send-saved-configuration-to-the-e-mail-of-the-user)
943
+ and [Opening an existing configuration again](#opening-an-existing-configuration-again).
944
+
945
+ 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
946
+ existing configuration. How this works is explained in the
947
+ section [Opening an existing configuration again](#opening-an-existing-configuration-again). If you setup this page and
948
+ everything is in place then you can set the `deeplink` parameter. The `deeplink` parameter should be a URL to a page in
949
+ your webshop and containing the following placeholder: `#CONFIGURATIONID#`. This placeholder is then replaced by the
950
+ real Configuration ID. The following code snippet shows the setup for the configurator.
951
+
952
+ **Important**: please also make sure that the deeplink is also set in the database of Roomle and linked to
953
+ the `configuratorId` you are using. If this is not done, the link in the e-mail won't be replaced. If you encounter any
954
+ problems, please contact your Roomle Contact Person.
955
+
956
+ ```JavaScript
957
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
958
+ 'demoConfigurator',
959
+ document.getElementById('configurator-container'),
960
+ {
961
+ ...options,
962
+ emails: true, deeplink: 'https://www.roomle.com/t/cp/?id=#CONFIGURATIONID#&configuratorId=demoConfigurator'
963
+ },
964
+ );
965
+ ```
966
+
967
+ The full example is similar to the `12_email.html` example and can be found
968
+ there: <a target="_blank" rel="noopener noreferrer" href="./examples/12_email.html">12_email.html</a>.
969
+
970
+ ### Google Analytics and user consent
971
+
972
+ The Roomle Rubens uses Google Analytics to improve UX and performance based on the anonymized data we collect. We **do
973
+ not** collect any private or personalized data. We **do not** use data collected with Google Analytics for personalized
974
+ marketing or spam. We follow the best practices to protect the data of the user. Nevertheless there are doubts if using
975
+ Google Analytics is allowed without the consent of the user. Currently you have two options:
976
+
977
+ * ask the user for consent before loading the Roomle Rubens configurator. This is similar how YouTube and Twitter
978
+ embedings work.
979
+ * if you prefere more fine grained control you can do the following:
980
+
981
+ #### Give consent for Google Analytics later
982
+
983
+ First you need to initialize Roomle Rubens without the Google Analytics consent. Therefore you need to do the following:
984
+
985
+ ```JavaScript
986
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
987
+ 'demoConfigurator',
988
+ document.getElementById('configurator-container'),
989
+ {...options, gaConsent: false}
990
+ );
991
+ ```
992
+
993
+ Now nothing will be tracked to Google Analytics. As described this is not the ideal case because we use this data for UX
994
+ and performance improvements. If you collect the user consent later you can just call the method `giveGaConsent`. This
995
+ works as follows:
996
+
997
+ ```JavaScript
998
+ configurator.ui.giveGaConsent();
999
+ ```
1000
+
1001
+ If you never call this method no data will be sent to Google Analytics. But keep in mind that you will lose insights
1002
+ into the usability of your configurator.
1003
+
1004
+ The full example can be found here: <a target="_blank" rel="noopener noreferrer" href="./examples/14_ga_consent.html">
1005
+ 14_ga_consent.html</a>.
1006
+
1007
+ ### Change text/labels in configurator UI
1008
+
1009
+ It's possible to change certain labels for certain languages in the UI of the configurator.
1010
+
1011
+ For example if you want to change what's written on the `Request Product` button you can use the `translations` options
1012
+ to change it:
1013
+
1014
+ ```typescript
1015
+ const options = {
1016
+ locale: 'en',
1017
+ translations: {
1018
+ en: {
1019
+ params: {
1020
+ 'request-product': 'Add to cart'
1021
+ }
1022
+ }
1023
+ }
1024
+ };
1025
+ ```
1026
+
1027
+ To see which labels exist you can take a look at
1028
+ the [API documentation](./api/interfaces/types.UiInitData.html#translations).
1029
+
1030
+ You can test and play around with those settings in this CodeSandbox:
1031
+ [![Edit intelligent-merkle-s589m](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/intelligent-merkle-s589m?fontsize=14&hidenavigation=1&theme=dark)
1032
+
1033
+ ### Implementing a custom share pop up
1034
+
1035
+ If you want to fully customize the share experience of your users you can do this by implementing your own pop-up.
1036
+ Therefore you need to apply the knowledge you already have from the
1037
+ section ["React on button clicks"](#react-on-button-clicks). In that specific case you need to wait for the click on the
1038
+ button `savedraft`. Since you want to implement your own share logic you just need to return `true` to indicate that you
1039
+ want to disable the default behavior. Now the event "click savedraft" is your hook to execute your own logic. This can
1040
+ vary widely depending on what you want to do but a very basic idea and gist is implemented inside the following
1041
+ CodeSandbox:
1042
+
1043
+ [![Edit custom-share-functionality-gou3u](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/custom-share-functionality-gou3u?file=/index.js?fontsize=14&hidenavigation=1&theme=dark)
1044
+
1045
+ ### Customize/change the UI
1046
+
1047
+ Learn more about UI elements and how to change them in the configurator tutorial
1048
+ about [UI Customization](../guides/tutorial/configurator/08_UI_customization.html).
1049
+
1050
+ ### Image of current configuration/product
1051
+
1052
+ There are two ways to get an image/render of the current configuration shown in the Rubens configurator:
1053
+
1054
+ - 3D / Perspective Image
1055
+ - 2D / Top Image
1056
+
1057
+ If you are using the SDK you can
1058
+ use [preparePerspectiveImage](../api/classes/roomleconfigurator.html#prepareperspectiveimage)
1059
+ or [prepareTopImage](../api/classes/roomleconfigurator.html#preparetopimage) directly on the `RoomleConfigurator`
1060
+ instance.
1061
+
1062
+ In case you are using the `EmbeddingLib`, you can use those calls on the `extended` object of the interface. For
1063
+ example:
1064
+
1065
+ ```JavaScript
1066
+ const configurator = await RoomleConfiguratorApi.createConfigurator(
1067
+ 'demoConfigurator',
1068
+ document.getElementById('configurator-container'),
1069
+ {...options}
1070
+ );
1071
+ const base64Image = await configurator.extended.preparePerspectiveImage({
1072
+ showDimensions: true
1073
+ });
1074
+ ```
1075
+
1076
+ <!---
1077
+ ### Multiple objects
1078
+
1079
+ The Rubens Configurator is also able to handle multiple objects in one scene. To setup this you need to apply some things:
1080
+
1081
+ * generate a plan without walls (walls are not supported at the moment). You can either do this in iOS or the web floor planer. Copy the ID of the plan because you will need this ID to load the scene. Please also make sure that the plan is public available because due to GDPR only public plans can be opened from everywhere.
1082
+ * generate a root tag to which you assign sub tags. Those sub tags should contain all the objects you want to place into one scene. Be aware that only one level of nesting is possible at the moment. This means you have a root tag and only one level of sub tags. Only the items of the sub tags are displayed.
1083
+ * add the correct options on initialization (or in the configurator settings), which are `moc: true` and the ID of the root tag you created previously, for example: `catalogRootTag: "moc_sample_catalog"`
1084
+
1085
+ To understand the following examples we highly recommend to read through the documentation from above (the regular Rubens Configurator) because it explains all the details very well. Everything works the same for multiple objects except the fact that you will get the information for multiple objects instead for one.
1086
+
1087
+ Additionally the following callbacks and methods are added
1088
+
1089
+ Methods:
1090
+
1091
+ * `RoomleConfiguratorApi.createPlanner`: instead of the regular `create` call `createPlanner`
1092
+ * Instead of `triggerRequestProduct` you can call `triggerRequestPlan` and you will get all the objects which are inside the scene
1093
+
1094
+ Callbacks:
1095
+
1096
+ * You will get an `onRequestPlan` callback. This callback is similar to `onRequestProduct` but it will give you more information. You will get every object which is in the scene with it's details including part list, coordinates etc.
1097
+
1098
+ An example can be found in this CodeSandbox:
1099
+ [![Edit multi-object-configurator-qbeyv](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/multi-object-configurator-qbeyv)
1100
+
1101
+ -->