From 6c6e712ee105474f888a9df80215bb118e15c265 Mon Sep 17 00:00:00 2001 From: Daniel Hug Date: Fri, 31 Mar 2017 15:40:58 -0700 Subject: [PATCH] live event listening --- panels.js | 114 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/panels.js b/panels.js index ebfea85..5fd9ca5 100644 --- a/panels.js +++ b/panels.js @@ -1,5 +1,38 @@ // toggleable tabbed panels (function(){ + // get nearest parent element matching selector + var closestParent = (function() { + var el = HTMLElement.prototype; + var matches = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector; + + function closestParent(el, selector) { + if (el === null) return null; + if (matches.call(el, selector)) { + return el; + } else { + return closestParent(el.parentElement, selector); + } + } + return closestParent; + })(); + + + // handle events whose target matches a selector + // works even on elements created after the event listener was added: + // + // delegateEvent('.todo .remove', 'click', function(removeBtn) { + // removeTodo(removeBtn.parentNode); + // }); + + function delegateEvent(selector, eventType, handler, elementScope) { + (elementScope || document).addEventListener(eventType, function(event) { + var listeningTarget = closestParent(event.target, selector); + if (listeningTarget) { + handler.call(listeningTarget, event); + } + }); + } + // Get descendants of scopeEl with className that have no // .tabbed-panels ancestor which is also a descendant of scopeEl // This is needed to support nesting of tabbed panels. @@ -14,48 +47,65 @@ }); } - [].forEach.call(document.getElementsByClassName('tabbed-panels'), function(parent) { + function isAInB(a, b) { + return a === b || b.contains(a); + } + + function tabClick(clickedTab) { + clickedTab = clickedTab.parentNode ? clickedTab : this; + var parent = closestParent(clickedTab, '.tabbed-panels'); + if (!parent) return; + var tabs = getElements('tab', parent); var panels = getElements('panel', parent); var closeable = parent.classList.contains('closeable'); - var hovering = parent.classList.contains('hovering'); - var lastClickedTab = null; - var lastClickedTabI = null; + var clickedTabI = tabs.indexOf(clickedTab); - function tabClick(clickedTab, clickedTabI) { - // non-clicked tabs: deactivate - tabs.forEach(function(tab) { - if (tab != clickedTab) tab.classList.remove('active'); - }); + // non-clicked tabs: deactivate + tabs.forEach(function(tab) { + if (tab != clickedTab) tab.classList.remove('active'); + }); - // clicked tab: if closeable, toggle its active state, otherwise activate - var isActive = clickedTab.classList[closeable ? 'toggle' : 'add']('active'); + // clicked tab: if closeable, toggle its active state, otherwise activate + var isActive = clickedTab.classList[closeable ? 'toggle' : 'add']('active'); - // parent: if not closeable or if a tab is active, activate parent, otherwise deactivate - parent.classList[(!closeable || isActive) ? 'add' : 'remove']('active'); + // parent: if not closeable or if a tab is active, activate parent, otherwise deactivate + parent.classList[(!closeable || isActive) ? 'add' : 'remove']('active'); - // panels: activate clicked tab's panel, deactivate other panels - panels.forEach(function(panel, panelI) { - panel.classList[panelI === clickedTabI ? (closeable ? 'toggle' : 'add') : 'remove']('active'); - }); + // panels: activate clicked tab's panel, deactivate other panels + panels.forEach(function(panel, panelI) { + if (panelI === clickedTabI) { + panel.classList[closeable ? 'toggle' : 'add']('active'); + } else { + panel.classList.remove('active'); + } + }); + } - lastClickedTab = clickedTab; - lastClickedTabI = clickedTabI; - } + delegateEvent('.tab', 'click', tabClick); - tabs.forEach(function(tab, i) { - tab.addEventListener('click', tabClick.bind(null, tab, i)); - }); + window.addEventListener('click', function(event) { + var parents = [].slice.call(document.querySelectorAll('.tabbed-panels.hovering.closeable.active')); + parents.forEach(function(parent) { + var tabs = getElements('tab', parent); + var panels = getElements('panel', parent); + + // exit if click was in a tab or panel + if (tabs.some(function(tab) { + return isAInB(event.target, tab); + }) || panels.some(function(panel) { + return isAInB(event.target, panel); + })) return; - if (closeable && hovering) { - window.addEventListener('click', function(e) { - if ( - parent.classList.contains('active') && - (!parent.contains(e.target) || e.target === parent) - ) { - tabClick(lastClickedTab, lastClickedTabI); + // otherwise unactivate everything + for (var i = 0; i < tabs.length; i++) { + if (tabs[i].classList.contains('active')) { + parent.classList.remove('active'); + tabs[i].classList.remove('active'); + panels[i].classList.remove('active'); + return; } - }); - } + } + }); }); })(); \ No newline at end of file