punchcard-content-types
Version:
Combines with input plugins to create forms with validation. Creates forms for Punchcard CMS.
314 lines (263 loc) • 9.03 kB
JavaScript
;
const utils = require('../utils');
/*
* @typedef FormInputValues
* @type object
*
* @property {string} {{input-plugin-id}}--{{input-plugin-component}}[--{{filed-instance}}] - The value of the input field. The name of the input field is in the given form
*/
/*
* @typedef SplitInputValues
* @type object
*
* @property {string} plugin - The Input Plugin ID (from plugin configuration)
* @property {string} input - The Input Plugin component (from plugin)
* @property {string|undefined} index - The index of the repeatable element ({undefined} if not a repeatable)
* @property {string} value - The value from the field input
*/
/*
* Splits raw form input into object we can use
*
* @param {FormInputValues} raw - The input from a form
*
* @returns {SplitInputValues} - The structured values to test
*/
const split = raw => {
const output = Object.keys(raw).filter(input => {
const boom = input.split('--');
if (boom.length >= 2) {
return true;
}
return false;
});
return output.map(input => {
const boom = input.split('--');
const value = raw[input];
return {
plugin: boom[0],
input: boom[1],
index: boom[2],
value,
};
});
};
/*
* @typedef WorkingInputValues
* @type SplitInputValues
*
* @property {string|boolean} validation - The result of running validation for the {SplitInputValues}. A {string} is an error message, a {boolean} (can only be true) for valid input.
*/
/*
* Determines if there are any errors in validation
*
* @param {WorkingInputValues} working - The validated input values
*
* @returns {true|FormInputValues} - Returns `true` if there are no validation issues, {FormInputValues} with error messages as values if there are
*/
const join = working => {
const glue = {};
working.map(input => {
let key = `${input.plugin}--${input.input}`;
if (input.index) {
key += `--${input.index}`;
}
if (!input.hasOwnProperty('validation')) {
throw new Error(`Validation for '${key}' requires a validation key!`);
}
if (input.validation !== true) {
glue[key] = input.validation;
}
});
if (Object.keys(glue).length !== 0) {
return glue;
}
return true;
};
/*
* @typedef ValidationSettings
* @type object
*
* @property {object} {{input-plugin-id}} - The ID of the input plugin
* @proeprty {object} {{input-plugin-id}}.{{input-plugin-component}} - The settings of the Input Plugin Component from the input type
*/
/*
* Builds settings for use in validation
*
* @param {InputType} type - Input Type being tested
*
* @returns {ValidationSettings} - All validation settings
*/
const buildSettings = type => {
const settings = {};
type.attributes.map(plugin => {
let input = {};
settings[plugin.id] = {};
// If multiple instances take the first one for settings
if (Array.isArray(plugin.inputs)) {
input = plugin.inputs[0];
}
else {
input = plugin.inputs;
}
return Object.keys(input).map(inp => {
settings[plugin.id][inp] = input[inp].settings;
});
});
return settings;
};
/*
* Builds repeatable contraints for use in validation
*
* @param {InputType} type - Input Type being tested
*
* @returns {RepeatableContraints} - All valid repeatable contraints
*/
const buildRepeatables = type => {
const repeatables = {};
type.attributes.map(plugin => {
repeatables[plugin.id] = {};
if (plugin.hasOwnProperty('repeatable')) {
repeatables[plugin.id] = plugin.repeatable;
}
});
return repeatables;
};
/*
* @typedef ValidationSingleValues
* @type object
*
* @property {object} {{input-plugin-id}} - The ID of the input plugin
* @proeprty {string} {{input-plugin-id}}.{{input-plugin-component}} - The value of the input field
*
*/
/*
* @typedef ValidationMultipleValues
* @type object
*
* @property {object} {{input-plugin-id}} - The ID of the input plugin
* @property {object} {{input-plugin-id}}.{{filed-instance}} - The field instance for a repeating field
* @proeprty {string} {{input-plugin-id}}.{{filed-instance}}.{{input-plugin-component}} - The value of the input field
*
*/
/*
* Builds the values for use in validation
*
* @param {SplitInputValues} working - The structured values to test
*
* @returns {object} - An {object} of {ValidationSingleValues} or {ValidationMultipleValues} (depending on the specific input)
*/
const buildValues = working => {
const values = {};
working.map(value => {
// If there isn't an existing key for the input plugin, make one
if (!values.hasOwnProperty(value.plugin)) {
if (value.index === undefined) {
values[value.plugin] = {};
}
else {
values[value.plugin] = [];
}
}
// If there are multiple entries, build an object
if (typeof value.index !== 'undefined') {
// If the index doesn't exist, make it
values[value.plugin][value.index] = {};
// Set the value of the input to the value in the index
values[value.plugin][value.index][value.input] = value.value;
}
else {
// Set the value of the input to the value
values[value.plugin][value.input] = value.value;
}
});
return values;
};
/*
* Checks for required fields before running validation
*
* @param {object} input - Input Type being tested
* @param {ValidationSettings} settings - All validation settings
* @param {function} validation - The validation function to run
* @param {string} required - The required check to run (save/publish)
*
* @returns {true|FormInputValues} - Returns `true` if there are no validation or required issues, {FormInputValues} with error messages as values if there are
*/
const valueCheck = (input, settings, validation, required) => {
// Check for Required
if (input.target.hasOwnProperty('required') && input.target.value === '') {
// Only require a publishable field if we're checking against publish
if (required === 'publish' && input.target.required === 'publish') {
return 'Field is required to be published!';
}
// Only return require save fields
if (input.target.required === 'save') {
return 'Field is required to be saved!';
}
}
if (settings.repeatable.hasOwnProperty('min') && ((!Array.isArray(input.all) && settings.repeatable.min > 1) || (Array.isArray(input.all) && input.all.length < settings.repeatable.min))) {
return `At Least ${settings.repeatable.min} instances are required`;
}
return validation(input, settings);
};
/*
* Validates the Raw Input
*
* @param {FormInputValues} raw - Raw form input
* @param {InputType} type - Individual input type
* @param {string} check - Either `save`, `publish` to validate required correctly
*
* @returns {true|FormInputValues} - Returns `true` if there are no validation issues, {FormInputValues} with error messages as values if there are
*/
const validate = (raw, type, check) => {
let required = check;
if (typeof required === 'undefined') {
required = 'publish';
}
if (typeof required !== 'string' || ['save', 'publish'].indexOf(required) < 0) {
throw new Error('Parameter `check` must either be `save` or `publish`');
}
let working = split(raw);
const allSettings = buildSettings(type);
const allRepeatables = buildRepeatables(type);
const values = buildValues(working);
working = working.map(value => {
const result = value;
const plugin = utils.singleItem('id', value.plugin, type.attributes);
const input = {};
const settings = {};
// Build Input
input.target = {};
input.target.name = result.input;
input.target.value = result.value;
input.all = values[result.plugin];
// Build Settings
settings.target = allSettings[value.plugin][result.input];
settings.all = allSettings[value.plugin];
settings.repeatable = allRepeatables[value.plugin];
// check if parent plugin is required
if (plugin.hasOwnProperty('required')) {
input.target.required = plugin.required;
}
// If multiple instances use the first instance
if (Array.isArray(plugin.inputs)) {
if (plugin.inputs[0][result.input].hasOwnProperty('required')) {
input.target.required = plugin.inputs[0][result.input].required;
}
// Run the validation function
result.validation = valueCheck(input, settings, plugin.validation[plugin.inputs[0][result.input].validation.function], required);
}
// check if input is required, supersede main plugin
else {
if (plugin.inputs[result.input].hasOwnProperty('required')) {
input.target.required = plugin.inputs[result.input].required;
}
// Run the validation function
result.validation = valueCheck(input, settings, plugin.validation[plugin.inputs[result.input].validation.function], required);
}
return result;
});
return join(working);
};
module.exports = validate;
module.exports.split = split;
module.exports.join = join;