Skip to content

Commit

Permalink
Merge branch 'nested-lists'
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbeier committed Feb 20, 2017
2 parents 51a1f34 + b5187f4 commit 87b98e2
Show file tree
Hide file tree
Showing 7 changed files with 449 additions and 6 deletions.
3 changes: 0 additions & 3 deletions admin/public/styles/keystone/field-types.less
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// Field Types
// ==============================




// Code
// ------------------------------

Expand Down
10 changes: 7 additions & 3 deletions fields/types/Type.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ function Field (list, path, options) {
this.label = options.label || utils.keyToLabel(this.path);
this.typeDescription = options.typeDescription || this.typeDescription || this.type;

this.list.automap(this);
if (!options._isNested) {
this.list.automap(this);
}

// Warn on required fields that aren't initial
if (this.options.required
Expand All @@ -78,10 +80,12 @@ function Field (list, path, options) {
}

// Add the field to the schema
this.addToSchema(this.list.schema);
this.addToSchema(options._isNested ? options._nestedSchema : this.list.schema);

// Add pre-save handler to the list if this field watches others
if (this.options.watch) {
if (options._isNested && this.options.watch) {
throw new Error('Nested fields do not support the `watch` option.');
} else if (this.options.watch) {
this.list.schema.pre('save', this.getPreSaveWatcher());
}

Expand Down
32 changes: 32 additions & 0 deletions fields/types/list/ListColumn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import ItemsTableCell from '../../components/ItemsTableCell';
import ItemsTableValue from '../../components/ItemsTableValue';
import { plural } from '../../../admin/client/utils/string';

var ListColumn = React.createClass({
displayName: 'ListColumn',
propTypes: {
col: React.PropTypes.object,
data: React.PropTypes.object,
},
getValue () {
var value = this.props.data.fields[this.props.col.path];
if (Array.isArray(value)) {
return plural(value.length, '* Value', '* Values');
} else {
return '';
}
},
render () {
const value = this.getValue();
return (
<ItemsTableCell>
<ItemsTableValue padded interior field={this.props.col.type}>
{value}
</ItemsTableValue>
</ItemsTableCell>
);
},
});

module.exports = ListColumn;
141 changes: 141 additions & 0 deletions fields/types/list/ListField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* eslint-disable react/jsx-no-bind */

import assign from 'object-assign';
import { css, StyleSheet } from 'aphrodite/no-important';
import React from 'react';
import Field from '../Field';
import Domify from 'react-domify';

import { Fields } from 'FieldTypes';
import { Button, GlyphButton } from '../../../admin/client/App/elemental';
import InvalidFieldType from '../../../admin/client/App/shared/InvalidFieldType';

let i = 0;
function generateId () {
return i++;
};

const ItemDom = ({ name, id, onRemove, children }) => (
<div style={{
borderTop: '2px solid #eee',
paddingTop: 15,
}}>
{name && <input type="hidden" name={name} value={id}/>}
{children}
<div style={{ textAlign: 'right', paddingBottom: 10 }}>
<Button size="xsmall" color="danger" onClick={onRemove}>
Remove
</Button>
</div>
</div>
);

module.exports = Field.create({
displayName: 'ListField',
statics: {
type: 'List',
},
propTypes: {
fields: React.PropTypes.object.isRequired,
label: React.PropTypes.string,
onChange: React.PropTypes.func.isRequired,
path: React.PropTypes.string.isRequired,
value: React.PropTypes.array,
},
addItem () {
const { path, value, onChange } = this.props;
onChange({
path,
value: [
...value,
{
id: generateId(),
_isNew: true,
},
],
});
},
removeItem (index) {
const { value: oldValue, path, onChange } = this.props;
const value = oldValue.slice(0, index).concat(oldValue.slice(index + 1));
onChange({ path, value });
},
handleFieldChange (index, event) {
const { value: oldValue, path, onChange } = this.props;
const head = oldValue.slice(0, index);
const item = {
...oldValue[index],
[event.path]: event.value,
};
const tail = oldValue.slice(index + 1);
const value = [...head, item, ...tail];
onChange({ path, value });
},
renderFieldsForItem (index, value) {
return Object.keys(this.props.fields).map((path) => {
const field = this.props.fields[path];
if (typeof Fields[field.type] !== 'function') {
return React.createElement(InvalidFieldType, { type: field.type, path: field.path, key: field.path });
}
const props = assign({}, field);
props.value = value[field.path];
props.values = value;
props.onChange = this.handleFieldChange.bind(this, index);
props.mode = 'edit';
props.inputNamePrefix = `${this.props.path}[${index}]`;
props.key = field.path;
// TODO ?
// if (props.dependsOn) {
// props.currentDependencies = {};
// Object.keys(props.dependsOn).forEach(dep => {
// props.currentDependencies[dep] = this.state.values[dep];
// });
// }
return React.createElement(Fields[field.type], props);
}, this);
},
renderItems () {
const { value = [], path } = this.props;
const onAdd = this.addItem;
return (
<div>
{value.map((value, index) => {
const { id, _isNew } = value;
const name = !_isNew && `${path}[${index}][id]`;
const onRemove = e => this.removeItem(index);

return (
<ItemDom key={id} {...{ id, name, onRemove }}>
{this.renderFieldsForItem(index, value)}
</ItemDom>
);
})}
<GlyphButton color="success" glyph="plus" position="left" onClick={onAdd}>
Add
</GlyphButton>
</div>
);
},
renderUI () {
const { label, value } = this.props;
return (
<div className={css(classes.container)}>
<h3 data-things="whatever">{label}</h3>
{this.shouldRenderField() ? (
this.renderItems()
) : (
<Domify value={value} />
)}
{this.renderNote()}
</div>
);
},
});

const classes = StyleSheet.create({
container: {
marginTop: '2em',
paddingLeft: '2em',
boxShadow: '-3px 0 0 rgba(0, 0, 0, 0.1)',
},
});
43 changes: 43 additions & 0 deletions fields/types/list/ListFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
TODO: Not sure what this should look like yet,
it's currently basically a copy of the Boolean filter
so that the Admin UI builds successfully
*/

import React from 'react';
import { SegmentedControl } from 'elemental';

const VALUE_OPTIONS = [
{ label: 'Has Values', value: true },
{ label: 'Is Empty', value: false },
];

function getDefaultValue () {
return {
value: true,
};
}

var ListFilter = React.createClass({
propTypes: {
filter: React.PropTypes.shape({
value: React.PropTypes.bool,
}),
},
statics: {
getDefaultValue: getDefaultValue,
},
getDefaultProps () {
return {
filter: getDefaultValue(),
};
},
updateValue (value) {
this.props.onChange({ value });
},
render () {
return <SegmentedControl equalWidthSegments options={VALUE_OPTIONS} value={this.props.filter.value} onChange={this.updateValue} />;
},
});

module.exports = ListFilter;
Loading

0 comments on commit 87b98e2

Please sign in to comment.