Default rendering

I have some questions regarding the React version of JsonForms.

The first question is about explicitly requesting a renderer within the UI schema. If for example I had a form with two string properties in the payload, and I wanted to explicitly use a specific renderer for the second string field, what options exist in the current UISchema to basically say “Use X renderer”. Do I need to set a specific property in the “Schema” called “useRenderer” with a value of the renderer I want to use and then write a tester that looks for this property and value? Is there a better way to handle this type of thing?

I also have a question about how to pass “props” or specific data down to a control renderer. Lets say for example I have a bunch of data I want to pass down into a control renderer from the schema, where is the proper place to put this data within the schema? Is it correct to put it inside the “options” object in the uiSchema or directly inside the “Schema” for the field itself as we see done with the “price” property on the example here: Custom Renderers | JSON Forms. It seems a little counter intuitive to pass props that are directly coupled to a renderer in through the Schema which can be applied to different renderers.

It looks like Jsonforms calculates its own UISchema based off the form schema automatically when none is passed, can I get a little more information on how exactly this works? Does it just shallow loop through the object and pass each property to a renderer? Are type: “objects” with nested properties passed to their own “Layout” renderer which knows how to call other renderers? It is a little unclear exactly how the Schema is being sliced up and passed to renderers, when I look at the example here: Generate UI schema | JSON Forms it appears the object personalData is passed to its own “object” renderer which knows how to call other renderers for each property? Is this a correct assumption?

My last question is when looking at the first example on this page: https://jsonforms.io/examples/layouts why is the “personalData.height” not in the data but “personalData.age” is?

[original thread by Chad Johnson]

The first question is about explicitly requesting a renderer within the UI schema. If for example I had a form with two string properties in the payload, and I wanted to explicitly use a specific renderer for the second string field, what options exist in the current UISchema to basically say “Use X renderer”. Do I need to set a specific property in the “Schema” called “useRenderer” with a value of the renderer I want to use and then write a tester that looks for this property and value? Is there a better way to handle this type of thing?

All testers receive the uischema element and resolved schema element to determine their priority. The testers for our basic renderers mostly look at the schema.type as well as schema.format or uischema.options.format. Of course you could just add arbitrary properties, like the suggested useRenderer: , and have a tester check that property. However typically this is not necessary as custom renderers usually cover a specific niche which can be described in a nicer way in the schema or ui schema, e.g. format: ‘star’ for integer controls. If you have a specific use case in mind, please let me know.

I also have a question about how to pass “props” or specific data down to a control renderer. Lets say for example I have a bunch of data I want to pass down into a control renderer from the schema, where is the proper place to put this data within the schema? Is it correct to put it inside the “options” object in the uiSchema or directly inside the “Schema” for the field itself as we see done with the “price” property on the example here: Custom Renderers | JSON Forms. It seems a little counter intuitive to pass props that are directly coupled to a renderer in through the Schema which can be applied to different renderers.

Using our default binding utilities the resolved schema and ui schema element will be handed over as props to the renderer. Therefore you can easily access anything in them. However you also always have full access to the JSON Forms context, so for example you can also inspect the whole schema. Also you can also always use your own React contexts and inject arbitrary data this way.

Typically it makes sense to configure your controls via the schema and ui schema as you mentioned. When to decide where to place each property I would like to suggest to think about its usage.

  • Is this an attribute describing the data itself which could be useful also outside of JSON Forms → Place it in the schema.

  • Is it a purely visual indicator, for example to render in italics → Place it in the ui schema

It looks like Jsonforms calculates its own UISchema based off the form schema automatically when none is passed, can I get a little more information on how exactly this works? Does it just shallow loop through the object and pass each property to a renderer? Are type: “objects” with nested properties passed to their own “Layout” renderer which knows how to call other renderers? It is a little unclear exactly how the Schema is being sliced up and passed to renderers, when I look at the example here: Generate UI schema | JSON Forms it appears the object personalData is passed to its own “object” renderer which knows how to call other renderers for each property? Is this a correct assumption?

The ui schema generator is very simple and only about 100 lines of sparse code. You can find it here. All it does is to traverse the root object and create a vertical layout with a control for each property. Nested objects are handled via the respective renderer. We have a MaterialObjectRenderer which itself will just call the same generator, producing a Group (i.e. a VerticalLayout with a label) with a control for all its properties and then just dispatches further to JSON Forms.

My last question is when looking at the first example on this page: https://jsonforms.io/examples/layouts why is the “personalData.height” not in the data but “personalData.age” is?

There is no specific reason. I agree, it would be nicer when the data would only contain what is actually shown in the example. I think we’re reusing the same initial data for a lot of examples which is why it’s contained there.

[Chad Johnson]

Wow thanks for the great responses that answered a ton of my questions. For a little more context on my first question we have been doing some POC’s to see if this tool can work for a project we are trying to build. What we are looking at building is a drag and drop style form builder, which can be used to create forms which are then rendered in another location. We were looking at the form builder outputting the JSON schema required for these fields. In the scenario we are thinking the user would want a specific “Control” to be rendered as opposed to the tool finding the best suited one for the payload.

Hi @chad-johnson(chad-johnson), you might want to look into the jsonforms-editor which is a visual editor for ui schemas. It’s a drag-and-drop editor for exisiting JSON Schemas (which can still be edited textually). It can be extended without much effort to also allow visual editing of JSON Schemas.

The editor could also just offer format options in the properties view to allow customizing the used renderers, e.g. a toggle checkbox for boolean controls which adds toggle: true into the ui schema. The jsonforms-editor is highly customizable so you can add whatever option your custom renderers support in there too.

You can find the deployed default version here and the repository here.

[Chad Johnson]

What is the best way to register the same renderer with multiple testers, should I just add multiple entries in the renderer registry array? (Note I want each tester to come back with a different number)

What is the best way to check against the uiSchema in a tester, is there a tester that acts the same asschemaMatches but instead for uischema like? uischemaMatches?

Also what is the best way to type cast the UISchemaElement in a tester function? I have done the following to try to add a renderer property into the uischema which gives me a back door to specifically render one renderer over another when they both have the same payload schema. Perhaps we can make use of more generics in some of the types and function type expressions to avoid stuff like below?

This just feels weird to me.

    rankWith(
        1000,
        (uischema) => {
            const uiSchemaWithRenderer = uischema as UISchemaWithRenderer;
            return Object.hasOwnProperty.call(uiSchemaWithRenderer, 'renderer') && uiSchemaWithRenderer.renderer === textInputKey
        }
    )

Thanks for the help in advance, I will probably submit some ideas for the documentation to be updated with a few of the parts that I found confusing.

What is the best way to register the same renderer with multiple testers, should I just add multiple entries in the renderer registry array? (Note I want each tester to come back with a different number)

Testers can be arbitrarily complex, so there is no need to add the same renderer multiple times. Note that all the utility functions we provide like rankWith, schemaMatches, isStringControl etc. are just helpers and don’t need to be used. In the end a tester is just a function (uischema, schema) => number. The utility functions just cover most of the use cases and are usually more concise and better readable than writing the tester manually. In your case it might be best to just write it manually. Of course you can reuse the existing utilities too.

What is the best way to check against the uiSchema in a tester, is there a tester that acts the same asschemaMatches but instead for uischema like? uischemaMatches?

UI schema elements are almost always just checked against their type, scope or options, so we provide helpers for that, e.g. uiTypeIs, scopeEndsWith, scopeEndIs, optionIs.

So for example the tester which checks whether a control points to an enum and has the option format: ‘radio’ set in the uischema can be tested like this:

rankWith(
  XX,
  and(isEnumControl, optionIs('format', 'radio'))
);

isEnumControl is just another tester which looks like this

and(
  uiTypeIs('Control'),
  or(
    schemaMatches(schema => schema.hasOwnProperty('enum')),
    schemaMatches(schema => schema.hasOwnProperty('const'))
  )

Also what is the best way to type cast the UISchemaElement in a tester function? I have done the following to try to add a renderer property into the uischema which gives me a back door to specifically render one renderer over another when they both have the same payload schema. Perhaps we can make use of more generics in some of the types and function type expressions to avoid stuff like below?

Generics might be interesting here. However I’m not fully convinced yet, any ui schema automatically generated by JSON Forms (which can easily happen, even when handing in own uischemas) will not automatically adhere to the generic type and the developer might try to access a (nested) property which is not there, i.e. the type contract is broken.

You might be interested in moving the renderer property into the options object instead. Then you could use our utilities to check for it and you don’t need to use type casting.