| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| var utils = require('cordova/utils'), |
| nextGuid = 1; |
| |
| /** |
| * Custom pub-sub "channel" that can have functions subscribed to it |
| * This object is used to define and control firing of events for |
| * cordova initialization, as well as for custom events thereafter. |
| * |
| * The order of events during page load and Cordova startup is as follows: |
| * |
| * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. |
| * onNativeReady* Internal event that indicates the Cordova native side is ready. |
| * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. |
| * onDeviceReady* User event fired to indicate that Cordova 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 events marked with an * are sticky. Once they have fired, they will stay in the fired state. |
| * All listeners that subscribe after the event is fired will be executed right away. |
| * |
| * The only Cordova events that user code should register for are: |
| * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript |
| * pause App has moved to background |
| * resume App has returned to foreground |
| * |
| * Listeners can be registered as: |
| * document.addEventListener("deviceready", myDeviceReadyListener, false); |
| * document.addEventListener("resume", myResumeListener, false); |
| * document.addEventListener("pause", myPauseListener, false); |
| * |
| * The DOM lifecycle events should be used for saving and restoring state |
| * window.onload |
| * window.onunload |
| * |
| */ |
| |
| /** |
| * Channel |
| * @constructor |
| * @param type String the channel name |
| */ |
| var Channel = function(type, sticky) { |
| this.type = type; |
| // Map of guid -> function. |
| this.handlers = {}; |
| // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. |
| this.state = sticky ? 1 : 0; |
| // Used in sticky mode to remember args passed to fire(). |
| this.fireArgs = null; |
| // Used by onHasSubscribersChange to know if there are any listeners. |
| this.numHandlers = 0; |
| // Function that is called when the first listener is subscribed, or when |
| // the last listener is unsubscribed. |
| this.onHasSubscribersChange = null; |
| }, |
| channel = { |
| /** |
| * Calls the provided function only after all of the channels specified |
| * have been fired. All channels must be sticky channels. |
| */ |
| join: function(h, c) { |
| var len = c.length, |
| i = len, |
| f = function() { |
| if (!(--i)) h(); |
| }; |
| for (var j=0; j<len; j++) { |
| if (c[j].state === 0) { |
| throw Error('Can only use join with sticky channels.'); |
| } |
| c[j].subscribe(f); |
| } |
| if (!len) h(); |
| }, |
| create: function(type) { |
| return channel[type] = new Channel(type, false); |
| }, |
| createSticky: function(type) { |
| return channel[type] = new Channel(type, true); |
| }, |
| |
| /** |
| * cordova Channels that must fire before "deviceready" is fired. |
| */ |
| deviceReadyChannelsArray: [], |
| deviceReadyChannelsMap: {}, |
| |
| /** |
| * Indicate that a feature needs to be initialized before it is ready to be used. |
| * This holds up Cordova's "deviceready" event until the feature has been initialized |
| * and Cordova.initComplete(feature) is called. |
| * |
| * @param feature {String} The unique feature name |
| */ |
| waitForInitialization: function(feature) { |
| if (feature) { |
| var c = channel[feature] || this.createSticky(feature); |
| this.deviceReadyChannelsMap[feature] = c; |
| this.deviceReadyChannelsArray.push(c); |
| } |
| }, |
| |
| /** |
| * Indicate that initialization code has completed and the feature is ready to be used. |
| * |
| * @param feature {String} The unique feature name |
| */ |
| initializationComplete: function(feature) { |
| var c = this.deviceReadyChannelsMap[feature]; |
| if (c) { |
| c.fire(); |
| } |
| } |
| }; |
| |
| function forceFunction(f) { |
| if (typeof f != 'function') throw "Function required as first argument!"; |
| } |
| |
| /** |
| * 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. |
| */ |
| Channel.prototype.subscribe = function(f, c) { |
| // need a function to call |
| forceFunction(f); |
| if (this.state == 2) { |
| f.apply(c || this, this.fireArgs); |
| return; |
| } |
| |
| var func = f, |
| guid = f.observer_guid; |
| if (typeof c == "object") { func = utils.close(c, f); } |
| |
| if (!guid) { |
| // first time any channel has seen this subscriber |
| guid = '' + nextGuid++; |
| } |
| func.observer_guid = guid; |
| f.observer_guid = guid; |
| |
| // Don't add the same handler more than once. |
| if (!this.handlers[guid]) { |
| this.handlers[guid] = func; |
| this.numHandlers++; |
| if (this.numHandlers == 1) { |
| this.onHasSubscribersChange && this.onHasSubscribersChange(); |
| } |
| } |
| }; |
| |
| /** |
| * Unsubscribes the function with the given guid from the channel. |
| */ |
| Channel.prototype.unsubscribe = function(f) { |
| // need a function to unsubscribe |
| forceFunction(f); |
| |
| var guid = f.observer_guid, |
| handler = this.handlers[guid]; |
| if (handler) { |
| delete this.handlers[guid]; |
| this.numHandlers--; |
| if (this.numHandlers === 0) { |
| this.onHasSubscribersChange && this.onHasSubscribersChange(); |
| } |
| } |
| }; |
| |
| /** |
| * Calls all functions subscribed to this channel. |
| */ |
| Channel.prototype.fire = function(e) { |
| var fail = false, |
| fireArgs = Array.prototype.slice.call(arguments); |
| // Apply stickiness. |
| if (this.state == 1) { |
| this.state = 2; |
| this.fireArgs = fireArgs; |
| } |
| if (this.numHandlers) { |
| // Copy the values first so that it is safe to modify it from within |
| // callbacks. |
| var toCall = []; |
| for (var item in this.handlers) { |
| toCall.push(this.handlers[item]); |
| } |
| for (var i = 0; i < toCall.length; ++i) { |
| toCall[i].apply(this, fireArgs); |
| } |
| if (this.state == 2 && this.numHandlers) { |
| this.numHandlers = 0; |
| this.handlers = {}; |
| this.onHasSubscribersChange && this.onHasSubscribersChange(); |
| } |
| } |
| }; |
| |
| |
| // defining them here so they are ready super fast! |
| // DOM event that is received when the web page is loaded and parsed. |
| channel.createSticky('onDOMContentLoaded'); |
| |
| // Event to indicate the Cordova native side is ready. |
| channel.createSticky('onNativeReady'); |
| |
| // Event to indicate that all Cordova JavaScript objects have been created |
| // and it's time to run plugin constructors. |
| channel.createSticky('onCordovaReady'); |
| |
| // Event to indicate that all automatically loaded JS plugins are loaded and ready. |
| // FIXME remove this |
| channel.createSticky('onPluginsReady'); |
| |
| // Event to indicate that Cordova is ready |
| channel.createSticky('onDeviceReady'); |
| |
| // Event to indicate a resume lifecycle event |
| channel.create('onResume'); |
| |
| // Event to indicate a pause lifecycle event |
| channel.create('onPause'); |
| |
| // Event to indicate a destroy lifecycle event |
| channel.createSticky('onDestroy'); |
| |
| // Channels that must fire before "deviceready" is fired. |
| channel.waitForInitialization('onCordovaReady'); |
| channel.waitForInitialization('onDOMContentLoaded'); |
| |
| module.exports = channel; |