Guidewire Jutro Codeless Components

Oct 26, 2020 | Article

The what and why of Guidewire Jutro codeless components.

UI metadata rendering is a very powerful capability that comes with Jutro, but in its basic variant, one are of difficulty is the ability to reuse more complex fragments across metadata files. Obviously it allows us to specify how a page should look, what most basic building blocks (components) should be placed there, but it’s still rather hard to share some bigger fragments (e.g. fragments of forms, like a set of address fields) between many pages based on metadata. Some trivial methods like copy-pasting, or $ref statements can be used to address this, but these are rather cumbersome, prone to errors, still resulting in heavy repetitions and not allowing parametrization or propagating further updates of such shared fragments in an easy way. Alternatively, one could also define a custom component as a ‘classic’ React component, directly coded in JS(X) (TS(X)), but such a solution lacks all the benefits of using metadata for defining your UIs. The codeless components feature aims to address all these problems.

Basics: defining a reusable component’s template and instantiating in a metadata file with Guidewire Jutro

The most basic thing that can be achieved with codeless components is just defining a component’s template in a metadata JSON file, which can specify things typical to React components (content, propTypes, default props) in a codeless way and using instances of a component defined in this way across metadata files.


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent", // react's display name
        "name": "myBasicCodelessComponent", // key for references in metadata files
        "type": "component", // type === 'component'
        "metadataType": "container",
        "propTypes": { // propTypes definitions, like in typical react components
            "id": "string",
            "header": "intlMessageShape",
            "description": "basicCodelessComponentCustomShape",
            "actionLabel": "string",
            "actionIcon": "string",
            "onActionClick": { "propType": "func", "required": true }
        },
        "defaultProps": { // default props, like in typical react components
            "header": "title of record",
            "actionLabel": "Click"
        },
        "content": [  // some content to display, based on more typical simple Jutro components (building blocks)
            {
                "id": "codeless_component",
                "type": "element",
                "component": "div",
                "contentLayout": {
                    "component": "grid"
                },
                "content": [
                    {
                        "id": "codelessCardTitle",
                        "type": "element",
                        "component": "h2",
                        "content": "Title"
                    },
                    {
                        "id": "codelessCardField",
                        "type": "field",
                        "component": "Input",
                        "componentProps": {
                            "path": "helloField",
                            "label": "Card Field:"
                        }
                    },
                    {
                        "id": "codelessCardAction",
                        "type": "action",
                        "component": "Button",
                        "content": "Action Label",
                        "componentProps": {
                            "type": "secondary",
                        },
                        "selfLayout": {
                            "component": "griditem",
                            "componentProps": {
                                "align": "end"
                            }
                        }
                    }
                ]
            }
        ]
    }
}

There are two ways to use such definitions: register them once globally, or pass them manually in a single component.

Registering definitions from metadata file

Definitions in metadata file can be registered globally. They will be recognized across metadata files by the Jutro metadata rendering engine:


// start.js
import { start } from '@jutro/app';
import { registerCodelessComponents } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
 
registerCodelessComponents(codelessComponentDefinitionFile);
 
start(Jutro, {
    ...
});

The definitions can also be used locally for a single component. They have to be explicitly passed to the rendering engine:


// CodelessPage.js
import React from 'react';
import { MetadataForm, extractCodelessComponentMap } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
 
const componentDefinition = codelessComponentDefinitionFile['codeless.component.basic-component'];
const codelessComponentMap = extractCodelessComponentMap(componentDefinition);
 
export const CodelessPage = () => {
  return (
     
  );
};

Both whole metadata file and a single definition can be used for registering.

Instantiating the components in a metadata file

After the definitions are registered they can be instantiated within the actual page’s metadata file:


// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.example.basic-component": {
        "id": "basicCodelessComponentExample",
        "type": "page",
        "contentLayout": {
            "component": "grid",
            "componentProps": {
                "gap": "small"
            }
        },
        "content": [
            {
                "id": "myBasicCodelessComponentInstance",
                "type": "container",
                "component": "myBasicCodelessComponent", // instance of our codeless component
                "componentProps": {
                    "header": {
                        "id": "myBasicCodelessComponent.header",
                        "defaultMessage": "Basic Codeless Component Example"
                    },
                    "description": "Example Codeless Component",
                    "actionLabel": "Click Me",
                    "onActionClick": "clickForm",
                    "path": "codelessComponentObject"
                }
            }
        ]
    }
}



// CodelessPage.js
import React from 'react';
import { MetadataForm, extractCodelessComponentMap } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
import codelessPageDefinitionFile from './codelessPageDefinition.metadata.json5';
 
const componentDefinition = codelessComponentDefinitionFile['codeless.component.basic-component'];
const codelessComponentMap = extractCodelessComponentMap(componentDefinition);
const uiProps = codelessPageDefinitionFile['codeless.component.example.basic-component'];
 
export const CodelessPage = () => {
  return (
    
  );
};

Or even instantiated a few times, with different props passed down to each instance:


// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.example.basic-component": {
        "id": "basicCodelessComponentExample",
        "type": "page",
        "contentLayout": {
            "component": "grid",
            "componentProps": {
                "gap": "small"
            }
        },
        "content": [
            {
                "id": "myBasicCodelessComponentInstance",
                "type": "container",
                "component": "myBasicCodelessComponent", // instance of our codeless component
                "componentProps": {
                    "header": {
                        "id": "myBasicCodelessComponent.header",
                        "defaultMessage": "Basic Codeless Component Example"
                    },
                    "description": "Example Codeless Component",
                    "actionLabel": "Click Me",
                    "onActionClick": "clickForm",
                    "path": "codelessComponentObject"
                }
            },
            {
                "id": "myAnotherBasicCodelessComponentInstance",
                "type": "container",
                "component": "myBasicCodelessComponent", // another instance of our codeless component
                "componentProps": {
                    "header": {
                        "id": "myAnotherBasicCodelessComponent.header",
                        "defaultMessage": "My Second Usage of Codeless Component within a single page"
                    },
                }
            }
        ]
    }
}

Defining prop types for Guidewire Jutro codeless components.

There’re a few options available here:

  • Most basic types that come from PropTypes library, so string, func, etc.
  • Custom Jutro prop-types shapes (exposed from @jutro/prop-types module)
  • One of the above with modifiers (e.g. making them required)
  • Completely custom shapes

PropTypes/ Guidewire Jutro shapes

The first two options are just simple string names used in the component definition’s propTypes section (camelCased):


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent",
        "name": "myBasicCodelessComponent",
        "type": "component",
        "metadataType": "container",
        "propTypes": { // propTypes definitions, like in typical react components
            "id": "string", // standard from prop-types lib
            "header": "intlMessageShape", // custom Jutro shape
            ...
        },
        ...
    }
}

With modifiers

An object-based definition may need to be used in such a case


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent",
        "name": "myBasicCodelessComponent",
        "type": "component",
        "metadataType": "container",
        "propTypes": { // propTypes definitions, like in typical react components
            "id": { "propType": "string", "required": true }, // standard from prop-types lib, but marked as required
            ...
        },
        ...
    }
}

Completely custom shape

A completely custom shape may also be used, but it requires a runtime resolution with a piece of JS code:


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent",
        "name": "myBasicCodelessComponent",
        "type": "component",
        "metadataType": "container",
        "propTypes": { // propTypes definitions, like in typical react components
            "someProp": "basicCodelessComponentCustomShape", // completely custom
            ...
        },
        ...
    }
}



// CodelessPage.js
import React from 'react';
import { MetadataForm, extractCodelessComponentMap } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
 
const componentDefinition = codelessComponentDefinitionFile['codeless.component.basic-component'];
const propTypesMap = {
    basicCodelessComponentCustomShape: PropTypes.string, // could be anything, e.g. some shape
};
const codelessComponentMap = extractCodelessComponentMap(componentDefinition, propTypesMap); // custom prop types map - passed as on optional second arg
 
export const CodelessPage = () => {
  return (
     
  );
};

Parameterizing (templating) concrete instances

Like it’s been mentioned at the very beginning, codeless components are supposed to be configurable. This is be achieved by allowing to pass appropriate runtime props (corresponding to variables used in templates) to particular components instances. The general convention is to provide dynamic values into components definitions (templates) using props names wrapped in a pair of { }. However, there are two kinds of variables that can be used:

Pre-defined variables

Each codeless component allows using some pre-defined variables for special purposes. These are:

  • {_id_} – re-using instance’s runtime id
  • {_children_} – injecting children (passed to a component’s instance as “content”: [ … ] – more details on this one below)
  • {_data_} – injecting the whole component’s data object that’s passed down to an instance of component, which is defined with “metadataType”: “container” and has got some data passed down to it by <MetadataForm> – more on this below
  • {_value_} – same thing as above one, except for components defined with “metadataType”: “field”

// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent",
        "name": "myBasicCodelessComponent",
        "type": "component",
        "metadataType": "container",       
        "content": [
            {
                "id": "codeless_component",
                "type": "element",
                "component": "div",
                "contentLayout": {
                    "component": "grid"
                },
                "content": [
                    {
                        "id": "codelessCardTitle",
                        "type": "element",
                        "component": "h2",
                        "content": "{_id_}" // runtime id attached to a particular instance will be inserted here
                    }
                ]
            }
        ]
    }
}
 
// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.example.basic-component": {
        "id": "basicCodelessComponentExample",
        "type": "page",
        "contentLayout": {
            "component": "grid",
            "componentProps": {
                "gap": "small"
            }
        },
        "content": [
            {
                "id": "myBasicCodelessComponentInstance", // this instance will render 'myBasicCodelessComponentInstance' as H2#codelessCardTitle
                "type": "container",
                "component": "myBasicCodelessComponent",
            },
            {
                "id": "myAnotherBasicCodelessComponentInstance", // this instance will render 'myAnotherBasicCodelessComponentInstance' as H2#codelessCardTitle
                "type": "container",
                "component": "myBasicCodelessComponent",
            }
        ]
    }
}

Custom variables

Completely custom variables can also be used for templating codeless components at runtime. These basically resolve to the same value as is passed to a particular instance’s corresponding prop at runtime. For now only strings, intlMessageShapes, and functions can be templated in this way, but we’re aiming to extend this to other types of values as well.


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.basic-component": {
        "displayName": "BasicCodelessComponent",
        "name": "myBasicCodelessComponent",
        "type": "component",
        "metadataType": "container",
        "propTypes": { // these become super useful in such cases
            "id": "string",
            "header": "intlMessageShape",
            "actionLabel": "string",
            "actionIcon": "string",
            "onActionClick": { "propType": "func", "required": true }
        },
        "defaultProps": { // these become super useful in such cases
            "header": "title of record",
            "actionLabel": "Click"
        },
        "content": [
            {
                "id": "codeless_component",
                "type": "element",
                "component": "div",
                "contentLayout": {
                    "component": "grid"
                },
                "content": [
                    {
                        "id": "codelessCardTitle",
                        "type": "element",
                        "component": "h2",
                        "content": "{header}" // runtime value attached to a particular instance's 'header' prop will be inserted here
                    },
                    {
                        "id": "codelessComponentAction",
                        "type": "action",
                        "component": "Button",
                        "content": "{actionLabel}", // runtime value attached to a particular instance's 'actionLabel' prop will be inserted here, or falls back to the default 'Click'
                        "componentProps": {
                            "type": "secondary",
                            "icon": "{actionIcon}",
                            "onClick": "{onActionClick}" // supposed to be resolved to some function at runtime
                        }
                    }
                ]
            }
        ]
    }
}
 
// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "3.1.1-next.0",
    "codeless.component.example.basic-component": {
        "id": "basicCodelessComponentExample",
        "type": "page",
        "contentLayout": {
            "component": "grid",
            "componentProps": {
                "gap": "small"
            }
        },
        "content": [
            {
                "id": "myBasicCodelessComponentInstance",
                "type": "container",
                "component": "myBasicCodelessComponent",
                "componentProps": {
                    "header": { // will be translated and inserted as this instance's H2#codelessCardTitle at runtime, resulting in 'Basic Codeless Component Example' getting displayed
                        "id": "myBasicCodelessComponent.header",
                        "defaultMessage": "Basic Codeless Component Example"
                    },
                    "onActionClick": "clickForm", // watch out - needs to be bound to a concrete implemention in JS
                }
            },
            {
                "id": "myAnotherBasicCodelessComponentInstance",
                "type": "container",
                "component": "myBasicCodelessComponent",
                "componentProps": {
                    "header": { // will be translated and inserted as this instance's H2#codelessCardTitle at runtime, resulting in 'My Another Basic Codeless Component Example' getting displayed
                        "id": "myAnotherBasicCodelessComponent.header",
                        "defaultMessage": "My Another Basic Codeless Component Example"
                    },
                    "actionLabel": "Click Me", // overrides default 'actionLabel' from the definition
                    "onActionClick": "clickForm", // watch out - needs to be bound to a concrete implemention in JS
                }
            }
        ]
    }
}



// CodelessPage.js
import React from 'react';
import { MetadataForm, extractCodelessComponentMap } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
import codelessPageDefinitionFile from './codelessPageDefinition.metadata.json5';
 
const componentDefinition = codelessComponentDefinitionFile['codeless.component.basic-component'];
const codelessComponentMap = extractCodelessComponentMap(componentDefinition);
const uiProps = codelessPageDefinitionFile['codeless.component.example.basic-component'];
 
export const CodelessPage = () => {
  return (
     alert('Clicked!'), // at runtime will be attached to #codelessComponentAction button for both instances of myBasicCodelessComponent in codelessPageDefinition.metadata.json5
       }}
       ...
    /> 
  );
};

Data handling

Data handling can be achieved for codeless components with metadataType attribute defined either as ‘container’ or ‘field’. This results in appropriate propTypes being automatically attached to such components at runtime (data + onDataChange for ‘container’ components and value + onValueChange for ‘field’ components’). Such components’ instances should also have a ‘path‘ prop and a type attribute corresponding to the component’s definition specified in order for the metadata renderer (renderContentFromMetadata() or MetadataForm) to inject the value.


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N",
    "codeless.component.fields": {
        "displayName": "Fields",
        "name": "myFields",
        "type": "component",
        "metadataType": "field", // needs to be field so that path -> value mapping is handled correctly. can also be 'container' sometimes
        "content": [
            {
                "id": "panel",
                "type": "container",
                "component": "panel",
                "componentProps": {
                    "title": "My Fields"
                }
                "content": [
                    {
                        "id": "yearBuilt",
                        "type": "field",
                        "datatype": "date",
                        "componentProps": {
                            "path": "yearBuilt", // path relative to the one that's specified for component's instance
                            "label": {
                                "id": "jutro-app.codeless.yearBuilt",
                                "defaultMessage": "Year built"
                            },
                            "placeholder": {
                                "id": "jutro-app.codeless.yearBuilt.pleaseSelect",
                                "defaultMessage": "Please select"
                            },
                            "required": true,
                            "minDate": "2019-01-01",
                            "maxDate": "2029-01-01",
                            "defaultValue": {
                                "year": "2020",
                                "month": "0",
                                "day": "1"
                            }
                        }
                    },
                    {
                        "id": "dataPreview",
                        "type": "container",
                        "component": "JsonContainer", // custom one - not relevant for this example
                        "componentProps": {
                            "title": "Component's value object preview:",
                            "json": "{_value_}" // references the whole data object passed to the instance. type of the component is 'field', so {_value_} needs to be used as a placeholder. would be {_data_} for components with metadataType === 'container'
                        }
                    }
                ]
            }
        ]
    }
}
 
// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "3.1.1-next.0",
    "codeless.component.example.fields-page": {
        "id": "fieldsWithInternalStateExample",
        "type": "page",
        "content": [
            {
                "id": "myFieldsInstance",
                "type": "field", // needs to be specified + match the component's metadataType to get appropriate data handling props attached
                "component": "myFields",
                "componentProps": {
                    "panelTitle": {
                        "id": "codelessFields.panelTitle",
                        "defaultMessage": "My Fields"
                    },
                    "path": "myFields" // needs to be specified
                }
            }
        ]
    }
}



// CodelessPage.js
import React, { useState } from 'react';
import { get, set } from 'lodash';
import { MetadataForm, extractCodelessComponentMap } from '@jutro/uiconfig';
import codelessComponentDefinitionFile from './codelessComponent.metadata.json5';
import codelessPageDefinitionFile from './codelessPageDefinition.metadata.json5';
 
const componentDefinition = codelessComponentDefinitionFile['codeless.component.fields'];
const codelessComponentMap = extractCodelessComponentMap(componentDefinition);
const uiProps = codelessPageDefinitionFile['codeless.component.example.fields-page'];
 
export const CodelessPage = () => {
  // additions for handling the state
  const [formData, setFormData] = useState({});
 
  const readValue = (id, path) => {
      return get(formData, path);
  };
 
  const writeValue = (value, path) => {
      const newData = { ...formData };
      set(newData, path, value);
      setFormData(newData);
  };
 
  return (
     
  );
};

Injecting dynamic instance’s content

As it’s been mentioned earlier, codeless components may also accept some dynamic content (children) to display on a per-instance basis.


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.container-with-static-and-dynamic-content": {
        "displayName": "ContainerWithStaticAndDynamicContent",
        "name": "myContainerWithStaticAndDynamicContent",
        "type": "component",
        "metadataType": "container",
        "propTypes": {
            "id": "string",
            "title": "intlMessageShape"
        },
        "defaultProps": {
            "title": {
                "id": "codeless.components.container.with.static.and.dynamic.content.title",
                "defaultMessage": "Codeless Container With Both Static And Dynamic Content"
            }
        },
        "content": [
            {
                "id": "{_id_}",
                "type": "container",
                "component": "Panel",
                "componentProps": {
                    "title": "{title}"
                },
                "contentLayout": {
                    "component": "grid"
                },
                "content": [
                    {
                        "id": "MixedField",
                        "type": "field",
                        "component": "Input",
                        "componentProps": {
                            "path": "componentLevelField",
                            "label": "Component Level Field:"
                        }
                    },
                    "{_children_}", // each instance's runtime content will be injected here
                    {
                        "id": "dataPreview",
                        "type": "container",
                        "component": "JsonContainer",
                        "componentProps": {
                            "className": "myFancyCustomClass",
                            "title": "Component's data object preview:",
                            "json": "{_data_}" // type of the component is container, so {_data_} needs to be used to reference the input data
                        }
                    }
                ]
            }
        ]
    }
}
 
// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example.container-with-static-and-dynamic-content": {
        "id": "containerWithStaticAndDynamicContentExample",
        "type": "page",
        "contentLayout": {
            "component": "grid",
            "componentProps": {
                "gap": "small"
            }
        },
        "content": [
            {
                "id": "myCodelessPanelWithBothComponentLevelContentAndInstanceLevelFieldInstance",
                "type": "container",
                "component": "myContainerWithStaticAndDynamicContent",
                "componentProps": {
                    "path": "dataContainer"
                },
                "content": [ // all that stuff will be mixed with a component's definition level content at runtime
                    {
                        "id": "roofType",
                        "type": "field",
                        "component": "imageradiobutton",
                        "componentProps": {
                            "path": "imageradiobutton",
                            "label": { "id": "example.page.imageradiobutton", "defaultMessage": "Instance Level Roof Type:" },
                            "availableValues": [
                                {"code": "composition", "name": "Composition Shingles"},
                                {"code": "wood", "name": "Wood Shingles"},
                                {"code": "tile", "name": "Tile Clay"},
                                {"code": "slateconcrete", "name": "Slate Concrete"},
                                {"code": "tileconcrete", "name": "Tile Concrete"},
                                {"code": "other", "name": "Other"}
                            ],
                            "imageBasePath": "./styles/images/imageRadioButton",
                            "defaultValue": "composition"
                        }
                    },
                    {
                        "id": "foundationType",
                        "type": "field",
                        "datatype": "select",
                        "componentProps": {
                            "path": "select",
                            "label": { "id": "example.page.select", "defaultMessage": "Instance Level Foundation Type:" },
                            "placeholder": "Please Select",
                            "availableValues": [
                                {"code": "slab", "name": "Slab"},
                                {"code": "notSlab", "name": "Not Slab"}
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

Customizing DOM content wrapping

Using custom wrapping node

By default, each instance of a codeless component will be wrapped with an extra root <div> node (that has got id, className etc. attached). However, in some cases, it might be needed to customize this, e.g. it’s possible to provide some custom tag (except ‘script’):


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example": {
        "displayName": "CustomWrappingNodeExample",
        "name": "customWrappingNode",
        "type": "component",                 
        "wrapperTag": "span", //  will be used as a wrapper node for all the instances of this component
        "metadataType": "container",
        "content": [
            {
                "id": "{_id_}",
                "type": "container",
                "component": "Panel",
                "componentProps": {
                    "title": "My Panel"
                },
                "contentLayout": {
                    "component": "grid"
                }
            }
        ]
    }
}

Avoiding the wrapping completely

In some cases, it’s also possible to completely avoid the wrapping. It’s possible when there’s only a single content child specified for a particular codeless component.

For instance:


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example": {
        "displayName": "CustomWrappingNodeExample",
        "name": "customWrappingNode",
        "type": "component",
        "wrapperTag": null, // no extra wrapping node at runtime for this component
        "metadataType": "container",
        "content": [
            {
                "id": "{_id_}",
                "type": "container",
                "component": "Panel",
                "componentProps": {
                    "title": "My Panel"
                },
                "contentLayout": {
                    "component": "grid"
                }
            }
        ]
    }
}

But in case of more than one content nodes at the root level, the wrapping still will be applied, using an extra <div> node (despite the “wrapperTag”: null setting):


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example.component": {
        "displayName": "CustomWrappingNodeExample",
        "name": "customWrappingNode",
        "type": "component",
        "wrapperTag": null, // no extra wrapping node at runtime for this component EXPECTED...      
        "metadataType": "container",
        "content": [ // ...BUT since there're multiple content children configured, it still falls back to wrapping with an extra 
node { "id": "description", "type": "element", "component": "p", "content": "Some interesting content" }, { "id": "{_id_}", "type": "container", "component": "Panel", "componentProps": { "title": "My Panel" }, "contentLayout": { "component": "grid" } } ] } }

Alternatively, the same works if there’re only dynamic children configured and just a single one injected at runtime (or still falls back to wrapping with <div> if there’re multiple runtime children provided to a particular instance):


// codelessComponent.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example.component": {
        "displayName": "CustomWrappingNodeExample",
        "name": "componentWithPossiblyNoDOMWrapping",
        "type": "component",
        "wrapperTag": null, // no extra wrapping node at runtime for this component is EXPECTED. if only single runtime child provided - otherwise still wraps with an div node
        "metadataType": "container",
        "content": ["{_children_}"] // dynamic content only
    }
}
 
// codelessPageDefinition.metadata.json5
{
    "$schema": "../../../../../../packages/jutro-uimetadata/common/json-schema/metadata.schema.json",
    "jutro": "N.N.N-next.0",
    "codeless.component.example.page.without-wrapping-node": {
        "id": "customRootNodesWrapping",
        "type": "page",
        "content": [
            {
                "id": "noWrappingNodeInstance",
                "type": "container",
                "component": "componentWithPossiblyNoDOMWrapping",
                "content": [ // single runtime child and definition comes with "wrapperTag": null, so no DOM wrapping at runtime for this instance
                    {
                        "id": "foundationType",
                        "type": "field",
                        "datatype": "select",
                        "componentProps": {
                            "path": "select",
                            "label": { "id": "example.page.select", "defaultMessage": "Instance Level Foundation Type:" },
                            "placeholder": "Please Select",
                            "availableValues": [
                                {"code": "slab", "name": "Slab"},
                                {"code": "notSlab", "name": "Not Slab"}
                            ]
                        }
                    }
                ]
            },
            {
                "id": "stillDivWrappedInstance",
                "type": "container",
                "component": "componentWithPossiblyNoDOMWrapping",
                "content": [ // multiple runtime children are provided for this instance so div wrapping still will be provided, despite the component's definition coming with "wrapperTag": null
                    {
                        "id": "roofType",
                        "type": "field",
                        "component": "imageradiobutton",
                        "componentProps": {
                            "path": "imageradiobutton",
                            "label": { "id": "example.page.imageradiobutton", "defaultMessage": "Instance Level Roof Type:" },
                            "availableValues": [
                                {"code": "composition", "name": "Composition Shingles"},
                                {"code": "wood", "name": "Wood Shingles"},
                                {"code": "tile", "name": "Tile Clay"},
                                {"code": "slateconcrete", "name": "Slate Concrete"},
                                {"code": "tileconcrete", "name": "Tile Concrete"},
                                {"code": "other", "name": "Other"}
                            ],
                            "imageBasePath": "./styles/images/imageRadioButton",
                            "defaultValue": "composition"
                        }
                    },
                    {
                        "id": "foundationType",
                        "type": "field",
                        "datatype": "select",
                        "componentProps": {
                            "path": "select",
                            "label": { "id": "example.page.select", "defaultMessage": "Instance Level Foundation Type:" },
                            "placeholder": "Please Select",
                            "availableValues": [
                                {"code": "slab", "name": "Slab"},
                                {"code": "notSlab", "name": "Not Slab"}
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

Guidewire Jutro related info

Article
Learn how the Jutro command-line interface (CLI) can be used during your Jutro digital app builds for testing and other scripting.
Cross-Platform Tool
Create beautiful and engaging digital apps that run on Guidewire - the world's most trusted P&C platform with Guidewire Jutro.
Use Case
Want to build beautiful and engaging digital experiences for Guidewire? This page has everything you need to get started.