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.js329
1 files changed, 289 insertions, 40 deletions
diff --git a/sca-cpp/trunk/modules/js/htdocs/ui.js b/sca-cpp/trunk/modules/js/htdocs/ui.js
index fb53598390..0c80b92bcf 100644
--- a/sca-cpp/trunk/modules/js/htdocs/ui.js
+++ b/sca-cpp/trunk/modules/js/htdocs/ui.js
@@ -31,6 +31,8 @@ ui.elementByID = function(node, id) {
return null;
for (var i in node.childNodes) {
var child = node.childNodes[i];
+ if (isNil(child))
+ continue;
if (child.id == id)
return child;
var gchild = ui.elementByID(child, id);
@@ -46,7 +48,16 @@ ui.elementByID = function(node, id) {
function $(id) {
if (id == document)
return document;
- return ui.elementByID($(document), id);
+ return memo(document, '$' + id, function() {
+ return ui.elementByID($(document), id);
+ });
+}
+
+/**
+ * Un-memoize elements previously found by id.
+ */
+ui.unmemo$ = function(prefix) {
+ return prefix? unmemo(document, '$' + prefix) : unmemo(document);
};
/**
@@ -114,10 +125,26 @@ ui.fragmentParams = function(url) {
};
/**
- * Convert a base64-encoded image to a data URL.
+ * Convert a base64-encoded PNG image to a data URL.
*/
-ui.b64img = function(b64) {
- return 'data:image/png;base64,' + 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) {
+ return 'data:image/jpeg;base64,' + b64.trim();
+};
+
+/**
+ * Convert a data URL to a base64-encoded image.
+ */
+ui.imgb64 = function(img) {
+ if (img.startsWith('data:'))
+ return img.split(',')[1]
+ return '';
};
/**
@@ -131,6 +158,16 @@ ui.declareCSS = function(s) {
};
/**
+ * Include a CSS stylesheet.
+ */
+ui.includeCSS = function(s) {
+ var e = ui.declareCSS(s);
+ var head = document.getElementsByTagName('head')[0];
+ head.appendChild(e);
+ return e;
+};
+
+/**
* Declare a script.
*/
ui.declareScript = function(s) {
@@ -151,15 +188,15 @@ ui.innerScripts = function(e) {
* Evaluate a script.
*/
ui.evalScript = function(s) {
- return eval('(function() {\n' + s + '\n})();');
+ return eval('(function evalscript() { try { \n' + s + '\n} catch(e) { debug(e.stack); throw e; }})();');
};
/**
* Include a script.
*/
ui.includeScript = function(s) {
- //log('include', s);
- return eval(s);
+ //debug('include', s);
+ return eval('try { \n' + s + '\n} catch(e) { debug(e.stack); throw e; }');
};
/**
@@ -171,13 +208,139 @@ ui.isMobile = function() {
if (ui.mobiledetected)
return ui.mobile;
var ua = navigator.userAgent;
- if (ua.match(/iPhone/i) || ua.match(/iPad/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))
ui.mobile = true;
ui.mobiledetected = true;
return ui.mobile;
};
/**
+ * Return true if the client is Webkit based.
+ */
+ui.isWebkit = function() {
+ return navigator.userAgent.match(/WebKit/i);
+};
+
+/**
+ * Return the Webkit version.
+ */
+ui.webkitVersion = function() {
+ return Number(navigator.userAgent.replace(/.*AppleWebKit\/(\d+\.\d+).*/, '$1'));
+};
+
+/**
+ * Return true if the client is Firefox.
+ */
+ui.isFirefox = function() {
+ return navigator.userAgent.match(/Firefox/i);
+};
+
+/**
+ * Return the Firefox version.
+ */
+ui.firefoxVersion = function() {
+ return Number(navigator.userAgent.replace(/.*Firefox\/(\d+\.\d+).*/, '$1'));
+};
+
+/**
+ * Return true if the client is Safari.
+ */
+ui.isSafari = function() {
+ return navigator.userAgent.match(/Safari/i);
+};
+
+/**
+ * Return true if the client is Chrome.
+ */
+ui.isChrome = function() {
+ return navigator.userAgent.match(/Chrome/i);
+};
+
+/**
+ * Return true if the client is Internet Explorer.
+ */
+ui.isMSIE = function() {
+ return navigator.userAgent.match(/MSIE/i);
+};
+
+/**
+ * Return the Internet Explorer version.
+ */
+ui.msieVersion = function() {
+ return Number(navigator.userAgent.replace(/.*MSIE (\d+\.\d+).*/, '$1'));
+};
+
+/**
+ * Run a UI rendering function asynchronously.
+ */
+ui.asyncFrame = null;
+ui.async = function(f) {
+ if (isNil(ui.asyncFrame))
+ // Use requestAnimationFrame when available, fallback to setTimeout
+ ui.asyncFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
+ function(f) {
+ return window.setTimeout(f, 16);
+ };
+ return ui.asyncFrame.call(window, f);
+};
+
+/**
+ * Delay the execution of a function.
+ */
+ui.delayed = {}
+ui.delay = function(f, t) {
+ var id = window.setTimeout(function() {
+ delete ui.delayed[id];
+ return f();
+ }, isNil(t)? 16 : 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 (isNil(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) || isNil(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);
+};
+
+/**
* Convert a CSS position to a numeric position.
*/
ui.numpos = function(p) {
@@ -192,46 +355,49 @@ ui.pixpos = function(p) {
};
/**
- * Default orientation change behavior.
+ * Default page load behavior.
*/
-ui.onorientationchange = function(e) {
+ui.filler = null;
+ui.onload = function() {
- // Scroll to the top and hide the address bar
+ // 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);
+ document.body.appendChild(ui.filler);
+ } else {
+ // Style scroll bars
+ var h = document.getElementsByTagName('html');
+ if (!isNil(h))
+ h[0].className = h[0].className? h[0].classname + ' flatscrollbars' : 'flatscrollbars';
+ }
+
+ // Scroll to hide the address bar
+ document.body.style.display = 'block';
window.scrollTo(0, 0);
- // Change fixed position elements to absolute then back to fixed
- // to make sure they're correctly layed out after the orientation
- // change
- map(function(e) {
- e.style.position = 'absolute';
- return e;
- }, ui.elementsByClassName(document, 'fixed'));
-
- setTimeout(function() {
- map(function(e) {
- e.style.position = 'fixed';
- return e;
- }, ui.elementsByClassName(document, 'fixed'));
- }, 0);
+ // Set unload handler
+ window.onunload = function() {
+ window.scrollTo(0, 0);
+ return true;
+ };
+
return true;
};
/**
- * Default page load behavior.
+ * Default orientation change behavior.
*/
-ui.onload = function() {
+ui.onorientationchange = function(e) {
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ // Adjust filler height
+ if (!isNil(ui.filler))
+ ui.filler.style.height = ui.pixpos(window.orientation == 0? screen.height : screen.width);
- // Initialize fixed position elements only after the page is loaded,
- // to workaround layout issues with fixed position on mobile devices
- setTimeout(function() {
- map(function(e) {
- e.style.position = 'fixed';
- return e;
- }, ui.elementsByClassName(document, 'fixed'));
- }, 0);
+ // Scroll to hide the address bar
+ window.scrollTo(0, 0);
return true;
};
@@ -239,7 +405,7 @@ ui.onload = function() {
* Navigate to a new document.
*/
ui.navigate = function(url, win) {
- //log('navigate', url, win);
+ //debug('navigate', url, win);
// Open a new window
if (win == '_blank') {
@@ -264,6 +430,27 @@ ui.navigate = function(url, win) {
if (win == '_view') {
if (!window.top.onnavigate)
return window.top.open(url, '_self');
+
+ // 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;
+ }
+
+ // Cleanup memoized element lookups
+ ui.unmemo$();
+
+ // Cancel any timers
+ for (d in ui.delayed)
+ ui.cancelDelay(d);
+
+ // Navigate
window.top.onnavigate(url);
return false;
}
@@ -369,7 +556,7 @@ ui.datatable = function(l) {
}
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.
@@ -405,5 +592,67 @@ ui.datalist = function(l) {
}
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) {
+ var reader = new FileReader();
+ reader.onerror = function(e) {
+ return onerror();
+ };
+ reader.onprogress = function(e) {
+ return onprogress(e.lengthComputable? Math.round((e.loaded / e.total) * 90) : 50);
+ };
+ reader.onload = function(r) {
+ return onload(r.target.result);
+ };
+ return reader.readAsDataURL(file);
+};
+
+/**
+ * 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;
+
+ // Create an image
+ var img = new Image();
+ img.onerror = function(e) {
+ 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'));
+ };
+
+ // Load the image
+ onprogress(90);
+ img.src = url;
+ return true;
+};
+
+/**
+ * Read an image file or url and convert it to a data url.
+ */
+ui.readimage = function(img, onerror, onprogress, onload, width, height) {
+ 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);
+};