Skip to content

Instantly share code, notes, and snippets.

@ronkorving
Created March 22, 2016 09:20
Show Gist options
  • Save ronkorving/3dbc17aea7534ccdb48a to your computer and use it in GitHub Desktop.
Save ronkorving/3dbc17aea7534ccdb48a to your computer and use it in GitHub Desktop.
A proof-of-concept for a tab-system
(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