Due to some long forms users want to have next and previous click to go to the top of the #stepper form. Need some tips on how to make this happen. I guess a customrender, but since a stepper with navbuttons already exists i need some pointers:)
Hi @trillian74,
Yes, a custom renderer is the way to go. The stepper renderer is quite simplistic so I would like to suggest to copy it and modify it your needs. You can find it here.
Thanks for the suggestion. Iâll do a copy of the code and transform it to my needs.
Ole
I tried creating a second version in my source, but the form disappears after the final rendering when i use it. Do i have to create my own âversionâ of the material renderers package?
Ole
Hi @sdirix
I tried creating a second version in my source, but the form disappears after the final rendering when i use it. Do i have to create my own âversionâ of the material renderers package?
Ole
Hi @trillian74,
You need to create a custom renderer which is added to renderer
registry (i.e. an array of tester
/renderer
pairs). There is no need to build an own version of the Material renderers.
You can see how this is done in the tutorial or the React seed.
thanxs @sdirix - iâve done some custom renderes with success. This one though, gives me problems. The way i see it is that i can copy that file into my code - exactly as it is and set another rank number and this one should be selected instead. when i add it to my renderes⌠eventhough i have materials renderes in my form?
const renderers = [
...materialRenderers,
//register custom renderers
{
tester: materialCategorizationStepperTester, // this one is mine (but equal)
renderer: MaterialCategorizationStepperLayoutRenderer,// this one is mine (but equalish... have rank number 3 instead of 2)
},
{
tester: contactPickerManagementControlTester,
renderer: ContactPickerManagementControl,
},
{
tester: peoplePickerManagementControlTester,
renderer: PeoplePickerManagementControl,
},
{
tester: singleChipSelectControlTester,
renderer: SingleChipSelectSingleControl,
},
{ tester: multiChipSelectControlTester, renderer: MultiChipSelectControl },
];
so when i apply the uischema it just dissapears from the gui⌠without an error message.
Any suggestions?
@sdirix i found out why in the end. I donât properly understand why it differs, but the property visible was undefined on the contrary to the one in material renderers.
public render() {
const {
data,
path,
renderers,
schema,
uischema,
visible = true, // i just hardcoded this - but i would like it not to be
cells,
config,
ajv,
} = this.props;
const categorization = uischema as Categorization;
const activeCategory = this.state.activeCategory;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const buttonWrapperStyle = {
textAlign: "right" as "right",
width: "100%",
margin: "1em auto",
};
const buttonNextStyle = {
float: "right" as "right",
};
const buttonStyle = {
marginRight: "1em",
};
const childProps: MaterialLayoutRendererProps = {
elements: categorization.elements[activeCategory].elements,
schema,
path,
direction: "column",
visible,
renderers,
cells,
};
Not sure how this can happen. Our bindings should make sure that visible
is set. Can you debug to find the cause of the issue? Are @jsonforms/core
, @jsonforms/react
and @jsonforms/material-renderers
on the exact same version?
â@jsonforms/coreâ: â2.5.2â,
â@jsonforms/material-renderersâ: â2.5.2â,
â@jsonforms/reactâ: â2.5.2â,
@sdirix Find my code below only edit is that the next button is named Next123 and rank is 3
The stepper from the package works alright, but this one makes it dissapear when it enter
<div>
<MaterialLayoutRenderer {...childProps} />
</div>
import * as React from "react";
import merge from "lodash/merge";
import { Button, Hidden, Step, StepButton, Stepper } from "@material-ui/core";
import {
and,
Categorization,
categorizationHasCategory,
Category,
isVisible,
optionIs,
RankedTester,
rankWith,
StatePropsOfLayout,
uiTypeIs,
} from "@jsonforms/core";
import { RendererComponent, withJsonFormsLayoutProps } from "@jsonforms/react";
import {
AjvProps,
MaterialLayoutRenderer,
MaterialLayoutRendererProps,
withAjvProps,
} from "@jsonforms/material-renderers";
export const myMaterialCategorizationStepperTester: RankedTester = rankWith(
3,
and(
uiTypeIs("Categorization"),
categorizationHasCategory,
optionIs("variant", "stepper")
)
);
export interface CategorizationStepperState {
activeCategory: number;
}
export interface MaterialCategorizationStepperLayoutRendererProps
extends StatePropsOfLayout,
AjvProps {
data: any;
}
export class MyMaterialCategorizationStepperLayoutRenderer extends RendererComponent<
MaterialCategorizationStepperLayoutRendererProps,
CategorizationStepperState
> {
constructor(props: MaterialCategorizationStepperLayoutRendererProps) {
super(props);
this.state = { activeCategory: 0 };
}
public handleStep = (step: number) => {
this.setState({ activeCategory: step });
}
public render() {
const {
data,
path,
renderers,
schema,
uischema,
visible,
cells,
config,
ajv,
} = this.props;
const categorization = uischema as Categorization;
const activeCategory = this.state.activeCategory;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const buttonWrapperStyle = {
textAlign: "right" as "right",
width: "100%",
margin: "1em auto",
};
const buttonNextStyle = {
float: "right" as "right",
};
const buttonStyle = {
marginRight: "1em",
};
const childProps: MaterialLayoutRendererProps = {
elements: categorization.elements[activeCategory].elements,
schema,
path,
direction: "column",
visible,
renderers,
cells,
};
console.log(childProps);
const categories = categorization.elements.filter((category: Category) =>
isVisible(category, data, undefined, ajv)
);
return (
<Hidden xsUp={!visible}>
<Stepper activeStep={activeCategory} nonLinear>
{categories.map((e: Category, idx: number) => (
<Step key={e.label}>
<StepButton onClick={() => this.handleStep(idx)}>
{e.label}
</StepButton>
</Step>
))}
</Stepper>
<div>
<MaterialLayoutRenderer {...childProps} />
</div>
{!!appliedUiSchemaOptions.showNavButtons ? (
<div style={buttonWrapperStyle}>
<Button
style={buttonNextStyle}
variant="contained"
color="primary"
disabled={activeCategory >= categories.length - 1}
onClick={() => this.handleStep(activeCategory + 1)}
>
Next123
</Button>
<Button
style={buttonStyle}
color="secondary"
variant="contained"
disabled={activeCategory <= 0}
onClick={() => this.handleStep(activeCategory - 1)}
>
Previous
</Button>
</div>
) : (
<></>
)}
</Hidden>
);
}
}
export default withJsonFormsLayoutProps(
withAjvProps(MyMaterialCategorizationStepperLayoutRenderer)
);
Are you actually registering the wrapped default export or did you by accident register the raw renderer? That would explain why the props are not properly determined. Did you debug the withJsonFormsLayoutProps
binding? Is it called?
@sdirix thanx for your patience with me:)
Iâm not sure what you mean about register the raw renderer. Do you mean that i import from is wrong and that i should create my own?
import {
AjvProps,
MaterialLayoutRenderer,
MaterialLayoutRendererProps,
withAjvProps,
} from "@jsonforms/material-renderers";
Hi @trillian74,
I meant the way how your custom control is registered. Can you show where you add the renderer and the tester to the renderers
registry and how you import your custom renderer?
@sdirix Hi again. You are so right. i had
{
tester: myMaterialCategorizationStepperTester,
renderer: MyMaterialCategorizationStepperLayoutRenderer, // this was my bad
},
changed it to
{
tester: myMaterialCategorizationStepperTester,
renderer: MyMaterialCategorizationStepperLayout,
},
man, it was right in front of me. thank you for your patience.
Hi
I am trying to create the custom stepper and using the above example and getting some errors, could you please let me know what i was doing wrong.
import * as React from 'react';
import merge from 'lodash/merge';
import { Button, Hidden, Step, StepButton, Stepper } from '@material-ui/core';
import {
and,
Categorization,
categorizationHasCategory,
Category,
isVisible,
optionIs,
RankedTester,
rankWith,
StatePropsOfLayout,
uiTypeIs,
} from '@jsonforms/core';
import { RendererComponent, withJsonFormsLayoutProps } from '@jsonforms/react';
import {
AjvProps,
MaterialLayoutRenderer,
MaterialLayoutRendererProps,
withAjvProps,
} from '@jsonforms/material-renderers';
export const myMaterialCategorizationStepperTester: RankedTester = rankWith(
3,
and(
uiTypeIs('Categorization'),
categorizationHasCategory,
optionIs('variant', 'stepper')
)
);
export interface CategorizationStepperState {
activeCategory: number;
}
export interface MaterialCategorizationStepperLayoutRendererProps
extends StatePropsOfLayout,
AjvProps {
data: any;
}
export class MyMaterialCategorizationStepperLayoutRenderer extends RendererComponent<
MaterialCategorizationStepperLayoutRendererProps,
CategorizationStepperState
> {
constructor(props: MaterialCategorizationStepperLayoutRendererProps) {
super(props);
this.state = { activeCategory: 0 };
}
public handleStep = (step: number) => {
this.setState({ activeCategory: step });
};
public render() {
const {
data,
path,
renderers,
schema,
uischema,
visible,
cells,
config,
ajv,
} = this.props;
const categorization = uischema as Categorization;
const activeCategory = this.state.activeCategory;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
const buttonWrapperStyle = {
textAlign: 'right' as 'right',
width: '100%',
margin: '1em auto',
};
const buttonNextStyle = {
float: 'right' as 'right',
};
const buttonStyle = {
marginRight: '1em',
};
const childProps: MaterialLayoutRendererProps = {
elements: categorization.elements[activeCategory].elements,
schema,
path,
direction: 'column',
visible,
renderers,
cells,
};
console.log(childProps);
const categories = categorization.elements.filter(
(category: Category ***| Categorization***) =>
isVisible(category, data, ***''***, ajv)
);
return (
<Hidden xsUp={!visible}>
<Stepper activeStep={activeCategory} nonLinear>
{categories.map((e: Category *| Categorization*, idx: number) => (
<Step key={e.label}>
<StepButton onClick={() => this.handleStep(idx)}>
{e.label}
</StepButton>
</Step>
))}
</Stepper>
<div>
<MaterialLayoutRenderer {...childProps} />
</div>
{!!appliedUiSchemaOptions.showNavButtons ? (
<div style={buttonWrapperStyle}>
<Button
style={buttonNextStyle}
variant='contained'
color='primary'
disabled={activeCategory >= categories.length - 1}
onClick={() => this.handleStep(activeCategory + 1)}
>
Next123
</Button>
<Button
style={buttonStyle}
color='secondary'
variant='contained'
disabled={activeCategory <= 0}
onClick={() => this.handleStep(activeCategory - 1)}
>
Previous
</Button>
</div>
) : (
<></>
)}
</Hidden>
);
}
}
export default withJsonFormsLayoutProps(
withAjvProps(MyMaterialCategorizationStepperLayoutRenderer)
);
got error at few places Type âCategorizationâ is not assignable to type âCategoryâ.
TS2769
90 | };
91 | console.log(childProps);
92 | const categories = categorization.elements.filter((category: Category) =>
| ^
93 | isVisible(category, data, undefined, ajv)
94 | );
95 | return (
Changed line 92 to (category: Category | Categorization) and next line removed undefined and passing empty string in i isVisible(category, data, ââ, ajv). Changed the line 99 to {categories.map((e: Category | Categorization, idx: number) => (
now getting an error
Argument of type â(props: MaterialCategorizationStepperLayoutRendererProps) => Elementâ is not assignable to parameter of type âComponentTypeâ.
Type â(props: MaterialCategorizationStepperLayoutRendererProps) => Elementâ is not assignable to type âFunctionComponentâ.
Types of parameters âpropsâ and âpropsâ are incompatible.
Property âajvâ is missing in type âPropsWithChildrenâ but required in type âMaterialCategorizationStepperLayoutRendererPropsâ. TS2345
138 |
139 | export default withJsonFormsLayoutProps(
140 | withAjvProps(MyMaterialCategorizationStepperLayoutRenderer)
| ^
141 | );
142 |
Any help is appreciated. Thanks in advance.
Hi @vivek, this is rather hard to follow. Can you post your code on a branch or in an executable demonstrator so I can easily reproduce this on my side?
@sdirix Hi,
I have working example code example.
Visible value is getting undefined, updated the visible value as following
let updatedProps = { âŚchildProps };
updatedProps.visible = !updatedProps.visible;
childProps = { âŚupdatedProps };
to show up the elements changed the line 102
Not sure what i am doing is right and also i am trying enable the âNEXTâ button if current step is valid. i am getting âajvâ value is undefined. Could you please let me now when you get a chance.
Thanks in advance
Vivek
Hi @vivek, by coincidence your code contains the same error as discussed with the previous person in the thread. Your renderer is wrapped and default
exported (!!) in lines 148-150. However in App.tsx
you are not importing the default exported renderer but the raw version.
So please change your import to
import MyRendererNameWhichCanBeAnything, {
myMaterialCategorizationStepperTester
} from "./MyMatCatLayoutRenderer";
and register that renderer instead.
Not sure what i am doing is right and also i am trying enable the âNEXTâ button if current step is valid. i am getting âajvâ value is undefined. Could you please let me now when you get a chance.
Only enabling the ânextâ button when the current data is valid is not that easy as the controls can point to arbitrary parts of the data. Of course if all your steps are always contained in separate objects you can take some large shortcuts.
What you have to do is to check all current errors, filter them according to your current ui schema and check whether some are left. To access all errors you can use
const ctx = useJsonForms();
const errors = ctx.core.errors;
To filter errors according to scope you can check out mapStateToControlProps and how itâs done there.
As I said at some point before: This use case is usually better handled by implementing an own stepper outside of JSON Forms and rendering each separate step via JSON Forms instead of placing all of this in a single form.
Thank you so much for quick response. Filtering errors should work. Thanks