/* * 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-2011, IBM Corporation */ if (typeof PhoneGap === "undefined") { /** * 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 * onPause User event fired to indicate a pause lifecycle event * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). * * 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); * document.addEventListener("pause", myPauseListener, false); */ if (typeof(DeviceInfo) !== 'object') { var DeviceInfo = {}; } /** * This represents the PhoneGap API itself, and provides a global namespace for accessing * information about the state of PhoneGap. * @class */ var PhoneGap = { queue: { ready: true, commands: [], timer: null } }; /** * List of resource files loaded by PhoneGap. * This is used to ensure JS and other files are loaded only once. */ PhoneGap.resources = {base: true}; /** * Determine if resource has been loaded by PhoneGap * * @param name * @return */ PhoneGap.hasResource = function(name) { return PhoneGap.resources[name]; }; /** * Add a resource to list of loaded resources by PhoneGap * * @param name */ PhoneGap.addResource = function(name) { PhoneGap.resources[name] = true; }; /** * Custom pub-sub channel that can have functions subscribed to it * @constructor */ 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" && typeof f === "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" && typeof f === "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 (typeof g === "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; var item, handler, rv; for (item in this.handlers) { if (this.handlers.hasOwnProperty(item)) { handler = this.handlers[item]; if (typeof handler === "function") { 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(); } }; var len = i; var j; for (j=0; j * * @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("Error: 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'); /** * onPhoneGapConnectionReady channel is fired when the PhoneGap connection properties * has been set. */ PhoneGap.onPhoneGapConnectionReady = new PhoneGap.Channel('onPhoneGapConnectionReady'); /** * 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'); /** * onDestroy channel is fired when the PhoneGap native code * is destroyed. It is used internally. * Window.onunload should be used by the user. */ PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); PhoneGap.onDestroy.subscribeOnce(function() { PhoneGap.shuttingDown = true; }); PhoneGap.shuttingDown = false; // _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'); // Array of channels that must fire before "deviceready" is fired PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady, PhoneGap.onPhoneGapConnectionReady]; // Hashtable of user defined channels that must also fire before "deviceready" is fired PhoneGap.deviceReadyChannelsMap = {}; /** * Indicate that a feature needs to be initialized before it is ready to be used. * This holds up PhoneGap's "deviceready" event until the feature has been initialized * and PhoneGap.initComplete(feature) is called. * * @param feature {String} The unique feature name */ PhoneGap.waitForInitialization = function(feature) { if (feature) { var channel = new PhoneGap.Channel(feature); PhoneGap.deviceReadyChannelsMap[feature] = channel; PhoneGap.deviceReadyChannelsArray.push(channel); } }; /** * Indicate that initialization code has completed and the feature is ready to be used. * * @param feature {String} The unique feature name */ PhoneGap.initializationComplete = function(feature) { var channel = PhoneGap.deviceReadyChannelsMap[feature]; if (channel) { channel.fire(); } }; /** * Create all PhoneGap objects once page has fully loaded and native side is ready. */ PhoneGap.Channel.join(function() { // Start listening for XHR callbacks setTimeout(function() { if (PhoneGap.UsePolling) { PhoneGap.JSCallbackPolling(); } else { var polling = prompt("usePolling", "gap_callbackServer:"); PhoneGap.UsePolling = polling; if (polling == "true") { PhoneGap.UsePolling = true; PhoneGap.JSCallbackPolling(); } else { PhoneGap.UsePolling = false; PhoneGap.JSCallback(); } } }, 1); // Run PhoneGap constructors PhoneGap.onPhoneGapInit.fire(); // Fire event to notify that all objects are created PhoneGap.onPhoneGapReady.fire(); // Fire onDeviceReady event once all constructors have run and PhoneGap info has been // received from native side, and any user defined initialization channels. PhoneGap.Channel.join(function() { PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded PhoneGap.onResume.fire(); }, PhoneGap.deviceReadyChannelsArray); }, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); // 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 (PhoneGap.onDeviceReady.fired) { PhoneGap.onResume.fire(); } } else if (e === 'pause') { PhoneGap.onPause.subscribe(handler); } else { // If subscribing to Android backbutton if (e === 'backbutton') { PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); } PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); } }; // Intercept calls to document.removeEventListener and watch for events that // are generated by PhoneGap native code PhoneGap.m_document_removeEventListener = document.removeEventListener; document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); // If unsubscribing to Android backbutton if (e === 'backbutton') { PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); } PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); }; /** * Method to fire event from native code */ PhoneGap.fireEvent = function(type) { var e = document.createEvent('Events'); e.initEvent(type); document.dispatchEvent(e); }; /** * If JSON not included, use our own stringify. (Android 1.6) * The restriction on ours is that it must be an array of simple types. * * @param args * @return {String} */ PhoneGap.stringify = function(args) { if (typeof JSON === "undefined") { var s = "["; var i, type, start, name, nameType, a; for (i = 0; i < args.length; i++) { if (args[i] !== null) { if (i > 0) { s = s + ","; } type = typeof args[i]; if ((type === "number") || (type === "boolean")) { s = s + args[i]; } else if (args[i] instanceof Array) { s = s + "[" + args[i] + "]"; } else if (args[i] instanceof Object) { start = true; s = s + '{'; for (name in args[i]) { if (args[i][name] !== null) { if (!start) { s = s + ','; } s = s + '"' + name + '":'; nameType = typeof args[i][name]; if ((nameType === "number") || (nameType === "boolean")) { s = s + args[i][name]; } else if ((typeof args[i][name]) === 'function') { // don't copy the functions s = s + '""'; } else if (args[i][name] instanceof Object) { s = s + PhoneGap.stringify(args[i][name]); } else { s = s + '"' + args[i][name] + '"'; } start = false; } } s = s + '}'; } else { a = args[i].replace(/\\/g, '\\\\'); a = a.replace(/"/g, '\\"'); s = s + '"' + a + '"'; } } } s = s + "]"; return s; } else { return JSON.stringify(args); } }; /** * Does a deep clone of the object. * * @param obj * @return {Object} */ PhoneGap.clone = function(obj) { var i, retVal; if(!obj) { return obj; } if(obj instanceof Array){ retVal = []; for(i = 0; i < obj.length; ++i){ retVal.push(PhoneGap.clone(obj[i])); } return retVal; } if (typeof obj === "function") { return obj; } if(!(obj instanceof Object)){ return obj; } if (obj instanceof Date) { return obj; } retVal = {}; for(i in obj){ if(!(i in retVal) || retVal[i] !== obj[i]) { retVal[i] = PhoneGap.clone(obj[i]); } } return retVal; }; 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 }; /** * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. * The native side can return: * Synchronous: PluginResult object as a JSON string * Asynchrounous: Empty string "" * If async, the native side will 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 Action to be run in PhoneGap * @param {Array.} [args] Zero or more arguments to pass to the method */ PhoneGap.exec = function(success, fail, service, action, args) { try { var callbackId = service + PhoneGap.callbackId++; if (success || fail) { PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; } var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true])); // If a result was returned if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status === PhoneGap.callbackStatus.OK) { // If there is a success callback, then call it now with // returned value if (success) { try { success(v.message); } catch (e) { console.log("Error in success callback: " + callbackId + " = " + e); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return v.message; } // If no result else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } // If error, then display error else { console.log("Error: Status="+v.status+" Message="+v.message); // If there is a fail callback, then call it now with returned value if (fail) { try { fail(v.message); } catch (e1) { console.log("Error in error callback: "+callbackId+" = "+e1); } // Clear callback if not expecting any more results if (!v.keepCallback) { delete PhoneGap.callbacks[callbackId]; } } return null; } } } catch (e2) { console.log("Error: "+e2); } }; /** * 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]; } } }; /** * Internal function used to dispatch the request to PhoneGap. It processes the * command queue and executes the next command on the list. If one of the * arguments is a JavaScript object, it will be passed on the QueryString of the * url, which will be turned into a dictionary on the other end. * @private */ // TODO: Is this used? 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 uri = []; var dict = null; var i; for (i = 1; i < args.length; i++) { var arg = args[i]; if (arg === undefined || arg === null) { arg = ''; } if (typeof(arg) === 'object') { dict = arg; } else { uri.push(encodeURIComponent(arg)); } } var url = "gap://" + args[0] + "/" + uri.join("/"); if (dict !== null) { var name; var query_args = []; for (name in dict) { if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) { query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); } } if (query_args.length > 0) { url += "?" + query_args.join("&"); } } document.location = url; }; PhoneGap.JSCallbackPort = null; PhoneGap.JSCallbackToken = null; /** * This is only for Android. * * Internal function that uses XHR to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from * Java to JavaScript. */ PhoneGap.JSCallback = function() { // Exit if shutting down app if (PhoneGap.shuttingDown) { return; } // If polling flag was changed, start using polling from now on if (PhoneGap.UsePolling) { PhoneGap.JSCallbackPolling(); return; } var xmlhttp = new XMLHttpRequest(); // Callback function when XMLHttpRequest is ready xmlhttp.onreadystatechange=function(){ if(xmlhttp.readyState === 4){ // Exit if shutting down app if (PhoneGap.shuttingDown) { return; } // If callback has JavaScript statement to execute if (xmlhttp.status === 200) { // Need to url decode the response var msg = decodeURIComponent(xmlhttp.responseText); setTimeout(function() { try { var t = eval(msg); } catch (e) { // If we're getting an error here, seeing the message will help in debugging console.log("JSCallback: Message from Server: " + msg); console.log("JSCallback Error: "+e); } }, 1); setTimeout(PhoneGap.JSCallback, 1); } // If callback ping (used to keep XHR request from timing out) else if (xmlhttp.status === 404) { setTimeout(PhoneGap.JSCallback, 10); } // If security error else if (xmlhttp.status === 403) { console.log("JSCallback Error: Invalid token. Stopping callbacks."); } // If server is stopping else if (xmlhttp.status === 503) { console.log("JSCallback Error: Service unavailable. Stopping callbacks."); } // If request wasn't GET else if (xmlhttp.status === 400) { console.log("JSCallback Error: Bad request. Stopping callbacks."); } // If error, revert to polling else { console.log("JSCallback Error: Request failed."); PhoneGap.UsePolling = true; PhoneGap.JSCallbackPolling(); } } }; if (PhoneGap.JSCallbackPort === null) { PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); } if (PhoneGap.JSCallbackToken === null) { PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); } xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); xmlhttp.send(); }; /** * The polling period to use with JSCallbackPolling. * This can be changed by the application. The default is 50ms. */ PhoneGap.JSCallbackPollingPeriod = 50; /** * Flag that can be set by the user to force polling to be used or force XHR to be used. */ PhoneGap.UsePolling = false; // T=use polling, F=use XHR /** * This is only for Android. * * Internal function that uses polling to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from * Java to JavaScript. */ PhoneGap.JSCallbackPolling = function() { // Exit if shutting down app if (PhoneGap.shuttingDown) { return; } // If polling flag was changed, stop using polling from now on if (!PhoneGap.UsePolling) { PhoneGap.JSCallback(); return; } var msg = prompt("", "gap_poll:"); if (msg) { setTimeout(function() { try { var t = eval(""+msg); } catch (e) { console.log("JSCallbackPolling: Message from Server: " + msg); console.log("JSCallbackPolling Error: "+e); } }, 1); setTimeout(PhoneGap.JSCallbackPolling, 1); } else { setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); } }; /** * Create a UUID * * @return {String} */ 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 = ""; var i, uuidchar; for (i=0; i frequency + 10 sec PhoneGap.exec( function(timeout) { if (timeout < (frequency + 10000)) { PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); } }, function(e) { }, "Accelerometer", "getTimeout", []); // Start watch timer var id = PhoneGap.createUUID(); navigator.accelerometer.timers[id] = setInterval(function() { PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); }, (frequency ? frequency : 1)); return id; }; /** * Clears the specified accelerometer watch. * * @param {String} id The id of the watch returned from #watchAcceleration. */ Accelerometer.prototype.clearWatch = function(id) { // Stop javascript timer & remove from timer list if (id && navigator.accelerometer.timers[id] !== undefined) { clearInterval(navigator.accelerometer.timers[id]); delete navigator.accelerometer.timers[id]; } }; PhoneGap.addConstructor(function() { if (typeof navigator.accelerometer === "undefined") { navigator.accelerometer = new Accelerometer(); } }); } /* * 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-2011, IBM Corporation */ if (!PhoneGap.hasResource("app")) { PhoneGap.addResource("app"); (function() { /** * Constructor * @constructor */ var App = function() {}; /** * Clear the resource cache. */ App.prototype.clearCache = function() { PhoneGap.exec(null, null, "App", "clearCache", []); }; /** * Load the url into the webview. * * @param url The URL to load * @param props Properties that can be passed in to the activity: * wait: int => wait msec before loading URL * loadingDialog: "Title,Message" => display a native loading dialog * hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs. * loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser. * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error * errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html"); * keepRunning: boolean => enable app to keep running in background * * Example: * App app = new App(); * app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); */ App.prototype.loadUrl = function(url, props) { PhoneGap.exec(null, null, "App", "loadUrl", [url, props]); }; /** * Cancel loadUrl that is waiting to be loaded. */ App.prototype.cancelLoadUrl = function() { PhoneGap.exec(null, null, "App", "cancelLoadUrl", []); }; /** * Clear web history in this web view. * Instead of BACK button loading the previous web page, it will exit the app. */ App.prototype.clearHistory = function() { PhoneGap.exec(null, null, "App", "clearHistory", []); }; /** * Override the default behavior of the Android back button. * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. * * Note: The user should not have to call this method. Instead, when the user * registers for the "backbutton" event, this is automatically done. * * @param override T=override, F=cancel override */ App.prototype.overrideBackbutton = function(override) { PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]); }; /** * Exit and terminate the application. */ App.prototype.exitApp = function() { return PhoneGap.exec(null, null, "App", "exitApp", []); }; PhoneGap.addConstructor(function() { navigator.app = new App(); }); }()); } /* * 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-2011, IBM Corporation */ if (!PhoneGap.hasResource("camera")) { PhoneGap.addResource("camera"); /** * This class provides access to the device camera. * * @constructor */ var Camera = function() { this.successCallback = null; this.errorCallback = null; this.options = null; }; /** * Format of image that returned from getPicture. * * Example: navigator.camera.getPicture(success, fail, * { quality: 80, * destinationType: Camera.DestinationType.DATA_URL, * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) */ Camera.DestinationType = { DATA_URL: 0, // Return base64 encoded string FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) }; Camera.prototype.DestinationType = Camera.DestinationType; /** * Encoding of image returned from getPicture. * * Example: navigator.camera.getPicture(success, fail, * { quality: 80, * destinationType: Camera.DestinationType.DATA_URL, * sourceType: Camera.PictureSourceType.CAMERA, * encodingType: Camera.EncodingType.PNG}) */ Camera.EncodingType = { JPEG: 0, // Return JPEG encoded image PNG: 1 // Return PNG encoded image }; Camera.prototype.EncodingType = Camera.EncodingType; /** * Source to getPicture from. * * Example: navigator.camera.getPicture(success, fail, * { quality: 80, * destinationType: Camera.DestinationType.DATA_URL, * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) */ Camera.PictureSourceType = { PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) CAMERA : 1, // Take picture from camera SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) }; Camera.prototype.PictureSourceType = Camera.PictureSourceType; /** * Gets a picture from source defined by "options.sourceType", and returns the * image as defined by the "options.destinationType" option. * The defaults are sourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback * @param {Object} options */ Camera.prototype.getPicture = function(successCallback, errorCallback, options) { // successCallback required if (typeof successCallback !== "function") { console.log("Camera Error: successCallback is not a function"); return; } // errorCallback optional if (errorCallback && (typeof errorCallback !== "function")) { console.log("Camera Error: errorCallback is not a function"); return; } this.options = options; var quality = 80; if (options.quality) { quality = this.options.quality; } var maxResolution = 0; if (options.maxResolution) { maxResolution = this.options.maxResolution; } var destinationType = Camera.DestinationType.DATA_URL; if (this.options.destinationType) { destinationType = this.options.destinationType; } var sourceType = Camera.PictureSourceType.CAMERA; if (typeof this.options.sourceType === "number") { sourceType = this.options.sourceType; } var encodingType = Camera.EncodingType.JPEG; if (typeof options.encodingType == "number") { encodingType = this.options.encodingType; } var targetWidth = -1; if (typeof options.targetWidth == "number") { targetWidth = options.targetWidth; } else if (typeof options.targetWidth == "string") { var width = new Number(options.targetWidth); if (isNaN(width) === false) { targetWidth = width.valueOf(); } } var targetHeight = -1; if (typeof options.targetHeight == "number") { targetHeight = options.targetHeight; } else if (typeof options.targetHeight == "string") { var height = new Number(options.targetHeight); if (isNaN(height) === false) { targetHeight = height.valueOf(); } } PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType, targetWidth, targetHeight, encodingType]); }; PhoneGap.addConstructor(function() { if (typeof navigator.camera === "undefined") { navigator.camera = new Camera(); } }); } /* * 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-2011, IBM Corporation */ if (!PhoneGap.hasResource("capture")) { PhoneGap.addResource("capture"); /** * Represents a single file. * * name {DOMString} name of the file, without path information * fullPath {DOMString} the full path of the file, including the name * type {DOMString} mime type * lastModifiedDate {Date} last modified date * size {Number} size of the file in bytes */ var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ this.name = name || null; this.fullPath = fullPath || null; this.type = type || null; this.lastModifiedDate = lastModifiedDate || null; this.size = size || 0; }; /** * Launch device camera application for recording video(s). * * @param {Function} successCB * @param {Function} errorCB */ MediaFile.prototype.getFormatData = function(successCallback, errorCallback){ PhoneGap.exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); }; /** * MediaFileData encapsulates format information of a media file. * * @param {DOMString} codecs * @param {long} bitrate * @param {long} height * @param {long} width * @param {float} duration */ var MediaFileData = function(codecs, bitrate, height, width, duration){ this.codecs = codecs || null; this.bitrate = bitrate || 0; this.height = height || 0; this.width = width || 0; this.duration = duration || 0; }; /** * The CaptureError interface encapsulates all errors in the Capture API. */ var CaptureError = function(){ this.code = null; }; // Capture error codes CaptureError.CAPTURE_INTERNAL_ERR = 0; CaptureError.CAPTURE_APPLICATION_BUSY = 1; CaptureError.CAPTURE_INVALID_ARGUMENT = 2; CaptureError.CAPTURE_NO_MEDIA_FILES = 3; CaptureError.CAPTURE_NOT_SUPPORTED = 20; /** * The Capture interface exposes an interface to the camera and microphone of the hosting device. */ var Capture = function(){ this.supportedAudioModes = []; this.supportedImageModes = []; this.supportedVideoModes = []; }; /** * Launch audio recorder application for recording audio clip(s). * * @param {Function} successCB * @param {Function} errorCB * @param {CaptureAudioOptions} options */ Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); }; /** * Launch camera application for taking image(s). * * @param {Function} successCB * @param {Function} errorCB * @param {CaptureImageOptions} options */ Capture.prototype.captureImage = function(successCallback, errorCallback, options){ PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); }; /** * Launch camera application for taking image(s). * * @param {Function} successCB * @param {Function} errorCB * @param {CaptureImageOptions} options */ Capture.prototype._castMediaFile = function(pluginResult){ var mediaFiles = []; var i; for (i = 0; i < pluginResult.message.length; i++) { var mediaFile = new MediaFile(); mediaFile.name = pluginResult.message[i].name; mediaFile.fullPath = pluginResult.message[i].fullPath; mediaFile.type = pluginResult.message[i].type; mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate; mediaFile.size = pluginResult.message[i].size; mediaFiles.push(mediaFile); } pluginResult.message = mediaFiles; return pluginResult; }; /** * Launch device camera application for recording video(s). * * @param {Function} successCB * @param {Function} errorCB * @param {CaptureVideoOptions} options */ Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ PhoneGap.exec(successCallback, errorCallback, "Capture", "captureVideo", [options]); }; /** * Encapsulates a set of parameters that the capture device supports. */ var ConfigurationData = function(){ // The ASCII-encoded string in lower case representing the media type. this.type = null; // The height attribute represents height of the image or video in pixels. // In the case of a sound clip this attribute has value 0. this.height = 0; // The width attribute represents width of the image or video in pixels. // In the case of a sound clip this attribute has value 0 this.width = 0; }; /** * Encapsulates all image capture operation configuration options. */ var CaptureImageOptions = function(){ // Upper limit of images user can take. Value must be equal or greater than 1. this.limit = 1; // The selected image mode. Must match with one of the elements in supportedImageModes array. this.mode = null; }; /** * Encapsulates all video capture operation configuration options. */ var CaptureVideoOptions = function(){ // Upper limit of videos user can record. Value must be equal or greater than 1. this.limit = 1; // Maximum duration of a single video clip in seconds. this.duration = 0; // The selected video mode. Must match with one of the elements in supportedVideoModes array. this.mode = null; }; /** * Encapsulates all audio capture operation configuration options. */ var CaptureAudioOptions = function(){ // Upper limit of sound clips user can record. Value must be equal or greater than 1. this.limit = 1; // Maximum duration of a single sound clip in seconds. this.duration = 0; // The selected audio mode. Must match with one of the elements in supportedAudioModes array. this.mode = null; }; PhoneGap.addConstructor(function(){ if (typeof navigator.device === "undefined") { navigator.device = window.device = new Device(); } if (typeof navigator.device.capture === "undefined") { navigator.device.capture = window.device.capture = new Capture(); } }); } /* * 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-2011, IBM Corporation */ if (!PhoneGap.hasResource("compass")) { PhoneGap.addResource("compass"); /** * This class provides access to device Compass data. * @constructor */ var Compass = function() { /** * The last known Compass position. */ this.lastHeading = null; /** * List of compass watch timers */ this.timers = {}; }; Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current heading. * * @param {Function} successCallback The function to call when the heading data is available * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) */ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { // successCallback required if (typeof successCallback !== "function") { console.log("Compass Error: successCallback is not a function"); return; } // errorCallback optional if (errorCallback && (typeof errorCallback !== "function")) { console.log("Compass Error: errorCallback is not a function"); return; } // Get heading PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); }; /** * Asynchronously aquires the heading repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the heading data is available * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) * @return String The watch id that must be passed to #clearWatch to stop watching. */ Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { // Default interval (100 msec) var frequency = (options !== undefined) ? options.frequency : 100; // successCallback required if (typeof successCallback !== "function") { console.log("Compass Error: successCallback is not a function"); return; } // errorCallback optional if (errorCallback && (typeof errorCallback !== "function")) { console.log("Compass Error: errorCallback is not a function"); return; } // Make sure compass timeout > frequency + 10 sec PhoneGap.exec( function(timeout) { if (timeout < (frequency + 10000)) { PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); } }, function(e) { }, "Compass", "getTimeout", []); // Start watch timer to get headings var id = PhoneGap.createUUID(); navigator.compass.timers[id] = setInterval( function() { PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); }, (frequency ? frequency : 1)); return id; }; /** * Clears the specified heading watch. * * @param {String} id The ID of the watch returned from #watchHeading. */ Compass.prototype.clearWatch = function(id) { // Stop javascript timer & remove from timer list if (id && navigator.compass.timers[id]) { clearInterval(navigator.compass.timers[id]); delete navigator.compass.timers[id]; } }; PhoneGap.addConstructor(function() { if (typeof navigator.compass === "undefined") { navigator.compass = new Compass(); } }); } /* * 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-2011, IBM Corporation */ if (!PhoneGap.hasResource("contact")) { PhoneGap.addResource("contact"); /** * Contains information about a single contact. * @constructor * @param {DOMString} id unique identifier * @param {DOMString} displayName * @param {ContactName} name * @param {DOMString} nickname * @param {Array.} phoneNumbers array of phone numbers * @param {Array.} emails array of email addresses * @param {Array.} addresses array of addresses * @param {Array.} ims instant messaging user ids * @param {Array.} organizations * @param {DOMString} birthday contact's birthday * @param {DOMString} note user notes about contact * @param {Array.} photos * @param {Array.} categories * @param {Array.} urls contact's web sites */ var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, ims, organizations, birthday, note, photos, categories, urls) { this.id = id || null; this.rawId = null; this.displayName = displayName || null; this.name = name || null; // ContactName this.nickname = nickname || null; this.phoneNumbers = phoneNumbers || null; // ContactField[] this.emails = emails || null; // ContactField[] this.addresses = addresses || null; // ContactAddress[] this.ims = ims || null; // ContactField[] this.organizations = organizations || null; // ContactOrganization[] this.birthday = birthday || null; this.note = note || null; this.photos = photos || null; // ContactField[] this.categories = categories || null; // ContactField[] this.urls = urls || null; // ContactField[] }; /** * ContactError. * An error code assigned by an implementation when an error has occurreds * @constructor */ var ContactError = function() { this.code=null; }; /** * Error codes */ ContactError.UNKNOWN_ERROR = 0; ContactError.INVALID_ARGUMENT_ERROR = 1; ContactError.TIMEOUT_ERROR = 2; ContactError.PENDING_OPERATION_ERROR = 3; ContactError.IO_ERROR = 4; ContactError.NOT_SUPPORTED_ERROR = 5; ContactError.PERMISSION_DENIED_ERROR = 20; /** * Removes contact from device storage. * @param successCB success callback * @param errorCB error callback */ Contact.prototype.remove = function(successCB, errorCB) { if (this.id === null) { var errorObj = new ContactError(); errorObj.code = ContactError.UNKNOWN_ERROR; errorCB(errorObj); } else { PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); } }; /** * Creates a deep copy of this Contact. * With the contact ID set to null. * @return copy of this Contact */ Contact.prototype.clone = function() { var clonedContact = PhoneGap.clone(this); var i; clonedContact.id = null; clonedContact.rawId = null; // Loop through and clear out any id's in phones, emails, etc. if (clonedContact.phoneNumbers) { for (i = 0; i < clonedContact.phoneNumbers.length; i++) { clonedContact.phoneNumbers[i].id = null; } } if (clonedContact.emails) { for (i = 0; i < clonedContact.emails.length; i++) { clonedContact.emails[i].id = null; } } if (clonedContact.addresses) { for (i = 0; i < clonedContact.addresses.length; i++) { clonedContact.addresses[i].id = null; } } if (clonedContact.ims) { for (i = 0; i < clonedContact.ims.length; i++) { clonedContact.ims[i].id = null; } } if (clonedContact.organizations) { for (i = 0; i < clonedContact.organizations.length; i++) { clonedContact.organizations[i].id = null; } } if (clonedContact.tags) { for (i = 0; i < clonedContact.tags.length; i++) { clonedContact.tags[i].id = null; } } if (clonedContact.photos) { for (i = 0; i < clonedContact.photos.length; i++) { clonedContact.photos[i].id = null; } } if (clonedContact.urls) { for (i = 0; i < clonedContact.urls.length; i++) { clonedContact.urls[i].id = null; } } return clonedContact; }; /** * Persists contact to device storage. * @param successCB success callback * @param errorCB error callback */ Contact.prototype.save = function(successCB, errorCB) { PhoneGap.exec(successCB, errorCB, "Contacts", "save", [this]); }; /** * Contact name. * @constructor * @param formatted * @param familyName * @param givenName * @param middle * @param prefix * @param suffix */ var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { this.formatted = formatted || null; this.familyName = familyName || null; this.givenName = givenName || null; this.middleName = middle || null; this.honorificPrefix = prefix || null; this.honorificSuffix = suffix || null; }; /** * Generic contact field. * @constructor * @param {DOMString} id unique identifier, should only be set by native code * @param type * @param value * @param pref */ var ContactField = function(type, value, pref) { this.id = null; this.type = type || null; this.value = value || null; this.pref = pref || null; }; /** * Contact address. * @constructor * @param {DOMString} id unique identifier, should only be set by native code * @param formatted * @param streetAddress * @param locality * @param region * @param postalCode * @param country */ var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { this.id = null; this.pref = pref || null; this.type = type || null; this.formatted = formatted || null; this.streetAddress = streetAddress || null; this.locality = locality || null; this.region = region || null; this.postalCode = postalCode || null; this.country = country || null; }; /** * Contact organization. * @constructor * @param {DOMString} id unique identifier, should only be set by native code * @param name * @param dept * @param title * @param startDate * @param endDate * @param location * @param desc */ var ContactOrganization = function(pref, type, name, dept, title) { this.id = null; this.pref = pref || null; this.type = type || null; this.name = name || null; this.department = dept || null; this.title = title || null; }; /** * Represents a group of Contacts. * @constructor */ var Contacts = function() { this.inProgress = false; this.records = []; }; /** * Returns an array of Contacts matching the search criteria. * @param fields that should be searched * @param successCB success callback * @param errorCB error callback * @param {ContactFindOptions} options that can be applied to contact searching * @return array of Contacts matching search criteria */ Contacts.prototype.find = function(fields, successCB, errorCB, options) { if (successCB === null) { throw new TypeError("You must specify a success callback for the find command."); } if (fields === null || fields === "undefined" || fields.length === "undefined" || fields.length <= 0) { if (typeof errorCB === "function") { errorCB({"code": ContactError.INVALID_ARGUMENT_ERROR}); } } else { PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); } }; /** * This function creates a new contact, but it does not persist the contact * to device storage. To persist the contact to device storage, invoke * contact.save(). * @param properties an object who's properties will be examined to create a new Contact * @returns new Contact object */ Contacts.prototype.create = function(properties) { var i; var contact = new Contact(); for (i in properties) { if (contact[i] !== 'undefined') { contact[i] = properties[i]; } } return contact; }; /** * This function returns and array of contacts. It is required as we need to convert raw * JSON objects into concrete Contact objects. Currently this method is called after * navigator.contacts.find but before the find methods success call back. * * @param jsonArray an array of JSON Objects that need to be converted to Contact objects. * @returns an array of Contact objects */ Contacts.prototype.cast = function(pluginResult) { var contacts = []; var i; for (i=0; i][;base64], * * @param file {File} File object containing file properties */ FileReader.prototype.readAsDataURL = function(file) { this.fileName = ""; if (typeof file.fullPath === "undefined") { this.fileName = file; } else { this.fileName = file.fullPath; } // LOADING state this.readyState = FileReader.LOADING; // If loadstart callback if (typeof this.onloadstart === "function") { this.onloadstart({"type":"loadstart", "target":this}); } var me = this; // Read file navigator.fileMgr.readAsDataURL(this.fileName, // Success callback function(r) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileReader.DONE) { return; } // Save result me.result = r; // If onload callback if (typeof me.onload === "function") { me.onload({"type":"load", "target":me}); } // DONE state me.readyState = FileReader.DONE; // If onloadend callback if (typeof me.onloadend === "function") { me.onloadend({"type":"loadend", "target":me}); } }, // Error callback function(e) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileReader.DONE) { return; } // Save error me.error = e; // If onerror callback if (typeof me.onerror === "function") { me.onerror({"type":"error", "target":me}); } // DONE state me.readyState = FileReader.DONE; // If onloadend callback if (typeof me.onloadend === "function") { me.onloadend({"type":"loadend", "target":me}); } } ); }; /** * Read file and return data as a binary data. * * @param file {File} File object containing file properties */ FileReader.prototype.readAsBinaryString = function(file) { // TODO - Can't return binary data to browser. this.fileName = file; }; /** * Read file and return data as a binary data. * * @param file {File} File object containing file properties */ FileReader.prototype.readAsArrayBuffer = function(file) { // TODO - Can't return binary data to browser. this.fileName = file; }; //----------------------------------------------------------------------------- // File Writer //----------------------------------------------------------------------------- /** * This class writes to the mobile device file system. * * For Android: * The root directory is the root of the file system. * To write to the SD card, the file name is "sdcard/my_file.txt" * * @constructor * @param file {File} File object containing file properties * @param append if true write to the end of the file, otherwise overwrite the file */ var FileWriter = function(file) { this.fileName = ""; this.length = 0; if (file) { this.fileName = file.fullPath || file; this.length = file.size || 0; } // default is to write at the beginning of the file this.position = 0; this.readyState = 0; // EMPTY this.result = null; // Error this.error = null; // Event handlers this.onwritestart = null; // When writing starts this.onprogress = null; // While writing the file, and reporting partial file data this.onwrite = null; // When the write has successfully completed. this.onwriteend = null; // When the request has completed (either in success or failure). this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. this.onerror = null; // When the write has failed (see errors). }; // States FileWriter.INIT = 0; FileWriter.WRITING = 1; FileWriter.DONE = 2; /** * Abort writing file. */ FileWriter.prototype.abort = function() { // check for invalid state if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { throw FileError.INVALID_STATE_ERR; } // set error var error = new FileError(), evt; error.code = error.ABORT_ERR; this.error = error; // If error callback if (typeof this.onerror === "function") { this.onerror({"type":"error", "target":this}); } // If abort callback if (typeof this.onabort === "function") { this.onabort({"type":"abort", "target":this}); } this.readyState = FileWriter.DONE; // If write end callback if (typeof this.onwriteend == "function") { this.onwriteend({"type":"writeend", "target":this}); } }; /** * Writes data to the file * * @param text to be written */ FileWriter.prototype.write = function(text) { // Throw an exception if we are already writing a file if (this.readyState === FileWriter.WRITING) { throw FileError.INVALID_STATE_ERR; } // WRITING state this.readyState = FileWriter.WRITING; var me = this; // If onwritestart callback if (typeof me.onwritestart === "function") { me.onwritestart({"type":"writestart", "target":me}); } // Write file navigator.fileMgr.write(this.fileName, text, this.position, // Success callback function(r) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileWriter.DONE) { return; } // position always increases by bytes written because file would be extended me.position += r; // The length of the file is now where we are done writing. me.length = me.position; // If onwrite callback if (typeof me.onwrite === "function") { me.onwrite({"type":"write", "target":me}); } // DONE state me.readyState = FileWriter.DONE; // If onwriteend callback if (typeof me.onwriteend === "function") { me.onwriteend({"type":"writeend", "target":me}); } }, // Error callback function(e) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileWriter.DONE) { return; } // Save error me.error = e; // If onerror callback if (typeof me.onerror === "function") { me.onerror({"type":"error", "target":me}); } // DONE state me.readyState = FileWriter.DONE; // If onwriteend callback if (typeof me.onwriteend === "function") { me.onwriteend({"type":"writeend", "target":me}); } } ); }; /** * Moves the file pointer to the location specified. * * If the offset is a negative number the position of the file * pointer is rewound. If the offset is greater than the file * size the position is set to the end of the file. * * @param offset is the location to move the file pointer to. */ FileWriter.prototype.seek = function(offset) { // Throw an exception if we are already writing a file if (this.readyState === FileWriter.WRITING) { throw FileError.INVALID_STATE_ERR; } if (!offset) { return; } // See back from end of file. if (offset < 0) { this.position = Math.max(offset + this.length, 0); } // Offset is bigger then file size so set position // to the end of the file. else if (offset > this.length) { this.position = this.length; } // Offset is between 0 and file size so set the position // to start writing. else { this.position = offset; } }; /** * Truncates the file to the size specified. * * @param size to chop the file at. */ FileWriter.prototype.truncate = function(size) { // Throw an exception if we are already writing a file if (this.readyState === FileWriter.WRITING) { throw FileError.INVALID_STATE_ERR; } // WRITING state this.readyState = FileWriter.WRITING; var me = this; // If onwritestart callback if (typeof me.onwritestart === "function") { me.onwritestart({"type":"writestart", "target":this}); } // Write file navigator.fileMgr.truncate(this.fileName, size, // Success callback function(r) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileWriter.DONE) { return; } // Update the length of the file me.length = r; me.position = Math.min(me.position, r); // If onwrite callback if (typeof me.onwrite === "function") { me.onwrite({"type":"write", "target":me}); } // DONE state me.readyState = FileWriter.DONE; // If onwriteend callback if (typeof me.onwriteend === "function") { me.onwriteend({"type":"writeend", "target":me}); } }, // Error callback function(e) { var evt; // If DONE (cancelled), then don't do anything if (me.readyState === FileWriter.DONE) { return; } // Save error me.error = e; // If onerror callback if (typeof me.onerror === "function") { me.onerror({"type":"error", "target":me}); } // DONE state me.readyState = FileWriter.DONE; // If onwriteend callback if (typeof me.onwriteend === "function") { me.onwriteend({"type":"writeend", "target":me}); } } ); }; /** * Information about the state of the file or directory * * @constructor * {Date} modificationTime (readonly) */ var Metadata = function() { this.modificationTime=null; }; /** * Supplies arguments to methods that lookup or create files and directories * * @constructor * @param {boolean} create file or directory if it doesn't exist * @param {boolean} exclusive if true the command will fail if the file or directory exists */ var Flags = function(create, exclusive) { this.create = create || false; this.exclusive = exclusive || false; }; /** * An interface representing a file system * * @constructor * {DOMString} name the unique name of the file system (readonly) * {DirectoryEntry} root directory of the file system (readonly) */ var FileSystem = function() { this.name = null; this.root = null; }; /** * An interface that lists the files and directories in a directory. * @constructor */ var DirectoryReader = function(fullPath){ this.fullPath = fullPath || null; }; /** * Returns a list of entries from a directory. * * @param {Function} successCallback is called with a list of entries * @param {Function} errorCallback is called with a FileError */ DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "readEntries", [this.fullPath]); }; /** * An interface representing a directory on the file system. * * @constructor * {boolean} isFile always false (readonly) * {boolean} isDirectory always true (readonly) * {DOMString} name of the directory, excluding the path leading to it (readonly) * {DOMString} fullPath the absolute full path to the directory (readonly) * {FileSystem} filesystem on which the directory resides (readonly) */ var DirectoryEntry = function() { this.isFile = false; this.isDirectory = true; this.name = null; this.fullPath = null; this.filesystem = null; }; /** * Copies a directory to a new location * * @param {DirectoryEntry} parent the directory to which to copy the entry * @param {DOMString} newName the new name of the entry, defaults to the current name * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); }; /** * Looks up the metadata of the entry * * @param {Function} successCallback is called with a Metadata object * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); }; /** * Gets the parent of the entry * * @param {Function} successCallback is called with a parent entry * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); }; /** * Moves a directory to a new location * * @param {DirectoryEntry} parent the directory to which to move the entry * @param {DOMString} newName the new name of the entry, defaults to the current name * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); }; /** * Removes the entry * * @param {Function} successCallback is called with no parameters * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.remove = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); }; /** * Returns a URI that can be used to identify this entry. * * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. * @return uri */ DirectoryEntry.prototype.toURI = function(mimeType) { return "file://" + this.fullPath; }; /** * Creates a new DirectoryReader to read entries from this directory */ DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) { return new DirectoryReader(this.fullPath); }; /** * Creates or looks up a directory * * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory * @param {Flags} options to create or excluively create the directory * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getDirectory", [this.fullPath, path, options]); }; /** * Creates or looks up a file * * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file * @param {Flags} options to create or excluively create the file * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getFile", [this.fullPath, path, options]); }; /** * Deletes a directory and all of it's contents * * @param {Function} successCallback is called with no parameters * @param {Function} errorCallback is called with a FileError */ DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "removeRecursively", [this.fullPath]); }; /** * An interface representing a directory on the file system. * * @constructor * {boolean} isFile always true (readonly) * {boolean} isDirectory always false (readonly) * {DOMString} name of the file, excluding the path leading to it (readonly) * {DOMString} fullPath the absolute full path to the file (readonly) * {FileSystem} filesystem on which the directory resides (readonly) */ var FileEntry = function() { this.isFile = true; this.isDirectory = false; this.name = null; this.fullPath = null; this.filesystem = null; }; /** * Copies a file to a new location * * @param {DirectoryEntry} parent the directory to which to copy the entry * @param {DOMString} newName the new name of the entry, defaults to the current name * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); }; /** * Looks up the metadata of the entry * * @param {Function} successCallback is called with a Metadata object * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.getMetadata = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); }; /** * Gets the parent of the entry * * @param {Function} successCallback is called with a parent entry * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.getParent = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); }; /** * Moves a directory to a new location * * @param {DirectoryEntry} parent the directory to which to move the entry * @param {DOMString} newName the new name of the entry, defaults to the current name * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); }; /** * Removes the entry * * @param {Function} successCallback is called with no parameters * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.remove = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); }; /** * Returns a URI that can be used to identify this entry. * * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. * @return uri */ FileEntry.prototype.toURI = function(mimeType) { return "file://" + this.fullPath; }; /** * Creates a new FileWriter associated with the file that this FileEntry represents. * * @param {Function} successCallback is called with the new FileWriter * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.createWriter = function(successCallback, errorCallback) { this.file(function(filePointer) { var writer = new FileWriter(filePointer); if (writer.fileName === null || writer.fileName === "") { if (typeof errorCallback == "function") { errorCallback({ "code": FileError.INVALID_STATE_ERR }); } } if (typeof successCallback == "function") { successCallback(writer); } }, errorCallback); }; /** * Returns a File that represents the current state of the file that this FileEntry represents. * * @param {Function} successCallback is called with the new File object * @param {Function} errorCallback is called with a FileError */ FileEntry.prototype.file = function(successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "getFileMetadata", [this.fullPath]); }; /** @constructor */ var LocalFileSystem = function() { }; // File error codes LocalFileSystem.TEMPORARY = 0; LocalFileSystem.PERSISTENT = 1; LocalFileSystem.RESOURCE = 2; LocalFileSystem.APPLICATION = 3; /** * Requests a filesystem in which to store application data. * * @param {int} type of file system being requested * @param {Function} successCallback is called with the new FileSystem * @param {Function} errorCallback is called with a FileError */ LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { if (type < 0 || type > 3) { if (typeof errorCallback == "function") { errorCallback({ "code": FileError.SYNTAX_ERR }); } } else { PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); } }; /** * * @param {DOMString} uri referring to a local file in a filesystem * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); }; /** * This function returns and array of contacts. It is required as we need to convert raw * JSON objects into concrete Contact objects. Currently this method is called after * navigator.service.contacts.find but before the find methods success call back. * * @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. * @returns an entry */ LocalFileSystem.prototype._castFS = function(pluginResult) { var entry = null; entry = new DirectoryEntry(); entry.isDirectory = pluginResult.message.root.isDirectory; entry.isFile = pluginResult.message.root.isFile; entry.name = pluginResult.message.root.name; entry.fullPath = pluginResult.message.root.fullPath; pluginResult.message.root = entry; return pluginResult; }; LocalFileSystem.prototype._castEntry = function(pluginResult) { var entry = null; if (pluginResult.message.isDirectory) { console.log("This is a dir"); entry = new DirectoryEntry(); } else if (pluginResult.message.isFile) { console.log("This is a file"); entry = new FileEntry(); } entry.isDirectory = pluginResult.message.isDirectory; entry.isFile = pluginResult.message.isFile; entry.name = pluginResult.message.name; entry.fullPath = pluginResult.message.fullPath; pluginResult.message = entry; return pluginResult; }; LocalFileSystem.prototype._castEntries = function(pluginResult) { var entries = pluginResult.message; var retVal = []; for (var i=0; i