rjsf-team / react-jsonschema-form

A React component for building Web forms from JSON Schema.
https://rjsf-team.github.io/react-jsonschema-form/
Apache License 2.0
14.13k stars 2.18k forks source link

Having Layouts Horizontal/Vertical #3840

Open indersj opened 1 year ago

indersj commented 1 year ago

Prerequisites

What theme are you using?

core

Is your feature request related to a problem? Please describe.

I have read about the ObjectFieldTemplate, where the layout can be customized. But the question is will there be a feature of having a layout controlling the form vertically/horizontally and Categorization as per the schema config?

Describe the solution you'd like

const uischema = { type: 'VerticalLayout', elements: [ { type: 'Control', label: false, scope: '#/properties/done', }, { type: 'Control', scope: '#/properties/name', }, { type: 'HorizontalLayout', elements: [ { type: 'Control', scope: '#/properties/due_date', }, { type: 'Control', scope: '#/properties/recurrence', }, ], }, ], };

Describe alternatives you've considered

What I am trying to suggest below jsonforms layouts.

https://jsonforms.io/examples/layouts

nickgros commented 1 year ago

I think @heath-freenome has an idea for how this may be implemented, but I don't think it's on our radar yet.

This issue includes references to many other issues that show how other RJSF users have made this work in the past: https://github.com/rjsf-team/react-jsonschema-form/issues/3261

aularon commented 12 months ago

You can use rjsf-layout (released to public today) to achieve what you're after:

import Form, { Field } from "rjsf-layout";

<Form {...props}>
  <Field name="done" />
  <Field name="name" />
  <div style={{ display: "flex" }}>
    <Field name="due_date" />
    <Field name="recurrence" />
  </div>
</Form>

I developed it specifically to address this class of needs. Check documentation for further goodies.

nagaozen commented 11 months ago

I've implemented this way to provide a way to be customizable by uiSchema:

import { useMemo } from 'react'
import { canExpand, descriptionId, getTemplate, getUiOptions, titleId } from '@rjsf/utils'

/** The LayoutFieldTemplate` is the template to use to render all the inner properties of an object along with the
 * title and description if available. If the object is expandable, then an `AddButton` is also rendered after all
 * the properties.
 *
 * @param props - The `LayoutFieldTemplateProps` for this component
 */
export default function LayoutFieldTemplate (props) {
  const {
    description,
    disabled,
    formData,
    idSchema,
    onAddClick,
    properties,
    readonly,
    registry,
    required,
    schema,
    title,
    uiSchema
  } = props
  const options = getUiOptions(uiSchema)
  const TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, options)
  const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, options)
  // Button templates are not overridden in the uiSchema
  const {
    ButtonTemplates: { AddButton }
  } = registry.templates

  const layout = uiSchema['ui:layout']
  const map = useMemo(() => properties.reduce((o, x) => ({ ...o, [x.name]: x }), {}), [properties])

  return (
    <fieldset id={idSchema.$id}>
      {title && (
        <TitleFieldTemplate
          id={titleId(idSchema)}
          title={title}
          required={required}
          schema={schema}
          uiSchema={uiSchema}
          registry={registry}
        />
      )}
      {description && (
        <DescriptionFieldTemplate
          id={descriptionId(idSchema)}
          description={description}
          schema={schema}
          uiSchema={uiSchema}
          registry={registry}
        />
      )}
      {layout.map((row, i) => (
        <div key={`L${i}j`} className='row'>
          {Object.keys(row).map((name, j) => (
            <div key={`L${i}${j}`} className={row[name].classNames}>
              {map[name].content}
            </div>
          ))}
        </div>
      ))}
      {canExpand(schema, uiSchema, formData) && (
        <AddButton
          className='object-property-expand'
          onClick={onAddClick(schema)}
          disabled={disabled || readonly}
          uiSchema={uiSchema}
          registry={registry}
        />
      )}
    </fieldset>
  )
}

Usage:

{
  schema: {
    title: 'A registration form',
    description:
      'This is the same as the simple form, but with an altered bootstrap grid. Set the theme to default, and try shrinking the browser window to see it in action.',
    type: 'object',
    required: ['firstName', 'lastName'],
    properties: {
      firstName: {
        type: 'string',
        title: 'First name'
      },
      lastName: {
        type: 'string',
        title: 'Last name'
      },
      telephone: {
        type: 'string',
        title: 'Telephone',
        minLength: 10
      },
      email: {
        type: 'string',
        format: 'email',
        title: 'Email'
      }
    }
  },
  uiSchema: {
    ObjectFieldTemplate: LayoutFieldTemplate,
    'ui:layout': [
      {
        firstName: { classNames: 'col-md-6' },
        lastName: { classNames: 'col-md-6' }
      },
      {
        email: { classNames: 'col-md-6' },
        telephone: { classNames: 'col-md-6' }
      }
    ]
  },
  formData: {}
}

Issue related to #3695.

billycrid commented 8 months ago

@nagaozen - Your example works perfect in a scenario where your schema is just one level of strings/numbers etc. Did you have any example of how this would work with multiple objects at child level? For example with a schema like such...

{ title: 'A registration form', description: 'This is the same as the simple form, but with an altered bootstrap grid. Set the theme to default, and try shrinking the browser window to see it in action.', type: 'object', required: ['firstName', 'lastName'], properties: { firstName: { type: 'string', title: 'First name' }, lastName: { type: 'string', title: 'Last name' }, telephone: { type: 'string', title: 'Telephone', minLength: 10 }, email: { type: 'string', format: 'email', title: 'Email' }, testObject: { type: 'object', title: 'Testing an Object', properties: { foo: { type: 'string', title: 'foo' }, bar: { type: 'string', title: 'bar' } } } } }

with this schema, the code will break

nagaozen commented 7 months ago

@nagaozen - Your example works perfect in a scenario where your schema is just one level of strings/numbers etc. Did you have any example of how this would work with multiple objects at child level? For example with a schema like such...

{ title: 'A registration form', description: 'This is the same as the simple form, but with an altered bootstrap grid. Set the theme to default, and try shrinking the browser window to see it in action.', type: 'object', required: ['firstName', 'lastName'], properties: { firstName: { type: 'string', title: 'First name' }, lastName: { type: 'string', title: 'Last name' }, telephone: { type: 'string', title: 'Telephone', minLength: 10 }, email: { type: 'string', format: 'email', title: 'Email' }, testObject: { type: 'object', title: 'Testing an Object', properties: { foo: { type: 'string', title: 'foo' }, bar: { type: 'string', title: 'bar' } } } } }

with this schema, the code will break

Could you please clarify what you mean by breaking code? We deeply believe in this project and use it in production. None of our contributions seems to break with nested objects.