keystone
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
177 lines (162 loc) • 4.18 kB
JavaScript
import classnames from 'classnames';
import evalDependsOn from '../utils/evalDependsOn.js';
import React from 'react';
import { findDOMNode } from 'react-dom';
import { FormField, FormInput, FormNote } from '../../admin/client/App/elemental';
import blacklist from 'blacklist';
import CollapsedFieldLabel from '../components/CollapsedFieldLabel';
function isObject (arg) {
return Object.prototype.toString.call(arg) === '[object Object]';
}
function validateSpec (spec) {
if (!spec) spec = {};
if (!isObject(spec.supports)) {
spec.supports = {};
}
if (!spec.focusTargetRef) {
spec.focusTargetRef = 'focusTarget';
}
return spec;
}
var Base = module.exports.Base = {
getInitialState () {
return {};
},
getDefaultProps () {
return {
adminPath: Keystone.adminPath,
inputProps: {},
labelProps: {},
valueProps: {},
size: 'full',
};
},
getInputName (path) {
// This correctly creates the path for field inputs, and supports the
// inputNamePrefix prop that is required for nested fields to work
return this.props.inputNamePrefix
? `${this.props.inputNamePrefix}[${path}]`
: path;
},
valueChanged (event) {
this.props.onChange({
path: this.props.path,
value: event.target.value,
});
},
shouldCollapse () {
return this.props.collapse && !this.props.value;
},
shouldRenderField () {
if (this.props.mode === 'create') return true;
return !this.props.noedit;
},
focus () {
if (!this.refs[this.spec.focusTargetRef]) return;
findDOMNode(this.refs[this.spec.focusTargetRef]).focus();
},
renderNote () {
if (!this.props.note) return null;
return <FormNote html={this.props.note} />;
},
renderField () {
const { autoFocus, value, inputProps } = this.props;
return (
<FormInput {...{
...inputProps,
autoFocus,
autoComplete: 'off',
name: this.getInputName(this.props.path),
onChange: this.valueChanged,
ref: 'focusTarget',
value,
}} />
);
},
renderValue () {
return <FormInput noedit>{this.props.value}</FormInput>;
},
renderUI () {
var wrapperClassName = classnames(
'field-type-' + this.props.type,
this.props.className,
{ 'field-monospace': this.props.monospace }
);
return (
<FormField htmlFor={this.props.path} label={this.props.label} className={wrapperClassName} cropLabel>
<div className={'FormField__inner field-size-' + this.props.size}>
{this.shouldRenderField() ? this.renderField() : this.renderValue()}
</div>
{this.renderNote()}
</FormField>
);
},
};
var Mixins = module.exports.Mixins = {
Collapse: {
componentWillMount () {
this.setState({
isCollapsed: this.shouldCollapse(),
});
},
componentDidUpdate (prevProps, prevState) {
if (prevState.isCollapsed && !this.state.isCollapsed) {
this.focus();
}
},
uncollapse () {
this.setState({
isCollapsed: false,
});
},
renderCollapse () {
if (!this.shouldRenderField()) return null;
return (
<FormField>
<CollapsedFieldLabel onClick={this.uncollapse}>+ Add {this.props.label.toLowerCase()}</CollapsedFieldLabel>
</FormField>
);
},
},
};
module.exports.create = function (spec) {
spec = validateSpec(spec);
var field = {
spec: spec,
displayName: spec.displayName,
mixins: [Mixins.Collapse],
statics: {
getDefaultValue: function (field) {
return typeof field.defaultValue !== 'undefined' ? field.defaultValue : '';
},
},
render () {
if (!evalDependsOn(this.props.dependsOn, this.props.values)) {
return null;
}
if (this.state.isCollapsed) {
return this.renderCollapse();
}
return this.renderUI();
},
};
if (spec.statics) {
Object.assign(field.statics, spec.statics);
}
var excludeBaseMethods = {};
if (spec.mixins) {
spec.mixins.forEach(function (mixin) {
Object.keys(mixin).forEach(function (name) {
if (Base[name]) {
excludeBaseMethods[name] = true;
}
});
});
}
Object.assign(field, blacklist(Base, excludeBaseMethods));
Object.assign(field, blacklist(spec, 'mixins', 'statics'));
if (Array.isArray(spec.mixins)) {
field.mixins = field.mixins.concat(spec.mixins);
}
return React.createClass(field);
};