summaryrefslogtreecommitdiffstats
path: root/sca-cpp/trunk/modules/js/htdocs/ui.js
diff options
context:
space:
mode:
Diffstat (limited to 'sca-cpp/trunk/modules/js/htdocs/ui.js')
-rw-r--r--sca-cpp/trunk/modules/js/htdocs/ui.js903
1 files changed, 702 insertions, 201 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)
+ui.elementByID = function(id) {
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;
};
/**
@@ -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,53 +352,18 @@ ui.msieVersion = function() {
};
/**
- * Run a UI rendering function asynchronously.
- */
-ui.async = function(f, t) {
- window.setTimeout(function() {
- return f();
- }, isNull(t)? 0 : t);
- return true;
-};
-
-/**
- * Delay the execution of a function.
- */
-ui.delayed = {}
-ui.delay = function(f, t) {
- var id = window.setTimeout(function() {
- delete ui.delayed[id];
- return f();
- }, isNull(t)? 0 : t);
- ui.delayed[id] = id;
- return id;
-};
-
-/**
- * Cancel the execution of a delayed function.
- */
-ui.cancelDelay = function(id) {
- delete ui.delayed[id];
- return window.clearTimeout(id);
-};
-
-/**
* Run a UI animation.
*/
-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 ||
+ui.animationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame ||
function(f) {
- if (!('interval' in f) || isNull(f.interval)) {
+ if (isNull(f.interval)) {
// First call, setup the interval
f.interval = window.setInterval(function animation() {
f.clearInterval = true;
- try {
+
+ // Call the animation function
f();
- } catch(ex) {}
+
// If the animation function didn't call ui.animation again to
// request another animation frame, clear the interval
if (f.clearInterval) {
@@ -363,50 +378,333 @@ ui.animation = function(f) {
f.clearInterval = false;
}
};
+
+ui.animation = function(f) {
return ui.animationFrame.call(window, f);
};
/**
+ * Run a UI rendering function asynchronously.
+ */
+ui.render = function(f) {
+ return ui.animation(f);
+};
+
+/**
+ * Delay the execution of a function.
+ */
+ui.unimportant = {}
+ui.delay = function(f, t, unimportant) {
+ var id = window.setTimeout(function delayed() {
+ delete ui.unimportant[id];
+ return f();
+ }, isNull(t)? 0 : t);
+ if (unimportant)
+ ui.unimportant[id] = id;
+ return id;
+};
+
+/**
+ * Cancel the execution of a delayed function.
+ */
+ui.cancelDelay = function(id) {
+ delete ui.unimportant[id];
+ return window.clearTimeout(id);
+};
+
+/**
* Convert a CSS position to a numeric position.
*/
-ui.numpos = function(p) {
- return p == ''? 0 : Number(p.substr(0, p.length - 2));
+ui.npos = function(p) {
+ return p == null || p == ''? 0 : Number(p.substr(0, p.length - 2));
};
/**
* Convert a numeric position to a CSS pixel position.
*/
-ui.pixpos = function(p) {
+ui.pxpos = function(p) {
return p + 'px';
};
/**
+ * Show a status message.
+ */
+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();
+ }
+ });
+ }
+};
+
+/**
* Default page load behavior.
*/
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) {
};
/**
- * Read an image url and convert it to a data url.
+ * Draw an image on a canvas and convert it to a data URL.
*/
-ui.readimageurl = function(url, onerror, onprogress, onload, width, height) {
- // Create a canvas to draw the image
+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');
- if (width)
- canvas.width = width;
- if (height)
- canvas.height = height;
+ 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, 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);
};