diff --git a/demo/collections/Post.js b/demo/collections/Post.js index caca4c88cb..20075be5bc 100644 --- a/demo/collections/Post.js +++ b/demo/collections/Post.js @@ -31,6 +31,12 @@ module.exports = { required: true, localized: true, }, + { + name: 'priority', + label: 'Priority', + type: 'number', + required: true, + }, ], timestamps: true, }; diff --git a/src/client/components/forms/field-types/Number/index.js b/src/client/components/forms/field-types/Number/index.js new file mode 100644 index 0000000000..221053a3ef --- /dev/null +++ b/src/client/components/forms/field-types/Number/index.js @@ -0,0 +1,98 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import useFieldType from '../../useFieldType'; +import Label from '../../Label'; +import Error from '../../Error'; + +import './index.scss'; + +const defaultError = 'Please fill in the field'; +const defaultValidate = value => value.length > 0; + +const Number = (props) => { + const { + name, + required, + defaultValue, + validate, + style, + width, + errorMessage, + label, + placeholder, + } = props; + + const { + value, + showError, + onFieldChange, + formProcessing, + } = useFieldType({ + name, + required, + defaultValue, + validate, + }); + + const classes = [ + 'field-type', + 'text', + showError && 'error', + ].filter(Boolean).join(' '); + + const fieldWidth = width ? `${width}%` : undefined; + + return ( +
+ +
+ ); +}; + +Number.defaultProps = { + label: null, + required: false, + defaultValue: null, + placeholder: undefined, + validate: defaultValidate, + errorMessage: defaultError, + width: 100, + style: {}, +}; + +Number.propTypes = { + name: PropTypes.string.isRequired, + required: PropTypes.bool, + placeholder: PropTypes.string, + defaultValue: PropTypes.string, + validate: PropTypes.func, + errorMessage: PropTypes.string, + width: PropTypes.number, + style: PropTypes.shape({}), + label: PropTypes.string, +}; + +export default Number; diff --git a/src/client/components/forms/field-types/Number/index.scss b/src/client/components/forms/field-types/Number/index.scss new file mode 100644 index 0000000000..d1bce7dfb5 --- /dev/null +++ b/src/client/components/forms/field-types/Number/index.scss @@ -0,0 +1,17 @@ +@import '~payload/client/scss/styles'; +@import '../shared'; + +.field-type.number { + margin-bottom: base(.5); + position: relative; + + input { + @include formInput; + } + + &.error { + input { + background-color: lighten($error, 20%); + } + } +} diff --git a/src/client/components/forms/field-types/index.js b/src/client/components/forms/field-types/index.js index 8ee214d7df..1a0abd7728 100644 --- a/src/client/components/forms/field-types/index.js +++ b/src/client/components/forms/field-types/index.js @@ -8,6 +8,7 @@ import password from './Password'; import repeater from './Repeater'; import textarea from './Textarea'; import select from './Select'; +import number from './Number'; export default { email, @@ -16,6 +17,7 @@ export default { text, relationship, // upload, + number, password, repeater, textarea, diff --git a/src/mongoose/buildQuery.js b/src/mongoose/buildQuery.js index 61de08ef15..4a0585b78b 100644 --- a/src/mongoose/buildQuery.js +++ b/src/mongoose/buildQuery.js @@ -30,59 +30,28 @@ class ParamParser { } async parse() { - // Construct searchParams - // let keys = Object.keys(this.rawParams); Object.keys(this.rawParams).forEach(async (key) => { const separatedParams = this.rawParams[key].match(/{\w+}(.[^{}]*)/g); - if (separatedParams === null) { - await this.parseParam(key, this.rawParams[key], this.model, this.locale); + await this.parseParam(key, this.rawParams[key]); } else { - for (let i = 0; i < separatedParams.length; ++i) { - await this.parseParam(key, separatedParams[i], this.model, this.locale); - } + await separatedParams.forEach(separatedParam => this.parseParam(key, separatedParam)); } }); return this.query; } - async parseParam(key, val, model, locale) { - const lcKey = key; - let operator = val.match(/\{(.*)\}/); - val = val.replace(/\{(.*)\}/, ''); - - if (operator) operator = operator[1]; - - if (val === '') { - return {}; - } if (lcKey === 'sort_by' || lcKey === 'order_by') { - const parts = val.split(','); - this.query.sort = {}; - this.query.sort[parts[0]] = parts[1] === 'asc' || parts.length <= 1 ? 1 : parts[1]; - } else if (lcKey === 'include') { - if (val.match(',')) { - const orArray = []; - val.split(',').map(id => orArray.push({ _id: id })); - this.addSearchParam('$or', orArray); - } else { - this.query.searchParams['_id'] = val; - } - } else if (lcKey === 'exclude') { - if (val.match(',')) { - const andArray = []; - val.split(',').map(id => andArray.push({ _id: { $ne: id } })); - this.addSearchParam('$and', andArray); - } else { - this.query.searchParams['_id'] = { $ne: val }; - } - } else if (lcKey === 'locale') { - // Do nothing - } else { - await this.parseSchemaForKey(model.schema, '', lcKey, val, operator, locale); - } + async parseParam(key, val) { + await this.parseSchemaForKey(this.model.schema, key, val); return Promise.resolve(true); } + async parseSchemaForKey(schema, key, val) { + const schemaObject = schema.obj[key]; + const localizedKey = `${key}${(schemaObject && schemaObject.localized) ? `.${this.locale}` : ''}`; + this.addSearchParam(localizedKey, val); + } + addSearchParam(key, value) { if (typeof this.query.searchParams[key] !== 'undefined') { Object.keys(value).forEach((i) => { @@ -96,144 +65,6 @@ class ParamParser { this.query.searchParams[key] = value; } } - - async parseSchemaForKey(schema, keyPrefix, lcKey, val, operator, locale) { - let paramType; - let key = keyPrefix + lcKey; - - const split = lcKey.split('.'); - if (split.length > 1 && schema.paths[lcKey] === undefined) { - // Parse SubSchema - if (schema.paths[split[0]].constructor.name === 'DocumentArray' - || schema.paths[split[0]].constructor.name === 'Mixed') { - await this.parseSchemaForKey(schema.paths[split[0]].schema, `${split[0]}.`, split[1], val, operator); - } else if (schema.paths[split[0]].constructor.name === 'SchemaType') { - // This wasn't handled in the original package but seems to work - paramType = schema.paths[split[0]].schema.paths.name.instance; - } else if (schema.paths[split[0]].constructor.name === 'SchemaArray') { - paramType = 'Array'; - } - } else if (schema.obj[lcKey] && typeof schema === 'object') { - if (schema.obj[lcKey].localized) { - key = `${key}.${locale}`; - } - paramType = schema.obj[lcKey].name || schema.obj[lcKey].type.name; - } else if (typeof schema === 'undefined') { - paramType = 'String'; - } else if (typeof schema.paths[lcKey] === 'undefined') { - // nada, not found - } else if (schema.paths[lcKey].constructor.name === 'SchemaBoolean') { - paramType = 'Boolean'; - } else if (schema.paths[lcKey].constructor.name === 'SchemaString') { - paramType = 'String'; - } else if (schema.paths[lcKey].constructor.name === 'SchemaNumber') { - paramType = 'Number'; - } else if (schema.paths[lcKey].constructor.name === 'ObjectId') { - paramType = 'ObjectId'; - } else if (schema.paths[lcKey].constructor.name === 'SchemaArray') { - paramType = 'Array'; - } - - if (paramType === 'Boolean') { - const convertToBoolean = (str) => { - return str.toLowerCase() === 'true' - || str.toLowerCase() === 't' - || str.toLowerCase() === 'yes' - || str.toLowerCase() === 'y' - || str === '1'; - }; - this.addSearchParam(key, convertToBoolean(val)); - } else if (paramType === 'Number') { - if (val.match(/([0-9]+,?)/) && val.match(',')) { - if (operator === 'all') { - this.addSearchParam(key, { $all: val.split(',') }); - } else if (operator === 'nin') { - this.addSearchParam(key, { $nin: val.split(',') }); - } else if (operator === 'mod') { - this.addSearchParam(key, { $mod: [val.split(',')[0], val.split(',')[1]] }); - } else { - this.addSearchParam(key, { $in: val.split(',') }); - } - } else if (val.match(/([0-9]+)/)) { - if (operator === 'gt' - || operator === 'gte' - || operator === 'lt' - || operator === 'lte' - || operator === 'ne') { - const newParam = {}; - newParam[`$${operator}`] = val; - this.addSearchParam(key, newParam); - } else { - this.addSearchParam(key, parseInt(val)); - } - } - } else if (paramType === 'String') { - if (val.match(',')) { - const options = val.split(',').map(str => new RegExp(str, 'i')); - - if (operator === 'all') { - this.addSearchParam(key, { $all: options }); - } else if (operator === 'nin') { - this.addSearchParam(key, { $nin: options }); - } else { - this.addSearchParam(key, { $in: options }); - } - } else if (val.match(/([0-9]+)/)) { - if (operator === 'gt' - || operator === 'gte' - || operator === 'lt' - || operator === 'lte') { - const newParam = {}; - newParam[`$${operator}`] = val; - this.addSearchParam(key, newParam); - } else { - this.addSearchParam(key, val); - } - } else if (operator === 'ne' || operator === 'not') { - const neregex = new RegExp(val, 'i'); - this.addSearchParam(key, { $not: neregex }); - } else if (operator === 'like') { - this.addSearchParam(key, { $regex: val, $options: '-i' }); - } else { - this.addSearchParam(key, val); - } - } else if (paramType === 'ObjectId') { - this.addSearchParam(key, val); - } else if (paramType === 'Array') { - - const recurseSchema = async (count, tempSchema) => { - const path = tempSchema.paths[split[count]]; - const ref = path && path.options && path.options.type && path.options.type[0].ref; - let refKey = split[count + 1]; - - if (ref && refKey) { - const refModel = mongoose.model(ref); - - if (count < split.length - 1) { - count++; - await recurseSchema(count, refModel.schema); - } - - if (refKey && refModel.schema.obj[refKey].intl) { - refKey = `${refKey}.${locale}`; - } - - // TODO: Need to recursively traverse paths longer than two segments. - // Example: this code handles "categories.title" but will not handle "categories.pages.title". - - // TODO: Support operators as above - const refQuery = await refModel.find({ [refKey]: val }); - if (refQuery) { - this.query.searchParams[split[count]] = { $in: refQuery.map(doc => doc.id) }; - } - } else if (!ref) { - this.addSearchParam(key, val); - } - }; - - await recurseSchema(0, schema); - } - } } module.exports = buildQueryPlugin;