fix: allow any default list as default value for list widgets (#5030)
This commit is contained in:
parent
9d049ca604
commit
83c235423e
@ -146,6 +146,28 @@ describe('entries', () => {
|
|||||||
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
|
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', () => {
|
it('should allow an empty array as list default for a fields list', () => {
|
||||||
const fields = fromJS([
|
const fields = fromJS([
|
||||||
{
|
{
|
||||||
@ -161,78 +183,49 @@ describe('entries', () => {
|
|||||||
expect(createEmptyDraftData(fields)).toEqual({ images: fromJS([]) });
|
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([
|
const fields = fromJS([
|
||||||
{
|
{
|
||||||
name: 'images',
|
name: 'images',
|
||||||
widget: 'list',
|
widget: 'list',
|
||||||
default: [{ name: 'url' }, { other: 'field' }],
|
default: [
|
||||||
field: { name: 'url', widget: 'text' },
|
{
|
||||||
|
title: 'default image',
|
||||||
|
url: 'https://image.png',
|
||||||
},
|
},
|
||||||
]);
|
],
|
||||||
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' }],
|
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'title', widget: 'text' },
|
{ name: 'title', widget: 'text' },
|
||||||
{ name: 'url', 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({
|
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([
|
const fields = fromJS([
|
||||||
{
|
{
|
||||||
name: 'images',
|
name: 'images',
|
||||||
widget: 'list',
|
widget: 'list',
|
||||||
default: [],
|
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'title', widget: 'text', default: 'default image' },
|
{ name: 'title', widget: 'text', default: 'default image' },
|
||||||
{ name: 'url', widget: 'text', default: 'https://image.png' },
|
{ name: 'url', widget: 'text', default: 'https://image.png' },
|
||||||
@ -298,6 +291,26 @@ describe('entries', () => {
|
|||||||
]);
|
]);
|
||||||
expect(createEmptyDraftData(fields)).toEqual({});
|
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', () => {
|
describe('persistLocalBackup', () => {
|
||||||
|
@ -771,7 +771,6 @@ interface DraftEntryData {
|
|||||||
|
|
||||||
export function createEmptyDraftData(
|
export function createEmptyDraftData(
|
||||||
fields: EntryFields,
|
fields: EntryFields,
|
||||||
withNameKey = true,
|
|
||||||
skipField: (field: EntryField) => boolean = () => false,
|
skipField: (field: EntryField) => boolean = () => false,
|
||||||
) {
|
) {
|
||||||
return fields.reduce(
|
return fields.reduce(
|
||||||
@ -795,36 +794,27 @@ export function createEmptyDraftData(
|
|||||||
return [[{}], {}].some(e => isEqual(val, e));
|
return [[{}], {}].some(e => isEqual(val, e));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (List.isList(subfields)) {
|
const hasSubfields = List.isList(subfields) || Map.isMap(subfields);
|
||||||
const subDefaultValue = list
|
if (hasSubfields) {
|
||||||
? [createEmptyDraftData(subfields as EntryFields, withNameKey, skipField)]
|
if (list && List.isList(defaultValue)) {
|
||||||
: 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
|
|
||||||
acc[name] = defaultValue;
|
acc[name] = defaultValue;
|
||||||
}
|
} else {
|
||||||
return acc;
|
const asList = List.isList(subfields)
|
||||||
}
|
? (subfields as EntryFields)
|
||||||
|
: List([subfields as EntryField]);
|
||||||
|
|
||||||
if (Map.isMap(subfields)) {
|
|
||||||
const subDefaultValue = list
|
const subDefaultValue = list
|
||||||
? [createEmptyDraftData(List([subfields as EntryField]), false, skipField)]
|
? [createEmptyDraftData(asList, skipField)]
|
||||||
: createEmptyDraftData(List([subfields as EntryField]), withNameKey, skipField);
|
: createEmptyDraftData(asList, skipField);
|
||||||
|
|
||||||
if (!isEmptyDefaultValue(subDefaultValue)) {
|
if (!isEmptyDefaultValue(subDefaultValue)) {
|
||||||
acc[name] = 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;
|
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue !== null) {
|
if (defaultValue !== null) {
|
||||||
if (!withNameKey) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
acc[name] = 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;
|
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);
|
return duplicateDefaultI18nFields(collection, i18nData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
// 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.
|
// custom preview templates, where the field object can't be passed in.
|
||||||
let field = fields && fields.find(f => f.get('name') === name);
|
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')) {
|
if (field.get('meta')) {
|
||||||
value = this.props.entry.getIn(['meta', field.get('name')]);
|
value = this.props.entry.getIn(['meta', field.get('name')]);
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,17 @@ function handleSummary(summary, entry, label, item) {
|
|||||||
return stringTemplate.compileStringTemplate(summary, null, '', data);
|
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 {
|
export default class ListControl extends React.Component {
|
||||||
validations = [];
|
validations = [];
|
||||||
|
|
||||||
@ -386,6 +397,9 @@ export default class ListControl extends React.Component {
|
|||||||
const valueType = this.getValueType();
|
const valueType = this.getValueType();
|
||||||
switch (valueType) {
|
switch (valueType) {
|
||||||
case valueTypes.MIXED: {
|
case valueTypes.MIXED: {
|
||||||
|
if (!validateItem(field, item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const itemType = getTypedFieldForValue(field, item);
|
const itemType = getTypedFieldForValue(field, item);
|
||||||
const label = itemType.get('label', itemType.get('name'));
|
const label = itemType.get('label', itemType.get('name'));
|
||||||
// each type can have its own summary, but default to the list summary if exists
|
// 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;
|
return labelReturn;
|
||||||
}
|
}
|
||||||
case valueTypes.MULTIPLE: {
|
case valueTypes.MULTIPLE: {
|
||||||
|
if (!validateItem(field, item)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const multiFields = field.get('fields');
|
const multiFields = field.get('fields');
|
||||||
const labelField = multiFields && multiFields.first();
|
const labelField = multiFields && multiFields.first();
|
||||||
const value = item.get(labelField.get('name'));
|
const value = item.get(labelField.get('name'));
|
||||||
|
@ -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
|
* **Data type:** list of widget values
|
||||||
* **Options:**
|
* **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
|
* `allow_add`: `false` hides the button to add additional items
|
||||||
* `collapsed`: when `true`, the entries collapse by default
|
* `collapsed`: when `true`, the entries collapse by default
|
||||||
* `summary`: specify the label displayed on collapsed entries
|
* `summary`: specify the label displayed on collapsed entries
|
||||||
@ -20,6 +23,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
* `max`: maximum number of items in the list
|
* `max`: maximum number of items in the list
|
||||||
* `min`: minimum 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
|
* `add_to_top`: when `true`, new entries will be added to the top of the list
|
||||||
|
|
||||||
* **Example** (`field`/`fields` not specified):
|
* **Example** (`field`/`fields` not specified):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -28,6 +32,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
widget: "list"
|
widget: "list"
|
||||||
default: ["news"]
|
default: ["news"]
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (`allow_add` marked `false`):
|
* **Example** (`allow_add` marked `false`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -37,6 +42,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
allow_add: false
|
allow_add: false
|
||||||
default: ["news"]
|
default: ["news"]
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (with `field`):
|
* **Example** (with `field`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -46,6 +52,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
summary: '{{fields.image}}'
|
summary: '{{fields.image}}'
|
||||||
field: {label: Image, name: image, widget: image}
|
field: {label: Image, name: image, widget: image}
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (with `fields`):
|
* **Example** (with `fields`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -62,6 +69,21 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
- {label: Name, name: name, widget: string, default: "Emmet"}
|
- {label: Name, name: name, widget: string, default: "Emmet"}
|
||||||
- {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"}
|
- {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`):
|
* **Example** (`collapsed` marked `false`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -73,6 +95,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
|
- {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
|
||||||
- {label: Author, name: author, widget: string }
|
- {label: Author, name: author, widget: string }
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (`minimize_collapsed` marked `true`):
|
* **Example** (`minimize_collapsed` marked `true`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -86,6 +109,7 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (with `max` & `min`):
|
* **Example** (with `max` & `min`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- label: "Tags"
|
- label: "Tags"
|
||||||
name: "tags"
|
name: "tags"
|
||||||
@ -94,7 +118,9 @@ The list widget allows you to create a repeatable item in the UI which saves as
|
|||||||
min: 1
|
min: 1
|
||||||
default: ["news"]
|
default: ["news"]
|
||||||
```
|
```
|
||||||
|
|
||||||
* **Example** (`add_to_top` marked `true`):
|
* **Example** (`add_to_top` marked `true`):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- label: "Tags"
|
- label: "Tags"
|
||||||
name: "tags"
|
name: "tags"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user