Replace jekyll-tabs plugin with custom code
authorLuna <lunacodes@gmail.com>
Fri, 28 May 2021 15:33:11 +0000 (11:33 -0400)
committerLuna <lunacodes@gmail.com>
Fri, 28 May 2021 15:33:11 +0000 (11:33 -0400)
_config.yml
_episodes/03-running.md
assets/css/tabs.css
assets/js/tabs.js

index dd769123f4eaa6eca604d53abcee85b713505bd9..9fc41fc084d754fff3da8ced2e8ea6fefe7b19d6 100644 (file)
@@ -99,6 +99,3 @@ exclude:
 
 # Turn on built-in syntax highlighting.
 highlighter: rouge
-
-plugins:
-  - jekyll-tabs
index 47f7aabf89006253a30384ed787bea496734788a..b7c6bb4b2f0dfceeb4367d7cef68900ef39bc691 100644 (file)
@@ -31,10 +31,8 @@ plain strings that may or may not be file paths.
 
 Note: if you don't have example sequence data or the STAR index files, see [setup](/setup.html).
 
-<div>
-{% tabs input %}
-
-{% tab input generic %}
+{% assign tabs = "generic, arvados" | split: ", " %}
+{% capture generic_tab_content %}
 main-input.yaml
 ```
 fq:
@@ -63,10 +61,9 @@ gtf:
 > logging.  The logging you see, how access other logs, and how to
 > track workflow progress will depend on your CWL runner platform.
 {: .challenge }
+{% endcapture %}
 
-{% endtab %}
-
-{% tab input arvados %}
+{% capture arvados_tab_content %}
 main-input.yaml
 ```
 fq:
@@ -88,8 +85,17 @@ gtf:
 > then use the `Run CWL Workflow on Arvados` task.
 >
 {: .challenge }
-{% endtab %}
-{% endtabs %}
+{% endcapture %}
+
+<div class="tabbed">
+  <ul class="tab">
+    {% for tab in tabs %}
+      <li><a href="#section-{{ tab }}">{{ tab }}</a></li>
+    {% endfor %}
+  </ul>
+
+  <section id="section-generic">{{ generic_tab_content | markdownify}}</section>
+  <section id="section-arvados">{{ arvados_tab_content | markdownify}}</section>
 </div>
 
 # Debugging the workflow
@@ -228,3 +234,83 @@ The CWL runner will print a results JSON object to standard output.  It will loo
 This has a similar structure as `main-input.yaml`.  The each output
 parameter is listed, with the `location` field of each `File` object
 indicating where the output file can be found.
+
+<script>
+  (function() {
+    // Get relevant elements and collections
+    const tabbed = document.querySelector('.tabbed');
+    const tablist = tabbed.querySelector('ul');
+    const tabs = tablist.querySelectorAll('a');
+    const panels = tabbed.querySelectorAll('[id^="section"]');
+
+    // The tab switching function
+    const switchTab = (oldTab, newTab) => {
+      newTab.focus();
+      // Make the active tab focusable by the user (Tab key)
+      newTab.removeAttribute('tabindex');
+      // Set the selected state
+      newTab.setAttribute('aria-selected', 'true');
+      newTab.classList.add('active');
+      oldTab.removeAttribute('aria-selected');
+      oldTab.setAttribute('tabindex', '-1');
+      oldTab.classList.remove('active');
+      // Get the indices of the new and old tabs to find the correct
+      // tab panels to show and hide
+      let index = Array.prototype.indexOf.call(tabs, newTab);
+      let oldIndex = Array.prototype.indexOf.call(tabs, oldTab);
+      panels[oldIndex].hidden = true;
+      panels[index].hidden = false;
+    }
+
+    // Add the tablist role to the first <ul> in the .tabbed container
+    tablist.setAttribute('role', 'tablist');
+
+    // Add semantics are remove user focusability for each tab
+    Array.prototype.forEach.call(tabs, (tab, i) => {
+      tab.setAttribute('role', 'tab');
+      tab.setAttribute('id', 'tab' + (i + 1));
+      tab.setAttribute('tabindex', '-1');
+  //     tab.setAttribute('class', 'active');
+      tab.parentNode.setAttribute('role', 'presentation');
+
+      // Handle clicking of tabs for mouse users
+      tab.addEventListener('click', e => {
+        e.preventDefault();
+        let currentTab = tablist.querySelector('[aria-selected]');
+        if (e.currentTarget !== currentTab) {
+          switchTab(currentTab, e.currentTarget);
+        }
+      });
+
+      // Handle keydown events for keyboard users
+      tab.addEventListener('keydown', e => {
+        // Get the index of the current tab in the tabs node list
+        let index = Array.prototype.indexOf.call(tabs, e.currentTarget);
+        // Work out which key the user is pressing and
+        // Calculate the new tab's index where appropriate
+        let dir = e.which === 37 ? index - 1 : e.which === 39 ? index + 1 : e.which === 40 ? 'down' : null;
+        if (dir !== null) {
+          e.preventDefault();
+          // If the down key is pressed, move focus to the open panel,
+          // otherwise switch to the adjacent tab
+          dir === 'down' ? panels[i].focus() : tabs[dir] ? switchTab(e.currentTarget, tabs[dir]) : void 0;
+        }
+      });
+    });
+
+    // Add tab panel semantics and hide them all
+    Array.prototype.forEach.call(panels, (panel, i) => {
+      panel.setAttribute('role', 'tabpanel');
+      panel.setAttribute('tabindex', '-1');
+      let id = panel.getAttribute('id');
+      panel.setAttribute('aria-labelledby', tabs[i].id);
+      panel.hidden = true;
+    });
+
+    // Initially activate the first tab and reveal the first tab panel
+    tabs[0].removeAttribute('tabindex');
+    tabs[0].setAttribute('aria-selected', 'true');
+    tabs[0].setAttribute('class', 'active');
+    panels[0].hidden = false;
+  })();
+</script>
index e9d72f2f9098f0029bafd59dcb07feaf49b06a0d..38b0cf3372c5a5e742d04d9938ce2e38f46d03a4 100644 (file)
     line-height: 20px;
 }
 
-.tab > .active > a {
+.tab > li {
+    transition: color .1s ease-in-out;
+}
+
+.tab > li > .active {
     color:#222;
     border-color: #1e87f0;
 }
 
 .tab li a {
     text-decoration: none;
-        cursor: pointer;
+    cursor: pointer;
+}
+
+.tab > li > a:focus {
+    outline: 0;
+}
+
+.tab > li > .active:focus-visible {
+    outline: 5px auto -webkit-focus-ring-color;
+    outline-offset: -2px;
 }
 
 .tab-content{
index 0148f3255c1339ccb102164bc935fb65d48d5642..0e5b795e4f2483755373a1c29122d4e7024a4f08 100644 (file)
@@ -1,43 +1,77 @@
-const removeActiveClasses = function (ulElement) {
-    const lis = ulElement.querySelectorAll('li');
-    Array.prototype.forEach.call(lis, function(li) {
-        li.classList.remove('active');
-    });
-  }
+window.addEventListener('load', function() {
+  // Get relevant elements and collections
+  const tabbed = document.querySelector('.tabbed');
+  const tablist = tabbed.querySelector('ul');
+  const tabs = tablist.querySelectorAll('a');
+  const panels = tabbed.querySelectorAll('[id^="section"]');
 
-  const getChildPosition = function (element) {
-        var parent = element.parentNode;
-        var i = 0;
-        for (var i = 0; i < parent.children.length; i++) {
-            if (parent.children[i] === element) {
-                return i;
-            }
-        }
+  // The tab switching function
+  const switchTab = (oldTab, newTab) => {
+    newTab.focus();
+    // Make the active tab focusable by the user (Tab key)
+    newTab.removeAttribute('tabindex');
+    // Set the selected state
+    newTab.setAttribute('aria-selected', 'true');
+    newTab.classList.add('active');
+    oldTab.removeAttribute('aria-selected');
+    oldTab.setAttribute('tabindex', '-1');
+    oldTab.classList.remove('active');
+    // Get the indices of the new and old tabs to find the correct
+    // tab panels to show and hide
+    let index = Array.prototype.indexOf.call(tabs, newTab);
+    let oldIndex = Array.prototype.indexOf.call(tabs, oldTab);
+    panels[oldIndex].hidden = true;
+    panels[index].hidden = false;
+  }
 
-        throw new Error('No parent found');
-    }
+  // Add the tablist role to the first <ul> in the .tabbed container
+  tablist.setAttribute('role', 'tablist');
 
-window.addEventListener('load', function () {
-    const tabLinks = document.querySelectorAll('ul.tab li a');
+  // Add semantics are remove user focusability for each tab
+  Array.prototype.forEach.call(tabs, (tab, i) => {
+    tab.setAttribute('role', 'tab');
+    tab.setAttribute('id', 'tab' + (i + 1));
+    tab.setAttribute('tabindex', '-1');
+//     tab.setAttribute('class', 'active');
+    tab.parentNode.setAttribute('role', 'presentation');
 
-    Array.prototype.forEach.call(tabLinks, function(link) {
-      link.addEventListener('click', function (event) {
-        event.preventDefault();
+    // Handle clicking of tabs for mouse users
+    tab.addEventListener('click', e => {
+      e.preventDefault();
+      let currentTab = tablist.querySelector('[aria-selected]');
+      if (e.currentTarget !== currentTab) {
+        switchTab(currentTab, e.currentTarget);
+      }
+    });
 
-        liTab = link.parentNode;
-        ulTab = liTab.parentNode;
-        position = getChildPosition(liTab);
-        if (liTab.className.includes('active')) {
-          return;
-        }
+    // Handle keydown events for keyboard users
+    tab.addEventListener('keydown', e => {
+      // Get the index of the current tab in the tabs node list
+      let index = Array.prototype.indexOf.call(tabs, e.currentTarget);
+      // Work out which key the user is pressing and
+      // Calculate the new tab's index where appropriate
+      let dir = e.which === 37 ? index - 1 : e.which === 39 ? index + 1 : e.which === 40 ? 'down' : null;
+      if (dir !== null) {
+        e.preventDefault();
+        // If the down key is pressed, move focus to the open panel,
+        // otherwise switch to the adjacent tab
+        dir === 'down' ? panels[i].focus() : tabs[dir] ? switchTab(e.currentTarget, tabs[dir]) : void 0;
+      }
+    });
+  });
 
-        removeActiveClasses(ulTab);
-        tabContentId = ulTab.getAttribute('data-tab');
-        tabContentElement = document.getElementById(tabContentId);
-        removeActiveClasses(tabContentElement);
+  // Add tab panel semantics and hide them all
+  Array.prototype.forEach.call(panels, (panel, i) => {
+    panel.setAttribute('role', 'tabpanel');
+    panel.setAttribute('tabindex', '-1');
+    let id = panel.getAttribute('id');
+    panel.setAttribute('aria-labelledby', tabs[i].id);
+    panel.hidden = true;
+  });
 
-        tabContentElement.querySelectorAll('li')[position].classList.add('active');
-        liTab.classList.add('active');
-      }, false);
-    });
+  // Initially activate the first tab and reveal the first tab panel
+  tabs[0].removeAttribute('tabindex');
+  tabs[0].setAttribute('aria-selected', 'true');
+  tabs[0].setAttribute('class', 'active');
+  panels[0].hidden = false;
 });