Skip to content

Commit

Permalink
Tabpane: support persistence of all active tabs (#1611)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalin authored Jul 13, 2023
1 parent 7f39b82 commit b53f5f1
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 23 deletions.
7 changes: 3 additions & 4 deletions layouts/shortcodes/tabpane.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@

{{ $_persist := .Get "persist" -}}
{{ with $_persist -}}
{{ $matched := findRE "^(header|lang|none)$" . -}}
{{ $matched := findRE "^(header|lang|disabled)$" . -}}
{{ if not $matched -}}
{{ errorf "Shortcode %q: parameter %q should be one of 'header', 'lang', or 'none'; but got %s. Error position: %s" $.Name "persist" $_persist $.Position -}}
{{ errorf "Shortcode %q: parameter %q should be one of 'header', 'lang', or 'disabled'; but got %s. Error position: %s" $.Name "persist" $_persist $.Position -}}
{{ end -}}
{{ end -}}

Expand All @@ -44,7 +44,7 @@
{{ $langEqualsHeader := default false ($.Get "langEqualsHeader") -}}
{{ $deprecatedPersistLang := $_persistLang | default true -}}
{{ $persistKeyKind := $_persist | default (cond (eq $langPane "") "lang" "header") -}}
{{ $persistTab := and $deprecatedPersistLang (ne $persistKeyKind "none") -}}
{{ $persistTab := and $deprecatedPersistLang (ne $persistKeyKind "disabled") -}}
{{ $rightPane := default false ($.Get "right") -}}
{{ $activeSet := false -}}
{{/* Scratchpad gets populated through call to .Inner */ -}}
Expand Down Expand Up @@ -106,7 +106,6 @@
{{ if $disabled }} disabled{{ end -}}"
id="{{ $tabid }}" data-bs-toggle="tab" data-bs-target="#{{ $entryid }}" role="tab"
{{ if and $persistTab $persistKey -}}
onclick="tdPersistActiveTab({{ $persistKey }});" {{/* */ -}}
{{ printf "%s=%q " $tpPersistAttrName $persistKey | safeHTMLAttr -}}
{{ end -}}
aria-controls="{{- $entryid -}}" aria-selected="{{- cond ( and ( not $activeSet ) ( not $disabled ) ) "true" "false" -}}">
Expand Down
124 changes: 105 additions & 19 deletions static/js/tabpane-persist.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,116 @@
// Storage key name also used as a data-* attribute suffix:
const storageKeyName = 'td-tp-persist';
// Storage key names and data attribute name:
const td_persistStorageKeyNameBase = 'td-tp-persist';
const td_persistCounterStorageKeyName = `${td_persistStorageKeyNameBase}-count`;
const td_persistDataAttrName = `data-${td_persistStorageKeyNameBase}`;

function tdActivateTabsWithKey(key) {
if (!key) return;
document
.querySelectorAll(`[data-${storageKeyName}="${key}"]`)
.forEach((element) => {
new bootstrap.Tab(element).show();
});
}
// Utilities

function tdPersistActiveTab(activeTabKey) {
if (!tdSupportsLocalStorage()) return;
const _tdPersistCssSelector = (attrValue) =>
attrValue
? `[${td_persistDataAttrName}="${attrValue}"]`
: `[${td_persistDataAttrName}]`;

const _tdStoragePersistKey = (tabKey) =>
td_persistStorageKeyNameBase + ':' + (tabKey || '');

const _tdSupportsLocalStorage = () => typeof Storage !== 'undefined';

// Helpers

function tdPersistKey(key, value) {
// @requires: tdSupportsLocalStorage();

try {
localStorage.setItem(storageKeyName, activeTabKey);
tdActivateTabsWithKey(activeTabKey);
if (value) {
localStorage.setItem(key, value);
} else {
localStorage.removeItem(key);
}
} catch (error) {
console.error(`Unable to save active tab '${activeTabKey}' to localStorage:`, error);
const action = value ? 'add' : 'remove';
console.error(
`Docsy tabpane: unable to ${action} localStorage key '${key}': `,
error
);
}
}

const tdSupportsLocalStorage = () => typeof Storage !== 'undefined';
// Retrieve, increment, and store tab-select event count, then returns it.
function tdGetTabSelectEventCountAndInc() {
// @requires: tdSupportsLocalStorage();

const storedCount = localStorage.getItem(td_persistCounterStorageKeyName);
let numTabSelectEvents = parseInt(storedCount) || 0;
numTabSelectEvents++;
tdPersistKey(td_persistCounterStorageKeyName, numTabSelectEvents.toString());
return numTabSelectEvents;
}

// Main functions

// On page load, activate tabs
if (tdSupportsLocalStorage()) {
const activeTabKey = localStorage.getItem(storageKeyName);
function tdActivateTabsWithKey(key) {
if (!key) return;

document.querySelectorAll(_tdPersistCssSelector(key)).forEach((element) => {
new bootstrap.Tab(element).show();
});
}

function tdPersistActiveTab(activeTabKey) {
if (!_tdSupportsLocalStorage()) return;

tdPersistKey(
_tdStoragePersistKey(activeTabKey),
tdGetTabSelectEventCountAndInc()
);
tdActivateTabsWithKey(activeTabKey);
}

// Handlers

function tdGetAndActivatePersistedTabs(tabs) {
// Get unique persistence keys of tabs in this page
var keyOfTabsInThisPage = [
...new Set(
Array.from(tabs).map((el) => el.getAttribute(td_persistDataAttrName))
),
];

// Create a list of active tabs with their age:
let key_ageList = keyOfTabsInThisPage
// Map to [tab-key, last-activated-age]
.map((k) => [
k,
parseInt(localStorage.getItem(_tdStoragePersistKey(k))) || 0,
])
// Exclude tabs that have never been activated
.filter(([k, v]) => v)
// Sort from oldest selected to most recently selected
.sort((a, b) => a[1] - b[1]);

// Activate tabs from the oldest to the newest
key_ageList.forEach(([key]) => {
tdActivateTabsWithKey(key);
});

return key_ageList;
}

function tdRegisterTabClickHandler(tabs) {
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const activeTabKey = tab.getAttribute(td_persistDataAttrName);
tdPersistActiveTab(activeTabKey);
});
});
}

// Register listeners and activate tabs

window.addEventListener('DOMContentLoaded', () => {
if (!_tdSupportsLocalStorage()) return;

var allTabsInThisPage = document.querySelectorAll(_tdPersistCssSelector());
tdRegisterTabClickHandler(allTabsInThisPage);
tdGetAndActivatePersistedTabs(allTabsInThisPage);
});

0 comments on commit b53f5f1

Please sign in to comment.