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

WIP: Dynamic Widget #498

Closed
wants to merge 12 commits into from
13 changes: 13 additions & 0 deletions example/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ collections: # A list of collections the CMS should be able to edit
folder: "_sink"
create: true
fields:
- name: dynamic_block
label: Dynamic Block
widget: list
fields:
- label: "Dynamic"
name: "dynamic"
widget: "dynamic"
dynamicWidgets:
- string
- text
- markdown
- slider
- label: "Related Post"
name: "post"
widget: "relationKitchenSinkPost"
Expand Down Expand Up @@ -162,3 +174,4 @@ collections: # A list of collections the CMS should be able to edit
- {label: "Image", name: "image", widget: "image"}
- {label: "File", name: "file", widget: "file"}
- {label: "Select", name: "select", widget: "select", options: ["a", "b", "c"]}

6 changes: 6 additions & 0 deletions src/components/Widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import ObjectPreview from './Widgets/ObjectPreview';
import RelationControl from './Widgets/RelationControl';
import RelationPreview from './Widgets/RelationPreview';
import BooleanControl from './Widgets/BooleanControl';
import DynamicControl from './Widgets/DynamicControl';
import DynamicPreview from './Widgets/DynamicPreview';
import SliderControl from './Widgets/SliderControl';
import SliderPreview from './Widgets/SliderPreview';


registry.registerWidget('string', StringControl, StringPreview);
Expand All @@ -41,6 +45,8 @@ registry.registerWidget('select', SelectControl, SelectPreview);
registry.registerWidget('object', ObjectControl, ObjectPreview);
registry.registerWidget('relation', RelationControl, RelationPreview);
registry.registerWidget('boolean', BooleanControl);
registry.registerWidget('dynamic', DynamicControl, DynamicPreview);
registry.registerWidget('slider', SliderControl, SliderPreview);
registry.registerWidget('unknown', UnknownControl, UnknownPreview);

export function resolveWidget(name) { // eslint-disable-line
Expand Down
118 changes: 118 additions & 0 deletions src/components/Widgets/DynamicControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Map } from 'immutable';
import { resolveWidget } from '../Widgets';
import controlStyles from '../ControlPanel/ControlPane.css';

export default class DynamicControl extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
onRemoveAsset: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
value: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
PropTypes.bool,
]),
field: PropTypes.object,
forID: PropTypes.string,
dynamicWidgets: PropTypes.object
};

constructor(props) {
super(props);
const fieldValue = this.props.value && Map.isMap(this.props.value) ?
this.props.value.get(this.props.field.get('name')) :
this.props.value;

if (!fieldValue) {
this.state = {
widget: null,
};
} else {
this.state = {
widget: resolveWidget(fieldValue),
};
}
}

handleChange = (e) => {
this.props.onChange(Map().set(e.target.id, e.target.value));

if (!e.target.value) {
this.setState({
widget: null,
});
} else {
this.setState({
widget: resolveWidget(e.target.value),
});
}
};

render() {
const { field, value, forID, onChange, onAddAsset, onRemoveAsset, getAsset, dynamicWidgets } = this.props;
const { widget } = this.state;

const name = field.get('name');
const selectedName = `${ field.get('name') }_selected`;

const fieldValue = value && Map.isMap(value) ?
value.get(name) :
value;
const fieldValueSelected = value && Map.isMap(value) ?
value.get(selectedName) :
value;

let options = field.get('dynamicWidgets').map((option) => {
if (typeof option === 'string') {
return { label: option, value: option };
}
return option;
});

options = options.insert(0, {
label: 'Please Select',
value: '',
});

return (
<div>
<div>
<select id={forID} value={fieldValue || ''} onChange={this.handleChange}>
{options.map((option, idx) => <option key={idx} value={option.value}>
{option.label}
</option>)}
</select>
</div>
<div>
{
widget ?
<div className={controlStyles.widget} key={selectedName}>
<div className={controlStyles.control} key={selectedName}>
<label className={controlStyles.label} htmlFor={selectedName}>{`${ field.get('label') } Data`}</label>
{
React.createElement(widget.control, {
id: selectedName,
field,
value: fieldValueSelected,
onChange: (val, metadata) => {
onChange((value || Map()).set(selectedName, val), metadata);
},
onAddAsset,
onRemoveAsset,
getAsset,
forID: selectedName,
})
}
</div>
</div>
:
''
}
</div>
</div>
);
}
}
13 changes: 13 additions & 0 deletions src/components/Widgets/DynamicPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { PropTypes, Component } from 'react';
import { resolveWidget } from '../Widgets';
import previewStyle from './defaultPreviewStyle';

const DynamicPreview = ({ field }) => (
<div style={previewStyle}>{(field && field.get('fields')) || null}</div>
);

DynamicPreview.propTypes = {
field: PropTypes.node,
};

export default DynamicPreview;
65 changes: 65 additions & 0 deletions src/components/Widgets/SliderControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { Component, PropTypes } from 'react';
import { Map, List } from 'immutable';
import { resolveWidget } from '../Widgets';
import controlStyles from '../ControlPanel/ControlPane.css';
import styles from './ObjectControl.css';

export default class SliderControl extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
onAddAsset: PropTypes.func.isRequired,
onRemoveAsset: PropTypes.func.isRequired,
getAsset: PropTypes.func.isRequired,
value: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
PropTypes.bool,
]),
field: PropTypes.object,
forID: PropTypes.string,
className: PropTypes.string,
};

controlFor(field) {
const { onAddAsset, onRemoveAsset, getAsset, value, onChange } = this.props;
if (field.get('widget') === 'hidden') {
return null;
}
const widget = resolveWidget(field.get('widget') || 'string');
const fieldValue = value && Map.isMap(value) ? value.get(field.get('name')) : value;

return (<div className={controlStyles.widget} key={field.get('name')}>
<div className={controlStyles.control} key={field.get('name')}>
<label className={controlStyles.label} htmlFor={field.get('name')}>{field.get('label')}</label>
{
React.createElement(widget.control, {
id: field.get('name'),
field,
value: fieldValue,
onChange: (val, metadata) => {
onChange((value || Map()).set(field.get('name'), val), metadata);
},
onAddAsset,
onRemoveAsset,
getAsset,
forID: field.get('name'),
})
}
</div>
</div>);
}

render() {
const { field, forID } = this.props;
const className = this.props.className || '';

const sliderFields = List([
Map({ label: "String", name: "string", widget: "string" }),
Map({ label: "String2", name: "string2", widget: "string" }),
]);

return (<div id={forID} className={`${ className } ${ styles.root }`}>
{sliderFields.map(f => this.controlFor(f))}
</div>);
}
}
13 changes: 13 additions & 0 deletions src/components/Widgets/SliderPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { PropTypes, Component } from 'react';
import { resolveWidget } from '../Widgets';
import previewStyle from './defaultPreviewStyle';

const SliderPreview = ({ field }) => (
<div style={previewStyle}>{(field && field.get('fields')) || null}</div>
);

SliderPreview.propTypes = {
field: PropTypes.node,
};

export default SliderPreview;