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;