Yahoo! UI Library

poller  1.0.0

Yahoo! UI Library > poller > io-poller.js (source view)
Search:
 
Filters
	/**
	 * gallery-io-poller - A utility to smartly poll (via XHR requests) a resource on the server.
	 * http://925html.com/code/smart-polling/
	 * 
	 * Oddnut Software
	 * Copyright (c) 2009 Eric Ferraiuolo - http://eric.ferraiuolo.name
	 * YUI BSD License - http://developer.yahoo.com/yui/license.html
	 */
	
	/**
	 * Extends Y.io to add support for xhr-polling.
	 * 
	 * @module poller
	 * @requires io-base, base
	 */
	
	var Poller,
		POLLER = 'poller',
		
		INTERVAL = 'interval',
		URI = 'uri',
		IO_CONFIG = 'ioConfig',
		POLLING = 'polling',
		
		IF_NONE_MATCH = 'If-None-Match',
		If_MODIFIED_SINCE = 'If-Modified-Since',
		
		E_MODIFIED = 'io:modified',
		
		isString = Y.Lang.isString,
		isNumber = Y.Lang.isNumber,
		isObject = Y.Lang.isObject;
	
	/**
	 * Create a polling task to continually check the server at the specified interval for updates of a resource at a URI.
	 * The poller will use conditional GET requests and notify the client via Callbacks and Events when the resource has changed.
	 * 
	 * @class Poller
	 * @extends Base
	 * @param {Object} config Configuration Object
	 * @constructor
	 */
	Poller = function (config) {
		
		Poller.superclass.constructor.apply( this, arguments );
	};
	
	Y.mix( Poller, {
		
		/**
		 * The identity of the component.
		 * 
		 * @property Poller.NAME
		 * @type String
		 * @static
		 */
		NAME : POLLER,
		
		/**
		 * Static property used to define the default attribute configuration of
		 * the component.
		 *
		 * @property Poller.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS : {
			
			/**
			 * The time in milliseconds for which the component should send a request to the server.
			 * 
			 * @attribute interval
			 * @type Number
			 */
			interval : {
				validator : isNumber
			},
			
			/**
			 * The URI of the resource which to xhr-poll.
			 * 
			 * @attribute uri
			 * @type String
			 * @writeOnce
			 */
			uri : {
				validator : isString,
				writeOnce : true
			},
			
			/**
			 * The configuration Y.io config.
			 * 
			 * @attribute ioConfig
			 * @type Object
			 * @writeOnce
			 */
			ioConfig : {
				validator : isObject,
				writeOnce : true
			},
			
			/**
			 * A read-only attribute the client can check to see if the component is actively polling the server.
			 * 
			 * @attribute polling
			 * @type Boolean
			 * @default false
			 * @readOnly
			 * @final
			 */
			polling : {
				value : false,
				readOnly : true
			}
			
		}
		
	});
	
	Y.extend( Poller, Y.Base, {
		
		/**
		 * Timer object to schedule next request to server.
		 * 
		 * @property _timer
		 * @type Y.later
		 * @protected
		 */
		_timer : null,
		
		/**
		 * Time of the last request was sent.
		 * 
		 * @property _txTime
		 * @type Number
		 * @protected
		 */
		_txTime : null,
		
		/**
		 * active IO transaction Object
		 * 
		 * @property _activeTx
		 * @type Object
		 * @protected
		 */
		_activeTx : null,
		
		/**
		 * Last-Modified date of the resource that the server returned, used to determine if the resource has changed.
		 * 
		 * @property _modifiedDate
		 * @type String
		 * @protected
		 */
		_modifiedDate : null,
		
		/**
		 * Etag of the resource returned by the server, used to determine if the resource has changed.
		 * 
		 * @property _etag
		 * @type String
		 * @protected
		 */
		_etag : null,
		
		/**
		 * Construction of the component.
		 * 
		 * @method initializer
		 * @param {Object} config Configuration Object
		 * @protected
		 */
		initializer : function (config) {
			
			/**
			 * Signals that the resource the component is pulling the server for has been modified.
			 * This is the interesting event for the client to subscribe to.
			 * The subscriber could, for example, update the UI in response to this event, poller:modified.
			 * 
			 * @event io:modified
			 * @param {Number} txId Y.io Transaction ID
			 * @param {Object} r Y.io Response Object
			 * @param {MIXED} args Arguments passed to response handler
			 */
			
			this._timer = null;
			this._txTime = null;
			this._activeTx = null;
			this._modifiedDate = null;
			this._etag = null;
		},
		
		/**
		 * Destruction of the component. Stops polling and cleans up.
		 * 
		 * @method destructor
		 * @protected
		 */
		destructor : function () {
			
			this.stop();
			this._modifiedDate = null;
			this._etag = null;
		},
		
		/**
		 * Starts the polling task.
		 * A request will be sent to the server right at the time of calling this method;
		 * and continued by sending subsequent requests at the set interval.
		 * To stop or pause polling call the stop method.
		 * 
		 * @method start
		 */
		start : function () {
			
			if ( ! this.get(POLLING)) {
				this._sendRequest();
				this._set(POLLING, true);
			}
		},
		
		/**
		 * Stops the polling task.
		 * Start can be called to resume polling.
		 * 
		 * @method stop
		 */
		stop : function () {
			
			if (this._activeTx) {
				this._activeTx.abort();
				this._activeTx = null;
			}
			
			if (this._timer) {
				this._timer.cancel();
				this._timer = null;
			}
			
			this._txTime = null;
			
			this._set(POLLING, false);
		},
		
		/**
		 * Sends the XHR request to the server at the given URI (resource).
		 * Method used internally to make the XHR requests.
		 * 
		 * @method sendRequest
		 * @protected
		 */
		_sendRequest : function () {
			
			var ioConfig = this.get(IO_CONFIG),
				headers = {},
				config;
				
			if (this._etag) {
				headers[IF_NONE_MATCH] = this._etag;
			}
			if (this._modifiedDate) {
				headers[If_MODIFIED_SINCE] = this._modifiedDate;
			}
			
			config = Y.merge(ioConfig, {
				
				headers	: headers,
				context	: this,
				on		: Y.merge(ioConfig, {
					
					start		: this._defStartHandler,
					complete	: this._defCompleteHandler,
					success		: this._defSuccessHandler,
					modified	: this._defModifiedHandler
					
				})
				
			});
			
			this._activeTx = Y.io(this.get(URI), config);
		},
		
		/**
		 * Sets the txTime and calls the config's on.start handler.
		 * 
		 * @method _defStartHandler
		 * @param {Number} txId Y.io Transaction ID
		 * @param {MIXED} args Arguments passed to handlers
		 * @protected
		 */
		_defStartHandler : function (txId, args) {
			
			var config = this.get(IO_CONFIG);
			
			this._txTime = new Date().getTime();
			
			if (config && config.on && config.on.start) {
				config.on.start.apply(config.context || Y, arguments);
			}
		},
		
		/**
		 * Schedules the next transaction and calls the config's on.complete handler.
		 * 
		 * @method _defCompleteHandler
		 * @param {Number} txId Y.io Transaction ID
		 * @param {Object} r Y.io Response Object
		 * @param {MIXED} args Arguments passed to handlers
		 * @protected
		 */
		_defCompleteHandler : function (txId, r, args) {
			
			var config = this.get(IO_CONFIG),
				deltaT = this._txTime ? (new Date()).getTime() - this._txTime : 0;
			
			this._timer = Y.later(Math.max(this.get(INTERVAL) - deltaT, 0), this, this._sendRequest);
			
			if (config && config.on && config.on.complete) {
				config.on.complete.apply(config.context || Y, arguments);
			}
		},
		
		/**
		 * Chains to the modified callback and calls the config's on.success handler.
		 * 
		 * @method _defSuccessHandler
		 * @param {Number} txId Y.io Transaction ID
		 * @param {Object} r Y.io Response Object
		 * @param {MIXED} args Arguments passed to handlers
		 * @protected
		 */
		_defSuccessHandler : function (txId, r, args) {
			
			var config = this.get(IO_CONFIG);
			
			if (config && config.on && config.on.success) {
				config.on.success.apply(config.context || Y, arguments);
			}
			
			this._defModifiedHandler.apply(this, arguments);
		},
		
		/**
		 * Caches Etag and Last-Modified sent back from the server and calls the config's on.modified handler.
		 * 
		 * @method _defModifiedHandler
		 * @param {Number} txId Y.io Transaction ID
		 * @param {Object} r Y.io Response Object
		 * @param {MIXED} args Arguments passed to handlers
		 * @protected
		 */
		_defModifiedHandler : function (txId, r, args) {
			
			var config = this.get(IO_CONFIG);
			
			this._etag = r.getResponseHeader('Etag');
			this._modifiedDate = r.getResponseHeader('Last-Modified');
			
			Y.fire(E_MODIFIED, txId, r);
			
			if (config && config.on && config.on.modified) {
				config.on.modified.apply(config.context || Y, arguments);
			}
		}
		
	});
	
	/**
	 * Method for scheduling a XHR-polling task. Returns an instance of Poller
	 * 
	 * @method Y.io.poll
	 * @param {Number} interval The time in milliseconds for which the component should send a request to the server.
	 * @param {Object} uri qualified path to transaction resource.
	 * @param {Object} config configuration object for the transaction(s); just like Y.io's config object, but with an on:modified callback/event.
	 * @return {Poller} an instance of Poller which has start/stop methods and a configurable interval
	 * @public
	 * @static
	 */
	Y.mix(Y.io, {
		
		poll: function (interval, uri, config) {
			return new Poller({
				interval	: interval,
				uri			: uri,
				ioConfig	: config
			});
		}
		
	}, true);
	

Copyright © 2009 Yahoo! Inc. All rights reserved.