UNPKG

keystone

Version:

Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose

177 lines (162 loc) 4.18 kB
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); };