diff options
Diffstat (limited to 'sca-cpp/trunk/modules/js/htdocs/ui.js')
-rw-r--r-- | sca-cpp/trunk/modules/js/htdocs/ui.js | 913 |
1 files changed, 707 insertions, 206 deletions
diff --git a/sca-cpp/trunk/modules/js/htdocs/ui.js b/sca-cpp/trunk/modules/js/htdocs/ui.js index 2bd21f6ed4..64acf6c14f 100644 --- a/sca-cpp/trunk/modules/js/htdocs/ui.js +++ b/sca-cpp/trunk/modules/js/htdocs/ui.js @@ -26,37 +26,8 @@ var ui = {}; /** * Return a child element of a node with the given id. */ -ui.elementByID = function(node, id) { - if (node.skipNode == true) - return null; - if (node == document) - return document.getElementById(id); - for (var i in node.childNodes) { - var child = node.childNodes[i]; - if (isNull(child)) - continue; - if (child.id == id) - return child; - var gchild = ui.elementByID(child, id); - if (gchild != null) - return gchild; - } - return null; -}; - -/** - * Remove ids in a tree of elements. - */ -ui.removeElementIDs = function(node) { - if (!isNull(node.id)) - node.id = null; - for (var i in node.childNodes) { - var child = node.childNodes[i]; - if (isNull(child)) - continue; - ui.removeElementIDs(child); - } - return true; +ui.elementByID = function(id) { + return document.getElementById(id); }; /** @@ -65,16 +36,25 @@ ui.removeElementIDs = function(node) { function $(id) { if (id == document) return document; - return memo(document, '$' + id, function() { - return ui.elementByID($(document), id); - }); + return document.getElementById(id); } /** - * Un-memoize elements previously found by id. + * Remove ids from a tree of elements. */ -ui.unmemo$ = function(prefix) { - return prefix? unmemo(document, '$' + prefix) : unmemo(document); +ui.removeElementIDs = function(node) { + function cleanIDs(node) { + for(var i = 0; i < node.childNodes.length; i++) { + var c = node.childNodes[i]; + if(c.nodeType == 1) { + if (c.id != null) + c.id = null; + cleanIDs(c); + } + } + return true; + } + return cleanIDs(node); }; /** @@ -105,7 +85,7 @@ ui.fragment = function(url) { /** * Return the path and parameters of a URL. */ -ui.pathandparams = function(url) { +ui.pathAndParams = function(url) { var u = '' + url; var ds = u.indexOf('//'); var u2 = ds > 0? u.substring(ds + 2) : u; @@ -117,7 +97,7 @@ ui.pathandparams = function(url) { * Return a dictionary of query parameters in a URL. */ ui.queryParams = function(url) { - var qp = new Array(); + var qp = []; var qs = ui.query(url).split('&'); for (var i = 0; i < qs.length; i++) { var e = qs[i].indexOf('='); @@ -131,7 +111,7 @@ ui.queryParams = function(url) { * Return a dictionary of fragment parameters in a URL. */ ui.fragmentParams = function(url) { - var qp = new Array(); + var qp = []; var qs = ui.fragment(url).split('&'); for (var i = 0; i < qs.length; i++) { var e = qs[i].indexOf('='); @@ -142,23 +122,79 @@ ui.fragmentParams = function(url) { }; /** + * Get a style property a DOM element. + */ +ui.getStyle = function(e, name) { + return e.style.getPropertyValue(name); +}; + +/** + * Set a style property of a DOM element. + */ +ui.setStyle = function(e, name, v, async) { + if (e.style.getPropertyValue(name) == v) + return false; + if (!async) + return e.style.setProperty(name, v, null); + ui.render(function() { + return e.style.setProperty(name, v, null); + }); + return true; +}; + +/** + * Remove a style property from a DOM element. + */ +ui.removeStyle = function(e, name, async) { + if (!async) + return e.style.removeProperty(name); + ui.render(function() { + return e.style.removeProperty(name); + }); + return true; +}; + +/** + * Set the CSS class of a DOM element. + */ +ui.getClass = function(e) { + return e.className; +}; + +/** + * Set the CSS class of a DOM element. + */ +ui.setClass = function(e, v, async) { + if (e.className == v) + return false; + if (!async) { + e.className = v; + return true; + } + ui.render(function() { + e.className = v; + }); + return true; +}; + +/** * Convert a base64-encoded PNG image to a data URL. */ -ui.b64png = function(b64) { +ui.b64PNG = function(b64) { return 'data:image/png;base64,' + b64.trim(); }; /** * Convert a base64-encoded JPEG image to a data URL. */ -ui.b64jpeg = function(b64) { +ui.b64JPEG = function(b64) { return 'data:image/jpeg;base64,' + b64.trim(); }; /** * Convert a data URL to a base64-encoded image. */ -ui.imgb64 = function(img) { +ui.imgB64 = function(img) { if (img.startsWith('data:')) return img.split(',')[1] return ''; @@ -198,36 +234,36 @@ ui.declareScript = function(s) { * Return the scripts elements under a given element. */ ui.innerScripts = function(e) { - return map(function(s) { return s.text; }, nodeList(e.getElementsByTagName('script'))); + return nodeList(e.getElementsByTagName('script')); }; /** * Evaluate a script. */ ui.evalScript = function(s) { - return eval('(function evalscript() { try { \n' + s + '\n} catch(e) { debug(e.stack); throw e; }})();'); + return eval('(function evalscript() {\n' + s + '\n})();'); }; /** * Include a script. */ ui.includeScript = function(s) { - //debug('include', s); - return eval('try { \n' + s + '\n} catch(e) { debug(e.stack); throw e; }'); + debug('ui.include', s); + return eval(s); }; /** * Return true if the client is a mobile device. */ -ui.mobiledetected = false; +ui.mobileDetected = false; ui.mobile = false; ui.isMobile = function() { - if (ui.mobiledetected) + if (ui.mobileDetected) return ui.mobile; var ua = navigator.userAgent; - if (ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/iPod/i) || ua.match(/Android/i) || ua.match(/Blackberry/i) || ua.match(/WebOs/i)) + if (ua.match(/iPhone/i) || ua.match(/iPad/i) || ua.match(/iPod/i) || ua.match(/Android/i) || ua.match(/Blackberry/i) || ua.match(/WebOs/i) || ua.match(/Mobile.*Firefox/i)) ui.mobile = true; - ui.mobiledetected = true; + ui.mobileDetected = true; return ui.mobile; }; @@ -246,17 +282,17 @@ ui.webkitVersion = function() { }; /** - * Return the Safari version. + * Return true if the client is Android based. */ -ui.browserVersion = function() { - return Number(navigator.userAgent.replace(/.*Version\/(\d+\.\d+).*/, '$1')); +ui.isAndroid = function() { + return navigator.userAgent.match(/Android/i); }; /** - * Return true if the client is Android based. + * Return the Android version. */ -ui.isAndroid = function() { - return navigator.userAgent.match(/Android/i); +ui.androidVersion = function() { + return Number(navigator.userAgent.replace(/.*Version\/(\d+\.\d+).*/, '$1')); }; /** @@ -281,6 +317,13 @@ ui.isSafari = function() { }; /** + * Return the Safari version. + */ +ui.safariVersion = function() { + return Number(navigator.userAgent.replace(/.*Version\/(\d+\.\d+).*/, '$1')); +}; + +/** * Return true if the client is Chrome. */ ui.isChrome = function() { @@ -288,6 +331,13 @@ ui.isChrome = function() { }; /** + * Return the Chrome version. + */ +ui.chromeVersion = function() { + return Number(navigator.userAgent.replace(/.*Chrome\/(\d+\.\d+).*/, '$1')); +}; + +/** * Return true if the client is Internet Explorer. */ ui.isMSIE = function() { @@ -302,25 +352,55 @@ ui.msieVersion = function() { }; /** + * Run a UI animation. + */ +ui.animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || + function(f) { + if (isNull(f.interval)) { + // First call, setup the interval + f.interval = window.setInterval(function animation() { + f.clearInterval = true; + + // Call the animation function + f(); + + // If the animation function didn't call ui.animation again to + // request another animation frame, clear the interval + if (f.clearInterval) { + f.clearInterval = false; + window.clearInterval(f.interval); + f.interval = null; + } + }, 16); + } else { + // Called to request another animation frame, do not clear the + // interval + f.clearInterval = false; + } + }; + +ui.animation = function(f) { + return ui.animationFrame.call(window, f); +}; + +/** * Run a UI rendering function asynchronously. */ -ui.async = function(f, t) { - window.setTimeout(function() { - return f(); - }, isNull(t)? 0 : t); - return true; +ui.render = function(f) { + return ui.animation(f); }; /** * Delay the execution of a function. */ -ui.delayed = {} -ui.delay = function(f, t) { - var id = window.setTimeout(function() { - delete ui.delayed[id]; +ui.unimportant = {} +ui.delay = function(f, t, unimportant) { + var id = window.setTimeout(function delayed() { + delete ui.unimportant[id]; return f(); }, isNull(t)? 0 : t); - ui.delayed[id] = id; + if (unimportant) + ui.unimportant[id] = id; return id; }; @@ -328,56 +408,253 @@ ui.delay = function(f, t) { * Cancel the execution of a delayed function. */ ui.cancelDelay = function(id) { - delete ui.delayed[id]; + delete ui.unimportant[id]; return window.clearTimeout(id); }; /** - * Run a UI animation. + * Convert a CSS position to a numeric position. */ -ui.animationFrame = null; -ui.animation = function(f) { - if (isNull(ui.animationFrame)) - // Use requestAnimationFrame when available, fallback to setInterval - ui.animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(f) { - if (!('interval' in f) || isNull(f.interval)) { - // First call, setup the interval - f.interval = window.setInterval(function animation() { - f.clearInterval = true; - try { - f(); - } catch(ex) {} - // If the animation function didn't call ui.animation again to - // request another animation frame, clear the interval - if (f.clearInterval) { - f.clearInterval = false; - window.clearInterval(f.interval); - f.interval = null; - } - }, 16); - } else { - // Called to request another animation frame, do not clear the - // interval - f.clearInterval = false; - } - }; - return ui.animationFrame.call(window, f); +ui.npos = function(p) { + return p == null || p == ''? 0 : Number(p.substr(0, p.length - 2)); }; /** - * Convert a CSS position to a numeric position. + * Convert a numeric position to a CSS pixel position. */ -ui.numpos = function(p) { - return p == ''? 0 : Number(p.substr(0, p.length - 2)); +ui.pxpos = function(p) { + return p + 'px'; }; /** - * Convert a numeric position to a CSS pixel position. + * Show a status message. */ -ui.pixpos = function(p) { - return p + 'px'; +ui.statusElement = undefined; + +ui.initStatus = function() { + ui.statusElement = $('status'); + if (isNull(ui.statusElement)) + return; + ui.setClass(ui.statusElement, ui.isMobile()? 'status3dm' : 'status3d'); + ui.setStyle(ui.statusElement, 'display', 'none'); + + function divtransitionend(e) { + ui.setClass(e.target, ui.isMobile()? 'status3dm' : 'status3d'); + ui.setStyle(e.target, 'display', 'none'); + e.target.error = false; + } + ui.statusElement.addEventListener('webkitTransitionEnd', divtransitionend, false); + ui.statusElement.addEventListener('transitionend', divtransitionend, false); + return true; +}; + +ui.status = function(s, c) { + debug('ui.status', s); + if(isNull(ui.statusElement) || ui.statusElement.error) + return s; + ui.statusElement.innerHTML = '<span class="' + (c? c : 'okstatus') + '">' + s + '</span>'; + ui.setClass(ui.statusElement, ui.isMobile()? 'status3dm' : 'status3d'); + ui.setStyle(ui.statusElement, 'display', 'block'); + ui.statusElement.error = c == 'errorstatus'; + if(ui.statusElement.delay) + ui.cancelDelay(ui.statusElement.delay); + ui.statusElement.delay = ui.delay(function hidestatus() { + ui.setClass(ui.statusElement, ui.isMobile()? 'statusout3dm' : 'statusout3d'); + ui.statusElement.error = false; + }, c == 'errorstatus'? 8000 : 3000); + return s; +}; + +/** + * Show an error message. + */ +ui.error = function(s) { + debug('ui.error', s); + return ui.status(s, 'errorstatus'); +}; + +/** + * Show the online/offline status. + */ +ui.onlineStatus = function() { + return navigator.onLine? true : errorstatus('Offline'); +}; + +/** + * Show the working/ready indicator. + */ +ui.workingElement = undefined; +ui.initWorking = function() { + ui.workingElement = $('working'); +}; + +ui.working = function() { + debug('ui.working'); + if (isNull(ui.workingElement)) + return false; + return ui.setStyle(ui.workingElement, 'display', 'block'); +}; + +ui.ready = function() { + debug('ui.ready'); + if (isNull(ui.workingElement)) + return false; + return ui.setStyle(ui.workingElement, 'display', 'none'); +}; + +/** + * Get and cache a resource. + */ +ui.appcache = {}; +ui.appcache.get = function(uri, mode) { + debug('ui.appcache.get', uri, mode); + + // Get resource from local storage first + var h = uri.indexOf('#'); + var u = h == -1? uri : uri.substring(0, h); + if(mode != 'remote') { + var item = lstorage.getItem('ui.r.' + u); + if(item != null && item != '') + return item; + if(mode == 'local') + return undefined; + } + + // Get resource from network + var http = new XMLHttpRequest(); + http.open("GET", mode == 'remote'? (u + '?t=' + new Date().getTime() + '&r=' + Math.random()) : u, false); + http.setRequestHeader('Accept', '*/*'); + http.setRequestHeader('X-Cache-Control', 'no-cache'); + http.send(null); + if(http.status == 200) { + var ct = http.getResponseHeader("Content-Type"); + if(http.responseText == '' || ct == null || ct == '') { + error('http error', u, 'No-Content'); + return undefined; + } + lstorage.setItem('ui.r.' + u, http.responseText); + return http.responseText; + } + error('http error', u, http.status, http.statusText); + + // Redirect to login page + if(http.status == 403 && window.top.location.href.pathname != '/logout/dologout/') { + if(window.onloginredirect) + window.onloginredirect(new Error('403 ' + http.statusText)); + } + return undefined; +}; + +/** + * Remove a resource from the cache. + */ +ui.appcache.remove = function(uri) { + debug('ui.appcache.remove', uri); + var h = uri.indexOf('#'); + var u = h == -1? uri : uri.substring(0, h); + return lstorage.removeItem(u); +}; + +/** + * Default app cache handling behavior. + */ +ui.onappcache = function(manifest, resources) { + + if(ui.isMobile() && !ui.isFirefox()) { + // On mobile devices, trigger usage of an application cache manifest + // Except on mobile Firefox which fails to send cookies with cache manifest requests + window.onappcachechecking = function(e) { + debug('appcache checking', e); + ui.working(); + }; + window.onappcacheerror = function(e) { + debug('appcache error', e); + ui.onlineStatus(); + ui.ready(); + return false; + }; + window.onappcachenoupdate = function(e) { + debug('appcache noupdate', e); + ui.ready(); + }; + window.onappcachedownloading = function(e) { + debug('appcache downloading', e); + ui.working(); + ui.status('Updating'); + }; + window.onappcacheprogress = function(e) { + debug('appcache progress', e); + ui.working(); + ui.status('Updating'); + }; + window.onappcacheupdateready = function(e) { + debug('appcache updateready', e); + + // Update offline resources in local storage and reload the page + ui.status('Updating'); + applicationCache.swapCache(); + ui.delay(function swapappcache() { + debug('appcache swapped', e); + map(function(res) { + ui.appcache.remove(car(res)); + ui.appcache.get(car(res), 'remote'); + }, resources); + ui.status('Installed'); + ui.ready(); + + debug('reloading'); + window.location.reload(); + }); + }; + window.onappcachecached = function(e) { + debug('appcache cached', e); + + // Install offline resources in local storage + ui.status('Installing'); + ui.delay(function installoffline() { + map(function(res) { + ui.appcache.remove(car(res)); + ui.appcache.get(car(res), 'remote'); + }, resources); + ui.status('Installed'); + ui.ready(); + }); + }; + + window.onloadappcache = function() { + debug('appcache iframe loaded'); + }; + + var installer = $('installer'); + installer.innerHTML = '<iframe src="' + manifest + '/" class="installer"></iframe>'; + + } else { + // On non-mobile devices, check for cache-manifest changes ourselves. + ui.working(); + var lcmf = ui.appcache.get(manifest + '/cache-manifest.cmf', 'local'); + var rcmf = ui.appcache.get(manifest + '/cache-manifest.cmf', 'remote'); + if(lcmf == rcmf) { + ui.ready(); + return true; + } + + debug('cache-manifest changed, reloading'); + ui.status(isNull(lcmf)? 'Installing' : 'Updating'); + ui.delay(function reloadapp() { + map(function(res) { + ui.appcache.remove(car(res)); + ui.appcache.get(car(res), 'remote'); + }, resources); + ui.ready(); + if(!isNull(lcmf)) { + ui.status('Installed'); + ui.ready(); + + debug('reloading'); + window.location.reload(); + } + }); + } }; /** @@ -385,28 +662,49 @@ ui.pixpos = function(p) { */ ui.filler = null; ui.onload = function() { + debug('ui.onload'); + + // Initialize status and working elements + ui.initStatus(); + ui.initWorking(); + + // Set orientation change handler + document.body.onorientationchange = function(e) { + return ui.onorientationchange(e); + }; + + // Handle network offline/online events. + window.addEventListener('offline', function(e) { + debug('going offline'); + ui.status('Offline'); + }, false); + window.addEventListener('online', function(e) { + debug('going online'); + ui.status('Online'); + }, false); // Add a filler div to make sure we can scroll if (ui.isMobile()) { ui.filler = document.createElement('div'); ui.filler.id = 'filler'; - ui.filler.className = 'filler'; - ui.filler.style.height = ui.pixpos(window.orientation == 0? screen.height : screen.width * 2); + ui.setClass(ui.filler, 'filler'); + ui.setStyle(ui.filler, 'height', ui.pxpos(window.orientation == 0? Math.floor(window.innerHeight * 1.5) : Math.floor(window.innerHeight * 1.5))); document.body.appendChild(ui.filler); } else { // Style scroll bars - var h = document.getElementsByTagName('html'); - if (!isNull(h)) - h[0].className = h[0].className? h[0].classname + ' flatscrollbars' : 'flatscrollbars'; + var h = nodeList(document.getElementsByTagName('html')); + if (!isNull(h)) { + ui.setClass(car(h), car(h).className? car(h).classname + ' flatscrollbars' : 'flatscrollbars'); + } } // Scroll to hide the address bar - document.body.style.display = 'block'; - window.scrollTo(0, 0); + ui.setStyle(document.body, 'display', 'block'); + document.body.scrollTop = 0; // Set unload handler window.onunload = function() { - window.scrollTo(0, 0); + document.body.scrollTop = 0; return true; }; @@ -417,13 +715,15 @@ ui.onload = function() { * Default orientation change behavior. */ ui.onorientationchange = function(e) { + debug('ui.onorientationchange'); // Adjust filler height if (!isNull(ui.filler)) - ui.filler.style.height = ui.pixpos(window.orientation == 0? screen.height : screen.width); + ui.setStyle(ui.filler, 'height', ui.pxpos(window.orientation == 0? Math.floor(window.innerHeight * 1.5) : Math.floor(window.innerHeight * 1.5))); - // Scroll to hide the address bar - window.scrollTo(0, 0); + // Scroll to refresh the page + document.body.scrollTop = document.body.scrollTop + 1; + document.body.scrollTop = document.body.scrollTop - 1; return true; }; @@ -431,22 +731,52 @@ ui.onorientationchange = function(e) { * Navigate to a new document. */ ui.navigate = function(url, win) { - //debug('navigate', url, win); + debug('ui.navigate', url, win); + if (url == '' || url == '#') + return false; + + function cleanup() { + // Cleanup window event handlers + window.onclick = null; + if (!ui.isMobile()) { + window.onmousedown = null; + window.onmouseup = null; + window.onmousemove = null; + } else { + window.ontouchstart = null; + window.ontouchend = null; + window.ontouchmove = null; + } + + // Cancel any cancelable HTTP requests + if (typeof HTTPBindingClient != 'undefined') + HTTPBindingClient.cancelRequests(); + + // Automatically cancel unimportant timers + for (var d in ui.unimportant) + ui.cancelDelay(d); + return true; + } // Open a new window if (win == '_blank') { + debug('window.open', url, win); window.top.open(url, win); return false; } // Open a new document in the current window if (win == '_self') { + cleanup(); + debug('window.open', url, win); window.top.open(url, win); return false; } // Reload the current window if (win == '_reload') { + cleanup(); + debug('window.reload', url); window.top.location = url; window.top.location.reload(); return false; @@ -454,36 +784,18 @@ ui.navigate = function(url, win) { // Let the current top window handle the navigation if (win == '_view') { - if (!window.top.onnavigate) - return window.top.open(url, '_self'); + cleanup(); - // Cleanup window event handlers - window.onclick = null; - if (!ui.isMobile()) { - window.onmousedown = null; - window.onmouseup = null; - window.onmousemove = null; - } else { - window.ontouchstart = null; - window.ontouchend = null; - window.ontouchmove = null; + if (!window.top.onnavigate) { + debug('window.open', url, '_self'); + window.top.open(url, '_self'); + return false; } - - // Cancel any cancelable HTTP requests - HTTPBindingClient.cancelRequests(); - - // Cleanup memoized element lookups - ui.unmemo$(); - - // Cancel any timers - for (d in ui.delayed) - ui.cancelDelay(d); - - // Navigate window.top.onnavigate(url); return false; } + debug('window.open', url, win); window.top.open(url, win); return false; } @@ -492,7 +804,7 @@ ui.navigate = function(url, win) { * Bind a click handler to a widget. */ ui.ontouchstart = function(widget, e) { - //debug('ontouchstart'); + debug('ui.ontouchstart', widget.id); widget.down = true; widget.moved = false; var t = e.touches[0]; @@ -514,10 +826,11 @@ ui.ontouchmove = function(widget, e) { }; ui.ontouchend = function(widget, e) { - //debug('ontouchend'); + debug('ui.ontouchend', widget.id); widget.down = false; if (!widget.moved) { e.preventDefault(); + debug('ui.fastonclick', widget.id); return widget.onclick(e); } }; @@ -535,7 +848,7 @@ ui.onclick = function(widget, handler) { }; } widget.onclick = function(e) { - //debug('onclick'); + debug('ui.onclick', widget.id); return handler(e); }; return widget; @@ -544,61 +857,131 @@ ui.onclick = function(widget, handler) { /** * Build a portable <a href> tag. */ -ui.href = function(id, loc, target, html) { +ui.href = function(id, loc, target, clazz, html) { if (target == '_blank') - return '<a id="' + id + '" href="' + loc + '" target="_blank">' + html + '</a>'; - return '<a id="' + id + '" href="' + loc + '" ' + (ui.isMobile()? 'ontouchstart="return ui.ontouchstart(this, event);" ontouchmove="return ui.ontouchmove(this, event);" ontouchend="return ui.ontouchend(this, event);" ' : '') + 'onclick="return ui.navigate(\'' + loc + '\', \'' + target + '\');">' + html + '</a>'; + return '<a href="' + loc + '" target="_blank"><span id="' + id + '" class="' + clazz + '">' + html + '</span></a>'; + return '<a href="' + loc + '" ' + + (ui.isMobile()? 'ontouchstart="return ui.ontouchstart(this, event);" ontouchmove="return ui.ontouchmove(this, event);" ontouchend="return ui.ontouchend(this, event);" ' : '') + + 'onclick="return ui.navigate(\'' + loc + '\', \'' + target + '\');"><span id="' + id + '" class="' + clazz + '">' + html + '</span></a>'; +}; + +/** + * Update a <a href> tag. + */ +ui.updateHref = function(item, loc, target) { + if (!isNull(loc) && !isNull(target)) { + var link = item.parentNode; + if (target == '_blank') { + link.href = loc; + link.target = '_blank'; + } else { + link.href = loc; + link.setAttribute('onclick', 'return ui.navigate(\'' + loc + '\', \'' + target + '\');'); + } + } + return item; }; /** * Build a menu bar. */ -ui.menu = function(id, name, href, target, hilight) { +ui.menuItem = function(id, name, href, target, hilight) { function Menu() { this.content = function() { if (hilight == true) - return ui.href(id, href, target, '<span class="tbarsmenu">' + name + '</span>'); + return ui.href(id, href, target, 'tbarsmenu', name); else if (hilight == false) - return ui.href(id, href, target, '<span class="tbaramenu">' + name + '</span>'); + return ui.href(id, href, target, 'tbaramenu', name); else - return ui.href(id, href, target, '<span class="' + hilight + '">' + name + '</span>'); + return ui.href(id, href, target, hilight, name); }; } return new Menu(); }; -ui.menufunc = function(id, name, fun, hilight) { +ui.textItem = function(id, name, hilight) { + function Item() { + this.content = function() { + return name; + }; + } + return new Item(); +}; + +ui.menuFunc = function(id, name, fun, hilight) { function Menu() { this.content = function() { - function href(id, fun, html) { - return '<a id="' + id + '" href="/" ' + (ui.isMobile()? 'ontouchstart="return ui.ontouchstart(this, event);" ontouchmove="return ui.ontouchmove(this, event);" ontouchend="return ui.ontouchend(this, event);" ' : '') + 'onclick="' + fun + '">' + html + '</a>'; + function href(id, fun, clazz, html) { + return '<a href="/" ' + + (ui.isMobile()? 'ontouchstart="return ui.ontouchstart(this, event);" ontouchmove="return ui.ontouchmove(this, event);" ontouchend="return ui.ontouchend(this, event);" ' : '') + + 'onclick="' + fun + '"><span id="' + id + '" class="' + clazz + '">' + html + '</span></a>'; } if (hilight == true) - return href(id, fun, '<span class="tbarsmenu">' + name + '</span>'); + return href(id, fun, 'tbarsmenu', name); else if (hilight == false) - return href(id, fun, '<span class="tbaramenu">' + name + '</span>'); + return href(id, fun, 'tbaramenu', name); else - return href(id, fun, '<span class="' + hilight + '">' + name + '</span>'); + return href(id, fun, hilight, name); }; } return new Menu(); }; -ui.menubar = function(left, right) { - var bar = ''; - for (i in left) - bar = bar + '<span class="tbarleft">' + left[i].content() + '</span>'; - for (i in right) - bar = bar + '<span class="tbarright">' + right[i].content() + '</span>'; - return bar; +ui.menuBar = function(left, center, right) { + return '<span class="tbartitle">' + + reduce(function(bar, item) { + return bar + '<span class="tbarcenter">' + item.content() + '</span>'; + }, '', center) + + '</span><span class="tbaritems">' + + reduce(function(bar, item) { + return bar + '<span class="tbarleft">' + item.content() + '</span>'; + }, '', left) + + reduce(function(bar, item) { + return bar + '<span class="tbarright">' + item.content() + '</span>'; + }, '', right) + + '</span>'; +}; + +ui.cmenuBar = function(items) { + return reduce(function(bar, item) { + return bar + '<span class="tbarcenter">' + item.content() + '</span>'; + }, '', items); }; /** - * Convert a list of elements to an HTML table. + * Update a menu item. */ -ui.datatable = function(l) { +ui.updateMenuItem = function(item, name, href, target, hilight) { + if (!isNull(name)) { + if (item.innerHTML != name) + item.innerHTML = name; + } + if (!isNull(hilight)) { + if (hilight == true) + ui.setClass(item, 'tbarsmenu'); + else if (hilight == false) + ui.setClass(item, 'tbaramenu'); + else + ui.setClass(item, hilight); + } + if (!isNull(href) && !isNull(target)) { + var link = item.parentNode; + if (target == '_blank') { + link.href = href; + link.target = '_blank'; + } else { + link.href = href; + link.setAttribute('onclick', 'return ui.navigate(\'' + href + '\', \'' + target + '\');'); + } + } + return item; +}; +/** + * Convert a list of elements to an HTML table. + */ +ui.dataTable = function(l) { function indent(i) { if (i == 0) return ''; @@ -621,30 +1004,23 @@ ui.datatable = function(l) { // Generate table row for a simple element value if (elementHasValue(e)) { var v = elementValue(e); - if (!isList(v)) { - return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' + - '<td class="datatdr tdw">' + (v != null? v : '') + '</td></tr>' + - rows(cdr(l), i); - } - + if (!isList(v)) + return '<tr><td class="datatdl">' + indent(i) + elementName(e).substring(1) + '</td>' + + '<td class="datatdr tdw">' + (v != null? v : '') + '</td></tr>' + rows(cdr(l), i); return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i); } // Generate table row for an element with children - return '<tr><td class="datatdl">' + indent(i) + elementName(e).slice(1) + '</td>' + - '<td class="datatdr tdw">' + '</td></tr>' + - rows(elementChildren(e), i + 1) + - rows(cdr(l), i); + return '<tr><td class="datatdl">' + indent(i) + elementName(e).substring(1) + '</td>' + + '<td class="datatdr tdw">' + '</td></tr>' + rows(elementChildren(e), i + 1) + rows(cdr(l), i); } - return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>'; }; /** * Convert a list of elements to an HTML single column table. */ -ui.datalist = function(l) { - +ui.dataList = function(l) { function rows(l, i) { if (isNull(l)) return ''; @@ -661,25 +1037,21 @@ ui.datalist = function(l) { // Generate table row for a simple element value if (elementHasValue(e)) { var v = elementValue(e); - if (!isList(v)) { - return '<tr><td class="datatd tdw">' + (v != null? v : '') + '</td></tr>' + - rows(cdr(l), i); - } - + if (!isList(v)) + return '<tr><td class="datatd tdw">' + (v != null? v : '') + '</td></tr>' + rows(cdr(l), i); return rows(expandElementValues(elementName(e), v), i) + rows(cdr(l), i); } // Generate rows for an element's children return rows(elementChildren(e), i + 1) + rows(cdr(l), i); } - return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>'; }; /** * Read a file and convert it to a data url. */ -ui.readfile = function(file, onerror, onprogress, onload) { +ui.readFile = function(file, onerror, onprogress, onload) { var reader = new FileReader(); reader.onerror = function(e) { return onerror(); @@ -694,31 +1066,147 @@ ui.readfile = function(file, onerror, onprogress, onload) { }; /** + * Draw an image on a canvas and convert it to a data URL. + */ +ui.drawImage = function(img, onload, width, height, crop) { + // Rotate an image + function rotate(icanvas, onload) { + debug('ui.drawImage.rotate'); + var img = document.createElement('img'); + img.onload = function() { + var canvas = document.createElement('canvas'); + canvas.width = icanvas.height; + canvas.height = icanvas.width; + var ctx = canvas.getContext('2d'); + ctx.setTransform(0, 1, -1, 0, icanvas.height, 0); + ctx.drawImage(img, 0, 0, icanvas.width, icanvas.height); + onload(canvas); + }; + img.src = icanvas.toDataURL('image/png') + return true; + } + + // Draw the image on a canvas and convert it to a JPEG data URL + function draw(img, onload, sx, sy, swidth, sheight, tx, ty, twidth, theight) { + debug('ui.drawImage.draw', sx, sy, swidth, sheight, tx, ty, twidth, theight); + var canvas = document.createElement('canvas'); + canvas.width = twidth; + canvas.height = theight; + var ctx = canvas.getContext('2d'); + ctx.drawImage(img, sx, sy, swidth, sheight, tx, ty, twidth, theight); + return onload(canvas); + } + + // Resize and optionally crop an image + function resize(img, onload, width, height, oncrop) { + debug('ui.drawImage.resize'); + var iwidth = img.width; + var iheight = img.height; + if (width || height || crop) { + if (crop) { + // Crop to fit target canvas size + var tratio = width / height; + var oratio = iwidth / iheight; + if (tratio > oratio) { + var scale = width / iwidth; + var cwidth = iwidth; + var cheight = height / scale; + var cut = (iheight - cheight) / 2; + // Crop top and bottom edges, then resize + return draw(img, onload, 0, cut, cwidth, cut + cheight, 0, 0, width, height); + } else if (tratio < oratio) { + var scale = height / iheight; + var cwidth = width / scale; + var cheight = iheight; + var cut = (iwidth - cwidth) / 2; + // Crop left and right edges, then resize + return draw(img, onload, cut, 0, cut + cwidth, cheight, 0, 0, width, height); + } else { + // Just resize + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, width, height); + } + } else { + // Resize to make the image fit + if (iwidth <= width && iheight == height) { + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, iwidth, iheight); + } else { + var tratio = width / height; + var oratio = iwidth / iheight; + if (tratio > oratio) { + // Resize to make height fit + var scale = height / iheight; + var swidth = iwidth * scale; + var sheight = height; + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, swidth, sheight); + } else if (tratio < oratio) { + // Resize to make width fit + var scale = width / iwidth; + var swidth = width; + var sheight = iheight * scale; + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, swidth, sheight); + } else { + // Resize to make both width and height fit + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, width, height); + } + } + } + } else { + // Draw image as is + return draw(img, onload, 0, 0, iwidth, iheight, 0, 0, iwidth, iheight); + } + } + + // Draw the image, optionally rotate, scale and crop it + (function drawImage() { + var iwidth = img.width; + var iheight = img.height; + document.body.removeChild(img); + var nwidth = img.width; + var nheight = img.height; + debug('ui.drawImage', 'img.width', iwidth, 'img.height', iheight, 'nwidth', nwidth, 'nheight', nheight, 'width', width, 'height', height, 'crop', crop); + + if (iwidth != iheight && iwidth == nheight) + // Rotate and resize the image + return resize(img, function(canvas) { + return rotate(canvas, function(canvas) { + return onload(canvas.toDataURL('image/jpeg', 0.95)); + }); + }, height, width, crop); + else { + // Just resize the image + return resize(img, function(canvas) { + return onload(canvas.toDataURL('image/jpeg', 0.95)); + }, width, height, crop); + } + })(); +} + +/** * Read an image url and convert it to a data url. */ -ui.readimageurl = function(url, onerror, onprogress, onload, width, height) { - // Create a canvas to draw the image - var canvas = document.createElement('canvas'); - if (width) - canvas.width = width; - if (height) - canvas.height = height; +ui.readImageURL = function(url, onerror, onprogress, onload, width, height, crop) { + if(!width && !height && !crop && url.substring(0, 5) == 'data:') { + // Just use the given data URL if we're not resizing the image + debug('ui.readImageURL', 'original url'); + onprogress(90); + ui.delay(function() { + return onload(url); + }); + return true; + } // Create an image - var img = new Image(); + var img = document.createElement('img'); + ui.setStyle(img, 'visibility', 'hidden'); + document.body.appendChild(img); img.onerror = function(e) { + document.body.removeChild(img); return onerror(); }; img.onload = function() { // Draw the image - var ctx = canvas.getContext('2d'); - if (width || height) - ctx.drawImage(img, 0, 0, width, height); - else - ctx.drawImage(img, 0, 0); - - // Convert new canvas image to a data url - return onload(canvas.toDataURL('image/png')); + debug('ui.readImageURL', 'new data url'); + return ui.drawImage(img, onload, width, height, crop); }; // Load the image @@ -730,11 +1218,24 @@ ui.readimageurl = function(url, onerror, onprogress, onload, width, height) { /** * Read an image file or url and convert it to a data url. */ -ui.readimage = function(img, onerror, onprogress, onload, width, height) { +ui.readImageFile = function(img, onerror, onprogress, onload, width, height, crop) { if (isString(img)) - return ui.readimageurl(img, onerror, onprogress, onload, width, height); - return ui.readfile(img, onerror, onprogress, function(url) { - return ui.readimageurl(url, onerror, onprogress, onload, width, height); - }, width, height); + return ui.readImageURL(img, onerror, onprogress, onload, width, height, crop); + return ui.readFile(img, onerror, onprogress, function onfile(url) { + return ui.readImageURL(url, onerror, onprogress, onload, width, height, crop); + }); +}; + +/** + * Read an image and convert it to a data url. + */ +ui.readImage = function(img, onload, width, height, crop) { + if(!width && !height && img.src.substring(0, 5) == 'data:') { + // Just use the given data URL if we're not resizing the image + return onload(img.src); + } + + // Draw the image + return ui.drawImage(img, onload, width, height, crop); }; |