Custom Components

The custom component template extension point allows you to encapsulate and reuse common logic in the form of a custom component template.

Similar to the built-in components, the custom components can also have properties.

You can use custom component templates in the following common use-case scenarios:

  • If you need a component that does not exist in the toolbox, you can implement your own or use a third-party component.
  • If you want to augment the functionality or adjust the appearance of a built-in component, you can wrap it in a custom component.

Defining the Schema

The <component_name>.json file represents the schema file which is used by the Studio to display all available properties and to generate the runtime code. As a standard, the Studio utilizes the JSON schema version 4.

The following example demonstrates the minimum setup that you need to apply.

  • <component_name>, <category>, and <description> are optional.
  • <category> is used by the toolbox to group the components in a convenient visual manner and accepts any string.
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "<component_name>",
    "type": "object",
    "name": "<component_name>",
    "description": "<description>",
    "category": "<category>",
    "properties": {
    }
}

Often, web projects require you to set at least one property, for example, a title, which you have to include in the properties object of the schema.

{
    "properties": {
        "title": {
            "type": "string",
            "title": "View Title",
            "description": "The title of the view shown in the header section",
            "default": "Hello",
            "order": 1
        }
    }
}

Some of the properties descriptor fields are:

  • (Required) type—Renders the proper editor for that property. The possible values and the corresponding editors are string (TextBox), boolean (CheckBox), number (Numeric Text Box), integer (Numeric Text Box with one step), array (Drop Down List), and object (renders nested, expandable levels in the property grid). The Studio also supports composite types too—for example, [ "integer", "null" ]. Using the correct type provides template users with a hint of what value type is expected.
  • (Required) title—Represents the name of the property that will be displayed in the property grid.
  • (Optional) description—Represents a hint in the code that is displayed nowhere else.
  • (Optional) default—Auto-populates the editor too. The generated code will have a default value too.
  • (Optional) order—Defines the order in the property grid for that property.
  • The properties field description is compliant with the JSON schema version 4. You can also use any other field that is defined in the standard.
  • To associate the Master Symbol design tool to a custom component and define a property as overridable in the design tool plugins, that property has to be marked as overridable:true in the designOptions of the schema.
...
   "content": {
      "type": "string",
      "title": "Text",
      "default": "Button",
      "order": 2,
      "designOptions": {
        "name": "content",
        "overridable": true
      }
    },
...

Using the Design-Time Template

The design-time template provides options for customizing the design process of the application. Once you create the template, you can choose the view from the Component list and use it.

The template consists of several files and some of them are optional. However, the template.html.ejs is required and represents the template that appears on the canvas. It can display anything, for example, a Hello World string or a very complex HTML structure. It does not allow you to interact directly with it and that is why it is recommended that you keep it simple. The purpose of the Design-Time template is to display sufficiently close visual representation of the generated application version. To interact with the template, use the exposed properties which are displayed in the property grid on the right.

To add styles to the HTML template, append a <style></style> section at the end of the file and "namespace" them with a prefixed class.

<div class="my-custom-calendar">custom calendar</div>

.my-custom-calendar .date-cell {
    color: blue
}

The wrapper HTML element has the my-custom-calendar class whit which the .date-cell selector is namespaced. The namespacing class itself has the my- prefix in this case which is done to minimize the risk of accidental style overrides in the canvas.

  • (Required) options.json.ejs—Defines the template properties which are later used to extend the initial template model. This approach is suitable when you want to provide the template with more dynamic behavior.

    If the template is simple, provide an empty object {} so that the file can be JSON-validated.

  • (Optional) generator/index.js—Used to augment the initial model of the template and, in this way, provide additional dynamic behavior when designing the application. For example, you can bind to data some components that are inside the view and display some sample data. Otherwise, they will be empty and not representative to other developers. Another options for you is to show or hide certain parts of the template based on the selected properties. For example, if the edit property is true, you display a form. In this file you have full access to the meta model. If the template is simple enough, skip it.

  • (Optional) <component_name>.png—Represents the image on the list of the components in the left toolbar. If not provided, a default one will be displayed.

Using Runtime Templates in Angular

The Angular template consists of the following files:

  • (Required) template.html.ejs—Represents the Angular component that will be rendered directly in the view when the user adds it. The definitions of the component and the controller are provided separately. For example, to create a custom my-calendar component, the template will look similar to the following example.

    <my-calendar
        [config]="$config.components.<%- id %>"
        [id]="'<%- id %>'">
    </my-calendar>
    
  • (Required) config.json.ejs—Provides a way to pass a subset or calculated properties from the meta definition to runtime. These properties become accessible through the $config object as previously demonstrated. Under the hood, the generator constructs the $config object and exposes it as a public member from the base view.

    public $config: any = {
        components: {
            <component_id>: {
                <prop_name>: <value>,
                <prop_name>: <value>,
                ...
            },
            ...
        },
    };
    
  • (Required) options.json.ejs—Provides a suitable way to pass the calculated properties from design-time to runtime. You can then use vm.$components.<component_id>.options to access them both from the template and from the controller.

    {
        widget: null,
        options: {
            title: "<%- title %>",
            minDate: "<%- minDate %>",
            maxDate: "<%- maxDate %>"
        },
        events: {
            onChange: (e) => {
                <% if (events.onChange) { %>
                    this['<%- events.onChange %>'](e);
                <% } %>
            }
        }
    }
    

Using Runtime Templates in React

The React template consists of the following files:

  • (Required) template.html.ejs—Represents the React JSX or TSX representation of the component that will be rendered directly in the base view when the user adds it. The definition of the component is provided separately. For example, to create a custom myCalendar component, the template will look similar to the following example.

    <CustomComponents.myCalendar
    {...componentsState.<%- id %>}
    >
    </CustomComponents.myCalendar>
    
  • (Required) config.json.ejs—Provides a way to pass a subset or calculated properties from the meta definition and from design-time to runtime.

      {
          id: '<%- id %>',
          key: '<%- id %>',
          units: '<%- units %>',
          label: '<%- label %>'
      }
    

    These properties become accessible through the componentsState object in the base view as previously demonstrated. Under the hood, the generator constructs the componentsState object and exposes it as a public member from the container view file.

    ```ts
    const propsConfig = {
        <component_id>: {
            <prop_name>: <value>,
            <prop_name>: <value>,
            // For example:
             title: '<%- title %>',
             label: '<%- label %>',
             onChange: function(e: CalendarChangeEvent) {
    
             }
            ...
        },
        ...
    };
    ```
    

Using Custom Component Templates in Angular

The Studio uses the custom component template to generate code and render it inside the application each time the user drags it from the toolbox to the canvas. At this point, Angular does not relate with the rendered <my-calendar> component and you have to define its template and controller. Otherwise, an exception will be thrown.

To define the template and controller in Angular:

  1. Create a folder inside app/src/app/shared next to the components/ folder and name it, for example, custom-components.
  2. Inside this folder, create a folder for each custom component. These components are Angular components and apply the specifics of the framework. You can also have an individual .css file.

    • In custom-components/my-calendar/my-calendar.component.html:

      
      <h2>{{name}} - {{config.min}}</h2>
      
      
    • In custom-components/my-calendar/my-calendar.component.ts:

      import { Component, Input } from '@angular/core';
      
      @Component({
          selector: 'my-calendar',
          templateUrl: './my-calendar.component.html'
      })
      export class MyCalendarComponent {
          @Input() public config;
          public name: string = 'My Calendar';
      }
      
  3. Register this component in the shared module config file so that Angular associates it. Each module that is generated by the Studio, including shared, is extendable. Locate the shared.module.ts file and add your component.

    ```ts-no-run
    /////////////////////////////////////////////////////
    // Add your custom code here.
    // This file and any changes you make to it are preserved every time the application is generated.
    /////////////////////////////////////////////////////
    import { NgModule } from '@angular/core';
    
    import { config } from './shared.config';
    
    // You can modify or replace the module config here.
    import { MyCalendarComponent } from './custom-components/my-calendar/my-calendar.component'; // Import the component from the folder where you place its files.
    
    config.declarations.push(MyCalendarComponent); // Push it to the declarations array.
    config.exports.push(MyCalendarComponent); // Remember to export your component so that it can be used by other modules too.
    
    @NgModule(config)
    export class SharedModule { }
    ```
    

Using Custom Component Templates in React

The Studio uses the custom component template to generate code and render it inside the application each time the user drags it from the toolbox to the canvas. At this point, React does not relate with the rendered <CustomComponents.myCalendar> component and you have to define or import the component. Otherwise, an exception will be thrown.

To define the component in React:

  1. Create a folder inside app/src/app/shared next to the HOCs/ folder and name it, for example, custom-components.
  2. Inside this folder, create a folder for each custom component. These components are Angular components and apply the specifics of the framework. You can also have an individual .css file.

    • In custom-components/myCalendar/myCalendar.tsx:

      import React from 'react';
      
      const myCalendar: React.FC<any> = props => {
      
          return (
          <h2>{props.label}</h2>
          );
      };
      
      export { myCalendar };
      
  3. Import this component in the shared/mainCustom file. mainCustom file exports the CustomComponents object and allows React to associate it and render the CustomComponents.myCalendar from each base view.

    ```ts-no-run
    /////////////////////////////////////////////////////
    // Add your custom code here.
    // This file and any changes you make to it are preserved every time the application is generated.
    /////////////////////////////////////////////////////
    import { myCalendar } from './custom-components/myCalendar/myCalendar';
    
    export {
        myCalendar
    };
    ```
    
In this article
Not finding the help you need?