Created
March 22, 2016 09:20
-
-
Save ronkorving/3dbc17aea7534ccdb48a to your computer and use it in GitHub Desktop.
A proof-of-concept for a tab-system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
// <minui-tabs> | |
// <minui-tabbar> | |
// <minui-tab name="tab1" active>Tab 1</minui-tab> | |
// <minui-tab name="tab2">Tab 2</minui-tab> | |
// </minui-tabbar> | |
// <minui-tabspace>Hello world</minui-tabspace> | |
// <minui-tabspace>Goodbye cruel world</minui-tabspace> | |
// </minui-tabs> | |
// DOM helpers: | |
function findParent(elm, tagName, distance) { | |
if (distance === undefined) { | |
distance = 1; | |
} | |
while (elm && distance > 0) { | |
elm = elm.parentNode; | |
distance -= 1; | |
} | |
if (elm && elm.tagName !== tagName) { | |
throw new Error('Expected <' + tagName + '> parent element, but found <' + elm.tagName + '> instead'); | |
} | |
return elm; | |
} | |
function getParent(elm, tagName, distance) { | |
elm = findParent(elm, tagName, distance); | |
if (!elm) { | |
throw new Error('Expected <' + tagName + '> but found no parent'); | |
} | |
return elm; | |
} | |
function getChildren(elm, filter) { | |
var result = []; | |
var len = elm.children.length; | |
for (var i = 0; i < len; i += 1) { | |
var child = elm.children[i]; | |
if (!filter || filter(child)) { | |
result.push(child); | |
} | |
} | |
return result; | |
} | |
function findChild(elm, filter) { | |
var len = elm.children.length; | |
for (var i = 0; i < len; i += 1) { | |
var child = elm.children[i]; | |
if (!filter || filter(child)) { | |
return child; | |
} | |
} | |
return undefined; | |
} | |
function getChild(elm, filter) { | |
var child = findChild(elm, filter); | |
if (!child) { | |
throw new Error('Could not find child element'); | |
} | |
return child; | |
} | |
function getChildIndex(parent, elm, filter) { | |
var len = parent.children.length; | |
var counted = -1; | |
for (var i = 0; i < len; i += 1) { | |
var child = parent.children[i]; | |
if (!filter || filter(child)) { | |
counted += 1; | |
if (child === elm) { | |
return counted; | |
} | |
} | |
} | |
return counted; | |
} | |
function getNthChild(parent, n, filter) { | |
var len = parent.children.length; | |
var counted = -1; | |
for (var i = 0; i < len; i += 1) { | |
var child = parent.children[i]; | |
if (!filter || filter(child)) { | |
counted += 1; | |
if (counted === n) { | |
return child; | |
} | |
} | |
} | |
throw new Error('Could not find child ' + n); | |
} | |
// Tab helpers | |
function isTab(elm) { | |
return elm instanceof HTMLElement && elm.tagName === 'MINUI-TAB'; | |
} | |
function isActiveTab(elm) { | |
return elm instanceof HTMLElement && elm.tagName === 'MINUI-TAB' && elm.hasAttribute('active'); | |
} | |
function isTabBar(elm) { | |
return elm instanceof HTMLElement && elm.tagName === 'MINUI-TABBAR'; | |
} | |
function isSpace(elm) { | |
return elm instanceof HTMLElement && elm.tagName === 'MINUI-TABSPACE'; | |
} | |
function getTabFromRoot(root, key) { | |
var bar, index; | |
if (typeof key === 'string') { | |
bar = getChild(root, isTabBar); | |
return getChild(bar, key, function (elm) { | |
return isTab(elm) && elm.getAttribute('name') === key; | |
}); | |
} | |
if (isSpace(key)) { | |
bar = getChild(root, isTabBar); | |
index = getChildIndex(root, key, isSpace); | |
} else if (typeof key === 'number') { | |
index = key; | |
} else { | |
throw new TypeError('Tab must be <minui-tabspace>, a tab name or a numerical index'); | |
} | |
if (index === -1) { | |
throw new Error('Tab not found'); | |
} | |
return getNthChild(this, index, isTab); | |
} | |
// minui-tab | |
var tab = Object.create(HTMLElement.prototype, { | |
name: { | |
get: function () { return this.getAttribute('name'); }, | |
set: function (name) { this.setAttribute('name', name); } | |
}, | |
isActive: { | |
get: function () { return this.hasAttribute('active'); }, | |
set: function () { throw new Error('Activate a tab with tab.activate() or set its "active" attribute'); } | |
}, | |
activate: { | |
value: function () { | |
return this.setAttribute('active', ''); | |
} | |
}, | |
createdCallback: { | |
value: function () { | |
this.addEventListener('click', this.activate); | |
} | |
}, | |
attributeChangedCallback: { | |
value: function (attr, prev, value) { | |
if (attr !== 'active') { | |
return; | |
} | |
var bar = getParent(this, 'MINUI-TABBAR', 1); | |
var root = getParent(bar, 'MINUI-TABS', 1); | |
var index = getChildIndex(bar, this, isTab); | |
var space = getNthChild(root, index, isSpace); | |
if (value === null || value === undefined) { | |
// deactivated | |
space.style.display = 'none'; | |
} else { | |
// activated | |
space.style.display = ''; | |
var that = this; | |
var prevActive = findChild(bar, function (elm) { | |
return elm !== that && isTab(elm) && elm.hasAttribute('active'); | |
}); | |
if (prevActive) { | |
prevActive.removeAttribute('active'); | |
} | |
root.dispatchEvent(new CustomEvent('activated', { detail: { tab: this, prev: prevActive } })); | |
} | |
} | |
} | |
}); | |
// minui-tabbar | |
var tabbar = Object.create(HTMLElement.prototype, { | |
tabs: { | |
get: function () { | |
return getChildren(this, isTab); | |
}, | |
set: function () { throw new Error('You cannot write to .tabs'); } | |
} | |
}); | |
// minui-tabspace | |
var tabspace = Object.create(HTMLElement.prototype, { | |
isActive: { | |
get: function () { | |
var root = getParent(this, 'MINUI-TABS', 1); | |
var index = getChildIndex(root, this, isSpace); | |
var tabbar = getChild(root, isTabBar); | |
var tab = getNthChild(tabbar, index, isTab); | |
return tab.hasAttribute('active'); | |
}, | |
set: function () { throw new Error('Activate a tab space with space.activate()'); } | |
}, | |
activate: { | |
value: function () { | |
var root = getParent(this, 'MINUI-TABS', 1); | |
var tab = getTabFromRoot(root, this); | |
tab.setAttribute('active', ''); | |
} | |
} | |
}); | |
// minui-tabs | |
var tabs = Object.create(HTMLElement.prototype, { | |
createdCallback: { | |
value: function () { | |
// hide all spaces, except the first or the one that should be visible | |
// based on the first tab with the "active" attr | |
var bar = findChild(this, isTabBar); | |
if (!bar) { | |
// create the tabbar if missing | |
bar = this.appendChild(document.createElement('minui-tabbar')); | |
} | |
var t, s, activeTab = -1; | |
// activate exactly one tab (or zero if none available) | |
var tabs = getChildren(bar, isTab); | |
for (t = 0; t < tabs.length; t += 1) { | |
var tab = tabs[t]; | |
if (tab.hasAttribute('active')) { | |
if (activeTab === -1) { | |
activeTab = t; | |
} else { | |
tab.removeAttribute('active'); | |
} | |
} | |
} | |
if (activeTab === -1 && tabs.length > 0) { | |
activeTab = 0; | |
tabs[0].setAttribute('active', ''); | |
} | |
// hide all spaces except for the activated one | |
var spaces = getChildren(this, isSpace); | |
for (s = 0; s < spaces.length; s += 1) { | |
spaces[s].style.display = s === activeTab ? '' : 'none'; | |
} | |
} | |
}, | |
getTabByName: { | |
value: function (name) { | |
var bar = getChild(this, isTabBar); | |
return getChild(bar, function (elm) { | |
return isTab(elm) && elm.getAttribute('name') === name; | |
}); | |
} | |
}, | |
activate: { | |
value: function (key) { | |
if (isTab(key)) { | |
tab = key; | |
} else { | |
tab = getTabFromRoot(this, key); | |
} | |
tab.setAttribute('active', ''); | |
} | |
}, | |
activeTab: { | |
get: function () { return getChild(this.bar, isActiveTab); }, | |
set: function () { throw new Error('You cannot write to .activeTab'); } | |
}, | |
bar: { | |
get: function () { return getChild(this, isTabBar); }, | |
set: function () { throw new Error('You cannot write to .bar'); } | |
}, | |
tabs: { | |
get: function () { return getChildren(this.bar, isTab); }, | |
set: function () { throw new Error('You cannot write to .tabs'); } | |
}, | |
spaces: { | |
get: function () { return getChildren(this, isSpace); }, | |
set: function () { throw new Error('You cannot write to .spaces'); } | |
}, | |
addTab: { | |
value: function (name) { | |
var tab = this.bar.appendChild(document.createElement('minui-tab')); | |
if (name) { | |
tab.setAttribute('name', name); | |
} | |
return tab; | |
} | |
}, | |
getTab: { | |
value: function (key) { | |
return getTabFromRoot(this, key); | |
} | |
}, | |
addSpace: { | |
value: function () { | |
return this.appendChild(document.createElement('minui-tabspace')); | |
} | |
}, | |
getSpace: { | |
value: function (key) { | |
var index; | |
if (typeof key === 'string') { | |
index = getChildIndex(this.bar, key, function (elm) { | |
return isTab(elm) && elm.getAttribute('name') === key; | |
}); | |
if (index === -1) { | |
throw new Error('Tab "' + key + '" not found'); | |
} | |
} else if (isTab(key)) { | |
index = getChildIndex(this.bar, key, isTab); | |
if (index === -1) { | |
throw new Error('Tab not found'); | |
} | |
} | |
return getNthChild(this, index, isSpace); | |
} | |
} | |
}); | |
// register elements | |
document.registerElement('minui-tabs', { prototype: tabs }); | |
document.registerElement('minui-tabbar', { prototype: tabbar }); | |
document.registerElement('minui-tab', { prototype: tab }); | |
document.registerElement('minui-tabspace', { prototype: tabspace }); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment