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..145a0f21f3 --- /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,104 @@ +/******************************************************************************* + * 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 AccordionMixin = window.Forms.CoreComponentsCommons.AccordionMixin; + + 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 e052b09882..b9ceb86eb4 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 @@ -19,4 +19,4 @@ allowProxy="{Boolean}true" categories="[core.forms.components.accordion.v1.runtime]" jsProcessor="[default:none,min:none]" - 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 c624dea2a6..a31e0146f8 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,11 @@ ******************************************************************************/ (function () { - class Accordion extends FormView.FormPanel { + const AccordionMixin = window.Forms.CoreComponentsCommons.AccordionMixin; + + 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 +32,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 +43,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 +92,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]) - } - } - } - } - - /** - * 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]; + if (this.isItemExpanded(items[i])) { + this.collapseItem(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; - } - /** * 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 +144,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 +161,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 +192,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 +201,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 +246,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 +285,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,7 +304,7 @@ 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; } @@ -559,51 +359,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) { @@ -614,7 +377,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) { @@ -656,17 +419,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/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 13c323cb4f..4dd738e70b 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/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..061e27e306 --- /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,149 @@ +/******************************************************************************* + * 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( wizardPanels[i] && tabs[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 ca7663dbfc..f783007439 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 @@ -19,5 +19,5 @@ allowProxy="{Boolean}true" categories="[core.forms.components.wizard.v1.runtime]" jsProcessor="[default:none,min:none]" - 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 815bd476fa..903a16b793 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,15 @@ ARROW_DOWN: 40 }; + const WizardMixin = window.Forms.CoreComponentsCommons.WizardMixin; - 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 +58,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 +76,7 @@ super.setFocus(id); this.setActive(); const index = this.#getTabIndexById(id + '_wizard-item-nav'); - this.#navigate(index); + this.navigate(index); } getWidget() { @@ -183,30 +133,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 +140,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,33 +173,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(tabs[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); } /** @@ -290,8 +196,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; @@ -306,13 +212,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() { @@ -320,13 +226,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); } /** @@ -337,7 +243,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); @@ -364,26 +270,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); } } @@ -392,7 +298,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); @@ -404,7 +310,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); @@ -416,18 +322,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 * @@ -435,13 +329,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; @@ -452,7 +346,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; @@ -484,7 +378,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 { @@ -492,10 +386,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)); @@ -511,9 +405,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) { @@ -525,11 +419,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); } @@ -563,16 +457,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) { @@ -583,7 +469,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) { @@ -594,7 +480,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) { @@ -612,7 +498,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 { @@ -632,11 +518,11 @@ 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.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(() => {