# Combeenation Docs > Create configurators at rocket speed This file contains all documentation content in a single document following the llmstxt.org standard. ## AI skills AI skills give your AI agent in-depth knowledge of the Combeenation platform. They follow the open [Agent Skills](https://agentskills.io) format, which is supported by many agents such as GitHub Copilot, Claude Code and Cursor. With the skills installed, your agent can: - assist you in [Custom Code](/docs/configurator-ui-ux/custom-code/custom-code-) development, taking both the code in your project and the configurator behind it into account - answer questions about the platform, for example about [Hive](/docs/configurator-logic/hive-language) The skills are maintained in the [Combeenation skills repository](https://github.com/Combeenation/skills) and evolve over time. ### Install Run this command in your project folder: ```bash npx skills add combeenation/skills ``` ### Prerequisites - Connect the [MCP server](/docs/ai/mcp-server), so your agent can access your configurator. - For Custom Code projects, link your project to its configurator: in the Custom Code section of the configurator editor, use "Connect to local IDE". This creates a `cbn.config.json` file in your project, which tells your agent which configurator to work with. --- ## Getting started with AI You can make use of AI tools in several ways: through assistants built into the Combeenation platform, through your own AI agent connected to Combeenation, and through AI tools that read this documentation. ### AI assistants on the platform Combeenation comes with AI assistants built into various areas of the platform. Currently, AI code assist helps you write code for [PDF assets](/docs/asset-management/pdf-asset) and [workflows](/docs/integrations-and-api/workflows/workflow-in-depth). We are working on bringing AI assistance to more areas, including a platform-wide assistant. ### Work with your own AI agent If you use an AI agent such as GitHub Copilot, Claude Code or Cursor, you can connect it to Combeenation: - The [MCP server](/docs/ai/mcp-server) gives your agent live access to the platform. It can explore your configurator and make changes to its drafts. - The [AI skills](/docs/ai/ai-skills) give your agent in-depth knowledge of the platform, such as Hive and Custom Code. We recommend using both: the skills tell your agent _how_ to work with Combeenation, and the MCP server lets it actually do so. ### Use these docs in any AI tool This documentation is available in a format optimized for AI tools. Point your AI tool to one of these files or paste the URL into a chat: - [llms.txt](https://docs.combeenation.com/llms.txt): an index of all pages with short descriptions - [llms-full.txt](https://docs.combeenation.com/llms-full.txt): the complete documentation in a single file The documentation is also available on [Context7](https://context7.com/llmstxt/combeenation_llms_txt). --- ## MCP server The Combeenation MCP server connects your AI agent to the Combeenation platform via the [Model Context Protocol](https://modelcontextprotocol.io). With it, your agent can explore your configurator and make changes to its drafts. ### Connection details | Setting | Value | | -------------- | ------------------------------------- | | URL | `https://portal.combeenation.com/mcp` | | Transport | HTTP | | Authentication | `Authorization: Bearer ` | `` is a Combeenation API token. See [here](/docs/integrations-and-api/integrations#bearer-token-authentication) for how to create one. An API token belongs to a single company. Use the token of the company that owns the configurator you're working on. ### Setup in VS Code The MCP server is configured per project in the file `.vscode/mcp.json`: 1. Open your project in VS Code. 2. Run the command **MCP: Open Workspace Folder MCP Configuration** from the command palette. 3. Add the following configuration: ```json { "inputs": [ { "type": "promptString", "id": "combeenation-api-token", "description": "Combeenation API token", "password": true } ], "servers": { "cbn-mcp": { "type": "http", "url": "https://portal.combeenation.com/mcp", "headers": { "Authorization": "Bearer ${input:combeenation-api-token}" } } } } ``` 4. Start the server: run the command **MCP: List Servers**, select your server and then **Start Server**. VS Code asks for your API token and stores it securely. With this setup, the file contains no secret and can safely be committed to version control. :::info[Token prompt not working] The `inputs` approach may not work in VS Code. If you aren't asked for your token or the server fails to authenticate, replace `${input:combeenation-api-token}` with your actual token and remove the `inputs` section. The file now contains a secret and **must be removed from version control**: 1. Add `.vscode/mcp.json` to your `.gitignore` file. 2. If the file is already tracked, run `git rm --cached .vscode/mcp.json`. ::: :::danger[Never commit your API token] Anyone with access to your repository could use a committed token to access your company's data. Removing the token later doesn't help, as it stays in the git history. If a token was ever committed, revoke it immediately and create a new one. ::: ### Setup in other agents Some agents, such as Claude Code or Cursor, don't read `.vscode/mcp.json` or can't prompt for the token. Add the [connection details](#connection-details) to the agent's own MCP configuration instead. Refer to your agent's documentation for the exact format. As the configuration then contains your token, store it in one of these places: - **Project-level configuration** (recommended): a configuration file in your project that is listed in `.gitignore`. - **User-level configuration**: applies to all your projects. As a token belongs to a single company, this doesn't work if you work on configurators of several companies. ### Check the connection 1. Make sure the server is running. In VS Code, run the command **MCP: List Servers** and check the status of `cbn-mcp`. 2. Check that the Combeenation tools are available in your agent's tool list. 3. Ask your agent something like "List the components of my configurator". For the best results, also install the [AI skills](/docs/ai/ai-skills). --- ## Basic workflow #### 1. Upload 3d file You can upload `.glb` and `.babylon` files to create a 3d asset. **GLB** is a default format for 3d models and widely known across the web. Most 3d tools support exporting in GLB format, and 3d model exchange platforms typically offer models in GLB format as well. **Babylon** files are representing Babylon.js scenes in JSON format. These kind of files are more powerful than GLB files, as additional Babylon.js specific features and technologies can be provided within the files. Babylon files are typically created by exporting manipulated scenes from Babylon.js tools like the [sandbox](https://sandbox.babylonjs.com/) or the [playground](https://playground.babylonjs.com/). **Material extraction** The system automatically extracts material definitions from the uploaded 3d file and creates new material assets inside a folder called `Materials3d`. If a material asset with the same name already exists in the `Materials3d` folder, the existing material will not be overwritten. Instead, the existing asset will be linked to the uploaded 3d asset. ![]() :::info The uploaded 3d file may only contains these kind of materials: - [PBRMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRMaterial) - [PBRMetallicRoughnessMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRMetallicRoughnessMaterial) - [PBRSpecularGlossinessMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRSpecularGlossinessMaterial) - [StandardMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.StandardMaterial) - [BackgroundMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.BackgroundMaterial) - [NodeMaterial](https://doc.babylonjs.com/typedoc/classes/BABYLON.NodeMaterial) The editor warns the user if they try to upload a 3d file containing unsupported material types and rejects the uploaded data completely. ::: #### 2. View and adjust data of 3d asset The 3d asset UI displays specific data about the model, helping the user get an impression of the 3d file's content. - **Nodes**: the structure of meshes and transform nodes - **Materials**: list of material assets that are assigned to nodes within the 3d asset - **Tags**: list of tags that are assigned to nodes within the 3d asset - **Animations**: animation groups that are defined in the 3d asset, see [Babylon.js docs](https://doc.babylonjs.com/features/featuresDeepDive/animation) for detailed information about animations - **Decals**: overview of “decals”, which represent mesh details, created on the Combeenation platform, see “[Decals](/docs/asset-management/3d-and-material-assets/decals)” for detailed information When selecting a certain node, you will see specific data of this node. - **Material**: link to material asset - **Tags**: list of assigned tags - **Transformation**: position, rotation and scaling information of the node - **Morph targets**: technology for interpolating mesh geometry, see [Babylon.js docs](https://doc.babylonjs.com/features/featuresDeepDive/mesh/morphTargets) for detailed information about morph targets ![]( 'image(52).png') **Material assignment** The material-to-node assignment is established during the **initial** upload when creating a new 3d asset. The user can then manually change this material assignment by selecting a different material asset. Each subsequent file upload will not overwrite the existing material assignment to preserve the user's changes. However, if new nodes are present in the upload, material assignments will still be created for these new nodes. **Tag assignment** "Tags" are a concept of Babylon.js for **grouping** objects like nodes or materials, see [Babylon.js docs](https://doc.babylonjs.com/features/featuresDeepDive/tags) for detailed information about tags. These tags can then be used as parameter subject of the viewer controls “[set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values)” slot or in the viewers “[parameter manager](https://3dviewer.docs.combeenation.com/classes/ParameterManager.html)” API. Similar to materials, tags are extracted from the 3d file only during the **initial** upload to preserve any changes made by the user. Users can modify tags within the node editor by adding, removing, and creating new tags. #### 3. Adjusting material assets As mentioned previously, material assets are automatically extracted and created when uploading a 3d model. But material assets can also be created manually by clicking the “**+ New**” button, however this feature only supports **PBR material**s. For a quick setup, you can choose from a variety of material templates. ![]() There are 2 editors, “**Basic**” and “**Expert**”. The “Basic” editor offers a UI for the most common material properties, available exclusively for **PBR materials**. This is because material properties vary significantly across different types of materials, and PBR materials are by far the most common ones in use. ![]() The “Expert“ editor represents the “raw” JSON description of the material. The user has full power to add, remove or modify certain properties to tweak the material. Some properties like `id`, `name` or `customType` are ignored though, as they will be overwritten by the system in a later step. Since the user modifies the JSON string directly, it's possible that the string could become invalid, such as by removing commas or colons. In such cases, the material becomes invalid and cannot be saved. However, the editor helps identify where JSON errors occur, and you can still click “Discard changes” anytime if you encounter issues. ![]() **Upload texture images & use them in materials** If your uploaded 3d model contains materials with **textures,** you will most likely receive some warnings in extracted material assets after the model upload. These warnings indicate that the images, which represent the dedicated textures, are not uploaded yet. You can easily do this by clicking the “Upload image” button in the corresponding texture channel. This action will automatically create an **image asset** in the `Textures3d` folder and links this image asset to the material texture channel. You can also upload your images manually into the `Textures3d` folder and select them in the material texture channel dropdown menu. ![]() #### 4. Assign bundle to configurator See [How to assign bundles to configurators](/docs/asset-management/assign-asset-bundles-to-configurators#how-to-assign-an-asset-bundle) for detailed description. #### 5. Use 3d & material assets in configurator After assigning the asset bundles to the configurators, 3D and material assets can be utilized within the configurator. You'll need to create a [viewer control](/docs/configurator-ui-ux/controls/3d-viewer-control) to visualize these assets. If no further action is taken, you may encounter two outcomes: - If there is exactly **one** 3d asset among all assigned asset bundles, that model will be visible immediately. - If there are multiple 3d assets, all models will be hidden by default, requiring manual handling of model visibility. Handling model visibility and applying parameter can be done in 2 ways. **Hive** Use viewer controls “[set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values)” slot. This is the go-to solution for simple projects, as it eliminates the need to set up a custom code environment. **Custom Code** For more advanced requirements you can establish a [custom code](/docs/configurator-ui-ux/custom-code/custom-code-) setup where you have the full power of the [combeenation viewer](https://3dviewer.docs.combeenation.com/) API, Babylon.js and Typescript in general. Tey components for interacting with 3D and material assets include the [ModelManager](https://3dviewer.docs.combeenation.com/classes/ModelManager.html) and [ParameterManager](https://3dviewer.docs.combeenation.com/classes/ParameterManager.html) though. #### 6. Upload & use viewer environment file In terms of 3d scene customization the user has the possibility to define an “environment”, which is responsible for image based lighting (IBL). See [Babylon.js docs](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/HDREnvironment) for further info about environments and IBL in general. The environment file (.env) is represented by a **file asset**. You can then create a component which provides the URL of the uploaded environment file e.g. using the following Hive rule: ```hive // "Cups3d" = Name (or alias) of bundle assigned to configurator // "env" = Folder name in which "studio" is located // "studio" = Asset name Cups3d.env.studio.url ``` This component can be added to the environment [parameter](/docs/configurator-ui-ux/controls/3d-viewer-control#environment-url) or [slot](/docs/configurator-ui-ux/controls/3d-viewer-control#set-environment-url) in the viewer control. --- ## Decals "Decals" are used to create details, like **badges** or **logos** on existing meshes. ![](https://docs.combeenation.com/assets/images/image-1733397612213-34c276199e0a64c86d7e023030cca377.png) Although such details can also be created inside the 3d model itself, having a customizable editor on the Combeenation platform provides certain advantages: - Change decal configuration like **position** or **size** without having to re-upload the 3d model - Works with any 3d model, no need for special preparation - Integrates smoothly into other parts of the Combeenation 3d framework, such as material assets and tags The basis for this feature is the "[Mesh Decal](https://doc.babylonjs.com/features/featuresDeepDive/mesh/decals#mesh-decals)" functionality from Babylon.js. The output of such a decal is just a **mesh**. Now you can assign [material assets](/docs/asset-management/3d-and-material-assets/basic-workflow#3-adjusting-material-assets) to that mesh or use it in the 3d viewers [parameter system](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) to bring life into that decal. You may also want to have a look at [this chapter](/docs/configurator-ui-ux/controls/3d-viewer-control#how-to-draw-dynamic-content-on-the-3d-model) for drawing dynamic content on the decal mesh. ### How to create a decal First navigate to the "Decals" tab of your 3d model and press "New decal". ![](https://docs.combeenation.com/assets/images/image-1733400785513-4e3081e98a04829607b1efe8f118bcf6.png) This action will open a new modal dialog, where you can enter the **name** of the decal, the "**p\*\***arent mesh**" and the associated **material\*\*. The parent mesh setting is important, as it defines on which mesh the decal geometry will be created. You can choose an existing material, or add a new one. Since decals often use textures with transparent areas, it is typically a good starting point to create a material from the "**Transparent SVG**" template. ![](https://docs.combeenation.com/assets/images/image-1738653626849-dc4471c171a690ddf44d8f063db8f707.png) You can still change all of these settings in the editor at a later step. The decal geometry is the result of the intersection between the parent mesh and a fictive box, that can be tweaked by the editor in the "**Geometry**" section. When opening the "Geometry" section you will see a box with blue edges appear. This box can be used to adjust the geometry data in an interactive way, since finding the correct data for the box geometry can be very tricky. ![](https://docs.combeenation.com/assets/images/image-1733401209009-8e4bed2b2c1bd0a887b2bbda59ea3319.png) Anyway here is an overview of the **geometry settings**: - **Position:** center of the box - this is **not** necessarily the position of the final decal - you can change the position interactively by dragging the box in the scene - use "Pick position in model" to define the box position via mouse click, this is typically a good starting point - **Size:**size of the box - use the gizmos on the corners and areas of the blue overlay box to adjust the size interactively - **Normal:**"main" (= z) direction of the box - required to indicate the first intersection area if "**Cull back faces**" is active - the front side, which is pointing towards the normal vector, is indicated by the red plane - use the gizmos on the edges of the overlay box to adjust the rotation of the box - use "Auto-detect normal" to align the decal box normal vector with the normal vector on the intersection area, this leads to undistorted intersection areas - **Rotation angle**: rotation around the normal vector in degree - edge gizmos may also affect the rotation around the normal vector - **Offset**: distance from the parent mesh - if the decal would be located exactly on the intersection surface, "clipping" effects would occur, therefore the decal has to be positioned slightly **in front** of the parent mesh - each vertice of the decal is shifted along its normal vector, so that round surfaces will be scaled as well - in praxis this value will be very low (e.g. 0.001), since too high offsets will be visible as gaps between the parent and the decal mesh ##### Additional settings - **Capture UVs**: - _false_: creates a "standard" UV mapping across the decal mesh![](https://docs.combeenation.com/assets/images/image-1733403021306-25ac6a298728b27897c34189bb60890c.png) - _true_: uses the UV mapping of the parent mesh on the intersection area![](https://docs.combeenation.com/assets/images/image-1733403104422-581238a27f1b1a4fda469e0d0e72275d.png) - **Cull back faces**: - _true_: decal is only created on the first intersection with the parent mesh - **Local mode**: - _true_: creates decal mesh as **child** of the associated parent mesh, in this way transformation of the parent mesh will also be applied on the decal mesh - _false_: creates decal mesh in world space (without parent mesh) - **NOTE:** this setting has NO effect on the initial position of the decal - **Use glTF side orientation**: - activate this flag if the decal will be created underneath a "glTF root node", which has a negative scaling in z direction - otherwise the faces will be flipped, leading to invisible decals - **CAUTION:** this flag directly relates to "**Local mode**", as "Local mode" decides if the decal is created underneath a parent hierarchy or not --- ## Assign asset bundles to configurators To use an asset in a configurator, you must first allow the configurator to access it. This is done by **assigning** the asset bundle that contains the asset to your configurator. You always assign a **specific version** of a bundle. Thus you are always in control over which data your configurator uses – you choose when it should use a newer version and can go back to using an older version whenever you please. Assignments work with draft versions of both the configurator and asset bundle as well, with only 1 limitation: a published configurator can only use a published asset bundle. This is to prevent any last-minute changes to an asset bundle draft from breaking a live configurator. ### How to assign an asset bundle Open a configurator draft and look for the asset bundle assignment dialog at the top of the page: ![Asset assign overview](img/assign-asset-bundles-to-configurators/Untitled (1)(1).png) Now, select the **bundle** you are interested in from the first dropdown. - A list of **versions** of that bundle will be shown. - Click the "**+**" button of the version you want to use. Usually that would be the newest version. - **Only one version** of a particular bundle can be assigned to the same configurator at a time. However, you can assign **multiple bundles** to the same configurator. You can click an assigned bundle version to see a few more details, such as the person who has assigned the bundle or the timestamp of the assignment. ![Assigned bundle info](img/assign-asset-bundles-to-configurators/Untitled (2)(1).png) #### Alias name You may have observed that an alias name is typically set automatically when assigning a bundle to a configurator. This alias name is usually the same as the asset bundle name, but it can be customized as needed. This alias name is useful for resolving naming conflicts with other assigned bundles or for clearly identifying the bundle you are using, without altering the original asset bundle name. ### Usage Once you have assigned an asset bundle to your configurator, you can use all the assets of that bundle. Simply type the name of the asset in any Hive rule to access it. Example: if you have an image asset with the name `SomeImageAsset` you can access its Url like this: ```hive SomeImageAsset.url ``` As mentioned above ([alias name](/docs/asset-management/assign-asset-bundles-to-configurators#alias-name)) you can also use the alias name to access the contents of a bundle. Especially useful if there are assets with the same names in multiple bundles. ```hive AilasName.SomeImageAsset.url ``` **Data source** assets can currently only be used in **table** components. --- ## Basics A data source contains tabular data that can be selectively loaded and filtered in a configurator with a table component. ### Supported File Types A data source can be created or updated with files of these types: - Microsoft Excel **.xls** or **.xlsx** - CSV files **.csv** - JSON files **.json** While you can create a single data source asset from such a file, we strongly recommend that you instead upload the file as a file asset first. This will allow you to create **multiple** data sources based on it and **update them all at once** whenever you upload a newer version of your file. We will walk you through this process in the next section. ### **Create a new data source** 1. **Create a new file asset** Upload the file that contains the data for your data source as a file asset (make sure that the Asset Type is set to “File”). It must be a file with [one of the supported file types](/docs/asset-management/data-source-asset/basics#supported-file-types). ![]() 2. Select the file asset, click on **Create data source** and enter a name for your new data source. ![]() You now have a data source that is **linked** to the file asset and will be updated automatically when you update the file asset. Now we have to configure which data will be extracted from the file. The asset manager will analyze the file and tries to create a starting point for you to work with. The exact process depends on the type of file: a CSV file already contains tabular data and should result in a faithful representation. The same is true for an Excel file, provided that it also contains tabular data – the asset manager will use the first worksheet it finds, but you can choose which one to use in the **import configuration**, which we will look at next. ### Adjust the Import Configuration The **import configuration** describes which part of the raw data should be read and how it should be interpreted. When you create a new data source, an initial import configuration will be created automatically as a starting point. While some parts of the import configuration are the same for all data sources, other parts depend the type of file that is used as source. For instance, an Excel-based data source allows you to choose the worksheet from which to load data. **JSON-based** data sources require more setup and are [explained in a separate article](/docs/asset-management/data-source-asset/json-based-data-sources), but the following general points also hold true for it. Each import configuration contains a list of **column definitions**, which allow you to describe the columns that you want your data source to have. If the underlying file contains tabular data, then these _can_ be a close match to the columns found in there, but they don’t have to be. You may only be interested in a subset of the data, or may want to interpret it differently – for instance, you can indicate that the texts found in one column are actually URLs or a list of values separated by a specific character. - Each column must have a unique **name**, which you can freely choose. Keep in mind though that the name must be a valid Hive identifier, since you will use it to access the data in your configurator. This name can be identical to the column name found in the underlying file (if it contains tabular data and column names), but it doesn’t have to be – though it’s usually a good idea to keep the names the same. - The column **type** indicates what kind of data it holds. The asset manager will try to convert the raw file contents so that they fit the column type and displays an error if a column value cannot be used. - The **default value** of the column will be used when the underlying file doesn’t contain a value for it (e.g. if an Excel cell is empty). Other settings of the import configuration depend on the underlying file. For CSV files, you can e.g. choose the character that is used to separate column values. Both CSV- and Excel-based data sources allow you to ignore conversion errors and use the column’s default value instead, e.g. if a Number column contains letters. You can change the import configuration at any time to alter how data is read and interpreted. Each time you do, the contents of the underlying fille will be read again and interpreted according to the updated import configuration. This process also happens automatically when the underlying file comes from a file asset that has just been updated. If there are any conversion errors, they will be displayed prominently in the asset manager. ### Advanced Topics #### Linked Asset Columns There is a convenient way to associate a data source column with other assets by using one of the **LinkedAsset** column types. Let’s say you have a `products` data source: you can link each product with a thumbnail, 3D model, material or PDF document so that you can easily access them in your configurator. To do so, place all assets that can be linked to in an asset folder and create a LinkedAsset column. Configure the column to use your asset folder and optionally select one of the assets as a fallback, which will be used if a linked asset is missing. The column values in the underlying file must be of type **Text**, specifically they must be the **names** of the assets in your asset folder. When the data source is imported, the asset manager will look for an asset with that name and use the configured fallback asset if none can be found. A hint will be displayed to inform you about the missing asset. The data source will be **linked** to each folder and fallback asset that is used in a Linked Asset column. If the folder is moved or deleted, it will result in an error to notify you about the compromised data source. #### Interaction with linked file asset - There is no limit on how many data sources you can link to a file asset - Each data source can only be linked to 1 file asset though. - If linked, you cannot update the contents of a data source directly; you have to update the file asset instead (which will update all linked data sources). - You can **copy** a linked data source. The copy will be linked to the same file asset but has an independent import configuration that you can change (e.g. select a different Excel worksheet). - When you copy a file asset, any linked data sources will _not_ be copied, nor will the new file asset be linked to any data sources. - If you **delete** a file asset, all links from it to data sources will be removed. The data sources still function, but cannot be updated until you upload a new file into _each_ data source separately. - There is currently no way to re-create a data source link, which is why we do not recommend you delete a file asset with data source links. - You **can delete** a data source that is linked to a file asset without affecting the file asset or any other linked data source. Only the deleted data source and its link will be removed. #### Automation You can update the contents of data sources automatically, e.g. by loading data from an external service on a fixed schedule, by using a [workflow](/docs/integrations-and-api/workflows/workflow-templates). --- ## JSON-based data sources A [JSON](https://json.org/) document does not contain tabular data, unlike a CSV or XLS file, which is why data sources based on them work a little different. You have fine-grained control over which parts of the JSON document end up in your data source by using [JMESPath](https://jmespath.site/) expressions. JMESPath is a powerful tool to select and transform JSON data, but the syntax needs some getting used to. On this page we demonstrate how to set up a simple data source with JMESPath expressions, followed by some in-depth examples. :::info[Try it yourself] You can download the file below to play around with the examples yourself. Upload it as file assetA file asset can be used to upload any file format. Max file size is 100MB. in the asset manager and then create a data source based on it. You can then edit the structure of the data source and fill in the JMESPath expressions explained below. ::: Download: [products.json](data/json-based-data-sources/products-1.json) ### Define a source Firstly, we have to define which part of our JSON document contains the data that we are interested in. Depending on the document, that may be fairly obvious – for instance if the document is one large JSON array. However, oftentimes a JSON document may be the output of an API endpoint or an export from an external system and hence contains a lot more information besides what we are interested in. To create a data source, we need an **array** or **object** and we can select (or create) one from our document with a JMESPath expression. The system will then create a data source row for each item in the array resp. property value of the object (as defined by the column expressions). Let's look at an example document: ```json { "products": [ { "id": 21, "title": "Cucumber" }, { "id": 22, "title": "Dog Food" }, { "id": 23, "title": "Eggs" } ], "categories": ["ingredients", "furniture", "pets"] } ``` Here we are only interested in the products property, which holds an array of objects. The JMESPath expression to get it is fairly simple – it’s just the name of the property: ```plaintext products ``` This will return the array of objects associated with the `products` property. For each item in the array, a data source row will be created. ### Define columns Now that we have our source, we must define which parts of it we want to convert into data source columns. Let’s say each product object from the example above is a bit more complex and looks like this: ```json { "id": 21, "title": "Cucumber", "description": "Crisp and hydrating cucumbers, ideal for salads, snacks, or as a refreshing side.", "category": "groceries", "price": 1.49, "discountPercentage": 11.44, "rating": 4.71, "stock": 22, "tags": ["vegetables", "onsale"], "sku": "6KGF2K6Z", "weight": 9, "warrantyInformation": "5 year warranty", "shippingInformation": "Ships overnight", "availabilityStatus": "In Stock", "reviews": [ { "rating": 4, "comment": "Very satisfied!", "date": "2024-05-23T08:56:21.620Z", "reviewerName": "Elijah Hill", "reviewerEmail": "elijah.hill@x.dummyjson.com" }, { "rating": 5, "comment": "Fast shipping!", "date": "2024-05-23T08:56:21.620Z", "reviewerName": "Liam Garcia", "reviewerEmail": "liam.garcia@x.dummyjson.com" }, { "rating": 4, "comment": "Excellent quality!", "date": "2024-05-23T08:56:21.620Z", "reviewerName": "Ella Cook", "reviewerEmail": "ella.cook@x.dummyjson.com" } ], "returnPolicy": "30 days return policy", "minimumOrderQuantity": 7, "images": ["https://cdn.dummyjson.com/products/images/groceries/Cucumber/1.png"], "thumbnail": "https://cdn.dummyjson.com/products/images/groceries/Cucumber/thumbnail.png" } ``` As you can see, each product contains nested data and we may not be interested in all of it and we have to bring it into a tabular form. To do so, we create as many column definitions as we need, each one defined by a unique name and type (as with other data sources). The data for each column can come from anywhere in the object, it doesn’t have to be a top-level property. Here are some examples for column definitions: - Column: **Id** Type: **Number** Path: `id` Result: `21` - Column: **Title** Type: **Text** Path: `title` Result: `Cucumber` - Column: **Reviews** Type: **List\** Path: `reviews[*].comment` Result: `[ "Very satisfied!", "Fast shipping!", "Excellent quality!" ]` Note how in the last column we are able to pick the `comment` from each object in the `reviews` array and get a single list of strings as a result, which we can store in a `List` column in our data source. JMESPath allows us to select and transform data in a myriad of ways – here are a few more advanced examples: - Get the URL of the first image (or `null` if the `images` array is empty or missing): Path: `images[0]` Result: `https://cdn.dummyjson.com/products/images/groceries/Cucumber/1.png` - Combine all items in the `tags` array into 1 string: Path: `join(',', tags)` Result: `vegetables,onsale` - Get the average rating across all reviews of the product: Path: `avg(reviews[*].rating)` Result: `4.333333333333333` ### Advanced Scenarios In this section we tackle a few more in-depth problems that you may run across in your data sources. We recommend that you familiarize yourself with [JMESPath](https://jmespath.org/) a bit before diving into these samples. :::info[JMESPath Community Edition] The [JMESPath Community Edition](https://jmespath.site/) enhances JMESPath with a number of useful features that we will make use of in the examples below. You won’t find information about these features on the official JMESPath website. Both sites are a valuable source of information and examples, but please keep in mind that only the Community Edition contains information about the newer features. You can try the examples yourself by using the below JSON file for your data source. ::: Download: [products_options.js](data/json-based-data-sources/products-options.json) Here is an excerpt from that file so you can get an idea of what we’re dealing with: ```json { "products": { "p1": { "title": "Snare Boot", "productType": "Boots", "options": [ { "name": "Color", "optionValues": [ { "name": "Black" }, { "name": "Blue" } ] } ] } } } // collapsed for brevity ``` This example showcases 2 common scenarios you may come across: 1. inside `products` the property names are used as unique keys to identify each product (`“p1"`) 1. compare this to the simple example above, where we have an `id` property in each product object, which is easier to access as we’ll soon learn 2. the `options` array can contain multiple objects which are identified by their `name` property 1. this makes the `“Color"` option more difficult to access, as we’ll see #### Property names as keys Let’s assume that we want to store the product IDs (`“p1"`, `“p2"` etc.) as an `id` column in our data source. We can use `products` as our source expression, which seems good at first but unfortunately, only the property values inside `products` will be available to our column expressions – the property names will be lost. - We can use `items(products)` instead, which will turn each property name/value pair into an array `[ , ]` instead - With that, we can go to our column definitions and use `[0]` to get the property name (`“p1"`) and `[1]` to access the property value (e.g. `[1].title` would yield `“Snare Boot"`) - While this works, it makes all our column definitions unwieldy, as most will have to start with `[1]` Fortunately, we can transform the source even more to make our column definitions easier: ```plaintext map(&merge({id: [0]}, [1]), items(products)) ``` Let’s break down what happens in this expression: - as before, we use `items(products)` to get an array of `[ , ]` arrays - this array is passed to the `map` function as its 2nd argument. `map` applies an expression to each item of an array, thus transforming it. These function-like expressions start with `&` - the expression we want to apply to each item is `merge({id: [0]}, [1])` which takes the product object (`[1]`) and merges it with another object that we create in place - This in-place created object is used to store our property name in an “id” property: `{id : [0]}` The result of this expression is an array of objects that now contain the product id as an easy-to-access property: ```json { "id": "p1", "title": "Snare Boot", "productType": "Boots", "options": [ { "name": "Color", "optionValues": [ { "name": "Black" }, { "name": "Blue" } ] } ] } ``` #### Filtering to find objects Let’s continue the previous example by creating a column definition to hold all colors of a product as a `List`. As we outlined in the introduction, the `options` array contains a number of objects that are identified by their `name` property, which makes it more difficult for us to access the `“Color"` object. Let’s look at the final expression again and then break it down: ```plaintext options[?name=='Color'].optionValues[*].name[] ``` - JMESPath allows us to filter arrays by writing a filter expression: `[? name == ‘Color’]` will return all objects from the array that have a `name` property with the value `“Color"` - Next we need to access the `optionValues` to get to the actual color names; `optionValues[*].name` will do that for us - We now get an array of names inside an array as a result: `[["Black", "Blue"]]` (due to how projection and filters work in JMESPath) - The flatten operator `[]` will collapse these nested arrays into a single array `["Black", "Blue"]` which we can finally use for our column #### Get all Colors across all Products You may want to create a data source that contains all possible product colors, e.g. to allow users to filter products by their preferred color. Unfortunately, our source JSON doesn’t list all available colors explicitly. Each product indicates in which colors it is available, but it is our job to combine all these options into a single table. We can use the filter expression from the previous example, but apply it to all products: ```plaintext products.*[].options[?name=='Color'].optionValues[][].name ``` As you can see we have to use the flatten operator `[]` several times here – it’s best if you just copy the expression and take it apart yourself to see the individual results and what each subsequent expression does. While this solves our problem, we do get duplicate color names. If we only want distinct color names in our data source, we have to augment the above expression even further. We can use the `group_by` function to group all our color objects by their `name` (thus eliminating any duplicates) and then use the `keys` function to return only the property names. Again, we recommend you play around with the expression yourself to see the in-between steps ```plaintext keys(group_by(products.*[].options[?name=='Color'].optionValues[][], &name)) ``` #### Create a Permutation / Cross-Join Table Let’s say we want to create a data source as shown below, where we list each product in each color it is available: ![]() One use case for a table such as this is to filter by product title, to end up with all available colors for that product. Note that you would usually want to use a product ID and not a title for something like this, but since the ID isn’t easily accessible in this scenario (as demonstrated in the 1st example), we will use `title` for simplicity. You can of course combine the 1st example with this one to use the product ID instead of the title. Here is the source expression to arrive at this table: ```plaintext map(&let $product = @ in options[?name=='Color'].optionValues[*].{ title: $product.title, color: name}[], values(products))[] ``` - We are using the `map` function again so that we can process each product individually - The key trick in this example is to introduce a **variable** with the `let` expression, which allows us to store the current object `@` in the variable `$product` so that we can use it further on. - In JMESPath, there is no way to access a ‘parent’ object once we navigated down to e.g. the Color level in an object. We can store such an object in a variable though and thus have access to it at that level. - The `Color` filter has already been showcased in previous examples, the last noteworthy action here is the in-place creation of a new object which holds the product title and color name: `{ title: $product.title, color: name}` --- ## Tips for translation data sources (i18n) If you want your configurator to be available in multiple **languages**, we recommend to store **translations** in a data source. The text content that must be translated is often interwoven with regular product data. If you are free to choose the structure of your data sources, you can take our below recommendations as a starting point. In general, you should keep these points in mind when designing a data source: 1. **Ease of data maintenance**. How much effort is it to add new languages to the data source? 2. **Ease of configurator maintenance**. What must be done to the configurator to add new texts or languages? 3. **Performance**. Sending too much data at once can degrade the user experience. The below recommendations try to strike a balance between these general points. We use **Microsoft Excel** in our examples, but the underlying concepts can be transferred to similar file types. :::info The term **internationalization**, or i18ni18n is an abbreviation for "internationalization", where 18 is the number of letters between i and n. Internationalization is the process of localizing an application or document for a target audience that varies in culture, region, or language. This encompasses e.g. translating text into a target language. for short, is commonly used in documents that talk about localizing a configurator. ::: ### Static Text / UI Controls This is a recommendation for the translation of static text content, as found e.g. in the user controls of your configuraor (labels, button text, tooltips etc.) - Unless you have a huge amount of texts, we recommend to keep all translations in one file / worksheet. This makes it simpler to e.g. hand that file over to professional translators, who then have all translations available as reference and for additional context. - Create a **column** for each text that must be translated and a **row** for each language - This is the simplest way to access each text in the configurator. - All texts of a language will be contained in a single record, which can be connected to UI controls with Signals and Slots - Maintaining the data in the worksheet will be a bit cumbersome for longer texts, but the ease of access in the configurator offsets that downside. You can try to split the data among several worksheets to improve ease of maintenance. - The **first column**, the key column, should identify the language. - Create a single data source from this file (we'll call ours `StaticTextDataSource` in this example) - Now you can load just the texts of the desired language in a table component: ```hive from StaticTextDataSource select all filter .Language = CurrentLanguage ``` - The table will contain just one record, which you can obtain with the `GetFirst()` method. - Finally, connect the columns of the record with Signals and Slots to your UI controls. ### Product Data This is a recommendation for the translation of a **variable number** of items, which is usually the case for product data. (e.g. categories, products, parts, …) The [Static Text recommendation](/docs/asset-management/data-source-asset/tips-for-translation-data-sources#static-text--ui-controls) does not work here, as the number of columns would be variable, and you can't use Signals and Slots on columns that may or may not exist. We assume you have a worksheet that contains arbitrary product data, where each row is a "product" and each a column an attribute of that product. Some columns contain text that must be translated. An Excel file will work best here, because we'll create multiple data sources and want to be able to update them all at once. Here is what we suggest: - For each text column that must be translated, create one column per language. - example: if you have a `Name` column, create a `NameEN`, `NameDE`, `NameFR` etc. - Upload your Excel document as file asset. Create the following data sources based on that file asset. - Create a data source for the first language, e.g. `ProductEN` - open the import configuration of the data source and remove all columns that belong to different languages - rename all translation columns (of your data source) to have a neutral name, e.g. rename `NameEN` to `Name` - Copy the data source for the next language, e.g. `ProductDE` - change each translation column to point to a German column instead, e.g. `Name` should target the `NameDE` column - repeat this step for each language. In the end you should have one data source per language. - In your configurator, create a value component `ProductData` that points to the data source of the current language: ```hive // if you have many languages, you may want to use a record component instead if CurrentLanguage = "de" then ProductDE else ProductEN end ``` - Now you can create table components that target the `ProductData` component and thus get your product data in the current language: ```sql from ProductData select all filter … // you can use any filter you want ``` - When the product data changes, you can update all data sources at once by re-uploading your Excel document in the file asset. - you can also add additional worksheets for other product data to the same Excel document. That way you only have to upload one file to update all your data sources at once. - Adding a new language requires: - adding new columns in the worksheet (e.g. `NameXY`, `DescriptionXY`) - creating a new `ProductXY` data source for that language - selecting the new data source in the `ProductData` value component when the language is "XY" This pattern makes it easy to import updated data and to process it in the configurator, as all translated texts are stored alongside the general product data. However, maintaining the data in the Excel file can become a challenge if you have to support many languages. --- ## Getting started with assets Assets are files that you can use when building a configurator. Images are one of the most common assets. Another common kind is the data source, which contains tabular data from e.g. a MS Excel or CSV file. Typically, assets are imported from files you already have, such as images, documents or 3D models. However, some assets can also be created and edited directly in the asset editor, or imported automatically with [workflows](/docs/integrations-and-api/workflows/workflow-basics). When you upload a file, you may be asked what kind of asset you'd like to create with it. Each asset serves a particular purpose: images can be displayed, files downloaded and data source filtered. Other assets are used for 3D visualization and more will be added over time. ### Asset Bundles Assets are organized into **asset bundles**, which must be [**assigned**](/docs/asset-management/assign-asset-bundles-to-configurators) to a configurator to allow it to use those assets. You can further organize the assets in a bundle with **folders**. An asset bundle can be assigned to multiple configurators, thus ensuring that the same data is used in all of them. If you have multiple configurators that share a common data set, this is a great way to prevent inconsistencies and simplify maintenance. On the other hand, you can also assign multiple asset bundles to a single configurator. This may be useful when you have a lot of assets and want to organize them into separate bundles. ### Versioning Asset bundles are **versioned**. You control when the changes you make to an asset bundle go live. You can edit the assets of a bundle for as long as you want, without affecting any of your configurators. Only once you **publish** your changes will a new version of your asset bundle be created. You decide which configurators should use that new version, allowing you to update your configurators one by one – or all at once, the choice is yours! Of course, you can also **revert** to an **earlier version** of your asset bundle at any time. The **version history** of an asset bundle allows you to assign any past version to your configurator. Any changes you make to an asset bundle are automatically collected into a **draft**. You can either publish a draft or delete it, if you want to discard those changes. You can also **assign** a bundle draft to a configurator draft to test your changes. When you later publish the bundle draft, you can choose to have the configurator draft be published automatically as well. (also works the other way around!) :::tip Assign an asset bundle draft to your configurator while you are still working on it. That way, you can make changes to both with the least hassle and keep your version histories tidy. ::: ### Errors and Warnings Assets within a bundle can have errors or warnings to make you aware of any issues (e.g. missing materials, folders, assets). They are visualized directly in the tree view of the asset manager, simply click on the asset to get more details about them. Currently, we have the following types of errors and warnings: | Icon | Type | Name | Cause | | ------------------------------------------------------------ | ------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | | ![](img/getting-started-with-assets/image-1709632532014.png) | Warning | Missing material | A [babylon asset](/docs/asset-management/3d-and-material-assets/basic-workflow) is linked to a material which is not existing. | | ![](img/getting-started-with-assets/image-1709632532014.png) | Warning | Missing texture image | The given texture of a material asset is linked to an image which is not existing. | | ![](img/getting-started-with-assets/image-1709632568382.png) | Error | Missing linked folder | This [data source](/docs/asset-management/data-source-asset/basics) contains a column with a linked folder which is not existing. | | ![](img/getting-started-with-assets/image-1709632568382.png) | Error | Missing linked default asset | This data source contains a column with a linked asset (i.e. default value) which is not existing. | | ![](img/getting-started-with-assets/image-1709632568382.png) | Error | Invalid data source value | A value of a column in a data source cannot be read. | | ![](img/getting-started-with-assets/image-1709632568382.png) | Error | Invalid data source list value | An item of a list column in a data source cannot be read. | | ![](img/getting-started-with-assets/image-1709632568382.png) | Error | Empty data source list value | An item of a list column in a data source is empty. | :::warning Errors prevent you to publish an asset bundle (and potentially break a configurator) to improve stability and robustness. ::: --- ## PDF asset A PDF asset can be used to build PDFs based on HTML Liquid templates. [Liquid](https://shopify.github.io/liquid/) is an open-source template language created by Shopify. ### How it works You define the PDF structure with the template code, which is HTML + Liquid. Liquid makes it possible to access data from the context. The context is represented by a JSON object and can contain data from a configuration. The template code and the context together result in the **final HTML**, which will be converted to a PDF by choosen [technology](#the-tech-under-the-hood). ![image.png]() :::warning Textual data which is injected via the context is HTML encoded by default. This means if you want to inject `HTML` or `SVG` into the PDF asset, then you have to disable this encoding. For this, simply use the `raw` filter (e.g. `{{ svg | raw }}` or use a `{% raw %} ... {% endraw %}` section as described [here](https://shopify.dev/docs/api/liquid/tags#raw). ::: ### The tech under the hood We currently offer two technologies to generate a PDF from the generated HTML (can be changed in the PDF asset settings tab). #### Headless browser In this approach, a headless browser is started which loads the HTML and creates the PDF. **➕ Pros** - What you see is what you get - the output matches the preview with no surprises - Uses standard web CSS - no special syntax required - Fast rendering for simple to medium complexity documents - Easy to debug using browser developer tools - Supports modern web layouts and flexbox/grid **➖ Cons** - Limited support for advanced print-specific features - No CMYK color profile or PDF/A compliance support - Headers and footers have limitations (no page breaks, limited styling) - Resource URLs in header & footer must be base64 encoded (see warning below) - Less control over pagination and print layout :::warning Resource URLs (e.g. images, fonts, ...) in header & footer won't be resolved by Puppeteer. They need to be embedded via base64 encoding. For more information, see [`data_uri` filter](/docs/asset-management/pdf-asset#filters). ::: #### Prince XML With this approach, we use the [PrinceXML](https://www.princexml.com/doc/intro-userguide/) library to convert the generated HTML to a PDF. **➕ Pros** - Professional print features including CMYK color profiles, bleed margins, and crop marks - Advanced CSS Paged Media support for precise pagination control - PDF/A compliance for archival-quality documents - Superior handling of complex multi-page layouts - Extensive control over PDF metadata and document properties - Better for print-ready, production-quality output **➖ Cons** - Requires knowledge of print-specific CSS - The live preview might not accurately reflect the final PDF output - You must generate the PDF after making changes to see the actual result - Some CSS features may render differently than in browsers - Requires understanding of PrinceXML-specific syntax for advanced features ### How to create a PDF asset 1. Got to the asset manager 2. Click on the (+) button to create a new PDF asset ![new-pdf-asset.png]() 3. Select a name, folder, template and click on `Create asset`. ### Template language: Liquid If you want to inject flexible data into your HTML sections, then simply use the context in combination with several Liquid statements. The most common ones are described in this documentation, for more details visit the official [liquid documentation](https://shopify.github.io/liquid/basics/introduction/). #### Objects For example, if you define the following Context with a `company` object and a `name` property, then you can inject this data into your HTML section as described here. ![image.png]() **Context** ```json { "company": { "name": "Acme Solutions Ltd." } } ``` **Input** `{{ company.name }}` **Output** Acme Solutions Ltd. #### For-loops In addition, it's possible to iterate over a list of objects using for-loops. **Context** ```json { "lineItems": [ { "position": 1, "description": "Service A description", "price": "40,00 €" }, { "position": 2, "description": "Service B description", "price": "25,00 €" } ] } ``` **Input** ```html {% for item in lineItems %} {% endfor %}
Pos. Description Price
{{ item.position }} {{ item.description }} {{ item.price }}
``` **Output** | Pos. | Description | Price | | ---- | --------------------- | ------- | | 1 | Service A description | 40,00 € | | 2 | Service B description | 25,00 € | #### Filters ##### Built-in Liquid filters For the official filters, check out the [Liquid documentation](https://shopify.github.io/liquid/). ###### Math :::warning **Important:** When using `divided_by`, ensure you're using the correct data type. The `divided_by` filter produces a result of the same type as the divisor. If you divide by an integer, the result will be an integer. If you divide by a float (a number with a decimal), the result will be a float. ::: **Example:** ```liquid {% assign netTotalPriceFloat = netTotalPrice | times: 1.0 %} {% assign sumDiscountPercentage = 100.0 | divided_by: netTotalPriceFloat | times: sumDiscounts | round: 2 %} ``` ###### Date and time :::warning Unfortunately, the `date` filter works differently and the `time_zone` filter does not exist at all in the live preview. Please always check the resulting PDF to verify the correct time zone is applied. ::: If you have a string-based date, then you can use the `date` filter to format it. For example: ```liquid {{ "2024-06-15 13:41:36 +02:00" | date: "%d.%m.%Y" }} // Result: "15.06.2024" ``` UNIX timestamps represent a point in time in UTC. To display a UNIX timestamp in a specific time zone, use the `time_zone` filter and then format the result with the `date` filter. :::info **Important:** To ensure a correct time zone, please always provide it directly with the string as shown in the above example or use a UNIX timestamp with a `time_zone` filter. The recommended pattern is: **UNIX timestamp (UTC) → time_zone → date** ::: For example: `{{ Timestamp | time_zone: 'Europe/Vienna' | date: '%Y-%m-%d %H:%M:%S' }}` The Timestamp with `1784288496` results in `2026-07-17 13:41:36`, because Europe/Vienna (CEST) is +02:00 ahead of UTC. :::warning The date filter determines how the timestamp is displayed. When **no format is specified**, the result is displayed using the default **ISO 8601 format** which is always shown as UTC time, regardless of the time zone you have specified. ::: For example: `{{ 1784288496 | time_zone: 'Europe/Vienna' | date }}` produces an `ISO 8601` value: `2026-07-17 11:41:36Z` To display the date and time in a specific format with a given time zone, then **always** provide the format explicitly: `{{ 1784288496 | time_zone: 'Europe/Vienna' | date: '%Y-%m-%d %H:%M:%S' }}` which results in: `2026-07-17 13:41:36` ##### Combeenation filters We extended the Liquid template language with custom filters for Combeenation-specific functionality. The following filter can be used in combination with Assets: - `url` ... to retrieve the URL of an image, text or file asset To directly embed images or fonts into the header/footer: - `data_uri` ... to retrieve the base64 encoded data of a URL We support the following media types: - Images: Png, Gif, Jpg, Svg, Bmp, Webp, Ktx2 - Fonts: Ttf, Woff, Woff2 For a PDF (e.g. quote) which contains several prices, the following custom filter can be used: - `money` ... to convert any number to a formatted price The money filter accepts several parameters which controls the formatted output. ```liquid {{ discount.adjustment.price | money:"de-de", "EUR", "symbol" }} // 4.711,02 € ``` Plain text - `"de-de"` ... the locale which affects the formatting of the resulting price - `"EUR"` ... optional currency code according to [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) - `"symbol"` ... is an optional parameter to decide if a currency symbol or code should be used, possible values: `"symbol"`, `"code"`; default: `"code"` It's of course also possible to use context parameters, for example `currency` from the context object instead of `"EUR"`. ##### Asset access Two recommended ways to access the image asset in your template code: - Direct access (blue arrow): Use the full asset path as a string (e.g. `folderName.assetName`.) before the above mentioned filter - Context-based injection (green arrow): Pass the full asset path via the context. This is preferred when the asset is dynamic and selected during the configuration process ![image.png]() If the given asset is not valid or is missing, then a corresponding asset error is shown. **Asset** There must be one asset named `logo`. **Context** ```json { "company": { "logo": "MyFolder.Logo" } } ``` **Input** ```html ``` **HTML Output** ```html ``` **Output** ![](https://cbnpeuwstassets.blob.core.windows.net/public/COMBEENATION/0Dxez2x2M09.png) #### Header, footer, page numbers (Headless browser) :::info These CSS classes only work in the header & footer! They have no effect inside the body. ::: Specific CSS classes can be used to inject auto-generated values into the PDF. To use them, simply create an HTML tag with one of the CSS classes listed below. ###### Available CSS classes - `date` - Formatted print date - `title` - Document title - `url` - Document location - `pageNumber` - Current page number - `totalPages` - Total pages in the document ###### Example usage ```html of ``` See also [Puppeteer PDFOptions.headerTemplate](https://pptr.dev/api/puppeteer.pdfoptions) #### Header, footer, page numbers, watermarks (Prince XML) For detailed guides on using Prince XML features, see: - [Headers & footers](https://www.europdf.eu/features/pdf-headers-footers/) - [Page numbers](https://www.europdf.eu/features/pdf-page-numbers/) - [Watermarks](https://www.europdf.eu/features/pdf-watermarks/) ### How to use a pdf asset A PDF asset can finally be used to create a PDF based on any data during a configuration. The required data can be dynamically passed via the `Context` object. As a result, it's possible to create a PDF using Custom Code. Basically, the PDF is created via the PDF asset and a dynamically passed Context object. This generated PDF is saved as a "configuration file" and be used as described [here](/docs/configurator-logic/file-upload-and-handling). #### Example "Create and merge PDFs" :::info Function can be used if all assets are in the same bundle and you just want to create a PDF and optionally merge it with others ::: ```typescript // creating the context const myContext = { company: { logo: 'MyFolder.MyFinalLogo' } }; const urlResult = await CfgnFiles.createPdfFromAssets( 'MyBundle', [ { type: CreatePdfTaskTypes.Asset, assetPath: 'Pdf1' }, { type: CreatePdfTaskTypes.PdfAsset, assetPath: 'quotePdfAsset', context: myContext }, { type: CreatePdfTaskTypes.Asset, assetPath: 'Pdf2' } ], 'Output', 'My cool PDF' ); ``` #### Example "Create PDF with dynamic convert document endpoint and merge it with static pdfs" Similar to the previous one, but the dynamic `convertDocument` endpoint supports all types of convert tasks. ```typescript // creating the context const myContext = { company: { logo: 'MyFolder.MyFinalLogo' } }; // Example "Creating the PDF based on the context and a PDF asset" const urlResult = await CfgnFiles.convertDocument([ { type: DocumentConvertTaskTypes.PdfAsset, name: 'HtmlToPdfTask', bundleAliasName: 'MyBundle', assetPath: 'MyPdfAsset', context: myContext }, { type: CreatePdfTaskTypes.Asset, name: 'Pdf1Task', bundleAliasName: 'MyBundle', assetPath: 'Pdf1' }, { type: CreatePdfTaskTypes.Asset, name: 'Pdf2Task', bundleAliasName: 'MyBundle', assetPath: 'Pdf2' }, { type: CreatePdfTaskTypes.Merge, name: 'MergeTask', inputTaskNames: ['Pdf1Task', 'HtmlToPdfTask', 'Pdf2Task'] }, { type: DocumentConvertTaskTypes.OutputToCfgnFile, name: 'OutputPdfTask', inputTaskName: 'MergeTask', cfgnFileName: 'Output', cfgnFileExtension: 'pdf', cfgnFileDisplayName: 'My cool PDF' } ]); ``` #### Example "If you only need to create a temporary PDF" ```typescript const urlResult = await createTempPdfFromAssets('MyBundle', [ { type: CreatePdfTaskTypes.PdfAsset, assetPath: 'MyPdfAsset', context: myContext }, { type: CreatePdfTaskTypes.Asset, assetPath: 'Pdf1' } ], 'My cool PDF'); ``` --- ## Text asset A text asset can contain large amounts of textual data. You can work with its text content in a configurator (unlike the contents of a file asset). As such, it's useful as a container for e.g. HTML print templates or SVG images (if you want to process them as text). ### How to create a text asset You have two options to create a text asset: you can either create an empty text asset or upload a text file. #### Create empty text asset 1. Click on the (+) button to create a new empty text asset ![empty-text.png](https://docs.combeenation.com/assets/images/empty-text-830e23e2f1a0dccf0ea0fa848b10dc16.png) 2. Select a name, folder, format and click on `Create asset`. ![empty-text-dialog.png]() #### Upload existing text file 1. Click on `Upload Files` to upload one or more text files. ![upload-text.png](https://docs.combeenation.com/assets/images/upload-text-46f0a1a1d5f26fabf6b3967fae2a3ccc.png) 2. Select the `Text` asset type. Keep in mind, a text file can also be uploaded as file asset. ![upload-text-dialog.png](https://docs.combeenation.com/assets/images/upload-text-dialog-cf03816b74a8f9ece698c0df728447e2.png) #### Change text content After creating or uploading a text asset, it is possible to change its content. Similar to other assets, you can also rename, copy, move and delete a text asset. ![image.png]() ### How to use a text asset A text asset can be used in the configurator. For this, initially assign the asset bundle which contains your text asset to your configurator as described [here](/docs/asset-management/assign-asset-bundles-to-configurators). :::info Keep in mind that retrieving text content requires time. This means, loading a large number of texts can slow down your configurator – so only use what you really need. ::: #### URL You can get the URL of the text content in the configurator with the following hive rule: ```hive MyBundle.MyText.url ``` #### Content Retrieve the content of a text asset in the configurator with the following hive rule: ```hive MyBundle.MyText.getText() ``` If your asset contains valid JSON, you may use the `getJson()` method instead, which returns a [JSON value](../configurator-logic/hive-types/json-type.md): ```hive MyBundle.MyText.getJson() ``` ### Supported formats Currently, text assets support the file formats listed below. The asset editor provides appropriate syntax highlighting and error notifications for them. If you create an empty text asset, the format must be explicitly defined. If you upload a text file, the format is detected based on its file extension. Note that the format cannot be changed in a subsequent step. For this, please re-upload the file with the desired file extension or create an empty text asset. :::info The format has no significant impact for the usage in the configurator. The content of a text assest will always be a text in hive. ::: | Format | Maximum size | | ------ | ------------ | | txt | 512 KB | | svg | 512 KB | | html | 512 KB | | xml | 512 KB | | json | 512 KB | --- ## Insights We collect insights data for each [configuration](/docs/fundamentals/configurations) a user creates when they open the configurator ### Inspect insights data Go to the dashboard, select your desired configuration and click "Open" on the configurations area. You can filter your insights and view details about your insights by clicking on the respective insights entry. When you select a value for a filter property, conditions are connected using AND logic, meaning all conditions must be met. If you select multiple values within a single filter property, these values are connected using OR logic, meaning any of the selected values can apply. ##### Example This example shows all configuration insights, which have been created in the last 7 days **AND** were the type is Configuration **OR** Finish. ![]() ### Data collection #### Generic information - Creation date - User who created the configuration (if it is a logged in Combeenation user) - for more info see [personal configurations](/docs/configuration-insights/personal-configurations) - Type of the configuration - On which version the configuration is based on - Preset id #### Information about the configuration - When a [configuration checkout](/docs/integrations-and-api/checkout) will be performed, we store: - Checkout type - Used shop name - Price (if the price property of the checkout component is set) - Custom insights fields Custom fields allow you to track specific values that are important to your use case. You can also use them in filters to find all configurations with a specific custom field value. #### User and device metadata We are also collecting device information from the client: - Height & width of the viewed device (correctly speaking the iframe in which the configurator is embedded) - Browser - Mobile detection - Country & City - Based on the IP address of the user, we try to roughly locate the country and city of the user. This is not 100% accurate. - Coming soon: Deactivate this location feature, if you don’t want to collect location information about your users --- ## Personal Configurations A personal configuration belongs to a specific user on the Combeenation platform and can only be opened and edited by that user. The user must be a member of the Company that owns the configurator. Users can organize their personal configurations on the Configurator Insights page and use them e.g. as templates or as a living document to assist in the sales process. Information about the current user is available in Hive and can be used to augment the configurator to e.g. show sensitive data or unlock advanced features (for more details see the [User](/docs/configurator-logic/user-variable) and [Custom Fields](/docs/fundamentals/custom-fields#user-fields) docs). ### Creating a personal configuration A personal configuration is created automatically when a configurator is opened by a user that is already signed into the Combeenation platform. If a user is currently not signed in, the configuration will be anonymous i.e. not tied to any particular user. This is the default for configurators that are made available to the general public on the internet. Note that anonymous configurations are still protected by an [authentication token](/docs/configurator-logic/configuration-variable#the-authorization-token-authtoken) so that even if a copy of such a configuration is shared, the original cannot be edited without the authentication token. Users can log in after they opened a configurator in order to create a personal configuration. In this case, a copy of the original configuration will be created and attributed to the signed-in user, while the original one remains anonymous. ### Add Combeenation Login to your configurator By using Combeenation Login, you can provide a convenient and secure way for your users to sign into a configurator to unlock advanced features or gain access to sensitive data. Each user must have an account on the Combeenation platform. Follow these 3 simple steps to use Combeenation Login: - create a `Button` control - add a Signal & Slots connection to your button: set the signal to `Clicked` and for the slot choose the `Configurator` control and then select the `User login` slot - finally, to hide the button when a user is already signed in, select the `Visible` property, click the “Enable Hive” button and paste the following rule into it: ```hive If user.isLoggedIn then Visibility.PluggedOut else Visibility.Show end ``` That’s it! Now when a user clicks the button, they are automatically taken to the Combeenation Login and afterward redirected back to the configurator. If the login has been successful, a personal configuration will be created for the user. --- ## Components overview There are five different component types: - Value component - Record component - Resource component - Graphic component - Table component To add a component, use the `+` at the top. To add a group you first have to add a component and during this process, you are able to add the component to a new group. There are two more components, that are added to the configurator automatically and cannot be deleted: the design component and the checkout component. These two can also not be renamed nor can you place them in another group. As the names suggest in the checkout component you can create the different checkouts you need and in the design component, you can create the UI design of the configurator. ### Properties | Name | Description | | ------- | ----------------------------- | | name | name of the component | | group | group of the compoet | | records | records of a record component | #### Group and name Each component has the following properties: `group` and `name`. To access the value of these properties you use the `#` operator (escape operator), the name of the component and the name of the property. Example: - \#TestRecord.Group - name of the group which contains the record component “TestRecord" - in this case "TestGroup" - \#TestRecord.Name - contains the name of the component - in this case "TestRecord" #### Records To access any property of a record component, you have to use the escape operator. For example, to access the Records property you have to write `#testComponnet.Records`. This will show the whole record table. On the plus side, `testComponent.Name` will always refer to the `"Name"` column (if there is one), meaning you don't have to write `testComponent.Value.Name`. --- ## Graphic component The graphic component allows you to create dynamic visualizations in the Scalable Vector Graphics (SVG) format and export them as JPG or PNG images. ### Properties | Name | Description | | -------------- | ------------------------------------------------------ | | width | the width of your graphic | | height | the height of your graphic | | archive | controls if and how the graphic will be exported | | url | the URL to the exported graphic (depends on `archive`) | | value | gets the generated SVG of the graphic | | visuals | a map of the component's composed visuals | | dynamicVisuals | a map of the component's dynamic visuals | ### Width and Height Defines the width and height of your graphic. You can give it a fixed size or make it dependent on a rule or even an Input. ### Archive and Url Archive determines whether the graphic should be saved to a file and which format and other settings to use. To accomplish this you must provide a graphic archive value, for example: ```hive GraphicArchive.Svg ``` This will save the graphic as an SVG image. The name of the GraphicComponent will be used as file name. The Url property returns the URL of the saved image. If the Archive rule is empty or set to GraphicArchive.None, the Url will return an empty Text "". ### Value Returns the raw SVG generated by the graphic component. It is not recommended to modify the raw SVG directly, but if you have to hand it to an external system (like a shopping cart) you can convert it to Text with the toText() function to be able to put it in a XML or JSON document: ```hive DefaultView.Value.toText() ``` ### Visuals and dynamic visuals These two properties are maps that contain all the composed visuals / dynamic visuals of the graphic component. You can use them for example to obtain a visual by name (see the map documentation for further details): ```hive // get the visual with the name "myVisual", or Empty if it doesn't exist Defaultview.Visuals.Get("myVisual") ``` ### The GraphicArchive type To use the Archive property of a graphic component you must provide a GraphicArchive value. This section introduces you to the various archive settings that are currently available. #### None No image will be generated and saved. It is recommended to use this setting when you don't need an exported image to reduce resource consumption. ```hive GraphicArchive.None ``` #### Svg An SVG image will be saved. If the SVG format works for your use case, you should consider using this option. Pure SVG images can be enlarged without loss of quality and exporting in this format is also faster. ```hive GraphicArchive.Svg ``` #### Png(width, height) A PNG image with the specified width and height will be saved. If you can't use SVG and need transparency in your image, you should use this option. ```hive // saves a 400 pixel wide and 400 pixel high PNG image GraphicArchive.Png(400, 400) ``` #### Jpg(width, height, quality) A JPG image with the specified width, height and quality will be saved. The quality setting accepts a number between 1 and 100. The higher it is, the better your image will look – but it will also take up more space and thus take longer to load. The width and height also play a role in the image's size and appearance (if you use raster images in your graphic component, they may look blurry if they are enlarged). ```hive // saves a 600 pixel wide and 300 pixel high JPG image at 60% quality GraphicArchive.Jpg(600, 300, 60) ``` :::warning The larger a JPG or PNG image is, the longer it will take to generate! Larger images also take longer to download or send and take up more space. You should make your image just large enough to be suitable for your use case. For JPG images, the quality also affects the file size. ::: #### Properties When you create a graphic archive in any of the ways shown above or work with the Archive property of a graphic component, you can access its properties: **Name** **Description** | Name | Description | | ---- | ----------------------------------- | | type | gets the type of the GraphicArchive | #### Type Gets the type of the graphic archive. You can use this information for example to figure out which kind of graphic archive is being used by a graphic component. You must compare this value with a graphic archive type, which has the properties None, Svg, Png and Jpg as you would expect. ```hive // do something when a PNG image is used if DefaultView.Archive.Type = GraphicArchiveType.PNG then ``` --- ## Record component A record component holds whole data sets in a tabular form. The structure of a record component can be defined by the user, i.e. any number/names of rows or columns can be added. Typical application cases for a record component are: - mapping of the possible characteristics of a product - color tables - everywhere where the user can make a selection from several possibilities A record component consists of so-called records, where one record represents a row in the record component. There is always one selected record, which is defined by the selected key. Therefore each record must have a unique key. The first column in a record component always serves as a key column and, as already mentioned, must be unique. The column does not necessarily have to be called "key" and the type does not necessarily have to be of type number - but in most cases, it makes sense to keep this scheme. The value of a record component is the selected record, i.e. a row including all column information. ### Properties | Name | Description | | ----------- | ---------------------------------------- | | Records | the table of records | | selectedKey | the key of the reocrd to choose as value | | value | contains the selected record | ### Records The table of records, from which one can be chosen as the Value. A table looks like this: ```hive { { Key=Number, Name=Text, Price=Number } { 1, "Apples", 10 } { 2, "Oranges", 20 } { 3, "Bananas", 30 } } ``` The first row of this table determines which columns it has, all subsequent rows contain values for these columns. A column definition must always consist of a name (like "Key" or "Price") and a type (like "Number" or "Text"). Note that the first column in a table is special: it is used to look up a record with the get function. While generally referred to as the "Key" column, you can give it any name you want. Because it is used to look up records, it must not contain a value that can change over time. In fact, you can only use literal values in a "Key" column, such as Numbers or Text. If you try to use a different value, you will get an error. For a better overview keep your table well structured. You can use the shortcut `strg+b` to automatically format your table. The examples below assume that there is a record component with the name "fruits" that looks like the one above. To access the Records of a record component in a different component, you need to use the escape operator: ```hive // Note the # at the beginning of the next line // Without it, you can't access the 'Records' property #fruits.Records.GetFirst() // Without the #, 'fruits' refers to the selected record // You can access the columns of the selected record directly fruits.Price ``` ##### Tipp You have a product with a top color and a bottom color. Both have the same selection of colors. You create the record components for both. You fill the first record component with your color table. In the second record component, instead of writing the whole table again, you just write `#TopColor.Records` and you have the same color table in there. If a new color needs to be added, you only have to add it in the first record component and not in both. This way the data is more maintainable in the editor. ### Selected Key The key of the record that should be selected as the Value. It is always of the same type as the Key column. In many cases, you will want to use a rule like the following (assuming the Key is of type number) ```hive Input or 1 ``` You can connect controls like the dataview to a record component, which will automatically write the Key of a selected item to the selected key property. Note that by itself, the selected key property has no effect on which record will be chosen – it's not even necessary to use it at all in some situations. In order for it to have an effect, you need to use it in the Value rule, which we will look at next. ### Value Contains the selected record. The default rule for this property looks like the following: ```hive Records.Get(SelectedKey) or Records.GetFirst() ``` This rule does two things: it first uses the get function to look up the record that has the same key as the selected key. If no such record can be found, it uses the getFirst function to return the first record from the records property. ### The Table Type The value produced by the Records property is called a Table. #### Functions | Name | Description | | ---------------- | ------------------------------------------------------------------------------ | | get(key) | gets the record with the specified key | | getFirst | gets the first record of the table | | add(table) | produces the union of two tables (unique rows from both tables) | | intersect(table) | produces the intersection of two tables (only rows that appear in both tables) | ##### get(key) Returns the record with the specified key, or `empty` if none of the records has that key. | Example | Result | | ------------------------ | -------------------- | | `#fruits.Records.get(1)` | `[1, "Apples", 10]` | | `#fruits.Record.get(3)` | `[3, "Bananas", 30]` | | `#fruits.Record.get(9)` | `Empty` | --- ##### getFirst Returns the first record of a table, or `empty` if there are no records. ```hive #fruits.Records.getFirst() // returns [ 1,"Apples", 10] ``` --- ##### add(table) Produces the union of two tables: returns a table that contains all records from both tables. ```hive #fruits.Records.add(#vegetables.Records) ``` A few things to keep in mind when using `add`: - Both tables must be of the same type, which means they must have the same columns - If both tables contain a record with the same `key`, only the first one will be taken. The "first" record is that of the table on which you call the `add` function – in the example above, that would be `#fruits.Records`. ##### intersect(table) Produces the intersection of two tables: returns a table that contains only records that appear in both tables. ```hive #fruits.Records.intersect(#vegetables.Records) ``` A few things to keep in mind when using intersect: - Records are identified by their key (the first column of the record), the other columns do not affect which records are picked - All records that make it into the result are taken from the first table (_fruits_ in the example above). - If one of the two tables is empty, the resulting table will also be empty - The second table (_vegetables_ in the example above) only has to contain the key column, all other columns will be ignored - it can be of the same type as the first table, but it doesn't have to be --- ## Resource component To access resources in the configurator, it is also necessary to store them in a component. There are the following 4 resource types that can be uploaded: - Image - e.g. product images - JavaScript - Custom code which should be executed additionally when opening the configurator. - Document - e.g. PDF - 3D Assets - 3D models or environment files If a configurator offers the possibility to individualize the product by uploading a custom image, it is necessary that the option "User can upload his own resources" is activated. User uploads are stored in a separate resource component just like normal product images. ### Properties | Name | Description | | ------ | --------------------------------------------------------------------------------------- | | url | url to the resource | | width | width of the resource (available for image resource) | | height | height of the resource (available for image resource) | | type | datatype of the resource (e.g png, available for image, document and 3D asset resource) | | size | size of the resource | | dpix | resolution x (available for image resource) | | dpiy | resolution y (available for image resource) | ### Value When referring to a resource, in another value component for example, you get all the properties for the resource. The example below represents an image resource. ```hive { "Url": "https://combeepublic.blob.core.windows.net/resources/0C6fxtWT3dA.png", "Width": 3040, "Height": 2943, "Type": "Png", "Size": 135915, "DpiX": 118.11, "DpiY": 118.11 } ``` --- ## Result visualization The result panel automatically converts the output data in a human-readable format. ### Bottom toolbar buttons - Toggle raw data: In case the original data is needed (e.g. for using the result as component input elsewhere) you can activate the raw data toggle. - Expand / Collapse all: Expand or collapse all inner tables recursively. - Copy result: Copies the whole content into the clipboard. Is only available when raw data is displayed. ### Record data The result visualization for the record table data renders columns based on it's type. You get several expand and collapse features that enable easy navigation through big data sets. #### Column rendering - Nested data: (Expandable) inner table - Logic datatype is displayed as checked/unchecked box - Image datatype directly loads the image url. If the overall size is too large for efficient loading, it will show only an icon. You can hover over the image for a larger preview. #### Pan / Navigate The right mouse button can be used to pan inside larger tables. Holding the right mouse button will hide the cursor and you can move in any direction until it's released. #### Minimize / Maximize columns Every column can be minimized by moving over it's header and clicking the `-` icon. #### Collapse all columns except the clicked one It's possible to focus on one column by clicking on the header with any modifier key held down (ctrl, alt, shift). To reverse the action and expand all columns again, click on the focused column again with any modifier key. #### Expand / Collapse nested data When a component contains nested data (e.g. record inside record-column) it's collapsed by default. The nested data can be expanded individually per row and nesting. ### JSON Plain text which is parsable as JSON gets automatically formatted. --- ## Table component A Table component is used to load tabular data from a [data source](/docs/asset-management/data-source-asset/basics) as well as filtering and modifying that data. This data is organized in a table of records. A record corresponds to one row of the data source and consists of multiple columns, where each column can be a number, text or other value. A table is a grouping of records and provides functions to access a specific record. You can use a table to: - fill a ComboBox or DataView control, to allow users to see and choose one record - access individual records and use its values in your product logic ### Properties A Table component has 2 properties: Query and Value. - The Query property is where you write a query to load data from a data source. - The Value property holds the resulting table. It's used automatically when you refer to the table component in other rules, you typically don't need to access it directly. ### Using a table component A typical use case is to select a particular record from a table; this could e.g. be the record that a user selected in a ComboBox or DataView control. Let's assume we have a table component with the name `parts` and that it has 3 columns `partNumber`, `name` and `price`. To select one part from this table, create a value component with the name `part` and enter this code: ```hive parts.Get(Input or 0) or parts.GetFirst() ``` You also have to change the **Input type** of the value component to `Number`. When you now type a part number into the **Input** field of your value component, you should see a part record in the result panel. Try typing different part numbers into the Input field to view different parts! This value component can now be connected to UI controls to hold a part selected from the parts table. Let's break down what the code above does: 1. We try to look up a part with a particular number with `parts.Get(…)` 2. As part number, we want to use `Input`, which can be connected to a UI control to automatically update - To prevent errors, we supply a default value for the Input with `Input or 0`. - Instead of `0` you could use the number of a part you want to be selected by default. 3. In case the `Input` contains a part number that cannot be found in the table, we use the first record of the table as fallback - We get the first record of the table with `parts.GetFirst()` - Now our component will always select a part record, no matter what we type into the Input box! ### Queries To load data from a data source, we can use a so-called query in our Hive code, written into the Query property. Let's assume our data source is called `BikeParts`, then the simplest query we could write is: ```hive from BikeParts select all ``` This will load all records from the data source and include all its columns. Queries support filtering, sorting, limiting and reshaping (projecting) the data they load – see the [Hive Queries](/docs/configurator-logic/hive-queries) page for the full syntax and examples. --- ## Value component Different data types can be stored in a value component. The most simple of these are numbers, text, and logic. These can be simply selected in the dropdown menu on the top right. Also, more complex data types like lists or the values of other components can be stored here. A list, for example, cannot be selected in the dropdown menu. In order to use the list data type for your value component you can write `List` into the input type textbox. Instead of number you can use the needed datatype. When referring to a resource, the type of the resource defines the data type of your value component e.g. image or document. While referring to a record component, the input looks like this: `Record<{Key=Number,Name=Text}>` - every existing column in the record is mentioned here. ### Parts of a value component A value component always consists of three areas: input type, rule, and result. The input is a value that can be used to calculate or which is saved in the value component e.g. a user input from a text box. The input data type is the one that is defined for the component as mentioned before The rule contains calculations, rules, or mappings to other components. The syntax which is used for these calculations is Hive. A multitude of operations (addition, multiplication, ...) can be performed in components using Hive. It is possible to access a value of another component and perform cross-component calculations. As in other programming languages, an [if / else condition](/docs/configurator-logic/the-if-expression) can be mapped in Hive. Hive also provides some auxiliary functions, e.g. to round a number commercially. The result area shows the calculated result of the rule. When a value is needed to be available after a configuration is finished, you can check the "archive" checkbox on the right side of a value component. Now this value will be shown in the results page for each configuration in the configurations menu. --- ## Configuration variable Every time one of your customers uses your configurator or you try out a draft of it, a configuration is created. This configuration is used to automatically save the user's progress, it can be copied or edited at a later time, shared on social networks or put in a shopping cart. You can interact with the currently active configuration via the configuration variable. ### Properties | name | description | | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | id | the unique ID of this configuration | | [authToken](/docs/configurator-logic/configuration-variable#the-authorization-token-authtoken) | the authorization token for this configuration | | [isFinished](/docs/configurator-logic/configuration-variable#isfinished) | will be `false` for new configurations and `true` for configurations that have already been finished | | [isViewMode](/docs/configurator-logic/configuration-variable#isviewmode) | will be `true` when the configuration is opened in a read-only mode | | hasQuote | will be `true` if a quote is linked with this configuration, otherwise `false` | | editUrl | a URL with which this configuration can be edited | | copyUrl | a URL that will create a copy of this configuration | | viewUrl | a URL for viewing this configuration in a read-only [view mode](/docs/configurator-logic/configuration-variable#isviewmode) | | insightsUrl | a URL to the insights of this configuration | | insightsOverviewUrl | a URL to the insights overview page of this configurator | | leadUrl | a URL to an already created lead of this configuration, otherwise empty | | leadOverviewUrl | a URL to the leads overview page | | quoteUrl | a URL to an already created quote of this configuration, otherwise empty | | quoteOverviewUrl | a URL to the quotes overview page, includes the correct quote hub name if a quote has already been created | ### Functions | name | description | | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | [getParameter(name)](/docs/configurator-logic/configuration-variable#getparametername) | returns the value of a URL parameter | | getImage(name) | returns the Image which was uploaded under the given name (or `Empty` if nothing was uploaded yet) | | getFile(name) | returns the File which was uploaded under the given name (or `Empty` if nothing was uploaded yet) | ### The Authorization Token (AuthToken) Every configuration is protected with an authorization token. Only those that can provide this secret text are allowed to edit the configuration. The AuthToken is part of the EditUrl of a configuration. If you want to enable your users to continue a configuration at a later time or after they have put it into a shopping cart, we recommend you use the EditUrl of the configuration. It should be pretty rare that you have to directly work with the AuthToken yourself. ### isFinished Once a configuration has been put into a shopping cart or, more generally speaking, has been completed (or "finished"), you may want it to behave slightly differently when edited again. For example, you may want to change the text of a button from "order now!" to "save changes" when a customer opens a configuration from his shopping cart. You can use the configuration.isFinished property to this end, as it will return true when the current configuration has already been completed at some point in the past. ### isViewMode Configurations can be opened in a read-only mode that allows users to view the configuration, but not modify it in any way. Component values can be changed in this mode, but the changes will not be saved. Certain actions like finish, share or uploading files are not allowed in view mode. If attempted, a warning dialog will be displayed that provides the option to create a copy of the configuration instead, which can then be edited. Configurations on the [Insights](/docs/configuration-insights/configuration-insights-), [Leads](/docs/quote-management/how-to-work-with-quotes#leads) or [Quotes](/docs/quote-management/getting-started-with-quotes) pages will by default open in view mode. Within a configurator, you can obtain the URL for viewing a configuration with `configuration.viewUrl` You may rely on `isViewMode` as an indicator to display the configurator in a read-only state, e.g. hiding certain controls or optimizing the UI for viewing instead of editing. If you would like to provide users with a way to continue editing, you can [restart the configuration](/docs/configurator-ui-ux/controls/configurator-control#restart-configuration) to create an editable copy of it. ### getParameter(name) When you embed your configurator in your website, you can add URL parameters to the embed URL. These parameters can then be used to alter how the configurator looks or works. For example, you could pass the SKU of a product, the current language of the website or shop-system specific information to your configurator. In your configurator, you can obtain the value of an URL parameter with the getParameter function. This function will either return a text value or empty if the parameter has not been used: ```hive // pass language by parameter, use "en" when parameter is not used configuration.getParameter("language") or "en" // pass ID of a shopping cart by parameter // be sure to provide a fallback value if the parameter is not used (e.g. "") // ToNumber() might also return Empty if the parameter is not a number (configuration.getParameter("cartId") or "").toNumber() or 1 ``` --- ## Configurator & presets variable Everything you create in the configurator editor is either directly or indirectly connected to your configurator. With the configurator variable you can access all these things and additional information about your configurator. ### Properties | name | description | | ----------- | ---------------------------------------------------------------------------------------------------------- | | isDraft | will be `true` inside a draft preview and `false` in a published or staged configurator or preview thereof | | isPreview | `true` in a configurator preview, `false` anywhere else | | isStaging | `true` in a staged configurator, `false` anywhere else | | isPublished | `true` in a published configurator, `false` anywhere else | | startUrl | the URL for starting a new configuration based on the default preset | | components | a map of all the components you have created | | owner | the owner of this configurator, can represent the company or a distributor | | company | the company that owns this configurator, regardless of the current owner | You can access these properties like so: ```hive configurator.isPublished ``` `owner` and `company` both hold tenant data (see the properties table below), but they are not the same thing: the `owner` is the company or distributor that the configurator is currently opened as, while `company` always refers to the company, even when a distributor is the current owner. Once you have obtained the owner of the configurator, you can use its properties (e.g. for applying custom styles or similar). But keep in mind, the owner depends on the tenant (company or distributor) where the configurator is opened and is not connected with the currently active user. For this, please utilize the [user](/docs/configurator-logic/user-variable) variable. | name | description | | ------------- | ------------------------------------------------------------------------------- | | name | the reference name of the company or distributor | | displayName | the display name of the company or distributor | | isCompany | `true` if the owner is a company, otherwise `false` | | isDistributor | `true` if the owner is a distributor, otherwise `false` | | logo | the `FileUrl` of the company or distributor logo, `Empty` if no logo is defined | You can access these properties like so: ```hive configurator.owner.name ``` ```hive configurator.owner.logo?.url ``` ### Methods | name | description | | ----------------- | --------------------------------------------------------------------------------------------- | | getUsers() | lazily loads all users of the company, returned as a map of users keyed by user `id` | | getDistributors() | lazily loads all distributors of the company, returned as a map of distributors keyed by name | ```hive configurator.getUsers().get("some-user-id") ``` The users and distributors returned by these methods have the same properties as the [user](/docs/configurator-logic/user-variable) variable and `configurator.owner`/`company`, with two exceptions: a user returned by `getUsers()` doesn't have `isLoggedIn`, and a distributor returned by `getDistributors()` doesn't have `isCompany`/`isDistributor`. ### Accessing components In addition to the properties above, you can also access any of your components with the configurator variable. For example, configurator.Price would refer to a "price" component. Of course, you can also just write price to work with that component – usually. You may end up in a situation where something else is also called "price" and simply writing price will return that thing. This can happen for example in a graphic component, when you give one of your visuals the name "price". Inside that component, "price" will then refer to your visual and not the price component. To fix that problem, you can write configurator. Price to get to your component. :::warning Super special case: if you end up in a situation where something else is called configurator, the solution above won't work. You can't use the configurator variable because that other thing is also called "configurator". ::: The best way to avoid this problem is not to call anything "configurator" – but in case you have to, there's a special keyword to help you out: @top. This rarely used keyword is always reachable and enables you to access things "at the top", like the configurator variable. For example, @top.configurator.Price will always refer to the "price" component, no matter what. ### Presets You can work with the presets that you have created for your configurator by using the presets variable. Similar to configurator, you can access any preset by name by writing presets.MyPreset (where "MyPreset" is the name you've given your preset). However, in this case the presets variable is mandatory – "MyPreset" alone will not return your preset. Additionally, the presets variable has these built-in properties: | name | description | | ------- | -------------------------------------------------------------- | | current | returns the preset on which the current configuration is based | | default | returns the default preset of your configurator | | all | returns a map of all the presets you have created | Once you have obtained a preset, you can use its properties: | name | description | | -------- | ------------------------------------------------------------------- | | id | the unique identifier of this preset | | startUrl | the URL for starting a brand-new configuration based on this preset | ### Example To get the URL to start a new configuration based on the same preset as the current configuration: ```hive presets.Current.StartUrl ``` :::info Just like configurator, the presets variable also lives "at the top" – meaning you can use @top.presets to always reach it, even if something else is called "presets". ::: --- ## Editor basics ### Components and groups On the left side of your configurator, you will find a list of components and groups. Each component is part of a group. For a better overview of your configurator, split your components into useful groups. Always use meaningful and unique names for your components and their groups. Components are basically like variables in programming - they contain values and logic. The values can be of different types and can also contain resources such as images or 3D files. There are also different types of components for all the different use cases. Components are the heart of a configurator since all data and logic are maintained there. :::info The user interface for the configurator is also built within one specific component - the design component. ::: ### Signals and Slots To connect components and UI controls we use signals and slots - also known as SigSlos. For example, you can display the data you get from your component in a control in the UI. It also works the other way around - data that the user puts into controls in the configurator will be saved in components if the corresponding SigSlo exists. An example of a SigSlo: if you click on a button, a value of a component should be changed. Signal: click on the button Slot: change the value If you create a SigSlo always think if there is a SigSlo in the other way back. If the value of the component can be changed at any other point, has it any effect on the button? You get a list of all your SigSlos on the right side of your design component. This is also the only place where you can create a SigSlo. To add SigSlo's you can use the button at the right top of the Design Page or you click on a control and open the SigSlo window from there. :::info SigSlos can connect a component with a control, a control with a component, and also two controls with each other. You cannot connect two components with a SigSlo. ::: ### Presets Within the editor, you can open the preset menu at the top of the page. There you can add predefined configurations which can be used as starting points for your configurator. These presets can be accessed through URL parameters. A possible use case could be that all different models of a product get their own preset. The different links are then integrated into the customer's website. Configurator users can now start their configuration with a predefined model if they want to. ### Publishing On the top of the editor page, you can also find the "publish" button. With this button, you can publish your configurator draft. When clicking on this button, a pop-up window will show where you can add notes. In these notes, you should summarize the changes that you implemented to the configurator. They will be shown in the history and help to define the different versions of a configurator. :::info When you publish a draft, it will be added to the history automatically and will no longer appear in the draft table in the dashboard. ::: ### Published link and preview While editing a configurator in a draft you can check your changes in the preview link. It is marked with a thin orange header section and it cannot be used for integration on a website. Checkouts cannot be tested in a preview. On the other hand the published link has the full range of functions and is used for the integration. It is not marked in any way and just contains the actual configurator design. Checkouts can be executed in this link. Another difference is the configuration ID, which is only generated for configurations in a public link. While opening a preview link no ID will be generated and the configurations will not be saved in the configurations table. --- ## File upload & handling ### General You can allow users of your configurator to upload files. These files will be permanently linked to the current configuration and can be accessed in Hive or Custom Code and viewed in the [Configuration Insights](/docs/configuration-insights/configuration-insights-). Files that are uploaded into a configuration are called "Configuration Files" or `CfgnFiles` for short. They can either be uploaded with Signals & Slots (SigSlos) or with Custom Code. ### File Upload with SigSlos Uploading a file is very easy to achieve: ![image.png]() 1. Select any `Signal`. E.g. `Clicked` of a `Button` 2. As slot choose `Configurator` which is the top-most element of the controls 3. Select one of the Upload slots - `Upload image`: Only images are allowed. Unsupported formats will not be uploaded - `Upload file`: Any file can be uploaded 4. Enter a unique name for your file 5. [Optional] Enter a `Download name`, which will be the filename when someone downloads the file Afterwards you can test it right away in the Preview of your configurator. 1. Upload an image with the button 2. Open the watches on the right 3. Enter `Configuration.getImage("myImage")` - `myImage` has to be name you entered in the SigSlo `Name` parameter 4. Afterwards you should see an JSON object below with the `Url` and some metadata (width, height, etc.) ### Using files in Hive Hive provides two functions to retrieve the uploaded file: - `configuration.getImage("myImage")` - `configuration.getFile("myFile")` If no file has been uploaded yet (or there is none with that name) `Empty` will be returned. Therefore it's required to do an empty check before accessing properties like the url. ```hive var myImage = configuration.getImage("myImage"); if myImage is empty then "url empty" else myImage.url end ``` Alternatively, you can also obtain a table of all files with `configuration.GetFiles()`. This table will contain all files that have been uploaded so far and will update automatically. You may also query the table inside a `TableComponent`, e.g. to only return files with a specific name or prefix. ```hive from configuration.GetFiles() select all filter .name.take(4) = "img_" ``` A CfgnFile has the following properties: - `DisplayName` is the display (or "download") name that you specify when uploading the file - `Size` holds the file size in bytes - `Extension` holds the file extension as text (e.g. `jpg`) - `Url` holds the URL of the file Files returned by `getImage()` have an additional `Image` property that contains a Hive image. If you upload a file as an image, then you can only access it with the `getImage` function. Trying to access an image with `getFile` will result in an error (and vice versa). If you upload a file with the name `myFile` then `getFile("MyFile")` will not return it. You must use the exact same name: `getFile("myfile")` ### File Upload with Custom Code Custom code allows you to upload programmatically created files like screenshots from a 2D or 3D preview, datasheets or similar. The available functions can be found under the namespace `CfgnFiles`. All functions can be awaited and will directly return the url of the uploaded file. Besides that, files are also available in Hive as explained in the previous chapters. ##### Example - Upload screenshot ```ts const uploadResult = await CfgnFiles.uploadImages({ screenshotTopView: { file: myScreenshotFile, displayName: 'top-view-2024-09-16' } }); if (uploadResult.success) { console.log(uploadResult.urls.screenshotTopView); } ``` ##### Example - Upload dummy file ```ts await CfgnFiles.uploadFiles({ fileText: { file: new File(['Lorem ipsum'], 'dummy.txt') } }); ``` #### Creating PDF files With the function `CfgnFiles.createPdfFromHtml()` you can create a PDF file from any HTML string. For details on how to create the HTML see: [Create PDF from HTML content](/docs/configurator-ui-ux/customjs/create-pdf-from-html-content#creating-the-html) ##### Example - Create PDF from HTML ```ts const resultUrl = await CfgnFiles.createPdfFromHtml('bomPdf', 'Hello world content', 'cfgr-bom-' + Date.now(), { displayHeaderFooter: true, headerTemplate: 'My Header' }); console.log('url is: ', resultUrl); ``` ##### Example - Create PDF from asset The latest and suggested way of creating a PDF is based on a [Pdf asset](/docs/asset-management/pdf-asset). You can configure the PDF in the asset editor and pass flexible context data to the final PDF creation method. ```typescript const context = { company: { logo: 'MyFolder.MyFinalLogo' } }; const resultUrl = await CfgnFiles.createPdfFromAsset('MyBundle', 'MyFolder.MyPdfAsset', context, 'MyCfgnFileName', 'MyCfgnFileDisplayName'); console.log('url is: ', resultUrl); ``` ### Configuration Insights Insights are only available for published or staged configurators. Configurations of a preview can't be found under Configuration Insights. All the uploaded files of a configuration are permanently stored and can be viewed in the `Configuration Insights` afterwards. To view them, simply open the `Configuration details` of a Configuration and expand the `Files` section. ![image.png]() ### Temporary configuration files Configuration files have an upper limit per configuration. If you only need files temporarily during a configuration - and they don't need to persist after it ends - use **temporary configuration files**. With them you can upload up to 10x of your regular cfgn file limit within a single configuration session. **Example use case:** You want to take multiple screenshots of your 3D model and embed them in a generated PDF. Since the PDF already contains the images, the original screenshot files don't need to be persisted. ##### Example upload multiple files ```ts const myFiles = [new File(['Hello World'], 'hello.txt'), new File(['Hello World2'], 'hello2.txt')]; uploadTempFiles(myFiles); ``` ##### Example upload file from file dialog ```ts const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement; fileInput.addEventListener('change', async () => { if (!fileInput.files) return; const result = await uploadTempFiles(Array.from(fileInput.files)); // result: contains the uploaded file URLs in the same order as the input files console.log(result); }); ``` --- ## Hive introduction Hive is the language that is used to express the rules and logic of every Combeenation configurator. Hive was specially developed for mapping product logic in our platform. It can be used in almost all properties of a component to determine a suitable value for the property - these expressions are also called rules. A Hive rule can be a simple constant value or an extensive formula. All components and their properties can be used in hive rules. ### Why a separate programming language? Why can't product logic be expressed in a widely used language such as JavaScript? By developing our own language, we can seamlessly integrate it into our components and adjust and optimize the syntax - the grammar and rules that make up the language. The goal here is to make the mapping of product logic as simple as possible. Tabular data can be displayed directly with Hive expressions. Another important aspect of Hive is how rules are translated internally. Unlike in common programming languages, when writing Hive rules, you don't have to worry about when to execute them. The system automatically detects which rules are affected (directly or indirectly) by user input and executes them in the correct order. This is not only convenient but also efficient. :::info Rules that are not affected by a current change are not executed. ::: --- ## Hive language ### Types Each value in Hive is of a certain type, which determines what you can do with that value. It is a statically typed language, unlike JavaScript for example. In short, this means that the language is very strict about which expressions you can combine. For a subtraction like `100 - 50`, only numbers may be used - if you try to replace an operand with a text, the system shows an error. The aim here is to avoid mistakes and to point out a problem to the user immediately so that it is not discovered during the live operation of the configurator. These are some of the most essential types in Hive: - Number - Text - Logic - List - Record :::info Each of these types has its own article, with use cases and detailed descriptions. ::: ### Comments Comments allow you to write down helpful information directly in your rules. They have no effect whatsoever on a rule itself, but they can help you or others better understand what the rule is all about. To write a comment, start with two slashes `//` and then write whatever you want. This kind of comment takes up the remainder of the line. To write a comment over multiple lines, start with `/*` and end with `*/` when you're done ```hive // this is a single-line comment /* this comment spans over multiple lines */ ``` ### Variables Use the `var` keyword to assign a name to an arbitrary value in order to re-use it in your rules: ```hive var myValue = Input or 1; if myValue> 10 then 10 else myValue end ``` At present, the value of a variable cannot be changed after the variable was initialized. The declaration of a variable must be separated with a semicolon `;` from the next expression. #### Scope A variable can only be used in the “scope” it is declared in, or any nested scopes. For instance, a variable declared in one rule cannot be used in another rule. The `then` and `else` branches of an `if` expression each form their own scopes, which means that a variable declared in one of these scopes is not available outside the `if`. A variable declared in an inner scope will have higher precedence than other named entities from outer scopes. What this means is that a variable with the name “x” will hide other variables (or components) that are also called “x”. ```hive var x = 1; if true then var y = 2; y else var x = 2; // this is allowed; x now has a different meaning in the else-scope x + y // ERROR! y is not available here, it belongs to the then-scope end; x // this is the x defined in the first line, it's still set to 1 ``` ### Input and empty Some components allow the use of a special variable in their Hive rules: `input`. This primarily refers to user input. For example, if you connect a value component with a UI element such as a text box, the text that a user writes in this text box ends up in the `input` variable. The value of a component cannot be changed directly, only its `input` can. The `input` value is also saved automatically, so we don't have to worry about that. By default, the type of `input` is identical to that of the component. For example, if a component produces a number, then its `input` will have the type number too. The [value component](/docs/configurator-logic/components/value-component) allows you to choose a specific type for `input`. The simplest possible way to store a value in a configurator is to create a value component with just this code: ```hive input ``` However, unless you assign an initial value to `input`, it will be `empty`. The `empty` value is similar to `null` in other programming languages and represents the absence of a meaningful value. It cannot be processed further in a meaningful way and essentially just exists to signal that a real value is missing. :::warning In almost all cases, you should strive to avoid rules that return `empty` ! ::: There are multiple ways to check if something is `empty`, the simplest being the `or` operator. It checks if the left operand is `empty` and if it is, it returns the right operand as result: ```hive input or 1 ``` This will return the `input` value unless it is `empty`, in which case `1` will be returned. You can use any value as the right operand, as long as it is of the same type as the left operand. For example, if `input` is a text, the above example would not work – but `Input or "hello"` would. You can also check for `empty` with the [if expression](/docs/configurator-logic/the-if-expression): ```hive if input = empty then "No value here yet!" else input end ``` ### Global Scope and Naming Conflicts There are a number of global variables that you can use in your rules: [configurator](/docs/configurator-logic/configurator-and-presets-variable), [presets](/docs/configurator-logic/configurator-and-presets-variable#presets), [configuration](/docs/configurator-logic/configuration-variable) and [user](/docs/configurator-logic/user-variable). Should you find yourself in a situation where one of these variables is hidden by a component or local variable of the same name, you can use the `@top` keyword to access it: ```hive @top.user.mail ``` Similarly, you may be unable to access one of the built-in types like [Math](/docs/configurator-logic/hive-types/math-type) or [Viewer3d](/docs/configurator-logic/hive-types/viewer3d-type) if you used their name for something else. These types belong to the `Hive` module, so you can explicitly access them by prefixing the module name: ```hive Hive.Math.Pi ``` ### Optional chaining operator (?.) The optional chaining operator (also known as conditional access operator) `?.` allows you to access a property or function of something that could potentially be `empty`, without causing an error: ```hive // instead of this: if MyRecord <> empty then MyRecord.Name else "" end // you can write: MyRecord?.Name or "" ``` The `?.` operator checks if the left operand is `empty` and if so, returns `empty` itself. Otherwise it returns the field (or calls the function) on its right-hand side. --- ## Hive Queries A query lets you load, filter, sort and reshape a collection of data with Hive code. Queries are most commonly used to load data from a [data source](/docs/asset-management/data-source-asset/basics) via a [table component](/docs/configurator-logic/components/table-component), but they can also run standalone in any rule, directly against a list or map. Let's assume our data source is called `BikeParts`, then the simplest query we could write is: ```hive from BikeParts select all ``` This will load all records from the data source and include all its columns. The same syntax works on a list or map component, or standalone in any rule – the result always has the same kind of collection as the source, i.e. a table stays a table, a list stays a list and a map stays a map. We can choose to only load specific records and columns too: ```hive from BikeParts select { .partNumber, .name, .price } filter .partNumber > 100 ``` This will produce a table that only has 3 columns – `partNumber`, `name` and `price` – and it will only contain parts that have a `partNumber` of 100 or greater. :::note Each column name must start with a period (`.`) ::: ### The query item Every query works with a "query item" – the single record, list item or [MapEntry](/docs/configurator-logic/hive-types/map-type#mapentry) that is currently being looked at while filtering, sorting or projecting. By default, the query item is named `all`, i.e. `from source` is short for `from all in source`. This is why you can write `...all` or `all.column` – you're simply referring to the query item by its default name. You can also give the query item your own name: ```hive from item in BikeParts select [ item.name ] ``` Here we named the query item `item`; note that `all` is not available anymore in this case, you have to use `item` instead. The ambient member syntax (`.column`) is a shorthand for `all.column` (or whatever name you've given the query item). It works no matter what kind of source you query – a record's columns in a table, the item itself in a list, or `.key`/`.value` of a [MapEntry](/docs/configurator-logic/hive-types/map-type#mapentry) in a map. ### Filtering Data The filter of a query has no fixed syntax; you can write any Hive code you want, as long as it results in either `true` or `false`. The filter will be run for each item of the source. When it results in `true`, the item will be loaded, otherwise it won't. You can access all columns of the query item in your filter, but also components (though there are some limitations for components). Here is an example of a more advanced filter: ```hive from BikeParts select all filter if BikeCategory <> "" then .category.toLower() = BikeCategory else true end and .netWeight + AccessoryWeight < MaxWeight ``` A few things to note: - a filter can be made optional by using an `if/then/else` expression - in the example we only filter by `.category` when the `BikeCategory` component is not an empty text - by using `true` or `false` in the `else` branch of an `if`, we can include or exclude the record - we could also rewrite the category filter to not require an `if/then/else` expression like this: - `(BikeCatogery = "" or .category.toLower() = BikeCategory)` - you can call functions on column values or use them in calculations: - we convert the `.category` to lower-case before comparing it with `BikeCategory` - we calculate the sum of `.netWeight` and the `AccessoryWeight` component and compare the result with `MaxWeight` :::warning **Limitations:** there are some types of components (or Hive values, more general) that cannot be used in queries, or only in specific ways. See the [Limitations](/docs/configurator-logic/hive-queries#limitations) section for details. ::: ### Projecting Data Data can not only be filtered, it can also be molded to fit your use case: the `select` clause determines both the contents and the kind of collection that is produced. - `select { ... }` produces a **table**, e.g. `select { .nr = .partNumber, .name }` - `select [ expr ]` produces a **list**, e.g. `select [ item * 3 ]` - `select [ key : value ]` produces a **map**, e.g. `select [ .id : .name ]` #### Table projections ```hive from BikeParts select { .nr = .partNumber, .name, .price = .price * if .onSale then 0.8 else 1 end, } ``` A few things to note: - if you want to use a different name for a column, you can just assign that column to a new one - in the example, we create a column `nr` that will hold the value of the `partNumber` column - **Note:** column names of the resulting table must be unique; you cannot have 2 columns with the same name - you can adjust the value of a column; in the example we lower the `price` by 20% when the `onSale` column is true - **Note:** modified columns are not available in filters or when modifying other columns. For instance, you cannot use `.nr` in a filter or the `.price` calculation (you must use `.partNumber`). Similarly, when you type `.price` you will receive the value stored in the data source, not the lowered price! (_this might become available in a future update_) - when adjusting a column value, you can use any Hive feature you want (\*) – there is no specialized syntax - (\*) the same limitations as for filters apply, see the [Limitations](/docs/configurator-logic/hive-queries#limitations) section If you want to use all columns of the source and add your own, you can use the spread operator (`...`): ```hive from BikeParts select { ...all, .formattedPrice = .price.toNumber("0.00 EUR"), } ``` The spread operator can target any expression, not just `all` – for example `... .value` to spread a map's values, or `...item.field` to spread a field of an object in a list. It only works in table projections. A field introduced by a spread can be overridden by an explicitly declared column, or by a later spread – the overriding column always wins and determines the field's name, type and value, even if it appears before the spread in your code. The **position** of the field in the resulting table is always determined by whichever spread or column introduced it first; spread fields simply "fill the gaps" between your explicitly declared columns. #### List projections ```hive from item in BikeParts select [ item.name ] ``` This produces a list of the `name` column of every part. #### Map projections ```hive from BikeParts select [ .partNumber : .name ] ``` This produces a map, using `partNumber` as key and `name` as value. ### Sorting Data You can change the order in which items are returned by a query by sorting them by one or more values: ```hive // sort first by category, then by net weight from BikeParts select all sort by .category, .netWeight desc ``` The `sort by` statement must come after a `filter` (if there is one). By default, values are sorted in ascending order. You can add `desc` after a sort expression to sort in descending order (you can also add `asc` to clarify your intent). While the value to sort by will typically involve a column, you can also write more complex expressions. ```hive // sort first by rounded weight, then sort so parts without a description come at thet end from BikeParts select all sort by .netWeight.round(), (if .Description = "" then 0 else 1 end) desc ``` Note that a sort value must be a `Number`, `Text` or `Logic` value. Furthermore, you must put complex sort expressions in parenthesis `()`, as in the example above (a column name or function call works without `()`, but anything more advanced requires them). :::tip `Text` is sorted in a case-sensitive manner, but you can achieve a case-insensitive ordering by converting the text to all-upper or all-lower case first: `sort by .myTextColumn.toUpper()` . ::: ### Limiting the number of rows You can optionally restrict how many rows can be returned by a query or skip over a number of records: ```hive // ignore the first record, then return up to 1000 records from BikeParts select all sort by .category skip 1 take 1000 ``` **NOTE:** this feature is best used in combination with `sort by`, as the order of rows in your data source may be subject to how it is imported. If you e.g. use a workflow to automatically update a data source, the order of rows may not be guaranteed. Both `skip` and `take` expect a `Number`. This can be a literal value like `1` or an expression that involves a component like `MaxRows` – but you cannot use columns, as the limits affect the entire query and a column value belongs to a specific record. Similar to `sort`, you must put complex expressions in parenthesis `()`. ### Limitations When talking about what is permitted in a query (i.e. in the filter or when calculating a column value), we must differentiate between 2 areas: - the parts of a query that involve a column and are evaluated by the query engine – **these have limits** - the parts of a query that do _not_ involve a column, which are evaluated beforehand – **these are limitless** - we call these parts "**query parameters**" For instance, it's currently not allowed to use a record component in a filter, because sending an entire record table to the query engine would often be expensive and slow down your query. However, a record component can still be used in a query parameter – consider these two cases: ```hive // not allowed // a column value is used in Get(), thus the entire component is needed to evaluate the query filter myRecordComponent.Get(.partNumber).Price < MaxPrice // allowed // everything to the right of (>) is a query parameter, as it doesn't involve a column value // the query parameter is a simple number that can be calculated before the query itself is run filter .price > myRecordComponent.Get(SelectedPartNumber).Price ``` The **key takeaway** is that you don't have to worry about limits in query parameters, only in code that directly involves column values. The system automatically tries to run as much code of the filter as possible before the query is started (i.e. turning those parts into parameters). 💡 If your query results in an error due to limits, try to restructure it so that the parts highlighted by the error do not depend on column values. **These types and features can currently be used in filters:** - Number: all operators (`>`, `>=`, `<`, `<=`, `=`, `<>`, `+`, `*`, `-`, `/`, `mod`, `^`) and functions (`toText`, `round`, `floor`, `ceiling`, `truncate`, `atLeast`, `atMost`) - Text: all operators (`+`, `=`, `<>`, `~=`, `*`) and functions (`length`, `contains`, `skip`, `take`, `replace`, `toUpper`, `toLower`, `toNumber`) - Logic: all operators (`not`, `and`, `or`, `=`, `<>`) - Lists: all functions (`length`, `get`, `contains`) - `if`/`then`/`else` expressions - variables - optional chaining operator `?.` --- ## Hash type The hash type provides functions for generating hash codes with various hashing algorithms. ### Usage To use any of the functions described on this page, you must prefix them with the hash type name: ```hive Hash.computeSha256("some value") ``` ### computeSha256 function Generates the SHA256 hash of the given text (first parameter). The second parameter defines in which format the generated hash will be returned. Syntax: computeSha256(value, isBase64Encoded) If it is true (which is the default value) the hash will be returned base64 encoded. If it is false the hash will be returned hex (base16) encoded. ```hive Hash.computeSha256("") // 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= Hash.computeSha256("some value") // qz0H8xacy9DtbEtF3iFRn5+TjHLSQSSZiquUnOg7tRs= Hash.computeSha256("", false) // e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 Hash.computeSha256("some value", false) // ab3d07f3169ccbd0ed6c4b45de21519f9f938c72d24124998aab949ce83bb51b ``` ### computeHmacSha256 function Generates the HMAC SHA256 hash of the given text (first parameter) with the given. The second parameter is the secret text which is used to generate the hash. The third parameter defines in which format the generated hash will be returned. Syntax: computeHmacSha256(value, secret, isBase64Encoded) If it is true (which is the default value) the hash will be returned base64 encoded. If it is false the hash will be returned hex (base16) encoded. ```hive Hash.computeHmacSha256("", "") // thNnmggU2ex3L5XXeMNfxf8Wl8STcVZTxscSFEKSxa0= Hash.computeHmacSha256("some value", "some secret") // gcOIOIoay4FP/bY7cDX/3xf8JheKkEwUWmL1cyVTdLg= Hash.computeHmacSha256("", "some secret", false) // f6ea2d6efc05da393de3fa47d17eda28ae5e25197501e889e4d995f8e53ea8f6 Hash.computeHmacSha256("some value", "some secret", false) // 81c388388a1acb814ffdb63b7035ffdf17fc26178a904c145a62f573255374b8 ``` --- ## JSON type The JSON type allows you to store arbitrary JSON content, e.g. for use in Custom Code or to pass it on to external APIs. :::info Currently, there is no way to interact with the JSON content. We plan to add `parse()` functions to convert JSON content to objects, lists, etc. ::: ### JSON literal To create a JSON value in a Hive rule, use the JSON literal: ```hive json`{ "sku": "12345", "color": "red", "price": 19.99, "tags": [ "clothing", "sale", "new" ], "availability": { "in_stock": true, "quantity": 50 } }` ``` Inside the backticks, you can write or paste raw JSON without having to make any formatting changes. :::info At present, there is no way to access components inside the JSON literal, but we plan to add an interpolation feature for this. ::: ### Usage To store arbitrary JSON content, create a new `ValueComponent` with an `Input or` rule and use a JSON literal as fallback value: ```hive Input or json`{}` ``` JSON values can also be part of typed objects, allowing you to create semi-structured models. For example, you can store a JSON value in an object passed to [`OnFinish.SetOutput`](../../integrations-and-api/configuration-finish): ```hive OnFinish.SetOutput({ color: SelectedColor, price: unitPrice * Amount, pdfContext: if user.isLoggedIn then MyJsonComponent else json`{}` end }) ``` --- ## List type A list contains multiple values of the same type. To create a list you just have to wrap a number of values in square brackets. a list of numbers: `[ 1, 2, 3 ]` a list of text: `[ "red", "green", "blue" ]` You can declare lists in any of your rules. If you want to use the same list in various places, you can for example put it in a value component. :::info The items of a list are ordered and can be accessed by their index / position in the list. **The first item has the index 1.** ::: ### Input A list can also be used as input for a rule. It works the same for them as for any other value: ```hive Input or [1, 2, 3] ``` In this example, if `input` contains a list, that list is used – otherwise the list `[1,2,3]` is used. **This example only works** when `input` is a list. By default, the `input` is of the same type as the rule, so the easiest way to change the type of `input` is to change the outcome of the rule. For example, start with the rule `[1, 2, 3]` to turn the rule (and its `input`) into a list, then you can change the rule to `Input or [1, 2, 3]` . In a value component, you also have the option to change the type of the `input` directly with the input type field. In this example, the required type would be `List` Input lists can be modified with signals & slots in various ways: you can add, remove or change items or replace the entire list. :::info Signals & slots can only modify an `input` list, not a list declared in a rule. If the `input` is `empty`, it is not considered a list and thus you cannot add items to it. Therefore, if you want to be able to modify a list `input` with Signals & slots, you should provide a default `input` list – an empty list `[]` works too. ::: ### Empty Lists ([]) You can create a list without any items in it with a pair of square brackets (`[]`). :::info Empty lists only work in places where the type of the list can be inferred! ::: A common use case is to use an empty list as starting point of a value component. First, ensure that the component returns a list of some kind, e.g. the rule `[1]` would produce a list of numbers. Now that the component and its `input` are lists, you can use the empty list syntax in your rule: ```hive Input or [] ``` The empty list will be of the same type as the `input`, a list of numbers in our example. This also works in more complex scenarios, for example if you are working with a list of records the following pattern may be useful: ```hive Input or if false then // sample format [ { id: "item-1", name: "this is an example for a list of records", price: 13.37, description: "these values will never be used (due to 'if false'), they are just examples", options: [ 3, 4, 5], language: [ "de", "en" ], parts: [{ id: "part-1", name: "nested lists and records also work", price: 0.815 }] } ] else [] end ``` In this example, we have a list of records that describe some imaginary products. The list is meant to store all records (products) that a user chooses, so we have to use the `input` variable for that. As starting point, we want to use an empty list. Now we have to describe the record in some way. We could directly write the type into the input type field (which would start like this: `List false` | `true` | Returns `true` if two logic values do not contain the same value | | logical negation | `not true` | `false` | Returns the inverse of a value. | | logical conjunction | `true and false` | `false` | Returns `true` if both values are true and false if either one is false | | logical disconjunction | `true or false` | `true` | Returns `true` if either value is true and false if both are false | #### Functions | name | description | | ------ | ------------------------ | | toText | converts a logic to text | #### toText Converts a logic to Text. | example | result | | --------------- | -------- | | `true.toText()` | `"true"` | --- ## Map type A map contains multiple values, each of which can be looked up by a key stored alongside it. In other words: keys are mapped to values, which is where the name "map" comes from. Values can be of any type, so long as they all have the same type. Keys are currently limited to either Number or Text: - a map with number keys: `[ 15 : "value" ]` - a map with text keys: `[ "one" : 1, "two" : 2 ]` :::info The items of a map are ordered and can also be accessed by their index / position in the map, in addition to their key. **The first item has the index 1.** ::: ### Input A map can also be used as input for a rule. It works the same for them as for any other value: ```hive Input or [ "one" : 1 ] ``` In this example, if `input` contains a map, that map is used – otherwise the map `[ "one" : 1 ]` is used. ### Empty Maps ([]) You can create a map without any items in it with a pair of square brackets (`[]`) – the same syntax used for [empty lists](/docs/configurator-logic/hive-types/list-type#empty-lists-). :::info Empty maps only work in places where the type of the map can be inferred! ::: ```hive Input or [] ``` ### MapEntry When you access an item of a map by its index (e.g. with [getEntryAt](#getentryatindex)), you get back a `MapEntry` – a value that holds both the `key` and the `value` of that item: `{ key: …, value: … }`. ### Functions | name | description | | --------------------------------------------------------------------------------- | --------------------------------------------- | | [length](/docs/configurator-logic/hive-types/map-type#length) | gets the number of items in the map | | [get(key)](/docs/configurator-logic/hive-types/map-type#getkey) | gets the value for the specified key | | [getAt(index)](/docs/configurator-logic/hive-types/map-type#getatindex) | gets the value at the specified index | | [getEntryAt(index)](/docs/configurator-logic/hive-types/map-type#getentryatindex) | gets the key and value at the specified index | #### length Returns the number of items in the map. | Example | Result | | ----------------------------------- | ------ | | `[ "one" : 1, "two" : 2 ].length()` | 2 | --- #### get(key) Returns the value for the specified key, or `Empty` if the map contains no such key. | Example | Result | | --------------------------------------- | ------ | | `[ "one" : 1, "two" : 2 ].get("one")` | 1 | | `[ "one" : 1, "two" : 2 ].get("three")` | Empty | When looking for a key, `get` uses the default equality operator as defined by the type of the keys in the map. Text keys are compared case-sensitive. --- #### getAt(index) Returns the value at the specified index. | Example | Result | | ------------------------------------ | ---------------------- | | `[ "one" : 1, "two" : 2 ].getAt(0)` | 1 (same as `getAt(1)`) | | `[ "one" : 1, "two" : 2 ].getAt(2)` | 2 | | `[ "one" : 1, "two" : 2 ].getAt(-1)` | 2 | `getAt` behaves the same way as [List.get(index)](/docs/configurator-logic/hive-types/list-type#getindex): - The first item has index 1, but 0 also returns it. - When the index is negative, items are counted from right to left. - When the index is greater than the number of items in the map, it is automatically capped. - If the map contains no items, `Empty` is returned. Check the length of the map to explicitly handle this case. --- #### getEntryAt(index) Returns the [MapEntry](#mapentry) (key and value) at the specified index. Use this when you need the key of an item you only know by its index. Indexing rules are the same as for [getAt](#getatindex). | Example | Result | | ---------------------------------------- | -------------------------- | | `[ "one" : 1, "two" : 2 ].getEntryAt(0)` | `{ key: "one", value: 1 }` | --- ## Math type The math type provides trigonometric and other advanced mathematical functions and constants. For commonly used, simple mathematical functions take a look at the number type. ### Usage To use any of the functions described on this page, you must prefix them with the math type name. ```hive Math.sin(90) ``` ### Constants | name | description | | ---- | --------------------------------------- | | `pi` | the number pi | | `e` | the number e (natural logarithmic base) | ### Functions | name | description | | ----------------------------------------------------------------------------------------- | ----------------------------------------------- | | [abs(number)](/docs/configurator-logic/hive-types/math-type#absnumber) | gets the absolute value of a number | | [sqrt(number)](/docs/configurator-logic/hive-types/math-type#sqrtnumber) | gets the square root of a number | | [max(number1, number2)](/docs/configurator-logic/hive-types/math-type#maxnumber1-number2) | gets the greater of two numbers | | [min(number1, number2)](/docs/configurator-logic/hive-types/math-type#minnumber1-number2) | gets the lower of two numbers | | [log(number)](/docs/configurator-logic/hive-types/math-type#lognumber) | gets the natural logarithm (base e) of a number | | [log2(number)](/docs/configurator-logic/hive-types/math-type#log2number) | gets the base 2 logarithm of a number | | [log10(number)](/docs/configurator-logic/hive-types/math-type#log10number) | gets the base 10 logarithm of a number | #### Trigonometric Functions (in degrees) These functions assume that angles are measured in degrees, see the next section for corresponding functions that take radians. | name | description | | ------------ | ---------------------------------------------------- | | sin(angle) | gets the sine of the specified angle | | cos(angle) | gets the cosine of the specified angle | | tan(angle) | gets the tangent of a specified angle | | asin(number) | gets the angle whose sine is the specified number | | acos(number) | gets the angle whose cosine is the specified number | | atan(number) | gets the angle whose tangent is the specified number | #### Trigonometric Functions (in radians) These functions assume that angles are measured in radians, otherwise they behave exactly like their degree counterparts. | name | description | | ------------- | ---------------------------------------------------- | | sinr(angle) | gets the sine of the specified angle | | cosr(angle) | gets the cosine of the specified angle | | tanr(angle) | gets the tangent of a specified angle | | asinr(number) | gets the angle whose sine is the specified number | | acosr(number) | gets the angle whose cosine is the specified number | | atanr(number) | gets the angle whose tangent is the specified number | #### abs(number) Returns the absolute value of a number. | example | result | | ------------------ | ------- | | `Math.abs(-13.37)` | `13.37` | | `Math.abs(5)` | `5` | #### sqrt(number) Returns the square root of a number. | example | result | | -------------- | ------ | | `Math.sqrt(4)` | `2` | #### log(number) Returns the natural logarithm (base e) of a number. | example | result | | ------------- | ------------------- | | `Math.log(2)` | `0.693147180559945` | #### log2(number) Returns the base 2 logarithm of a number. | example | result | | -------------- | ------ | | `Math.log2(2)` | `1` | #### log10(number) Returns the base 10 logarithm of a number. | example | result | | --------------- | ------------------- | | `Math.log10(2)` | `0.301029995663981` | #### max(number1, number2) Returns the greater of two numbers. | example | result | | ------------------ | ------ | | `Math.max(3, 5)` | `5` | | `Math.max(-7, -2)` | `-2` | #### min(number1, number2) Returns the lesser of two numbers. | example | result | | ------------------ | ------ | | `Math.min(3, 5)` | `3` | | `Math.min(-7, -2)` | `-7` | --- ## Number type Numbers are used for all kinds of calculations. There are literal numbers, like `7` or `1.5`, but a component or variable can also be of type number. Both kinds can be used interchangeably and everything described on this page applies to either kind. For example, you can call a function like `round()` on a component: `price.round()` or on a literal number: `13.5.round()`. ### Operations | name | example | result | description | | -------------- | ------------- | ------ | ------------------------------------------------------- | | negate | `-price` | `-5` | gets the inverse of a number | | add | `price + 2` | `7` | adds two numbers | | subtract | `2 - price` | `-3` | subtracts one number from another | | multiply | `2 * price` | `10` | multiplies two numbers | | divide | `2 / price` | `2.5` | divides one number by another | | exponentiation | `price ^ 2` | `25` | raises a number to the power of another number | | modulo | `price mod 2` | `1` | divides one number by another and returns the remainder | ### Comparing numbers In addition to the above, there are also six operators for comparing numbers. | name | example | result | description | | --------------------- | ------------- | ------- | ------------------------------------------------------------------------- | | greater than | `price > 5` | `false` | returns `true` if the first number is greater than the second | | greater than or equal | `price >= 5` | `true` | returns `true` if the first number is greater than or equal to the second | | less than | `4 < price` | `true` | returns `true` if the first number is less than the second | | less than or equal | `4 <= price` | `false` | returns `true` if the first number is less than or equal to the second | | equal to | `price = 5` | `true` | returns `true` if one number is equal to another | | not equal to | `price <> 19` | `true` | returns `true` if one number is not equal to another | ### Functions | name | description | | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [round(digits)](/docs/configurator-logic/hive-types/number-type#round) | rounds a number to the nearest integer or to the specified number of fractional digits | | [floor](/docs/configurator-logic/hive-types/number-type#floor) | gets the largest integer less than or equal to the specified number | | [ceiling](/docs/configurator-logic/hive-types/number-type#ceiling) | gets the smallest integer greater than or equal to the specified number | | [truncate](/docs/configurator-logic/hive-types/number-type#truncate) | calculates the integral part of a number | | [atLeast(min)](/docs/configurator-logic/hive-types/number-type#atleastmin) | gets a number that is greater than or equal to the specified threshold | | [atMost(max)](/docs/configurator-logic/hive-types/number-type#atmostmax) | gets a number that is less than than or equal to the specified threshold | | [toText(format,locale)](/docs/configurator-logic/hive-types/number-type#totextformatlocale) | converts a number to text | :::info For additional mathematical functions check out the article about the math type. ::: #### round Rounds a number to the specified number of fractional digits, away from zero. If no parameter is used, or the number of digits is zero, rounds to the nearest integer. | example | result | | ---------------- | ------ | | `price.round()` | 4 | | `price.round(1)` | 4.4 | | `price.round(2)` | 4.45 | :::info If you specify a negative number of digits, its positive inverse will be used, i.e. `round(-2)` is the same as `round(2)`. ::: --- #### floor Returns the largest integer that is less than or equal to the specified number. | example | result | | ---------------- | ------ | | `1.1.floor()` | 1 | | `1.9.floor()` | 1 | | `(-1.1).floor()` | -2 | --- #### ceiling Returns the smallest integer that is greater than or equal to the specified number. | example | result | | ------------------ | ------ | | `1.1.ceiling()` | 2 | | `1.3.ceiling()` | 2 | | `(-1.1).ceiling()` | -1 | --- #### truncate Calculates the integral part of a number by rounding it to the nearest integer towards zero. | example | result | | ------------------- | ------ | | `1.1.truncate()` | 1 | | `1.9.truncate()` | 1 | | `(-1.9).truncate()` | -1 | --- #### atLeast(min) Compares a number to a specified threshold and returns it if it is greater than or equal to the threshold, otherwise returns the threshold. | example | result | | --------------- | ------ | | `5.atLeast(10)` | 10 | | `5.atLeast(3)` | 5 | --- #### atMost(max) Compares a number to a specified threshold and returns it if it is less than or equal to the threshold, otherwise returns the threshold. | example | result | | -------------- | ------ | | `5.atMost(2)` | 2 | | `5.atMost(10)` | 5 | --- #### toText(format,locale) Converts a number to text. You can optionally customize the conversion with the format parameter and specify the desired locale. | example | result | | ------------------------------------ | -------------- | | `price.toText()` | `"4.445"` | | `price.toText("€ 0.##")` | `"€ 4.45"` | | `1337.toText("$ 0,0.00")` | `"$ 1,337.00"` | | `1337.toText("€ 0,0.00", Locale.de)` | `"€ 1.337,00"` | You may use a combination of the following characters to customize the conversion of numbers to text. #### zero placeholder (O) Replaces the zero with the corresponding digit if one is present; otherwise, zero appears in the result text. `123.456.toText("0000")` becomes `0123` `123.toText("000.00")` becomes `123.00` #### digit placeholder (#) Replaces the hash sign with the corresponding digit if one is present; otherwise, no digit appears in the result text. `123.456.toText("####")` becomes `123` `123.toText("###.##")` becomes `123` #### decimal placeholder (.) Determines the location of the decimal separator in the result text. `0.1234.toText("0.00")` becomes `0.12` #### group separator placeholder (,) Inserts a localized group separator between each number group in the integral part of the output. `12345678.toText("##,#")` becomes `12,345,678` #### percentage placeholder (%) Multiplies a number by 100 and inserts a percentage symbol in the result text. `0.35.toText("0%")` becomes `35%` #### section separator (;) Defines sections with separate format strings for positive, negative and zero numbers. `(-13.37).toText("##.##;(##.##)")` becomes `(13.37)` #### Other characters All characters that have not been mentioned above will simply be copied to the result text: `24.toText("0 degrees")` becomes `24 degrees` In order to use placeholder characters literally, you can precede them with a backslash: `13.37.toText("\# 00.0")` becomes `# 13.4` You can also use single quotes `'` or double quotes `"` to enclose literal text that should be copied to the result: `13.37.toText("00.0'% of total'")` becomes `13.4% of total` #### Rounding If the formatted value contains more significant digits than are present in the format text, the value will be rounded to the nearest digit and away from zero. `10.5.toText("#")` becomes `11` `0.25.toText("0.0")` becomes `0.3` --- ## QR code type ### Usage To use any of the functions described on this page, you must prefix them with the QRCode type name: ```hive QRCode.generate("some text") ``` ### Generate function Generates the QR code of the given text and returns the SVG representation of it. Syntax: generate(text, darkColor, lightColor, errorCorrectionLevel) - The first parameter is required and is the text of which the QR code gets generated (type: text, max length: 500 characters) - The second parameter is optional and defines the main color of the QR code (type: color, default value: #000) - The third parameter is optional and defines the background color of the QR code (type: color, default value: #FFF) - The fourth parameter is optional and defines the error correction level of the QR code (type: number, default value: 0). Possible values: - \<=1 - Level L (Low) - 7% of codewords can be restored - 2 - Level M (Medium) - 15% of codewords can be restored - 3 - Level Q (Quartile) - 25% of codewords can be restored - \>=4 - Level H (High) - 30% of codewords can be restored ```hive QRCode.generate("some text a") QRCode.generate("some text b", "#F00".toColor()) QRCode.generate("some text c", "#012".toColor(), "#ABC".toColor()) QRCode.generate("some text d", "#00D".toColor(), "#F0F".toColor(), 3) ``` The SVG QR code scales to any width/height needed. If you put the SVG into a visual in a graphic component and set the height/width, it will scale to this size accordingly. Tips for improving the readability of the QR code: - Always leave a space between your QR code and the content or design around it. The QR code should always stand alone in the background and the foreground should not touch other elements. - Make sure there is enough contrast when using other colors than black and white. - Don’t put too much content into a QR code. Use a low error correction level for smaller QR codes. --- ## Text type In Hive, a text (often called “string” in other programming languages) must be surrounded with quotation marks (`”`). All the operations and functions described on this page work with literal texts, components, and variables alike: ```hive var t = "Hello, "; t.toUpper() + "world!".toUpper() // returns "HELLO, WORLD!" ``` ### Formatted Text Start and end your text with three quotes (`”””`) instead of one to create a formatted text. A formatted text can span over multiple lines (preserving each new line), you can use quotation marks without having to escape them and embed code fragments in it: ```hive var answer = 42; """The answer is of course "{=answer}". What else?""" ``` This will create a multi-line text: ```hive The answer is of course "42". What else? ``` If you want to use a quotation mark in a regular text, you must escape it with `\`. A regular text cannot contain line breaks either, instead requiring you to write `\n`. The example above can be written as a regular text like this: ```hive "The answer is of course \"" + answer + "\".\nWhat else?" ``` Not very convenient. Therefore, when you are dealing with text that contains quotation marks or line breaks, we recommend you use formatted text. However, there is one caveat: an opening curly brace (`{`) must be written as two curly braces in formatted text: ```hive """This is a curly {{brace}""" ``` Plain text This will create this text: `This is a curly {brace}` ### Operations | name | example | result | description | | ---------- | ---------------- | ----------- | -------------------------------- | | add text | `"awe" + "some"` | `"awesome"` | combines two texts | | add number | `"high " + 5` | `"high 5"` | combines a text and a number | | repeat | `"$" * 3` | `"$$$"` | repeats a text a number of times | ### Comparing Text | name | example | result | description | | ------------ | -------------- | ------- | --------------------------------------------------------- | | equal to | `"up" = "UP"` | `false` | returns `true` if two texts are equal, case sensitive | | equal to | `"up" ~= "UP"` | `true` | returns `true` if two texts are equal, case insensitive | | not equal to | `"up" <> "UP"` | `true` | returns `true` if two texts are not equal, case sensitive | There is no case insensitive version of the `<>` operator, but you can negate the outcome of the `~=` operator to achieve the same goal: `not("up" ~= "UP")` returns `false`, for example. :::warning Whitespace characters such as spaces, tabs, or line breaks are significant when comparing text. The expression `"a " = "a"` is `false` because there is a space in the second text that is not present in the first. ::: ### Functions | name | description | | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | [length](/docs/configurator-logic/hive-types/text-type#length) | gets the number of characters in a text | | [contains(text)](/docs/configurator-logic/hive-types/text-type#containstext) | returns `true` if a text contains a specific text | | [toUpper](/docs/configurator-logic/hive-types/text-type#toupper) | converts a text to upper case | | [toLower](/docs/configurator-logic/hive-types/text-type#tolower) | converts a text to lower case | | [take(amount)](/docs/configurator-logic/hive-types/text-type#takeamount) | gets a subset of a text that contains a specified number of characters | | [skip(amount)](/docs/configurator-logic/hive-types/text-type#skipamount) | gets a text with a specified number of characters omitted | | [replace(old, new)](/docs/configurator-logic/hive-types/text-type#replaceold-new) | replaces all occurrences of a text with a different text | | [encodeHtml](/docs/configurator-logic/hive-types/text-type#encodehtml) | gets an HTML-encoded copy of a text | | [decodeHtml](/docs/configurator-logic/hive-types/text-type#decodehtml) | decodes an HTML-encoded text | | [encodeUrl](/docs/configurator-logic/hive-types/text-type#encodeurl) | gets an URL-encoded copy of a text | | [decodeUrl](/docs/configurator-logic/hive-types/text-type#decodeurl) | decodes an URL-encoded text | | [encodeJson](/docs/configurator-logic/hive-types/text-type#encodejson) | gets a JSON-encoded copy of a text | | [toNumber(locale)](/docs/configurator-logic/hive-types/text-type#tonumberlocale) | converts a text to a Number | | [toColor](/docs/configurator-logic/hive-types/text-type#tocolor) | converts a text to a Color | | [isBase64](/docs/configurator-logic/hive-types/text-type#isbase64) | returns `true` if it is a valid base64 string | | [isEmail](/docs/configurator-logic/hive-types/text-type#isemail) | returns `true` if it is a valid mail address | #### length Returns the number of characters in a text. | example | result | | -------------------- | ------ | | `"123".length()` | 3 | | `"awesome".length()` | 7 | --- #### contains(text) Returns true if a text contains a specific text. | example | result | | ---------------------------- | ------ | | `"awesome".contains("some")` | `true` | --- #### toUpper Returns a copy of the text with all characters converted to upper case. | example | result | | ---------------------- | ------------ | | `"Awesome!".toUpper()` | `"AWESOME!"` | #### toLower Returns a copy of the text with all characters converted to lower case. | example | result | | ---------------------- | ------------ | | `"Awesome!".toLower()` | `"awesome!"` | --- #### take(amount) Returns a subset of a text that contains a specified number of characters. - If a negative number is used, characters will be taken from the end of the text towards the beginning. - If the number is greater than the number of characters in the text, the same text will be returned. - If the number is zero, an empty text will be returned. | example | result | | --------------------- | ----------- | | `"awesome".take(3)` | `"awe"` | | `"awesome".take(-4)` | `"some"` | | `"awesome".take(100)` | `"awesome"` | | `"awesome".take(0)` | `""` | --- #### skip(amount) Returns a text with a specified number of characters omitted. - If a negative number is used, characters will be skipped from the end of the text toward the beginning. - If the number is greater than the number of characters in the text, an empty text will be returned. - If the number is zero, the same text will be returned. | example | result | | --------------------- | ----------- | | `"awesome".skip(3)` | `"some"` | | `"awesome".skip(-4)` | `"awe"` | | `"awesome".skip(100)` | `""` | | `"awesome".skip(0)` | `"awesome"` | :::info This function is the exact opposite of `take`. You can use either one and accomplish the same goal, but often it is easier to reason about a problem when combining the two. ::: Example: Imagine you wanted to extract `123` from an inventory number like `ST-123-XYZ`. The most straightforward way to accomplish this probably is `inventoryNumber.skip(3).take(3)`. But you could just as well write `inventoryNumber.take(6).take(-3)`. The choice is yours! --- #### replace(old, new) Replaces all occurrences of a text with a different text. - If the searched text isn't found, the function will return the original text - All occurrences of the searched text will be replaced, not just the first one. | example | result | | ------------------------------------- | ------------------- | | `"awesome".replace("esome", "ful")` | `"awful"` | | `"a;divided;text;".replace(";", " ")` | `"a divided text "` | --- #### encodeHtml Returns a text with all characters replaced that have special meaning in HTML, so that it can be safely used. Note: while the text may look strange, your browser will still display it correctly. | example | result | | --------------------------------- | --------------------------- | | `"I <3 Hive & HTML".EncodeHtml()` | `"I <3 Hive & HTML"` | --- #### decodeHtml The opposite of EncodeHtml: converts an HTML-encoded text back to its original form. | example | result | | ---------------------------------------- | -------------------- | | `"I <3 Hive & HTML".DecodeHtml()` | `"I <3 Hive & HTML"` | --- #### encodeUrl Similar to EncodeHtml, returns a text with all characters replaced that have special meaning in URLs, so that it can be safely used. | example | result | | -------------------------------- | ------------------------ | | `"I <3 Hive & HTML".EncodeUrl()` | `"I+%3C3+Hive+%26+HTML"` | --- #### decodeUrl The opposite of EncodeUrl: converts an URL-encoded text back to its original form. | example | result | | ------------------------------------ | -------------------- | | `"I+%3C3+Hive+%26+HTML".DecodeUrl()` | `"I <3 Hive & HTML"` | --- #### encodeJson Returns a text with all characters replaced that have special meaning in JSON – or that could pose a security risk via injection attacks. The returned text can be safely embedded in a JSON document. **Note:** use this on a text that will be _embedded_ in a JSON text, not on a text that contains JSON content itself! | example | result | | ----------------------------------------------------------------------- | ------------------------------------------------------------------ | | `"Dangerous text: \" \\ + <>".encodeJson()` | `"Dangerous text: \u0022 \\ \u002B \u003C\u003E"` | | `"""{ "example": "{= "Dangerous text: \" \\ + <>".encodeJson() }" }"""` | `"{ "example": "Dangerous text: \u0022 \\ \u002B \u003C\u003E" }"` | #### toNumber(locale) Converts a text to a number. If the text cannot be converted, for example because it contains non-numerical characters, `Empty` will be returned. You can optionally specify a [locale](/docs/configurator-logic/hive-types/local-type) to support different number formats, the default is `Locale.en` Always provide a fallback value in case the conversion fails. We made it easy to do so with the `or` operator. | example | result | | ----------------------------- | ------- | | `"13.37".toNumber()` | 13.37 | | `"13$".toNumber()` | `Empty` | | `"13$".toNumber() or 1` | 1 | | `"13,37".toNumber(Locale.de)` | 13.37 | --- #### toColor Converts a text in hexadecimal color format to a color. If the color format is invalid, an error will be returned. | example | result | | --------------------- | ----------------------------------- | | `"#0000ff".ToColor()` | 6-digit format: `#RRGGBB` | | `"#f00".ToColor()` | 3-digit format: `#RGB` | | `"#ff00".ToColor()` | ! ERROR ! invalid format (4 digits) | --- #### isBase64 Returns true if the given text is a valid base64 string. | example | result | | ----------------------- | ------- | | `"".isBase64()` | `true` | | `"=".isBase64()` | `false` | | `"a=".isBase64()` | `false` | | `"Zm9vYmE=".isBase64()` | `true` | #### isEmail Returns true if the text is a valid mail address. | example | result | | ---------------------------------- | ------- | | `"john.doe@example.com".isEmail()` | `true` | | `"john doe@example.com".isEmail()` | `false` | | `"john.doe@example".isEmail()` | `false` | To be considered valid, a mail address must satisfy the following regular expression: ```javascript ^[\w-]+(?:[\.+][\w-]+)*@(?:[\w-]+\.)+[a-zA-Z0-9-]{2,}$ ``` --- ## Viewer3d type The `Viewer3d` type provides helper functions which can be used with slots of the [Viewer 3d control](/docs/configurator-ui-ux/controls/3d-viewer-control). ### Usage To use any of the functions described on this page, you must prefix them with the `Viewer3d` type name: ```hive Viewer3d.modelParameter(Models3d.CoffeeCup, "visible", true) ``` :::warning `Models3d.CoffeeCup` is the preferred syntax to refer to a babylon asset for `Viewer3d.modelParameter()`. This term will generate the valid string (`"Models3d/CoffeeCup"`) for our 3d viewer. In order to avoid future breaking change, DO NOT construct this string on your own. ::: ### Functions - `modelParameter(modelName, parameterName, parameterValue)` - `tagParameter(tagName, parameterName, parameterValue)` - `nodeParameter(nodeName, parameterName, parameterValue)` - `materialParameter(materialName, parameterName, parameterValue)` All these functions can be used to create records with parameter descriptions that can be passed to the viewer 3d control's [Set parameter value](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) slot. See the [Set parameter value](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) docs for more details on how to use these functions and what values to pass to them --- ## The if expression The if expression allows you to perform different actions based on one or more conditions. In the example below, we calculate the price of a product. If the customer buys at least 5 units of our product, we grant him a discount. Price refers to a component named price and amount to the component named amount. ```hive if Amount >= 5 then Price * 0.9 else Price end ``` Plain text To break this down: Writing an if expression follows these rules: - Every if expression begins with the keyword if and an arbitrary logic expression: the condition. - If the condition is true, the expression that follows the then keyword will be executed. - If the condition is false, the expression that follows the else keyword will be executed instead. - Every if expression ends with the keyword end. This is to distinguish the else expression from any subsequent ones. We will see why this is necessary in the next example. :::warning All if expressions must have an else expression. Many other programming languages do not have this requirement, so if you're a programmer then this might feel a bit unusual. ::: ### Multiple conditions You can also define more than one condition in an if expression. Let's look at a more complex example. Imagine we wanted to give customers an additional discount if they buy more than 9 units, and let's also add some shipping costs to the price: ```hive if Amount > 9 then Price * 0.8 elif Amount >= 5 then Price * 0.9 else Price end + ShippingCosts ``` Let's break it down again: - We first check if the amount is greater than 9. We must check for this first because only one condition in an if can be true, and conditions are looked at one after another. If the greater-than-5 condition would come first, any number greater than 9 would also satisfy it and we'd never reach the next condition. - Subsequent conditions start with the elif keyword (that's a combination of else and if). Aside from the queer name, it's basically the same as the initial if. - You can use as many elif expressions as you like, but there can only be one if and one else expression. - Every if expression results in a value that can be processed further. In this case, we add the ShippingCosts to whatever price was calculated by the if. - This is also the reason why every if must have an else expression: without it, there is no guarantee that the if would return a value. In our example, an amount of 3 would satisfy neither of our two conditions. --- ## User variable The `user` variable contains information about the current user, provided they are signed into the Combeenation platform. Otherwise, the current user is considered anonymous and the variable will contain fallback values. ### Properties | name | description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `isLoggedIn` | `true` if the user is signed into the Combeenation platform and is a member of the CompanyOn the Combeenation platform, a company means exactly what it describes: a company. For the most part, these are customers who have ordered a configurator from us. However, there are also internal companies that we use for best practice examples, for testing or for internal configurators. that owns the configurator, `false` otherwise | | `id` | the unique identifier of the user | | `firstName` | the first name of the user or an empty text if not set or the user isn’t signed in | | `lastName` | the last name of the user or an empty text if not set or the user isn’t signed in | | `mail` | the mail address of the user or a fallback value if the user isn’t signed in (currently `anonymous@combeenation.com`) **Note** the fallback mail address does not exist, mails cannot be sent to it. It's intended to assist in debugging, not for actual use! | | `distributor` | the name of the distributor the user belongs to, or an empty text if they belong to the company | | `avatar` | the `FileUrl` of the user's avatar image, or an empty text if none is set | | `fields` | Container for all [custom field](/docs/fundamentals/custom-fields#user-fields) values, provided any have been created in the CompanyOn the Combeenation platform, a company means exactly what it describes: a company. For the most part, these are customers who have ordered a configurator from us. However, there are also internal companies that we use for best practice examples, for testing or for internal configurators. | --- ## Accessibility ### General Accessibility ensures that web applications like your configurator, are usable by everyone, including people with disabilities. This includes individuals with visual, auditory, motor, and cognitive impairments. By following accessibility best practices, the configurator can create an inclusive experience that allows all users to interact with the configurator without barriers. To get an overview and an understanding of what accessibility is, we can recommend the following resources: - [How to review for accessibility](https://web.dev/articles/how-to-review?utm_source=lighthouse&utm_medium=devtools) - [A11ycasts! - Youtube Playlist](https://www.youtube.com/watch?v=HtTyRajRuyY&list=PLNYkxOF6rcICWx0C9LVWWVqvHlYJyqw7g&index=3) - [WebAIM](https://webaim.org/) #### European Accessibility Act (EAA) The European Accessibility Act (EAA) is a directive aimed at improving accessibility across digital and physical products and services within the European Union. Starting **June 28, 2025**, businesses offering digital solutions, including configurators, must ensure compliance with accessibility standards. It has to align with the **WCAG 2.2 Level AA** standards for web accessibility. ##### Legal note By ensuring compliance with the EAA, businesses not only avoid legal risks but also create a more inclusive experience for all users. ### Tools Besides the built-in features from browsers or the OS in general, there are helpful tools and extensions: - [Chrome Lighthouse](https://developer.chrome.com/docs/lighthouse/overview?hl=de) - [WebAIM - Wave browser extension](https://wave.webaim.org/extension/) - [NVDA Screenreader (NV Access)](https://www.nvaccess.org/download/) ### Configurator editor :::info While the configurator editor aids in accessibility implementation, using these features does not automatically guarantee full compliance with accessibility standards (e.g., WCAG, EAA, ADA). **Final compliance should be verified through audits and real-world testing.** ::: #### aria-label Form controls like a `Textbox`, `Checkbox` or `Button` should always have a describing label. The configurator automatically chooses the "best fitting property" for the label. ##### Example A button is only displayed as icon without any visible text. - For a visually impaired user it might be impossible to grasp what this button does - Entering an `aria-label` would solve this, but it might still be hard to understand for non-impaired users, solely by the icon Solution: The best option is to enter a `Tooltip text` as it shows a describing text when navigating to it (mouse hover or keyboard) and the configurator will also automatically set this text as `aria-label`. ![image.png]() ##### Automated check By default the editor will check if every input control has a corresponding label and will show a warning otherwise. ![image.png]() #### Custom CSS Styles Because the CSS can be fully customized it is up to the configurator creator to provide an accessible styling. - Text size - Color contrast - Outline (keyboard navigation) - etc. See also [WCAG Checklist](https://webaim.org/standards/wcag/checklist) --- ## 3d viewer control with custom code If you need more functionality than the [3d viewer control](/docs/configurator-ui-ux/controls/3d-viewer-control) provides out of the box in the configurator editor, you can use [custom code](/docs/category/custom-code) to extend it by adding custom parameter observers, adjusting the viewer settings and much more. ### Basic setup & usage - Set the 3d viewer control's [viewer version property](/docs/configurator-ui-ux/controls/3d-viewer-control#viewer-version) to `Injected` - Install the viewer in your custom code with `npm i @combeenation/3d-viewer` - Use the viewer control's [injectViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#injectViewer) method to pass the viewer to the control: ```js import { Viewer } from '@combeenation/3d-viewer'; // ... CfgrUtils.onCfgrReady(() => { // ... CtrlUtils.getViewerCtrl('viewer3d').injectViewer(Viewer); // ... }); ``` - Use the viewer control's [getViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#getViewer) to get access to the viewer instance and the [Babylon.js scene](https://doc.babylonjs.com/typedoc/classes/BABYLON.Scene). ```js const viewerCtrl = CtrlUtils.getViewerCtrl('viewer3d'); const viewer = await viewerCtrl.getViewer(); // E.g. create custom skybox from JPGs const scene = viewer.scene; const customReflection = new CubeTexture('', scene, null, null, files); const customSkybox = scene.createDefaultSkybox(customReflection, true, 10000); ``` ### Common use cases When extending the viewer control's capabilities in custom code, there are almost no limits in what you can do. If you want to, you can have direct access to the underlying [Combeenation 3d viewer](https://3dviewer.docs.combeenation.com/) & [Babylon.js](https://www.babylonjs.com/) core which power the viewer control. In the following chapters we'll explore some common use cases and explain how to best deal with them. #### Add custom parameter observer The viewer comes with some [built in parameters](https://3dviewer.docs.combeenation.com/variables/BuiltInParameter.html) which you can use with the control's [set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) slot but you can also add your own, custom parameters using custom code. These observers should be added **before** the initial viewer parameter update, as this update would not trigger the custom observers then. Since [getViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#getViewer) is awaiting this initial parameter update, we have to access the observer in a different way. For this case we can use the `onAfterViewerCreated` parameter of [injectViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#injectViewer). This example shows, how to add a custom parameter named `ShowBoundingBox` with which you can show or hide a meshs bounding box: ```js CtrlUtils.getViewerCtrl('viewer3d').injectViewer(Viewer, { onAfterViewerCreated: (viewer: Viewer) => { viewer.tagManager.setParameterObserver('ShowBoundingBox', async ({ nodes, newValue }) => { nodes.filter(node => node instanceof AbstractMesh).forEach(node => (node.showBoundingBox = newValue)); }); } }); ``` You can set & change the value of all custom parameters with the control's `set parameter values` slot like with all built in parameters. #### Adjust viewer settings The viewer control automatically creates a viewer instance which comes with a set of default values. You can customize these viewer settings **before** the viewer instance is **created**, using the `onBeforeCreateViewer` parameter of [injectViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#injectViewer). ```js CtrlUtils.getViewerCtrl('viewer3d').injectViewer(Viewer, { onBeforeCreateViewer: (viewerSettings: ViewerSettings) => { viewerSettings.useParallelShaderCompile = false; viewerSettings.engineOptions.useHighPrecisionMatrix = true; } }); ``` #### React on parameter application There are cases where you want to execute code right **after** viewer parameters, set by the [set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) slot, have been applied. For example changing light setup or moving the camera to show the adjusted scene in a better way. There is the `onAfterParametersApplied` parameter of [injectViewer](https://custom-code.docs.combeenation.com/classes/Cbn-1.ctrls.Viewer3d.html#injectViewer) to achieve this. ```js CtrlUtils.getViewerCtrl('viewer3d').injectViewer(Viewer, { onAfterParametersApplied: async (modelVisibility: ModelVisibilityEntry[], parameterValues: ParameterValues) => { // e.g. center camera if model has been changed if (modelVisibility.length) { await centerCamera(); } } }); ``` --- ## 3d viewer control The 3d viewer control gives you the power to **showcase your 3d models** in the configurator. It allows you to dynamically compose the shown scene & change it at runtime so that the user always sees what he's about to buy. Amongst other things, the 3d viewer gives you the following options to **dynamically adjust the scene**: - Show & hide complete models - Show & hide specific nodes of a model - Changing certain aspects of specific nodes of a model: - Reposition, rotate & scale nodes - Change shown materials & adjust material details like color, roughness, metallness etc. - Draw dynamically generated content like text or images onto nodes Once the user is happy with what he sees, he can use the control's built in **[AR mode](/docs/configurator-ui-ux/controls/3d-viewer-control#start-ar)** to experience the customized model in an very engaging manner. ### How to show models After creation, the 3d viewer control shows all [3d assets](/docs/asset-management/3d-and-material-assets/basic-workflow) from all asset bundles which are [assigned to your configurator](/docs/asset-management/assign-asset-bundles-to-configurators). You can use the [set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) slot to overwrite this behavior and actively define which models it should show or not. ### How to alter 3d models You can use the [set parameter values](/docs/configurator-ui-ux/controls/3d-viewer-control#set-parameter-values) slot to control most dynamic aspects of the scene like which models & nodes are shown, which materials & colors are used etc. ### Properties Besides the general control properties like `width`, `height`, `background` etc., the 3d viewer control comes with some special properties: #### Viewer version The 3d viewer control is powered by our own [3d viewer](https://3dviewer.docs.combeenation.com/) and we give you the freedom to chose the exact version which should be used by the 3d viewer control. When first creating the control, we automatically set this to use the latest available version so you don't have to touch this at all. Please consult the [3d viewer release notes](https://3dviewer.docs.combeenation.com/pages/release-notes/releases/index.html) when changing the viewer version to get an idea of what might change with the new version. The option `Injected` is used when extending the viewer control's capabilities by custom code. See [3d viewer control with custom code](/docs/configurator-ui-ux/controls/3d-viewer-control-with-custom-code) for more details on how to use this. #### Load in editor When set to `false`, no 3d content will be loaded when inside the design editor. This can be helpful in situations where you're working on things which are not related to the shown 3d content like overall configurator layout or logic. This setting only affects the design editor & does not change the 3d viewer control's behavior in preview or published configurators. #### Autofocus model Defaults to `true`, which automatically autofocuses the camera after the visibility of a certain model has been changed. The camera will be zoomed in as far as possible while the whole scene content remains visible. This behaviour is benefitial for simple projects, as the user won't get lost in the scene after switching models. However it is more of a hinderance if the camera is controlled via custom code. In this way the user can deactivate the autofocus behaviour by setting this flag to `false`. If this flag is set, the viewer control will also automatically adapt the **ground size** and mesh list for **shadow generation** according to the scene content, defined by the currently visible models. This only takes effect if dedicated ground meshes and shadow lights are available, which is the case if the "Grounded" scene asset is selected. #### Scene asset A "scene asset" represents a collection of settings that define the appearance of a scene. It includes: - light elements, shadow configuration and environment - camera limits and settings - environment meshes like ground or skybox - global scene settings like background color You can choose a "**Grounded**" scene asset or "**-**", if none should be used. ##### Grounded The "Grounded" scene asset comes with a ground mesh, that receives shadows from the currently visible models. These shadows are created by a directional light, which serves as an additional lighting source to the environment texture. Also the camera can not move underneath the ground, making the scene appear more realistic. #### Environment URL This property is deprecated and will be ignored if the "**scene asset**" property is used, as the environment texture is part of the scene asset. The environment is responsible for reflections and indirect lighting of the 3d scene. Without it and without any other lights, all materials in the 3d scene are rendered completely black. See [Babylon.js docs](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/HDREnvironment) for further info about environments and IBL in general. This property defaults to a [environment asset](https://assets.babylonjs.com/environments/environmentSpecular.env) provided on the Babylon.js server. You can change the property either via providing a static url or a hive value. The latter is the preferred one, as you can access environment files from the [asset editor](/docs/asset-management/3d-and-material-assets/basic-workflow) and adjust the environment at runtime in this case. ### Slots You can use the following slots the 3d viewer control to interactively change the 3d scene and its shown 3d content at runtime: #### Set parameter values The `set parameter values` slot is the central piece of the 3d viewer control which lets you adjust the scene to your likings & change it based on the users input in the configurator. This slot lets you: - Define which 3d models are visible or not - Use the viewers [parameter system](https://3dviewer.docs.combeenation.com/classes/ParameterManager.html) to show or hide nodes & set other parameters like used materials, color, position etc. ##### Basic usage The `set parameter values` slot needs to be connected to a [value component](/docs/configurator-logic/components/value-component) which creates a list of records from the [Viewer3d type](/docs/configurator-logic/hive-types/viewer3d-type) parameter functions `modelParameter`, `tagParameter`, `nodeParameter` or `materialParameter`. E.g.: ```hive [ // Set visibility of 3d models // // 3d asset path, parameter name, value Viewer3d.modelParameter(Models3d.CoffeeCup, "visible", true), Viewer3d.modelParameter(Models3d.CoffeeCoaster, "visible", false), // Set parameter values using the tagging system // Here: Show all nodes which contain the tag "Handle" and hide all nodes // which contain the tag "Cap" // Also set "metallic" value to 1 for all materials which contain the tag "Metal" // // Tag name, parameter name, value Viewer3d.tagParameter("Handle", "visible", true), Viewer3d.tagParameter("Cap", "visible", false), Viewer3d.tagParameter("Metal" "metallic", 1), // Set parameter values using the node system // Here: Hide the node with the name "Spoon" & set a material for it which // will be applied once the value of the "visible" parameter is changed to // `true` at a later point // // Node name, parameter name, value Viewer3d.nodeParameter("Spoon", "visible", false), Viewer3d.nodeParameter("Spoon", "material", "SilverMat"), // Set parameter values using the material system // Here: Set color red and roughness 0.1 to material "matHandle" // // Material name, parameter name, value Viewer3d.materialParameter(Materials3d.matHandle, "color", "(255, 0, 0)"), Viewer3d.materialParameter(Materials3d.matHandle, "roughness", 0.1) ] ``` In real live, you'll usually get the parameter values from other components whose result changes based on some user input or product logic: ```hive [ // "CupIsVisible" & "HandleColor" are components Viewer3d.modelParameter(Models3d.CoffeeCup, "visible", CupIsVisible), Viewer3d.tagParameter("Handle", "color", HandleColor) ] ``` JavaScript ##### Available 3d models With `Viewer3d.modelParameter` you can show or hide all 3d assets from all asset bundles which are assigned to your configurator. ##### Available tag, node and material parameters With the tag, node and material parameter functions you can either use the viewers [built in parameters](https://3dviewer.docs.combeenation.com/variables/BuiltInParameter.html) or you can use custom parameters which are defined & handled by custom code. The available built in parameters for **nodes** are the following: - visible - material - position - rotation - scaling - influence The available built in parameters for **materials** are the following: - color - roughness - metallic Some parameters require an advanced parameter value structure. You can use built-in Hive functions, which return the value in the expected stringified structure: ```hive [ // move node to position x=1, y=0, z=0 Viewer3d.nodeParameter("Spoon", "position", Viewer3d.vector3(1, 0, 0)), // set rotation around y to 90 degrees Viewer3d.nodeParameter("Spoon", "rotation", Viewer3d.vector3(0, 90, 0)), // invert scaling in z direction Viewer3d.nodeParameter("Spoon", "scaling", Viewer3d.vector3(1, 1, -1)), // set color to "red" (FF0000) Viewer3d.materialParameter(Materials3d.matHandle, "color", Viewer3d.color3(255, 0, 0)), // set morph target influence value of morph target "morphTarget1" from node "node1" to 0.6 Viewer3d.tagParameter("node1", "influence", Viewer3d.morphTarget("morphTarget1", 0.6)) ] ``` In addition to the "basic" material parameters mentioned above, you can also use **texture parameters** for materials. Texture parameters are available for multiple "channels" (e.g. `albedoTexture`, `bumpTexture`) in the material, therefore the parameter configuration has to be defined as **dotted path**. ```hive [ // assign checker texture to albedo channel of material "matBox" Viewer3d.materialParameter(Materials3d.matBox, "albedoTexture.image", Textures3d.Checker), // update "uScale" value depending on a component value Viewer3d.materialParameter(Materials3d.matBox, "albedoTexture.uScale", uScale) ] ``` The available **texture channels** are the following: - albedoTexture - metallicRoughnessTexture - bumpTexture - emissiveTexture - opacityTexture - ambientTexture - lightmapTexture - detailmapTexture Built in **parameters** for these texture channels are the following: - image: supports **url** and **svg** strings - uOffset - vOffset - uScale - vScale - uAng - vAng - wAng - clampU - clampV - uvSet According to the underlying Babylon.js engine, "detail maps" require an additional flag for activation. This flag can be adjusted with the `useDetailmap` parameter. ```hive [ Viewer3d.materialParameter(Materials3d.matBox, "detailmapTexture.image", Textures3d.Checker), Viewer3d.materialParameter(Materials3d.matBox, "useDetailmap", true) ] ``` Tags can be used for nodes and materials alike, therefore `viewer3d.tagParameter` supports all built in parameters in general. ##### Custom parameters When using custom parameters, you pass the name defined by [setParameterObserver](https://3dviewer.docs.combeenation.com/classes/ParameterManager.html#setParameterObserver) to `Viewer3d.tagParameter`, `Viewer3d.nodeParameter` or `Viewer3d.materialParameter`. See [3d viewer control with custom code](/docs/configurator-ui-ux/controls/3d-viewer-control-with-custom-code) to get more information about how to define them in custom code. #### Set environment URL Allows you to use an environment from the asset editor and/or change the environment dynamically at runtime. Usually you use an environment from the asset editor by creating a value component which provides the URL of the environment file using the following Hive rule: ```hive // "Cups3d" = Name (or alias) of bundle assigned to configurator // "env" = Folder name in which "studio" is located // "studio" = Asset name Cups3d.env.studio.url ``` You can then connect this component to the `Set environment URL` slot. See [environment URL](/docs/configurator-ui-ux/controls/3d-viewer-control#environment-url) property for more details about environment files in general. #### Auto focus camera This slot can be used to move the camera to a position where all active nodes in the scene are visible. Calling this slot with default parameter values also adjusts the cameras panning sensibility as well as its pinch & wheel precision to provide a good usability regardless of the size of the shown models. The slot accepts the following optional parameters to adjust this default behavior to your likings: - `Alpha`, `Beta` & `Radius factor` to adjust the final camera position. - `Adjust panning sensibility`, `Adjust pinch precision` & `Adjust wheel precision` to define whether those camera settings should be auto adjusted or not. - `Exclude tags` & `Exclude nodes` to exclude the given nodes from the calculation. Both those parameters expect a coma separated list of node or tag names like `tagName1, tagName2, tagName3`. See the viewers [autofocusActiveCamera](https://3dviewer.docs.combeenation.com/classes/CameraManager.html#autofocusActiveCamera) for more details about the parameters. #### Start AR The AR mode can be activated with the `Start AR` slot. The viewer automatically detects if the device is capable of showing the model in an augmented reality environment. The minimum requirement for an AR capable device is that it comes with a camera (mobile phones, tablets), but also some browser and operating system limitations are considered. Have a look at Googles AR core [documentation](https://developers.google.com/ar/devices) for more details about AR capabilities. ##### AR capable devices If the device is AR capable, the augmented reality scene will be entered automatically. The user will be redirected to the corresponding app, which is responsible for showing the configured 3d model in the AR environment. ##### Non AR capable devices If the configurator is started from a PC or if the used mobile device doesn't match the AR requirements, the viewer will show a QR code instead, which can then be scanned by an AR capable mobile device. ![image.png]() The QR code will lead to the exact same AR endpoint that would be reached when starting the AR journey from an AR capable device directly. ##### Slot parameter - `QR subtitle`, `QR description`: Customizable text for QR code layer ("See the product at home" & the text below in the screenshot above). - `Preparation text`: Customizable text for the load mask which is shown whilst the 3d model is being prepared for the AR scene - `Popup blocked title`, `Popup blocked description`, `Popup blocked link text`: Customizable text that will be shown if the AR endpoint can't be reached due to a popup blocker - `Exclude tags`, `Exclude nodes`: Filter for hiding certain nodes in the AR scene - `Environment`: HDR environment for the AR viewer on the mobile device - `Loading screen logo`: Image URL for logo on the AR loading screen ### How to draw dynamic content on the 3d model You can use **texture parameters** for drawing images on certain meshes. Check out the "[Decals](/docs/asset-management/3d-and-material-assets/decals)" chapter to find out how to create such meshes on the Combeenation platform. You can also target existing meshes as basis for the texture parameter. Anyway in most cases `albedoTexture.image` is the preferred option, but other texture channels can be used as well. For example `bumpTexture.image` works well for engravings. Here are some tips and tricks for practical usage of these parameters. 1. Use `GraphicView.Value.ToText()` to use the content of a [Graphic component](/docs/configurator-logic/components/graphic-component) as texture image ```hive [Viewer3d.materialParameter(Materials3d.matPaintable, "albedoTexture.image", PaintableGraphic.Value.ToText())] ``` 2. Use additional texture settings parameter like `uScale`/`vScale` to scale or invert your texture and `uOffset`/`vOffset` for tweaking the position on the desired mesh ```hive [ // negative UV scaling for inverting the image Viewer3d.materialParameter(Materials3d.matPaintable, "albedoTexture.uScale", -1), Viewer3d.materialParameter(Materials3d.matPaintable, "albedoTexture.vScale", -1) ] ``` 3. Create individual components for texture image parameters with large string values (eg: SVGs) :::info The **whole component** value will be updated by the server, whenever a change within the component has been detected. This can lead to larger network load if large string values are involved, which can be the case when using SVGs or data strings in the texture image parameter. ::: It can be avoided, if the parameters are split across multiple components. Each of these parameter components has to be linked as an individual `"set parameter values"` slot though. ```hive // ❌ Don"t: Single component "ParameterValues" [ Viewer3d.tagParameter("Handle", "visible", HandleVisible), Viewer3d.tagParameter(Materials3d.matHandle, "color", HandleColor), Viewer3d.materialParameter(Materials3d.matPaintable, "albedoTexture.image", PaintableGraphic.Value.ToText()) ] // If component "HandleVisible" changes, the whole content of the component // "ParameterValues" will be updated, even if the graphic component // "PaintableGraphic" didn"t even change // => 🔥 Lots of unnecessary network load ``` ```hive // ✅ Do instead: Split into 2 components with 2 SigSlos: // Component "StandardParameterValues" [Viewer3d.tagParameter("Handle", "visible", HandleVisible), Viewer3d.tagParameter(Materials3d.matHandle, "color", HandleColor)][ // Component "PaintableGraphicParameterValue" Viewer3d.materialParameter(Materials3d.matPaintable, "albedoTexture.image", PaintableGraphic.Value.ToText()) ] // If component "HandleVisible" changes, only the component // "StandardParameterValues" will be updated // Component "PaintableGraphicParameterValue" stays untouched // => 👍 Less network load ``` 4. Be cautious when interacting with other properties of the affected material 4.1 Albedo Color The materials [albedoColor](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRMaterial#albedoColor), which is set by the "color" parameter, will be layered with the "albedoTexture" from the albedo texture parameter. The resulting texture will be a mixture of "albedoTexture" and "albedoColor" . If this is not desired, make sure to set the "albedoColor" to white, in this case the paintables original color will be preserved. Texture with **turquoise** color, albedo color is set to **white**. Original turquoise color is preserved. ![image25]() **Turquoise** texture overlapped with **red** albedo color. Original turquoise color is altered. ![image.png]() This also works the other way around. If you want to control the paintables color via the "albedoColor" property you have to make sure, that the original albedo texture image is completely **white** as well. 4.2 Transparency Mode You may face the issue that transparent areas of albedo texture images appear black. ![image.png]() This can be fixed by tweaking some material settings: - set [transparencyMode](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRMaterial#transparencymode) of the desired material to **2**, which is "alpha blend". - activate [useAlphaFromAlbedoTexture](https://doc.babylonjs.com/typedoc/classes/BABYLON.PBRMaterial#usealphafromalbedotexture) - activate [albedoTexture.hasAlpha](https://doc.babylonjs.com/typedoc/classes/BABYLON.Texture#hasalpha) ![image.png]() :::info These properties are automatically set when creating a material asset from the "**Transparent SVG**" template. ![image.png]() ::: ### Extension with custom code If you need more functionality than the 3d viewer control provides out of the box, you can use custom code to extend it by adding custom parameter observers, adjusting the viewer settings and much more. See [3d viewer control with custom code](/docs/configurator-ui-ux/controls/3d-viewer-control-with-custom-code) for more details on how to do this. --- ## 3d viewer docs For detailed description of the 3D viewer api see the dedicated [3d viewer documentation](https://3dviewer.docs.combeenation.com/). --- ## Combobox The combobox control allows you to present tabular data in an interactive dropdown list, showing a variety of options of which the user can chose one. Here is an example of such a combobox showing a list of products: ![image.png]() ### Basic usage The usage of the combobox is mostly similar to the [dataview](/docs/configurator-ui-ux/controls/dataview). The most notable difference is that the combobox only comes with a **default layout** and provides **no custom layout** like the dataview. See the following sections of the dataview for more details: - [Quick start](/docs/configurator-ui-ux/controls/dataview#quick-start) - [How to connect the data](/docs/configurator-ui-ux/controls/dataview#how-to-connect-the-data) - [How to prepare the table component](/docs/configurator-ui-ux/controls/dataview#how-to-prepare-the-table-component) - [Minimize the data](/docs/configurator-ui-ux/controls/dataview#minimize-the-data) - [How to handle user selections)](/docs/configurator-ui-ux/controls/dataview#how-to-handle-user-selections) --- ## Configurator control The configurator control is a static control which exists by default in every configurator and cannot be deleted or renamed. It has no visual representation and mainly serves as a hub for Signals and Slots which neither belong to a control nor a component. ### SigSlos #### IFrame size The size of the configurator itself can be observed with the `Size changed` signal and adjusted with the slots `Set IFrame width` & `Set IFrame height`. E.g. you can create the following `SigSlo` to write the current width of the configurator into the `CfgrWidth` components `input` every time the configurators size changed: ```none Configurator.SizeChanged -> CfgrWidth.SetValue(Configurator.ConfiguratorWidth) ``` :::info The `Size changed` `SigSlo` also works in the design editor. Therefore one advantage of implementing different layouts based on the configurator size saved in a component like `CfgrWidth` & `CfgrHeight` versus using CSS media queries is that the former allows easier testing and editing for different sizes live within the design editor. ::: The `Set IFrame width` & `Set IFrame height` slots allow you to set a dedicated width & height for the IFrame on the [parent page](/docs/integrations-and-api/embedding-and-whitelist) in which the configurator is embedded. Those slots won't do anything if the configurator is opened "full screen" (not embedded in parent page). :::warning We can't guarantee that the slots `Set IFrame width` & `Set IFrame height` are working as expected under all circumstances. For example, the parent page could interfere by defining the IFrame's size using some very strong CSS rules etc. ::: #### Load mask You can show & hide a system load mask with some text of your choice with the slots `Show load mask` & `Hide load mask`. #### Restart configuration The configurator control's `Restart configuration` slot can be used to restart a running configuration. The slot takes 2 paramters: - `Preset ID`: The preset ID from which the components `input` values should be copied from. If not given (an empty string), the preset of the currently open configuration is used. - `Copy configuration values`: True to copy all component `input` values to the newly created configuration. False to create a completely new configuration. Possible use cases for this slot: - Start a new configuration after the checkout succeeded to allow the user to configure a second product. - Reset configurator to start afresh. #### Redirect parent page Allows the configurator to change the URL of the parent page. This requires the correct embedded Combeenation plugin on the [parent page](/docs/integrations-and-api/embedding-and-whitelist). #### Fire analytics event Forwards certain event data into the "[data layer](https://developers.google.com/tag-platform/tag-manager/datalayer?hl=de)", which can then be interpreted by the [Google Tag Manager](https://developers.google.com/tag-platform/tag-manager?hl=de). If the configurator is embedded in an IFrame, the event data will be forwarded to the data layer of the parent page instead. This requires the correct embedded Combeenation plugin on the [parent page](/docs/integrations-and-api/embedding-and-whitelist). The slot takes 2 parameters: - `event`: Key for event identification - `eventData`: Optional metadata for the event. By default this is a string, valid JSON notation like `{ "price": 10 }` will be interpreted and forwarded as object. --- ## Control DOM & CSS classes ### Common classes for all controls - cfgr-ctrl - cfgrctrl-disabled ### Label div.cfgr-label div.cfgrctrl-innerlabel ### Buttons - Button - Checkout Button - Share Button - Wizard navigator button {"button"} {".cfgr-btn"} {".cfgr-checkoutbtn"} {".cfgr-sharebtn"} {".cfgr-wizardbtn"} {".cfgrctrl-pressed"} {"div.cfgrctrl-innerlabel"} :::info The inner structure of an upload button differs a little. ::: ### Textbox {"div.cfgr-textbox"} {"label.cfgrctrl-caption"} {"input.cfgrctrl-field.cfgrctrl-input"} :::info The `input` element is a `textarea` for multiline textboxes ::: ### Checkbox & Radiobutton - Checkbox - Radiobutton {"div"} {".cfgr-checkbox"} {".cfgr-radiobutton"} {".cfgrctrl-pressed"} {"label.cfgrctrl-caption"} {"div.input-wrapper"} {"input.cfgrctrl-input.cfgrctrl-field"} {":checked:disabled"} {"label.custom-bckgr"} ### Panel, Layer & Wizard Page - Panel - Layer - Wizard Page {"div.cfgr-container"} {".cfgr-panel"} {".cfgr-layer"} {".cfgr-wizardpage"} {".cfgrctrl-withheader"} {".cfgrctrl-collapsible"} {".cfgrctrl-expanded"} {".cfgrctrl-collapsed"} {"div.cfgr-container-header"} {"label.cfgr-container-headerlabel"} {"div.cfgrctrl-collapse-icon"} {"div.cfgr-container-inner"} {"Child controls"} ![](https://docs.combeenation.com/assets/images/panel-bd92efbb8fcbb499632f6fcc95a56de9.png)click to expand ### Wizard & Wizard Navigator - Wizard - Wizard navigator {"div.cfgr-container.cfgr-panel"} {".cfgr-wizard"} {".cfgr-wizardnavigator"} {"div.cfgr-container-inner"} {"Wizard page"} {" "} {"Wizard navigator button"} ### Dataview :::info The divs for the image (".cfgr-dataview-item-img") and label are not there when using a [custom layout](/docs/configurator-ui-ux/controls/dataview#layouting) with your dataview. ::: {"div.cfgr-panel.cfgr-dataview"} {"div.cfgr-dataview-view"} {"div.cfgr-dataview-item"} {".cfgrctrl-hover.cfgrctrl-selected"} {"div.cfgr-dataview-item-img"} {"label"} ![](https://docs.combeenation.com/assets/images/dataview-595686af0d8db880d4b4e489bc609db5.png) ### Combobox {"div.cfgr-combobox"} {"label.cfgrctrl-caption"} {"div.input-wrapper"} {"div.x-form-item"} {"div.x-form-item-body"} {"div.x-form-trigger-wrap"} {"div.x-form-text-wrap"} {"input.cfgrctrl-field"} {"div.x-form-trigger"} {"div.cfgr-combobox-list"} {"div.cfgr-combobox-list-list-ct"} {"ul.x-list-plain"} {"div.cfgr-combobox-item"} {".cfgrctrl-hover.cfgrctrl-selected"} {"label"} {"div.cfgr-combobox-item-img"} ![](https://docs.combeenation.com/assets/images/combobox-e386b8d3b3f771b70ca0c69716a43a10.png) The inner structure of `div.cfgr-combobox-item` may vary depending on the value of the comboboxs Layout property. The `div.x-form-trigger` is hidden in most themes since they implement the "drop down icon" using CSS after & before pseudo selectors. ### Graphicview {"div.cfgr-graphicview"} {"svg"} ### External content - External content - ShapeDiver 3d {"div.cfgr-externalcontent"} {".cfgr-shapediver3d"} {"iframe.cfgr-iframe"} ### Slider {"div.cfgr-slider"} {"div.slider"} {"div.noUi-base"} {"div.noUi-origin"} {"div.noUi-handle"} ![](https://docs.combeenation.com/assets/images/slider-e312b81082b4ba8548b8add9b91928a4.png) ### Viewer 3d {"div.cfgr-viewer3d"} {"canvas"} {"div.viewer-load-mask"} {"div.viewer-load-mask-icon"} {"div.viewer-load-mask-text"} {"div.viewer-error-text"} {"span"} {"span.viewer-error-text-hide-icon"} #### Augemented reality layer The viewers augmented reality layer is implemented in the "cfgr parent" control, to ensure that the layer is shown across the whole configurator an not only above the viewer control. The layer still consists of viewer CSS classes and can be adapted accordingly. {"div.viewer-ar-layer"} {"div.viewer-ar-layer-content-panel"} {"div.viewer-ar-layer-exit-icon"} {"div.viewer-ar-layer-qr-code"} {"span.viewer-ar-layer-sub-title"} {"span.viewer-ar-layer-description"} {"a.viewer-ar-layer-link"} --- ## Dataview The dataview control allows you to present tabular data in a visually appealing and user-friendly interface. You can seamlessly showcase information with diverse layouts, allowing for customization based on specific needs. The dataview usually allows your users to select 1 item from a set of different options. Here is an example of such a dataview showing a list of vertically stacked products, where the second one is currently selected and the last one is disabled as it is out of stock at the moment: ![image.png]() ### Quick start The data shown in a dataview usually comes from a [table component](/docs/configurator-logic/components/table-component). It can also come from a [record component](/docs/configurator-logic/components/record-component) but that is an older approach which is not recommended anymore. Each record (or row) in the connected table component is represented by 1 entry (or item) in the dataview. The example dataview shown above, is connected to a table component which consists of 4 records. The usual workflow to populate the dataview with data looks like this: 1. Create a table component which queries the data you'd like to show from a [data source](/docs/asset-management/data-source-asset/basics). When using the dataviews default layout, you need to ensure that your query results in at least 2 columns named `value` & `text`. You can rename the columns of your data source if it does not come with a `value` & `text` column. See [projecting the data](/docs/configurator-logic/hive-queries#projecting-data) in the Hive Queries documentation for more details on how to rename columns in a query. 2. Enable Hive for the dataviews `entries` property by clicking on the code icon next to the properties input field. 3. Get the data from the table component by writing a simple script like `NameOfTableCmp` in the `entries` property. See [how to connect the data](/docs/configurator-ui-ux/controls/dataview#how-to-connect-the-data) and [how to handle user selections](/docs/configurator-ui-ux/controls/dataview#how-to-handle-user-selections) for more details on how to properly prepare the connected table component etc. ### Layouting You can either use the **default layouting** options which allows you to show exactly **1 text and 1 image** per item (or entry) or you can use a **[custom layout](/docs/configurator-ui-ux/controls/dataview#custom-layouts)** which gives you the freedom to provide your **own HTML layout** and show **arbitrary data**. The dataview uses the default layout when the `custom layout` property is not set. You can find a set of customization options for the default layout within the `item layout` section in the dataviews property list. Those properties allow you to show/hide the text, position it, change the font, adjust spacings & sizes etc. You can always use [custom CSS](/docs/category/custom-css) to further customize the look of your dataview but you cannot change the shown content with a default layout which is where the `custom layout` comes into play. ### How to connect the data The `entries` property is where your dataview **receives its data to show from**. You usually connect it to a [table component](/docs/configurator-logic/components/table-component) which queries data from a [data source](/docs/asset-management/data-source-asset/basics) like an Excel file. You usually want to directly reference the records of a table component which you can do by **first activating Hive** for the `entries` property and then simply writing the name of the table component into the property. You can also write [queries](/docs/configurator-logic/hive-queries) directly into the `entries` property, e.g. to only return relevant columns or adjust column values. #### How to prepare the table component ##### Default layout When using the default layout (i.e. when property `custom layout` is not set), your table component **needs to provide columns** with the following names: - `value` (required) - `text`, `imageUrl`, `itemTooltip` & `itemDisabled` (all optional) If your data source comes with columns of **different names**, you can **rename those columns** within the table component query. Here is an example of how that could look like for a data source named `ProductsData` which comes with columns named `Id`, `Name` & `InStock`: ```hive from ProductsData select { // Rename column `Id` to `value` .value = .Id, // Rename column `Name` to `text` .text = .Name, // Rename column `Name` to `itemTooltip` .itemTooltip = .Name, // Rename column `InStock` to `itemDisabled` and invert its value .itemDisabled = not .InStock } ``` See [projecting the data](/docs/configurator-logic/hive-queries#projecting-data) in the Hive Queries documentation for more details. ##### Custom layout When using a custom layout, you can use **arbitrary column names**. See the [custom layouts](/docs/configurator-ui-ux/controls/dataview#custom-layouts) section for more details on how to structure & use the data in a custom layout. There are only 2 **columns with special meaning** when working with a custom layout: - The **first column** is always considered the **unique value** of a given record. The dataview uses this value to decide which item to select & when writing selection changes back to the components. See [how to handle user selections](/docs/configurator-ui-ux/controls/dataview#how-to-handle-user-selections) for more details on this. - A column named `itemDisabled` can be used to [disable items in a dataview](/docs/configurator-ui-ux/controls/dataview#how-to-disable-items). #### Minimize the data All data within your table component will be transmitted to the configurator. Including data which is not needed by the dataview **can make the configurator slow**, especially for users with slow internet. A good rule of thumb is to **only include the columns** in your query which you actually **want to show in your dataview**. When using the default layout, this is only columns named `value`, `text`, `imageUrl` & `itemDisabled`. When using a custom layout, you should only include the columns which are actively referenced in your custom layout template and nothing else. You can always check which data is transmitted to the configurator by selecting the dataviews `entries` property. All data shown in the result view is transmitted to the configurator. #### How to use data from a record component Accessing data from a record component pretty much works the same as accessing data from a table component. The only difference is that you need to write `#NameOfRecordComponent.Records` (note the `#`) into the `entries` property instead of just the component name like with table components. ### How to handle user selections The dataviews `value` property is used to tell the dataview **which item to select** and can be used to write selection changes back into a component using the dataviews `value changed` slot. When using a **default layout**, you usually set this property to the value of the **`value` column** in your connected table component. When using a **custom layout**, you set it to the value of the **key column** which is always the **first column** in a table component. Dealing with user selections in a dataview usually consists of the following parts: - A **table component** which is connected to the `entries` property and provides the data to show (see [how to connect the data](/docs/configurator-ui-ux/controls/dataview#how-to-connect-the-data)). Let's call this component `ProductsTable` here. - A **value component** which holds the record of the selected entry e.g. named `SelectedProduct`. Use the "Create selection component" button in the table component editor to let the system create this component with a sensible default rule for. - Reflecting `SelectedProduct` as **selection in the dataview**: To achieve this, simply activate Hive for the dataviews `value` property and write `SelectedProduct.value` into the rule. When working with custom layouts, the name `value` could vary and is the name of the first column in your connected table component. - **Updating `SelectedProduct`** when the dataview selection changes using SigSlo: Signal `dataview.ValueChanged`, slot `SelectedProduct.SetValue` given the `value` property of the dataview. If the `value` property is not set or set to some invalid value, no item is selected. The selection is usually represented by some highlighting like a different colored border, different background etc. for the selected item. When using the default layout, you can use the properties within the `item layout` section to customize this highlighting. If you need more flexibility, you can always use custom CSS to customize all items which match the selector `.cfgr-dataview-item.cfgrctrl-selected` within your dataview. This also works for custom layouts! ### Custom layouts With the default layout, the dataview can show exactly 1 text and 1 image and gives you a few basic options to customize the layout (position of text & image etc.). The `custom layout` property gives you the freedom to provide **your own HTML** layout and **show arbitrary data**. #### How to define the layout Within the `custom layout` you can write pure HTML with the additional option to access data coming from the `entries` property (i.e. data from a table component). Since we also allow you to [access data from your table component](/docs/configurator-ui-ux/controls/dataview#how-to-access-the-data) in this HTML, this is not strictly HTML but actually a HTML template which is why we'll be referring to this HTML as "template" from now on. The template you write here is automatically repeated for every entry in the dataview. Also, each entry is automatically wrapped inside a `div` with the class `.cfgr-dataview-item` which is the same as for entries in a default layout. This `div` is needed because it also provides you helper classes like `.cfgrctrl-selected` & `.cfgrctrl-hover` with which you apply different styles to selected & hovered items in your custom layout as well. See [control DOM & CSS classes](/docs/configurator-ui-ux/controls/control-dom-and-css-classes) for more details on those automatically added classes we just mentioned. For example, when using the template `
hello!
` and connecting `entries` to a table component with 3 records, you'll see the text "hello!" repeated 3 times in your dataview and the final HTML in the configurator will look like this: ```html
hello!
hello!
hello!
``` Obviously, it doesn't make much sense to repeat the same static content over and over again which is why the next section will explain how to put actual data into your template. #### How to access the data In order to inject data from the connected table component into your HTML template, you can add **template blocks** starting with `{[` and ending with `]}` to your template. Within those blocks you can **write JavaScript code** which is executed at runtime, when rendering the dataviews entries. Access to all **data from the connected table component** is provided through an object named `values` which you can use within the template blocks. All columns from the connected table component are accessible as properties of this `values` object. E.g. if the connected table component consists of columns named `Key`, `Title`, `Size` & `PricePerLiter`, you can access the corresponding data using `{[ values.key ]}`, `{[ values.title ]}`, `{[ values.size ]}` & `{[ values.priceperliter ]}` within the `custom layout` template. The properties accessed within the `values` object need to be written all lower case, no matter how they're actually named in the table component. E.g. the column named `PricePerLiter` needs to be accessed using `values.priceperliter`. If the column value you're accessing is not of a primitive type like `number`, `text` or `boolean`, but a more complex object like an `image`, you have to use the correct casing to get its sub properties. Imagine a column named `Thumbnail` which is of type `image`. This `image` is an object which consists of properties like `Url`, `Width` & `Height`. If you'd like to get the URL of the image, you have to write `values.thumbnail.Url` where `thumbnail` is all lower case and its property `Url` is still camel cased. #### Debugging tip If you're unsure how the properties within your `values` object are actually named or structured, you can use the following trick: Use the template block `{[ console.log('values:', values) ]}` anywhere within your template. This will print the values of each record to your console whenever the dataview renders new content which allows you to investigate the `values` object in detail. Remember: Within the template blocks `{[ ... ]}` you can write any kind of JavaScript code which is executed at runtime! #### How to disable items The dataview automatically disables all items whose corresponding record in the connected table component has a value of `true` in a column named `itemDisabled`. Those disabled items come with a default styling (reduced opacity). If you'd like to adjust this styling, you can do so by providing your own styles in custom CSS for elements with the classes `.cfgr-dataview-item.cfgrctrl-disabled` within your dataview. #### How to add a tooltip Similar to "disabling items" it's also possible to add a tooltip. To do so, simply add a column named `itemTooltip` with the desired text in the connected table. #### Usage example Imagine a table component named `ProductsTable` with the following query: ```hive from Products select { .Id, .Title, .DetailText, .Size, .PricePerLiter, .Thumbnail, .itemDisabled } ``` Now you'd like to show this data in the following form: ![image.png]() You can achieve this layout using the following property values: - Entries: `ProductsTable` (activate Hive for the property!) - Custom layout: ```html
{[ values.title ]}
€ {[ (values.priceperliter * values.size).toFixed(2) ]} *
€ {[ values.priceperliter.toFixed(2) ]}/Liter *
``` As you can see in the custom layout template above, you can write fully functional JavaScript inside the template blocks (`{[ ... ]}`). This means that you can also perform calculations, formatting etc. there if needed. E.g. `values.priceperliter * values.size).toFixed(2)` is vanilla JavaScript. Obviously, this also needs some custom CSS to look like the dataview shown above but I leave this as an exercise to the reader. Hint: You can provide your own classes in the template (like `products-dv--background` in the `custom layout` above) and use those classes to style the entries. #### Adding interactive elements to the template You can also add interactive elements like buttons to the template. One use case of this would be to show a plus and a minus 1 button on each element which adds or removes items from a cart. Whilst you cannot interact with those buttons directly in Hive or via SigSlo, you can always use [custom JS](/docs/configurator-ui-ux/customjs/custom-js) to deal with button clicks etc. Here's a very simple example, showing how you can add such buttons & how to react on clicks in custom JS: **Template:** ```html
{[ values.title ]}
``` HTML **Custom JS:** ```js CfgrUtils.onCfgrReady(() => { // ... $('.cfgr-dataview').on('click', '.add-button', event => { // Get info about clicked row from `data-item-id`: const itemId = event.currentTarget.dataset.itemId; // ... }); // ... }); ``` There are 2 things to be especially aware of: - Ensure that the click listener is installed after the control has surely been rendered. E.g. in callback to [CfgrUtils.onCfgrReady](https://cjs.docs.combeenation.com/classes/cfgrutils.html#oncfgrready). - Don't install the click event on the button itself but use event delegation on the dataview to "get to the button". In the example above, this is why we're using `$('.cfgr-dataview').on('click', '.add-button', () => ...)` instead of `$('.cfgr-dataview .add-button').on('click', () => ...)`. Events installed directly on the button would probably work initially but won't work anymore after the items in the dataview change at runtime as all dataview items are destroyed & re-created on such updates. #### More advanced use cases The templates for our custom layouts are powered by a technology called `XTemplates` from `ExtJS`. Therefore you can also use most of the features they're providing in your templates as well. Since we couldn't do a better job in explaining them, then the ExtJS team itself, we don't even try and refer you to their excellent docs to find out more about what is possible: [https://docs.sencha.com/extjs/6.2.0/classic/Ext.XTemplate.html](https://docs.sencha.com/extjs/6.2.0/classic/Ext.XTemplate.html) --- ## Social Share The Share-Control is essentially a button with a fixed click behaviour. Based on the chosen platform it changes it's default design and opens the respective 'Share window' when clicked. :::info The share functionality is only available in published configurators. ::: ### Styling #### Default buttons appareance ![SocialShareButtons.png]() It's recommended to use the default styling to preserve it across all platforms and be safe for future updates. #### Small design changes Some changes are specifically supported without the need to overrule the CSS: - Text only: Set `Show icon` to false - Icon only: Remove `Value` - Width/Height: The content stays centered and the icon resizes to some extent Also the whole hover effects can be disabled by simply removing the CSS class `default-hover`. #### Blank button If a totally different design is required, a blank button can be aquired with the following steps: - `Value`: remove content - `Show icon`: set to false - `CSS Class`: remove class "default-hover" Afterwards it can be designed like any other button with background images, custom css-rules, etc. ### Share options By default all the relevant values in the properties are provided as so called "Open Graph Meta Tags" or in short "og-Tags". Most platforms process those tags but the result can differ between every platform. It's recommended to fill all properties with valid content to get the best result, even though some properties aren't visible to the user (like author). **Graphic**: Name of a Graphic-Component **Embed Url**: Base Url where the Configurator is embedded. The Share-Link will be appended automatically. #### Known behaviour The behaviour highly depends on the implementation of the respective platform. This means that the following table may not be up to date or it gets changed from time to time. ✓ Part of the post/message (maybe shown a second time in the preview) ○ Visible but not directly in the post/message (e.g. as part of the preview) ✖ Not Visible | Platform | Title | Description | Graphic/Preview | | --------- | ----- | ----------- | ----------------------- | | Facebook | ○ | ○ | ✓ | | Pinterest | ✕ | ✓ | ✓ | | Twitter | ✓ | ✓ | ✓ (may load with delay) | | LinkedIn | ○ | ✕ | ✓ | | WhatsApp | ✓ | ✓ | ✓ (may load with delay) | --- ## Tooltips for controls Tooltips can be set for most of the controls in the configurator editor to provide additional information to the user. To get started, simply create a control like a `Button`, go to `Properties > Tooltip > Tooltip text` and enter your value. Afterwards you instantly can see your tooltip, when hovering over the control. ![image.png]() ### General tooltip properties Under the top-most control `Configurator` you can find some general settings which will be applied to every tooltip. (e.g. don't show the arrow which is pointing at the target control). Other properties can also be set with Custom Code: `CtrlUtils.Tooltips.updateGeneralProperties({ trigger: 'click' });` #### Property "Touch hold behavior" By default the tooltips have a basic support for touch devices, by holding the control. It's recommended to show the tooltip only after a brief amount of time, so that the user doesn't immediately see a tooltip when he just wants to press a button or similar. Available options: - Hold 0.1s: Touch and hold 100ms to show the tooltip - Hold 0.5s: Touch and hold 500ms to show the tooltip - Hold: Instantly show the tooltip (even when the user touches/clicks very fast) - Disabled: Never show tooltips on touch ### CSS styling and themes In the `CSS Styles` tab of the `User interface` you can easily style your tooltips. #### Quick start 1. Go to the the `CSS Styles` editor 2. [Recommended] Create a dedicated module called `Tooltip` 3. Enter `.tippy-snippet` in the code editor to get a snippet-suggestion - Press Enter or Tab to insert the snippet 4. Overwrite the colors or extend the CSS as needed ![image.png]() #### Themes (multiple tooltip styles) If a configurator requires different designs of tooltips, it's also possible to add additional themes. As a quick starting point we also provide the code snippet `.tippy-snippet-theme` for that. 1. Insert the snippet 2. Enter your theme name (e.g. `[data-theme~="light"]`) 3. Enter the identical name `light` under the property `Tooltip > Theme` of your control #### DOM structure and further styling The tooltips we provide are based on tippy.js. Please see the [official docs of tippy.js](https://atomiks.github.io/tippyjs/v6/themes/) for details about the structure and required CSS selectors. ### Dataview control It's also possible to add tooltips to the individual items of the Dataview. If the entries are static or connected to a `Table Component` a field/column named `itemTooltip` is required in the property `Entries`. For details see [Dataview - Connect data](/docs/configurator-ui-ux/controls/dataview#how-to-prepare-the-table-component). ##### SigSlo restriction If a `Table Component` is connected by a SigSlo it's not possible to map a tooltip. However, it's possible by using `Hive in controls` for the property `Entries` as explained above. In general this is also the recommended way, as the SigSlo may be phased out in the future. For `Record Components` the mapping is possible and can be chosen in the SigSlo dialog. ### Custom code With Custom code you can get full access to the tippy.js instances of existing tooltips and also create new ones. Simply retrieve a control with one of the functions under `CtrlUtils` and Besides that, general functions can be found under the namespace `CtrlUtils.Tooltips`. ```ts // Set custom content/text const btn = CtrlUtils.getButton(CtrlNames.button1)!; btn.setTooltipContent('My new content!'); ``` ```ts // Change `placement` and add custom `onShow` function const btn = CtrlUtils.getButton(CtrlNames.button1)!; const btnTooltip = btn.getTooltip(); btnTooltip.setProps({ placement: 'right', onShow: function () { console.log('onShow'); } }); ``` See the [official docs of tippy.js](https://atomiks.github.io/tippyjs/v6/all-props/) for a full list of all the available properties and the API in general. ### FAQ #### How can I change text dynamically (e.g. for i18n) The text property of tooltips allows `Hive in controls`. Simply click the icon next to the property and enter your Hive rule which outputs your translated text. #### Why can't I set a specific property in the editor which exists for tippy.js The main intention was to keep it as simple as possible and that the editor doesn't get cluttered with rarely used properties. With Custom code we also provide full control over the general settings and individual instances of tippy.js, so you can customize it in any possible way. Feel free to contact us, if you still think that additional properties would be helpful. --- ## Custom code :::info Custom code is based on TypeScript and fully replaces the (now deprecated) CustomJS projects. It provides all the features from CustomJS in a more sophisticated manner. ::: With custom code you can **extend your configurators capabilities** in many ways. It allows you to write TypeScript which then runs in your configurator. The following are just a few examples of the countless possibilities which open up with this feature: - Extend functionalities for existing **components** and **controls** (see [custom code utils](https://custom-code.docs.combeenation.com/)) - Extend functionalities for the **viewer control** (see [3d viewer](https://3dviewer.docs.combeenation.com/)) - Add **new controls** like a date time picker etc. - **Fetch data** from external services like **real-time** prices calculated on your servers or real-time inventory from your CMS etc. - Send **live analytics** to external services like [Google Tag Manager](https://tagmanager.google.com/) etc. ### How to get started #### Setup a TypeScript project We provide a starter project which comes with our recommended configuration (build tools, config, pre-installed packages etc.) and gives you everything you need to get started quickly. :::info All of the following assumes that you have a current version of Node.js & npm (or some equivalent) installed on your computer. ::: - Open a terminal (e.g. command line in windows) in the folder where you want to create your project - Run `npm create @combeenation/custom-code@latest` - Follow the instructions of the script to create a starter project using TypeScript ### How to use it #### Where to start writing code The main starting point is usually the callback to [CfgrUtils.onCfgrReady](https://custom-code.docs.combeenation.com/functions/CfgrUtils.onCfgrReady.html#onCfgrReady) within `src/index.ts` in which you have the guarantee that you can safely access all components & controls. You can either write all your TypeScript logic within this function or spread it over as many functions/modules/files as you like. :::info Always ensure that you only access components & controls **after** the callback to [CfgrUtils.onCfgrReady](https://cjs.docs.combeenation.com/classes/cfgrutils.html#oncfgrready) was called. Otherwise there is no guarantee that all components and controls are fully bootstrapped and you could run into undefined behavior or errors. ::: #### How to run & debug during development There are 2 ways to run & debug your application during development: - Debugging in **Chrome dev tools**: - Run `npm run dev` which will start a local dev server - Open the preview of your configurator draft - Click the "TS icon" in the upper right to connect to the local instance - As alternative you can also add the URI parameter `&localcts=true` manually - Debug in browser dev tools (`F12`) - Debugging in **VS Code**: - Open the view "Run and debug" in VSCode & select "Start dev" in dropdown at the top (this is only needed once). - Press F5 to start debugging. This will automatically open a new Chrome window and run `npm run dev` in the background. - Open the preview of your configurator draft - Click the "TS icon" in the upper right to connect to the local instance - As alternative you can also add the URI parameter `&localcts=true` manually - Debug directly in VSCode :::info The steps above won't persist your custom code in the configurator but will load it from the hard drive of your computer. If you'd like to share the configurator with someone else or want to publish it, you always have to upload your custom code to the `CustomJS` component as explained in [how to upload custom code to the configurator](/docs/configurator-ui-ux/custom-code/custom-code-#how-to-upload-custom-code-to-the-configurator). ::: #### How to upload custom code to the configurator Uploading your custom code to the configurator is always required when you want to share your configurator with someone else or want to publish it. - Run `npm run build` in your custom code project on your computer - Open the configurator editor of your draft and expand "Custom Code" in the header bar - Upload or replace the script with the newly built file in your project folder under dist/cfgr.js #### Update component typings The required typings to work with Components can be retrieved in your Configurator editor or the preview. The recommended way is to run the development server (as explained above) as it allows you to directly connect this local environment with your Configurator. The UI controls should automatically be available as soon as you open a preview which is connected to the local TS. **Configurator Preview** ![image.png]() **Configurator Editor** - If you aren't connected to your local IDE yet, ensure that your dev server is up and running with `npm run dev` - Expand "Custom Code" in the header bar and click "Connect" - On a successful connection you get a new toolbar in the Components list on the left: ![image.png]() **Update them manually** - Open your configurator draft - Expand the menu of the `Components` list on the left - Select `Download custom code definitions` - Save/Replace the file in your code project #### How to access components & controls The [custom code utils](https://www.npmjs.com/package/@combeenation/custom-code-utils) package is mandatory to interact between your custom code and the configurator. It allows you to read values from components, write new input values to them and interact with the controls in your configurator and many other features. To work with a component ensure that your "configurator definitions file" is up to date (see previous step). This file basically represents your components list. If you want to access a component simply import the variable which has the identical name as your component. This variable then provides all the available functions and properties you need. ```ts const myValue = await MyCmp.getValue(); MyCmp.setInput('myNewString'); ``` TypeScript See the [utils API documentation](https://custom-code.docs.combeenation.com/) for more details on how to use them. The custom code utils come pre-installed with our [TypeScript start project](/docs/configurator-ui-ux/custom-code/custom-code-#setup-a-typescript-project). You can also install them manually using `npm install @combeenation/custom-code-utils`. #### How to interact with the 3d viewer Combeenation has built its own 3d viewer, which is used in the [3d viewer control](/docs/configurator-ui-ux/controls/3d-viewer-control). If the functionality of this 3d viewer control and its SigSlos is not sufficient, it can be extended in custom code using the API of the 3d viewer package itself. See [3d viewer control with custom JS](/docs/configurator-ui-ux/controls/3d-viewer-control-with-custom-code) for more details. ### FAQ #### What is the config file `cbn.config.json`? This file can hold information specific to the Combeenation platform. One of its usages is, that it can create a link between your code project and the configurator. This way, the typings (`cfgr-defs.generated.ts`) can be automatically transferred during development. #### How can I upload files into the configuration? With version 1.1.0 the so called `Configuration Files` were introduced. In Custom code you can find the respective functions under the [namespace CfgnFiles](https://custom-code.docs.combeenation.com/modules/CfgnFiles.html). PS: It's recommended not to use `Resource components` anymore, as they will be phased out in the future. --- ## Quick start ### Prerequisite The following guide assumes that the following prerequisites are met. Otherwise please see the more detailed guide about [Custom Code](/docs/configurator-ui-ux/custom-code/custom-code-). - A Combeenation configurator with basic functionality (some Components, Design, etc.) - `Node.js` & `npm` (or some equivalent) - Visual Studio Code (our recommended IDE, but not mandatory) - Basic knowledge about these tools ### Setup - In a terminal navigate to the folder where your project should be created - Run `npm create @combeenation/custom-code@latest` - Follow the instructions of the script ### Local development After the setup is done: - Run `npm run dev` inside your code project - In the browser, open the preview of your configurator draft - On the top right click the TS-icon (Toggle local custom TS) - This will simply add the query parameter `&localcts=true` to your URI ### Coding #### [Optional] Create a test Component In your configurator create a simple Value Component called `MyTestCmp` with the rule ```hive Input or "Hello World" ``` Plain text We will refer to this Component in the upcoming snippets. #### Get Components in code At this point, our code is runnable but it has no knowledge about the Components yet. To communicate with them, we provide an auto-generated typescript-file which can be easily synced with our tools. The required UI controls are automatically available as soon as you open a preview which is properly connected to the local environment. **Configurator Preview** ![image.png]() **Configurator Editor** ![image.png]() Simply click on one of the "Sync buttons" and your Components will instantly be available in your IDE. With every change in the Components you can click this button again to update them. At its core, this feature updates the file under `src\typings\cfgr-defs.generated.ts`. #### Entry point Inside the `index.ts` you will find the following: ```ts CfgrUtils.onCfgrReady(() => {}); ``` This is the main entry point of the configurator which ensures that your configurator is fully up and running. Any kind of initialization code which communicates with the configurator should happen in here. #### Read the value of a Component ```ts // See above how to get Components import { MyTestCmp } from './typings/cfgr-defs.generated'; CfgrUtils.onCfgrReady(async () => { const myValue = await MyTestCmp.getValue(); console.log('RESULT: ', myValue); }); ``` #### Write (set input) of a Component ```ts // See above how to get components import { MyTestCmp } from './typings/cfgr-defs.generated'; CfgrUtils.onCfgrReady(() => { MyTestCmp.setInput('Hello from custom code!'); }); ``` #### Listen to a Component change ```ts // See above how to get components import { MyTestCmp } from './typings/cfgr-defs.generated'; CfgrUtils.onCfgrReady(() => { // MyTestCmp.setInput('Hello from custom code!'); MyTestCmp.onValueChanged(newValue => { console.log('value updated: ', newValue); }); }); ``` #### Communicate with controls The controls can be accessed via their unique name. To prevent hard-coded strings inside the code, we recommend to extend the `enum CtrlNames {}` which is already pre-defined under `src\constants.ts`. ```ts // constants.ts export enum CtrlNames { JS_DummyBtn = 'JS_DummyBtn' } ``` ```ts // index.ts import { CtrlNames } from './constants'; CtrlUtils.onCtrlClicked(CtrlNames.JS_DummyBtn, () => { // your code }); ``` #### Other utils & helpers For a full documentation of the available functions see [custom-code.docs.combeenation.com](https://custom-code.docs.combeenation.com/) Quick overview of other, often used, functions: - Listen to multiple components at once (e.g. to prevent multiple calls of the handler-function) `CmpUtils.onAnyCmpValueChanged(_update3dModel, [Cmp1, Cmp2, Cmp3]);` - Create PDF from HTML which is directly stored/uploaded as "Configuraton File" `CfgnFiles.createPdfFromHtml('bom', htmlContent);` --- ## CSS properties (variables) All browsers which are [officially supported browsers](/docs/integrations-and-api/supported-browsers) also support [CSS properties (variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/--*). Therefore you can basically use them as you wish in your custom CSS. See [Can I use](https://caniuse.com/#feat=css-variables) for more details on browser support. ### Organizational tip It is recommended to declare all CSS variables in a dedicated custom CSS module named `Variables` or something the like instead of spreading them all over different modules. This helps you keep an overview of all available variables. ### Changing variable values at runtime The values of CSS variables declared in custom CSS can be changed dynamically at runtime using the following Signals and Slots: ```hive CssVars.ValueChanged -> Configurator.updateCssVariables(CssVars.Value) ``` The value which is given to the `updateCssVariables` slot needs to be the value of a record component with column names representing the names of the CSS variable without the leading `--`. If you want to change CSS variable values dynamically via SigSlo you have to use names which are also allowed in Hive. E.g. you couldn't use names like `label-text-color` but would have to stick to `labelTextColor` etc. #### Example Configurator `CssVariables` in company `Combeenation College`: [Published demo](https://portal.combeenation.com/Cfgr/College/CSSVARIABLES) ```css /* Custom CSS */ :root { --cfgrBckgrColor: #dd0060; --labelTextColor: yellow; --labelBckgrColor: #37a58c; } .cfgr-parent { background: var(--cfgrBckgrColor) !important; } .cfgr-label { color: var(--labelTextColor); background: var(--labelBckgrColor); } ``` ```hive /* Record component "CssVars" */ { { Theme=Text, cfgrBckgrColor=Text, labelTextColor=Text, labelBckgrColor=Text } { "Colorful", "#DD0060", "yellow", "#37A58C" } { "Light", "white", "white", "rgba(0, 0, 0, 0.6)" } { "Dark", "black", "black", "rgba(255, 255, 255, 0.6)" } } ``` --- ## Load mask styles The configurator automatically displays a load mask in several situations to inform users that background tasks are in progress. By default, these load masks are designed to be unobtrusive and generic, ensuring they blend well with most configurators. However, if you prefer a more distinctive look that aligns closely with your configurator's design, you can easily customize the load masks using custom CSS. ### General purpose configurator load mask This load mask is designed to inform users when tasks are in progress, during which interaction with the configurator is not possible. By default, it displays a loading spinner icon accompanied by descriptive text: ![2024-12-02_10h57_34.png](https://docs.combeenation.com/assets/images/2024-12-02_10h57_34-9aeb8d4d06763aa4f9ea458c2dcafc62.png) This type of load mask is displayed in the following situations: - When a configuration is being prepared for sharing - During file uploads - When the configurator is reconnecting to the server, such as in cases of poor internet connectivity - When the "Show load mask" slot of the configurator is activated - When custom code invokes [CtrlUtils.showLoadMask](https://custom-code.docs.combeenation.com/functions/CtrlUtils.showLoadMask.html) #### How to customize Here is a code snippet, showing how to customize certain parts of the load mask which should give you a good starting point for your own customizations: ```css /* Change the load masks background which covers the whole configurator */ .cbn-page-mask { background-color: rgba(0, 64, 160, 0.4); } /* Remove the background of the box in the middle */ .cbn-page-mask .x-mask-msg { background: transparent; } /* Change font family & color of the description text */ .cbn-page-mask .x-mask-msg .x-mask-msg-text { color: white; font-family: Inter; } /* Hide the default loading spinner icon */ .cbn-page-mask .x-mask-msg .x-mask-msg-text { background: none; } /* Show a custom pulsating icon */ .cbn-page-mask .x-mask-msg .x-mask-msg-text:after { content: ''; position: absolute; left: calc(50% - 16px); top: -38px; width: 32px; height: 32px; background: url(https://path/to/your/logo.png) no-repeat center / 32px; animation: pulse 1.5s ease-in-out infinite; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.2); } 100% { transform: scale(1); } } ``` All of the above would lead to the following load mask: ![image.png]() ### 3d viewer load mask There are two types of load masks displayed in the 3D viewer: - A **large load mask** which covers the entire 3d viewer and includes descriptive text to let the user know what is currently happening. ![2024-12-02_10h55_15.png](https://docs.combeenation.com/assets/images/2024-12-02_10h55_15-8bbdd2c461130f85414dd118ae6a3a36.png) - A **small load mask** which only shows a subtle loading spinner icon located in the bottom right corner of the 3d viewer, without any accompanying text. ![2024-12-02_10h55_31.png](https://docs.combeenation.com/assets/images/2024-12-02_10h55_31-7ed20092b81ca6403a3279cc9eff1efe.png) The **large load mask** is shown when initially bootstrapping the viewer and when preparing the scene to be [viewed in AR](/docs/configurator-ui-ux/controls/3d-viewer-control#start-ar). The **small load mask** is shown when new data is being loaded in the background which can be the case when loading a new 3d model into the scene or when changing shown materials & textures. #### How to customize Here is a code snippet, showing how to customize certain parts of both, the small & the large viewer load mask, which should give you a good starting point for your own customizations: ```css /* Make the large load mask cover the whole configurator, not just the 3d viewer, and change its background color */ .cfgr-viewer3d .viewer-load-mask { position: fixed; background-color: rgba(0, 64, 160, 0.4); } /* Change font family & color of the shown description */ .cfgr-viewer3d .viewer-load-mask .viewer-load-mask-text { color: white; font-family: Inter; } /* Hide the default loading spinner icon of the small & large load mask */ .cfgr-viewer3d .viewer-load-mask .viewer-load-mask-icon:before { display: none; } /* Show a custom pulsating icon for the small & large load mask */ .cfgr-viewer3d .viewer-load-mask .viewer-load-mask-icon { width: 32px; height: 32px; background: url(https://path/to/your/logo.png) no-repeat center / 32px; animation: pulse 1.5s ease-in-out infinite; } /* Show a background for the small load mask & move it to the top left */ .cfgr-viewer3d .viewer-load-mask.viewer-load-mask-small { left: 16px; top: 16px; width: 52px; height: 52px; background: #0040a0; border-radius: 52px; } ``` All of the above would lead to the following load masks where the large one resembles the appearance of the customized configurator load mask [shown above](/docs/configurator-ui-ux/custom-css/load-mask-styles#how-to-customize): ![image.png]() --- ## Create PDF from HTML content :::warning This is no longer the recommended way to create a pdf during a configuration. Please refer to [PDF assets](/docs/asset-management/pdf-asset) for the preferred approach. ::: ### General The custom-js-utils function [CfgrUtils.convertHtmlToPdf](https://cjs.docs.combeenation.com/classes/cfgrutils.html#converthtmltopdf) allows you to create a fully custom PDF, based on any valid HTML input. This can be useful for creating offers, a bill of material or similar. ### Minimum requirements - [Resource component](/docs/configurator-logic/components/resource-component) of type `Document` with a pdf, which acts as a placeholder and will be overwritten - When creating the component, the checkbox `User can upload his own resources` must be checked - `custom-js` project (based on the `.template-project` in your repository) - A string with valid html - Can be generated anywhere, as long as it's available in custom-js afterwards. (components & hive rules, custom-js function, etc.) ### Creating your first PDF 1. Create a resource component of type `Document` and check `User can upload his own resources` 2. Add a placeholder pdf to the `Documents` ![image.png]() 3. Create a value component which holds the url of the pdf - Rule: `OfferPdf.myPdf.Url` 4. Add the following code inside the `CfgrUtils.onCfgrReady` of your custom-js project - Change the component & resource names as needed ```ts CfgrUtils.onCfgrReady(async () => { const myResourceName = 'myPdf'; const myHtml = 'Hello World!'; CfgrUtils.convertHtmlToPdf(CmpNames.OfferPdf, myResourceName, myHtml); }); ``` #### Result When you run this project, a pdf is immediately created and you can retrieve and open the url from the value component we created earlier. ![image.png]() ### Retrieve the updated pdf url Although the `CfgrUtils.convertHtmlToPdf` returns a `Promise` it's not guaranteed that the url of the pdf got updated yet. Instead, the recommended way is to create a component whose rule returns the url of the pdf and we wait for this component to change. ```ts CfgrUtils.convertHtmlToPdf(CmpNames.OfferPdf, myResourceName, myHtml); await CmpUtils.waitCmpValueChanged(CmpNames.OfferPdfUrl); // ... retrieve the new url ``` :::info With the introduction of `Custom code` and `CfgnFiles` this was simplified. [File upload & Handling](/docs/configurator-logic/file-upload-and-handling#creating-pdf-files) ::: ### Creating the HTML #### Understanding the tech At it's very base the generated PDF is very similar to the result of the "Print" feature in your browser. This also means that you can build and preview your basic layout simply by editing a html-file in a text-editor, then open it in the Chrome browser and press `Ctrl+P` (shortcut for Print). ![image.png]() #### Page layout CSS provides special selectors & rules to format your html specifically for printing. Because there exist plenty of articles on the internet we only list some keywords and links here: - [@page](https://developer.mozilla.org/en-US/docs/Web/CSS/@page): Overall page formatting like size, margin, etc. - `page-break-*`: Control where a page should (or should not) break and start a new page ##### Html2PdfOptions It's also possible to provide options like format, margin, etc. when calling `CfgrUtils.convertHtmlToPdf`. For details see [Html2PdfOptions](https://cjs.docs.combeenation.com/interfaces/cbn.cfgnclient.html2pdfoptions.html). #### Header & Footer To show a header or footer on every page you can pass individual html-strings to the `convertHtmlToPdf` function. The body, header and footer don't share their styles and must be defined individually! ```ts CfgrUtils.convertHtmlToPdf(CmpNames.OfferPdf, 'pdf', bodyHtmlString, { displayHeaderFooter: true, footerTemplate: footerHtmlString, headerTemplate: headerHtmlString }); ``` #### Page numbers and similar :::info These only work in the header & footer! They have no effect inside the body. ::: Specific css classes can be used to inject auto-generated values into the PDF. To do so, simply create a html-tag with one of the css classes stated below. ##### Possible values (css classes) - `date` - formatted print date - `title` - document title - `url` - document location - `pageNumber` - current page number - `totalPages` - total pages in the document ##### Example usage ```html Page of ``` See also [Puppeteer PDFOptions.headerTemplate](https://pptr.dev/api/puppeteer.pdfoptions) ### Troubleshooting #### Puppeteer This whole feature is based on the library [Puppeteer](https://pptr.dev/). In case of any troubles or unclear results you may find answers by searching their docs or using the keyword `Puppeteer` in your search engine. #### Images or fonts inside the header or footer aren't working External links aren't supported. Instead they have to be inserted directly into the html. - If the image is available as SVG, simply add it like any other tag - Other images can be converted to a base64 string and be inserted like this: - Example: `` - For larger base64 strings it's recommended to store them inside a [TextAsset](/docs/asset-management/text-asset) --- ## Custom JS :::warning CustomJS is fully replaced by [Custom Code](/docs/configurator-ui-ux/custom-code/custom-code-). If you're starting a new project please use Custom Code as it provides a far better experience when extending our configurators. ::: With custom JS, which is short for custom JavaScript, you can **extend your configurators capabilities** in many ways. It allows you to write JavaScript (or TypeScript) which then runs in your configurator. The following are just a few examples of the countless possibilities which open up with this feature: - Extend functionalities for existing **components** and **controls** (see [custom JS utils](https://cjs.docs.combeenation.com/)) - Extend functionalities for the **viewer control** (see [3d viewer](https://3dviewer.docs.combeenation.com/)) - Add **new controls** like a date time picker etc. - **Fetch data** from external services like **real-time** prices calculated on your servers or real-time inventory from your CMS etc. - Send **live analytics** to external services like [Google Tag Manager](https://tagmanager.google.com/) etc. ### How to get started #### 1. Prepare your configurator :::info It is important that you use the exact same names that we're using in this guide when you create your components etc. ::: - [Create a new configurator](/docs/fundamentals/creating-a-configurator) if you don't already have one. Preferably use one of our templates which mentions "custom JS" in its name or description. - See if your configurator has a component `CustomJSCmps`. If yes: You can skip the following steps and go to the [next section](/docs/configurator-ui-ux/customjs/custom-js#2-setup-a-typescript-or-javascript-project). - Create a value component named `CustomJSCmps` and paste the following into the rule of the component: ```hive // In this component, you have to tell the server which components you're // accessing (reading or writing to) in your custom JS. // // By default the server only sends the result value of the listed components to // the client. If more data like the list of all records etc. is needed, a "+" // needs to be prepended to the name. // Examples for how to use this. Replace with real cmp names... [ #DefaultView.Name, // Retrieve all records of a component "+" + #MyRecords.Name ] // Why do we need this? // // In published configurators, the server optimizes the used network bandwidth // and only sends information about components to the client when actually // needed. // // Since the server has no way of knowing which components are used in custom JS, // we need to give him this information manually here so that he can optimize // properly. ``` See [What is the CustomJSCmps component](/docs/configurator-ui-ux/customjs/custom-js#what-is-the-customjscmps-component) down below for some more details. #### 2. Setup a TypeScript or JavaScript project Theoretically you can upload **any** `.js` file to your configurator. However, we provide a starter project which comes with our recommended configuration (build tools, config, pre-installed packages etc.) and gives you everything you need to get started quickly. :::info All of the following assumes that you have a current version of Node.js & npm (or some equivalent) installed on your computer. ::: - Open a terminal (e.g. command line in windows) in the folder where you'd like your code to reside - Run `npm create @combeenation/custom-ts@latest` This will create a starter project using TypeScript. If you prefer to write your code in JavaScript, see [this section](/docs/configurator-ui-ux/customjs/custom-js#how-to-setup-a-javascript-project) further down below. ### How to use it #### Where to start writing code The main starting point is usually the callback to [CfgrUtils.onCfgrReady](https://cjs.docs.combeenation.com/classes/cfgrutils.html#oncfgrready) within `src/index.ts` in which you have the guarantee that you can safely access all components & controls. You can either write all your JavaScript logic within this function or spread it over as many functions/modules/files as you like. :::info Always ensure that you only access components & controls **after** the callback to [CfgrUtils.onCfgrReady](https://cjs.docs.combeenation.com/classes/cfgrutils.html#oncfgrready) was called. Otherwise there is no guarantee that all components and controls are fully bootstrapped and you'll run into undefined behavior. ::: #### How to run & debug during development There are 2 ways to run & debug your application during development: - Debugging in **Chrome dev tools**: - Run `npm run dev` - Open the preview of your configurator draft and append the URI parameter `&localcts=true` - Use `&localcjs=true` when working with the JavaScript starter - Debug in browser dev tools (`F12`) - Debugging in **VS Code** (this only works with the TypeScript starter): - Open the view "Run and debug" in VSCode & select "Start dev" in dropdown at the top (this is only needed once). - Press F5 to start debugging. This will automatically open a new Chrome window and run `npm run dev` in the background. - Open the preview of your configurator draft and append the URI parameter `&localcts=true`. - Debug directly in VSCode. :::info The steps above won't persist your custom JS in the configurator but will load it from the hard drive of your computer. If you'd like to share the configurator with someone else or want to publish it, you always have to upload your custom JS to the `CustomJS` component as explained in [how to upload custom JS to the configurator](/docs/configurator-ui-ux/customjs/custom-js#how-to-upload-custom-js-to-the-configurator). ::: #### How to upload custom JS to the configurator Uploading your custom JS to the configurator is always required when you want to share your configurator with someone else or want to publish it. - Run `npm run build` in your custom JS project on your computer - Open the configurator editor of your draft and expand "Custom Code" in the header bar - Upload or replace the script with the newly built file in your project folder under `dist/cfgr.js` #### How to access components & controls The [custom JS utils](https://www.npmjs.com/package/@combeenation/custom-js-utils) package is a set of helper functions which simplify the interaction between custom JS and your configurator. Besides others, it allows you to read values from components, write new input values to them and interact with the controls in your configurator. See the [utils API documentation](https://cjs.docs.combeenation.com/) for more details on how to use them. The custom JS utils come pre-installed with our [TypeScript or JavaScript start projects](/docs/configurator-ui-ux/customjs/custom-js#2-setup-a-typescript-or-javascript-project). You can also install them manually using `npm install @combeenation/custom-js-utils`. #### How to interact with the 3d viewer Combeenation has built it's own 3d viewer, which is used in the [3d viewer control](/docs/configurator-ui-ux/controls/3d-viewer-control). If the functionality of this 3d viewer control and its SigSlos is not sufficient, it can be extended in custom JS using the API of the 3d viewer package itself. See [3d viewer control with custom JS](/docs/configurator-ui-ux/controls/3d-viewer-control-with-custom-code) for more details. ### What is the CustomJSCmps component In this component, you have to tell the server which components you're accessing (reading or writing to) in your custom JS. You'll get a warning in the configurator preview if you access components which are not listed here. Always ensure that you list all required components as **published configurators will fail silently if components are missing**. See [this section above](/docs/configurator-ui-ux/customjs/custom-js#1-prepare-your-configurator) for an example of how the content of this component has to look like. ### How to setup a JavaScript project - Navigate to [https://github.com/Combeenation/custom-js-template](https://github.com/Combeenation/custom-js-template) - Click "Use this template" and "Create a new repository" to do so: ![image.png]() If you don't have a GitHub account or if you want to use a different or no source code management tool at all, you can also just download the code as a **zip**-file: ![image.png]() Ensure that you have [prepared your configurator](/docs/configurator-ui-ux/customjs/custom-js#1-prepare-your-configurator) and see [how to use it](/docs/configurator-ui-ux/customjs/custom-js#how-to-use-it) for some info on how to proceed. --- ## Headless configurator If you want to provide all the configurator UI using your own tooling, frameworks & libraries instead of using our built in UI editor, you can do so by starting a headless configurator project. You can connect your self-built configurator UI to your configurator logic (component values, inputs, assets etc.) using our publicly available [configurator-client](https://www.npmjs.com/package/@combeenation/configurator-client). Expect more info to come or [contact support](https://community.combeenation.com/c/questions/6) if you have any questions. --- ## User input sanitization :::info Always leave the SigSlo parameter [Sanitize Value](/docs/configurator-ui-ux/user-input-sanitization#sigslo-parameter-sanitize-value) at its default value `true` or use [Hives encodeHtml()](/docs/configurator-ui-ux/user-input-sanitization#mixed-content) when displaying user text input in the configurator! ::: ### SigSlo parameter `Sanitize Value` To prevent [Cross-Site Scripting (XSS) attacks](https://owasp.org/www-community/attacks/xss/) we've introduced a new paramter `Sanitize Value` for a wide range of `SigSlo` slots like `Button.setValue`, `Label.setValue`, `DataView.setEntries` etc. This parameter comes with a default value of `true` which ensures that HTML code which is given to the slot is not rendered as such but only shown as raw text. ### Imaginary XSS attack scenario - Imagine a configurator where the user can enter some text which is then shown in a label. E.g. a custom name for his configuration. - This configuration name is saved to a component `CfgnName` via the following `SigSlos`: - `CfgnNameTxtbox.ValueChanged -> CfgnName.setValue(CfgnNameTxtBox.Value)` - `CfgnName.ValueChanged -> CfgnNameLbl.setValue(CfgnName.Value, false)` **Note** that the second parameter `Sanitize Value` is `false` here! - A user can now enter HTML in the `CfgnNameTxtbox` which is saved in the `CfgnName` component and in turn rendered to the `CfgnNameLbl` via its `setValue` slot. This HTML code can potentially include JavaScript that is executed on rendering. - An evil user could do the following: - Create a new configuration in the published & embedded configurator. - Enter some HTML in the `CfgnNameTxtbox` which renders an unsuspiciously looking name but also executes some JavaScript. The JavaScript could add a dialog to the configurator that asks users for credit card information before checkout and sends it to the evil user. - This HTML is saved in the `CfgnName` component by the `SigSlos` shown above. - The evil user can now send a copy link to this configuration to anyone, promising a fake discount of 80% to anyone who buys a product via this link. - Everyone who performs checkout via this copy link is presented with the malicious credit card dialog and unknowingly sends his credit card information to the evil user... ### Mixed content If you have to write HTML in Hive which includes text from user input AND shall be rendered as HTML you can use Hives [encodeHtml](/docs/configurator-logic/hive-types/text-type#encodehtml) method to sanitize the user input. #### Example Imagine that the configuration name from the imaginary attack scenario above shall be shown in a "configuration overview" label whose content contains complex HTML to allow some fancy styling via custom CSS: ```hive /* Component "CfgnOverview" */ """
{=CfgnName}
{=CfgnOverviewText}
{=CfgnPrice.toText("€ 0.00")}
""" ``` ```hive /* SigSlos */ CfgnOverview.ValueChanged -> CfgnOverviewLabel.setValue(CfgnOverview.Value, false) ``` You can't set the `Sanitize Value` parameter to `true` here since that would destroy the beautiful HTML rendering that you actually require here, which leads to this code being open to [XSS attacks](https://owasp.org/www-community/attacks/xss/). **Solution:** Encode the user input via Hives [encodeHtml](/docs/configurator-logic/hive-types/text-type#encodehtml) method when embedding it in the HTML code: ```hive /* Component "CfgnOverview" */ """
{=CfgnName.encodeHtml()}
{=CfgnOverviewText}
{=CfgnPrice.toText("€ 0.00")}
""" ``` --- ## 3D/AR Viewer To display a 3D model with an included AR experience, the Combeenation platform offers a free 3D/AR viewer. ### Prerequisites - 3D model in `glb` or `babylon` format - Combeenation account ([Register now](https://www.combeenation.com/en/platform/registration/)) ### How to create a 3D/AR Viewer - Once you have a Combeenation company, go to your asset manager (1) - Create a new asset bundle (2) - Upload your 3D model by clicking on `+ New` button and selecting `Upload files ...` (3) ![3D Viewer Preview]() - Once the upload is completed and your 3d model is displayed correctly, click the `Create 3D/AR Viewer` button ![image.png]() - After successful creation, you should be redirected to the embed page. Here, you can view your newly created viewer and proceed with embedding it in your website or shop system. ### Update your 3d model - If you want to update your 3d model, go to your asset manager (1) - Select the babylon asset and upload your new updated 3d file - Once the upload is completed and your 3d model is displayed correctly, click the `Publish` button. - In order to also update your 3d viewer configurator, make sure both checkboxes are checked for your configurator and hit `Publish & Update assignments` ### Limits - The sole purpose of those configurators is displaying 3D objects - Interactions are limited to: - Enter AR mode - Reset camera - No further UI elements can be added - Feel free to contact us, if you want to upgrade this 3D/AR Viewer to a configurator - You can only create a 3D/AR Viewer for babylon assets, which are in an asset bundle which is published OR which has no connection to another configurator yet. --- ## Configurations A configuration is created each time a user opens a configurator and is used to save the user's progress. This is done by saving all the user input values, which are used to compute the result values. The configuration is identified by a unique ID. All configurations which were made in any configurator within a company are listed in the configuration table. You can access the table by clicking on configuration" in the top right corner of the configuration metrics on the dashboard. You can filter all configurations by type, ID, configurator name, and configurator version and also select a time period for your search. By clicking on one configuration entry in the table you get a bunch of information on the right side including the preset it was based on, the configurator name and version, and the creation time. You can copy and edit the configuration. Below the basic information, you get to see the "input values" and "result values". Within the input values table, you can see the inputs that were made by the user. The result values show all results that the user interaction triggered. ### Shared configuration If a configuration is shared on social media a shared configuration is created. Besides some additional metadata needed for the social media platform, this is basically a normal configuration. ### Finish A Finish is created when a checkout is performed. Besides the input values, the result values are stored in a finish. Those values can be used by the company for further processing (e.g. production). Each finish can also contain a price, which will be set via the shop property price. The price will be used for the calculation of the total revenue in the metrics. overview. ### Configuration metrics Every configurator has its own configuration metrics. The metrics include data about the number of configurations, finishes, shared configurations, total revenue and the conversion rate. Definitions for those wordings are listed below. These metrics can be viewed for predefined time periods and also for custom time periods. You can also compare two periods of time. | metrics data type | description | | :---------------- | :------------------------------------------------------------------------------------------ | | finishes | Configurations which have been completed through a checkout (mail, add to cart, …) | | shared | Configurations which have been shared via social sharing (Facebook, Instagram, WhatsApp, …) | | total revenue | Sum of shopping cart value of all finished configurations | | conversion rate | Ratio of configurations & finishes (finishes / configurations \* 100) | --- ## Copying Configurators and Asset Bundles ### Overview The export/copy functionality enables you to duplicate configurators and asset bundles for use in other companies where you are a member. ### Guide You can start your export to a different company by visiting the `Export` menu. By clicking on the `Start` button, the copy operation can be configured and started. ![export_1.png](https://docs.combeenation.com/assets/images/export_1-5f2cbcf2b5748dfa1e8bd89768382657.png) You can copy multiple entities to a different company with the following three steps described below. #### Select source entities to copy Please select the source configurators which you would like to export to a different company. All required (assigned) asset bundles will be automatically selected as well. You can copy a configurator without assigned asset bundles, but keep in mind that the configurator will very likely contain errors if any assigned asset bundles are missing in the target company. You can of course also just copy an asset bundle without selecting any configurators. By clicking `Reset selections` you can revert your settings and start from scratch. ![export_2.png](https://docs.combeenation.com/assets/images/export_2-468759cdfa929645a7ec24740879af52.png) #### Specify the source versions (advanced) After clicking on `Confirm selection` you can choose which version of the selected configurators and asset bundles should be copied. By default, the published version of each configurator will be selected. For asset bundles, the pre-selected versions are those that are assigned to a configurator. In most cases the default selection should be sufficient. To proceed, click on `Prepare for copy`. ![export_3.png](https://docs.combeenation.com/assets/images/export_3-968839f79f765d63f34381ac2299c833.png) By clicking on `Edit versions`, you can select additional versions of an asset bundle which should also be copied. :::warning Deselecting a required asset bundle version could potentially break your new configurator. Also selecting other bundles instead will not re-assign them to the configurator. Warnings will inform you about potential problems of your selection. ::: #### Define your target company After the target company is selected, you will get an overview over which entities are pending to be copied. You can optionally update the target names of your configurators or asset bundles. The auto-publish option enables you to additionally publish your configurators and asset bundles. If a name is already taken in the target company, you can decide if you want to add it to the existing one or create a new one. ![export_4.png](https://docs.combeenation.com/assets/images/export_4-753e1832bd962dec0c09d3f24cf2a675.png) With the `Start copy` button, the copy operation is started in the background. You don't need to stay on this page to wait for it to complete. You will be notified as soon as the copy process is completed, in the notification area in the top-right corner of the page. ![export_5.png](https://docs.combeenation.com/assets/images/export_5-100ff57a58dc5d148e3cedf25e3a5150.png) ### Important Considerations #### Copying a configurator without assigned asset bundles If you copy a configurator and unselect the assigned asset bundles, the configurator will probably not work as intended. This is because any assets that it uses will be missing. You will have to either manually fix any errors resulting from these missing assets, or upload new assets that you wish to use instead. You can also copy the original asset bundle belatedly. #### Asset bundle aliases If you decide to change the name of an assigned asset bundle during the copy process e.g. from `BikeAssets` to `MyBikeAssets`, the platform will use the old name ( `BikeAssets`) as [alias name](/docs/asset-management/assign-asset-bundles-to-configurators#alias-name) to avoid potential errors in your configurator. Such errors could occur if the configurator uses the asset bundle name to refer to assets. For instance, the code `BikeAssets.MyThumbnail` would no longer work if the new asset bundle is called `MyBikeAssets`. By using `BikeAssets` as alias name, this problem can be avoided. Alternatively, you could update all uses of the old asset bundle name, e.g. change `BikeAssets.MyThumbnail` to `MyBikeAssets.MyThumbnail`. :::info If you changed the name of an asset bundle, consider updating your assignments (aliases) after the copy process. E.g. change the alias name to `MyBikeAssets`, so that it matches the name of your new asset bundle. Use of the old name may be confusing for other users working on the configurator. Note that if you do this, you must manually update all uses of the old asset bundle name as described above. ::: ![export_6.png](https://docs.combeenation.com/assets/images/export_6-21a9cbd4d32c8d5097d783c0bbe69c5a.png) --- ## Create a basic 3D configurator The goal is to create a basic 3D Mokapot configurator. The user should be able to - change the color of the upper and lower part - open and close the lid - set a individual motive for the bottom part - view the Mokapot in AR mode ![2024-04-29_10h21_41](https://docs.combeenation.com/assets/images/2024-04-29_10h21_41-0fe7745301a23dd216787e4076dba8d6.png) Link to the finished [configurator](https://portal.combeenation.com/Cfgr/COMBEENATIONACADEMY/CREATEMOKA). ### Needed content For the implemenatation you need the following files :::note[Downloads] [moka-pot.glb](data/create-a-basic-3d-configurator/moka-pot.glb) [Graphics.zip](data/create-a-basic-3d-configurator//Graphics.zip) ::: ### Steps to reproduce To achieve the functionalities mentioned, the following steps must be implemented.
First steps 1. Create a new configurator based on the `Mokapot step by step example` template. - `Name`: Use a meaningful name of your choice - `ID` and `Asset bundles` are prefilled correctly :::warning If the assigned bundles are changed/renamed, it cannot be guaranteed that the following example can be implemented without any problems. ::: ![create-cfgr](https://docs.combeenation.com/assets/images/create-cfgr-eb08ccd70a5b090a63f27e83407621fd.jpg) 2. Use the asset manager to upload the `mokapot.glb` file. **Asset bundle**: Assets3DMoka **Folder**: Models3d ![2024-06-04_15h23_22](https://docs.combeenation.com/assets/images/2024-06-04_15h23_22-7f84d75c345644367143cbd2d04ba365.png) See the [documentation](/docs/configurator-ui-ux/controls/3d-viewer-control#how-to-show-models) for detailed information. :::info The bundle already contains some textures (see folder Textures3d) which will automatically be linked with the respective material when uploading your model. In general, textures need to be uploaded separately as GLB files encode textures into base64 strings, which might lead to warnings in your Materials3d folder. If such warnings occur, just select the detected asset and set the Image in Channels to `-` and save the changes. It's best practice to save your textures separately when exporting your models to GLB (e.g. in Blender or Autodesk Maya). That way you won't run into the described issues with textures being encoded to base64. ![2024-06-04_15h23_22](https://docs.combeenation.com/assets/images/asset-warning-168913d5df9234b945f150bd56c5fe78.png) ::: 3. Add the following line of code to the `ViewerParams` value component. ```hive Viewer3d.modelParameter(Models3d.mokapot, "visible", true) ``` To gain more information about this component, have a look at our [documentation](/docs/configurator-ui-ux/controls/3d-viewer-control#slots). 4. To make the 3D model visible in the viewer, connect the viewer control with the viewer parameters component. Add the following SigSlo. ![2024-06-04_15h33_30](https://docs.combeenation.com/assets/images/2024-06-04_15h33_30-353c81ae9c4bae41ce491b84efc64843.png)
Change colour individually 1. Go to the Asset Editor and create data source `Colours` based on the file asset `Moka_ProductData` (see [Create data source](/docs/category/asset-management)) ![2024-06-04_15h40_36](https://docs.combeenation.com/assets/images/2024-06-04_15h40_36-6bcf48980df14a07eab3522a12bf52c0.png) 2. Back in the configurator backend, create a table component named `Colours_Tbl`. 3. Load the colours into the component (see [Queries](/docs/configurator-logic/hive-queries)). To do this, add the following query to your table component: ```sql from ProductData.Colours select all ``` 4. Connect the table component and dataview for the top colour
Solution 1. Set the entries of the dataview - Open the SigSlo editor. - As Signal, select your `Colours_Tbl` component. - As Slot, select your dataview `TopColours_dv` with the option `Set entries`. - Set the source type of the entries-parameter to `Control or Component` and select your `Colours_Tbl` component, for property select `Value`. ![2024-06-04_15h48_36](https://docs.combeenation.com/assets/images/2024-06-04_15h48_36-0350ed8fe122370ec064449551cee513.png) - Edit the mapping-parameter so that `Text` and `Value` is mapped to `ID` and `Image background` to `HEX_CODE`. ![2024-06-04_15h49_01](https://docs.combeenation.com/assets/images/2024-06-04_15h49_01-8176dad17434a7018887b527b86283cf.png) 2. Write selection changes from the dataview back to the value component - Create a value component with the name `TopColour`. - Add the following code to the component ```hive Colours_Tbl.Get(Input or 0) or Colours_Tbl.GetFirst() ``` - Set the input type of the component to `Number`. ![2024-06-05_14h05_50](https://docs.combeenation.com/assets/images/2024-06-05_14h05_50-2d8a4ceea494c025bb02b63747801181.png) - Open the SigSlo Editor - As Signal use the `TopColours_dv` with the `Value Changed` signal - The Slot is your created `TopColour` value component with the option `Set value` - Set the source type of the entries-parameter to `Control or Component` and select your control `TopColours_dv`, for property select `Value`. ![2024-06-04_15h54_25](https://docs.combeenation.com/assets/images/2024-06-04_15h54_25-47a8a14007aa816f86bdb3fa632c6663.png) 3. Write selection changes from the value component back to the dataview. - Go to the `TopColours_dv` dataview and enable Hive for the dataviews value property, by clicking on the code icon next to the input field - Open the Editor by clicking on the Value label ![2024-06-04_15h56_37](https://docs.combeenation.com/assets/images/2024-06-04_15h56_37-7ecb7e3773d7b6345954ae22e86986b9.png) - Get the data from the value component `TopColour` by writing a simple script like ```hive TopColour ```
5. Connect the table component and dataview for the lower colour To do this repeat step 4. When creating value or table components use the word `Lower` instead of `Top`. Following components and controls are needed: `Colours_Tbl`, `LowerColour`, `LowerColours_dv` 6. Make colour changes visible in 3D Adapt the viewer parameters in the `ViewerParams` component. Add the following lines of code ```hive Viewer3d.materialParameter(Materials3d.matPaint_top, "color", TopColour.HEX_CODE), Viewer3d.materialParameter(Materials3d.matPaint_bot, "color", LowerColour.HEX_CODE) ```
Open/Close lid 1. Create a new value component named `LidOpened`, to store if the lid should be opened or closed. 2. Set the input type of the component to `Logic` and add the following script ```hive if Input is Empty then false else Input end ``` 3. To store the user input, connect the checkbox with the value component. To do this, create a SigSlo (the button with the node-like icon in the top right corner of the Controls panel) with the shown settings below. ![2024-06-05_08h08_40](https://docs.combeenation.com/assets/images/2024-06-05_08h08_40-2efe9b35df4c878bde62712bebe649e7.png) 4. You also need a connection in the other direction. If the value in `LidOpened` changes, the checkbox has to be updated. For that, enable Hive for the checkbox value property by clicking on the code icon next to the input field. ![2024-06-05_08h10_08](https://docs.combeenation.com/assets/images/2024-06-05_08h10_08-2f8bb1c61b937706ef164731584a0bf2.png) 5. Set the position of the opened/closed lid. To do this, you have to adapt the `ViewerParams` component. Add the following line of code to make the animation visible in 3D. `hive Viewer3d.nodeParameter("Toplid", "rotation", if LidOpened then "(-45,0,0)" else "(0,0,0)" end) `
Display an individual motive on the bottom part 1. Upload the images `graphic1.svg`, `graphic2.svg`, `graphic3.svg` and `graphic4.svg` as **text assets** in the asset manager. Make sure that the uploaded SVG files do not contain any `DOCTYPE` or `xml` tags as they must only include the pure `svg`. **Asset bundle**: ProductDataMoka **Folder**: Visualization **Asset type**: Text ![2024-06-05_08h18_52](https://docs.combeenation.com/assets/images/2024-06-05_08h18_52-686770d8bb0b75463277cbcfc2af4306.png) The corresponding data is already available in the configurator (components `Graphic_Tbl` and `LowerGraphic`). 2. Create a new Graphic component named `TextureView`. ![2024-06-05_08h24_05](https://docs.combeenation.com/assets/images/2024-06-05_08h24_05-edc2ee843c1bf60fe0ef873e4d24785a.png) - Set the size to 1024 x 1024px. Please click on the texts (Width, Height) to bring up the component editor rather than entering the values directly into the input field. - Add a new Visual of type `Single Visual` → `SVG` and name it `Graphic`. - To show the chosen image, set the `Texture.Source` to ```hive """ {=LowerGraphic.VIS_IMG.getText()} """ ``` :::info Nesting the motive within a parent svg element is necessary in this example as we want the motives to have a certain size, which is given by the outer svg's viewBox attribute. ::: 3. To display the motive on the mokapot use a so called [paintable](/docs/configurator-ui-ux/controls/3d-viewer-control#how-to-draw-dynamic-content-on-the-3d-model). Add the following line of code to the `ViewerParams` component. ```hive Viewer3d.materialParameter(Materials3d.matLogo_2, "paintable", { src: TextureView.Value.ToText(), uScale: 1, vScale: -1 }.ToText()) ``` :::info The paintable will most likely just show a black surface instead of the expected motive. If so, head over to the Asset Editor, go to the Materials3d folder and select the **matLogo_2** material. Head over to the Transparency section and set the Transparency mode property to **Alpha blend and test**. :::
AR mode To make the AR button work, you have to create a SigSlo with the `Start AR` slot. ![2024-06-05_08h28_11](https://docs.combeenation.com/assets/images/2024-06-05_08h28_11-7d4764fbd243ddeab47561592d75a9e7.png) Adjust the parameters below. These parameters are essentially texts displayed in the User Interface. By default, those values are static and filled with texts in English. If you want this texts to be dependent on the currently selected language, you might want to link to a component containing translations. In our example that's the **GeneralUI** component which holds dedicated columns for those AR-related texts. | Parameter | Source | Property | | ---------------- | --------- | ------------- | | QR sub title | GeneralUI | qrSubtitle | | QR description | GeneralUI | qrDescription | | Preparation text | GeneralUI | arPrepText | For detailed information about the AR mode have a look at the [documenation](/docs/configurator-ui-ux/controls/3d-viewer-control#start-ar).
Auto focus camera The auto-focus camera is used to find a default position, where all nodes are visible in the scene. To make the `CenterCamera_btn` work you have to create a SigSlo with the following settings. ![2024-06-05_08h30_58](https://docs.combeenation.com/assets/images/2024-06-05_08h30_58-c7d67c00d977a743881e87a25b6da2b6.png) For a good camera perspective, adjust the parameters as follows - `Alpha` to `180`, - `Beta` to `90` and - `Radius factor` to `1.4`. See [auto focus camera](/docs/configurator-ui-ux/controls/3d-viewer-control#auto-focus-camera) for detailed information.
--- ## Creating a configurator As it is possible on the [welcome](welcome) page, you can also create a configurator by simply clicking on the `+ New configurator` button in your configurator section. You will get a pop-up in which you can select a template and choose a name for your configurator or optional required asset bundles. ### Drafts When you add a new configurator, a draft is created automatically. **A draft is an unpublished version of a configurator.** In a draft you can edit your data and the configurator design. You can preview your changes in your draft and can decide when your draft is ready to be published. As soon as your draft is published, you have a live configurator, ready to be used and embedded on your website. Deleting a draft will not be available until one published version exists. ### Published configurator (link) If there is a **published** version of the configurator, it can be accessed by everyone through a link. The link can be found on the dashboard or on the embed page. The structure of the link looks something like: `portal.combeenation.com/Cfgr/{CompanyName}/{ConfiguratorName}` If the configurator isn't used anymore, it can be **unpublished** through the button in the drafts page. This action will delete the published version of the configurator, but it will not delete anything from the history. ### Configurator history Each draft that has ever been published is archived in the history. If something doesn't look good in your published configurator, you can always access the previous version in the history (preview the configurator as well as entering the editor in readonly mode). In order to find the desired history version, you can enter a description text each time you publish the configurator. ### Draft management Besides the initial draft which gets created upon configurator setup, you can of course create a new draft by clicking the `+ New draft` button in the drafts page. (Tip: This also gives you the option to create a new configurator based on an existing draft). **By default, a new draft is based on the published configurator version**. You can also create a draft based on another draft or a stage (if available). There is a limit of 10 drafts that can exist at the same time per configurator. This means there can be 10 different unpublished versions of a configurator. To avoid chaos we recommend the following naming conventions for drafts: draft naming usage `dev {your name}` In a "dev" draft you work productively on a configurator. There should never be more than one dev draft. `del {your name}` This draft can be deleted and is only used for looking up things in the configurator or for test purposes. `hotfix {bug}` A "hotfix" draft is for small changes and should be published quickly. The draft should always be named with a short description explaining its purpose. `review {your name}` This draft is used when someone reviews the development of the configurator. Instead of your name, you can also describe the changes or adaptions that you will implement in this draft. e.g.: "dev price calculation" You have several interaction possibilities with drafts (see three dot menu): - Edit draft ... this will enter the configurator editor and allows you to make changes - View draft in editor … this also opens the configurator editor, but you cannot make any changes here, only view the internals of the configurator. - Preview draft ... will preview the draft configurator in a new tab, so you can test the changes you made while you are working. Do **not embed** a preview on your website, only embed the published (or staged) version of your configurator! - Duplicate draft ... creates a new draft (or configurator) based on the selected draft - Delete draft … removes the draft. Deleted drafts can be restored within a period of time (see the [next section](/docs/fundamentals/creating-a-configurator#restore-deleted-drafts)) :::warning Previews don't support the **Share** and **Checkout** features. These are only available in published or staged configurators. ::: #### Restore Deleted Drafts If you have accidentally deleted a configurator draft you can recover it **within 30 days** of it being deleted. Expand the "**Deleted drafts**" section at the bottom of the drafts page and find the draft you wish to restore. One click on the "**Restore**" button and you have your draft back. Keep in mind that you cannot exceed the maximum number of drafts this way – so if you already have 10 drafts, you first have to publish or delete one of them before you can create or restore more. ### Configurator Stages A stage is a version of a configurator that can be embedded in a website to test all features of a configurator before publishing it. Stages are a powerful tool that unlock more sophisticated development workflows and are typically embedded in a dedicated QA environment, not on your actual production website or shop. A stage can be created in one of two ways: 1. by opening a draft in the configurator editor and pressing the "Stage" button 2. by staging an already existing history version of the configurator (mostly as a starting point for further development) :::info You can only create a limited number of stages, which depends on your subscription. Contact us if you are interested in (more) stages! ::: A staged version of a configurator works in almost the same way as a published configurator. Unlike a preview, a stage supports all features of a configurator to allow you to make end-to-end tests of a configurator before publishing it. Keep in mind though that a stage does not replace a published configurator! While it supports all configurator features, there are limits on how many configurations can be created with a stage in a certain period of time. **A configurator stage cannot be edited directly.** Like with the published version of your configurator, you have to create a draft based on the stage in order to make changes. Once the draft is ready, you can stage it again and thus update the staged version. Unlike a preview, the embed URL of the stage won't change when you stage a new draft. You can alter the behavior of a configurator when it [is staged, published or in preview](/docs/configurator-logic/configurator-and-presets-variable#properties), which may be necessary e.g. in [checkout](/docs/integrations-and-api/checkout#checkout-and-stages) to account for differences between a production and QA environment. #### Development workflow with stages Here is an example of a typical development workflow that makes use of stages: 1. create a configurator draft as usual and make the desired changes to your configurator 2. stage the draft. Enter a name for the stage if you don't have one already. 3. embed the stage in your QA website to test it 4. create a new draft based on the stage to continue development 5. once the new draft is ready, stage it again. - Repeat these two steps until you have made all necessary changes. - You can keep testing the embedded configurator throughout the entire process. 6. when you are done testing it, publish the stage - Until you publish a stage, the live version of your configurator remains fully unaffected by your work on drafts and stages ### Assign asset bundle and configurators After you have entered the configurator editor you can of course start editing the configurator logic, but at some point you probably want to use assets inside the configurator. In this case, an asset bundle must be assigned to a configurator. This can be done clicking on the asset drawer in the top center, which lets you select your desired asset bundle. All versions of the asset bundle should now be listed, you probably want to assign the newest version at the top to your configurator. This will be done by clicking on the `+` icon on the left of the desired version. ### Component and groups In order to build your configurator logic, you can create components and cluster them together in groups. The content of those components is Hive code, for more information see [Hive introduction](/docs/configurator-logic/hive-introduction). ### Embed finished configurator After you have successfully created your first configurator, it is ready to publish, simple hit `Publish` button in the top center in the configurator editor. This will create a new version (as already discussed above) and redirect you to the drafts page. You can now embed your configurator wherever you want, for more info see [embedding](/docs/integrations-and-api/embedding-and-whitelist). --- ## Custom Fields Custom fields allow you to store additional data in different areas of your company: - [User](#user-fields) … per user (e.g. discount, language) - [Company/Distributor](#companydistributor-fields) … per company or distributor (e.g. region) - [Quote](#quote-fields) … per quote (e.g. terms of payment) - [Quote Item](#quote-item-fields) … per line item (e.g. delivery time) :::info[Pro Feature] The availability of custom fields depends on your subscription! Contact us if you are interested in enabling (more) custom fields in your CompanyOn the Combeenation platform, a company means exactly what it describes: a company. For the most part, these are customers who have ordered a configurator from us. However, there are also internal companies that we use for best practice examples, for testing or for internal configurators.. ::: ### Creating custom fields Each custom field consists of three properties: - **Name** must be a valid Hive identifier. Names are case-insensitive, so `discount` and `Discount` are considered as the same - **Type** is one of the Hive types `Number`, `Text`, `Logic`, `List` or `List` - **Fallback Value** is the value of the field that will be used if no explicit value for this field is set. You can create and manage custom fields on your company’s **Settings** page. - Define fields and fallback values. - Assign values at the **Company** or **Distributor** level. - For **User Custom Fields**, you can also assign values per individual user. :::info Published Configurators are not affectedThe published link is the published version of the configurator and is used for integration with the customer. Checkouts and share functions can only be tested here. Everyone who knows this link has access to the configurator. When you create, modify, delete or change the fallback value of a custom field, your staged or published configurators won't be affected immediately – they must be published again to use the updated fields. This is a safeguard to protect these configurators and allows you to adapt them as necessary when you have to alter fields. Note that changes to field (company, distributor or user) _values_ do immediately affect staged and published configurators! ::: ### Custom Field Management ##### Company On the **Company Settings** page, you can: - Create, edit, or delete custom fields. - Assign company-specific values. - **Keep in mind**: deleting a field removes its values in all related entities. ![]() ##### Distributor On the **Distributor Settings** page, you can only set distributor-specific values. If no value is set, the **Fallback Value** from the company is used. ![]() ### Types of custom fields You can create different types of fields to support various use cases in configurators. #### User Fields Custom field values for the signed-in user are accessible in Hive, in the `user.fields` variable. **Examples** - Apply different discounts to sales users. - Restrict feature availability based on user fields. - Add a `UserId` field to fetch external user data from workflows. **Value Priority** 1. User-specific value (set on the Users page) 2. Company/Distributor value 3. Fallback value ##### Hive Example ```hive title="Hive" // assuming you created a custom field 'Discount' BasePrice * (1 - user.fields.Discount) ``` **Note** that you do not have to check a field value for `Empty`, because even if no value for this field is set on the current user (or the user isn’t signed in), the field will still have a value thanks to the Fallback Value. See the [Hive User](/docs/configurator-logic/user-variable) docs for more details on the `user` variable. #### Company/Distributor Fields Company and distributor custom fields are available via `configurator.owner.fields`in Hive. **Examples** - Define regions per company or distributor. - Limit configurator features by company. - Restrict products for certain distributors **Value Priority** 1. Company/Distributor value 2. Fallback value ##### Hive Example ```hive title="Hive" // assuming you created a custom field 'Region' configurator.owner.fields.Region ``` :::note You do not have to check a field value for `Empty`, because even if no value for this field is set, the field will still have a value thanks to the Fallback Value. See the [Configurator Variable](/docs/configurator-logic/configurator-and-presets-variable) docs for more details on the `configurator` variable. ::: #### Quote Fields Quote custom fields let you enrich your quote infrastructure. **Examples** - Add terms of payment to your quotes - Mark important quotes with a custom severity level ![]() **Value Priority** 1. Company/Distributor value 2. Fallback value The fallback value of a Quote custom field is used, if no value is provided for the currently selected company or distributor. If you define a Company/Distributor value, then this value will be used when no value is provided during CheckoutThis completes a configuration and exports the data to a third party. The choice of a checkout depends entirely on the needs of the customer or their target group.. ##### Hive Example :::info Coming soon after this feature is available! ::: #### Quote Item Fields Quote item custom fields apply to each line item of a quote. **Examples** - Add an image field to each line item. - Define delivery times per product. ![]() **Value Priority** 1. Company/Distributor value 2. Fallback value The fallback value of a Quote Item field is used, if no value is provided for the currently selected company or distributor. If you define a Company/Distributor value, then this value will be used when no value is provided during CheckoutThis completes a configuration and exports the data to a third party. The choice of a checkout depends entirely on the needs of the customer or their target group.. ##### Hive Example :::info Coming soon after this feature is available! ::: --- ## Keyboard shortcuts --- | Hotkey | Action | Detail | | ------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `Ctrl + K` | Quick launcher (backend) Available actions depend on the current view | Fast way to: - go to pages - find and select components - switch company | | `Ctrl + O` | Focus component filter box | Only in editor view | | `Ctrl + Shift + L` | Send current session log to the monitoring tool (backend & configurator) | To reproduce or analyze possible bugs | ### Code editor The editor is based on the "Monaco editor" which is also used by VS Code. #### How to find shortcuts - Press F1 inside the editor and search for your command. If a shortcut is available it will be shown on the right. - Most of the shortcuts match with the ones from VS Code. If you have VS Code installed you can find them under "File > Preferences > Keyboard shortcuts" #### Custom Hive hotkeys | Hotkey | Action | Detail | | ---------- | ------------------ | ------------------------------------------------- | | `Ctrl + B` | Autoformat records | Formats a record component in a table-like manner | --- ## User Permissions Every user can be assigned to multiple companies with its own individual role. Each role always includes the permissions of the preceding one. ### Basic explanation | role | info | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | sales | View and analyze configuration insights. | | developer | Can create/edit/delete/publish configurators and asset bundles. In addition, a developer is allowed to manage workflows and edit CompanyOn the Combeenation platform, a company means exactly what it describes: a company. For the most part, these are customers who have ordered a configurator from us. However, there are also internal companies that we use for best practice examples, for testing or for internal configurators. settings. | | admin | Is allowed to edit the staff (invite, manage permissions). This role is only given upon special agreement with Combeenation. | ### Detailed Overview | Permission | sales | developer | admin | | --------------------------------------- | ----- | --------- | ----- | | analyze configurations/insights/metrics | ✓ | ✓ | ✓ | | view, edit and publish configurators | | ✓ | ✓ | | view, edit and publish asset bundles | | ✓ | ✓ | | manage company settings | | ✓ | ✓ | | run workflows | | ✓ | ✓ | | manage workflows | | ✓ | ✓ | | manage staff | | | ✓ | ### Manage staff Only admins can manage staff. To manage the staff, go to the users page found in the left sidebar. In this view you can do the following: - Create invite links to invite new users - Edit role of existing users - Remove users from the company --- ## Welcome After your first login or after simply **clicking on the Combeenation logo** in the upper left corner, the welcome page is shown immediately. This is your entry to create awesome configurators described below. ![welcome_cards.png](https://docs.combeenation.com/assets/images/welcome_cards-352a122f3618c5761b001f4349526431.png) #### Creating a new configurator It is possible to select a template or create a configurator from scratch. ![welcome.png](https://docs.combeenation.com/assets/images/welcome-be35bec018d0ef80743d92064a0718c5.png) After selecting a template, you can choose your custom configurator name and ID. If the template uses asset bundles, then choose names for all required asset bundles. :::info The ID is used to create the URL of your configurator. ::: ![create_configurator.png](https://docs.combeenation.com/assets/images/create_configurator-3d4fbc8f78090d1dd7591493a1f7c4ee.png) --- ## Analytics ### Google Tag Manager Whilst there is no direct integration of a configurator in the Google Tag Manager (GTM), we provide a ready-to-use interface to forward certain configurator events into the “[data layer](https://developers.google.com/tag-platform/tag-manager/datalayer)”. The events in the “data layer” can be interpreted by the GTM in the process. :::info[Google Tag Manager (GTM) setup/configuration] You are responsible for setting up GTM (embedding the script on the page) and configuring your GTM dashboard (e.g. widgets). ::: There are 2 ways of triggering such a configurator event: - via [fireAnalyticsEvent](/docs/configurator-ui-ux/controls/configurator-control#fire-analytics-event) slot - via [Cbn.utils.IFrameHelper.fireAnalyticsEvent](https://custom-code.docs.combeenation.com/classes/Cbn-2.utils.IFrameHelper.html#fireAnalyticsEvent) Custom Code function Both variant push the event into the data layer in a standardized format: ```json // 1: event key only { "event": "myEventKey" } // 2: event data as string { "event": "myEventKey", "eventData": "Some event data as string" } // 3: event data as object { "event": "myEventKey", "eventData": { price: 10, amount: 3 } } ``` --- ## Flex checkout Basically the flex checkout lets you decide which data (e.g. amount, description, generated images, ...) will be passed to the parent page when a user performs a Checkout. As already described in [Embedding and configurations](/docs/integrations-and-api/embedding-and-whitelist), the configurator will communicate with the parent page via cross-document messaging during the checkout. Technically speaking this all happens within your browser on the client side, so it is extremly fast. ### Combeenation platform On the Combeenation platform you just have to define the data within the property `Parameter` in the flex shop. Combeenation won't enforce a specific file format (can be JSON, CSV, XML, ...), but we strongly recommend JSON. If you aren't required to use a specific JSON format, we strongly recommend the following structure: ```json { "products": [ { // (mandatory) at least one product must be defined "main": { // (mandatory) "productId": "123456", // (mandatory) SKU "quantity": 1, "price": 99.99, // gross price of the configured product WITHOUT accessories "imageUrl": "Assetbundle1.MyImage.url", // url to the image which will be used in the cart "title": "My coffee mug", // new title of the product "productText": "My own description", // new description of your product "customFields": [ // (optional) { "id": "model", // (mandatory) unique identifier "label": "Model type", // label which the user will also see in the cart (if it is visible) "value": "espresso", // value of the custom field "position": 1, // position in the cart "isVisible": true // if it should be visible in the cart } ] }, "accessories": [ // (optional) additional predefined products which will be added to the cart (no custom price/image/title) { "productId": "987654", "quantity": 1 }, { "productId": "456789", "quantity": 1 } ] } ] } ``` ### Your website/shop/system/... On the parent page (on which the configurator is embedded) you just have to implement the JavaScript function `window.Combeenation.onCheckout`. This function will be called during the checkout and has one parameter which contains: - configuration id - configuration auth token - `CheckoutParameters` are the values you have defined previously - `CheckoutParametersHash` SHA256 hash of `CheckoutParameters` - queryParameters which are set to the iframe url With this data on your hand you can implement your further checkout logic on your own. Example implementation of the checkout function: ```html ``` ### Data security (integrity) :::warning If there is sensitive information in the flex checkout parameter (e.g. price), we strongly recommend that you verify that the information hasn't been tampered with by a malicious user (e.g. price set to 0). Combeenation provides a [HMAC](https://en.wikipedia.org/wiki/HMAC) hash over the data in order to ensure unforgeability. ::: The checkout response property `checkoutParametersHash` contains a SHA256 hash over the `checkoutParameters`. The hash will be salted with a secret which can be defined in the `HashSecret` property of the flex shop. This secret should be kept private and only be stored on your server. With the `checkoutParameters` and the secret you can calculate a hash and test whether it matches the given `checkoutParametersHash`. If the hash differs the `checkoutParameters` have been altered. Example for checking the hash in PHP: ```php $secret = "myPrivateSecretWhichIsOnlyStoredOnTheServer"; $data = $_POST["checkoutParameters"]; $givenHash = $_POST["checkoutParametersHash"]; // (optional) normalize line endings; there should be no \r in the checkoutParameters $data = str_replace("\r\n", "\n", $data); // convert base64 hash $base64Hash = base64_encode(hash_hmac('sha256', $data, $secret, true)); // check hash if($base64Hash == $givenHash) { echo "Add to cart successful"; } else { echo "Hash invalid!"; } ``` --- ## FTP checkout This Checkout shop can upload one or multiple files to a given FTP server. The uploading files are defined via a Hive record, with the following structure: ```hive [{ FilePath : "", ContentEncoding : Encoding.Utf8, Content : "" }] ``` #### Limits - Maximum size of all attachments combined is 100 MB. - Currently only text files are allowed as files (e.g. XML, CSV, JSON, ...). --- ## Mail checkout This checkout will send one or multiple mails on Checkout. The mails are defined via a Hive record, with the following structure: ```hive [{ From : { Name : "John Doe", Mail : "john.doe@example.com" }, To : [{ Name : "Jane Doe", Mail : "jane.doe@example.com" }], CC : [{ Name : "", Mail : "" }], BCC : [{ Name : "", Mail : "" }], Subject : "", Message : "", Attachments : [{ FileName : "", ContentEncoding : Encoding.Utf8, Content : "" }] }] ``` :::warning You can set a from address, but per default this will only be used as reply address and the real from address will be `mail.combeenation.com`. In order to use your company's domain as sending domain, checkout the options in [Mail settings](/docs/integrations-and-api/mail-settings). ::: #### Limits - Maximum size of all attachments combined is 5 MB - Currently only text files are allowed as attachments (e.g. XML, CSV, JSON, ...). --- ## REST checkout This Checkout will perform up to five parallel REST requests to given endpoints. The endpoint should return successful HTTP code (200-299). Requests are defined via a Hive record, with the following structure: ```hive [{ Method : "", Url : "", Content : "", ContentEncoding : Encoding.Utf8, AuthIntegration : "", // deprecated Headers : [ { Key : "Content-Type", Value : "" }, RestShop.BasicAuth("username", "password") // (optional) helper method if basic auth is required ] }] ``` Hive #### Limits\Restrictions - Maximum size for all requests (content + headers) is 10 MB - Maximum of 5 requests - Supported HTTP methods: `POST`, `GET`, `PUT`, `DELETE`, `HEAD`, `OPTIONS`, `TRACE` - Pending requests will be cancelled after 60 seconds - If an exception occurred or the response code was 408 (`RequestTimeout`), 500 (`InternalServerError`), 502 (`BadGateway`), 503 (`ServiceUnavailable`) or 504 (`GatewayTimeout`) the call will be retried up to three times. - For `GET` and `HEAD` calls, the content will be omitted - Currently only text files are allowed as request body (e.g. XML, CSV, JSON, form data, ...). --- ## Shopify We highly recommend utilizing our free Shopify app if you wish to sell customizable products online through your Shopify shop. The app offers a smooth integration between the configurator and the checkout process of your Shopify shop. ### Setup To get started, some simple steps must be performed on the Combeenation platform and Shopify shop. ``` Plain text The ` ``` And the ` ``` :::info The values for COMPANY and CONFIGURATOR need to be exchanged to those of your configurator. ::: ##### Step 3 Next step would be the customizing the `