Merge pull request #470 from netlify/relation-preview-improvements

Relation preview improvements
This commit is contained in:
Shawn Erquhart 2017-07-10 15:46:20 -04:00 committed by GitHub
commit 1d08f1a33b
8 changed files with 122 additions and 33 deletions

View File

@ -16,11 +16,16 @@ Although possible, it may be cumbersome or even impractical to add a React build
Register a custom widget.
```js
CMS.registerWidget(field, control, \[preview\])
CMS.registerWidget(name, control, \[preview\])
```
**Params:**
Param | Type | Description
--- | --- | ---
`name` | string | Widget name, allows this widget to be used via the field `widget` property in config
`control` | React.Component \| string | <ul><li>React component that renders the control, receives the following props: <ul><li>**value:** Current field value</li><li>**onChange**: Callback function to update the field value</li></ul></li><li>Name of a registered widget whose control should be used (includes built in widgets).</li></ul>
[`preview`] | React.Component, optional | Renders the widget preview, receives the following props: <ul><li>**value:** Current preview value</li><li>**field:** Immutable map of current field configuration</li><li>**metadata:** Immutable map of any available metadata for the current field</li><li>**getAsset:** Function for retrieving an asset url for image/file fields</li><li>**entry:** Immutable Map of all entry data</li><li>**fieldsMetaData:** Immutable map of metadata from all fields.</li></ul>
* **field:** The field type which this widget will be used for.
* **control:** A React component that renders the editing interface for this field. Two props will be passed:
* **value:** The current value for this field.
@ -43,7 +48,17 @@ var CategoriesControl = createClass({
}
});
CMS.registerWidget('categories', CategoriesControl);
var CategoriesPreview = createClass({
render: function() {
return h('ul', {},
this.props.value.map(function(val, index) {
return h('li', {key: index}, val);
})
);
}
});
CMS.registerWidget('categories', CategoriesControl, CategoriesPreview);
</script>
```

View File

@ -87,19 +87,7 @@ You point to where the files are stored, and specify the fields that define them
### Widgets
Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in widgets, including:
Widget | UI | Data Type
--- | --- | ---
`string` | text input | string
`text` | text area input | plain text, multiline input
`number` | text input with `+` and `-` buttons | number
`markdown` | rich text editor with raw option | markdown-formatted string
`datetime` | date picker widget | ISO date string
`image` | file picker widget with drag-and-drop | file path saved as string, image uploaded to media folder
`hidden` | No UI | Hidden element, typically only useful with a `default` attribute
Were always adding new widgets, and you can also create your own.
Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in [widgets](/docs/widgets).
## Customization

View File

@ -4,19 +4,60 @@
Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in widgets, including:
| Name | UI | Data Type |
| -------- | --------------------------------- | --------------------------------------------------|
| `string` | text input | string |
| `boolean` | toggle switch | boolean |
| `text` | textarea input | string (multiline) |
| `number` | number input | number |
| `markdown` | rich text editor | string (markdown) |
| `datetime` | date picker | string (ISO date) |
| `select` | select input (dropdown) | string |
| `image` | file picker w/ drag and drop | image file |
| `file` | file picker w/ drag and drop | file |
| `hidden` | none | string |
| `object` | group of other widgets | Immutable Map containing field values |
| `list` | repeatable group of other widgets | Immutable List of objects containing field values |
| Name | UI | Data Type |
| -------- | ---------------------------------- | ---------------------------------------------------|
| `string` | text input | string |
| `boolean` | toggle switch | boolean |
| `text` | textarea input | string (multiline) |
| `number` | number input | number |
| `markdown` | rich text editor | string (markdown) |
| `datetime` | date picker | string (ISO date) |
| `select` | select input (dropdown) | string |
| `image` | file picker w/ drag and drop | image file |
| `file` | file picker w/ drag and drop | file |
| `hidden` | none | string |
| `object` | group of other widgets | Immutable Map containing field values |
| `list` | repeatable group of other widgets | Immutable List of objects containing field values |
| `relation` | text input w/ suggestions dropdown | value of `valueField` in related entry (see below) |
Were always adding new widgets, and you can also [create your own](/docs/extending)!
### Relation Widget
The relation widget allows you to reference an existing entry from within the entry you're editing. It provides a search input with a list of entries from the collection you're referencing, and the list automatically updates with matched entries based on what you've typed.
The following field configuration properties are specific to fields using the relation widget:
Property | Accepted Values | Description
--- | --- | ---
`collection` | string | name of the collection being referenced
`searchFields` | list | one or more names of fields in the referenced colleciton to search for the typed value
`valueField` | string | name a field from the referenced collection whose value will be stored for the relation
`name` | text input | string
Let's say we have a "posts" collection and an "authors" collection, and we want to select an author for each post - our config might look something like this:
```yaml
collections:
- name: authors
label: Authors
folder: "authors"
create: true
fields:
- {name: name, label: Name}
- {name: twitterHandle, label: "Twitter Handle"}
- {name: bio, label: Bio, widget: text}
- name: posts
label: Posts
folder: "posts"
create: true
fields:
- {name: title, label: Title}
- {name: body, label: Body, widget: markdown}
- name: author
label: Author
widget: relation
collection: authors
searchFields: [name, twitterHandle]
valueField: name
```

View File

@ -62,6 +62,12 @@ collections: # A list of collections the CMS should be able to edit
folder: "_sink"
create: true
fields:
- label: "Related Post"
name: "post"
widget: "relationKitchenSinkPost"
collection: "posts"
searchFields: ["title", "body"]
valueField: "title"
- {label: "Title", name: "title", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean", default: true}
- {label: "Text", name: "text", widget: "text"}
@ -77,6 +83,12 @@ collections: # A list of collections the CMS should be able to edit
name: "object"
widget: "object"
fields:
- label: "Related Post"
name: "post"
widget: "relationKitchenSinkPost"
collection: "posts"
searchFields: ["title", "body"]
valueField: "title"
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean", default: false}
- {label: "Text", name: "text", widget: "text"}
@ -119,6 +131,12 @@ collections: # A list of collections the CMS should be able to edit
name: "list"
widget: "list"
fields:
- label: "Related Post"
name: "post"
widget: "relationKitchenSinkPost"
collection: "posts"
searchFields: ["title", "body"]
valueField: "title"
- {label: "String", name: "string", widget: "string"}
- {label: "Boolean", name: "boolean", widget: "boolean"}
- {label: "Text", name: "text", widget: "text"}

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,7 @@ import styles from './PreviewPane.css';
export default class PreviewPane extends React.Component {
getWidget = (field, value, props) => {
const { fieldsMetaData, getAsset } = props;
const { fieldsMetaData, getAsset, entry } = props;
const widget = resolveWidget(field.get('widget'));
return !widget.preview ? null : React.createElement(widget.preview, {
@ -22,6 +22,8 @@ export default class PreviewPane extends React.Component {
value: value && Map.isMap(value) ? value.get(field.get('name')) : value,
metadata: fieldsMetaData && fieldsMetaData.get(field.get('name')),
getAsset,
entry,
fieldsMetaData,
});
};

View File

@ -69,7 +69,7 @@ class RelationControl extends Component {
const collection = field.get('collection');
const searchFields = field.get('searchFields').toJS();
this.props.query(this.controlID, collection, searchFields, value);
}, 100);
}, 500);
onSuggestionsClearRequested = () => {
this.props.clearSearch();

View File

@ -22,7 +22,10 @@ export default {
return _registry.previewStyles;
},
registerWidget(name, control, preview) {
_registry.widgets[name] = { control, preview };
// A registered widget control can be reused by a new widget, allowing
// multiple copies with different previews.
const newControl = typeof control === 'string' ? _registry.widgets[control].control : control;
_registry.widgets[name] = { control: newControl, preview };
},
getWidget(name) {
return _registry.widgets[name];