Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render a select widget for arrays if specified in the ui schema #2125

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/usage/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ render((

## Multiple-choice list

The default behavior for array fields is a list of text inputs with add/remove buttons. There are two alternative widgets for picking multiple elements from a list of choices. Typically this applies when a schema has an `enum` list for the `items` property of an `array` field, and the `uniqueItems` property set to `true`.
The default behavior for array fields is a list of text inputs with add/remove buttons. There are two alternative widgets for picking multiple elements from a list of choices. This applies when a schema has an `enum` list for the `items` property of an `array` field and the `uniqueItems` property set to `true`, or when the `ui:widget` is set to `select`.

Example:

Expand Down
2 changes: 1 addition & 1 deletion packages/antd/src/templates/ArrayFieldTemplate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const ArrayFieldTemplate = ({
if (isFilesArray(schema, uiSchema, rootSchema)) {
return renderFiles();
}
if (isMultiSelect(schema, rootSchema)) {
if (isMultiSelect(schema, uiSchema, rootSchema)) {
return renderMultiSelect();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
const { schema, registry = getDefaultRegistry() } = props;

// TODO: update types so we don't have to cast registry as any
if (isMultiSelect(schema, (registry as any).rootSchema)) {
if (isMultiSelect(schema, props.uiSchema, (registry as any).rootSchema)) {
return <DefaultFixedArrayFieldTemplate {...props} />;
} else {
return <DefaultNormalArrayFieldTemplate {...props} />;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ declare module '@rjsf/core' {

export function isSelect(_schema: JSONSchema7, definitions?: FieldProps['registry']['definitions']): boolean;

export function isMultiSelect(schema: JSONSchema7, definitions?: FieldProps['registry']['definitions']): boolean;
export function isMultiSelect(schema: JSONSchema7, uiSchema: UiSchema, definitions?: FieldProps['registry']['definitions']): boolean;

export function isFilesArray(
schema: JSONSchema7,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ class ArrayField extends Component {
if (isFilesArray(schema, uiSchema, rootSchema)) {
return this.renderFiles();
}
if (isMultiSelect(schema, rootSchema)) {
if (isMultiSelect(schema, uiSchema, rootSchema)) {
return this.renderMultiSelect();
}
return this.renderNormalArray();
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ function computeDefaults(
});
}
if (schema.minItems) {
if (!isMultiSelect(schema, rootSchema)) {
// The UI schema is not important when calculating the defaults, so we just pass a default empty object.
if (!isMultiSelect(schema, {}, rootSchema)) {
const defaultsLength = defaults ? defaults.length : 0;
if (schema.minItems > defaultsLength) {
const defaultEntries = defaults || [];
Expand Down Expand Up @@ -389,7 +390,7 @@ export function getDisplayLabel(schema, uiSchema, rootSchema) {
let { label: displayLabel = true } = uiOptions;
if (schema.type === "array") {
displayLabel =
isMultiSelect(schema, rootSchema) ||
isMultiSelect(schema, uiSchema, rootSchema) ||
isFilesArray(schema, uiSchema, rootSchema);
}
if (schema.type === "object") {
Expand Down Expand Up @@ -528,7 +529,10 @@ export function isSelect(_schema, rootSchema = {}) {
return false;
}

export function isMultiSelect(schema, rootSchema = {}) {
export function isMultiSelect(schema, uiSchema, rootSchema = {}) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we restricting this to only use the "select" widget? Can we remove that restriction and leave it up to the user. The reason being, it could be possible, that the customWidget for select handles only single select and one could have a separate widget for multi select.

if (uiSchema["ui:widget"] === "select") {
return true;
}
if (!schema.uniqueItems || !schema.items) {
return false;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/core/test/utils_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,17 @@ describe("utils", () => {
};
expect(isMultiSelect(schema)).to.be.false;
});

it("should be true if widget is select", () => {
const schema = {
items: { foo: { type: "string" } },
};
const uiSchema = {
"ui:widget": "select",
};

expect(isMultiSelect(schema, uiSchema)).to.be.true;
});
});

describe("isFilesArray()", () => {
Expand Down
12 changes: 6 additions & 6 deletions packages/fluent-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import AddButton from "../AddButton/AddButton";
import IconButton from "../IconButton/IconButton";

const rightJustify = {
float: "right"
float: "right",
} as React.CSSProperties;

const { isMultiSelect, getDefaultRegistry } = utils;
Expand All @@ -17,7 +17,7 @@ const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
const { schema, registry = getDefaultRegistry() } = props;

// TODO: update types so we don't have to cast registry as any
if (isMultiSelect(schema, (registry as any).rootSchema)) {
if (isMultiSelect(schema, props.uiSchema, (registry as any).rootSchema)) {
return <DefaultFixedArrayFieldTemplate {...props} />;
} else {
return <DefaultNormalArrayFieldTemplate {...props} />;
Expand Down Expand Up @@ -70,11 +70,11 @@ const DefaultArrayItem = (props: any) => {
<div key={props.key} className="ms-Grid" dir="ltr">
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm6 ms-md8 ms-lg9">
<div className="ms-Grid-row">
{props.children}
</div>
<div className="ms-Grid-row">{props.children}</div>
</div>
<div className="ms-Grid-col ms-sm6 ms-md4 ms-lg3" style={{textAlign: "right"}}>
<div
className="ms-Grid-col ms-sm6 ms-md4 ms-lg3"
style={{ textAlign: "right" }}>
<IconButton
icon="arrow-up"
className="array-item-move-up"
Expand Down
43 changes: 19 additions & 24 deletions packages/material-ui/src/ArrayFieldTemplate/ArrayFieldTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import React from 'react';
import React from "react";

import { utils } from '@rjsf/core';
import { utils } from "@rjsf/core";

import Box from '@material-ui/core/Box';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import Box from "@material-ui/core/Box";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";

import { ArrayFieldTemplateProps, IdSchema } from '@rjsf/core';
import { ArrayFieldTemplateProps, IdSchema } from "@rjsf/core";

import AddButton from '../AddButton/AddButton';
import IconButton from '../IconButton/IconButton';
import AddButton from "../AddButton/AddButton";
import IconButton from "../IconButton/IconButton";

const {
isMultiSelect,
getDefaultRegistry,
} = utils;
const { isMultiSelect, getDefaultRegistry } = utils;

const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
const { schema, registry = getDefaultRegistry() } = props;

// TODO: update types so we don't have to cast registry as any
if (isMultiSelect(schema, (registry as any).rootSchema)) {
if (isMultiSelect(schema, props.uiSchema, (registry as any).rootSchema)) {
return <DefaultFixedArrayFieldTemplate {...props} />;
} else {
return <DefaultNormalArrayFieldTemplate {...props} />;
Expand Down Expand Up @@ -73,7 +70,7 @@ const DefaultArrayItem = (props: any) => {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
fontWeight: 'bold',
fontWeight: "bold",
};
return (
<Grid container={true} key={props.key} alignItems="center">
Expand Down Expand Up @@ -130,23 +127,21 @@ const DefaultFixedArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema['ui:title'] || props.title}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>

{(props.uiSchema['ui:description'] || props.schema.description) && (
{(props.uiSchema["ui:description"] || props.schema.description) && (
<div
className="field-description"
key={`field-description-${props.idSchema.$id}`}
>
{props.uiSchema['ui:description'] || props.schema.description}
key={`field-description-${props.idSchema.$id}`}>
{props.uiSchema["ui:description"] || props.schema.description}
</div>
)}

<div
className="row array-item-list"
key={`array-item-list-${props.idSchema.$id}`}
>
key={`array-item-list-${props.idSchema.$id}`}>
{props.items && props.items.map(DefaultArrayItem)}
</div>

Expand All @@ -169,17 +164,17 @@ const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema['ui:title'] || props.title}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>

{(props.uiSchema['ui:description'] || props.schema.description) && (
{(props.uiSchema["ui:description"] || props.schema.description) && (
<ArrayFieldDescription
key={`array-field-description-${props.idSchema.$id}`}
DescriptionField={props.DescriptionField}
idSchema={props.idSchema}
description={
props.uiSchema['ui:description'] || props.schema.description
props.uiSchema["ui:description"] || props.schema.description
}
/>
)}
Expand Down