blob: b89153d283200f12a7e1a1006f25b558af0fa74c [file] [log] [blame]
/*
* 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);
}
};