From 9dca9f912ad667a2d77be52e68c2b08bda8b9a43 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 6 Mar 2017 12:27:41 -0500 Subject: [PATCH 1/5] add widgetsFor helper This allows individual widgets to be accessed from preview templates that handle lists --- example/index.html | 16 +++++++++++++++ src/components/PreviewPane/PreviewPane.js | 25 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/example/index.html b/example/index.html index 73c98009..6737c292 100644 --- a/example/index.html +++ b/example/index.html @@ -110,8 +110,24 @@ } }); + var AuthorsPreview = createClass({ + render: function() { + return h('div', {}, + h('h1', {}, 'Authors'), + this.props.widgetsFor('authors').map(function(author, index) { + return h('div', {key: index}, + h('hr', {}), + h('strong', {}, author.getIn(['data', 'name'])), + author.getIn(['widgets', 'description']) + ); + }) + ); + } + }); + CMS.registerPreviewTemplate("posts", PostPreview); CMS.registerPreviewTemplate("general", GeneralPreview); + CMS.registerPreviewTemplate("authors", AuthorsPreview); CMS.registerPreviewStyle("/example.css"); CMS.registerEditorComponent({ id: "youtube", diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index 07903ab1..04cf3c93 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -1,5 +1,6 @@ import React, { PropTypes } from 'react'; import ReactDOM from 'react-dom'; +import { Map } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { ScrollSyncPane } from '../ScrollSync'; import registry from '../../lib/registry'; @@ -50,6 +51,28 @@ export default class PreviewPane extends React.Component { }); }; + widgetsFor = (name) => { + const { fields, entry, getAsset } = this.props; + const field = fields && fields.find(f => f.get('name') === name); + const nestedFields = field && field.get('fields'); + const values = entry.getIn(['data', field.get('name')]); + + const widgetFor = (field, value) => { + const widget = resolveWidget(field.get('widget')); + return (
{React.createElement(widget.preview, { + key: field.get('name'), + value: value && value.get(field.get('name')), + field, + getAsset, + })}
); + }; + + return values ? values.map((value, index) => { + const widgets = nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, value)])); + return Map({ data: value, widgets }); + }) : null; + }; + handleIframeRef = (ref) => { if (ref) { registry.getPreviewStyles().forEach((style) => { @@ -80,7 +103,9 @@ export default class PreviewPane extends React.Component { const previewProps = { ...this.props, widgetFor: this.widgetFor, + widgetsFor: this.widgetsFor, }; + // We need to use this API in order to pass context to the iframe ReactDOM.unstable_renderSubtreeIntoContainer( this, From 9bc80ed5e4cc9d11483de07ea94f2e3e2df0ad6e Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 6 Mar 2017 13:28:11 -0500 Subject: [PATCH 2/5] add object support to widgetsFor --- example/index.html | 4 ++-- src/components/PreviewPane/PreviewPane.js | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/example/index.html b/example/index.html index 6737c292..f3e5affb 100644 --- a/example/index.html +++ b/example/index.html @@ -98,10 +98,10 @@ h('h1', {}, title), h('dl', {}, h('dt', {}, 'Posts on Frontpage'), - h('dd', {}, posts && posts.get('front_limit') || '0'), + h('dd', {}, this.props.widgetsFor('posts').getIn(['widgets', 'front_limit']) || 0), h('dt', {}, 'Default Author'), - h('dd', {}, posts && posts.get('author') || 'None'), + h('dd', {}, this.props.widgetsFor('posts').getIn(['data', 'front_limit']) || 'None'), h('dt', {}, 'Default Thumbnail'), h('dd', {}, thumb && h('img', {src: this.props.getAsset(thumb).toString()})) diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index 04cf3c93..b7da6e36 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -1,6 +1,6 @@ import React, { PropTypes } from 'react'; import ReactDOM from 'react-dom'; -import { Map } from 'immutable'; +import { List, Map } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { ScrollSyncPane } from '../ScrollSync'; import registry from '../../lib/registry'; @@ -55,22 +55,29 @@ export default class PreviewPane extends React.Component { const { fields, entry, getAsset } = this.props; const field = fields && fields.find(f => f.get('name') === name); const nestedFields = field && field.get('fields'); - const values = entry.getIn(['data', field.get('name')]); + const value = entry.getIn(['data', field.get('name')]); const widgetFor = (field, value) => { const widget = resolveWidget(field.get('widget')); return (
{React.createElement(widget.preview, { key: field.get('name'), - value: value && value.get(field.get('name')), + value: value && List.isList(value) ? value.get(field.get('name')) : value, field, getAsset, })}
); }; - return values ? values.map((value, index) => { - const widgets = nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, value)])); - return Map({ data: value, widgets }); - }) : null; + if (List.isList(value)) { + return value.map((val, index) => { + const widgets = nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, val)])); + return Map({ data: val, widgets }); + }); + } + + return Map({ + data: value, + widgets: nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, value)])), + }); }; handleIframeRef = (ref) => { From e48221a04fbea3565beb912b94f8c5b2a7b469c2 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 6 Mar 2017 15:22:40 -0500 Subject: [PATCH 3/5] add shared widget getter for widgetFor and widgetsFor --- example/index.html | 2 +- src/components/PreviewPane/PreviewPane.js | 44 ++++++++++------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/example/index.html b/example/index.html index f3e5affb..6ab62374 100644 --- a/example/index.html +++ b/example/index.html @@ -101,7 +101,7 @@ h('dd', {}, this.props.widgetsFor('posts').getIn(['widgets', 'front_limit']) || 0), h('dt', {}, 'Default Author'), - h('dd', {}, this.props.widgetsFor('posts').getIn(['data', 'front_limit']) || 'None'), + h('dd', {}, this.props.widgetsFor('posts').getIn(['data', 'author']) || 'None'), h('dt', {}, 'Default Thumbnail'), h('dd', {}, thumb && h('img', {src: this.props.getAsset(thumb).toString()})) diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index b7da6e36..33171a9e 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -29,54 +29,48 @@ export default class PreviewPane extends React.Component { if (authorField) this.inferedFields[authorField] = INFERABLE_FIELDS.author; } + _getWidget = (field, value, props) => { + const { fieldsMetaData, getAsset } = props; + const widget = resolveWidget(field.get('widget')); + return React.createElement(widget.preview, { + field, + key: field.get('name'), + value: value && Map.isMap(value) ? value.get(field.get('name')) : value, + metadata: fieldsMetaData && fieldsMetaData.get(field.get('name')), + getAsset, + }); + }; + widgetFor = (name) => { - const { fields, entry, fieldsMetaData, getAsset } = this.props; + const { fields, entry } = this.props; const field = fields.find(f => f.get('name') === name); let value = entry.getIn(['data', field.get('name')]); - const metadata = fieldsMetaData.get(field.get('name')); const labelledWidgets = ['string', 'text', 'number']; if (Object.keys(this.inferedFields).indexOf(name) !== -1) { value = this.inferedFields[name].defaultPreview(value); } else if (value && labelledWidgets.indexOf(field.get('widget')) !== -1 && value.toString().length < 50) { value =
{field.get('label')}: {value}
; } - if (!value) return null; - const widget = resolveWidget(field.get('widget')); - return React.createElement(widget.preview, { - key: field.get('name'), - value, - field, - metadata, - getAsset, - }); + + return value ? this._getWidget(field, value, this.props) : null; }; widgetsFor = (name) => { - const { fields, entry, getAsset } = this.props; - const field = fields && fields.find(f => f.get('name') === name); + const { fields, entry } = this.props; + const field = fields.find(f => f.get('name') === name); const nestedFields = field && field.get('fields'); const value = entry.getIn(['data', field.get('name')]); - const widgetFor = (field, value) => { - const widget = resolveWidget(field.get('widget')); - return (
{React.createElement(widget.preview, { - key: field.get('name'), - value: value && List.isList(value) ? value.get(field.get('name')) : value, - field, - getAsset, - })}
); - }; - if (List.isList(value)) { return value.map((val, index) => { - const widgets = nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, val)])); + const widgets = nestedFields && Map(nestedFields.map((f, i) => [f.get('name'),
{this._getWidget(f, val, this.props)}
])); return Map({ data: val, widgets }); }); } return Map({ data: value, - widgets: nestedFields && Map(nestedFields.map(f => [f.get('name'), widgetFor(f, value)])), + widgets: nestedFields && Map(nestedFields.map(f => [f.get('name'), this._getWidget(f, value, this.props)])), }); }; From 03714f8d1bd83eab92eaa45d6cb8341d9cb9bba3 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 6 Mar 2017 17:57:43 -0500 Subject: [PATCH 4/5] add documentation for widgetsFor helper --- docs/extending.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/extending.md b/docs/extending.md index 2fb44d23..82d4d57d 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -37,9 +37,10 @@ Registers a template for a collection. **React Component Props:** * collection: The name of the collection which this preview component will be used for. -* react_component: A React component that renders the collection data. Three props will be passed to your component during render: +* react_component: A React component that renders the collection data. Four props will be passed to your component during render: * entry: Immutable collection containing the entry data. * widgetFor: Returns the appropriate widget preview component for a given field. + * [widgetsFor](#lists-and-objects): Returns an array of objects with widgets and associated field data. For use with list and object type entries. * getAsset: Returns the correct filePath or in-memory preview for uploaded images. **Example:** @@ -63,6 +64,95 @@ CMS.registerPreviewTemplate("posts", PostPreview); ``` +### Lists and Objects +The API for accessing the individual fields of list and object type entries is similar to the API +for accessing fields in standard entries, but there are a few key differences. Access to these +nested fields is facilitated through the `widgetsFor` function, which is passed to the preview +template component during render. + +**Note**: as is often the case with the Netlify CMS API, arrays and objects are created with +Immutable.js. If some of the methods that we use are unfamiliar, such as `getIn`, check out +[their docs](https://facebook.github.io/immutable-js/docs/#/) to get a better understanding. + +**List Example:** + +```html + +``` + +**Object Example:** + +```html + +``` + ### Accessing Metadata Preview Components also receive an additional prop: `fieldsMetaData`. It contains aditional information (besides the plain plain textual value of each field) that can be useful for preview purposes. From ff8b420777fdbe1cf015c911fcdfa85d9b79d0d9 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Mon, 6 Mar 2017 19:38:21 -0500 Subject: [PATCH 5/5] remove leading underscore from getWidget method --- src/components/PreviewPane/PreviewPane.js | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/PreviewPane/PreviewPane.js b/src/components/PreviewPane/PreviewPane.js index 33171a9e..07c1fd72 100644 --- a/src/components/PreviewPane/PreviewPane.js +++ b/src/components/PreviewPane/PreviewPane.js @@ -16,6 +16,18 @@ export default class PreviewPane extends React.Component { this.renderPreview(); } + getWidget = (field, value, props) => { + const { fieldsMetaData, getAsset } = props; + const widget = resolveWidget(field.get('widget')); + return React.createElement(widget.preview, { + field, + key: field.get('name'), + value: value && Map.isMap(value) ? value.get(field.get('name')) : value, + metadata: fieldsMetaData && fieldsMetaData.get(field.get('name')), + getAsset, + }); + }; + inferedFields = {}; inferFields() { @@ -29,18 +41,6 @@ export default class PreviewPane extends React.Component { if (authorField) this.inferedFields[authorField] = INFERABLE_FIELDS.author; } - _getWidget = (field, value, props) => { - const { fieldsMetaData, getAsset } = props; - const widget = resolveWidget(field.get('widget')); - return React.createElement(widget.preview, { - field, - key: field.get('name'), - value: value && Map.isMap(value) ? value.get(field.get('name')) : value, - metadata: fieldsMetaData && fieldsMetaData.get(field.get('name')), - getAsset, - }); - }; - widgetFor = (name) => { const { fields, entry } = this.props; const field = fields.find(f => f.get('name') === name); @@ -52,7 +52,7 @@ export default class PreviewPane extends React.Component { value =
{field.get('label')}: {value}
; } - return value ? this._getWidget(field, value, this.props) : null; + return value ? this.getWidget(field, value, this.props) : null; }; widgetsFor = (name) => { @@ -63,14 +63,14 @@ export default class PreviewPane extends React.Component { if (List.isList(value)) { return value.map((val, index) => { - const widgets = nestedFields && Map(nestedFields.map((f, i) => [f.get('name'),
{this._getWidget(f, val, this.props)}
])); + const widgets = nestedFields && Map(nestedFields.map((f, i) => [f.get('name'),
{this.getWidget(f, val, this.props)}
])); return Map({ data: val, widgets }); }); } return Map({ data: value, - widgets: nestedFields && Map(nestedFields.map(f => [f.get('name'), this._getWidget(f, value, this.props)])), + widgets: nestedFields && Map(nestedFields.map(f => [f.get('name'), this.getWidget(f, value, this.props)])), }); };