| |
| /* |
| * PhoneGap is available under *either* the terms of the modified BSD license *or* the |
| * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. |
| * |
| * Copyright (c) 2005-2010, Nitobi Software Inc. |
| * Copyright (c) 2010, IBM Corporation |
| */ |
| |
| /** |
| * The order of events during page load and PhoneGap startup is as follows: |
| * |
| * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. |
| * window.onload Body onload event. |
| * onNativeReady Internal event that indicates the PhoneGap native side is ready. |
| * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). |
| * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created |
| * onPhoneGapInfoReady Internal event fired when device properties are available |
| * onDeviceReady User event fired to indicate that PhoneGap is ready |
| * onResume User event fired to indicate a start/resume lifecycle event |
| * |
| * The only PhoneGap events that user code should register for are: |
| * onDeviceReady |
| * onResume |
| * |
| * Listeners can be registered as: |
| * document.addEventListener("deviceready", myDeviceReadyListener, false); |
| * document.addEventListener("resume", myResumeListener, false); |
| */ |
| |
| function debugPrint(body) { |
| var list = document.getElementById("debuglist"); |
| var item = document.createElement("li"); |
| item.appendChild(document.createTextNode(body)); |
| list.appendChild(item); |
| } |
| /** |
| * This represents the PhoneGap API itself, and provides a global namespace for accessing |
| * information about the state of PhoneGap. |
| * @class |
| */ |
| PhoneGap = { |
| queue: { |
| ready: true, |
| commands: [], |
| timer: null |
| }, |
| _constructors: [] |
| }; |
| |
| /** |
| * Boolean flag indicating if the PhoneGap API is available and initialized. |
| */ // TODO: Remove this, it is unused here ... -jm |
| PhoneGap.available = function() { |
| return window.device.uuid != undefined; |
| } |
| |
| /** |
| * Custom pub-sub channel that can have functions subscribed to it |
| */ |
| PhoneGap.Channel = function(type) |
| { |
| this.type = type; |
| this.handlers = {}; |
| this.guid = 0; |
| this.fired = false; |
| this.enabled = true; |
| }; |
| |
| /** |
| * Subscribes the given function to the channel. Any time that |
| * Channel.fire is called so too will the function. |
| * Optionally specify an execution context for the function |
| * and a guid that can be used to stop subscribing to the channel. |
| * Returns the guid. |
| */ |
| PhoneGap.Channel.prototype.subscribe = function(f, c, g) { |
| // need a function to call |
| if (f == null) { return; } |
| |
| var func = f; |
| if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); } |
| |
| g = g || func.observer_guid || f.observer_guid || this.guid++; |
| func.observer_guid = g; |
| f.observer_guid = g; |
| this.handlers[g] = func; |
| return g; |
| }; |
| |
| /** |
| * Like subscribe but the function is only called once and then it |
| * auto-unsubscribes itself. |
| */ |
| PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { |
| var g = null; |
| var _this = this; |
| var m = function() { |
| f.apply(c || null, arguments); |
| _this.unsubscribe(g); |
| } |
| if (this.fired) { |
| if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); } |
| f.apply(this, this.fireArgs); |
| } else { |
| g = this.subscribe(m); |
| } |
| return g; |
| }; |
| |
| /** |
| * Unsubscribes the function with the given guid from the channel. |
| */ |
| PhoneGap.Channel.prototype.unsubscribe = function(g) { |
| if (g instanceof Function) { g = g.observer_guid; } |
| this.handlers[g] = null; |
| delete this.handlers[g]; |
| }; |
| |
| /** |
| * Calls all functions subscribed to this channel. |
| */ |
| PhoneGap.Channel.prototype.fire = function(e) { |
| if (this.enabled) { |
| var fail = false; |
| for (var item in this.handlers) { |
| var handler = this.handlers[item]; |
| if (handler instanceof Function) { |
| var rv = (handler.apply(this, arguments)==false); |
| fail = fail || rv; |
| } |
| } |
| this.fired = true; |
| this.fireArgs = arguments; |
| return !fail; |
| } |
| return true; |
| }; |
| |
| /** |
| * Calls the provided function only after all of the channels specified |
| * have been fired. |
| */ |
| PhoneGap.Channel.join = function(h, c) { |
| var i = c.length; |
| var f = function() { |
| if (!(--i)) h(); |
| } |
| for (var j=0; j<i; j++) { |
| (!c[j].fired?c[j].subscribeOnce(f):i--); |
| } |
| if (!i) h(); |
| }; |
| |
| /** |
| * Add an initialization function to a queue that ensures it will run and initialize |
| * application constructors only once PhoneGap has been initialized. |
| * @param {Function} func The function callback you want run once PhoneGap is initialized |
| */ |
| PhoneGap.addConstructor = function(func) { |
| PhoneGap.onPhoneGapInit.subscribeOnce(function() { |
| // try { |
| func(); |
| // } catch(e) { |
| // if (typeof(debug['log']) == 'function') { |
| // debug.log("Failed to run constructor: " + debug.processMessage(e)); |
| // } else { |
| // alert("Failed to run constructor: " + e.message); |
| // } |
| // } |
| }); |
| }; |
| |
| /** |
| * Plugins object. |
| */ |
| if (!window.plugins) { |
| window.plugins = {}; |
| } |
| |
| /** |
| * Adds new plugin object to window.plugins. |
| * The plugin is accessed using window.plugins.<name> |
| * |
| * @param name The plugin name |
| * @param obj The plugin object |
| */ |
| PhoneGap.addPlugin = function(name, obj) { |
| if (!window.plugins[name]) { |
| window.plugins[name] = obj; |
| } |
| else { |
| console.log("Plugin " + name + " already exists."); |
| } |
| }; |
| |
| /** |
| * onDOMContentLoaded channel is fired when the DOM content |
| * of the page has been parsed. |
| */ |
| PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); |
| |
| /** |
| * onNativeReady channel is fired when the PhoneGap native code |
| * has been initialized. |
| */ |
| PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); |
| |
| /** |
| * onPhoneGapInit channel is fired when the web page is fully loaded and |
| * PhoneGap native code has been initialized. |
| */ |
| PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); |
| |
| /** |
| * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. |
| */ |
| PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); |
| |
| /** |
| * onPhoneGapInfoReady channel is fired when the PhoneGap device properties |
| * has been set. |
| */ |
| PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); |
| |
| /** |
| * onResume channel is fired when the PhoneGap native code |
| * resumes. |
| */ |
| PhoneGap.onResume = new PhoneGap.Channel('onResume'); |
| |
| /** |
| * onPause channel is fired when the PhoneGap native code |
| * pauses. |
| */ |
| PhoneGap.onPause = new PhoneGap.Channel('onPause'); |
| |
| // _nativeReady is global variable that the native side can set |
| // to signify that the native code is ready. It is a global since |
| // it may be called before any PhoneGap JS is ready. |
| if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } |
| |
| /** |
| * onDeviceReady is fired only after all PhoneGap objects are created and |
| * the device properties are set. |
| */ |
| PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); |
| |
| /** |
| * Create all PhoneGap objects once page has fully loaded and native side is ready. |
| */ |
| PhoneGap.Channel.join(function() { |
| |
| // Run PhoneGap constructors |
| PhoneGap.onPhoneGapInit.fire(); |
| |
| // Fire event to notify that all objects are created |
| PhoneGap.onPhoneGapReady.fire(); |
| |
| }, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); |
| |
| /** |
| * Fire onDeviceReady event once all constructors have run and PhoneGap info has been |
| * received from native side. |
| */ |
| PhoneGap.Channel.join(function() { |
| PhoneGap.onDeviceReady.fire(); |
| |
| // Fire the onresume event, since first one happens before JavaScript is loaded |
| PhoneGap.onResume.fire(); |
| }, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]); |
| |
| // Listen for DOMContentLoaded and notify our channel subscribers |
| document.addEventListener('DOMContentLoaded', function() { |
| PhoneGap.onDOMContentLoaded.fire(); |
| }, false); |
| |
| // Intercept calls to document.addEventListener and watch for deviceready |
| PhoneGap.m_document_addEventListener = document.addEventListener; |
| |
| document.addEventListener = function(evt, handler, capture) { |
| var e = evt.toLowerCase(); |
| if (e == 'deviceready') { |
| PhoneGap.onDeviceReady.subscribeOnce(handler); |
| } else if (e == 'resume') { |
| PhoneGap.onResume.subscribe(handler); |
| // if subscribing listener after event has already fired, invoke the handler |
| if (PhoneGap.onResume.fired && handler instanceof Function) { |
| handler(); |
| } |
| } else if (e == 'pause') { |
| PhoneGap.onPause.subscribe(handler); |
| } else { |
| PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); |
| } |
| }; |
| |
| PhoneGap.m_element_addEventListener = Element.prototype.addEventListener; |
| |
| /** |
| * For BlackBerry, the touchstart event does not work so we need to do click |
| * events when touchstart events are attached. |
| */ |
| Element.prototype.addEventListener = function(evt, handler, capture) { |
| if (evt === 'touchstart') { |
| evt = 'click'; |
| } |
| PhoneGap.m_element_addEventListener.call(this, evt, handler, capture); |
| }; |
| |
| /** |
| * Does a deep clone of the object. |
| * |
| * @param obj |
| * @return |
| */ |
| PhoneGap.clone = function(obj) { |
| if(!obj) { |
| return obj; |
| } |
| |
| if(obj instanceof Array){ |
| var retVal = new Array(); |
| for(var i = 0; i < obj.length; ++i){ |
| retVal.push(PhoneGap.clone(obj[i])); |
| } |
| return retVal; |
| } |
| |
| if (obj instanceof Function) { |
| return obj; |
| } |
| |
| if(!(obj instanceof Object)){ |
| return obj; |
| } |
| |
| if(obj instanceof Date){ |
| return obj; |
| } |
| |
| retVal = new Object(); |
| for(i in obj){ |
| if(!(i in retVal) || retVal[i] != obj[i]) { |
| retVal[i] = PhoneGap.clone(obj[i]); |
| } |
| } |
| return retVal; |
| }; |
| |
| PhoneGap.close = function(context, func, params) { |
| if (typeof params === 'undefined') { |
| return function() { |
| return func.apply(context, arguments); |
| } |
| } else { |
| return function() { |
| return func.apply(context, params); |
| } |
| } |
| }; |
| |
| PhoneGap.callbackId = 0; |
| PhoneGap.callbacks = {}; |
| PhoneGap.callbackStatus = { |
| NO_RESULT: 0, |
| OK: 1, |
| CLASS_NOT_FOUND_EXCEPTION: 2, |
| ILLEGAL_ACCESS_EXCEPTION: 3, |
| INSTANTIATION_EXCEPTION: 4, |
| MALFORMED_URL_EXCEPTION: 5, |
| IO_EXCEPTION: 6, |
| INVALID_ACTION: 7, |
| JSON_EXCEPTION: 8, |
| ERROR: 9 |
| }; |
| |
| /** |
| * Called by native code when returning successful result from an action. |
| * |
| * @param callbackId |
| * @param args |
| */ |
| PhoneGap.callbackSuccess = function(callbackId, args) { |
| if (PhoneGap.callbacks[callbackId]) { |
| |
| // If result is to be sent to callback |
| if (args.status == PhoneGap.callbackStatus.OK) { |
| try { |
| if (PhoneGap.callbacks[callbackId].success) { |
| PhoneGap.callbacks[callbackId].success(args.message); |
| } |
| } |
| catch (e) { |
| console.log("Error in success callback: "+callbackId+" = "+e); |
| } |
| } |
| |
| // Clear callback if not expecting any more results |
| if (!args.keepCallback) { |
| delete PhoneGap.callbacks[callbackId]; |
| } |
| } |
| }; |
| |
| /** |
| * Called by native code when returning error result from an action. |
| * |
| * @param callbackId |
| * @param args |
| */ |
| PhoneGap.callbackError = function(callbackId, args) { |
| if (PhoneGap.callbacks[callbackId]) { |
| try { |
| if (PhoneGap.callbacks[callbackId].fail) { |
| PhoneGap.callbacks[callbackId].fail(args.message); |
| } |
| } |
| catch (e) { |
| console.log("Error in error callback: "+callbackId+" = "+e); |
| } |
| |
| // Clear callback if not expecting any more results |
| if (!args.keepCallback) { |
| delete PhoneGap.callbacks[callbackId]; |
| } |
| } |
| }; |
| |
| /** |
| * Create a UUID |
| * |
| * @return |
| */ |
| PhoneGap.createUUID = function() { |
| return PhoneGap.UUIDcreatePart(4) + '-' + |
| PhoneGap.UUIDcreatePart(2) + '-' + |
| PhoneGap.UUIDcreatePart(2) + '-' + |
| PhoneGap.UUIDcreatePart(2) + '-' + |
| PhoneGap.UUIDcreatePart(6); |
| }; |
| |
| PhoneGap.UUIDcreatePart = function(length) { |
| var uuidpart = ""; |
| for (var i=0; i<length; i++) { |
| var uuidchar = parseInt((Math.random() * 256)).toString(16); |
| if (uuidchar.length == 1) { |
| uuidchar = "0" + uuidchar; |
| } |
| uuidpart += uuidchar; |
| } |
| return uuidpart; |
| }; |
| /** |
| * Execute a PhoneGap command in a queued fashion, to ensure commands do not |
| * execute with any race conditions, and only run when PhoneGap is ready to |
| * receive them. |
| * |
| */ |
| PhoneGap.exec = function() { |
| |
| PhoneGap.queue.commands.push(arguments); |
| if (PhoneGap.queue.timer == null) |
| PhoneGap.queue.timer = setInterval(PhoneGap.run_command, 10); |
| }; |
| |
| /** |
| * Internal function used to dispatch the request to PhoneGap. It processes the |
| * command queue and executes the next command on the list. Simple parameters are passed |
| * as arguments on the url. JavaScript objects converted into a JSON string and passed as a |
| * query string argument of the url. |
| * Arguments may be in one of two formats: |
| * FORMAT ONE (preferable) |
| * The native side will call PhoneGap.callbackSuccess or PhoneGap.callbackError, |
| * depending upon the result of the action. |
| * |
| * @param {Function} success The success callback |
| * @param {Function} fail The fail callback |
| * @param {String} service The name of the service to use |
| * @param {String} action The name of the action to use |
| * @param {String[]} [args] Zero or more arguments to pass to the method |
| * |
| * FORMAT TWO |
| * @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method" |
| * @param {String[]} [args] Zero or more arguments to pass to the method |
| * object parameters are passed as an array object [object1, object2] each object will be passed as JSON strings |
| * @private |
| */ |
| PhoneGap.run_command = function() { |
| if (!PhoneGap.available() || !PhoneGap.queue.ready) |
| return; |
| PhoneGap.queue.ready = false; |
| |
| var args = PhoneGap.queue.commands.shift(); |
| if (PhoneGap.queue.commands.length == 0) { |
| clearInterval(PhoneGap.queue.timer); |
| PhoneGap.queue.timer = null; |
| } |
| |
| var service; |
| var callbackId = null; |
| var start=0; |
| try { |
| if (args[0] == null || typeof args[0] === "function") { |
| var success = args[0]; |
| var fail = args[1]; |
| service = args[2] + "." + args[3]; |
| args = args[4]; //array of arguments to |
| if (success || fail) { |
| callbackId = service + PhoneGap.callbackId++; |
| PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; |
| } |
| } else { |
| service = args[0]; |
| start = 1; |
| } |
| |
| var uri = []; |
| var query = []; |
| for (var i = start; i < args.length; i++) { |
| var arg = args[i]; |
| if (arg == undefined || arg == null) |
| continue; |
| if (typeof(arg) == 'object') |
| for(i in arg) { |
| query.push(i + '=' + encodeURIComponent(arg[i])); |
| } |
| else |
| uri.push(encodeURIComponent(arg)); |
| } |
| var next = callbackId != null ? ("/" + callbackId + "/") : "/"; |
| var url = "gap://" + service + next + uri.join("/"); |
| |
| if (query.length > 0) { |
| url += "?" + encodeURIComponent(query.join("&")); |
| } |
| document.location = url; |
| |
| } catch (e) { |
| console.log("PhoneGapExec Error: "+e); |
| } |
| |
| |
| }; |
| |