How does the Vanilla ArrayControlRenderer work?

In the source code I found the ArrayControlRenderer.tsx

This control seems to be nearly to what I need. I want to show my Object Array as separate DIV tags and not in a table row, which currently happens when I provide an array of objects:

The ArrayControlRenderer seems not to be documented.

Can someone please give me a short instruction what this component is good for and how I can use it?

===
Ralph

In most renderer sets we support two different ways of visualizing arrays:

  • As a table
  • As (collapsible) elements vertically. We usually call this “array layout”. In the vanilla renderers it’s named “array control” which is not consistent to the other renderer sets.

By default it usually works like this:

  • If the array items object is a flat object consistent of simple attributes the array table renderer is used.
  • If the array items object is more complex and therefore not suited for a table, the array layout is used.

Sadly the React Vanilla renderers had a bug, so that the table was used too often. This was fixed here and is available with the latest release 3.0.0-beta.4 Edit: 3.0.0-beta.5

Sometimes users don’t want to use the table renderer at all or in certain cases or they want to manually specify a UI Schema for the array layout case. So the behavior can be customized via UI Schema options, see here. Alternatively the array layout renderer can be reregistered with a higher priority so it always (or in some special cases) wins over the array table renderer.

1 Like

Thanks for the good explanation. I will try it out now once again. The question to me is, what is a ‘more complex object’? I found in the sources that the tester ask for a isObjectArrayWithNesting. But I did not figure out what my data/schema should look like to fulfill this condition. Can you give a short example?

Maybe this text could also be good for the doc page: Layouts - JSON Forms ?

The question to me is, what is a ‘more complex object’?

An object which contains at least one object or array attribute as these are generally hard to visualize within a table.

@sdirix I tried setting the “details” options. But still only the table renderer is used. I didn’t manage to get my array rendered by the vanilla array renderer.

This is how my data looks like:

{
	"name": "Event-0",
	"rating": "3",
	"documentation": "",
	"conditions": [
		{
			"formalExpression": "...some expression...",
			"language": "java",
			"condition": "blue"
		},
		{
			"formalExpression": "...some expression...",
			"language": "java",
			"condition": "red"
		},
		{
			"formalExpression": "...some expression...",
			"language": "java",
			"condition": "green"
		}
	]
}

The schema JSON looks like this:

{
	"properties": {
		"name": {
			"type": "string"
		},
		"rating": {
			"type": "integer"
		},
		"documentation": {
			"type": "string"
		},
		"conditions": {
			"type": "array",
			"items": {
				"type": "object",
				"properties": {
					"formalExpression": {
						"type": "string"
					},
					"language": {
						"type": "string"
					},
					"condition": {
						"type": "string"
					}
				}
			}
		}
	}
}

And the UISchema - were I now set the “details” option looks like this:

{
	"type": "Categorization",
	"elements": [
		{
			"type": "Category",
			"label": "General",
			"elements": [
				{
					"type": "VerticalLayout",
					"elements": [
						{
							"type": "Control",
							"scope": "#/properties/name"
						},
						{
							"type": "Control",
							"scope": "#/properties/rating"
						},
						{
							"type": "Control",
							"scope": "#/properties/documentation",
							"label": "Documentation",
							"options": {
								"multi": true
							}
						}
					]
				}
			]
		},
		{
			"type": "Category",
			"label": "Event",
			"elements": [
				{
					"type": "VerticalLayout",
					"elements": [
						{
							"type": "Control",
							"scope": "#/properties/conditions",
							"label": "Conditions",
							"options": {
								"detail": "GENERATED"
							}
						}
					]
				}
			]
		}
	]
}

I am using 3.0.0-beta.4 but the outcome is still always a table layout

Would you expect in this scenario that the array renderer should be used?

@sdirix I did some more tests and removed the “Categorization”, as my thought was this could cause the issue. But even with a simple horizontal layout the array is always rendered as a table and not as a collection of DIV tags.

Hi @rsoika,

Sorry I gave out wrong information. The fix mentioned above was merged right after the 3.0.0-beta.4 release and is therefore not contained there. This is why you are not able to trigger the non-table renderer. The fix can now be consumed via 3.0.0-beta.5.

1 Like

Ah, that’s great news. I had already doubted myself and my JavaScript knowledge. I’ll try this out and then give you feedback.

1 Like

@sdirix With version beta.5 it’s now working. Thanks for your help!

The only thing I am missing are the delete-button. Do you plan to add this feature to the array Renderer?

Another point is the add-button. I think it would be better to place the botton below the legend. In this way you have more flexibility to style the button layout and it’s position? What do you think about this?

The only thing I am missing are the delete-button. Do you plan to add this feature to the array Renderer?

In general we would definitely like to have a remove button here. Feel free to open an issue and/or contributing it to the project :wink:

Another point is the add-button. I think it would be better to place the botton below the legend. In this way you have more flexibility to style the button layout and it’s position? What do you think about this?

Having it as part of the legend is reasonable I think. Using CSS you could for example move it to the right. Of course I also agree with you that there are other reasonable placements, for example within the field set or below the whole control. Using the most flexible approach probably makes sense.

hi @sdirix,
I am trying to implement a variant of the Vanilla ArrayRenderer now for the OpenBPMN project and maybe the result can be such a contribution.

But for the moment I have some strange behavior in setting up a new version of the ArrayRenderer.
As discussed before my goal is to pack all components (renderer, component and tester) in one local module file. You can see the result here:

With the help from Tobias we fixed already some minor issues with the Typescript coding rules and now everything can be build fine (Eclipse GLSP is using more strict rules as in the JSONForms project which was an issue by overtaking the code).

But now during runtime, when the form is rendered I get the following runtime errors:

Uncaught TypeError: getStyleAsClassName is not a function
    at BPMNArrayControl (ImixsArrayRenderer.tsx:116)
    at renderWithHooks (react-dom.development.js:14803)
    at mountIndeterminateComponent (react-dom.development.js:17482)
    at beginWork (react-dom.development.js:18596)
    at HTMLUnknownElement.callCallback (react-dom.development.js:188)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
    at invokeGuardedCallback (react-dom.development.js:292)
    at beginWork$1 (react-dom.development.js:23203)
    at performUnitOfWork (react-dom.development.js:22157)
    at workLoopSync (react-dom.development.js:22130)

We thought that by defining a custom Props Interface this should be fixed:

export interface BPMNArrayControlProps extends ArrayControlProps, VanillaRendererProps {
    getStyleAsClassName(string: string, ...args: any[]): string;
    uischemas: JsonFormsUISchemaRegistryEntry[];
}

But this is not the case. Also if I try to ignore the getStyleAsClassName method and set the style class hard coded there are still runtime errors like:

BPMNArrayRenderer.tsx:59 Uncaught TypeError: addItem is not a function
    at BPMNArrayComponent (ImixsArrayRenderer.tsx:59)
    at renderWithHooks (react-dom.development.js:14803)
    at mountIndeterminateComponent (react-dom.development.js:17482)
    at beginWork (react-dom.development.js:18596)
    at HTMLUnknownElement.callCallback (react-dom.development.js:188)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
    at invokeGuardedCallback (react-dom.development.js:292)
    at beginWork$1 (react-dom.development.js:23203)
    at performUnitOfWork (react-dom.development.js:22157)
    at workLoopSync (react-dom.development.js:22130)

this is from the component adding a button with a onClick event:

 <button className={classNames.button} onClick={addItem(path, createDefaultValue(schema))}>
                        +
</button>

Is it possible that these are all functions that I am not allowed to use? How can I solve this problem. My understanding was that these methods are part of @jsonforms/core, ‘correctly’?

Can you please give me an advice how I should setup my custom renderer?

Is it possible that my final export instruction is causing the problem?

export const BPMNEventDefinitionRenderer: any = {
    tester: rankWith(7, scopeEndsWith('conditions')),
    renderer: withJsonFormsControlProps(BPMNEventDefinitionControl)
};

Hi @rsoika,

First, some general information:

withJsonFormsControlProps is our most basic control binding. We offer numerous other ones like withJsonFormsEnumProps or withJsonFormsArrayControlProps which internally use the same functionality as withJsonFormsControlProps but modify some props or add additional things on top.

Then there are additional HOCs which can be used composition wise to add additional props on top, especially when these props are only relevant for specific renderer sets.

Is it possible that these are all functions that I am not allowed to use? How can I solve this problem. My understanding was that these methods are part of @jsonforms/core, ‘correctly’?

As outline above, the problem here is that withJsonFormsControlProps does not offer addItem nor getStyleAsClassName.

Instead you can use withJsonFormsArrayControlProps, which besides the usual props hands you over childErrors, uischemas, renderers, cells, addItem, removeItems, moveUp and moveDown.
Using withJsonFormsArrayControlProps is also important for your code as schema will then point to the items object in your JSON Schema. Using withJsonFormsControlProps the schema will then point to the type: 'array' (parent of items) schema instead.

The getStyleAsClassName is a React Vanilla specific functionality, therefore it’s part of a separate HOC, withVanillaControlProps. This HOC also offers getStyle and classNames.

So in the end your code should look like this:

export const BPMNEventDefinitionRendererEntry: JsonFormsRendererRegistryEntry = {
    tester: rankWith(7, scopeEndsWith('conditions')),
    renderer: withVanillaControlProps(withJsonFormsArrayControlProps(BPMNEventDefinitionControl))
};

You can always check what bindings our already offered renderers are using to see what you probably need to use too.

Note that there is no need to repeat the getStyleAsClassName and uischemas in your interface. They are already part of the ArrayControlProps and VanillaRendererProps. So you could define

export interface BPMNArrayControlProps extends ArrayControlProps, VanillaRendererProps {}

or

type BPMNArrayControlProps = ArrayControlProps & VanillaRendererProps;

Hello @sdirix
thanks for your response. I followed your advice and changed my implementation.

You can see the implementation now here: open-bpmn/BPMNArrayRenderer.tsx at master · imixs/open-bpmn · GitHub

But this code is now again not accepted by our compiler settings which are form “@eclipse-glsp/config”: “1.0.0”. It seems to be only one thing in the BPMNArrayControl in Line 139:

Type 'JsonFormsUISchemaRegistryEntry[] | undefined' is not assignable to type 'JsonFormsUISchemaRegistryEntry[]'.
  Type 'undefined' is not assignable to type 'JsonFormsUISchemaRegistryEntry[]'.

But before your begin again to dig into this issue, I think it is better when I stop this all. I am really not the JavaScript expert and I don’t think that I can contribute a lot. I’m just stealing your time…

I do implementing a BPMN Modeller Tool Platform based on GLSP. JSONForms is a brilliant concept because of its flexibility. I use this to compute server side schemas for BPMN elements selected by the user. Depending on aspects within the model, the properties of an element can be very different. With the concept of Schemas, JSONForms provides me the perfect solution to solve the property panel part.

But I now see that it makes no sense if I try to implement a new renderer. I possible need months for that and end with a bad solution incompatible with your current development.

So back to the root: With the vanilla ArrayControllRenderer I can solve one important aspect of my dynamic computed property panel and I have already implemented a lot of Java code to compute the different schemas.

So I think there are only two things I am missing in the current implementation:

a) the delete button:
As far as I understand, this can be taken from the Table array control:

<button
          aria-label={`Delete`}
          onClick={() => {
            if (window.confirm('Are you sure you wish to delete this item?')) {
              this.confirmDelete(childPath, index);
            }
          }}
        >
          Delete
</button>

b) the add button

I think the layout of the Table array control is much more flexible. So my suggestion is to go a simmilar way for the array control. Replace the fieldset with a simple div and a header elmenet and add the delete button to each details form.

Which maybe can look finally like this:

        <div className={classNames.wrapper}>
        
	        <header>
	          <label className={labelClass}>{label}</label>
	          <button
	            className={buttonClass}
	            onClick={addItem(path, createDefaultValue(schema))}
	          >
	            Add to {labelObject.text}
	          </button>
	        </header>
	        <div className={divClassNames}>
	          {!isValid ? errors : ''}
	        </div>        
        
        
            <div className={classNames.fieldSet}>
                
                <div className={classNames.children}>
                    {data ? (
                        range(0, data.length).map(index => {
                            const childPath = composePaths(path, `${index}`);

                            return (
                                <JsonFormsDispatch
                                    schema={schema}
                                    uischema={childUiSchema || uischema}
                                    path={childPath}
                                    key={childPath}
                                    renderers={renderers}
                                />
                                <button
					  aria-label={`Delete`}
					  onClick={() => {
					    if (window.confirm('Are you sure you wish to delete this item?')) {
					      this.confirmDelete(childPath, index);
					    }
					  }}
					>
					  Delete
				</button>
                            );
                        })
                    ) : (
                        <p>No data</p>
                    )}
                </div>
            </div>
        </div>

I am not sure if the placement of the delete button here is correct. But please let me know what you are thinking about this change for the current development stream?

But this code is now again not accepted by our compiler settings which are form “@eclipse-glsp/config”:

I don’t really understand on a first glance why you get this error. The uischemas you receive in BPMNArrayControl should exist as your BPMNArrayControlProps specifically define them to be there. In Line 139 you hand them over to BPMNArrayComponent which uses BPMNArrayComponentProps which extends ArrayControlProps which define them as optional. So handing over a non-optional prop to the optional equivalent should not trigger an error.

Are you sure you compiled with the sources as committed? Maybe some caching issue?

But before your begin again to dig into this issue, I think it is better when I stop this all. I am really not the JavaScript expert and I don’t think that I can contribute a lot. I’m just stealing your time…

It seems you are mostly struggling because you can’t 1:1 copy over the sources from JSON Forms because of some typing issues. Maybe convert this file to JS for the time being and remove all the types in case your build can handle that? This would at least enable you to use the code as it exists at the moment and do your modifications until there is a better solution.

But please let me know what you are thinking about this change for the current development stream?

In general yes, adding a delete button and restructuring the renderer definitely make sense and would be welcomed additions to JSON Forms.

The suggested JSX mostly looks fine on a first glance…, but we should probably no longer use the fieldSet class then anymore. Also we should add a class to all elements (which is something we should do in general for all Vanilla renderers! However for now we should definitely do it whenever we do changes :wink: )

If you like you can open a contribution with a version you are happy with, we’ll definitely take a look.

@sdirix I tried again to play arround with the interface definition, but I am not able to solve that kind of compiler issues. The full compiler error message looks like this:

@open-bpmn/open-bpmn-properties: yarn run v1.22.19
@open-bpmn/open-bpmn-properties: $ yarn clean && yarn build && yarn lint
@open-bpmn/open-bpmn-properties: $ rimraf lib tsconfig.tsbuildinfo
@open-bpmn/open-bpmn-properties: $ tsc
@open-bpmn/open-bpmn-properties: src/BPMNArrayRenderer.tsx(54,28): error TS2345: Argument of type 'JsonFormsUISchemaRegistryEntry[] | undefined' is not assignable to parameter of type 'JsonFormsUISchemaRegistryEntry[]'.
@open-bpmn/open-bpmn-properties:   Type 'undefined' is not assignable to type 'JsonFormsUISchemaRegistryEntry[]'.
@open-bpmn/open-bpmn-properties: src/BPMNArrayRenderer.tsx(58,25): error TS2532: Object is possibly 'undefined'.
@open-bpmn/open-bpmn-properties: src/BPMNArrayRenderer.tsx(59,34): error TS2532: Object is possibly 'undefined'.
@open-bpmn/open-bpmn-properties: src/BPMNArrayRenderer.tsx(61,40): error TS2532: Object is possibly 'undefined'.
@open-bpmn/open-bpmn-properties: src/BPMNArrayRenderer.tsx(66,33): error TS2532: Object is possibly 'undefined'.
@open-bpmn/open-bpmn-properties: error Command failed with exit code 2.
@open-bpmn/open-bpmn-properties: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
@open-bpmn/open-bpmn-properties: error Command failed with exit code 2.
@open-bpmn/open-bpmn-properties: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
lerna ERR! yarn run prepare exited 2 in '@open-bpmn/open-bpmn-properties'

But as mentioned before we should not waist to much time into this kind of problem.

As you have suggested I have created now a pull request which is just replacing the filedset with div/header tags:

I also tried to add a error section as it is used in the TableArrayComponent. I am not sure if this is correct, as the property ‘errors’ is not defined. You know this is the part in your concept where I lost you :wink:

So maybe you can take a look at it if this is somehow acceptable. The delete button should be handled in a seperate pull request later.