diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java index f221030478..63852f5843 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractFormComponentImpl.java @@ -17,10 +17,12 @@ import java.io.IOException; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -61,9 +63,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ArrayNode; public class AbstractFormComponentImpl extends AbstractComponentImpl implements FormComponent { @ValueMapValue(injectionStrategy = InjectionStrategy.OPTIONAL, name = "dataRef") @@ -278,6 +283,10 @@ protected boolean getEditMode() { if (rulesProperties.size() > 0) { properties.put(CUSTOM_RULE_PROPERTY_WRAPPER, rulesProperties); } + List disabledScripts = getDisabledXFAScripts(); + if (disabledScripts.size() > 0) { + properties.put("fd:disabledXfaScripts", disabledScripts); + } return properties; } @@ -506,4 +515,25 @@ public Map getDorProperties() { } return customDorProperties; } + + private List getDisabledXFAScripts() { + Set disabledScripts = new HashSet<>(); + String xfaScripts = resource.getValueMap().get("fd:xfaScripts", ""); + if (StringUtils.isNotEmpty(xfaScripts)) { + // read string xfaScripts to jsonNode + ObjectMapper mapper = new ObjectMapper(); + try { + ArrayNode node = (ArrayNode) mapper.readTree(xfaScripts); + // iterate through the array node and add the elements which have disabled property set to true + for (JsonNode jsonNode : node) { + if (jsonNode.has("disabled") && jsonNode.get("disabled").asBoolean()) { + disabledScripts.add(jsonNode.get("activity").asText()); + } + } + } catch (IOException e) { + logger.error("Error while parsing xfaScripts {} {}", e, resource.getPath()); + } + } + return new ArrayList<>(disabledScripts); + } } diff --git a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java index 908cae4f99..412b1bd93f 100644 --- a/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java +++ b/bundles/af-core/src/main/java/com/adobe/cq/forms/core/components/util/AbstractOptionsFieldImpl.java @@ -56,7 +56,7 @@ public boolean isEnforceEnum() { /** * Function to retrieve enum values and enum names paired together - * + * * @return map containing enum values and enum names as key-value pairs */ private Map getEnumPairs() { @@ -106,7 +106,11 @@ public String[] getEnumNames() { String[] enumName = map.values().toArray(new String[0]); return Arrays.stream(enumName) .map(p -> { - return this.translate("enumNames", p); + String value = this.translate("enumNames", p); + if (value == null) { + value = ""; + } + return value; }) .toArray(String[]::new); } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/.content.xml new file mode 100644 index 0000000000..aaa876c946 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/.content.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js.txt new file mode 100644 index 0000000000..9f3b9ebc04 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2023 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +common.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js/common.js new file mode 100644 index 0000000000..ecfe9c6bd5 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/af-commons/v1/clientlibs/tabs/js/common.js @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + function TabsMixin(Base) { + return class extends Base { + + /** + * Private property for storing the active tab ID. + * @type {string} + */ + #_active; + + /** + * Private property for storing the IS. + * @type {string} + */ + #_IS; + /** + * Private property for storing the namespace. + * @type {string} + */ + #_NS; + /** + * Private property for storing the selectors. + * @type {object} + */ + #_selectors; + + constructor(params, ns, is, selectors) { + super(params); + this.#_IS = is; + this.#_NS = ns; + this.#_selectors = selectors; + const { element } = params; + this.#cacheElements(element); + this.#_active = this.getActiveTabId(this.#getCachedTabs()); + this.#refreshActive(); + } + + /** + * Gets the cached tab panels. + * @returns {NodeList} The cached tab panels. + * @private + */ + #getCachedTabPanels() { + return this._elements["tabpanel"] + } + + /** + * Gets the cached tabs. + * @returns {NodeList} The cached tabs. + * @private + */ + #getCachedTabs() { + return this._elements["tab"]; + } + + /** + * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API. + * @private + * @param {HTMLElement} wrapper - The Tabs wrapper element. + */ + #cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + var hooks = this._elements.self.querySelectorAll("[data-" + this.#_NS + "-hook-" + this.#_IS + "]"); + + for (var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.#_IS + "]") === this._elements.self) { // only process own tab elements + var lowerCased = this.#_IS.toLowerCase(); + var capitalized = lowerCased.charAt(0).toUpperCase() + lowerCased.slice(1); + var key = hook.dataset[this.#_NS + "Hook" + capitalized]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + var tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + /** + * Refreshes the tab markup based on the current active index. + * @private + */ + #refreshActive() { + var tabpanels = this.#getCachedTabPanels(); + var tabs = this.#getCachedTabs(); + if (tabpanels) { + for (var i = 0; i < tabpanels.length; i++) { + if(tabs[i]) { + if (tabs[i].id === this.#_active) { + tabpanels[i].classList.add(this.#_selectors.active.tabpanel); + tabpanels[i].removeAttribute("aria-hidden"); + tabs[i].classList.add(this.#_selectors.active.tab); + tabs[i].setAttribute("aria-selected", true); + tabs[i].setAttribute("tabindex", "0"); + tabs[i].setAttribute("aria-current", "true"); + } else { + tabpanels[i].classList.remove(this.#_selectors.active.tabpanel); + tabpanels[i].setAttribute("aria-hidden", true); + tabs[i].classList.remove(this.#_selectors.active.tab); + tabs[i].setAttribute("aria-selected", false); + tabs[i].setAttribute("tabindex", "-1"); + tabs[i].setAttribute("aria-current", "false"); + } + } + } + } + } + + /** + * Returns the id of the active tab, if no tab is active returns 0th element id + * + * @param {Array} tabs Tab elements + * @returns {Number} Id of the active tab, 0th element id if none is active + */ + getActiveTabId(tabs) { + if (tabs) { + var result = tabs[0].id; + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].classList.contains(this.#_selectors.active.tab)) { + result = tabs[i].id; + break; + } + } + return result; + } + } + + /** + * Navigates to the tab at the provided index + * + * @private + * @param {Number} index The index of the tab to navigate to + */ + navigate(index) { + this.#_active = index; + this.#refreshActive(); + } + } + } + + window.Forms = window.Forms || {}; + window.Forms.CoreComponentsCommons = window.Forms.CoreComponentsCommons || {}; + window.Forms.CoreComponentsCommons.TabsMixin = TabsMixin; + +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/accordion.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/accordion.html index 8d42a320a4..0ad0b906f3 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/accordion.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/accordion.html @@ -84,6 +84,6 @@ data-sly-test="${(wcmmode.edit || wcmmode.preview) && accordion.items.size < 1}"> + data-sly-call="${clientlib.js @ categories='core.forms.components.accordion.v1.contentframe'}"/> diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/.content.xml new file mode 100644 index 0000000000..7f8a033ed9 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js.txt new file mode 100644 index 0000000000..9f3b9ebc04 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2023 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +common.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js new file mode 100644 index 0000000000..0d112b1c02 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/commons/js/common.js @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + function AccordionMixin(Base) { + return class extends Base { + static NS = "cmp"; + static IS = "adaptiveFormAccordion"; + static bemBlock = 'cmp-accordion'; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]' + }; + + + static idSuffixes = { + item: "-item", + button: "-button", + panel: "-panel" + } + + static cacheKeys = { + buttonKey: "button", + panelKey: "panel", + itemKey: "item" + } + + static cssClasses = { + button: { + disabled: "cmp-accordion__button--disabled", + expanded: "cmp-accordion__button--expanded" + }, + panel: { + hidden: "cmp-accordion__panel--hidden", + expanded: "cmp-accordion__panel--expanded" + } + }; + + static delay = 100; + + static dataAttributes = { + item: { + expanded: "data-cmp-expanded" + } + }; + + constructor(params) { + super(params); + } + + getCachedItems() { + return (this._elements[this.constructor.cacheKeys.itemKey] != null) ? this._elements[this.constructor.cacheKeys.itemKey] : []; + } + + getCachedPanels() { + return this._elements[this.constructor.cacheKeys.panelKey]; + } + + getCachedButtons() { + return this._elements[this.constructor.cacheKeys.buttonKey] + } + + getItemById(itemId) { + var items = this.getCachedItems(); + if (items) { + for (var i = 0; i < items.length; i++) { + if (items[i].id === itemId) { + return items[i]; + } + } + } + } + + + /** + * Returns all expanded items. + * + * @private + * @returns {HTMLElement[]} The expanded items + */ + getExpandedItems() { + var expandedItems = []; + + for (var i = 0; i < this.getCachedItems().length; i++) { + var item = this.getCachedItems()[i]; + var expanded = this.isItemExpanded(item); + if (expanded) { + expandedItems.push(item); + } + } + + return expandedItems; + } + + /** + * Gets an item's expanded state. + * + * @private + * @param {HTMLElement} item The item for checking its expanded state + * @returns {Boolean} true if the item is expanded, false otherwise + */ + isItemExpanded(item) { + return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; + } + + /** + * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. + * + * @private + * @param {HTMLElement} wrapper The Accordion wrapper element + */ + cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + var hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]"); + + for (var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own accordion elements + var lowerCased = this.constructor.IS.toLowerCase(); + var capitalized = lowerCased.charAt(0).toUpperCase() + lowerCased.slice(1); + var key = hook.dataset[this.constructor.NS + "Hook" + capitalized]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + var tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + collapseAllOtherItems(itemId) { + var itemToToggle = this.getItemById(itemId); + var itemList = this.getCachedItems(); + for (var i = 0; i < itemList.length; i++) { + if (itemList[i] !== itemToToggle) { + var expanded = this.isItemExpanded(itemList[i]); + if (expanded) { + this.collapseItem(this.getCachedItems()[i]); + } + } + } + } + + /** + * General handler for toggle of an item. + * + * @private + * @param {Number} id The id of the item to toggle + */ + toggle(id) { + var itemToToggle = this.getItemById(id); + if (itemToToggle) { + (this.isItemExpanded(itemToToggle) === false) ? this.expandItem(itemToToggle) : this.collapseItem(itemToToggle); + } + } + + + /** + * Refreshes an item based on its expanded state. + * + * @private + * @param {HTMLElement} item The item to refresh + */ + refreshItem(item) { + var expanded = this.isItemExpanded(item); + if (expanded) { + this.expandItem(item); + } else { + this.collapseItem(item); + } + } + + /** + * Refreshes all items based on their expanded state. + * + * @private + */ + refreshItems() { + for (var i = 0; i < this.getCachedItems().length; i++) { + this.refreshItem(this.getCachedItems()[i]); + } + } + + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as expanded + */ + expandItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.setAttribute(this.constructor.dataAttributes.item.expanded, ""); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.add(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", true); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.expanded); + panel.classList.remove(this.constructor.cssClasses.panel.hidden); + panel.setAttribute("aria-hidden", false); + } + } + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is not expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as not expanded + */ + collapseItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.removeAttribute(this.constructor.dataAttributes.item.expanded); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.remove(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", false); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.hidden); + panel.classList.remove(this.constructor.cssClasses.panel.expanded); + panel.setAttribute("aria-hidden", true); + } + } + } + } + + window.Forms = window.Forms || {}; + window.Forms.CoreComponentsCommons = window.Forms.CoreComponentsCommons || {}; + window.Forms.CoreComponentsCommons.AccordionMixin = AccordionMixin; + +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/.content.xml new file mode 100644 index 0000000000..3f029c4283 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js.txt new file mode 100644 index 0000000000..093ea3890d --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2023 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +accordion.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js/accordion.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js/accordion.js new file mode 100644 index 0000000000..feb3b34479 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/contentframe/js/accordion.js @@ -0,0 +1,338 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + function AccordionMixin(Base) { + return class extends Base { + static NS = "cmp"; + static IS = "adaptiveFormAccordion"; + static bemBlock = 'cmp-accordion'; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]' + }; + + + static idSuffixes = { + item: "-item", + button: "-button", + panel: "-panel" + } + + static cacheKeys = { + buttonKey: "button", + panelKey: "panel", + itemKey: "item" + } + + static cssClasses = { + button: { + disabled: "cmp-accordion__button--disabled", + expanded: "cmp-accordion__button--expanded" + }, + panel: { + hidden: "cmp-accordion__panel--hidden", + expanded: "cmp-accordion__panel--expanded" + } + }; + + static delay = 100; + + static dataAttributes = { + item: { + expanded: "data-cmp-expanded" + } + }; + + constructor(params) { + super(params); + } + + getCachedItems() { + return (this._elements[this.constructor.cacheKeys.itemKey] != null) ? this._elements[this.constructor.cacheKeys.itemKey] : []; + } + + getCachedPanels() { + return this._elements[this.constructor.cacheKeys.panelKey]; + } + + getCachedButtons() { + return this._elements[this.constructor.cacheKeys.buttonKey] + } + + getItemById(itemId) { + var items = this.getCachedItems(); + if (items) { + for (var i = 0; i < items.length; i++) { + if (items[i].id === itemId) { + return items[i]; + } + } + } + } + + + /** + * Returns all expanded items. + * + * @private + * @returns {HTMLElement[]} The expanded items + */ + getExpandedItems() { + var expandedItems = []; + + for (var i = 0; i < this.getCachedItems().length; i++) { + var item = this.getCachedItems()[i]; + var expanded = this.isItemExpanded(item); + if (expanded) { + expandedItems.push(item); + } + } + + return expandedItems; + } + + /** + * Gets an item's expanded state. + * + * @private + * @param {HTMLElement} item The item for checking its expanded state + * @returns {Boolean} true if the item is expanded, false otherwise + */ + isItemExpanded(item) { + return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; + } + + /** + * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. + * + * @private + * @param {HTMLElement} wrapper The Accordion wrapper element + */ + cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + var hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]"); + + for (var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own accordion elements + var lowerCased = this.constructor.IS.toLowerCase(); + var capitalized = lowerCased.charAt(0).toUpperCase() + lowerCased.slice(1); + var key = hook.dataset[this.constructor.NS + "Hook" + capitalized]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + var tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + collapseAllOtherItems(itemId) { + var itemToToggle = this.getItemById(itemId); + var itemList = this.getCachedItems(); + for (var i = 0; i < itemList.length; i++) { + if (itemList[i] !== itemToToggle) { + var expanded = this.isItemExpanded(itemList[i]); + if (expanded) { + this.collapseItem(this.getCachedItems()[i]); + } + } + } + } + + /** + * General handler for toggle of an item. + * + * @private + * @param {Number} id The id of the item to toggle + */ + toggle(id) { + var itemToToggle = this.getItemById(id); + if (itemToToggle) { + (this.isItemExpanded(itemToToggle) === false) ? this.expandItem(itemToToggle) : this.collapseItem(itemToToggle); + } + } + + + /** + * Refreshes an item based on its expanded state. + * + * @private + * @param {HTMLElement} item The item to refresh + */ + refreshItem(item) { + var expanded = this.isItemExpanded(item); + if (expanded) { + this.expandItem(item); + } else { + this.collapseItem(item); + } + } + + /** + * Refreshes all items based on their expanded state. + * + * @private + */ + refreshItems() { + for (var i = 0; i < this.getCachedItems().length; i++) { + this.refreshItem(this.getCachedItems()[i]); + } + } + + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as expanded + */ + expandItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.setAttribute(this.constructor.dataAttributes.item.expanded, ""); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.add(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", true); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.expanded); + panel.classList.remove(this.constructor.cssClasses.panel.hidden); + panel.setAttribute("aria-hidden", false); + } + } + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is not expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as not expanded + */ + collapseItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.removeAttribute(this.constructor.dataAttributes.item.expanded); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.remove(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", false); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.hidden); + panel.classList.remove(this.constructor.cssClasses.panel.expanded); + panel.setAttribute("aria-hidden", true); + } + } + } + } + + class Accordion extends AccordionMixin(class {}) { + + constructor(params) { + super(params); + const { element } = params; + this.cacheElements(element); + if (this.getCachedItems()) { + var expandedItems = this.getExpandedItems(); + // multiple expanded items annotated, display the last item open. + if (expandedItems.length > 1) { + var lastExpandedItem = expandedItems[expandedItems.length - 1] + this.expandItem(lastExpandedItem); + this.collapseAllOtherItems(lastExpandedItem.id); + } + this.refreshItems(); + } + element.removeAttribute("data-" + this.constructor.NS + "-is"); + + if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { + /* + * Editor message handling: + * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame + * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component + * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data + */ + window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); + var _self = this; + window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { + if (message.data && message.data.type === "cmp-accordion" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { + if (message.data.operation === "navigate" && _self.getCachedItems()[message.data.index] !== undefined) { + _self.toggle(_self.getCachedItems()[message.data.index].id); + _self.collapseAllOtherItems(_self.getCachedItems()[message.data.index].id); + } + } + }); + } + } + } + + /** + * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. + * + * @private + */ + function onDocumentReady() { + + var elements = document.querySelectorAll(Accordion.selectors.self); + for (var i = 0; i < elements.length; i++) { + new Accordion({ element: elements[i] }); + } + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var body = document.querySelector("body"); + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // needed for IE + var nodesArray = [].slice.call(mutation.addedNodes); + if (nodesArray.length > 0) { + nodesArray.forEach(function(addedNode) { + if (addedNode.querySelectorAll) { + var elementsArray = [].slice.call(addedNode.querySelectorAll(Accordion.selectors.self)); + elementsArray.forEach(function(element) { + new Accordion({ element: element }); + }); + } + }); + } + }); + }); + + observer.observe(body, { + subtree: true, + childList: true, + characterData: true + }); + } + + if (document.readyState !== "loading") { + onDocumentReady(); + } else { + document.addEventListener("DOMContentLoaded", onDocumentReady); + } +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/.content.xml index 65fb5291b4..f553b9b6e1 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/.content.xml @@ -18,4 +18,4 @@ jcr:primaryType="cq:ClientLibraryFolder" allowProxy="{Boolean}true" categories="[core.forms.components.accordion.v1.runtime]" - dependencies="[core.wcm.components.commons.site.container,core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.wcm.components.accordion.v1]"/> + dependencies="[core.wcm.components.commons.site.container,core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.forms.components.accordion.v1.commons]"/> diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js index 0b9df2d561..5a752c0cb4 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/accordion/v1/accordion/clientlibs/site/js/accordionview.js @@ -15,11 +15,246 @@ ******************************************************************************/ (function () { - class Accordion extends FormView.FormPanel { + const AccordionMixin = function AccordionMixin(Base) { + return class extends Base { + static NS = "cmp"; + static IS = "adaptiveFormAccordion"; + static bemBlock = 'cmp-accordion'; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]' + }; + + + static idSuffixes = { + item: "-item", + button: "-button", + panel: "-panel" + } + + static cacheKeys = { + buttonKey: "button", + panelKey: "panel", + itemKey: "item" + } + + static cssClasses = { + button: { + disabled: "cmp-accordion__button--disabled", + expanded: "cmp-accordion__button--expanded" + }, + panel: { + hidden: "cmp-accordion__panel--hidden", + expanded: "cmp-accordion__panel--expanded" + } + }; + + static delay = 100; + + static dataAttributes = { + item: { + expanded: "data-cmp-expanded" + } + }; + + constructor(params) { + super(params); + } + + getCachedItems() { + return (this._elements[this.constructor.cacheKeys.itemKey] != null) ? this._elements[this.constructor.cacheKeys.itemKey] : []; + } + + getCachedPanels() { + return this._elements[this.constructor.cacheKeys.panelKey]; + } + + getCachedButtons() { + return this._elements[this.constructor.cacheKeys.buttonKey] + } + + getItemById(itemId) { + var items = this.getCachedItems(); + if (items) { + for (var i = 0; i < items.length; i++) { + if (items[i].id === itemId) { + return items[i]; + } + } + } + } + + + /** + * Returns all expanded items. + * + * @private + * @returns {HTMLElement[]} The expanded items + */ + getExpandedItems() { + var expandedItems = []; + + for (var i = 0; i < this.getCachedItems().length; i++) { + var item = this.getCachedItems()[i]; + var expanded = this.isItemExpanded(item); + if (expanded) { + expandedItems.push(item); + } + } + + return expandedItems; + } + + /** + * Gets an item's expanded state. + * + * @private + * @param {HTMLElement} item The item for checking its expanded state + * @returns {Boolean} true if the item is expanded, false otherwise + */ + isItemExpanded(item) { + return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; + } + + /** + * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. + * + * @private + * @param {HTMLElement} wrapper The Accordion wrapper element + */ + cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + var hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]"); + + for (var i = 0; i < hooks.length; i++) { + var hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own accordion elements + var lowerCased = this.constructor.IS.toLowerCase(); + var capitalized = lowerCased.charAt(0).toUpperCase() + lowerCased.slice(1); + var key = hook.dataset[this.constructor.NS + "Hook" + capitalized]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + var tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + collapseAllOtherItems(itemId) { + var itemToToggle = this.getItemById(itemId); + var itemList = this.getCachedItems(); + for (var i = 0; i < itemList.length; i++) { + if (itemList[i] !== itemToToggle) { + var expanded = this.isItemExpanded(itemList[i]); + if (expanded) { + this.collapseItem(this.getCachedItems()[i]); + } + } + } + } + + /** + * General handler for toggle of an item. + * + * @private + * @param {Number} id The id of the item to toggle + */ + toggle(id) { + var itemToToggle = this.getItemById(id); + if (itemToToggle) { + (this.isItemExpanded(itemToToggle) === false) ? this.expandItem(itemToToggle) : this.collapseItem(itemToToggle); + } + } + + + /** + * Refreshes an item based on its expanded state. + * + * @private + * @param {HTMLElement} item The item to refresh + */ + refreshItem(item) { + var expanded = this.isItemExpanded(item); + if (expanded) { + this.expandItem(item); + } else { + this.collapseItem(item); + } + } + + /** + * Refreshes all items based on their expanded state. + * + * @private + */ + refreshItems() { + for (var i = 0; i < this.getCachedItems().length; i++) { + this.refreshItem(this.getCachedItems()[i]); + } + } + + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as expanded + */ + expandItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.setAttribute(this.constructor.dataAttributes.item.expanded, ""); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.add(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", true); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.expanded); + panel.classList.remove(this.constructor.cssClasses.panel.hidden); + panel.setAttribute("aria-hidden", false); + } + } + + /** + * Annotates the item and its internals with + * the necessary style and accessibility attributes to indicate it is not expanded. + * + * @private + * @param {HTMLElement} item The item to annotate as not expanded + */ + collapseItem(item) { + var index = this.getCachedItems().indexOf(item); + if (index > -1) { + item.removeAttribute(this.constructor.dataAttributes.item.expanded); + var button = this.getCachedButtons()[index]; + var panel = this.getCachedPanels()[index]; + button.classList.remove(this.constructor.cssClasses.button.expanded); + // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute + // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 + setTimeout(function () { + button.setAttribute("aria-expanded", false); + }, this.constructor.delay); + panel.classList.add(this.constructor.cssClasses.panel.hidden); + panel.classList.remove(this.constructor.cssClasses.panel.expanded); + panel.setAttribute("aria-hidden", true); + } + } + } + } + + + class Accordion extends AccordionMixin(FormView.FormPanel) { static NS = FormView.Constants.NS; - static IS = "adaptiveFormAccordion"; - static bemBlock = 'cmp-accordion'; static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible'; _templateHTML = {}; static selectors = { @@ -32,29 +267,6 @@ item: `.${Accordion.bemBlock}__item` }; - static idSuffixes = { - item: "-item", - button: "-button", - panel: "-panel" - } - - static cacheKeys = { - buttonKey: "button", - panelKey: "panel", - itemKey: "item" - } - - static cssClasses = { - button: { - disabled: "cmp-accordion__button--disabled", - expanded: "cmp-accordion__button--expanded" - }, - panel: { - hidden: "cmp-accordion__panel--hidden", - expanded: "cmp-accordion__panel--expanded" - } - }; - static keyCodes = { ENTER: 13, SPACE: 32, @@ -66,47 +278,21 @@ ARROW_DOWN: 40 }; - static delay = 100; - - static dataAttributes = { - item: { - expanded: "data-cmp-expanded" - } - }; - constructor(params) { super(params); const {element} = params; - this.#cacheElements(element); - if (this.#getCachedItems()) { - var expandedItems = this.#getExpandedItems(); + this.cacheElements(element); + if (this.getCachedItems()) { + var expandedItems = this.getExpandedItems(); // multiple expanded items annotated, display the last item open. if (expandedItems.length > 1) { var lastExpandedItem = expandedItems[expandedItems.length - 1] - this.#expandItem(lastExpandedItem); - this.#collapseAllOtherItems(lastExpandedItem.id); + this.expandItem(lastExpandedItem); + this.collapseAllOtherItems(lastExpandedItem.id); } - this.#refreshItems(); + this.refreshItems(); this.#bindEvents(); } - if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { - /* - * Editor message handling: - * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame - * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component - * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data - */ - window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); - var _self = this; - window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { - if (message.data && message.data.type === "cmp-accordion" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { - if (message.data.operation === "navigate") { - _self.#toggle(_self.#getCachedItems()[message.data.index].id); - _self.#collapseAllOtherItems(_self.#getCachedItems()[message.data.index].id); - } - } - }); - } } getClass() { @@ -141,98 +327,37 @@ super.setFocus(id); this.setActive(); this.#collapseAllItems(); - const item = this.#getItemById(id + '-item'); - this.#expandItem(item) + const item = this.getItemById(id + '-item'); + this.expandItem(item) } #collapseAllItems() { - var items = this.#getCachedItems(); + var items = this.getCachedItems(); if (items) { for (var i = 0; i < items.length; i++) { - if (this.#isItemExpanded(items[i])) { - this.#collapseItem(items[i]) + if (this.isItemExpanded(items[i])) { + this.collapseItem(items[i]) } } } } - /** - * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. - * - * @private - * @param {HTMLElement} wrapper The Accordion wrapper element - */ - #cacheElements(wrapper) { - this._elements = {}; - this._elements.self = wrapper; - var hooks = this._elements.self.querySelectorAll("[data-" + Accordion.NS + "-hook-" + Accordion.IS + "]"); - - for (var i = 0; i < hooks.length; i++) { - var hook = hooks[i]; - if (hook.closest("[data-cmp-is=" + Accordion.IS + "]") === this._elements.self) { // only process own accordion elements - var lowerCased = Accordion.IS.toLowerCase(); - var capitalized = lowerCased.charAt(0).toUpperCase() + lowerCased.slice(1); - var key = hook.dataset[Accordion.NS + "Hook" + capitalized]; - if (this._elements[key]) { - if (!Array.isArray(this._elements[key])) { - var tmp = this._elements[key]; - this._elements[key] = [tmp]; - } - this._elements[key].push(hook); - } else { - this._elements[key] = [hook]; - } - } - } - } - - /** - * Returns all expanded items. - * - * @private - * @returns {HTMLElement[]} The expanded items - */ - #getExpandedItems() { - var expandedItems = []; - - for (var i = 0; i < this.#getCachedItems().length; i++) { - var item = this.#getCachedItems()[i]; - var expanded = this.#isItemExpanded(item); - if (expanded) { - expandedItems.push(item); - } - } - - return expandedItems; - } - - /** - * Gets an item's expanded state. - * - * @private - * @param {HTMLElement} item The item for checking its expanded state - * @returns {Boolean} true if the item is expanded, false otherwise - */ - #isItemExpanded(item) { - return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; - } - /** * Binds Accordion event handling. * * @private */ #bindEvents() { - var buttons = this.#getCachedButtons(); + var buttons = this.getCachedButtons(); if (buttons) { var _self = this; for (var i = 0; i < buttons.length; i++) { (function (index) { buttons[index].addEventListener("click", function (event) { var itemDivId = _self.#convertToItemDivId(buttons[index].id); - _self.#toggle(itemDivId); - _self.#collapseAllOtherItems(itemDivId); + _self.toggle(itemDivId); + _self.collapseAllOtherItems(itemDivId); _self.#focusButton(buttons[index].id); }); buttons[index].addEventListener("keydown", function (event) { @@ -254,8 +379,8 @@ var button = this.#getButtonById(buttonId); button.addEventListener("click", function (event) { var itemDivId = _self.#convertToItemDivId(buttonId); - _self.#toggle(itemDivId); - _self.#collapseAllOtherItems(itemDivId); + _self.toggle(itemDivId); + _self.collapseAllOtherItems(itemDivId); _self.#focusButton(buttonId); }); button.addEventListener("keydown", function (event) { @@ -271,7 +396,7 @@ * @param {Number} id The id of the button triggering the event */ #onButtonKeyDown(event, id) { - var buttons = this.#getCachedButtons(); + var buttons = this.getCachedButtons(); var lastIndex = buttons.length - 1; var index = this.#getButtonIndexById(id); @@ -302,8 +427,8 @@ case Accordion.keyCodes.SPACE: event.preventDefault(); var itemDivId = this.#convertToItemDivId(buttons[index].id); - this.#toggle(itemDivId); - this.#collapseAllOtherItems(itemDivId); + this.toggle(itemDivId); + this.collapseAllOtherItems(itemDivId); this.#focusButton(buttons[index].id); break; default: @@ -311,96 +436,6 @@ } } - /** - * General handler for toggle of an item. - * - * @private - * @param {Number} id The id of the item to toggle - */ - #toggle(id) { - var itemToToggle = this.#getItemById(id); - if (itemToToggle) { - (this.#isItemExpanded(itemToToggle) === false) ? this.#expandItem(itemToToggle) : this.#collapseItem(itemToToggle); - } - } - - /** - * Refreshes an item based on its expanded state. - * - * @private - * @param {HTMLElement} item The item to refresh - */ - #refreshItem(item) { - var expanded = this.#isItemExpanded(item); - if (expanded) { - this.#expandItem(item); - } else { - this.#collapseItem(item); - } - } - - /** - * Refreshes all items based on their expanded state. - * - * @private - */ - #refreshItems() { - for (var i = 0; i < this.#getCachedItems().length; i++) { - this.#refreshItem(this.#getCachedItems()[i]); - } - } - - - /** - * Annotates the item and its internals with - * the necessary style and accessibility attributes to indicate it is expanded. - * - * @private - * @param {HTMLElement} item The item to annotate as expanded - */ - #expandItem(item) { - var index = this.#getCachedItems().indexOf(item); - if (index > -1) { - item.setAttribute(Accordion.dataAttributes.item.expanded, ""); - var button = this.#getCachedButtons()[index]; - var panel = this.#getCachedPanels()[index]; - button.classList.add(Accordion.cssClasses.button.expanded); - // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute - // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 - setTimeout(function () { - button.setAttribute("aria-expanded", true); - }, Accordion.delay); - panel.classList.add(Accordion.cssClasses.panel.expanded); - panel.classList.remove(Accordion.cssClasses.panel.hidden); - panel.setAttribute("aria-hidden", false); - } - } - - /** - * Annotates the item and its internals with - * the necessary style and accessibility attributes to indicate it is not expanded. - * - * @private - * @param {HTMLElement} item The item to annotate as not expanded - */ - #collapseItem(item) { - var index = this.#getCachedItems().indexOf(item); - if (index > -1) { - item.removeAttribute(Accordion.dataAttributes.item.expanded); - var button = this.#getCachedButtons()[index]; - var panel = this.#getCachedPanels()[index]; - button.classList.remove(Accordion.cssClasses.button.expanded); - // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute - // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 - setTimeout(function () { - button.setAttribute("aria-expanded", false); - }, Accordion.delay); - panel.classList.add(Accordion.cssClasses.panel.hidden); - panel.classList.remove(Accordion.cssClasses.panel.expanded); - panel.setAttribute("aria-hidden", true); - } - } - /** * Focuses the button at the provided index. * @@ -446,31 +481,31 @@ handleChildAddition(childView) { var itemDivToExpand; - this.#cacheElements(this._elements.self); + this.cacheElements(this._elements.self); this.#bindEventsToAddedChild(childView.id); if (childView.getInstanceManager().getModel().minOccur != undefined && childView.getInstanceManager().children.length > childView.getInstanceManager().getModel().minOccur) { - itemDivToExpand = this.#getItemById(childView.id + Accordion.idSuffixes.item); + itemDivToExpand = this.getItemById(childView.id + Accordion.idSuffixes.item); } else { //this will run at initial runtime loading when the repeatable panel is being added minOccur no of times. // in this case we want the focus to stay at first tab - itemDivToExpand = this.findFirstVisibleChild(this.#getCachedItems()); + itemDivToExpand = this.findFirstVisibleChild(this.getCachedItems()); } - this.#expandItem(itemDivToExpand); - this.#collapseAllOtherItems(itemDivToExpand.id); + this.expandItem(itemDivToExpand); + this.collapseAllOtherItems(itemDivToExpand.id); this.#showHideRepeatableButtons(childView.getInstanceManager()); } handleChildRemoval(removedInstanceView) { var removedAccordionItemDivId = removedInstanceView.element.id + Accordion.idSuffixes.item; - var removedAccordionItemDiv = this.#getItemById(removedAccordionItemDivId); + var removedAccordionItemDiv = this.getItemById(removedAccordionItemDivId); removedAccordionItemDiv.remove(); this.children.splice(this.children.indexOf(removedInstanceView), 1); - this.#cacheElements(this._elements.self); - var cachedItems = this.#getCachedItems(); + this.cacheElements(this._elements.self); + var cachedItems = this.getCachedItems(); if (cachedItems && cachedItems.length > 0) { var firstItem = cachedItems[0]; - this.#expandItem(firstItem); - this.#collapseAllOtherItems(firstItem.id); + this.expandItem(firstItem); + this.collapseAllOtherItems(firstItem.id); } this.#showHideRepeatableButtons(removedInstanceView.getInstanceManager()); } @@ -485,13 +520,13 @@ syncMarkupWithModel() { super.syncMarkupWithModel(); this.#syncLabel(); - for (var itemDiv of this.#getCachedItems()) { + for (var itemDiv of this.getCachedItems()) { this.#syncAccordionMarkup(itemDiv); } } getChildViewByIndex(index) { - var accordionPanels = this.#getCachedPanels(); + var accordionPanels = this.getCachedPanels(); var fieldId = accordionPanels[index].id.substring(0, accordionPanels[index].id.lastIndexOf("-")); return this.getChild(fieldId); } @@ -504,13 +539,13 @@ var closestRepeatableFieldInstanceManagerIds = this._templateHTML[instanceManagerId]['closestRepeatableFieldInstanceManagerIds']; var indexToInsert = this.getIndexToInsert(closestNonRepeatableFieldId, closestRepeatableFieldInstanceManagerIds); if (indexToInsert > 0) { - result.beforeViewElement = this.#getCachedItems()[indexToInsert - 1]; + result.beforeViewElement = this.getCachedItems()[indexToInsert - 1]; } else { result.parentElement = this.element; } } else { var previousInstanceElement = instanceManager.children[instanceIndex - 1].element; - var previousInstanceItemDiv = this.#getItemById(previousInstanceElement.id + Accordion.idSuffixes.item); + var previousInstanceItemDiv = this.getItemById(previousInstanceElement.id + Accordion.idSuffixes.item); result.beforeViewElement = previousInstanceItemDiv; } return result; @@ -555,51 +590,14 @@ if (childView.getInstanceManager() != null && (this._templateHTML == null || this._templateHTML[childView.getInstanceManager().getId()] == null)) { var accordionItemDivId = childView.element.id + Accordion.idSuffixes.item; var instanceManagerId = childView.getInstanceManager().getId(); - var accordionItemDiv = this.#getItemById(accordionItemDivId); + var accordionItemDiv = this.getItemById(accordionItemDivId); this._templateHTML[instanceManagerId] = {}; this._templateHTML[instanceManagerId]['accordionItemDiv'] = accordionItemDiv; } } - #collapseAllOtherItems(itemId) { - var itemToToggle = this.#getItemById(itemId); - var itemList = this.#getCachedItems(); - for (var i = 0; i < itemList.length; i++) { - if (itemList[i] !== itemToToggle) { - var expanded = this.#isItemExpanded(itemList[i]); - if (expanded) { - this.#collapseItem(this.#getCachedItems()[i]); - } - } - } - } - - - #getCachedItems() { - return (this._elements[Accordion.cacheKeys.itemKey] != null) ? this._elements[Accordion.cacheKeys.itemKey] : []; - } - - #getCachedPanels() { - return this._elements[Accordion.cacheKeys.panelKey]; - } - - #getCachedButtons() { - return this._elements[Accordion.cacheKeys.buttonKey] - } - - #getItemById(itemId) { - var items = this.#getCachedItems(); - if (items) { - for (var i = 0; i < items.length; i++) { - if (items[i].id === itemId) { - return items[i]; - } - } - } - } - #getButtonById(buttonId) { - var buttons = this.#getCachedButtons(); + var buttons = this.getCachedButtons(); if (buttons) { for (var i = 0; i < buttons.length; i++) { if (buttons[i].id === buttonId) { @@ -610,7 +608,7 @@ } #getButtonIndexById(buttonId) { - var buttons = this.#getCachedButtons(); + var buttons = this.getCachedButtons(); if (buttons) { for (var i = 0; i < buttons.length; i++) { if (buttons[i].id === buttonId) { @@ -652,17 +650,17 @@ } updateChildVisibility(visible, state) { - this.updateVisibilityOfNavigationElement(this.#getItemById(state.id + Accordion.idSuffixes.item), visible); + this.updateVisibilityOfNavigationElement(this.getItemById(state.id + Accordion.idSuffixes.item), visible); if (!visible) { - var expandedItems = this.#getExpandedItems(); + var expandedItems = this.getExpandedItems(); for (let i = 0; i < expandedItems.length; i++) { if (expandedItems[i].getAttribute(Accordion.DATA_ATTRIBUTE_VISIBLE) === 'false') { - this.#collapseItem(expandedItems[i]); + this.collapseItem(expandedItems[i]); } } - let child = this.findFirstVisibleChild(this.#getCachedItems()); + let child = this.findFirstVisibleChild(this.getCachedItems()); if (child) { - this.#expandItem(child); + this.expandItem(child); } } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_editConfig.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_editConfig.xml index fc6d62c32d..952ac1bf3c 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_editConfig.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/base/v1/base/_cq_editConfig.xml @@ -23,6 +23,12 @@ handler="CQ.FormsCoreComponents.editorhooks.viewQualifiedName" icon="viewSOMExpression" text="View Qualified Name"/> + - \ No newline at end of file + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/_cq_editConfig.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/_cq_editConfig.xml index 3e6675c4ea..0a97dd3be2 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/_cq_editConfig.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/button/v1/button/_cq_editConfig.xml @@ -23,6 +23,12 @@ handler="CQ.FormsCoreComponents.editorhooks.viewQualifiedName" icon="viewSOMExpression" text="View Qualified Name"/> + - \ No newline at end of file + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/qualifiedNameHook.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/qualifiedNameHook.js index 58277436e2..9feb8d6566 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/qualifiedNameHook.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/container/v2/container/clientlibs/editorhook/js/qualifiedNameHook.js @@ -41,4 +41,141 @@ }); }; -})(window, Granite.author, Coral); \ No newline at end of file + window.CQ.FormsCoreComponents.editorhooks.hasXfaScripts = function (editable) { + const result = $.ajax({ + type: 'GET', + async: false, + url: Granite.HTTP.externalize(editable.path + ".json"), + cache: false + }); + const json = result.responseJSON; + + if (json && json.dataRef?.startsWith('xfa[0]')) { + if (json['fd:xfaScripts']) { + try { + const scripts = JSON.parse(json['fd:xfaScripts']); + return scripts.length > 0; + } catch(e) { + console.error('Error parsing xfaScripts', e, json['fd:xfaScripts']); + } + } + } + return false + } + + window.CQ.FormsCoreComponents.editorhooks.viewXfaScripts = function (editable) { + fetch(Granite.HTTP.externalize(editable.path + ".json")).then(async function (resp) { + const json = await resp.json(); + // Assuming `resp` contains the JSON string with `fd:xfaScripts` + var xfaScripts = JSON.parse(json['fd:xfaScripts']); + var dialogContent = document.createElement('div'); + + // Create a Coral Table + var table = document.createElement('coral-table'); + + // Create the header + var thead = document.createElement('coral-table-head'); + var headerRow = document.createElement('coral-table-row'); + + var eventNameHeader = document.createElement('coral-table-headercell'); + eventNameHeader.textContent = 'Event Name'; + var eventContentHeader = document.createElement('coral-table-headercell'); + eventContentHeader.textContent = 'Event Content'; + var disableHeader = document.createElement('coral-table-headercell'); + disableHeader.textContent = 'Disable'; + + headerRow.appendChild(eventNameHeader); + headerRow.appendChild(eventContentHeader); + headerRow.appendChild(disableHeader); + thead.appendChild(headerRow); + table.appendChild(thead); + + // Populate the table with data from xfaScripts + var tbody = document.createElement('coral-table-body'); + xfaScripts.forEach(function(script) { + var row = document.createElement('coral-table-row'); + + var nameCell = document.createElement('coral-table-cell'); + nameCell.textContent = script.runAt === "server" ? `${script.activity}(server)` : script.activity; + var contentCell = document.createElement('coral-table-cell'); + contentCell.innerHTML = script.value.replaceAll("\n", "
"); + + var checkboxCell = document.createElement('coral-table-cell'); + var checkbox = new Coral.Checkbox(); + checkbox.name = 'disableCheckbox'; + checkbox.on('change', function() { + script.disabled = this.checked; + }); + checkboxCell.appendChild(checkbox); + checkbox.checked = !!script.disabled; + if (script.runAt === "server") { + checkbox.disabled = true; + } + row.appendChild(nameCell); + row.appendChild(contentCell); + row.appendChild(checkboxCell); + + tbody.appendChild(row); + }); + table.appendChild(tbody); + + dialogContent.appendChild(table); + + // Create the dialog + var dialog = new Coral.Dialog().set({ + id: 'xfaScriptsDialog', + header: { + innerHTML: 'XFA Scripts' + }, + content: { + innerHTML: '' + }, + footer: {}, + closable: "on" + }); + + // Add the table to the dialog content + //dialog.content.appendChild(dialogContent); + + var okButton = new Coral.Button(); + okButton.label.textContent = 'OK'; + okButton.variant = Coral.Button.variant.PRIMARY; + okButton.addEventListener('click', function() { + // Prepare the modified xfaScripts for POST request + var modifiedXfaScripts = JSON.stringify({ 'fd:xfaScripts': JSON.stringify(xfaScripts) }); + $.ajax({ + url: editable.path, + type: 'POST', + data: { + "_charset_" : "UTF-8", + ':operation': 'import', + ':contentType': 'json', + ':content': modifiedXfaScripts, + ':replaceProperties': true + }, + success: function(response) { + console.log('Successfully posted the data'); + dialog.remove(); + }, + error: function(xhr, status, error) { + console.error('Error posting the data', error); + dialog.remove(); + } + }); + }); + dialog.footer.appendChild(okButton); + +// Append and show the dialog + document.body.appendChild(dialog); + + // add a listener on dialog show event + dialog.on('coral-overlay:open', function() { + dialog.content.appendChild(dialogContent); + }); + dialog.show(); + + }) + return true; + }; + +})(window, Granite.author, Coral); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerview.js index bf4296e2fe..2eb7ff9c27 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/datepicker/v1/datepicker/clientlibs/site/js/datepickerview.js @@ -94,10 +94,12 @@ //setDisplayValue is required for cases where value remains same while focussing in and out. this.widgetObject.setDisplayValue(this._model.value); this.setInactive(); + this.triggerExit(); }, this.getWidget()); this.widgetObject.addEventListener('focus', (e) => { this.widgetObject.setValue(e.target.value); this.setActive(); + this.triggerEnter(); }, this.getWidget()); this.widgetObject.addEventListener('input', (e) => { if( e.target.value === '') { @@ -112,9 +114,11 @@ this.widget.addEventListener('blur', (e) => { this._model.value = e.target.value; this.setInactive(); + this.triggerExit(); }); this.widget.addEventListener('focus', (e) => { this.setActive(); + this.triggerEnter(); }); } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js index 83d5a5ff31..a4e6354de1 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/dropdown/v1/dropdown/clientlibs/site/js/dropdownview.js @@ -206,9 +206,11 @@ }); this.widget.addEventListener('focus', (e) => { this.setActive(); + this.triggerEnter(); }); this.widget.addEventListener('blur', (e) => { this.setInactive(); + this.triggerExit(); }); } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js index 81282127e8..92778bc463 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/emailinput/v1/emailinput/clientlibs/site/js/emailinputview.js @@ -74,10 +74,12 @@ this._model.value = e.target.value; this.setWidgetValueToDisplayValue(); this.setInactive(); + this.triggerExit(); }); this.widget.addEventListener('focus', (e) => { this.setActive(); this.setWidgetValueToModelValue(); + this.triggerEnter(); }); } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputview.js index 057ca8dbb8..f3fc4da4ff 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/numberinput/v1/numberinput/clientlibs/site/js/numberinputview.js @@ -100,12 +100,14 @@ this._model.value = e.target.value; if(this.element) { this.setInactive(); + this.triggerExit(); } }); } this.getWidget().addEventListener('focus', (e) => { if (this.element) { this.setActive(); + this.triggerEnter(); } }); } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/_cq_editConfig.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/_cq_editConfig.xml index b9cf9ad1bb..428607e492 100755 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/_cq_editConfig.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/panelcontainer/v1/panelcontainer/_cq_editConfig.xml @@ -25,5 +25,11 @@ handler="CQ.FormsCoreComponents.editorhooks.viewQualifiedName" icon="viewSOMExpression" text="View Qualified Name"/> + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/.content.xml new file mode 100644 index 0000000000..0e3dd79b23 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt new file mode 100644 index 0000000000..7cb945cd83 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2023 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +tabs.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js new file mode 100644 index 0000000000..be37e3d278 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/contentframe/js/tabs.js @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + const TabsMixin = window.Forms.CoreComponentsCommons.TabsMixin; + + class Tabs extends TabsMixin(class {}) { + static NS = "cmp"; + static IS = "adaptiveFormTabs"; + static bemBlock = "cmp-tabs"; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + active: { + tab: "cmp-tabs__tab--active", + tabpanel: "cmp-tabs__tabpanel--active" + }, + }; + + constructor(params) { + super(params, Tabs.NS, Tabs.IS, Tabs.selectors) + params.element.removeAttribute("data-" + Tabs.NS + "-is"); + + if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { + /* + * Editor message handling: + * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame + * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component + * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data + */ + CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); + var _self = this; + CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { + if (message.data && message.data.type === "cmp-tabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { + if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] !== undefined) { + _self.navigate(_self._elements["tab"][message.data.index].id); + } + } + }); + } + } + } + + /** + * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. + * + * @private + */ + function onDocumentReady() { + + var elements = document.querySelectorAll(Tabs.selectors.self); + for (var i = 0; i < elements.length; i++) { + new Tabs({ element: elements[i] }); + } + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var body = document.querySelector("body"); + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // needed for IE + var nodesArray = [].slice.call(mutation.addedNodes); + if (nodesArray.length > 0) { + nodesArray.forEach(function(addedNode) { + if (addedNode.querySelectorAll) { + var elementsArray = [].slice.call(addedNode.querySelectorAll(Tabs.selectors.self)); + elementsArray.forEach(function(element) { + new Tabs({ element: element }); + }); + } + }); + } + }); + }); + + observer.observe(body, { + subtree: true, + childList: true, + characterData: true + }); + } + + if (document.readyState !== "loading") { + onDocumentReady(); + } else { + document.addEventListener("DOMContentLoaded", onDocumentReady); + } +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js index 441ac40392..7a7495f340 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/clientlibs/site/js/tabs.js @@ -37,23 +37,6 @@ constructor(params) { super(params, Tabs.NS, Tabs.IS, Tabs.selectors); - if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { - /* - * Editor message handling: - * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame - * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component - * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data - */ - CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); - var _self = this; - CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { - if (message.data && message.data.type === "cmp-tabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { - if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] != undefined) { - _self.navigate(_self._elements["tab"][message.data.index].id); - } - } - }); - } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html index ab946ee8f5..c86f0a19c5 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/tabsontop/v1/tabsontop/tabsontop.html @@ -63,6 +63,6 @@ data-sly-test="${(wcmmode.edit || wcmmode.preview)}"> - + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js index d7d2142a60..e0c0d7e384 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/telephoneinput/v1/telephoneinput/clientlibs/site/js/telephoneinputview.js @@ -74,10 +74,12 @@ this._model.value = e.target.value; this.setWidgetValueToDisplayValue(); this.setInactive(); + this.triggerExit(); }); this.widget.addEventListener('focus', (e) => { this.setActive(); this.setWidgetValueToModelValue(); + this.triggerEnter(); }); } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js index 183b2fe4db..0fde87ba73 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/textinput/v1/textinput/clientlibs/site/js/textinputview.js @@ -66,18 +66,22 @@ } setModel(model) { + console.log(`setting model for ${model.qualifiedName}`) super.setModel(model); if (this.widget.value !== '') { this._model.value = this.widget.value; } this.widget.addEventListener('blur', (e) => { + console.log(`blur event on ${model.qualifiedName}`) this._model.value = e.target.value; this.setWidgetValueToDisplayValue(); this.setInactive(); + this.triggerExit(); }); this.widget.addEventListener('focus', (e) => { this.setActive(); this.setWidgetValueToModelValue(); + this.triggerEnter(); }); } } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml new file mode 100644 index 0000000000..edb9403aa3 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt new file mode 100644 index 0000000000..2702421cd2 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +verticaltabs.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js new file mode 100644 index 0000000000..4db1b36749 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/contentframe/js/verticaltabs.js @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + const TabsMixin = window.Forms.CoreComponentsCommons.TabsMixin; + + class VerticalTabs extends TabsMixin(class {}) { + static NS = "cmp"; + static IS = "adaptiveFormVerticalTabs"; + static bemBlock = "cmp-verticaltabs"; + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + active: { + tab: "cmp-verticaltabs__tab--active", + tabpanel: "cmp-verticaltabs__tabpanel--active" + }, + }; + + constructor(params) { + super(params, VerticalTabs.NS, VerticalTabs.IS, VerticalTabs.selectors) + params.element.removeAttribute("data-" + VerticalTabs.NS + "-is"); + + if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { + /* + * Editor message handling: + * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame + * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component + * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data + */ + CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); + var _self = this; + CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { + if (message.data && message.data.type === "cmp-verticaltabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { + if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] !== undefined) { + _self.navigate(_self._elements["tab"][message.data.index].id); + } + } + }); + } + } + } + + /** + * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. + * + * @private + */ + function onDocumentReady() { + + var elements = document.querySelectorAll(VerticalTabs.selectors.self); + for (var i = 0; i < elements.length; i++) { + new VerticalTabs({ element: elements[i] }); + } + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var body = document.querySelector("body"); + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // needed for IE + var nodesArray = [].slice.call(mutation.addedNodes); + if (nodesArray.length > 0) { + nodesArray.forEach(function(addedNode) { + if (addedNode.querySelectorAll) { + var elementsArray = [].slice.call(addedNode.querySelectorAll(VerticalTabs.selectors.self)); + elementsArray.forEach(function(element) { + new VerticalTabs({ element: element }); + }); + } + }); + } + }); + }); + + observer.observe(body, { + subtree: true, + childList: true, + characterData: true + }); + } + + if (document.readyState !== "loading") { + onDocumentReady(); + } else { + document.addEventListener("DOMContentLoaded", onDocumentReady); + } +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js index 3b272b089d..69667ae682 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/clientlibs/site/js/verticaltabs.js @@ -37,23 +37,6 @@ constructor(params) { super(params, VerticalTabs.NS, VerticalTabs.IS, VerticalTabs.selectors); - if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { - /* - * Editor message handling: - * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame - * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component - * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data - */ - CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); - var _self = this; - CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { - if (message.data && message.data.type === "cmp-verticaltabs" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { - if (message.data.operation === "navigate" && _self._elements["tab"][message.data.index] != undefined) { - _self.navigate(_self._elements["tab"][message.data.index].id); - } - } - }); - } } getClass() { @@ -63,7 +46,9 @@ setFocus(id) { super.setFocus(id); this.setActive(); - this.navigateAndFocusTab(id + '__tab'); + if(id) { + this.navigateAndFocusTab(id + '__tab'); + } } getWidget() { diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html index e6c810e3df..ea042f2561 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/verticaltabs/v1/verticaltabs/verticaltabs.html @@ -64,6 +64,6 @@ data-sly-test="${(wcmmode.edit || wcmmode.preview)}"> - + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml new file mode 100644 index 0000000000..118c0b592c --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt new file mode 100644 index 0000000000..90e023d251 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +common.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js new file mode 100644 index 0000000000..ea862b67d5 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/commons/js/common.js @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + function WizardMixin(Base) { + return class extends Base { + static NS = "cmp"; + static IS = "adaptiveFormWizard"; + static bemBlock = "cmp-adaptiveform-wizard"; + static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible'; + + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + active: { + tab: "cmp-adaptiveform-wizard__tab--active", + wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--active" + } + }; + + _active; + + constructor(params) { + super(params); + } + + /** + * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API + * + * @private + * @param {HTMLElement} wrapper The Tabs wrapper element + */ + cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + const hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]"); + + for (let i = 0; i < hooks.length; i++) { + let hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own tab elements + let key = hook.dataset[this.constructor.NS + "Hook" + "Adaptiveformwizard"]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + let tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + setActive(tabs) { + if (tabs) { + tabs[0].classList.add(this.constructor.selectors.active.tab); + } + } + + /** + * Returns the index of the active tab, if no tab is active returns 0 + * + * @param {Array} tabs Tab elements + * @returns {Number} Index of the active tab, 0 if none is active + */ + getActiveIndex(tabs) { + if (tabs) { + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].classList.contains(this.constructor.selectors.active.tab)) { + return i; + } + } + } + return 0; + } + + getCachedTabs() { + return this._elements["tab"]; + } + + getCachedWizardPanels() { + return this._elements["wizardpanel"] + } + + /** + * Navigates to the tab at the provided index + * + * @private + * @param {Number} index The index of the tab to navigate to + */ + navigate(index) { + this._active = index; + this.refreshActive(); + } + + /** + * Refreshes the tab markup based on the current {@code Tabs_active} index + * + * @private + */ + refreshActive() { + const wizardPanels = this.getCachedWizardPanels(); + const tabs = this.getCachedTabs(); + if (wizardPanels) { + for (let i = 0; i < wizardPanels.length; i++) { + if (i === parseInt(this._active)) { + wizardPanels[i].classList.add(this.constructor.selectors.active.wizardpanel); + wizardPanels[i].removeAttribute("aria-hidden"); + tabs[i].classList.add(this.constructor.selectors.active.tab); + tabs[i].setAttribute("aria-selected", true); + tabs[i].setAttribute("tabindex", "0"); + } else { + wizardPanels[i].classList.remove(this.constructor.selectors.active.wizardpanel); + wizardPanels[i].setAttribute("aria-hidden", true); + tabs[i].classList.remove(this.constructor.selectors.active.tab); + tabs[i].setAttribute("aria-selected", false); + tabs[i].setAttribute("tabindex", "-1"); + } + } + } + if (this.hideUnhideNavButtons) { + this.hideUnhideNavButtons(this._active); + } + } + } + } + + window.Forms = window.Forms || {}; + window.Forms.CoreComponentsCommons = window.Forms.CoreComponentsCommons || {}; + window.Forms.CoreComponentsCommons.WizardMixin = WizardMixin; + +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml new file mode 100644 index 0000000000..abbbd1c97a --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/.content.xml @@ -0,0 +1,6 @@ + + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt new file mode 100644 index 0000000000..fd5429b6f7 --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js.txt @@ -0,0 +1,18 @@ +############################################################################### +# Copyright 2024 Adobe +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### + +#base=js +wizard.js \ No newline at end of file diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js new file mode 100644 index 0000000000..bb8a2932ef --- /dev/null +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/contentframe/js/wizard.js @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + + +(function () { + + const WizardMixin = window.Forms.CoreComponentsCommons.WizardMixin; + + class Wizard extends WizardMixin(class {}) { + + constructor(params) { + super(params); + const {element} = params; + this.cacheElements(element); + this.setActive(this.getCachedTabs()) + this._active = this.getActiveIndex(this.getCachedTabs()); + this.refreshActive(); + + element.removeAttribute("data-" + Wizard.NS + "-is"); + + if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { + /* + * Editor message handling: + * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame + * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component + * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data + */ + CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); + const _self = this; + CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { + if (message.data && message.data.type === "cmp-adaptiveform-wizard" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { + if (message.data.operation === "navigate") { + _self.navigate(message.data.index); + } + } + }); + } + } + } + + /** + * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. + * + * @private + */ + function onDocumentReady() { + + var elements = document.querySelectorAll(Wizard.selectors.self); + for (var i = 0; i < elements.length; i++) { + new Wizard({ element: elements[i] }); + } + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + var body = document.querySelector("body"); + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + // needed for IE + var nodesArray = [].slice.call(mutation.addedNodes); + if (nodesArray.length > 0) { + nodesArray.forEach(function(addedNode) { + if (addedNode.querySelectorAll) { + var elementsArray = [].slice.call(addedNode.querySelectorAll(Wizard.selectors.self)); + elementsArray.forEach(function(element) { + new Wizard({ element: element }); + }); + } + }); + } + }); + }); + + observer.observe(body, { + subtree: true, + childList: true, + characterData: true + }); + } + + if (document.readyState !== "loading") { + onDocumentReady(); + } else { + document.addEventListener("DOMContentLoaded", onDocumentReady); + } +}()); diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml index d1a7d42c9a..096e638e9a 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/.content.xml @@ -18,5 +18,5 @@ jcr:primaryType="cq:ClientLibraryFolder" allowProxy="{Boolean}true" categories="[core.forms.components.wizard.v1.runtime]" - dependencies="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.wcm.components.commons.site.container]" + dependencies="[core.forms.components.runtime.base,core.forms.components.container.v2.runtime,core.wcm.components.commons.site.container,core.forms.components.wizard.v1.commons]" /> diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js index c86095258a..52590a86dc 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/clientlibs/site/js/wizardview.js @@ -25,20 +25,136 @@ ARROW_DOWN: 40 }; + const WizardMixin = function WizardMixin(Base) { + return class extends Base { + static NS = "cmp"; + static IS = "adaptiveFormWizard"; + static bemBlock = "cmp-adaptiveform-wizard"; + static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible'; + + static selectors = { + self: "[data-" + this.NS + '-is="' + this.IS + '"]', + active: { + tab: "cmp-adaptiveform-wizard__tab--active", + wizardpanel: "cmp-adaptiveform-wizard__wizardpanel--active" + } + }; + + _active; + + constructor(params) { + super(params); + } + + /** + * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API + * + * @private + * @param {HTMLElement} wrapper The Tabs wrapper element + */ + cacheElements(wrapper) { + this._elements = {}; + this._elements.self = wrapper; + const hooks = this._elements.self.querySelectorAll("[data-" + this.constructor.NS + "-hook-" + this.constructor.IS + "]"); + + for (let i = 0; i < hooks.length; i++) { + let hook = hooks[i]; + if (hook.closest("[data-cmp-is=" + this.constructor.IS + "]") === this._elements.self) { // only process own tab elements + let key = hook.dataset[this.constructor.NS + "Hook" + "Adaptiveformwizard"]; + if (this._elements[key]) { + if (!Array.isArray(this._elements[key])) { + let tmp = this._elements[key]; + this._elements[key] = [tmp]; + } + this._elements[key].push(hook); + } else { + this._elements[key] = [hook]; + } + } + } + } + + setActive(tabs) { + if (tabs) { + tabs[0].classList.add(this.constructor.selectors.active.tab); + } + } + + /** + * Returns the index of the active tab, if no tab is active returns 0 + * + * @param {Array} tabs Tab elements + * @returns {Number} Index of the active tab, 0 if none is active + */ + getActiveIndex(tabs) { + if (tabs) { + for (let i = 0; i < tabs.length; i++) { + if (tabs[i].classList.contains(this.constructor.selectors.active.tab)) { + return i; + } + } + } + return 0; + } + + getCachedTabs() { + return this._elements["tab"]; + } + + getCachedWizardPanels() { + return this._elements["wizardpanel"] + } + + /** + * Navigates to the tab at the provided index + * + * @private + * @param {Number} index The index of the tab to navigate to + */ + navigate(index) { + this._active = index; + this.refreshActive(); + } + + /** + * Refreshes the tab markup based on the current {@code Tabs_active} index + * + * @private + */ + refreshActive() { + const wizardPanels = this.getCachedWizardPanels(); + const tabs = this.getCachedTabs(); + if (wizardPanels) { + for (let i = 0; i < wizardPanels.length; i++) { + if (i === parseInt(this._active)) { + wizardPanels[i].classList.add(this.constructor.selectors.active.wizardpanel); + wizardPanels[i].removeAttribute("aria-hidden"); + tabs[i].classList.add(this.constructor.selectors.active.tab); + tabs[i].setAttribute("aria-selected", true); + tabs[i].setAttribute("tabindex", "0"); + } else { + wizardPanels[i].classList.remove(this.constructor.selectors.active.wizardpanel); + wizardPanels[i].setAttribute("aria-hidden", true); + tabs[i].classList.remove(this.constructor.selectors.active.tab); + tabs[i].setAttribute("aria-selected", false); + tabs[i].setAttribute("tabindex", "-1"); + } + } + } + if (this.hideUnhideNavButtons) { + this.hideUnhideNavButtons(this._active); + } + } + } + } - class Wizard extends FormView.FormPanel { + class Wizard extends WizardMixin(FormView.FormPanel) { _templateHTML = {}; - #_active; static NS = FormView.Constants.NS; - static IS = "adaptiveFormWizard"; - static bemBlock = "cmp-adaptiveform-wizard"; static #tabIdSuffix = "_wizard-item-nav"; static #wizardPanelIdSuffix = "__wizardpanel"; - maxEnabledTab = 0; - minEnabledTab = 0; - static DATA_ATTRIBUTE_VISIBLE = 'data-cmp-visible'; static selectors = { self: "[data-" + Wizard.NS + '-is="' + Wizard.IS + '"]', @@ -63,59 +179,14 @@ constructor(params) { super(params); const {element} = params; - this.#cacheElements(element); - this.#setActive(this.#getCachedTabs()) - this.#_active = this.#getActiveIndex(this.#getCachedTabs()); + this.cacheElements(element); + this.setActive(this.getCachedTabs()) + this._active = this.getActiveIndex(this.getCachedTabs()); this.#setNavigationRange(); - this.#hideUnhideNavButtons(this.#_active); - this.#refreshActive(); + this.#hideUnhideNavButtons(this._active); + this.refreshActive(); this.#bindEvents(); - if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { - /* - * Editor message handling: - * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame - * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component - * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data - */ - CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); - const _self = this; - CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function (message) { - if (message.data && message.data.type === "cmp-adaptiveform-wizard" && message.data.id === _self._elements.self.dataset["cmpPanelcontainerId"]) { - if (message.data.operation === "navigate") { - _self.#navigate(message.data.index); - } - } - }); - } - } - - /** - * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API - * - * @private - * @param {HTMLElement} wrapper The Tabs wrapper element - */ - #cacheElements(wrapper) { - this._elements = {}; - this._elements.self = wrapper; - const hooks = this._elements.self.querySelectorAll("[data-" + Wizard.NS + "-hook-" + Wizard.IS + "]"); - - for (let i = 0; i < hooks.length; i++) { - let hook = hooks[i]; - if (hook.closest("[data-cmp-is=" + Wizard.IS + "]") === this._elements.self) { // only process own tab elements - let key = hook.dataset[Wizard.NS + "Hook" + "Adaptiveformwizard"]; - if (this._elements[key]) { - if (!Array.isArray(this._elements[key])) { - let tmp = this._elements[key]; - this._elements[key] = [tmp]; - } - this._elements[key].push(hook); - } else { - this._elements[key] = [hook]; - } - } - } } getClass() { @@ -126,7 +197,7 @@ super.setFocus(id); this.setActive(); const index = this.#getTabIndexById(id + '_wizard-item-nav'); - this.#navigate(index); + this.navigate(index); } getWidget() { @@ -183,30 +254,6 @@ } - /** - * Returns the index of the active tab, if no tab is active returns 0 - * - * @param {Array} tabs Tab elements - * @returns {Number} Index of the active tab, 0 if none is active - */ - #getActiveIndex(tabs) { - if (tabs) { - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].classList.contains(Wizard.selectors.active.tab)) { - return i; - } - } - } - return 0; - } - - - #setActive(tabs) { - if (tabs) { - tabs[0].classList.add(Wizard.selectors.active.tab); - } - } - /** * Handles tab keydown events * @@ -214,9 +261,9 @@ * @param {Object} event The keydown event */ #onKeyDown(event) { - const index = this.#_active; + const index = this._active; - const lastIndex = this.#getCachedTabs().length - 1; + const lastIndex = this.getCachedTabs().length - 1; switch (event.keyCode) { case keyCodes.ARROW_LEFT: @@ -247,31 +294,13 @@ } /** - * Refreshes the tab markup based on the current {@code Tabs#_active} index + * Refreshes the tab markup based on the current {@code Tabs_active} index * * @private */ - #refreshActive() { - const wizardPanels = this.#getCachedWizardPanels(); - const tabs = this.#getCachedTabs(); - if (wizardPanels) { - for (let i = 0; i < wizardPanels.length; i++) { - if (i === parseInt(this.#_active)) { - wizardPanels[i].classList.add(Wizard.selectors.active.wizardpanel); - wizardPanels[i].removeAttribute(FormView.Constants.ARIA_HIDDEN); - tabs[i].classList.add(Wizard.selectors.active.tab); - tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, true); - tabs[i].setAttribute(FormView.Constants.TABINDEX, "0"); - } else { - wizardPanels[i].classList.remove(Wizard.selectors.active.wizardpanel); - wizardPanels[i].setAttribute(FormView.Constants.ARIA_HIDDEN, true); - tabs[i].classList.remove(Wizard.selectors.active.tab); - tabs[i].setAttribute(FormView.Constants.ARIA_SELECTED, false); - tabs[i].setAttribute(FormView.Constants.TABINDEX, "-1"); - } - } - } - this.#hideUnhideNavButtons(this.#_active); + refreshActive() { + super.refreshActive(); + this.#hideUnhideNavButtons(this._active); } /** @@ -288,8 +317,8 @@ #navigateToNextTab() { - const activeIndex = this.#_active; - const activeTabElement = this.#getCachedTabs()[activeIndex]; + const activeIndex = this._active; + const activeTabElement = this.getCachedTabs()[activeIndex]; const activeChildId = activeTabElement.id.substring(0, activeTabElement.id.lastIndexOf(Wizard.#tabIdSuffix)); const activeChildView = this.getChild(activeChildId); let activeChildModel; @@ -304,13 +333,13 @@ validationErrorList = activeChildModel.validate(); } if (validationErrorList === undefined || validationErrorList.length == 0) { - let tabs = this.#getCachedTabs(); + let tabs = this.getCachedTabs(); let nextVisibleIndex = this.#findNextVisibleChildIndex(activeIndex); if (tabs && nextVisibleIndex >= 0) { this.#navigateAndFocusTab(nextVisibleIndex); } } - this.#hideUnhideNavButtons(this.#_active); + this.#hideUnhideNavButtons(this._active); } #isAuthoring() { @@ -318,13 +347,13 @@ } #navigateToPreviousTab() { - const activeIndex = this.#_active; - const tabs = this.#getCachedTabs(); + const activeIndex = this._active; + const tabs = this.getCachedTabs(); const lastVisibleIndex = this.#findLastVisibleChildIndex(activeIndex); if (tabs && lastVisibleIndex >= 0) { this.#navigateAndFocusTab(lastVisibleIndex); } - this.#hideUnhideNavButtons(this.#_active); + this.#hideUnhideNavButtons(this._active); } /** @@ -335,7 +364,7 @@ * @param {Number} total number of tabs */ #hideUnhideNavButtons(activeTabIndex) { - const tabsLength = this.#getCachedTabs() ? this.#getCachedTabs().length : 0; + const tabsLength = this.getCachedTabs() ? this.getCachedTabs().length : 0; const nextVisible = this.#findNextVisibleChildIndex(activeTabIndex); const previousVisible = this.#findLastVisibleChildIndex(activeTabIndex); @@ -362,26 +391,26 @@ } #setNavigationRange() { - const wizardPanels = this.#getCachedWizardPanels(); + const wizardPanels = this.getCachedWizardPanels(); if(wizardPanels) { this.maxEnabledTab = wizardPanels.length-1; this.minEnabledTab = 0; for (let i = 0; i < wizardPanels.length; i++) { - if(!this.#childComponentVisible(this.#getCachedWizardPanels()[i])) { + if(!this.#childComponentVisible(this.getCachedWizardPanels()[i])) { this.minEnabledTab = i+1; } else { break; } } for (let i = wizardPanels.length - 1; i >= 0; i--) { - if(!this.#childComponentVisible(this.#getCachedWizardPanels()[i])) { + if(!this.#childComponentVisible(this.getCachedWizardPanels()[i])) { this.maxEnabledTab = i; } else { break; } } this.minEnabledTab = Math.max(0, this.minEnabledTab); - this.maxEnabledTab = Math.min(this.#getCachedTabs().length-1, this.maxEnabledTab); + this.maxEnabledTab = Math.min(this.getCachedTabs().length-1, this.maxEnabledTab); } } @@ -390,7 +419,7 @@ } #findNextVisibleChildIndex(currentIndex) { - const tabs = this.#getCachedTabs(); + const tabs = this.getCachedTabs(); const tabsLength = tabs? tabs.length : 0; for (let i = currentIndex + 1; i < tabsLength; i++) { let isVisible = tabs[i].getAttribute(Wizard.DATA_ATTRIBUTE_VISIBLE); @@ -402,7 +431,7 @@ } #findLastVisibleChildIndex(currentIndex) { - const tabs = this.#getCachedTabs(); + const tabs = this.getCachedTabs(); if(tabs) { for (let i = currentIndex - 1; i >= 0; i--) { let isVisible = tabs[i].getAttribute(Wizard.DATA_ATTRIBUTE_VISIBLE); @@ -414,18 +443,6 @@ return -1; } - - /** - * Navigates to the tab at the provided index - * - * @private - * @param {Number} index The index of the tab to navigate to - */ - #navigate(index) { - this.#_active = index; - this.#refreshActive(); - } - /** * Navigates to the item at the provided index and ensures the active tab gains focus * @@ -433,13 +450,13 @@ * @param {Number} index The index of the item to navigate to */ #navigateAndFocusTab(index) { - this.#navigate(index); - this.focusWithoutScroll(this.#getCachedTabs()[index]); + this.navigate(index); + this.focusWithoutScroll(this.getCachedTabs()[index]); } #syncWizardNavLabels() { - const tabs = this.#getCachedTabs(); - const wizardPanels = this.#getCachedWizardPanels(); + const tabs = this.getCachedTabs(); + const wizardPanels = this.getCachedWizardPanels(); if (tabs) { for (let i = 0; i < tabs.length; i++) { let id = wizardPanels[i].querySelectorAll("[data-cmp-is]")[0].id; @@ -450,7 +467,7 @@ } #syncWizardPanels() { - const wizardPanels = this.#getCachedWizardPanels(); + const wizardPanels = this.getCachedWizardPanels(); if (wizardPanels) { for (let i = 0; i < wizardPanels.length; i++) { let id = wizardPanels[i].querySelectorAll("[data-cmp-is]")[0].id; @@ -482,7 +499,7 @@ let tabListParentElement = this.#getTabListElement(); tabListParentElement.insertBefore(navigationTabToBeRepeated, tabListParentElement.firstChild); } else { - let beforeElement = this.#getCachedTabs()[indexToInsert - 1]; + let beforeElement = this.getCachedTabs()[indexToInsert - 1]; beforeElement.after(navigationTabToBeRepeated); } } else { @@ -490,10 +507,10 @@ let beforeElement = this.#getTabNavElementById(beforeTabNavElementId); beforeElement.after(navigationTabToBeRepeated); } - this.#cacheElements(this._elements.self); + this.cacheElements(this._elements.self); let repeatedWizardPanel = this.#getWizardPanelElementById(childView.id + Wizard.#wizardPanelIdSuffix); repeatedWizardPanel.setAttribute("aria-labelledby", childView.id + Wizard.#tabIdSuffix); - this.#refreshActive(); + this.refreshActive(); this.#getTabIndexById(); if (childView.getInstanceManager().getModel().minOccur != undefined && childView.getInstanceManager().children.length > childView.getInstanceManager().getModel().minOccur) { this.#navigateAndFocusTab(this.#getTabIndexById(navigationTabToBeRepeated.id)); @@ -509,9 +526,9 @@ tabNavElement.remove(); wizardPanelElement.remove(); this.children.splice(this.children.indexOf(removedInstanceView), 1); - this.#cacheElements(this._elements.self); - this.#_active = this.#getActiveIndex(this.#getCachedTabs()); - this.#refreshActive(); + this.cacheElements(this._elements.self); + this._active = this.getActiveIndex(this.getCachedTabs()); + this.refreshActive(); } addChild(childView) { @@ -523,11 +540,11 @@ this.handleHiddenChildrenVisibility(); } this.#setNavigationRange(); - this.#hideUnhideNavButtons(this.#_active); + this.#hideUnhideNavButtons(this._active); } getChildViewByIndex(index) { - let wizardPanels = this.#getCachedWizardPanels(); + let wizardPanels = this.getCachedWizardPanels(); let fieldId = wizardPanels[index].id.substring(0, wizardPanels[index].id.lastIndexOf("__")); return this.getChild(fieldId); } @@ -561,16 +578,8 @@ } } - #getCachedTabs() { - return this._elements["tab"]; - } - - #getCachedWizardPanels() { - return this._elements["wizardpanel"] - } - #getTabNavElementById(tabId) { - let tabs = this.#getCachedTabs(); + let tabs = this.getCachedTabs(); if (tabs) { for (let i = 0; i < tabs.length; i++) { if (tabs[i].id === tabId) { @@ -581,7 +590,7 @@ } #getWizardPanelElementById(wizardPanelId) { - let wizardPanels = this.#getCachedWizardPanels(); + let wizardPanels = this.getCachedWizardPanels(); if (wizardPanels) { for (let i = 0; i < wizardPanels.length; i++) { if (wizardPanels[i].id === wizardPanelId) { @@ -592,7 +601,7 @@ } #getTabIndexById(tabId) { - let tabs = this.#getCachedTabs(); + let tabs = this.getCachedTabs(); if (tabs) { for (let i = 0; i < tabs.length; i++) { if (tabs[i].id === tabId) { @@ -610,7 +619,7 @@ let closestNonRepeatableFieldId = this._templateHTML[instanceManagerId]['closestNonRepeatableFieldId']; let closestRepeatableFieldInstanceManagerIds = this._templateHTML[instanceManagerId]['closestRepeatableFieldInstanceManagerIds']; let indexToInsert = this.getIndexToInsert(closestNonRepeatableFieldId, closestRepeatableFieldInstanceManagerIds); - let wizardPanels = this.#getCachedWizardPanels(); + let wizardPanels = this.getCachedWizardPanels(); if (indexToInsert > 0) { result.beforeViewElement = this.#getWizardPanelElementById(wizardPanels[indexToInsert - 1].id); } else { @@ -619,18 +628,18 @@ } else { let previousInstanceElement = instanceManager.children[instanceIndex - 1].element; let previousInstanceWizardPanelIndex = this.#getTabIndexById(previousInstanceElement.id + Wizard.#tabIdSuffix); - result.beforeViewElement = this.#getCachedWizardPanels()[previousInstanceWizardPanelIndex]; + result.beforeViewElement = this.getCachedWizardPanels()[previousInstanceWizardPanelIndex]; } return result; } updateChildVisibility(visible, state) { this.updateVisibilityOfNavigationElement(this.#getTabNavElementById(state.id + Wizard.#tabIdSuffix), visible); - let activeTabNavElement = this.#getCachedTabs()[this.#_active]; + let activeTabNavElement = this.getCachedTabs()[this._active]; this.#setNavigationRange(); - this.#hideUnhideNavButtons(this.#_active); + this.#hideUnhideNavButtons(this._active); if (!visible && activeTabNavElement.id === state.id + Wizard.#tabIdSuffix) { - let child = this.findFirstVisibleChild(this.#getCachedTabs()); + let child = this.findFirstVisibleChild(this.getCachedTabs()); if (child) { this.#navigateAndFocusTab(this.#getTabIndexById(child.id)); } diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html index 2e0967c8ec..565ee7130d 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v1/wizard/wizard.html @@ -77,6 +77,6 @@ - + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html index a052d40b54..b377027624 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/form/wizard/v2/wizard/wizard.html @@ -78,6 +78,6 @@ - + diff --git a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/page/v1/page/customfooterlibs.html b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/page/v1/page/customfooterlibs.html index 268a57ac5c..41224af368 100644 --- a/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/page/v1/page/customfooterlibs.html +++ b/ui.af.apps/src/main/content/jcr_root/apps/core/fd/components/page/v1/page/customfooterlibs.html @@ -19,6 +19,8 @@ + + diff --git a/ui.frontend/package-lock.json b/ui.frontend/package-lock.json index 7902ea8494..c6e45ace70 100644 --- a/ui.frontend/package-lock.json +++ b/ui.frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "Apache-2.0", "dependencies": { - "@aemforms/af-core": "^0.22.80", + "@aemforms/af-core": "file:///Users/varundua/codebase/af2-web-runtime/packages/forms-next-core", "@aemforms/af-custom-functions": "1.0.7", "@aemforms/af-formatters": "^0.22.80" }, @@ -26,6 +26,40 @@ "webpack-merge": "^5.8.0" } }, + "../../af2-web-runtime/packages/forms-next-core": { + "name": "@aemforms/af-core-xfa", + "version": "0.1.0", + "license": "Adobe Proprietary", + "dependencies": { + "@adobe/json-formula": "0.1.50", + "@aemforms/af-formatters": "^0.22.82" + }, + "devDependencies": { + "@babel/preset-env": "^7.20.2", + "@types/jest": "29.2.4", + "@types/lodash": "^4.14.171", + "@typescript-eslint/eslint-plugin": "^4.28.2", + "@typescript-eslint/parser": "^4.28.2", + "babel-jest": "^29.4.1", + "blob-polyfill": "^7.0.20220408", + "eslint": "^7.30.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jest": "^24.3.6", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "form-data": "^4.0.0", + "jest": "29.3", + "jest-environment-jsdom": "^29.3.1", + "jest-junit": "^12.2.0", + "nock": "^13.1.3", + "node-fetch": "^2.6.1", + "ts-jest": "29.0", + "typedoc": "0.22.11", + "typedoc-plugin-markdown": "3.11.13", + "typescript": "^4.3.5" + } + }, "../../af2-web-runtime/packages/forms-next-formatters": { "name": "@aemforms/af-formatters", "version": "0.22.75", @@ -52,22 +86,9 @@ "typedoc-plugin-markdown": "3.11.13" } }, - "node_modules/@adobe/json-formula": { - "version": "0.1.50", - "resolved": "https://registry.npmjs.org/@adobe/json-formula/-/json-formula-0.1.50.tgz", - "integrity": "sha512-dmlLYfbty8NPVIdxvI9cJ+ZdXsrRCFrCdmL1+aR2auEzXJ86rD0bm1qu+S4NOpFiZLKIyx0zvUTykms40vNjsA==", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aemforms/af-core": { - "version": "0.22.80", - "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.80.tgz", - "integrity": "sha512-jbzx7eGV33GhG5FOQFa+3EzQ38TI/9U8YDj/wIHrL23PiUf3oMtdo/agWmgjJN6D+J/WRotkLiervWG3jpphHw==", - "dependencies": { - "@adobe/json-formula": "0.1.50", - "@aemforms/af-formatters": "^0.22.80" - } + "resolved": "../../af2-web-runtime/packages/forms-next-core", + "link": true }, "node_modules/@aemforms/af-custom-functions": { "version": "1.0.7", @@ -11070,18 +11091,34 @@ } }, "dependencies": { - "@adobe/json-formula": { - "version": "0.1.50", - "resolved": "https://registry.npmjs.org/@adobe/json-formula/-/json-formula-0.1.50.tgz", - "integrity": "sha512-dmlLYfbty8NPVIdxvI9cJ+ZdXsrRCFrCdmL1+aR2auEzXJ86rD0bm1qu+S4NOpFiZLKIyx0zvUTykms40vNjsA==" - }, "@aemforms/af-core": { - "version": "0.22.80", - "resolved": "https://registry.npmjs.org/@aemforms/af-core/-/af-core-0.22.80.tgz", - "integrity": "sha512-jbzx7eGV33GhG5FOQFa+3EzQ38TI/9U8YDj/wIHrL23PiUf3oMtdo/agWmgjJN6D+J/WRotkLiervWG3jpphHw==", + "version": "file:../../af2-web-runtime/packages/forms-next-core", "requires": { "@adobe/json-formula": "0.1.50", - "@aemforms/af-formatters": "^0.22.80" + "@aemforms/af-formatters": "^0.22.82", + "@babel/preset-env": "^7.20.2", + "@types/jest": "29.2.4", + "@types/lodash": "^4.14.171", + "@typescript-eslint/eslint-plugin": "^4.28.2", + "@typescript-eslint/parser": "^4.28.2", + "babel-jest": "^29.4.1", + "blob-polyfill": "^7.0.20220408", + "eslint": "^7.30.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jest": "^24.3.6", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "form-data": "^4.0.0", + "jest": "29.3", + "jest-environment-jsdom": "^29.3.1", + "jest-junit": "^12.2.0", + "nock": "^13.1.3", + "node-fetch": "^2.6.1", + "ts-jest": "29.0", + "typedoc": "0.22.11", + "typedoc-plugin-markdown": "3.11.13", + "typescript": "^4.3.5" } }, "@aemforms/af-custom-functions": { diff --git a/ui.frontend/package.json b/ui.frontend/package.json index 2fd0d82743..2d60fed872 100644 --- a/ui.frontend/package.json +++ b/ui.frontend/package.json @@ -23,7 +23,7 @@ "webpack-merge": "^5.8.0" }, "dependencies": { - "@aemforms/af-core": "^0.22.80", + "@aemforms/af-core": "file:///Users/varundua/codebase/af2-web-runtime/packages/forms-next-core", "@aemforms/af-formatters": "^0.22.80", "@aemforms/af-custom-functions": "1.0.7" } diff --git a/ui.frontend/src/handleXfa.js b/ui.frontend/src/handleXfa.js new file mode 100644 index 0000000000..f1a6468b58 --- /dev/null +++ b/ui.frontend/src/handleXfa.js @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright 2024 Adobe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +export function loadXfa(formdom, renderContext) { + if (window.xfalib) { + formBridge.registerConfig("disabledServerScripts", ["initialize", "$formready", "$layoutready"]) + const xfaJson = JSON.parse(JSON.parse(JSON.stringify(formdom))); + xfalib.runtime.renderContext = JSON.parse(JSON.parse(JSON.stringify(renderContext))); + xfalib.script.XfaModelRegistry.prototype.createModel(xfaJson); + //initialize Acrobat specific scripts + new xfalib.acrobat.Acrobat(); + return function (model) { + model._syncXfaProps(); + xfalib.runtime.xfa.form._initialize(true); + $(window).trigger("XfaInitialized"); + } + } +} diff --git a/ui.frontend/src/utils.js b/ui.frontend/src/utils.js index 95c5dd3789..3d19e2a65f 100644 --- a/ui.frontend/src/utils.js +++ b/ui.frontend/src/utils.js @@ -18,7 +18,7 @@ import {Constants} from "./constants.js"; import HTTPAPILayer from "./HTTPAPILayer.js"; import {customFunctions} from "./customFunctions.js"; import {FunctionRuntime} from '@aemforms/af-core' - +import {loadXfa} from "./handleXfa"; /** * @module FormView @@ -172,7 +172,7 @@ class Utils { console.debug("Initializing field views ", formContainer); Object.values(Utils.#fieldCreatorOrder).forEach(function (fieldSelectorInCreator) { const {fieldSelector, fieldCreator, fieldClass} = Utils.#fieldCreatorSets[fieldSelectorInCreator]; - console.debug("Initializing all fields of field selector ", fieldSelector); + //console.debug("Initializing all fields of field selector ", fieldSelector); let fieldElements = document.querySelectorAll(fieldSelector); Utils.#createFormContainerFields(fieldElements, fieldCreator, formContainer); Utils.registerMutationObserver(formContainer, fieldCreator, fieldSelector, fieldClass); @@ -307,7 +307,18 @@ class Utils { if (_path == null) { console.error(`data-${Constants.NS}-${formContainerClass}-path attribute is not present in the HTML element. Form cannot be initialized` ) } else { - const _formJson = await HTTPAPILayer.getFormDefinition(_path, _pageLang); + const loader = elements[i].parentElement?.querySelector('[data-cmp-adaptiveform-container-loader]'); + let _formJson, callback; + if (loader) { + const id = loader.getAttribute('data-cmp-adaptiveform-container-loader'); + const response = await fetch(`/adobe/forms/af/${id}`) + _formJson = (await response.json()).afModelDefinition; + _formJson.id = id; + //window.formJson = _formJson + callback = loadXfa(_formJson.formdom, _formJson.xfaRenderContext); + } else { + _formJson = await HTTPAPILayer.getFormDefinition(_path, _pageLang); + } console.debug("fetched model json", _formJson); await this.registerCustomFunctions(_formJson.id); const urlSearchParams = new URLSearchParams(window.location.search); @@ -317,6 +328,12 @@ class Utils { // only execute when fd:formDataEnabled is present and set to true _prefillData = await HTTPAPILayer.getPrefillData(_formJson.id, params) || {}; _prefillData = Utils.stripIfWrapped(_prefillData); + window.formBridge.restoreFormState({ + formState : {xfaDom: _prefillData.data.xfaDom, xfaRenderContext: _prefillData.data.xfaRenderContext}, + context : this, + error : function() {}, + success : function () {} + }); } const formContainer = await createFormContainer({ _formJson, @@ -324,6 +341,9 @@ class Utils { _path, _element: elements[i] }); + if (typeof callback === 'function') { + callback(formContainer.getModel()); + } Utils.initializeAllFields(formContainer); const event = new CustomEvent(Constants.FORM_CONTAINER_INITIALISED, { "detail": formContainer }); document.dispatchEvent(event); diff --git a/ui.frontend/src/view/FormCheckBox.js b/ui.frontend/src/view/FormCheckBox.js index bfcff396eb..b52fad0a6e 100644 --- a/ui.frontend/src/view/FormCheckBox.js +++ b/ui.frontend/src/view/FormCheckBox.js @@ -50,7 +50,15 @@ class FormCheckBox extends FormFieldBase { } else { this._model.value = this._offValue; } - }) + }); + this.widget.addEventListener('focus', (e) => { + this.setActive(); + this.triggerEnter(); + }); + this.widget.addEventListener('blur', (e) => { + this.setInactive(); + this.triggerExit(); + }); } } diff --git a/ui.frontend/src/view/FormField.js b/ui.frontend/src/view/FormField.js index b272bc16da..df88078a8d 100644 --- a/ui.frontend/src/view/FormField.js +++ b/ui.frontend/src/view/FormField.js @@ -101,6 +101,14 @@ class FormField { return this.element.getAttribute(Constants.DATA_ATTRIBUTE_ACTIVE) === 'true'; } + triggerExit() { + this._model.dispatch(new FormView.Actions.CustomEvent('xfaexit')); + } + + triggerEnter() { + this._model.dispatch(new FormView.Actions.CustomEvent('xfaenter')); + } + /** * Returns the form container path of the form field. * @returns {string} The form container path. diff --git a/ui.frontend/src/view/FormOptionFieldBase.js b/ui.frontend/src/view/FormOptionFieldBase.js index 34b6267b63..c016269fcc 100644 --- a/ui.frontend/src/view/FormOptionFieldBase.js +++ b/ui.frontend/src/view/FormOptionFieldBase.js @@ -91,6 +91,21 @@ class FormOptionFieldBase extends FormFieldBase { } } + /** + * Applies the full state of the field to the HTML. + * Generally done just after the model is bound to the field. + * @param {Object} state - The state object. + */ + applyState(state) { + super.applyState(state); + if (state.enum) { + this.updateEnum(state.enum); + } + if (state.enumNames) { + this.updateEnumNames(state.enumNames); + } + } + } export default FormOptionFieldBase; diff --git a/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js b/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js index 468cfa396c..d5831802a1 100644 --- a/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js +++ b/ui.tests/test-module/specs/accordion/accordion.authoring.spec.js @@ -84,6 +84,22 @@ describe('Page - Authoring', function () { cy.deleteComponentByPath(accordionEditPath); }); + it('runtime time library should not be loaded', function() { + cy.intercept('GET', /jcr:content\/guideContainer\/accordion\.html/).as('accordionRequest'); + dropAccordionInContainer() + cy.wait('@accordionRequest').then((interception) => { + const htmlContent = interception.response.body; + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/; + const scriptTags = Array.from(doc.querySelectorAll('script[src]')); + console.log("tags ", scriptTags); + const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src)); + expect(isClientLibraryLoaded).to.be.false; + }) + cy.deleteComponentByPath(accordionEditPath); + }) + it('open edit dialog of Accordion', {retries: 3}, function () { cy.cleanTest(accordionEditPath).then(function() { testAccordionBehaviour(accordionPathSelector, accordionEditPath); diff --git a/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js b/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js index f7dade60a6..9f3fd84df3 100644 --- a/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js +++ b/ui.tests/test-module/specs/tabsontop/tabsontop.authoring.spec.js @@ -98,6 +98,21 @@ describe.only('Page - Authoring', function () { cy.deleteComponentByPath(tabsPath); }); + it('runtime library should not be loaded', function() { + cy.intercept('GET', /jcr:content\/guideContainer\/tabsontop\.html/).as('tabsRequest'); + dropTabsInContainer() + cy.wait('@tabsRequest').then((interception) => { + const htmlContent = interception.response.body; + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/; + const scriptTags = Array.from(doc.querySelectorAll('script[src]')); + const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src)); + expect(isClientLibraryLoaded).to.be.false; + }) + cy.deleteComponentByPath(tabsPath); + }) + it('drop element in tabs on top', {retries: 3}, function () { cy.cleanTest(tabsPath).then(function () { diff --git a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js index 9670a02ebd..411753b934 100644 --- a/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js +++ b/ui.tests/test-module/specs/verticaltabs/verticaltabs.authoring.spec.js @@ -97,6 +97,21 @@ describe.only('Page - Authoring', function () { cy.deleteComponentByPath(tabsPath); }); + it('runtime library should not be loaded', function() { + cy.intercept('GET', /jcr:content\/guideContainer\/verticaltabs\.html/).as('verticaltabsRequest'); + dropTabsInContainer() + cy.wait('@verticaltabsRequest').then((interception) => { + const htmlContent = interception.response.body; + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/; + const scriptTags = Array.from(doc.querySelectorAll('script[src]')); + const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src)); + expect(isClientLibraryLoaded).to.be.false; + }) + cy.deleteComponentByPath(tabsPath); + }) + it ('open edit dialog of Vertical Tabs',{ retries: 3 }, function(){ cy.cleanTest(tabsPath).then(function() { testPanelBehaviour(tabsContainerPathSelector, tabsPath); diff --git a/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js b/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js index 2d4ad015df..29d4290f94 100644 --- a/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js +++ b/ui.tests/test-module/specs/wizard/wizard.authoring.spec.js @@ -66,6 +66,21 @@ describe('Page - Authoring', function () { cy.openAuthoring(pagePath); }); + it('runtime library should not be loaded', function() { + cy.intercept('GET', /jcr:content\/guideContainer\/wizard\.html/).as('wizardRequest'); + dropWizardInContainer(); + cy.wait('@wizardRequest').then((interception) => { + const htmlContent = interception.response.body; + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, 'text/html'); + const runtimeUrlPattern = /core\/fd\/af-clientlibs\/core-forms-components-runtime-base/; + const scriptTags = Array.from(doc.querySelectorAll('script[src]')); + const isClientLibraryLoaded = scriptTags.some(script => runtimeUrlPattern.test(script.src)); + expect(isClientLibraryLoaded).to.be.false; + }) + cy.deleteComponentByPath(wizardLayoutDrop); + }) + it('verify Basic tab in edit dialog of Wizard', function () { dropWizardInContainer(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector).then(() => {