Rework menus for mobile compatibility

Start supporting touch events for non-buggy iOS support and faster menus
Display and hide wallets on click instead of on mouseout to prevent accidental clicks or mouseout events
Add a wrapper function to detect ghost and accidental clicks
Reorganize wallet javascript functions for better readability
Fix bug in which a wallet would display on mouse over after user clicked inside it's bubble
Fix accidental use of global variables
This commit is contained in:
Saivann 2015-07-07 14:15:23 -04:00
parent 8c3852e230
commit 5707cea630
No known key found for this signature in database
GPG key ID: 5780F5D31B5577B0
7 changed files with 462 additions and 300 deletions

View file

@ -83,32 +83,88 @@ for (var i = 0, nd = document.getElementsByTagName('*'), n = nd.length; i < n; i
}
}
function onTouchClick(e, callback) {
// Detect and handle clicks using click and touch events while preventing accidental or ghost clicks.
var timeout = 1000,
touchEndListener = function(e) {
// Call callback if touch events match the patterns of a click.
removeEvent(t, 'touchend', touchEndListener);
setClickTimeout();
if (Math.abs(e.changedTouches[0].pageX - x) > 20 || Math.abs(e.changedTouches[0].pageY - y) > 20) return;
callback(e);
},
wrongClickListener = function(e) {
// Cancel click events on different targets within timeframe.
// This avoids accidental clicks when the page is scrolled or updated due to the 300ms click event delay on mobiles.
removeEvent(document.body, 'click', wrongClickListener);
if (!clickReady() && getEventTarget(e) != t) cancelEvent(e);
}
setClickTimeout = function() {
// Update timeout during which click events will be blocked.
document.body.setAttribute('data-touchtimeout', new Date().getTime() + timeout);
},
clickReady = function() {
// Check if timeout during click events are blocked has expired.
var ti = document.body.getAttribute('data-touchtimeout');
return (ti === null || ti === '' || parseInt(ti, 10) < new Date().getTime());
};
// Apply appropriate actions according to each event type.
switch (e.type) {
case 'touchstart':
// Save initial touchstart coordinates and listen for touchend events and accidental click events.
var x = e.changedTouches[0].pageX,
y = e.changedTouches[0].pageY,
t = e.changedTouches[0].target;
setClickTimeout();
addEvent(t, 'touchend', touchEndListener);
addEvent(document.body, 'click', wrongClickListener);
setTimeout(function() {
removeEvent(document.body, 'click', wrongClickListener);
}, timeout);
break;
case 'click':
// Call callback on click in the absence of a recent touchstart event to prevent ghost clicks.
if (!clickReady()) return;
callback();
break;
}
}
function mobileMenuShow(e) {
// Show the mobile menu when the visitors touch the menu icon.
var mm = document.getElementById('menusimple');
var ml = document.getElementById('langselect');
var t = document.getElementById('menumobile');
mm.style.display = ml.style.display = (mm.style.display == 'block') ? '' : 'block';
cancelEvent(e);
var show = function() {
var mm = document.getElementById('menusimple');
var ml = document.getElementById('langselect');
mm.style.display = ml.style.display = (mm.style.display == 'block') ? '' : 'block';
cancelEvent(e);
};
onTouchClick(e, show);
}
function mobileMenuHover(e) {
// Add a delay before hiding menu for mobiles to prevent accidental clicks.
var p = t = getEventTarget(e);
if (t.nodeName != 'A') return;
while (p.parentNode.nodeName != 'DIV') p = p.parentNode;
while (t.nodeName != 'LI' || t.parentNode != p) t = t.parentNode;
var ul = null;
if (t.getElementsByTagName('UL').length > 0) {
var ul = t.getElementsByTagName('UL')[0];
addClass(ul, 'hover');
}
setTimeout(function() {
for (var i = 0, nd = p.getElementsByTagName('UL'), n = nd.length; i < n; i++) {
if (nd[i] == ul) continue;
removeClass(nd[i], 'hover');
// Prevent mobile menu to shrink on hover to prevent accidental clicks on other entries.
var t = getEventTarget(e),
fn = (t.parentNode.className.indexOf('hover') === -1) ? addClass : removeClass,
initHover = function() {
if (t.nodeName != 'A') return;
var p = t;
while (p.parentNode.nodeName == 'UL' || p.parentNode.nodeName == 'LI') p = p.parentNode;
addClass(p, 'menutap');
for (var i = 0, nds = p.getElementsByTagName('LI'), n = nds.length; i < n; i++) {
if (nds[i] == t.parentNode) continue;
removeClass(nds[i], 'active');
if (nds[i].getElementsByTagName('UL').length > 0) continue;
removeClass(nds[i], 'hover');
}
}, 1);
while (t != p) {
if (t.nodeName == 'LI') {
fn(t, 'hover');
fn(t, 'active');
}
t = t.parentNode;
}
};
onTouchClick(e, initHover);
}
function addAnchorLinks() {

View file

@ -104,7 +104,7 @@ document.body.setAttribute('data-scrollstatus', setInterval(function() {
function supportCSS(id) {
// Return true if the browser supports given CSS feature.
var x = domPrefixes = 'Webkit Moz ms O'.split(' ');
var domPrefixes = 'Webkit Moz ms O'.split(' ');
var nd = document.createElement('DIV');
id = id.toLowerCase();
if (nd.style[id] !== undefined) return true;
@ -162,7 +162,8 @@ cancelEvent(e);
function materialShow(e) {
// Display more materials on the "Press center" page at user request.
var p = t = getEventTarget(e);
var t = getEventTarget(e),
p = t;
while (p.nodeType != 1 || p.nodeName != 'DIV') p = p.parentNode;
expandBox(p);
cancelEvent(e);
@ -170,7 +171,8 @@ cancelEvent(e);
function librariesShow(e) {
// Display more open source projects on the "Development" page at user request.
var p = t = getEventTarget(e);
var t = getEventTarget(e),
p = t;
while (p.nodeType != 1 || p.nodeName != 'UL') p = p.parentNode;
expandBox(p);
cancelEvent(e);
@ -333,106 +335,119 @@ if (typeof(Storage) === 'undefined') return;
if (sessionStorage.getItem('develdocdisclaimerclose') === '1') disclaimerClose();
}
function walletMenuListener(e) {
// Listen for events on the wallet menu.
var t = getEventTarget(e),
walletSelectPlatform = function() {
if (t.nodeName != 'A') return;
walletShowPlatform(t.getAttribute('data-walletcompat'));
if (isMobile() && t.parentNode.getElementsByTagName('UL').length == 0) scrollToNode(document.getElementById('wallets'));
};
// Pre-process events and call appropriate function.
onTouchClick(e, walletSelectPlatform);
}
function walletListener(e) {
// Listen events on the wallets categories menu and hide / show / select wallets accordingly.
var t = getEventTarget(e);
switch (e.type) {
case 'click':
if (t.nodeName == 'A') walletSelectPlatform(t);
break;
case 'mouseover':
if (t.nodeName == 'A') walletShowPlatform(t.getAttribute('data-walletcompat'));
if (t.nodeName == 'A') clearTimeout(document.getElementById('walletmenu').getAttribute('data-wallettimeout'));
break;
case 'mouseout':
clearTimeout(document.getElementById('walletmenu').getAttribute('data-wallettimeout'));
document.getElementById('walletmenu').setAttribute('data-wallettimeout', setTimeout(walletFallbackPlatform, 100));
break;
}
}
function walletSelectPlatform(t) {
// Select wallets platform when the mouse clicks on the menu.
var p = t;
while (p.nodeName != 'DIV') p = p.parentNode;
for (var i = 0, nds = p.getElementsByTagName('A'), n = nds.length; i < n; i++) {
nds[i].removeAttribute('data-select');
removeClass(nds[i].parentNode, 'select');
}
t.setAttribute('data-select', '1');
addClass(t.parentNode, 'select');
if (isMobile() && t.parentNode.getElementsByTagName('UL').length == 0) {
setTimeout(function() {
scrollToNode(document.getElementById('wallets'));
}, 10);
}
}
function walletFallbackPlatform() {
// Show back wallets for selected platform when the mouse leaves the menu without selecting another platform.
var select = null;
var active = null;
for (var i = 0, nds = document.getElementById('walletmenu').getElementsByTagName('A'), n = nds.length; i < n; i++) {
if (nds[i].getAttribute('data-select') == '1') select = nds[i];
if (nds[i].getAttribute('data-active') == '1') active = nds[i];
}
if (select === null || active === null) return;
walletShowPlatform(select.getAttribute('data-walletcompat'));
// Listen for events on wallets.
var t = getEventTarget(e),
walletShow = function() {
// Show wallet on click on mobile or desktop.
if (t.id == 'wallets') return;
while (t.parentNode && t.parentNode.id != 'wallets') t = t.parentNode;
if (!t.parentNode) return;
if (isMobile()) {
var p = document.getElementById('walletsmobile');
t = t.cloneNode(true);
p.innerHTML = '';
p.appendChild(t);
scrollToNode(p);
} else {
addClass(t, 'active');
addEvent(document.body, 'click', walletListener);
}
},
walletHide = function() {
// Disable wallet when the mouse clicks elsewhere.
for (var i = 0, wallets = document.getElementById('wallets').childNodes, n = wallets.length; i < n; i++) {
if (wallets[i].nodeType != 1) continue;
removeClass(wallets[i], 'active');
}
removeEvent(document.body, 'click', walletListener);
};
// Call appropriate function on click.
onTouchClick(e, function() {
walletHide();
walletShow();
});
}
function walletShowPlatform(platform) {
// Show wallets for given platform in the menu.
var fallback = '';
// Update menu and set fallback category if hovering in a submenu.
for (var i = 0, nds = document.getElementById('walletmenu').getElementsByTagName('A'), n = nds.length; i < n; i++) {
if (nds[i].getAttribute('data-walletcompat') != platform) continue;
if (nds[i].getAttribute('data-active') == 1) return;
var t = nds[i];
if (nds[i].parentNode.parentNode.parentNode.nodeName == 'LI') fallback = nds[i].parentNode.parentNode.parentNode.getElementsByTagName('A')[0].getAttribute('data-walletcompat');
break;
}
for (var i = 0, nds = document.getElementById('walletmenu').getElementsByTagName('A'), n = nds.length; i < n; i++) {
nds[i].removeAttribute('data-active');
removeClass(nds[i].parentNode, 'active');
}
if (platform != 'default') {
t.setAttribute('data-active', '1');
addClass(t.parentNode, 'active');
if (t.parentNode.parentNode.parentNode.nodeName == 'LI') addClass(t.parentNode.parentNode.parentNode, 'active');
}
// Replace wallets by those for given platform and rotate.
var p = document.getElementById('wallets');
var ti = 200;
if (p.getAttribute('timeout') === null || p.getAttribute('timeout') === '' || !supportCSS('transition')) ti = 1;
addClass(p, 'disabled');
clearTimeout(p.getAttribute('timeout'));
p.setAttribute('timeout', setTimeout(function() {
p.innerHTML = '';
for (var i = 0, nds = document.getElementById('walletsswitch').childNodes, n = nds.length; i < n; i++) {
if (nds[i].nodeType != 1) continue;
var id = nds[i].id.split('-')[1];
if (document.getElementById('wallet-' + id)) continue;
var nd = null;
if (platform == 'default') {
var defpl = ['desktop', 'mobile'];
for (var ii = 0, nn = defpl.length; ii < nn; ii++) {
if (document.getElementById('wallet-' + id + '-' + defpl[ii])) var nd = document.getElementById('wallet-' + id + '-' + defpl[ii]);
}
}
else {
if (document.getElementById('wallet-' + id + '-' + platform)) var nd = document.getElementById('wallet-' + id + '-' + platform);
else if (document.getElementById('wallet-' + id + '-' + fallback) && document.getElementById('wallet-' + id + '-' + fallback).getAttribute('data-walletcompat').indexOf(platform) !== -1) var nd = document.getElementById('wallet-' + id + '-' + fallback);
}
if (nd === null) continue;
nd = nd.cloneNode(true);
nd.id = 'wallet-' + id;
addClass(nd, 'nohover');
p.appendChild(nd);
var t = null,
fallback = '',
walletMenu = document.getElementById('walletmenu'),
getMenuState = function() {
// Find active node in the menu for given platform and fallback category if in a submenu.
for (var i = 0, nds = walletMenu.getElementsByTagName('A'), n = nds.length; i < n; i++) {
if (nds[i].getAttribute('data-walletcompat') != platform) continue;
if (nds[i].getAttribute('data-active') == 1) return false;
t = nds[i];
var p = nds[i].parentNode.parentNode.parentNode;
if (p.nodeName == 'LI') fallback = p.getElementsByTagName('A')[0].getAttribute('data-walletcompat');
break;
}
walletRotate()
removeClass(p, 'disabled');
document.getElementById('walletsmobile').innerHTML = '';
}, ti));
return true;
},
updateMenu = function() {
// Set active nodes in the menu for the new platform.
for (var i = 0, nds = walletMenu.getElementsByTagName('A'), n = nds.length; i < n; i++) {
nds[i].removeAttribute('data-active');
removeClass(nds[i].parentNode, 'active');
}
if (platform != 'default') {
t.setAttribute('data-active', '1');
addClass(t.parentNode, 'active');
var p = t.parentNode.parentNode.parentNode;
if (p.nodeName == 'LI') addClass(p, 'active');
}
},
updateWallets = function() {
// Replace wallets by those for given platform and rotate them.
var p = document.getElementById('wallets');
var lasttimeout = p.getAttribute('data-timeout');
var timeout = (lasttimeout !== null && lasttimeout !== '' && supportCSS('transition')) ? 200 : 1;
addClass(p, 'disabled');
addClass(p, 'nohover');
clearTimeout(lasttimeout);
p.setAttribute('data-timeout', setTimeout(function() {
p.innerHTML = '';
var platforms = (platform == 'default') ? ['desktop', 'mobile'] : [platform];
for (var i = 0, nds = document.getElementById('walletsswitch').childNodes, n = nds.length; i < n; i++) {
if (nds[i].nodeType != 1) continue;
var id = nds[i].id.split('-')[1];
if (document.getElementById('wallet-' + id)) continue;
var nd = null;
for (var ii = 0, nn = platforms.length; ii < nn; ii++) {
var wp = document.getElementById('wallet-' + id + '-' + platforms[ii]);
if (wp) var nd = wp;
}
if (nd === null) {
var wf = document.getElementById('wallet-' + id + '-' + fallback);
if (wf && wf.getAttribute('data-walletcompat').indexOf(platform) !== -1) var nd = wf;
}
if (nd === null) continue;
nd = nd.cloneNode(true);
nd.id = 'wallet-' + id;
p.appendChild(nd);
}
walletRotate();
removeClass(p, 'disabled');
document.getElementById('walletsmobile').innerHTML = '';
}, timeout));
};
if(!getMenuState()) return;
updateMenu();
updateWallets();
}
function walletRotate() {
@ -455,41 +470,6 @@ for (var k in ar) {
}
}
function walletShow(e) {
// Show wallet on click on mobile or desktop.
var t = getEventTarget(e);
if (t.id == 'wallets') return;
while (t.parentNode.id != 'wallets') t = t.parentNode;
if (isMobile()) {
var p = document.getElementById('walletsmobile');
t = t.cloneNode(true);
p.innerHTML = '';
p.appendChild(t);
scrollToNode(p);
} else {
t.setAttribute('data-previousclass', t.className);
removeClass(t, 'nohover');
removeClass(t, 'disabled');
addEvent(t, 'mouseover', walletHide);
addEvent(t, 'mouseout', walletHide);
}
}
function walletHide(e) {
// Disable wallet when the mouse leaves the wallet bubble.
var t = getEventTarget(e);
if (t.id == 'wallets') return;
while (t.parentNode.id != 'wallets') t = t.parentNode;
clearTimeout(t.getAttribute('data-disabletimeout'));
if (e.type == 'mouseover') return;
t.setAttribute('data-disabletimeout', setTimeout(function() {
for (var i = 0, nds = t.getAttribute('data-previousclass').split(' '), n = nds.length; i < n; i++) addClass(t, nds[i]);
t.removeAttribute('data-disabletimeout');
removeEvent(t, 'mouseout', walletHide);
removeEvent(t, 'mouseover', walletHide);
}, 1));
}
function makeEditable(e) {
// An easter egg that makes the page editable when user click on the page and hold their mouse button for one second.
// This trick allows translators and writers to preview their work.