Concorde documentation (crawl) ยท interactive version

Concorde functional component โ€” SDUI. SDUI stands for Server Driven User Interfaces. Doc ID: core/components/functional/sdui/sdui. Keywords: Concorde, supersoniks, core/components/functional/sdui/sdui, sdui, SDUI, functional component, sonic-sdui, web component, Server Driven User Interfaces, props, contents. URL: https://concorde.supersoniks.org/crawl/core/components/functional/sdui/sdui.html.

SDUI

What is SDUI?

SDUI stands for Server Driven User Interfaces. Basically, it generates a user interface based on a JSON SDUI Descriptor.

Subscriber extension:

SDUI extends the mixin Subscriber. This means that you must set a dataProvider with an id pointing to the publisher that will hold your SDUI Descriptor. This also means that it has a reactive property named props that you can set with the JSON SDUI Descriptor in order to configure it.

Fetcher extension:

SDUI extends the mixin Fetcher as Fetch and List.

In this case, it will parse the JSON coming from the request. The JSON must comply with the SDUI descriptor format.

As a fetcher, invalidating the publisher or updating the endpoint will trigger a new fetch and update accordingly.

Rendering:

It has no shadowdom, and its display style is set to contents, so an empty SDUI has the fewest impact on the layout.

SDUI descriptor

TypeScript definition:

export type SDUIDescriptor = {
      js?: Array<string>;
      css?: Array<string>;
      library?: Record<string, SDUINode>;
      nodes?: Array<SDUINode>;
    };

Meaning of the keys:

SDUINode

Descriptor

An SDUINode represents an HTMLElement.

TypeScript definition:

export type SDUINode = {
      markup?: string;
      tagName?: string;
      attributes?: Record<string, string>;
      nodes?: Array<SDUINode>;
      innerHTML?: string;
      prefix?: string;
      suffix?: string;
      libraryKey?: string;
      contentElementSelector?: string;
      parentElementSelector?: string;
    };

Summary of properties:

Markup

Use it if you prefer to define your node entirely using an HTML string.

<sonic-sdui 
      dataProvider="sdui-markup-example" 
      props='{
          "nodes":[
            {
              "markup":"keep shouting <sonic-badge>foo</sonic-badge> <sonic-badge>bar</sonic-badge> !"
            }
          ]}'
    ></sonic-sdui>

Tag name

Here we use the field tagName of SDUINode to create an element with tag name sonic-input.

<sonic-sdui 
      dataProvider="sdui-tagName-example" 
      props='{
          "nodes":[
            {
              "tagName":"sonic-input"
            }
          ]}'
    ></sonic-sdui>

Attributes

A string key/value pair storing each attribute name/value of the HTML element created. As no tag name is defined, a div element is created. Here we define the style attribute to create a tiny red square.

<sonic-sdui 
      dataProvider="sdui-attributes-example" 
      props='{
        "nodes":[{
          "attributes":{
            "style":"width:50px;height:10px;background:red;"
          }
        }]
      }'
    ></sonic-sdui>

Nodes

Children of the current node can be added using the field nodes recursively.

<sonic-sdui 
      dataProvider="sdui-nodes-example" 
      props='{
        "nodes":[{
          "nodes":[
            {"innerHTML":"A"},
            {
              "prefix":"B",
              "nodes":[
                {"innerHTML":"๐Ÿ‘‰ B.1"},
                {"innerHTML":"๐Ÿ‘‰ B.2"}
              ]
            }
          ]
        }]
      }'
    ></sonic-sdui>

InnerHTML

As no tag name is defined, a div element is created, and then we use innerHTML to add content to it.

<sonic-sdui 
      dataProvider="sdui-html-example" 
      props='{
        "nodes":[{
          "innerHTML":"keep shouting <sonic-badge>foo</sonic-badge> <sonic-badge>bar</sonic-badge> !"
        }]
      }'
    ></sonic-sdui>

Prefix and suffix

Use them if you want to wrap the component with some HTML string.

<sonic-sdui 
      dataProvider="sdui-prefixSuffix-example" 
      props='{
        "nodes":[{
          "prefix":"๐Ÿ‘‡",
          "suffix":"โ˜๏ธ",
          "innerHTML":"The node content"
        }]
      }'
    ></sonic-sdui>

Library key and contentElementSelector

The libraryKey of SDUINode is an identifier that points to an SDUINode description in the library to be used as a model for this node.

Note that the SDUI has a default library containing some basic component definitions useful for form declaration (see default-library.json).

This example uses the library key submit which points to a sonic-submit containing a button also coming from the library.

The element named button in the library is a sonic-button of type success containing a check icon as a prefix.

Since the submit library element contains an attribute contentElementSelector with the value sonic-button, the "injected content" is put inside the button.

We used innerHTML for the sake of simplicity, but you can use nodes to add any complex content.

<sonic-sdui dataProvider="sdui-library-example" props='
          {
            "nodes":[
              {
                "libraryKey":"submit",
                "innerHTML":"Injected content"
              }
            ]
          }
    '></sonic-sdui>

parentElementSelector

This special field lets you inject the content at any place in the HTML flow already set inside the parent SDUI component by using a CSS selector.

โš ๏ธ Note that it doesn't work with top-level nodes.

<sonic-sdui dataProvider="sdui-parentElementSelector-example" props='
          {
            "nodes":[
              {
                "nodes":[
                  {
                    "prefix":"๐Ÿ‘‡ Selected parent element",
                    "suffix":"โ˜๏ธ End of selected parent element",
                    "tagName":"span"
                  },
                  {
                    "parentElementSelector":"span",
                    "innerHTML":"Content having a parentElementSelector attribute"
                  }
                ]
              }
            ]
          }
    '></sonic-sdui>

Fetch example:

<sonic-sdui dataProvider="sdui-fetch-example" endPoint="/src/core/components/functional/sdui/example.json"></sonic-sdui>

Transformers:

The transformers let you transform the structure of the SDUI Descriptor into a new one before parsing and rendering it.

To enable it, you must fill the transformation attribute of the component with a URL pointing to a Transform descriptor.

export type SDUITransformDescription = {
    library?: Record&lt;string, SDUINode&gt;;
    transforms: Array&lt;SDUITransformAction&gt;;
  };

The library:

Each key of the library will be merged with the current library in the SDUI Descriptor, by replacing or creating items library key by key.

The transform actions:

Each transform represents an action that will be done on the SDUI descriptor before the creation of the entire UI.

export type SDUITransformAction = {
  action: SDUITransformActionName;
  patterns?: Array&lt;SDUINode&gt;;
  after?: SDUINode;
  before?: SDUINode;
  in?: SDUINode;
  ui?: SDUINode;
};

The action that will be done is a verb:

export type SDUITransformActionName = "remap" | "unwrap" | "wrap" | "delete" | "insert" | "move";

Misc Tricks:

sduiKey

This is an HTML attribute to set on the component if needed. In this case, the component will not treat its props value directly as an SDUI descriptor. Internally, it will basically extract the SDUIDescriptor by doing something like this: const sduiDescriptor = this.props[this.sduikey].

messageKey

This is an HTML attribute to set on the component if needed. In this case, the component will automatically create a sonic-toast-message-subscriber component. The data found in props[messageKey] will be treated as the data expected by the sonic-toast-message-subscriber. So, by respecting the format of data supported by this component, you will be able to show multiple toasts in the result of a request.

Example of JSON with messageKey=messages:

{
  "messages": [{
    "content": "The product has been added to your cart", 
    "status": "success", 
    "type": "public"
  }]
}

library

This is an HTML attribute to set on the component if needed. You can set a URL pointing to a JSON describing a library with the same structure as the library in the SDUIDescriptor. The component will simply override its library with the given one.

Playground

Here is a little playground to let you test some simple tricks.

<sonic-textarea 
    rows="10" 
    onChange="SonicPublisherManager.get('sdui-playground').set(JSON.parse(this.value))" 
    value='{"nodes":[{"tagName":"div", "innerHTML":"test"}]}'
    >
    </sonic-textarea>
    <sonic-sdui dataProvider="sdui-playground" props='{"nodes":[{"tagName":"div", "innerHTML":"test"}]}'>
    </sonic-sdui>