The code in this post is relevant to and tested on AEM 6.2.
Download the clientlib code as a zip package from here: rte-validator
Here is the code:
I won’t get into explaining the code as it is fully documented (jsDoc) and should be easy to follow by any Javascript developer.
The most important thing to keep in mind is that this code uses foundation validator. Please take the time to read that documentaton.
/* jshint undef: true, unused: true, esversion:5, node: true */
/* globals Coral, window, $ */
/**@author Ahmed Musallam */
(function ($window) {
var foundationRegistry = 'foundation-registry';
var foundationValidator = 'foundation.validation.validator';
var foundationSelector = 'foundation.validation.selector';
var validationTooltipDataAttr = 'rte-validation.error.tooltip';
var validationErrorDataAttr = 'rte-validation.error';
// coral ui util class to hide elements https://docs.adobe.com/docs/en/aem/6-2/develop/ref/coral-ui/styles/#screen-readerOnly
var screenReaderClass = 'u-coral-screenReaderOnly';
/**
* a helper that set's the field to coral ui invalid
* for AEM 6.2, this sets the is-invalid attribute
* @param {Element | jQuery} el the element, can be an Element or jQuery
* @param {Boolean} invalid true, set to invalid. false, set to valid
*/
function _setInvalid(el, invalid){
if(!el) return;
var $el = el.length ? el : $(el);
// find non hidden form fields
$el = $el.find('.coral-Form-field:not(:hidden)');
var fieldAPI = $el.adaptTo('foundation-field');
// set the field to invalid
if (fieldAPI && fieldAPI.setInvalid) fieldAPI.setInvalid(invalid);
// if we cant, show warning
else console.warn('cannot use foundation field api for this element', $el);
}
/**
* a helper to show/hide the field info icon and tooltip
* @param {Boolean} show true to show the fieldinfo, false to hide
*/
function _toggleFieldInfo(field, show){
if(show) field.nextAll('.coral-Form-fieldinfo').removeClass(screenReaderClass);
else field.nextAll('.coral-Form-fieldinfo').addClass(screenReaderClass);
}
/**
* adds/removes the error ui
* mostly the same as:
* http://localhost:4502/libs/granite/ui/components/coral/foundation/clientlibs/foundation/js/coral/validations.js
* @param {Element} element the element on which the validation is hapening
* @param {String} message the error message to show
* @return void
*/
function _showRteError(element, message){
var el = $(element);
var field = el.closest('.richtext-container');
// set the field to invalid (adds the red border)
_setInvalid(field, true);
// hide the fieldinfo element
_toggleFieldInfo(field, false);
var tooltip;
var error = field.data(validationErrorDataAttr);
// if we already set the error in the data attribue, retrieve and show
if (error) {
tooltip = $(error).data(validationTooltipDataAttr);
tooltip.content.innerHTML = message;
if (!error.parentNode) {
field.after(error, tooltip);
}
}
// if this is the first time we validate, create and add errors
else {
// create error icon
error = new Coral.Icon();
error.icon = 'alert';
error.classList.add('coral-Form-fielderror');
// create tooltip
tooltip = new Coral.Tooltip();
tooltip.variant = 'error';
tooltip.placement = field.closest('form').hasClass('coral-Form--vertical') ? 'left' : 'bottom';
tooltip.target = error;
tooltip.content.innerHTML = message;
// set the error and tooltip as data attributes for later use
$(error).data(validationTooltipDataAttr, tooltip);
field.data(validationErrorDataAttr, error);
// add error and tooltip to the ui
field.after(error, tooltip);
}
}
/**
* Clears error ui (for when the field is valid)
* @param {*} element the element on which the validation is hapening
* @return void
*/
function _clearRteError(element){
var el = $(element);
var field = el.closest('.richtext-container');
// set the field to valid (removes the red border)
_setInvalid(field, false);
var error = field.data(validationErrorDataAttr);
if (error) {
var tooltip = $(error).data(validationTooltipDataAttr);
// hide and remove both tooltip and error icon
tooltip.hide();
tooltip.remove();
error.remove();
}
// show the fieldinfo element
_toggleFieldInfo(field, false);
}
/**
* A helper method to register a selector
* Add the data attribue selector to foundation submittable
* This just means that elements with the data attribute now can be validated
* This is needed because in the case of RTE, the field is hidden and hidden
* fields are excluded from validation by default.
* @param selector the selector to register
*/
function _registerSelector(selector){
$window.adaptTo(foundationRegistry).register(foundationSelector, {
submittable: selector,
candidate: selector,
exclusion: ''
});
}
/**
* Check if variable is a function
* credit: https://stackoverflow.com/questions/5999998/how-can-i-check-if-a-javascript-variable-is-function-type
* @param {*} functionToCheck
*/
function _isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
/** Clear Fnction
@name Clear
@function
@param {Element} element the element on which validation is hapening;
@param {FoundationValidationValidatorContext} ctx
*/
/** Show Function
@name Show
@function
@param {Element} element the element on which validation is hapening;
@param {string} message the message returned from validate
@param {FoundationValidationValidatorContext} ctx
*/
/**
* RteValidator type def
* @typedef {Object} RteValidator
* @property {string|Functon} selector: Only the element satisfying the selector will be validated using this validator.
* @property {Function} validate: The actual validation function. It must return a string of error message if the element fails.
* @property {Clear} beforeClear: optional hook function to be executed before the clear function
* @property {Clear} afterClear: optional hook function to be executed after the clear function
* @property {Show} beforeShow: optional hook function to be executed before the clear function
* @property {Show} afterShow: optional hook function to be executed after the clear function
*/
/**
* a function to register an RTE validator
* @param {*} attribute the attrubute to use for validation
* @param {RteValidator} validator the validator object documented here: https://docs.adobe.com/docs/en/aem/6-2/develop/ref/granite-ui/api/jcr_root/libs/granite/ui/components/coral/foundation/clientlibs/foundation/js/validation/index.html#validator
*/
function registerRteValidator(validator){
if(!validator){
console.error("cannot register an empty validator");
}
// first register the selector so we can use it for validation.
_registerSelector(validator.selector);
var rteValidator =
{
selector: validator.selector,
validate: validator.validate,
show: function(element, message, ctx){
if(_isFunction(validator.beforeShow)) validator.beforeShow(element, message, ctx);
_showRteError(element, message, ctx);
if(_isFunction(validator.afterShow)) validator.afterShow(element, message, ctx);
},
clear: function(element, ctx){
if(_isFunction(validator.beforeClear)) validator.beforeClear(element, ctx);
_clearRteError(element, ctx);
if(_isFunction(validator.afterClear)) validator.afterClear(element, ctx);
}
};
/**
* register the validator
*/
$window.adaptTo(foundationRegistry).register(foundationValidator, rteValidator);
}
// expose the
window.customValidator = window.customValidator || {};
window.customValidator = window.customValidator || {};
window.customValidator.registerRteValidator = registerRteValidator;
})($(window));
Now, to the fun part, let’s say you want to add a validation that makes RTE field required
(function () {
// register an RTE validator to make RTE required
window.customValidator.registerRteValidator({
selector: '[data-rte-required]',
validate: function (element) {
// if there is a value, return
if ($(element).val()) return;
// no value, return error message
else return 'This field is required.';
}
});
})();
All code above needs to be in a clientlib with the following categories:
categories=”[granite.ui.foundation,cq.authoring.dialog]”
Since the selector we chose is data-rte-required we need to somehow add that attribute on the RTE in our dialog. Luckily, RTE granite UI widget adds the data attribute for any unknown attribute added to the field. So all we have to do is add rte-required to the RTE field as follows:
and voila! You can create all sorts of complex validations like that without having to worry about showing or hiding error UI and messages.
When trying to submit an empty field, you’ll get the following dialog validation:
Additionally, I have added optional function hooks that can be provided and executed before and after the show/clear methods of the granite validator:
beforeClear: optional hook function to be executed before the clear function
afterClear: optional hook function to be executed after the clear function
beforeShow: optional hook function to be executed before the show function
afterShow: optional hook function to be executed after the show function
Let’s look at an example of how to use those hooks. The following code snippet is the same required validation above but with added hook methods:
// register an RTE validator to make RTE required
window.customValidator.registerRteValidator({
selector: '[data-rte-required]',
validate: function (element) {
// if there is a value, return
if ($(element).val()) return;
// no value, return error message
else return 'This field is required.';
}
});
})();
All code above needs to be in a clientlib with the following categories:
categories=”[granite.ui.foundation,cq.authoring.dialog]”
Since the selector we chose is data-rte-required we need to somehow add that attribute on the RTE in our dialog. Luckily, RTE granite UI widget adds the data attribute for any unknown attribute added to the field. So all we have to do is add rte-required to the RTE field as follows:
and voila! You can create all sorts of complex validations like that without having to worry about showing or hiding error UI and messages.
When trying to submit an empty field, you’ll get the following dialog validation:
Additionally, I have added optional function hooks that can be provided and executed before and after the show/clear methods of the granite validator:
beforeClear: optional hook function to be executed before the clear function
afterClear: optional hook function to be executed after the clear function
beforeShow: optional hook function to be executed before the show function
afterShow: optional hook function to be executed after the show function
Let’s look at an example of how to use those hooks. The following code snippet is the same required validation above but with added hook methods:
(function () {
window.customValidator.registerRteValidator({
selector: '[data-rte-required]',
validate: function (element) {
var rteValue = $(element).val();
if(rteValue.indexOf('simple') < 0){
return "the word 'simple' must be in the field"
}
},
beforeShow: function(element, message){ console.log("this message is printed BEFORE showing error UI")},
afterShow: function(element, message){ console.log("this message is printed AFTER showing error UI")},
beforeClear: function(element){ console.log("this message is printed BEFORE hiding error UI")},
afterClear: function(element){ console.log("this message is printed AFTER hiding error UI")}
});
})();
No comments:
Post a Comment
If you have any doubts or questions, please let us know.