From ff671dcc4dd886e9cc7271d0b9790dcb20534c2f Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 23 May 2024 00:40:38 +0300 Subject: [PATCH] improved Required control options - added more validation checks (resolves #277) changed `lzb.editor.control.isValueValid` hook to `lzb.editor.control.validate` --- assets/components/render-controls/index.js | 36 +++++++---------- assets/editor/blocks/main/edit.js | 6 +-- assets/editor/index.scss | 3 +- assets/utils/check-control-validity/index.js | 41 ++++++++++++++++++++ assets/utils/is-control-value-valid/index.js | 24 ------------ controls/checkbox/script.js | 21 ++++++++++ controls/color/script.js | 20 ++++++++++ controls/date_time/script.js | 33 ++++++++++++++++ controls/email/script.js | 24 ++++++++++++ controls/number/script.js | 39 +++++++++++++++++++ controls/radio/script.js | 30 +++++++++----- controls/range/script.js | 6 +-- controls/select/script.js | 30 +++++++++----- controls/text/script.js | 30 ++++++++++++++ controls/url/script.js | 28 +++++++++++++ 15 files changed, 298 insertions(+), 73 deletions(-) create mode 100644 assets/utils/check-control-validity/index.js delete mode 100644 assets/utils/is-control-value-valid/index.js diff --git a/assets/components/render-controls/index.js b/assets/components/render-controls/index.js index 19c842501..ffaaaf0c9 100644 --- a/assets/components/render-controls/index.js +++ b/assets/components/render-controls/index.js @@ -3,7 +3,6 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies import { cloneDeep } from 'lodash'; -import { __ } from '@wordpress/i18n'; import { Component, Fragment, RawHTML } from '@wordpress/element'; import { applyFilters } from '@wordpress/hooks'; import { PanelBody, Notice } from '@wordpress/components'; @@ -13,7 +12,7 @@ import { PanelBody, Notice } from '@wordpress/components'; */ import getControlTypeData from '../../utils/get-control-type-data'; import getControlValue from '../../utils/get-control-value'; -import isControlValueValid from '../../utils/is-control-value-valid'; +import checkControlValidity from '../../utils/check-control-validity'; let options = window.lazyblocksGutenberg; if (!options || !options.blocks || !options.blocks.length) { @@ -288,29 +287,22 @@ export default class RenderControls extends Component { } if (controlResult) { + const val = controlRenderData.getValue(); let controlNotice = ''; // show error for required fields - if ( - controlTypeData && - controlTypeData.restrictions.required_settings && - controlData.required && - controlData.required === 'true' - ) { - const val = controlRenderData.getValue(); - - if (!isControlValueValid(val, controlData)) { - controlNotice = ( - - {__('This field is required', 'lazy-blocks')} - - ); - } + const requiredError = checkControlValidity(val, controlData); + if (requiredError) { + controlNotice = ( + + {requiredError} + + ); } if (placement === 'inspector') { diff --git a/assets/editor/blocks/main/edit.js b/assets/editor/blocks/main/edit.js index 1534da248..537c53d71 100644 --- a/assets/editor/blocks/main/edit.js +++ b/assets/editor/blocks/main/edit.js @@ -25,7 +25,7 @@ import { import PreviewServerCallback from '../../../components/preview-server-callback'; import RenderControls from '../../../components/render-controls'; import getControlValue from '../../../utils/get-control-value'; -import isControlValueValid from '../../../utils/is-control-value-valid'; +import checkControlValidity from '../../../utils/check-control-validity'; let options = window.lazyblocksGutenberg; if (!options || !options.blocks || !options.blocks.length) { @@ -120,7 +120,7 @@ export default function BlockEdit(props) { childIndex ); - if (!isControlValueValid(val, control)) { + if (checkControlValidity(val, control)) { shouldLock += 1; } }); @@ -136,7 +136,7 @@ export default function BlockEdit(props) { control ); - if (!isControlValueValid(val, control)) { + if (checkControlValidity(val, control)) { shouldLock += 1; } } diff --git a/assets/editor/index.scss b/assets/editor/index.scss index 9a172ed56..028e73128 100644 --- a/assets/editor/index.scss +++ b/assets/editor/index.scss @@ -725,6 +725,7 @@ .components-notice { margin: 0; - margin-top: 15px; + margin-top: -10px; + margin-bottom: 24px; } } diff --git a/assets/utils/check-control-validity/index.js b/assets/utils/check-control-validity/index.js new file mode 100644 index 000000000..df6122792 --- /dev/null +++ b/assets/utils/check-control-validity/index.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies. + */ +import { applyFilters } from '@wordpress/hooks'; +import { __ } from '@wordpress/i18n'; + +import getControlTypeData from '../get-control-type-data'; + +export default function checkControlValidity(val, control) { + let result = false; + + const controlTypeData = getControlTypeData(control.type); + + if ( + controlTypeData && + controlTypeData.restrictions.required_settings && + control.required && + control.required === 'true' + ) { + const isValid = val !== '' && typeof val !== 'undefined'; + + // custom validation filter. + const { valid, message } = applyFilters( + 'lzb.editor.control.validate', + applyFilters( + `lzb.editor.control.${control.type}.validate`, + { valid: isValid, message: '' }, + val, + control + ), + val, + control + ); + + if (!valid) { + result = message || __('This field is required.', 'lazy-blocks'); + } + } + + return result; +} diff --git a/assets/utils/is-control-value-valid/index.js b/assets/utils/is-control-value-valid/index.js deleted file mode 100644 index c3b5fe7f6..000000000 --- a/assets/utils/is-control-value-valid/index.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * WordPress dependencies. - */ -import { applyFilters } from '@wordpress/hooks'; - -export default function isControlValueValid(val, control) { - let isValid = val !== '' && typeof val !== 'undefined'; - - // custom validation filter. - isValid = applyFilters( - `lzb.editor.control.${control.type}.isValueValid`, - isValid, - val, - control - ); - isValid = applyFilters( - 'lzb.editor.control.isValueValid', - isValid, - val, - control - ); - - return isValid; -} diff --git a/controls/checkbox/script.js b/controls/checkbox/script.js index 0fd1e194e..48cc9c141 100644 --- a/controls/checkbox/script.js +++ b/controls/checkbox/script.js @@ -28,6 +28,27 @@ addFilter( ) ); +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * + * @return {Object} validation data. + */ +function validate(validationData, value) { + if (!value) { + return { + valid: false, + message: 'Please tick this box if you want to proceed.', + }; + } + + return validationData; +} +addFilter('lzb.editor.control.checkbox.validate', 'lzb.editor', validate); +addFilter('lzb.editor.control.toggle.validate', 'lzb.editor', validate); + /** * Control settings render in constructor. */ diff --git a/controls/color/script.js b/controls/color/script.js index 336feaf7e..be125635d 100644 --- a/controls/color/script.js +++ b/controls/color/script.js @@ -33,6 +33,26 @@ addFilter('lzb.editor.control.color.render', 'lzb.editor', (render, props) => ( )); +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * + * @return {Object} validation data. + */ +function validate(validationData, value) { + if (!value) { + return { + valid: false, + message: 'Please choose a color.', + }; + } + + return validationData; +} +addFilter('lzb.editor.control.color.validate', 'lzb.editor', validate); + /** * Control settings render in constructor. */ diff --git a/controls/date_time/script.js b/controls/date_time/script.js index 64a7f887a..3ea13baa4 100644 --- a/controls/date_time/script.js +++ b/controls/date_time/script.js @@ -167,6 +167,39 @@ function DateTimePicker(props) { ); } +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * @param {Object} data + * + * @return {Object} validation data. + */ +function validate(validationData, value, data) { + if (!value) { + if ('date' === data.date_time_picker) { + return { + valid: false, + message: 'Please select date.', + }; + } else if ('time' === data.date_time_picker) { + return { + valid: false, + message: 'Please select time.', + }; + } + + return { + valid: false, + message: 'Please select date and time.', + }; + } + + return validationData; +} +addFilter('lzb.editor.control.date_time.validate', 'lzb.editor', validate); + /** * Control render in editor. */ diff --git a/controls/email/script.js b/controls/email/script.js index 490490df2..5b131c1e8 100644 --- a/controls/email/script.js +++ b/controls/email/script.js @@ -33,6 +33,30 @@ addFilter('lzb.editor.control.email.render', 'lzb.editor', (render, props) => { ); }); +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * + * @return {Object} validation data. + */ +function validate(validationData, value) { + if (!value) { + return { valid: false }; + } + + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { + return { + valid: false, + message: 'Please enter a valid email address.', + }; + } + + return validationData; +} +addFilter('lzb.editor.control.email.validate', 'lzb.editor', validate); + /** * Control settings render in constructor. */ diff --git a/controls/number/script.js b/controls/number/script.js index e0e64c978..a69acba56 100644 --- a/controls/number/script.js +++ b/controls/number/script.js @@ -38,6 +38,45 @@ addFilter('lzb.editor.control.number.render', 'lzb.editor', (render, props) => { ); }); +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * @param {Object} data + * + * @return {Object} validation data. + */ +function validate(validationData, value, data) { + if (value === '' || isNaN(value)) { + return { valid: false }; + } + + if (data.min !== '' && value < parseInt(data.min, 10)) { + return { + valid: false, + message: `Value must be greater than or equal to ${parseInt( + data.min, + 10 + )}.`, + }; + } + + if (data.max !== '' && value > parseInt(data.max, 10)) { + return { + valid: false, + message: `Value must be less than or equal to ${parseInt( + data.max, + 10 + )}.`, + }; + } + + return validationData; +} +addFilter('lzb.editor.control.number.validate', 'lzb.editor', validate); +addFilter('lzb.editor.control.range.validate', 'lzb.editor', validate); + /** * Control settings render in constructor. */ diff --git a/controls/radio/script.js b/controls/radio/script.js index 950ff5254..477425c8b 100644 --- a/controls/radio/script.js +++ b/controls/radio/script.js @@ -28,19 +28,29 @@ addFilter('lzb.editor.control.radio.render', 'lzb.editor', (render, props) => ( )); /** - * Control value valid in editor. + * Required check. + * + * @param {Object} validationData + * @param {number} value + * @param {Object} data + * + * @return {Object} validation data. */ -addFilter( - 'lzb.editor.control.radio.isValueValid', - 'lzb.editor', - (isValid, value, data) => { - if (data.allow_null === 'true') { - isValid = true; - } +function validate(validationData, value, data) { + if (data.allow_null === 'true') { + return { valid: true }; + } - return isValid; + if (!value) { + return { + valid: false, + message: 'Please select an item in the list.', + }; } -); + + return validationData; +} +addFilter('lzb.editor.control.radio.validate', 'lzb.editor', validate); /** * Control settings render in constructor. diff --git a/controls/range/script.js b/controls/range/script.js index 6ae0c3eb7..fe2ce839b 100644 --- a/controls/range/script.js +++ b/controls/range/script.js @@ -18,9 +18,9 @@ addFilter('lzb.editor.control.range.render', 'lzb.editor', (render, props) => ( { props.onChange(parseFloat(val)); diff --git a/controls/select/script.js b/controls/select/script.js index a30205ccd..829157873 100644 --- a/controls/select/script.js +++ b/controls/select/script.js @@ -52,19 +52,29 @@ addFilter('lzb.editor.control.select.render', 'lzb.editor', (render, props) => { }); /** - * Control value valid in editor. + * Required check. + * + * @param {Object} validationData + * @param {number} value + * @param {Object} data + * + * @return {Object} validation data. */ -addFilter( - 'lzb.editor.control.select.isValueValid', - 'lzb.editor', - (isValid, value, data) => { - if (data.allow_null === 'true') { - isValid = true; - } +function validate(validationData, value, data) { + if (data.allow_null === 'true') { + return { valid: true }; + } - return isValid; + if (!value || (data.multiple === 'true' && !value.length)) { + return { + valid: false, + message: 'Please select an item in the list.', + }; } -); + + return validationData; +} +addFilter('lzb.editor.control.select.validate', 'lzb.editor', validate); /** * Control settings render in constructor. diff --git a/controls/text/script.js b/controls/text/script.js index 3ed0d64dd..fdc5e44c7 100644 --- a/controls/text/script.js +++ b/controls/text/script.js @@ -32,6 +32,36 @@ addFilter('lzb.editor.control.text.render', 'lzb.editor', (render, props) => { ); }); +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * @param {Object} data + * + * @return {Object} validation data. + */ +function validate(validationData, value, data) { + if (!value.length) { + return { valid: false }; + } + + if (data.characters_limit) { + const limit = parseInt(data.characters_limit, 10); + + if (value.length > limit) { + return { + valid: false, + message: `Please shorten this text to ${limit} characters or less (you are currently using ${value.length} characters).`, + }; + } + } + + return validationData; +} +addFilter('lzb.editor.control.text.validate', 'lzb.editor', validate); +addFilter('lzb.editor.control.textarea.validate', 'lzb.editor', validate); + /** * Control settings render in constructor. */ diff --git a/controls/url/script.js b/controls/url/script.js index b9363e135..96f43256e 100644 --- a/controls/url/script.js +++ b/controls/url/script.js @@ -46,3 +46,31 @@ function ComponentRender(props) { addFilter('lzb.editor.control.url.render', 'lzb.editor', (render, props) => ( )); + +/** + * Required check. + * + * @param {Object} validationData + * @param {number} value + * + * @return {Object} validation data. + */ +function validate(validationData, value) { + if (!value) { + return { valid: false }; + } + + if ( + !/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( + value + ) + ) { + return { + valid: false, + message: 'Please enter a valid URL.', + }; + } + + return validationData; +} +addFilter('lzb.editor.control.url.validate', 'lzb.editor', validate);