UNPKG

punchcard-content-types

Version:

Combines with input plugins to create forms with validation. Creates forms for Punchcard CMS.

211 lines (183 loc) 6.57 kB
'use strict'; const nunjucks = require('nunjucks'); /* * Add Error Messaging to Rendered Inputs * * @param {string} html - Rendered HTML * @param {InputType} type - Input type being rendered * @param {FormInputValues} - Errors associated with form inputs * @param {Integer} - Index of the instance if repeatable * * @returns {string} - Rendered HTML, with appropriate error messages, and aria labels for invalid inputs */ const addError = (html, input, errors, index) => { let render = html; let result = ''; const inputs = []; if (errors) { if (Array.isArray(input.inputs) && index !== undefined) { Object.keys(input.inputs[index]).map(inp => { inputs.push({ name: `${input.id}--${inp}--${index}`, id: input.inputs[index][inp].id, }); }); } else { Object.keys(input.inputs).map(inp => { inputs.push({ name: `${input.id}--${inp}`, id: input.inputs[inp].id, }); }); } inputs.map(inp => { const find = new RegExp(`name=['"]\\s*${inp.name}\\s*['"]`); const errored = render.replace(find, `name="${inp.name}" aria-invalid="true"`); if (errors.hasOwnProperty(inp.name)) { result += `<p class="form--alert" role="alert" for="${inp.id}">${errors[inp.name]}</p>`; render = errored; } }); } result += render; return result; }; /* * Add Required Rendered Inputs * * @param {string} html - Rendered HTML * @param {InputType} type - Input type being rendered * @param {Integer} - Index of the instance if repeatable * * @returns {string} - Rendered HTML, with required indicators */ const addRequired = (html, input, index) => { let render = html; let result = ''; const inputs = []; if (Array.isArray(input.inputs) && index !== undefined) { Object.keys(input.inputs[index]).map(inp => { inputs.push({ required: input.inputs[index][inp].required, name: `${input.id}--${inp}--${index}`, id: input.inputs[index][inp].id, type: input.inputs[index][inp].type, }); }); } else { Object.keys(input.inputs).map(inp => { inputs.push({ required: input.inputs[inp].required, name: `${input.id}--${inp}`, id: input.inputs[inp].id, type: input.inputs[inp].type, }); }); } inputs.map(inp => { // Skip the required attribute for checkbox and radio on browser if (inp.type === 'checkbox' || inp.type === 'radio') { return; } if (input.required === 'save' || inp.required === 'save' || input.required === 'publish' || inp.required === 'publish') { const required = inp.required || input.required; const level = `required--${required}`; // pre-regex strings const stringFor = `for=["']\\s*${inp.id}\\s*["']`; // regex to get the label for THIS input const regexLabel = new RegExp(`.*(<label[\\w\\W\\s]*${stringFor}[\\w\\W\\s]*?>)`); // THIS input's label const label = render.match(regexLabel); // checks if label is not found if (label === null || !Array.isArray(label) || label.length < 2) { return; } // regex to search for `for` const reFor = new RegExp(`(${stringFor})`); render = render.replace(reFor, `for="${inp.id}"`); // add required to input const reName = new RegExp(`name=['"]\\s*${inp.name}\\s*['"]`); render = render.replace(reName, `name="${inp.name}" aria-required="true" required`); // Add required text to label const reLabel = new RegExp('</label>'); render = render.replace(reLabel, `\u00a0<mark class="${level}">required to ${required}</mark>`); render += '</label>'; } }); result += render; return result; }; /* * Renders the input in to HTML * * @param {InputType} type - Input type being rendered * @param {FormInputValues} - Errors associated with form inputs * * @returns {string} - Rendered HTML form body (not wrapped in <form>) */ const renderer = (type, errors) => { return new Promise((res) => { let rendered = type.attributes.map(input => { let inputs = Object.keys(input.inputs).length; if (Array.isArray(input.inputs) && input.inputs.length > 0) { inputs = Object.keys(input.inputs[0]).length; } const description = input.hasOwnProperty('description') && input.description !== ''; const context = input.inputs; let html = nunjucks.renderString(input.html, context); let render = ''; // Set opening tags based on number of inputs if (inputs > 1) { render += `<fieldset id="${input.id}" class="form--fieldset">`; render += `<legend class="form--legend">${input.name}</legend>`; // Add Description if (description) { render += `<p class="form--description">${input.description}</p>`; } } else { render += `<div id="${input.id}" class="form--field">`; } // Wraps repeatable attributes around div if (Array.isArray(input.inputs) && input.hasOwnProperty('repeatable')) { context.forEach((data, index) => { render += `<div id="sub-${input.id}--${index}" class="form--repeatable">`; html = nunjucks.renderString(input.html, data); // Add error messaging and required render += addRequired(addError(html, input, errors, index), input, index); // Adds delete button if instances are greater than minimum if (input.inputs.length > input.repeatable.min) { render += `<button type="button" class="form--delete" id="${input.id}--delete--${index}">Delete</button>`; } render += '</div>'; }); if (input.inputs.length < input.repeatable.max) { render += `<button type="button" class="form--add" id="${input.id}--add">Add</button>`; } } else { // Add error messaging and required render += addRequired(addError(html, input, errors), input); } // Set closing tags based on number of inputs if (inputs > 1) { render += '</fieldset>'; } else { // Add Description if (description) { render += `<p class="form--description">${input.description}</p>`; } render += '</div>'; } return render; }); rendered = rendered.join('\n\n'); res(rendered); }); }; module.exports = renderer; module.exports.error = addError; module.exports.required = addRequired;