]> git.ipfire.org Git - thirdparty/foundation/foundation-sites.git/commitdiff
Use pull request #11477 from ncoden/fix/initial-tab-deep-linking-11100 for v6.5.0
authorNicolas Coden <nicolas@ncoden.fr>
Wed, 12 Sep 2018 20:20:54 +0000 (22:20 +0200)
committerNicolas Coden <nicolas@ncoden.fr>
Wed, 12 Sep 2018 20:20:54 +0000 (22:20 +0200)
5ea51f3b9 fix: return to initial state when reseting hash in Accordion & Tabs #11100
314b4ee53 fix: fix initial state for Accordion without any panel active
abbbdbff6 refactor: split DOM operations from logics in Accordion
e26190579 refactor: move "init only" Accordion logic to its own methods
b819fafba docs: remove outdated comments in Accordion related to the initial state
7307c4395 feat: add "Tabs._collapse()" to collapse the active tab
012db1077 refactor: clean up the Tab initialization
c6d37c42e fix: close the active Tab when going back to a "tab-less" history state
267356747 docs: fix "zplugin" typo in Accordion & Tabs events

Signed-off-by: Nicolas Coden <nicolas@ncoden.fr>
js/foundation.accordion.js
js/foundation.tabs.js

index c7ded0f97a08ebfe48d9c8abad5cc15b47a154e5..311a5df96edcbafe7f6354b60b498cd89969d41d 100644 (file)
@@ -59,41 +59,50 @@ class Accordion extends Plugin {
 
       $content.attr({'role': 'tabpanel', 'aria-labelledby': linkId, 'aria-hidden': true, 'id': id});
     });
+
     var $initActive = this.$element.find('.is-active').children('[data-tab-content]');
-    this.firstTimeInit = true;
-    if($initActive.length){
-      this.down($initActive, this.firstTimeInit);
-      this.firstTimeInit = false;
+    if ($initActive.length) {
+      // Save up the initial hash to return to it later when going back in history
+      this._initialAnchor = $initActive.prev('a').attr('href');
+      this._openSingleTab($initActive);
     }
 
     this._checkDeepLink = () => {
       var anchor = window.location.hash;
-      //need a hash and a relevant anchor in this tabset
-      if(anchor.length) {
-        var $link = this.$element.find('[href$="'+anchor+'"]'),
-        $anchor = $(anchor);
-
-        if ($link.length && $anchor) {
-          if (!$link.parent('[data-accordion-item]').hasClass('is-active')) {
-            this.down($anchor, this.firstTimeInit);
-            this.firstTimeInit = false;
-          };
-
-          //roll up a little to show the titles
-          if (this.options.deepLinkSmudge) {
-            var _this = this;
-            onLoad($(window), function() {
-              var offset = _this.$element.offset();
-              $('html, body').animate({ scrollTop: offset.top }, _this.options.deepLinkSmudgeDelay);
-            });
-          }
-
-          /**
-            * Fires when the zplugin has deeplinked at pageload
-            * @event Accordion#deeplink
-            */
-          this.$element.trigger('deeplink.zf.accordion', [$link, $anchor]);
-        }
+
+      // If there is no anchor, return to the initial panel
+      if (!anchor.length && this._initialAnchor) {
+        anchor = this._initialAnchor;
+      }
+
+      var $anchor = anchor && $(anchor);
+      var $link = anchor && this.$element.find(`[href$="${anchor}"]`);
+
+      // If there is an anchor for the hash, open it (if not already active)
+      if ($anchor && $link && $link.length) {
+        if (!$link.parent('[data-accordion-item]').hasClass('is-active')) {
+          this._openSingleTab($anchor);
+        };
+      }
+      // Otherwise, close everything
+      else {
+        this._closeAllTabs();
+      }
+
+      // Roll up a little to show the titles
+      if (this.options.deepLinkSmudge) {
+        onLoad($(window), () => {
+          var offset = this.$element.offset();
+          $('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
+        });
+      }
+
+      if ($anchor && $link) {
+        /**
+         * Fires when the plugin has deeplinked at pageload
+         * @event Accordion#deeplink
+         */
+        this.$element.trigger('deeplink.zf.accordion', [$link, $anchor]);
       }
     }
 
@@ -180,32 +189,82 @@ class Accordion extends Plugin {
   /**
    * Opens the accordion tab defined by `$target`.
    * @param {jQuery} $target - Accordion pane to open (`.accordion-content`).
-   * @param {Boolean} firstTime - flag to determine if reflow should happen.
    * @fires Accordion#down
    * @function
    */
-  down($target, firstTime) {
-    /**
-     * checking firstTime allows for initial render of the accordion
-     * to render preset is-active panes.
-     */
-    if ($target.closest('[data-accordion]').is('[disabled]') && !firstTime)  {
+  down($target) {
+    if ($target.closest('[data-accordion]').is('[disabled]'))  {
       console.info('Cannot call down on an accordion that is disabled.');
       return;
     }
-    $target
-      .attr('aria-hidden', false)
-      .parent('[data-tab-content]')
-      .addBack()
-      .parent().addClass('is-active');
-
-    if (!this.options.multiExpand && !firstTime) {
-      var $currentActive = this.$element.children('.is-active').children('[data-tab-content]');
-      if ($currentActive.length) {
-        this.up($currentActive.not($target));
-      }
+
+    if (this.options.multiExpand)
+      this._openTab($target);
+    else
+      this._openSingleTab($target);
+  }
+
+  /**
+   * Closes the tab defined by `$target`.
+   * It may be ignored if the Accordion options don't allow it.
+   *
+   * @param {jQuery} $target - Accordion tab to close (`.accordion-content`).
+   * @fires Accordion#up
+   * @function
+   */
+  up($target) {
+    if (this.$element.is('[disabled]')) {
+      console.info('Cannot call up on an accordion that is disabled.');
+      return;
+    }
+
+    // Don't close the item if it is already closed
+    const $targetItem = $target.parent();
+    if (!$targetItem.hasClass('is-active')) return;
+
+    // Don't close the item if there is no other active item (unless with `allowAllClosed`)
+    const $othersItems = $targetItem.siblings();
+    if (!this.options.allowAllClosed && !$othersItems.hasClass('is-active')) return;
+
+    this._closeTab($target);
+  }
+
+  /**
+   * Make the tab defined by `$target` the only opened tab, closing all others tabs.
+   * @param {jQuery} $target - Accordion tab to open (`.accordion-content`).
+   * @function
+   * @private
+   */
+  _openSingleTab($target) {
+    // Close all the others active tabs.
+    const $activeContents = this.$element.children('.is-active').children('[data-tab-content]');
+    if ($activeContents.length) {
+      this._closeTab($activeContents.not($target));
     }
 
+    // Then open the target.
+    this._openTab($target);
+  }
+
+  /**
+   * Opens the tab defined by `$target`.
+   * @param {jQuery} $target - Accordion tab to open (`.accordion-content`).
+   * @fires Accordion#down
+   * @function
+   * @private
+   */
+  _openTab($target) {
+    const $targetItem = $target.parent();
+    const targetContentId = $target.attr('aria-labelledby');
+
+    $target.attr('aria-hidden', false);
+    $targetItem.addClass('is-active');
+
+    $(`#${targetContentId}`).attr({
+      'aria-expanded': true,
+      'aria-selected': true
+    });
+
     $target.slideDown(this.options.slideSpeed, () => {
       /**
        * Fires when the tab is done opening.
@@ -213,11 +272,6 @@ class Accordion extends Plugin {
        */
       this.$element.trigger('down.zf.accordion', [$target]);
     });
-
-    $(`#${$target.attr('aria-labelledby')}`).attr({
-      'aria-expanded': true,
-      'aria-selected': true
-    });
   }
 
   /**
@@ -225,35 +279,40 @@ class Accordion extends Plugin {
    * @param {jQuery} $target - Accordion tab to close (`.accordion-content`).
    * @fires Accordion#up
    * @function
+   * @private
    */
-  up($target) {
-    if ($target.closest('[data-accordion]').is('[disabled]')) {
-      console.info('Cannot call up on an accordion that is disabled.');
-      return;
-    }
+  _closeTab($target) {
+    const $targetItem = $target.parent();
+    const targetContentId = $target.attr('aria-labelledby');
 
-    var $aunts = $target.parent().siblings(),
-        _this = this;
+    $target.attr('aria-hidden', true)
+    $targetItem.removeClass('is-active');
 
-    if((!this.options.allowAllClosed && !$aunts.hasClass('is-active')) || !$target.parent().hasClass('is-active')) {
-      return;
-    }
+    $(`#${targetContentId}`).attr({
+     'aria-expanded': false,
+     'aria-selected': false
+    });
 
-    $target.slideUp(_this.options.slideSpeed, function () {
+    $target.slideUp(this.options.slideSpeed, () => {
       /**
        * Fires when the tab is done collapsing up.
        * @event Accordion#up
        */
-      _this.$element.trigger('up.zf.accordion', [$target]);
+      this.$element.trigger('up.zf.accordion', [$target]);
     });
+  }
 
-    $target.attr('aria-hidden', true)
-           .parent().removeClass('is-active');
-
-    $(`#${$target.attr('aria-labelledby')}`).attr({
-     'aria-expanded': false,
-     'aria-selected': false
-   });
+  /**
+   * Closes all active tabs
+   * @fires Accordion#up
+   * @function
+   * @private
+   */
+  _closeAllTabs() {
+    var $activeTabs = this.$element.children('.is-active').children('[data-tab-content]');
+    if ($activeTabs.length) {
+      this._closeTab($activeTabs);
+    }
   }
 
   /**
index b59a10cda296fa364e8f79e7fb30029ec4e3fe58..489640e5305c22c68e9356d574706220443ab7b7 100644 (file)
@@ -73,6 +73,11 @@ class Tabs extends Plugin {
         'aria-labelledby': linkId
       });
 
+      // Save up the initial hash to return to it later when going back in history
+      if (isActive) {
+        _this._initialAnchor = `#${hash}`;
+      }
+
       if(!isActive) {
         $tabContent.attr('aria-hidden', 'true');
       }
@@ -85,6 +90,7 @@ class Tabs extends Plugin {
         });
       }
     });
+
     if(this.options.matchHeight) {
       var $images = this.$tabContent.find('img');
 
@@ -95,29 +101,41 @@ class Tabs extends Plugin {
       }
     }
 
-     //current context-bound function to open tabs on page load or history hashchange
+     // Current context-bound function to open tabs on page load or history hashchange
     this._checkDeepLink = () => {
       var anchor = window.location.hash;
-      //need a hash and a relevant anchor in this tabset
-      if(anchor.length) {
-        var $link = this.$element.find('[href$="'+anchor+'"]');
-        if ($link.length) {
-          this.selectTab($(anchor), true);
-
-          //roll up a little to show the titles
-          if (this.options.deepLinkSmudge) {
-            var offset = this.$element.offset();
-            $('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
-          }
 
-          /**
-            * Fires when the zplugin has deeplinked at pageload
-            * @event Tabs#deeplink
-            */
-           this.$element.trigger('deeplink.zf.tabs', [$link, $(anchor)]);
-         }
-       }
-     }
+      // If there is no anchor, return to the initial panel
+      if (!anchor.length && this._initialAnchor) {
+        anchor = this._initialAnchor;
+      }
+
+      var $anchor = anchor && $(anchor);
+      var $link = anchor && this.$element.find('[href$="'+anchor+'"]');
+
+      // If there is an anchor for the hash, select it
+      if ($anchor && $anchor.length && $link && $link.length) {
+        this.selectTab($anchor, true);
+      }
+      // Otherwise, collapse everything
+      else {
+        this._collapse();
+      }
+
+      // Roll up a little to show the titles
+      if (this.options.deepLinkSmudge) {
+        var offset = this.$element.offset();
+        $('html, body').animate({ scrollTop: offset.top }, this.options.deepLinkSmudgeDelay);
+      }
+
+      if ($anchor && $link) {
+        /**
+         * Fires when the plugin has deeplinked at pageload
+         * @event Tabs#deeplink
+         */
+        this.$element.trigger('deeplink.zf.tabs', [$link, $anchor]);
+      }
+    }
 
     //use browser to open a tab, if it exists in this tabset
     if (this.options.deepLink) {
@@ -223,18 +241,10 @@ class Tabs extends Plugin {
    */
   _handleTabChange($target, historyHandled) {
 
-    /**
-     * Check for active class on target. Collapse if exists.
-     */
+    // With `activeCollapse`, if the target is the active Tab, collapse it.
     if ($target.hasClass(`${this.options.linkActiveClass}`)) {
         if(this.options.activeCollapse) {
-            this._collapseTab($target);
-
-           /**
-            * Fires when the zplugin has successfully collapsed tabs.
-            * @event Tabs#collapse
-            */
-            this.$element.trigger('collapse.zf.tabs', [$target]);
+            this._collapse();
         }
         return;
     }
@@ -310,6 +320,25 @@ class Tabs extends Plugin {
       .attr({ 'aria-hidden': 'true' })
   }
 
+  /**
+   * Collapses the active Tab.
+   * @fires Tabs#collapse
+   * @function
+   */
+  _collapse() {
+    var $activeTab = this.$element.find(`.${this.options.linkClass}.${this.options.linkActiveClass}`);
+
+    if ($activeTab.length) {
+      this._collapseTab($activeTab);
+
+      /**
+      * Fires when the plugin has successfully collapsed tabs.
+      * @event Tabs#collapse
+      */
+      this.$element.trigger('collapse.zf.tabs', [$activeTab]);
+    }
+  }
+
   /**
    * Public method for selecting a content pane to display.
    * @param {jQuery | String} elem - jQuery object or string of the id of the pane to display.
@@ -333,6 +362,7 @@ class Tabs extends Plugin {
 
     this._handleTabChange($target, historyHandled);
   };
+
   /**
    * Sets the height of each panel to the height of the tallest panel.
    * If enabled in options, gets called on media query change.