fix: allow any default list as default value for list widgets (#5030)

This commit is contained in:
Pearce 2021-04-05 08:54:07 -07:00 committed by GitHub
parent 9d049ca604
commit 83c235423e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 193 additions and 147 deletions

View File

@ -146,6 +146,28 @@ describe('entries', () => {
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
});
it('should allow a complex array as list default for a single field list', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [
{
url: 'https://image.png',
},
],
field: { name: 'url', widget: 'text' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({
images: fromJS([
{
url: 'https://image.png',
},
]),
});
});
it('should allow an empty array as list default for a fields list', () => {
const fields = fromJS([
{
@ -161,78 +183,49 @@ describe('entries', () => {
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
});
it('should not allow setting a non empty array as a default value for a single field list', () => {
it('should allow a complex array as list default for a fields list', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [{ name: 'url' }, { other: 'field' }],
field: { name: 'url', widget: 'text' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({});
});
it('should not allow setting a non empty array as a default value for a fields list', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [{ name: 'url' }, { other: 'field' }],
default: [
{
title: 'default image',
url: 'https://image.png',
},
],
fields: [
{ name: 'title', widget: 'text' },
{ name: 'url', widget: 'text' },
],
},
]);
expect(createEmptyDraftData(fields)).toEqual({});
});
it('should set default value for list field widget', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: ['https://image.png'] });
});
it('should override list default with field default', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [],
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: ['https://image.png'] });
});
it('should set default values for list fields widget', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
fields: [
{ name: 'title', widget: 'text', default: 'default image' },
{ name: 'url', widget: 'text', default: 'https://image.png' },
],
},
]);
expect(createEmptyDraftData(fields)).toEqual({
images: [{ title: 'default image', url: 'https://image.png' }],
images: fromJS([
{
title: 'default image',
url: 'https://image.png',
},
]),
});
});
it('should override list default with fields default', () => {
it('should use field default when no list default is provided', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
field: { name: 'url', widget: 'text', default: 'https://image.png' },
},
]);
expect(createEmptyDraftData(fields)).toEqual({ images: [{ url: 'https://image.png' }] });
});
it('should use fields default when no list default is provided', () => {
const fields = fromJS([
{
name: 'images',
widget: 'list',
default: [],
fields: [
{ name: 'title', widget: 'text', default: 'default image' },
{ name: 'url', widget: 'text', default: 'https://image.png' },
@ -298,6 +291,26 @@ describe('entries', () => {
]);
expect(createEmptyDraftData(fields)).toEqual({});
});
it('should populate nested fields', () => {
const fields = fromJS([
{
name: 'names',
widget: 'list',
field: {
name: 'object',
widget: 'object',
fields: [
{ name: 'first', widget: 'string', default: 'first' },
{ name: 'second', widget: 'string', default: 'second' },
],
},
},
]);
expect(createEmptyDraftData(fields)).toEqual({
names: [{ object: { first: 'first', second: 'second' } }],
});
});
});
describe('persistLocalBackup', () => {

View File

@ -771,7 +771,6 @@ interface DraftEntryData {
export function createEmptyDraftData(
fields: EntryFields,
withNameKey = true,
skipField: (field: EntryField) => boolean = () => false,
) {
return fields.reduce(
@ -795,36 +794,27 @@ export function createEmptyDraftData(
return [[{}], {}].some(e => isEqual(val, e));
}
if (List.isList(subfields)) {
const subDefaultValue = list
? [createEmptyDraftData(subfields as EntryFields, withNameKey, skipField)]
: createEmptyDraftData(subfields as EntryFields, withNameKey, skipField);
if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
} else if (list && List.isList(defaultValue) && (defaultValue as List<unknown>).isEmpty()) {
// allow setting an empty list as a default
const hasSubfields = List.isList(subfields) || Map.isMap(subfields);
if (hasSubfields) {
if (list && List.isList(defaultValue)) {
acc[name] = defaultValue;
}
return acc;
}
} else {
const asList = List.isList(subfields)
? (subfields as EntryFields)
: List([subfields as EntryField]);
if (Map.isMap(subfields)) {
const subDefaultValue = list
? [createEmptyDraftData(List([subfields as EntryField]), false, skipField)]
: createEmptyDraftData(List([subfields as EntryField]), withNameKey, skipField);
if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
} else if (list && List.isList(defaultValue) && (defaultValue as List<unknown>).isEmpty()) {
// allow setting an empty list as a default
acc[name] = defaultValue;
const subDefaultValue = list
? [createEmptyDraftData(asList, skipField)]
: createEmptyDraftData(asList, skipField);
if (!isEmptyDefaultValue(subDefaultValue)) {
acc[name] = subDefaultValue;
}
}
return acc;
}
if (defaultValue !== null) {
if (!withNameKey) {
return defaultValue;
}
acc[name] = defaultValue;
}
@ -843,7 +833,7 @@ function createEmptyDraftI18nData(collection: Collection, dataFields: EntryField
return field.get(I18N) !== I18N_FIELD.DUPLICATE && field.get(I18N) !== I18N_FIELD.TRANSLATE;
}
const i18nData = createEmptyDraftData(dataFields, true, skipField);
const i18nData = createEmptyDraftData(dataFields, skipField);
return duplicateDefaultI18nFields(collection, i18nData);
}

View File

@ -76,7 +76,7 @@ export class PreviewPane extends React.Component {
// We retrieve the field by name so that this function can also be used in
// custom preview templates, where the field object can't be passed in.
let field = fields && fields.find(f => f.get('name') === name);
let value = values && values.get(field.get('name'));
let value = Map.isMap(values) && values.get(field.get('name'));
if (field.get('meta')) {
value = this.props.entry.getIn(['meta', field.get('name')]);
}

View File

@ -80,6 +80,17 @@ function handleSummary(summary, entry, label, item) {
return stringTemplate.compileStringTemplate(summary, null, '', data);
}
function validateItem(field, item) {
if (!Map.isMap(item)) {
console.warn(
`'${field.get('name')}' field item value value should be a map but is a '${typeof item}'`,
);
return false;
}
return true;
}
export default class ListControl extends React.Component {
validations = [];
@ -386,6 +397,9 @@ export default class ListControl extends React.Component {
const valueType = this.getValueType();
switch (valueType) {
case valueTypes.MIXED: {
if (!validateItem(field, item)) {
return;
}
const itemType = getTypedFieldForValue(field, item);
const label = itemType.get('label', itemType.get('name'));
// each type can have its own summary, but default to the list summary if exists
@ -402,6 +416,9 @@ export default class ListControl extends React.Component {
return labelReturn;
}
case valueTypes.MULTIPLE: {
if (!validateItem(field, item)) {
return;
}
const multiFields = field.get('fields');
const labelField = multiFields && multiFields.first();
const value = item.get(labelField.get('name'));

View File

@ -9,7 +9,10 @@ The list widget allows you to create a repeatable item in the UI which saves as
* **Data type:** list of widget values
* **Options:**
* `default`: you may specify a list of strings to populate the basic text field, but declare defaults on the child widgets if you are specifying `fields`;
* `default`: you may specify a list of strings to populate the basic text
field, or an array of list items for lists using the `fields` option. If no
default is declared when using `field` or `fields`, will default to a single
list item using the defaults on the child widgets
* `allow_add`: `false` hides the button to add additional items
* `collapsed`: when `true`, the entries collapse by default
* `summary`: specify the label displayed on collapsed entries
@ -20,84 +23,107 @@ The list widget allows you to create a repeatable item in the UI which saves as
* `max`: maximum number of items in the list
* `min`: minimum number of items in the list
* `add_to_top`: when `true`, new entries will be added to the top of the list
* **Example** (`field`/`fields` not specified):
```yaml
- label: "Tags"
name: "tags"
widget: "list"
default: ["news"]
```
```yaml
- label: "Tags"
name: "tags"
widget: "list"
default: ["news"]
```
* **Example** (`allow_add` marked `false`):
```yaml
- label: "Tags"
name: "tags"
widget: "list"
allow_add: false
default: ["news"]
```
```yaml
- label: "Tags"
name: "tags"
widget: "list"
allow_add: false
default: ["news"]
```
* **Example** (with `field`):
```yaml
- label: "Gallery"
name: "galleryImages"
widget: "list"
summary: '{{fields.image}}'
field: {label: Image, name: image, widget: image}
```
```yaml
- label: "Gallery"
name: "galleryImages"
widget: "list"
summary: '{{fields.image}}'
field: {label: Image, name: image, widget: image}
```
* **Example** (with `fields`):
```yaml
- label: "Testimonials"
name: "testimonials"
widget: "list"
summary: '{{fields.quote}} - {{fields.author.name}}'
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- label: Author
name: author
widget: object
fields:
- {label: Name, name: name, widget: string, default: "Emmet"}
- {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"}
```
```yaml
- label: "Testimonials"
name: "testimonials"
widget: "list"
summary: '{{fields.quote}} - {{fields.author.name}}'
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- label: Author
name: author
widget: object
fields:
- {label: Name, name: name, widget: string, default: "Emmet"}
- {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"}
```
* **Example** (with `default`):
```yaml
- label: "Gallery"
name: "galleryImages"
widget: "list"
fields:
- { label: "Source", name: "src", widget: "string" }
- { label: "Alt Text", name: "alt", widget: "string" }
default:
- { src: "/img/tenis.jpg", alt: "Tenis" }
- { src: "/img/footbar.jpg", alt: "Football" }
```
* **Example** (`collapsed` marked `false`):
```yaml
- label: "Testimonials"
name: "testimonials"
collapsed: false
widget: "list"
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- {label: Author, name: author, widget: string }
```
```yaml
- label: "Testimonials"
name: "testimonials"
collapsed: false
widget: "list"
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- {label: Author, name: author, widget: string }
```
* **Example** (`minimize_collapsed` marked `true`):
```yaml
- label: "Testimonials"
name: "testimonials"
minimize_collapsed: true
widget: "list"
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- {label: Author, name: author, widget: string }
```
```yaml
- label: "Testimonials"
name: "testimonials"
minimize_collapsed: true
widget: "list"
fields:
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
- {label: Author, name: author, widget: string }
```
* **Example** (with `max` & `min`):
```yaml
- label: "Tags"
name: "tags"
widget: "list"
max: 3
min: 1
default: ["news"]
```
```yaml
- label: "Tags"
name: "tags"
widget: "list"
max: 3
min: 1
default: ["news"]
```
* **Example** (`add_to_top` marked `true`):
```yaml
- label: "Tags"
name: "tags"
widget: "list"
add_to_top: true
```
```yaml
- label: "Tags"
name: "tags"
widget: "list"
add_to_top: true
```