How to style a specefic deep neasted element

Hello,
i have a complex structure with many levels. It looks fine so far, but now i want to change some options like “elementLabelProp”: “type” for a deep array (“styleRule”). Also this is only the beginning, it will get more deeper levels.

  • Is it possible to work with deep structures?
  • can i export the generated uiSchema.json or is there a ui generator for the data-schema?
  • can i set the options of a specific element?

schema
{
  "definitions": {
    "styleRule": {
      "type": "array",
      "items": {
        "anyOf": [
          {
            "type": "object",
            "title": "default",
            "properties": {
              "type": {
                "type": "string",
                "enum": ["default"],
                "default": "default"
              },
              "output": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "title": "min",
            "properties": {
              "type": {
                "type": "string",
                "enum": ["default"],
                "default": "default"
              },
              "output": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "title": "category",
            "properties": {
              "type": {
                "type": "string",
                "enum": ["default"],
                "default": "default"
              },
              "output": {
                "type": "string"
              }
            }
          }
        ]
      }
    },
    "layer": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "enum": ["fill", "line"]
        },
        "styleRules": {
          "type": "object",
          "properties": {
            "color": {
              "$ref": "#/definitions/styleRule"
            }
          }
        }
      }
    },
    "atlas": {
      "source": {
        "type": "object",
        "title": "Source",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["vector", "geojson"],
            "default": "vector"
          },
          "provider": {
            "type": "string",
            "enum": ["tileserver", "publicTileserver"],
            "default": "tileserver"
          },
          "providerId": {
            "type": "string"
          },
          "maxzoom": {
            "type": "number",
            "default": 18
          },
          "minzoom": {
            "type": "number",
            "default": 5
          }
        },
        "required": ["label"]
      }
    },
    "sectionObj": {
      "type": "object",
      "title": "New Section",
      "properties": {
        "id": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "parentId": {
          "type": "string"
        },
        "childrenType": {
          "type": "string",
          "enum": ["switch", "radio"]
        },
        "activityControlType": {
          "type": "string",
          "enum": ["switch", "icon"]
        }
      },
      "required": ["label"]
    }
  },
  "type": "object",
  "properties": {
    "mainType": {
      "type": "string",
      "enum": ["layerPackage"]
    },
    "section": {
      "oneOf": [
        {
          "type": "string",
          "title": "parentId",
          "minLength": 1
        },
        {
          "type": "null",
          "title": "unset",
          "minLength": 1
        },
        {
          "$ref": "#/definitions/sectionObj"
        }
      ]
    },
    "source": {
      "$ref": "#/definitions/atlas/source"
    },
    "layers": {
      "type": "array",
      "items": { "$ref": "#/definitions/layer" }
    }
  }
}
ui-schema
{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "label": "MainType",
      "scope": "#/properties/mainType"
    },
    {
      "type": "Group",
      "label": "Section",
      "elements": [
        {
          "type": "Control",
          "label": "MainSection",
          "scope": "#/properties/section"
        }
      ]
    },

    {
      "type": "Control",
      "label": "Main Source",
      "scope": "#/properties/source"
    },

    {
      "type": "Control",
      "label": "layers",
      "scope": "#/properties/layers",
      "options": {
        "showSortButtons": true
      }
    }
  ]
}

Hi @stephan1,

There are two built-in mechanisms to handle “deep” Ui Schemas:

  • You can directly specify it by providing nested ui schemas via options.detail. See here for the documentation. This will result in one very large UI Schema, or
  • You can use the uischemas registry which is handed over to JSON Forms similar to the renderers registry. It contains (tester, uischema) tuples. This allows to only hand over the UI Schema for the nested level you want to customize. The uischemas registry is queried whenever a detail shall be rendered, e.g. when rendering objects or arrays.

Alternatively you can always express all customizations via custom renderers in case you don’t want to maintain UI Schemas.

Hey @sdirix
thanks for your solutions.

is there a example for uischemas registry?

i found this

registerUISchema example
import { Actions, NOT_APPLICABLE } from '@jsonforms/core';

store.dispatch(
  Actions.registerUISchema(
    (jsonSchema, schemaPath) => {
      return schemaPath === '#/properties/firstarray' ? 2 : NOT_APPLICABLE;
    },
    {
      type: 'VerticalLayout',
      elements: [
        {
          type: 'Control',
          scope: '#/properties/firstName',
        },
        {
          type: 'Control',
          scope: '#/properties/lastName',
        },
      ],
    }
  )
);

but dont know how to integrate it into the jsonforms-react-seed demo

jsonformsDemo.tsx
import {
  materialCells,
  materialRenderers,
} from '@jsonforms/material-renderers';
import { JsonForms } from '@jsonforms/react';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { FC, useMemo, useState } from 'react';
import ratingControlTester from '../ratingControlTester';
import schema from '../schema.json';
import uischema from '../uischema.json';
import RatingControl from './RatingControl';

const classes = {
  container: {
    padding: '1em',
    width: '100%',
  },
  title: {
    textAlign: 'center',
    padding: '0.25em',
  },
  dataContent: {
    display: 'flex',
    justifyContent: 'center',
    borderRadius: '0.25em',
    backgroundColor: '#cecece',
    marginBottom: '1rem',
  },
  resetButton: {
    margin: 'auto !important',
    display: 'block !important',
  },
  demoform: {
    margin: 'auto',
    padding: '1rem',
  },
};

const initialData = {
  name: 'Send email to Adrian',
  description: 'Confirm if you have passed the subject\nHereby ...',
  done: true,
  recurrence: 'Daily',
  rating: 3,
  section: {
    id: 'test',
    label: 'test labek',
  },
};

const renderers = [
  ...materialRenderers,
  //register custom renderers
  { tester: ratingControlTester, renderer: RatingControl },
];

export const JsonFormsDemo: FC = () => {
  const [data, setData] = useState<object>(initialData);
  const stringifiedData = useMemo(() => JSON.stringify(data, null, 2), [data]);

  const clearData = () => {
    setData({});
  };
  return (
    <Grid
      container
      justifyContent={'center'}
      spacing={1}
      style={classes.container}>
      <Grid item sm={6}>
        <Typography variant={'h4'}>Bound data</Typography>
        <div style={classes.dataContent}>
          <pre id="boundData">{stringifiedData}</pre>
        </div>
        <Button
          style={classes.resetButton}
          onClick={clearData}
          color="primary"
          variant="contained"
          data-testid="clear-data">
          Clear data
        </Button>
      </Grid>
      <Grid item sm={6}>
        <Typography variant={'h4'}>Rendered form</Typography>
        <div style={classes.demoform}>
          <JsonForms
            schema={schema}
            uischema={uischema}
            data={data}
            renderers={renderers}
            cells={materialCells}
            onChange={({ data }) => setData(data)}
          />
        </div>
      </Grid>
    </Grid>
  );
};

The example you posted is for a very old version of JSON Forms and is no longer applicable.

You just need to hand over a uischemas prop to JSON Forms. It is structured exactly like the renderers prop. See here, here and here for the types.

I don’t think we have an example on the website, however there is one in our internal dev app, see here.

ok i create a list of regietries and add them to the element

uiSchemas
const uiSchemas: Array<JsonFormsUISchemaRegistryEntry> = [
  {
    tester: (schema, schemaPath, path) => {
      console.log(schema, schemaPath, path);
      return schemaPath === '#/properties/layers' ? 1 : -1;
    },
    uischema: {
      type: 'array',
      options: { showSortButtons: true },
    },
  },
];
JsonForms
<JsonForms
            schema={schema}
            uischema={uischema}
            uischemas={uiSchemas}
            data={data}
            renderers={renderers}
            cells={materialCells}
            onChange={({ data }) => setData(data)}
          />

it shows me some schemas but there are no array entries.
If i run it like that i will get ‘No applicable renderer found.’ inside the array entry.
Also what does the number means, which need to be returnd inside tester?

Hi @stephan1,

Your registered UI Schema needs to be valid. We don’t define a uischema with type: 'array', therefore there is no off-the-shelf renderer which reacts to that and you get the “no applicable renderer” found message.

The uischemas registry is called for all objects and arrays. So if you want to customize the showSortButtons of an array within your schema, then you need test for and return the ui schema of the parent of the array.

i checked your example here

but there is no elements property of JsonFormsUISchemaRegistryEntry

and also here the question, where to put the options: { showSortButtons: true },

there is no exporter which returns the generated uiSchema of a given jsonSchema, right?

Hi @stephan1,

Layout extends UISchemaElement and thereby adds the elements property, see here.

You can invoke the UI Schema generator manually via Generate.uiSchema. Generate can be imported from @jsonforms/core.

As part of the control referring to the array, e.g.

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "Control",
      "label": "First Name",
      "scope": "#/properties/firstName"
    },
    {
      "type": "Control",
      "label": "Last Name",
      "scope": "#/properties/lastName"
    },
    {
      "type": "Control",
      "label": "Age",
      "scope": "#/properties/age"
    },
    {
      "type": "Control",
      "label": "Children",
      "scope": "#/properties/children",
      "options": { "showSortButtons": true }
    }
  ]
}

Fyi, if you want to use showSortButtons on ALL arrays, then you can simply hand this option over in the config prop. It will then be respected by all array renderers.

nice :slight_smile: i will test it. Is there a documentation of the config?

it it also possible to have null or better undefined as a type?
i have a property which can be a object or can be leaved unset. but im not shure how to leave it unset.