Skip to main content

· 7 min read
Anco Postma

This guide will show you how to create new master data in Elfsquad (for example: from ERP), and use it to create configuration models using only the API. Ofcourse all data created here can be viewed, changed and deleted through both the API and the interface (EMS). For this purpose the elfsquad data api will be used. For more information, refer to the provided documentation. https://docs.elfsquad.io/docs/apis/data-api

1. Creating master data: features

Before you can start building your model, you will first need the building blocks of Elfsquad: features. In order to continue it is highly recommended to read our basic documentation about features. https://support.elfsquad.io/hc/en-us/articles/360035473554-What-are-Features. For some more information: https://support.elfsquad.io/hc/en-us/articles/5266441178130-Create-or-update-Features-via-Data-API.

The data API offers two separate endpoints for creating new features: one for adding a single feature and another for adding multiple features. We strongly advice using the latter over sending multiple requests to the single feature endpoint. To create multiple features, the following request must be sent:

POST: https://api.elfsquad.io/data/1/Features/Default.BulkInsert

The body should have the following format:

{
"entities": [
{
"id": "32915340-0924-409b-a938-f7024b3d883c",
"name": "Product 1",
"articleCode": "AB1",
"type": "Feature",
"salesPrice": 75,
"minValue": 0,
"maxValue": 0,
"stepValue": 0,
"packingUnit": 0,
"reference": "ERP-REF-01",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
},
{
"id": "32915340-0924-409b-a938-f7024b3d883d",
"name": "Product 2",
"articleCode": "AB2",
"type": "Feature",
"salesPrice": 100,
"minValue": 0,
"maxValue": 0,
"stepValue": 0,
"packingUnit": 0,
"reference": "ERP-REF-02",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

Specifying ids in this example is crucial for the next step. While Elfsquad can generate them if omitted, knowing the ids beforehand avoids having to fetch them later on.

Fore more information about the request and response bodies, refer to the provided documentation.

Single feature: https://docs.elfsquad.io/apis/data#tag/Features/operation/Features.Feature.CreateFeature

Multiple features: https://docs.elfsquad.io/apis/data#tag/Features/operation/Features.BulkInsert

2. Enriching master data: feature texts

In order to provide the eventual users with some descriptive information about the features, some feature texts can be created. Both HTML and plain text values are supported. More info about feature texts can be found here: https://support.elfsquad.io/hc/en-us/articles/360018554940-Feature-Texts.

Just as with features, the data API offers two seperate endpoints for creating new feature texts: one for adding a single feature text and one for adding multiple feature texts. To create multiple feature texts, the following request must be sent:

POST: https://api.elfsquad.io/data/1/FeatureTexts/Default.BulkInsert

The body should have the following format:

{
"entities": [
{
"value": "Plain text example",
"languageIso": "en",
"type": "Description",
"featureId": "32915340-0924-409b-a938-f7024b3d883c",
"reference": "string",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
},
{
"value": "<p>Html text example</p>",
"languageIso": "en",
"type": "ExtendedDescription",
"featureId": "32915340-0924-409b-a938-f7024b3d883c",
"reference": "string",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

Note that the featureId property should be equal to the id of the created feature(s) in the previous step.

Fore more information about the request and response bodies, refer to the provided documentation.

Single feature: https://docs.elfsquad.io/apis/data/#tag/FeatureTexts/operation/FeatureTexts.FeatureText.CreateFeatureText

Multiple features: https://docs.elfsquad.io/apis/data#tag/FeatureTexts/operation/FeatureTexts.BulkInsert

3. Expanding master data: feature properties

3.1. Feature properties

Next you will expand your master data with product specifications. In Elfsquad we call these: feature properties. In order to continue it is highly recommended to read our basic documentation about feature properties. https://support.elfsquad.io/hc/en-us/articles/360033531234-What-are-Feature-Properties

For a more detailed aproach for this step: https://support.elfsquad.io/hc/en-us/articles/6244044992668-Feature-properties-via-the-API.

First you have to create the diffrent kind of feature properties that your features will have. Just as with the other entities, the data API offers two seperate endpoints for creating new feature properties: one for adding a single feature property and one for adding multiple feature properties. To create multiple feature properties, the following request must be sent:

POST: https://api.elfsquad.io/data/1/FeatureProperties/Default.BulkInsert

The body should have the following format:

{
"entities": [
{
"name": "Width",
"type": "Input",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"reference": "ERP-PROP-REF-01",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
},
{
"name": "Color",
"type": "Text",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f09",
"reference": "ERP-PROP-REF-02",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

Specifying ids in this example is once again important for the next step.

Fore more information about the request and response bodies, refer to the provided documentation.

Single feature property: https://docs.elfsquad.io/apis/data#tag/FeatureProperties/operation/FeatureProperties.FeatureProperty.CreateFeatureProperty

Multiple feature properties: https://docs.elfsquad.io/apis/data#tag/FeatureProperties/operation/FeatureProperties.BulkInsert

3.2. Feature has feature properties

Next you have to assign your newly created properties to the features created in step 1. To do these you have to create a linking entity. This entity is called FeatureHasFeatureProperty. Just as with the other entities, the data API offers two seperate endpoints for creating new feature has feature properties: one for adding a single feature has feature property and one for adding multiple feature has feature properties. To create multiple feature has feature properties, the following request must be sent:

POST: https://api.elfsquad.io/data/1/FeatureHasFeatureProperties/Default.BulkInsert

The body should have the following format:

{
"entities": [
{
"featureId": "32915340-0924-409b-a938-f7024b3d883c",
"featurePropertyId": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"value": 150,
"reference": "ERP-PROP-REF-01",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
},
{
"featureId": "32915340-0924-409b-a938-f7024b3d883c",
"featurePropertyId": "497f6eca-6276-4993-bfeb-53cbbbba6f09",
"textValue": "red",
"reference": "ERP-PROP-REF-02",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

By sending the payload above we have assigned our feature created in step 1 a width of 150 and a color of "red".

Fore more information about the request and response bodies, refer to the provided documentation.

Single feature property: https://docs.elfsquad.io/apis/data/#tag/FeatureHasFeatureProperties/operation/FeatureHasFeatureProperties.FeatureHasFeatureProperty.CreateFeatureHasFeatureProperty

Multiple feature properties: https://docs.elfsquad.io/apis/data/#tag/FeatureHasFeatureProperties/operation/FeatureHasFeatureProperties.BulkInsert

4. Building the model: configuration model

Now that you have succesfully implemented your master data, you can start building your model. In Elfsquad this model is called a configuration model (or feature model). More info: https://support.elfsquad.io/hc/en-us/articles/9579633505308-What-is-a-Configuration-Models. A configuration model is based on a feature. So if you want to create a model based on the feature you created in step 1 (Product 1), you would have to sent the following request:

POST: https://api.elfsquad.io/data/1/FeatureModels

With the following JSON body format:

{
"rootFeatureId": "32915340-0924-409b-a938-f7024b3d883c",
"hideInShowroom": true,
"hideInOrderEntry": true,
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"reference": "string",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}

Fore more information about the request and response bodies, refer to the provided documentation. https://docs.elfsquad.io/apis/data/#tag/FeatureModels/operation/FeatureModels.FeatureModel.CreateFeatureModel

5. Building the model: nodes

Now that you have a configuration model, you will have to link your features to your model. In order to do this you will have to create (feature model) nodes. These nodes have quite a few optional properties, but the only ones important for now are: id, featureModelId and featureId. To create new nodes, send the following request:

POST: https://api.elfsquad.io/data/1/FeatureModelNodes/Default.BulkInsert

With the following JSON body format:

{
"entities": [
{
"featureModelId": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"featureId": "32915340-0924-409b-a938-f7024b3d883d",
"hideInQuotation": false,
"hideInConfigurator": false,
"hideInOrderEntry": false,
"hideInOverview": false,
"id": "237f6eca-6276-4993-bfeb-53cbbbba6f08",
"reference": "string",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

Fore more information about the request and response bodies, refer to the provided documentation. https://docs.elfsquad.io/apis/data/#tag/FeatureModelNodes/operation/FeatureModelNodes.BulkInsert

6. Building the model: relationships

Now that your features are linked to your model, it is time for the final step: adding structure. In Elfsquad we use (feature model) relationships to do this. More information about how they work here: https://support.elfsquad.io/hc/en-us/articles/360035613073-Parent-Child-Relations.

Relationships also have quite a few properties, but for now we'll focus on four of them: featureModelId, fromNodeId, toNodeId, type. All of these are mandatory. To create new relationships, send the following request:

POST: https://api.elfsquad.io/data/1/FeatureModelRelationships/Default.BulkInsert

With the following JSON body format:

{
"entities": [
{
"featureModelId": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"fromNodeId": "237f6eca-6276-4993-bfeb-53cbbbba6f08",
"toNodeId": "237f6eca-6276-4993-bfeb-53cbbbba6f09",
"order": 1,
"default": false,
"type": "Optional",
"reference": "string",
"customField1": "string",
"customField2": "string",
"customField3": "string",
"customField4": "string",
"customField5": "string"
}
]
}

Property explanation:

  • featureModelId: The id of configuration model this relationship belongs to.
  • fromNodeId: The id of the node that should be the parent in this relationship.
  • toNodeId: The id of the node that should be the child in this relationship.
  • type: The type of the relationship:
    • Optional: The child node can be turned on or off.
    • Mandatory: The child node is always turned on.
    • Alternative: For the parent node, only one alternative child node can be selected at a time.

Fore more information about the request and response bodies, refer to the provided documentation. https://docs.elfsquad.io/apis/data/#tag/FeatureModelNodes/operation/FeatureModelNodes.BulkInsert

After completing all the steps above, you have succesfully created your master data and model structure. Please refer to our internal documentation if you want to add more logic to your model using formulas, conditions and other constraints: https://support.elfsquad.io/hc/en-us/articles/7528093533212-Create-cross-tree-relations-via-data-API.

· 5 min read
Anco Postma

This guide explains how to start a new configuration, and update it, using the configurator API. It will also explain how this configuration can be added to a (new) quotation.

1. Retrieve your configuration models

To ensure successful configuration, start by identifying the model you want to configure. You can retrieve a list of all available configuration models by sending the following request:

GET: https://api.elfsquad.io/configurator/3/configurator/configurationmodels

The response will look something like the following:

{
"categories": [],
"features": [
{
"featureModelId": "32915340-0924-409b-a938-f7024b3d883a",
"featureId": "dbe93c07-198c-449f-9f22-91c529a4d060",
"articleCode": "abc",
"name": "Product 1"
}
],
"language": "en"
}

The featureModelId property, which uniquely identifies the configuration model, is essential for the next step. For more information about the response object, refer to the provided api documentation. https://docs.elfsquad.io/apis/configurator/#tag/ConfigurationModels.

2. Start a new configuration

Now that we know the id of our model, we can start a new configuration. This can be achieved by sending the following request:

POST: https://api.elfsquad.io/configurator/3/configurator/new/{featureModelId}

The featureModelId in this request is the same as the one retrieved in the previous step. Note: the id can also be found in the EMS. It is also possible to send a body containing startup requirements when starting a new configuration. This body must have the following format:

{
"startupRequirements": [
{
"nodeId": "123e4567-e89b-12d3-a456-426614174000",
"type": 0,
"value": 0
},
{
"nodeId": "123e4567-e89b-12d3-a456-426614174001",
"type": 1,
"value": 10
},
{
"nodeId": "123e4567-e89b-12d3-a456-426614174002",
"type": 2,
"value": "Some text"
}
]
}

Property explanation:

  • nodeId: The id of the node you want to change.
  • type & value: We currently support 3 different types:
    1. Selection(0): When using a selection type requirement, sending a value of 0 will turn the specified node off. Any other value will turn the node on and change the value to the provided value. Only numeric values are supported.
    2. Value(1): When using a value type requirement, the specified node will turn on (if not on already) and will have it's value changed to the provided value. Even when the value is 0. Only numeric values are supported.
    3. Text(2): When using a text type requirement, the specified node will have it's textValue changed to the provided value. Only string values are supported.

The response body will contain the id of the newly created configuration. This id is needed for the other steps. For more information about the request and response bodies, refer to the provided api documentation. https://docs.elfsquad.io/apis/configurator/#tag/NewConfiguration.

3. Updating the configuration

For further configuration changes, you can also utilize the configurator API. However, if you possess all required modifications beforehand, consider using the startup requirements covered earlier for optimal performance. Updating numeric values can be done by sending the following request:

PUT: https://api.elfsquad.io/configurator/3/configurator/{configurationId}/multiple

Note that configurationId is the id of the configuration created in the previous step. The request should contain a JSON body with an array of requirements that have the following format:

[
{
"featureModelNodeId": "ad1e70a3-199a-4363-9b12-40472c568135",
"value": 0,
"isSelection": true
},
{
"featureModelNodeId": "ad1e70a3-199a-4363-9b12-40472c568136",
"value": 5,
"isSelection": false
}
]

Property explanation:

  • featureModelNodeId: The id of the node that has to be updated.
  • value: The value that has to be assigned to the specified node. Only numeric values are supported.
  • isSelection: When set to true, the requirement will behave like a selection type requirement from the previous step. Otherwise it will act like a value type requirement.

A slightly different request has to be sent when updating text values:

PUT: https://api.elfsquad.io/configurator/3/configurator/{configurationId}/text/multiple

The JSON body is also slightly different:

[
{
"featureModelNodeId": "ad1e70a3-199a-4363-9b12-40472c568135",
"textValue": ""
},
{
"featureModelNodeId": "ad1e70a3-199a-4363-9b12-40472c568136",
"textValue": "Example Text"
}
]

Property explanation:

  • featureModelNodeId: The id of the node that has to be updated.
  • textValue: The text value that has to be assigned to the specified node. Only string values are supported.

For more information regarding the request and response bodies, refer to the provided api documentation. https://docs.elfsquad.io/apis/configurator#tag/Configurator.

4. Creating a new quotation

Before you can add the properly adjusted configuration to a quotation, you have to create a new quotation. This can be done using our quotation api. The api will automatically assign the correct quotation status and quotation number. This can be done by sending the following request:

POST: https://api.elfsquad.io/quotation/1/quotations

This request requires a JSON body with optional properties. The response body will contain the id of the newly created quotation. For more information about the request and response bodies, refer to the provided api documentation. https://docs.elfsquad.io/apis/quotation

5. Adding the configuration to the quotation

In order to complete this step you need the id of the configuration that was created in step 2. You will also need the id of the quotation that was created in the previous step. To add the configuration to the quotation the following request must be sent:

PUT: https://api.elfsquad.io/configurator/3/configurator/addtoquotation

For this request a JSON body with the following format is required:

{
"quotationId": "1ceb365d-7c0e-435f-8012-dd887707710b",
"configurationIds": ["747fe28d-fd35-4938-aac2-7a102b20196e"]
}

The value of quotationId should be replaced by the id of the quotation from the previous step. The value of configurationIds should be an array containing the id of the configuration from step 2. Please note that adding multiple (unique) configurations in one request is supported. After the request completes, the configuration will be added to the quotation. The new quotation is immediately visable in the EMS. Want to change the status of your newly created quotation? Please refer to our documentation: https://support.elfsquad.io/hc/en-us/articles/9435941123484-Quotation-functions-in-API.

· 6 min read
Johannes Heesterman

In this tutorial I will show you a basic integration of the Sketchfab viewer with Elfsquad.

1. Embed Sketchfab viewer

Let's start with creating creating a new HTML page that embeds a Sketchfab viewer. This sample code can also be found on the official Sketchfab for Developers Viewer API page.

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sketchfab Viewer API example</title>

<!-- Insert the Sketchfab viewer script -->
<script type="text/javascript" src="https://static.sketchfab.com/api/sketchfab-viewer-1.12.1.js"></script>

<style>
html, body{
margin:0;
padding:0;
overflow: hidden;
}
iframe {
border:none;
height:100vh;
width:100vw;
}
</style>
</head>

<body>
<!-- Insert an empty iframe with attributes -->
<iframe src="" id="api-frame" allow="autoplay; fullscreen; xr-spatial-tracking" xr-spatial-tracking execution-while-out-of-viewport execution-while-not-rendered web-share allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

<!-- Initialize the viewer -->
<script type="text/javascript">
const iframe = document.getElementById( 'api-frame' );
const uid = 'cee9dd8f490c47469d2a4e64f99e8056'; // Replace with the ID of your model.
const client = new Sketchfab( iframe );
let sketchfabApi;
client.init( uid, {
success: function onSuccess( api ){
sketchfabApi = api;
api.start();
api.addEventListener( 'viewerready', function() {

console.log( 'Viewer is ready' );

} );
},
error: function onError() {
console.log( 'Viewer error' );
}
} );
</script>
</body>
</html>

Now you should be able to open the HTML file in your browser and see your 3D model:

Screenshot 1

2. Serve through web server

In order to be able to embed the viewer as a third party visualization within Elfsquad, the newly created HTML page needs to be served through a web server. For this tutorial it is sufficient to use localhost.

From within the folder in which you created the HTML file type the following command:

python -m http.server 8000

Now if you navigate to http://localhost:8000, you should see your page being served.

3. Embed in Elfsquad

Now that we have a web server running, we can embed the viewer in Elfsquad. Within the Elfsquad EMS navigate to the step editor of the model you want to embed the viewer in.

Create a new step and select the Type "Third party visualization". In the "Third party visualization URL" field enter the URL of your HTML page. In this case, it would be http://localhost:8000.

Screenshot 2

Now hit save and you should see your 3D model being embedded in Elfsquad.

4. Listening to configuration changes

In order to be able to react to configuration changes, we need to add a listener to the viewer. This listener will be activated in the viewerReady event callback.

Update the viewerready event listener to first retrieve a node map of the model. This node map will be used to identify the parts of the model that need to be shown or hidden. Then we will call the activateConfigurationUpdateListener function that will add the listener to the page.

let nodeMap = null;
client.init( uid, {
success: function onSuccess( api ){
sketchfabApi = api;
api.start();
api.addEventListener( 'viewerready', function() {

api.getNodeMap(function(err, nodes) {
if (!err) {
nodeMap = nodes;
activateConfigurationUpdateListener();
}
});
} );
},
error: function onError() {
console.log( 'Viewer error' );
}
} );

Now we can define the activateConfigurationUpdateListener function. This function will be called when the viewer is ready and will add a listener to page that will be activated when the configuration changes. The listener will then call the updateViewer function with the new configuration as an argument.

function activateConfigurationUpdateListener() {
window.addEventListener('message', function(e){
if (e.data && e.data.name == 'elfsquad.configurationUpdated'){
updateViewer(e.data.args);
}
});
}

Now we can define the updateViewer function. This function will be called when the configuration changes and will update the viewer with the new configuration.

For this example I will recursively iterate over all the features in the configuration and based on whether the feature is enabled or not, I will either show or hide the corresponding part of the model. This is just an example and you can implement any logic you want here.

function updateViewer(configuration) {
const stack = configuration.steps.map(step => step.features).flat(1);

while(stack.length > 0) {
const current = stack.pop();
for (let f of current.features) stack.push(f);
if (!current.code) continue;

const ids = Object.values(nodeMap)
.filter(node => node.name == current.code)
.map(node => node.instanceID);

for (let id of ids) {
if (current.isSelected) {
sketchfabApi.show(id);
} else {
sketchfabApi.hide(id);
}
}
}
}

If you mapped the feature codes to the corresponding parts of the model correctly, you should now be able to see the model update when you change the configuration in Elfsquad:

Result

5. Initialize with configuration

Now you may have noticed that the model is not initialized with the configuration. This is because the updateViewer function is only called when the configuration changes. In order to initialize the model with the configuration, we need to trigger the configurationUpdated event once manually within the viewerready function when the viewer is ready.

After the activateConfigurationUpdateListener call add a postMessage call to the top window. This will trigger the configurationUpdated event in Elfsquad and the model will be initialized with the configuration.

api.addEventListener( 'viewerready', function() {
api.getNodeMap(function(err, nodes) {
if (!err) {
nodeMap = nodes;
console.log('nodeMap', nodeMap);
activateConfigurationUpdateListener();

window.top.postMessage({
name: 'elfsquad.triggerConfigurationUpdated'
}, '*');
}
});
} );

6. Result

The final code should look like this:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sketchfab Viewer API example</title>

<script type="text/javascript" src="https://static.sketchfab.com/api/sketchfab-viewer-1.12.1.js"></script>

<style>
html, body{
margin:0;
padding:0;
overflow: hidden;
}
iframe {
border:none;
height:100vh;
width:100vw;
}
</style>
</head>

<body>
<iframe src="" id="api-frame" allow="autoplay; fullscreen; xr-spatial-tracking" xr-spatial-tracking execution-while-out-of-viewport execution-while-not-rendered web-share allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

<script type="text/javascript">
const iframe = document.getElementById( 'api-frame' );
const uid = 'cee9dd8f490c47469d2a4e64f99e8056';
const client = new Sketchfab( iframe );
let sketchfabApi;
let nodeMap;
client.init( uid, {
success: function onSuccess( api ){
sketchfabApi = api;
api.start();
api.addEventListener( 'viewerready', function() {

api.getNodeMap(function(err, nodes) {
if (!err) {
nodeMap = nodes;
console.log('nodeMap', nodeMap);
activateConfigurationUpdateListener();

window.top.postMessage({
name: 'elfsquad.triggerConfigurationUpdated'
}, '*');
}
});
} );
},
error: function onError() {
console.log( 'Viewer error' );
}
} );


function activateConfigurationUpdateListener() {
window.addEventListener('message', function(e){
if (e.data && e.data.name == 'elfsquad.configurationUpdated'){
updateViewer(e.data.args);
}
});
}

function updateViewer(configuration) {
const stack = configuration.steps.map(step => step.features).flat(1);

while(stack.length > 0) {
const current = stack.pop();
for (let f of current.features) stack.push(f);
if (!current.code) continue;

const ids = Object.values(nodeMap)
.filter(node => node.name == current.code)
.map(node => node.instanceID);

for (let id of ids) {
if (current.isSelected) {
sketchfabApi.show(id);
} else {
sketchfabApi.hide(id);
}
}
}
}

</script>
</body>
</html>

7. Next steps

Now that you have a working integration, you can start to customize it to your needs. Here are some resources you can use to get started:

· 9 min read
Stan van Rooy

In this tutorial, I will provide a quick overview of how to implement your customer-facing showroom in Angular.

You can find the resulting project in the showroom-example repository on our Github.

ℹ️ If you’re not familiar with Angular, you can follow the Angular getting started guide.

Setting up a new Angular project

Create a new Angular project using the Angular CLI.

Creating the project

    ng new ShowroomExample --routing=true --style=css

Once that’s finished, you should be able to run the application and open it on localhost:4200.

    ng serve

Installing dependencies

For this tutorial, we’ll make use of the @elfsquad/authentication and @elfsquad/configurator packages.

These are developed and maintained by Elfsquad.

    npm install @elfsquad/authentication
npm install @elfsquad/configurator

Adding some basic html/css

In the index.html, we add a little bit of styling:

    <style>
* {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}

body, p {
margin: 0;
padding: 0;
}
</style>

And in the app.component.html file, we’ll remove everything but the <router-outlet></router-outlet> tag.

Creating the configurator context

We communicate with the Elfsquad API through the ConfiguratorContext. We can initialize this class in the app.module.ts file. The configurator context can be used for both anonymous and showrooms that require a logged in user.

    import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {ConfiguratorContext, IConfiguratorOptions} from '@elfsquad/configurator';
import {AuthenticationMethod} from '@elfsquad/configurator/dist/configurator/IConfiguratorOptions';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ProductOverviewComponent } from './product-overview/product-overview.component';
import { FormsModule } from '@angular/forms';

const options: IConfiguratorOptions = {
authenticationMethod: AuthenticationMethod.ANONYMOUS,
tenantId: '5dcd73c7-c0e9-44e8-85f3-dfef7553e8a2',
};

const configuratorContext = new ConfiguratorContext(options);

@NgModule({
declarations: [
AppComponent,
ProductOverviewComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [
{ provide: ConfiguratorContext, useValue: configuratorContext }
],
bootstrap: [AppComponent],
})
export class AppModule { }

If you want to require a logged in user, you can need to change a few snippets of code in the example above.

  1. Add the authenticationOptions to the configuratorOptions object:
    const options = {
tenantId: '5dcd73c7-c0e9-44e8-85f3-dfef7553e8a2',
authenticationMethod: AuthenticationMethod.USER_LOGIN,
authenticationOptions: {
clientId: '60a98ec8-c9f7-4b4e-a809-0492f25b8037',
redirectUri: 'http://localhost:4200',
}
};
  1. Check if the user is logged in and if not, redirect to the login page.
    const configuratorContext = new ConfiguratorContext(options);
configuratorContext.authenticationContext.isSignedIn().then(signedIn => {
if (signedIn) {
return;
}
configuratorContext.authenticationContext.signIn();
});

⚠️ Make sure to replace the tenantId with your tenant id

Creating the product overview page

We start by creating a ProductOverview component. This component will show all configuration models available.

ng generate component ProductOverview

Adding the product overview route

Now that we’ve created the component, we should register it as a route, so our users can access it. You can register the route by adding it to the app-routing-module.ts file.

    import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {ProductOverviewComponent} from './product-overview/product-overview.component';

const routes: Routes = [
{ path: '', component: ProductOverviewComponent },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Retrieving the configuration models

The first step to creating the product overview is retrieving a list of available configuration models. We can do this in the ngOnInit method of the ProductOverview component.

    import { Component, Inject, OnInit } from '@angular/core';
import { ConfigurationModel, ConfiguratorContext } from '@elfsquad/configurator';

@Component({
selector: 'app-product-overview',
templateUrl: './product-overview.component.html',
styleUrls: ['./product-overview.component.css']
})
export class ProductOverviewComponent implements OnInit {
public configurationModels: ConfigurationModel[] = [];

constructor(
@Inject(ConfiguratorContext) private configuratorContext: ConfiguratorContext,
) { }

ngOnInit(): void {
this.configuratorContext.getConfigurationModels().then(configurationModels => {
this.configurationModels = configurationModels.features;
});
}
}

The configuration models should now be retrieved when you open the page.

Displaying the configuration models

To display those models, we create a grid overview in the product-overview.component.html file.

    <div class="product-overview">
<div *ngFor="let model of configurationModels" class="product-card" [routerLink]="['configure', model.featureModelId]">
<img [src]="model.imageUrl" />
<h3 [innerHTML]="model.description"></h3>
</div>
</div>

And the following CSS:

    div.product-overview {
padding: 80px;
display: flex;
gap: 40px;
flex-wrap: wrap;
justify-content: center;
}

div.product-overview > div.product-card {
padding: 8px;
width: 28%;
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
}

div.product-overview > div.product-card > img {
height: auto;
width: 100%;
}

Creating the configurator page

Now that we have a product overview page, we can proceed to build the actual configurator. This is the page on which users can configure their model.

Let’s start by creating a ConfiguratorComponent

    ng generate component Configurator

And registering a route to access the configurator page. Notice we use a :id parameter in the path. This id can either be the name or the id of a configuration model.

    { path: 'configure/:id', component: ConfiguratorComponent }

Starting a new configuration

Once the user visits the configurator page, we need to start a new configuration. To do this, we’ll:

  1. Inject the ActivatedRoute, from which we can retrieve the configuration model id

  2. Use the ConfiguratorContext to start a new configuration

  3. Store the new configuration on the ConfiguratorComponent

  4. Update ConfiguratorComponent.configuration every time the configuration is updated.

    import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Configuration, ConfiguratorContext } from '@elfsquad/configurator';

@Component({
selector: 'app-configurator',
templateUrl: './configurator.component.html',
styleUrls: ['./configurator.component.css']
})
export class ConfiguratorComponent implements OnInit {
public configuration: Configuration | undefined;

constructor(
private route: ActivatedRoute,
private configuratorContext: ConfiguratorContext
) { }

ngOnInit(): void {
this.route.params.subscribe(params => {
this.configuratorContext.newConfiguration(params['id'])
.then(configuration => {
this.configuration = configuration;
});
this.configuratorContext.onUpdate((e: CustomEvent) => {
this.configuration = e.detail;
});
});
}
}

The resulting configuration object contains many different fields, all of which can be explored on docs.elfsquad.io.

This tutorial will focus mainly on steps and displaying the price.

The configuration object has a steps property, which contains an array of all steps. A step contains features, and each feature can contain child features.

  • Title
  • Features
    • Description
    • UnitPrice
    • TotalPrice
    • Type
    • Features (Children of the current feature, recursive)
      • ...
    • ...

Displaying the steps

We will display only one step at a time. To do this, we’ll add a activeIndex and functions to go the next/previous step to the configurator.component.ts file.

      public activeIndex: number = 0;

public next() {
this.activeIndex += 1;
}

public previous() {
this.activeIndex -= 1;
}

In the HTML, we’ll iterate overall features in the step and display them by using the app-feature tag. This is a component we’ll create in the next step.

    <div *ngFor="let step of configuration?.steps ?? []; let i = index">
<div class="step" [class.active]="i === activeIndex">
<app-feature [configuration]="configuration" [feature]="feature" *ngFor="let feature of step.features"></app-feature>
</div>
</div>

<div class="footer">
<span>
<b>Total price:</b>
{{ configuration?.totalPrice | currency: 'EUR': true }}
</span>
<br />
<button
[disabled]="activeIndex === 0"
(click)="previous()"
>
Previous
</button>
<button
[disabled]="activeIndex === (configuration?.steps ?? []).length - 1"
(click)="next()"
>
Next
</button>
</div>

and CSS

    div.step {
display: none;
max-width: 400px;
}

div.step.active {
display: block;
}

div.footer {
margin-top: 12px;
margin-left: 24px;
}

Creating the feature component

We show features using the app-feature tag in the previous step. This is a new component that we’re about to implement.

Because of the recursive nature of features, we need to create a new component for them.

    ng generate component Feature

This component will take a feature as input.

    import { Component, Input, OnInit } from '@angular/core';
import { ConfigurationFeature, Configuration } from '@elfsquad/configurator';

@Component({
selector: 'app-feature',
templateUrl: './feature.component.html',
styleUrls: ['./feature.component.css']
})
export class FeatureComponent implements OnInit {
@Input('feature') feature: ConfigurationFeature | undefined;
@Input('configuration') configuration: Configuration | undefined;

constructor( ) { }

ngOnInit(): void { }
}

Toggling features on/off

For this example, we’ll only enable toggling features on and off, so we’ll only have to implement the toggle() function.

This function will (de)select an option within the configuration model.

      toggle(): void {
if (!this.feature)
return;

if (!this.configuration)
return;

const value = this.feature.isSelected ? 0 : 1;
this.configuration.updateRequirement(
this.feature.id,
this.feature.isSelected,
value
);
}

Displaying the feature

For displaying features, we’ll add some HTML to the feature.component.html file.

    <div class="feature">
<div class="header">
<span [innerHTML]="feature?.description"></span>
<input
type="checkbox"
[checked]="feature?.isSelected"
(click)="toggle()"
*ngIf="feature?.type === 0"
/>
<input
type="radio"
[checked]="feature?.isSelected"
(click)="toggle()"
*ngIf="feature?.type === 2"
/>
</div>
<span *ngIf="feature?.minValue == feature?.maxValue && feature?.value != 0 && feature?.value != 1">
{{feature?.value}} {{feature?.unitOfMeasurement}}
</span>
<span>{{feature?.unitPrice}}</span>
<app-feature
[configuration]="configuration"
[feature]="f"
*ngFor="let f of feature?.features"
></app-feature>
</div>

And add the css below to feature.component.css

    div.feature {
width: 100%;
height: 100%;
margin-left: 24px;
margin-bottom: 6px;
}

div.feature > div.header {
display: flex;
align-items: center;
justify-content: space-between;
}

Requesting a quote

Now that we are able to configure a product, we can go ahead and request a quotation. We’ll add a button to the configurator page:

    <button [routerLink]="['/checkout', configuration?.id]">Request quote</button>

Creating the checkout page

As before, we’ll start by creating the Checkout component.

    ng generate component Checkout

and add the route to the app-routing.module.ts file. In this route, the id stands for the configuration id.

      { path: 'checkout/:id', component: CheckoutComponent }

In the checkout.component.ts file, we’ll create a function to request a quote

     import { Component, OnInit } from '@angular/core';
import { ConfiguratorContext, QuotationRequest } from '@elfsquad/configurator';

@Component({
selector: 'app-checkout',
templateUrl: './checkout.component.html',
styleUrls: ['./checkout.component.css']
})
export class CheckoutComponent implements OnInit {

constructor(private configuratorContext : ConfiguratorContext) { }

ngOnInit(): void {
}
public isSubmitted = false;
public model: QuotationRequest = {};

requestQuote() {
this.configuratorContext.requestQuote(this.model).then(_ => {
this.isSubmitted = true;
});
}
}

The checkout page itself, is divided into two sections. One before the request is submitted, and one afterward:

    <h3>Request quote</h3>

<div *ngIf="!isSubmitted">
<form>
<fieldset>
<legend>Contact information</legend>
<label>
First name:
<input [(ngModel)]="model.firstName" name="firstName" />
</label>
<label>
Last name:
<input [(ngModel)]="model.lastName" name="lastName" />
</label>
<label>
Email:
<input [(ngModel)]="model.email" name="email" />
</label>
<label>
Phone:
<input [(ngModel)]="model.phoneNumber" name="phone" />
</label>
<legend>Company information</legend>
<label>
Company name:
<input [(ngModel)]="model.companyName" name="companyName" />
</label>
<label>
Street:
<input [(ngModel)]="model.streetName" name="streetName" />
</label>
<label>
City:
<input [(ngModel)]="model.city" name="city" />
</label>
<label>
Postal code:
<input [(ngModel)]="model.postalCode" name="state" />
</label>
<input type="submit" value="Submit" (click)="requestQuote()" />
</fieldset>
</form>
</div>

<div *ngIf="isSubmitted">
<h3>Thank you for your request!</h3>
<p>We will contact you shortly.</p>
</div>

· 3 min read
Johannes Heesterman

Quick overview

Verge3D is a 3D viewer that can be used to bring your Blender, 3ds Max or Maya scene to life in the browser. For more information on how to use it, please refer to the Verge3D documentation.

Using the Elfsquad Verge3D blocks, you can easily create interactions between the Showroom and your 3D scene. An overview of the available blocks and other examples can be found on GitHub.

Installing the Elfsquad Verge3D blocks

Plugins can be installed by unzipping the zip file inside Verge3D's puzzles/plugins directory. The default location for the plugins should be: %USERPROFILE%\verge3d_blender\puzzles\plugins.

Download the latest version here.

A list of all releases can be found here.

Setting the example scene in Verge3D

The Elfsquad Verge3D plugin provides sample scenes for the Showroom. These can be found in the Examples directory.

If you would like to use this example scene you must setup the External application directory in the Verge3D App Manager: Verge3D App Manager

This should add a new entry to your Verge3D App Manager: Parametric Models. This is a modified version of the original Parameteric Models sample scene, that interacts with the Showroom, using the newly added Elfsquad Verge3D blocks.

A block has been added to listen to configuration updates and update the configuration model accordingly:

On configuration update sample

Update requirement actions have been added to the UI buttons to tell the configurator to update the configuration model:

On update sample

A JavasScript block has been added to trigger the On configuration update event when the scene is fully loaded:

On scene load sample

Available puzzle blocks

As you can see in the examples, the Elfsquad Verge3D plugin provides several puzzle blocks that can be used to interact with the configurator.

Events

On configuration update

On configuration update block

This block is called when the configuration is updated.

Actions

Get value by code

Get value by code block

This block can be used to retrieve the value of a feature by its feature code.

Get value by name

Get value by name block

This block can be used to retrieve the value of a feature by its feature name.

Update requirement by code

Update requirement by code block

This block can be used to update the requirement of a feature by its feature code.

Setting up the Showroom

Configuration model

In order to fully interact with the sample Verge3D scene, we need to setup a configuration model that has 3 features: Height, Thickness, and Corners. It is important to use the same names and codes as the sample model:

Sample model

Third party visualization step

Now we can add a step to the configuration model to display our Verge3D scene, using the following settings:

Step editor

Result

With the setup done we can open the example model in the showroom and should see the following result:

Result

· 3 min read
Johannes Heesterman

Quick overview

In this tutorial I will provide a quick overview of the endpoints that can be used to implement your own customer-facing configurator.

Step 1. Retrieve a list of configuration models.

The first thing we want to do is provide the user with a selection of available configuration models. In order to achieve this we can use the configuration models endpoint

GET /configurator/1/configurator/configurationmodels

Step 2. Start a new configuration

When a user has selected a configuration model to be configured, we can use the featureModelId property to start a new configuration session. We use the start configuration endpoint for this.

GET /configurator/1/configurator/new/{featureModelId}

Result (Some data has been emitted for clarity.):

{
"id": "9c83b007-7e61-4b4c-9920-f125606844d5",
"currencyIso": "eur",
"currency": {
"iso": "eur",
"name": "Euro",
"symbol": "\u20ac",
"decimalDigits": 2,
"rounding": 0,
"namePlural": "Euros",
"isDefault": false
},
"languageIso": "nl",
"countryIso": "nl",
"featureModelId": "b8230fc6-454c-4190-a33a-08d5640714ca",
"featureModel": ...,
"values": {
"7bcf2363-4300-4a6a-faa9-08d5640714d2": 1,
"580b4519-1a7a-46f9-fa7d-08d5640714d2": 4,
"8c81fabc-eaed-41e2-8ec2-08d5d10145ad": 4,
"e22f606d-3e4e-462e-8ec3-08d5d10145ad": 72,
...
},
"conflicts": null,
"basePrice": 42750,
"basePriceLabel": "\u20ac 42.750,00",
"basePriceIncVAT": 42750,
"basePriceIncVATLabel": "\u20ac 42.750,00",
"basePriceExVAT": 42750,
"basePriceExVATLabel": "\u20ac 42.750,00",
"additionalPrice": 10730,
"additionalPriceLabel": "\u20ac 10.730,00",
"additionalPriceIncVAT": 10730,
"additionalPriceIncVATLabel": "\u20ac 10.730,00",
"additionalPriceExVAT": 10730,
"additionalPriceExVATLabel": "\u20ac 10.730,00",
"totalPrice": 53480,
"totalPriceLabel": "\u20ac 53.480,00",
"totalPriceExVAT": 53480,
"totalPriceExVATLabel": "\u20ac 53.480,00",
"totalPriceIncVAT": 53480,
"totalPriceIncVATLabel": "\u20ac 53.480,00",
"leasePrices": null,
"leasePricesLabels": null
}

Overview of important configuration properties:

NameTypeDescription
IdGuidIdentifier of the configuration session. This id is used for updating configuration values and requesting a quotation.
FeatureModelobjectObject that represents the entire configuration model. This object should be used to build the configurator UI.
ValuesobjectObject that contains all values of the configuration. The key represents the identifier of a feature model node. The value represents the selected value for that particular node.
ConflictsobjectIf a conflict occurs during the configuration process this object will be filled with conflicting nodes and solution options.

Step 3. Update value

In order to update a value for a particular option we use the update requirement endpoint

PUT /configurator/1/configurator/{configurationId}?ignoreConflicts=true

  • The ignoreConflicts parameter is optional. When this parameter is set to true the configurator will automatically resolve any conflicts that might occur.

Request body:

{featureModelNodeId: "949af91c-8a0b-4540-fa87-08d5640714d2", value: 1}

NameTypeDescription
featureModelNodeIdGuidIdentifier of the feature model node.
valuenumberUser-selected value for the option. 0 for false, 1 for true, or any other number when the option is provided as an input field.