From 8ba758c0849c4f73509f9703cba92e5d86249219 Mon Sep 17 00:00:00 2001 From: mheil Date: Wed, 6 Mar 2024 12:59:43 +0100 Subject: [PATCH 1/4] #43 adopted user interaction logic and controlling error flow --- .../clientlibs/js/CUI.GenericMultiField.js | 48 +++++++++++++++---- .../clientlibs/js/validations.js | 41 +--------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js index 6963c0e..061de59 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js @@ -53,7 +53,7 @@ this._addListeners(); } // get list elements - this._updateList(false); + this._updateList(false,true); }, /** @@ -62,7 +62,7 @@ * @param {Boolean} triggerEvent if 'change' event should be triggered. * @private */ - _updateList: function (triggerEvent) { + _updateList: function (triggerEvent,clearValidationErrors) { var that = this; $.ajax({ type: "GET", @@ -70,6 +70,7 @@ url: that.crxPath + "/" + that.itemStorageNode + ".-1.json" }).done(function (data) { that.ol.empty(); + var itemCount = 0; $.each(data, function (key) { if (typeof data[key] === 'object' && !Array.isArray(data[key]) && data[key] !== undefined && data[key]["jcr:primaryType"] !== undefined && data[key]["sling:resourceType"] !== "wcm/msm/components/ghost") { @@ -97,13 +98,17 @@ } var li = that._createListEntry(key, propertyValue); li.appendTo(that.ol); + itemCount++; + } + var $field = that.$element.closest(".coral-Form-field") + if(itemCount >= that.minElements) { + clearValidationError($field); } - } }); // trigger change event on update of items if (triggerEvent === true) { - that._triggerChangeEvent(); + that._triggerChangeEvent(clearValidationErrors); } }); }, @@ -240,7 +245,7 @@ return {}; }, onSuccess: function () { - that._updateList(true); + that._updateList(true,true); return $.Deferred().promise(); }, onCancel: cancelCallback @@ -281,7 +286,7 @@ that._openEditDialog(path, function (event, dialog) { that._deleteNode(path, function () { // call update list after successful deletion of node - that._updateList(true); + that._updateList(true,true); }); }); }); @@ -444,11 +449,38 @@ * * @private */ - _triggerChangeEvent: function () { + _triggerChangeEvent: debounce(function(clearValidationErrors) { + if (clearValidationErrors) { + clearValidationError(this.$element.closest(".coral-Form-field")); + } this.$element.trigger("change"); - } + }, 250, false), }); + function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + clearTimeout(timeout); + timeout = setTimeout(function() { + timeout = null; + if (!immediate) func.apply(context, args); + }, wait); + if (immediate && !timeout) func.apply(context, args); + }; + } + function clearValidationError($field) { + var $coralTab = $field.find(".coral-Tab"); + if ($coralTab.length > 0) { + $coralTab.removeClass("is-invalid").attr("aria-invalid", "false"); + $coralTab.find(".coral-Form-errorlabel").remove(); + } + $field.removeClass("is-invalid"); + $field.attr("aria-invalid", "false"); + $field.find(".coral-Form-errorlabel").remove(); + $field.siblings(".coral-Form-errorlabel").remove(); + $field.siblings().removeClass(".coral-Form-errorlabel"); + } // put Merkle.GenericMultiField on widget registry CUI.Widget.registry.register(" ", Merkle.GenericMultiField); diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js index 8ec46a8..3b32cf7 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js @@ -67,48 +67,9 @@ exclusion: ".coral-GenericMultiField *" }); - // register validator for generic multi-field - registry.register("foundation.validation.validator", { - selector: ".coral-GenericMultiField", - validate: function (el) { - var $field = $(el).closest(".coral-Form-field"), items = $field.find(".coral-GenericMultiField-list li"), - minElements = $field.data("minelements"), maxElements = $field.data("maxelements"); - - // validate required attribute - if ($field.adaptTo("foundation-field").isRequired() && items.length === 0) { - return Granite.I18n.get("Error: Please fill out this field."); - } - - // validate min and max elements (only if field is required) - if ($field.adaptTo("foundation-field").isRequired()) { - // validate if minElements restriction is met - if (items && !isNaN(minElements) && items.length < minElements) { - return Granite.I18n.get('Error: At least {0} items must be created', minElements); - } - // validate if maxElements restriction is met - if (items && !isNaN(maxElements) && items.length > maxElements) { - return Granite.I18n.get('Error: At most {0} items can be created', maxElements); - } - } - - return null; - }, - show: function (el, message, ctx) { - var $field = $(el).closest(".coral-Form-field"); - $field.adaptTo("foundation-field").setInvalid(true); - ctx.next(); - }, - clear: function (el, ctx) { - var $field = $(el).closest(".coral-Form-field"); - $field.adaptTo("foundation-field").setInvalid(false); - $field.siblings(".coral-Icon--alert").remove(); - ctx.next(); - } - }); - // perform validation every time generic multi-field changed $(document).on("change", ".coral-GenericMultiField", function () { _performValidation($(this)); }); -})(window, Granite.$, CUI); +})(window, Granite.$, CUI); \ No newline at end of file From 4148766daa1d3e72278e452949016c8a0c1fdb2e Mon Sep 17 00:00:00 2001 From: mheil Date: Thu, 7 Mar 2024 08:13:32 +0100 Subject: [PATCH 2/4] #43 restored original functionality and fixed trigger execution for validation --- .../clientlibs/js/CUI.GenericMultiField.js | 50 ++++--------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js index 061de59..a16c17d 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/CUI.GenericMultiField.js @@ -53,7 +53,7 @@ this._addListeners(); } // get list elements - this._updateList(false,true); + this._updateList(false); }, /** @@ -62,7 +62,7 @@ * @param {Boolean} triggerEvent if 'change' event should be triggered. * @private */ - _updateList: function (triggerEvent,clearValidationErrors) { + _updateList: function (triggerEvent) { var that = this; $.ajax({ type: "GET", @@ -70,7 +70,6 @@ url: that.crxPath + "/" + that.itemStorageNode + ".-1.json" }).done(function (data) { that.ol.empty(); - var itemCount = 0; $.each(data, function (key) { if (typeof data[key] === 'object' && !Array.isArray(data[key]) && data[key] !== undefined && data[key]["jcr:primaryType"] !== undefined && data[key]["sling:resourceType"] !== "wcm/msm/components/ghost") { @@ -98,17 +97,13 @@ } var li = that._createListEntry(key, propertyValue); li.appendTo(that.ol); - itemCount++; - } - var $field = that.$element.closest(".coral-Form-field") - if(itemCount >= that.minElements) { - clearValidationError($field); } + } }); // trigger change event on update of items if (triggerEvent === true) { - that._triggerChangeEvent(clearValidationErrors); + that._triggerChangeEvent(); } }); }, @@ -245,7 +240,7 @@ return {}; }, onSuccess: function () { - that._updateList(true,true); + that._updateList(true); return $.Deferred().promise(); }, onCancel: cancelCallback @@ -286,7 +281,7 @@ that._openEditDialog(path, function (event, dialog) { that._deleteNode(path, function () { // call update list after successful deletion of node - that._updateList(true,true); + that._updateList(true); }); }); }); @@ -449,38 +444,11 @@ * * @private */ - _triggerChangeEvent: debounce(function(clearValidationErrors) { - if (clearValidationErrors) { - clearValidationError(this.$element.closest(".coral-Form-field")); - } + _triggerChangeEvent: function () { this.$element.trigger("change"); - }, 250, false), + } }); - function debounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - clearTimeout(timeout); - timeout = setTimeout(function() { - timeout = null; - if (!immediate) func.apply(context, args); - }, wait); - if (immediate && !timeout) func.apply(context, args); - }; - } - function clearValidationError($field) { - var $coralTab = $field.find(".coral-Tab"); - if ($coralTab.length > 0) { - $coralTab.removeClass("is-invalid").attr("aria-invalid", "false"); - $coralTab.find(".coral-Form-errorlabel").remove(); - } - $field.removeClass("is-invalid"); - $field.attr("aria-invalid", "false"); - $field.find(".coral-Form-errorlabel").remove(); - $field.siblings(".coral-Form-errorlabel").remove(); - $field.siblings().removeClass(".coral-Form-errorlabel"); - } // put Merkle.GenericMultiField on widget registry CUI.Widget.registry.register(" ", Merkle.GenericMultiField); @@ -488,7 +456,7 @@ if (CUI.options.dataAPI) { $(document).on("cui-contentloaded.data-api", function (e, data) { $(".coral-GenericMultiField[data-init~='genericmultifield']", e.target).genericMultiField(); - if (data && data.restored) { + if (data && data._foundationcontentloaded) { $(".coral-GenericMultiField[data-init~='genericmultifield']", e.target).trigger("change"); } }); From bf50004a2e29733b55cd1772630038d013fa3399 Mon Sep 17 00:00:00 2001 From: mheil Date: Thu, 7 Mar 2024 08:13:57 +0100 Subject: [PATCH 3/4] #43 introduced constant collection --- .../js/GenericMultifieldDialogHandler.js | 1 - .../clientlibs/js/GenericMultifieldHelper.js | 16 +++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldDialogHandler.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldDialogHandler.js index 96e89e6..5957bb6 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldDialogHandler.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldDialogHandler.js @@ -218,7 +218,6 @@ if (dialogContainer) { // replace content with previous $(DIALOG_CONTENT_SELECTOR, dialogContainer).replaceWith(self.parentDialogsData.pop()); - // trigger "foundation-contentloaded" event with data restored=true dialogContainer.trigger("foundation-contentloaded", {restored: true}); } } diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldHelper.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldHelper.js index a3d9a73..2338a7d 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldHelper.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/GenericMultifieldHelper.js @@ -9,14 +9,20 @@ */ ns.Helper = { - CUSTOM_BACKDROP_CLASS: "cq-dialog-backdrop-GenericMultiField", - CUSTOM_BACKDROP_SELECTOR: ".cq-dialog-backdrop-GenericMultiField", + CONST: { + CUSTOM_BACKDROP_CLASS: 'q-dialog-backdrop-GenericMultiField', + CUSTOM_BACKDROP_SELECTOR: '.cq-dialog-backdrop-GenericMultiField', + CORAL_GENERIC_MULTIFIELD_SELECTOR: '.coral-GenericMultiField', + ERROR_MESSAGE_REQUIRED: 'Error: Please fill out this field.', + ERROR_MESSAGE_MIN: 'Error: At least {0} items must be created.', + ERROR_MESSAGE_MAX: 'Error: At most {0} items can be created.' + }, /** * Displays the dialog backdrop over the content. */ createCustomBackdrop: function () { - var $customBackdrop = $(ns.Helper.CUSTOM_BACKDROP_SELECTOR), + var $customBackdrop = $(ns.Helper.CONST.CUSTOM_BACKDROP_SELECTOR), $originalBackdrop = $(".cq-dialog-backdrop"); // don't create backdrop if it already exists @@ -25,7 +31,7 @@ } // create backdrop - $customBackdrop = $('
'); + $customBackdrop = $('
'); if ($originalBackdrop.length) { $customBackdrop.insertAfter($originalBackdrop); } else { @@ -69,7 +75,7 @@ * Hides the dialog backdrop over the content. */ removeCustomBackdrop: function () { - var $customBackdrop = $(ns.Helper.CUSTOM_BACKDROP_SELECTOR); + var $customBackdrop = $(ns.Helper.CONST.CUSTOM_BACKDROP_SELECTOR); $customBackdrop.one("transitionend", function () { $customBackdrop.remove(); }); From c46576de5263588801045c6aa8950f8cbfe4f3cc Mon Sep 17 00:00:00 2001 From: mheil Date: Thu, 7 Mar 2024 08:15:30 +0100 Subject: [PATCH 4/4] #43 restored original functionality - fixed update view with new spectrum selectors - workaround for update ui race condition with 0.2 sec delay due to false handling for error message update by OOTB validation handler --- .../clientlibs/js/validations.js | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js index 3b32cf7..2725567 100644 --- a/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js +++ b/src/main/resources/SLING-INF/apps/merkle/genericmultifield/clientlibs/js/validations.js @@ -26,7 +26,7 @@ // register adapter for generic multi-field registry.register("foundation.adapters", { type: "foundation-field", - selector: ".coral-GenericMultiField", + selector: Merkle.Helper.CONST.CORAL_GENERIC_MULTIFIELD_SELECTOR, adapter: function (el) { var $el = $(el); return { @@ -62,14 +62,65 @@ // register selector for generic multi-field registry.register("foundation.validation.selector", { - submittable: ".coral-GenericMultiField", + submittable: Merkle.Helper.CONST.CORAL_GENERIC_MULTIFIELD_SELECTOR, candidate: ".coral-GenericMultiField:not([disabled]):not([data-renderreadonly=true])", exclusion: ".coral-GenericMultiField *" }); - // perform validation every time generic multi-field changed - $(document).on("change", ".coral-GenericMultiField", function () { + // register validator for generic multi-field + registry.register("foundation.validation.validator", { + selector: Merkle.Helper.CONST.CORAL_GENERIC_MULTIFIELD_SELECTOR, + validate: function (el) { + var $field = $(el).closest(".coral-Form-field"), items = $field.find(".coral-GenericMultiField-list li"), + minElements = $field.data("minelements"), maxElements = $field.data("maxelements"); + + // validate required attribute + if ($field.adaptTo("foundation-field").isRequired() && items.length === 0) { + return Granite.I18n.get(Merkle.Helper.CONST.ERROR_MESSAGE_REQUIRED); + + } + + // validate min and max elements (only if field is required) + if ($field.adaptTo("foundation-field").isRequired()) { + // validate if minElements restriction is met + if (items && !isNaN(minElements) && items.length < minElements) { + return Granite.I18n.get(Merkle.Helper.CONST.ERROR_MESSAGE_MIN, minElements); + } + // validate if maxElements restriction is met + if (items && !isNaN(maxElements) && items.length > maxElements) { + return Granite.I18n.get(Merkle.Helper.CONST.ERROR_MESSAGE_MAX, maxElements); + + } + } + + return null; + }, + show: function (el, message, ctx) { + var $field = $(el).closest(".coral-Form-field"); + $field.adaptTo("foundation-field").setInvalid(true); + + setTimeout(function() { + $field.siblings(".coral-Form-errorlabel").each(function (index, element) { + if (index > 0) { + element.remove() + } + }); + }, 200); + + ctx.next(); + }, + clear: function (el, ctx) { + var $field = $(el).closest(".coral-Form-field"); + $field.adaptTo("foundation-field").setInvalid(false); + $field.siblings(".coral-Form-fielderror").remove(); + $field.siblings(".coral-Form-errorlabel").remove(); + ctx.next(); + } + }); + + // perform validation every time generic multifield changed + $(document).on("change", Merkle.Helper.CONST.CORAL_GENERIC_MULTIFIELD_SELECTOR, function () { _performValidation($(this)); }); -})(window, Granite.$, CUI); \ No newline at end of file +})(window, Granite.$, CUI);