diff options
-rw-r--r-- | polldance.js | 242 |
1 files changed, 135 insertions, 107 deletions
diff --git a/polldance.js b/polldance.js index 9fedaa6..b933e04 100644 --- a/polldance.js +++ b/polldance.js @@ -3,29 +3,62 @@ * Copyright 2013 Fan Out Networks, Inc. * Released under the MIT license (see COPYING file in source distribution) */ +(function () { +var DEBUG = true; +"use strict"; (function (window, undefined) { + var NAMESPACE = "PollDance"; var TIMEOUT = 60000; + var emptyMethod = function(){}; - // don't break if there's no console - if (typeof (window.console) === "undefined") { - window.console = { log: function () { }, dir: function () { } }; + var log; + if (DEBUG) { + // don't break if there's no console + if (typeof (window.console) === "undefined") { + window.console = { log: emptyMethod, dir: emptyMethod }; + } + log = function(output) { window.console.log(output); }; + } else { + log = emptyMethod; } - // PollDance.Request has on('finished', int code, object result, object headers) and on('error', int reason) callback members + var copyArray = function(array) { + var args = Array.prototype.slice.call(arguments, 1); + return Array.prototype.slice.apply(array, args); + }; + + var indexOfItemInArray = function(array, item) { + for (var i = 0, length = array.length; i < length; i++) { + if (array[i] === item) { + return i; + } + } + return -1; + }; + var removeFromArray = function (array, item) { + var again = true; + while (again) { + var index = indexOfItemInArray(array, item); + if (index != -1) { + array.splice(index, 1); + } else { + again = false; + } + } + }; - var JsonCallbacks = { + var jsonCallbacks = { id: 0, requests: {}, - emptyCallback: function() {}, getJsonpCallback: function (id) { var cb; var requests = this.requests; if (id in this.requests) { cb = function (result) { requests[id]._jsonp_callback(result); }; } else { - console.log("no callback with id " + id); - cb = this.emptyCallback; + log("no callback with id " + id); + cb = emptyMethod; } return cb; }, @@ -45,99 +78,100 @@ } }; - var corsAvailable = "withCredentials" in new XMLHttpRequest(); + var corsAvailable = "withCredentials" in new window.XMLHttpRequest(); - function sameOrigin(url) { + var sameOrigin = function(url) { var loc = window.location; - var a = document.createElement('a'); + var a = window.document.createElement('a'); a.href = url; return !a.hostname || (a.hostname == loc.hostname && a.port == loc.port && a.protocol == loc.protocol); - } + }; var Events = function () { this._events = {}; }; - Events.prototype._getEventsForType = function (type) { + Events.prototype._getHandlersForType = function (type) { if (!(type in this._events)) { this._events[type] = []; } return this._events[type]; }; - Events.prototype._setEventsForType = function (type, events) { - this._events[type] = events; - }; Events.prototype.on = function (type, handler) { - var events = this._getEventsForType(type); - events.push(handler); + var handlers = this._getHandlersForType(type); + handlers.push(handler); }; Events.prototype.off = function (type) { if (arguments.length > 1) { - var events = this._getEventsForType(type); - var handlersToKeep = []; - var handlers = Array.prototype.slice.call(arguments, 1); - for (var i = 0; i < events.length; i++) { - var event = events[i]; - var keep = true; - for (var j = 0; j < handlers.length; j++) { - var handler = handlers[j]; - if (event == handler) { - keep = false; - } - } - if (keep) { - handlersToKeep.push(event); - } - } - this._setEventsForType(type, handlersToKeep); + var handler = arguments[1]; + var handlers = this._getHandlersForType(type); + removeFromArray(handlers, handler); } else { delete this._events[type]; } - }; Events.prototype.trigger = function (type, obj) { - var args = Array.prototype.slice.call(arguments, 2); - var events = this._getEventsForType(type); - for (var i = 0; i < events.length; i++) { - var handler = events[i]; + var args = copyArray(arguments, 2); + var handlers = copyArray(this._getHandlersForType(type)); + for (var i = 0, n = handlers.length; i < n; i++) { + var handler = handlers[i]; handler.apply(obj, args); } }; - var ErrorTypes = { + var errorTypes = { "TransportError": 0, "TimeoutError": 1 }; - var TransportTypes = { + var transportTypes = { "Auto": 0, "Xhr": 1, "Jsonp": 2 }; - var Request = function (config) { + // PollDance.Request has callback members: + // on('finished', int code, object result, object headers) + // on('error', int reason) + + var Request = function () { if (!(this instanceof Request)) { throw new Error("Constructor called as a function"); } this._events = new Events(); + this._tries = 0; this._delayNext = false; + this._retryTime = 0; + this._timer = null; + this._callbackInfo = null; + + this._xhr = null; + this._method = null; + this._url = null; + this._headers = null; + this._body = null; + this.transport = transportTypes.Auto; this.rawResponse = false; this.maxTries = 1; this.maxDelay = 1000; - if (config !== undefined) { - if (config.transport !== undefined) + if (arguments.length > 0) { + var config = arguments[0]; + if ("transport" in config) { this.transport = config.transport; - if (config.rawResponse !== undefined) + } + if ("rawResponse" in config) { this.rawResponse = config.rawResponse; - if (config.maxTries !== undefined) + } + if ("maxTries" in config) { this.maxTries = config.maxTries; - if (config.maxDelay !== undefined) + } + if ("maxDelay" in config) { this.maxDelay = config.maxDelay; + } } }; - Request.prototype.transport = TransportTypes.Auto; Request.prototype.start = function (method, url, headers, body) { var self = this; @@ -147,7 +181,7 @@ if (self._delayNext) { self._delayNext = false; delaytime = Math.floor(Math.random() * self.maxDelay); - console.log("PD: polling again in " + delaytime + "ms"); + log("PD: polling again in " + delaytime + "ms"); } else { delaytime = 0; // always queue the call, to prevent browser "busy" } @@ -157,7 +191,7 @@ self._headers = headers; self._body = body; - self._timer = setTimeout(function() { self._connect(); }, delaytime); + self._timer = window.setTimeout(function () { self._connect(); }, delaytime); }; Request.prototype.retry = function () { this._retry(); @@ -166,19 +200,19 @@ var self = this; self._timer = window.setTimeout(function () { self._timeout(); }, TIMEOUT); - ++self._tries; + self._tries++; - if (self.transport == TransportTypes.Auto) { + if (self.transport == transportTypes.Auto) { if (corsAvailable || sameOrigin(self._url)) { - self.transport = TransportTypes.Xhr; + self.transport = transportTypes.Xhr; } else { - self.transport = TransportTypes.Jsonp; + self.transport = transportTypes.Jsonp; } } - if (self.transport == TransportTypes.Xhr) { + if (self.transport == transportTypes.Xhr) { - self._xhr = new XMLHttpRequest(); + self._xhr = new window.XMLHttpRequest(); self._xhr.onreadystatechange = function () { self._xhr_readystatechange(); }; self._xhr.open(self._method, self._url, true); @@ -190,15 +224,15 @@ self._xhr.send(self._body); - console.log("PD: xhr " + self._url + " " + self._body); + log("PD: xhr " + self._url + " " + self._body); } else { // Jsonp - this._callbackInfo = JsonCallbacks.newCallbackInfo(); + this._callbackInfo = jsonCallbacks.newCallbackInfo(); var paramList = []; - paramList.push("_callback=" + encodeURIComponent("PollDance._getJsonpCallback(\"" + this._callbackInfo.id + "\")")); + paramList.push("_callback=" + encodeURIComponent("window['" + NAMESPACE + "']._getJsonpCallback(\"" + this._callbackInfo.id + "\")")); if (self._method != "GET") { paramList.push("_method=" + encodeURIComponent(self._method)); } @@ -217,12 +251,12 @@ src = self._url + "?" + params; } - console.log("PD: json-p " + this._callbackInfo.id + " " + src); + log("PD: json-p " + this._callbackInfo.id + " " + src); this._addJsonpCallback(this._callbackInfo, src); } }; Request.prototype.abort = function () { - clearTimeout(this._timer); + window.clearTimeout(this._timer); this._timer = null; this._cancelreq(); }; @@ -230,32 +264,33 @@ this._events.on(type, handler); }; Request.prototype.off = function (type) { - var args = Array.prototype.slice.call(arguments); + var args = copyArray(arguments, 1).unshift(type); this._events.off.apply(this._events, args); }; Request.prototype._addJsonpCallback = function (callbackInfo, src) { - JsonCallbacks.addJsonpCallback(callbackInfo.id, this); + jsonCallbacks.addJsonpCallback(callbackInfo.id, this); - var head = document.getElementsByTagName("head")[0]; - var script = document.createElement("script"); + var script = window.document.createElement("script"); script.type = "text/javascript"; script.id = callbackInfo.scriptId; script.src = src; + + var head = window.document.getElementsByTagName("head")[0]; head.appendChild(script); }; Request.prototype._removeJsonpCallback = function (callbackInfo) { - JsonCallbacks.removeJsonpCallback(callbackInfo.id, this); + jsonCallbacks.removeJsonpCallback(callbackInfo.id, this); - var script = document.getElementById(callbackInfo.scriptId); + var script = window.document.getElementById(callbackInfo.scriptId); script.parentNode.removeChild(script); }; Request.prototype._cancelreq = function () { - if (this.transport == TransportTypes.Xhr) { - this._xhr.onreadystatechange = function () { } + if (this.transport == transportTypes.Xhr) { + this._xhr.onreadystatechange = emptyMethod; this._xhr.abort(); this._xhr = null; } else { // Jsonp - console.log("PD: json-p " + this._callbackInfo.id + " cancel"); + log("PD: json-p " + this._callbackInfo.id + " cancel"); this._removeJsonpCallback(this._callbackInfo); this._callbackInfo = null; } @@ -268,24 +303,21 @@ window.clearTimeout(this._timer); this._timer = null; - if (xhr.status) { - if (xhr.status >= 500 && xhr.status <= 599 && (this.maxTries == -1 || this._tries < this.maxTries)) { - this._retry(); - } else { + if ((!xhr.status || (xhr.status >= 500 && xhr.status < 600)) && + (this.maxTries == -1 || this._tries < this.maxTries)) { + this._retry(); + } else { + if (xhr.status) { // TODO: xhr.getAllResponseHeaders() this._handle_response(xhr.status, xhr.statusText, {}, xhr.responseText); - } - } else { - if (this.maxTries == -1 || this._tries < this.maxTries) { - this._retry(); } else { - this._error(ErrorTypes.TransportError); + this._error(errorTypes.TransportError); } } } }; Request.prototype._jsonp_callback = function (result) { - console.log("PD: json-p " + this._callbackInfo.id + " finished"); + log("PD: json-p " + this._callbackInfo.id + " finished"); window.clearTimeout(this._timer); this._timer = null; @@ -293,27 +325,21 @@ this._removeJsonpCallback(this._callbackInfo); this._callbackInfo = null; - var headers; - if (result.headers !== undefined) { - headers = result.headers; - } else { - headers = {}; - } - + var headers = ("headers" in result) ? result.headers : {}; this._handle_response(result.code, result.status, headers, result.body); }; Request.prototype._handle_response = function (code, status, headers, body) { - var result; - if (this.rawResponse) { - result = body; - } else { - try { - result = JSON.parse(body); - } catch (e) { - result = body; - } - } - this._finished(code, result, headers); + var result; + if (this.rawResponse) { + result = body; + } else { + try { + result = JSON.parse(body); + } catch (e) { + result = body; + } + } + this._finished(code, result, headers); }; Request.prototype._timeout = function () { this._timer = null; @@ -322,10 +348,10 @@ if (this.maxTries == -1 || this._tries < this.maxTries) { this._retry(); } else { - this._error(ErrorTypes.TimeoutError); + this._error(errorTypes.TimeoutError); } }; - Request.prototype._retry = function() { + Request.prototype._retry = function () { if (this._tries === 1) { this._retryTime = 1; } else if (this._tries < 8) { @@ -334,10 +360,10 @@ var delaytime = this._retryTime * 1000; delaytime += Math.floor(Math.random() * this.maxDelay); - console.log("PD: trying again in " + delaytime + "ms"); + log("PD: trying again in " + delaytime + "ms"); var self = this; - self._timer = setTimeout(function() { self._connect(); }, delaytime); + self._timer = window.setTimeout(function () { self._connect(); }, delaytime); }; Request.prototype._finished = function (code, result, headers) { this._delayNext = true; @@ -348,13 +374,15 @@ this._events.trigger('error', this, reason); }; - var pollDance = { + var exports = { Request: Request, + TransportTypes: transportTypes, + ErrorTypes: errorTypes, _getJsonpCallback: function (id) { - return JsonCallbacks.getJsonpCallback(id); + return jsonCallbacks.getJsonpCallback(id); } }; - - window.PollDance = window.PollDance || pollDance; + window[NAMESPACE] = window[NAMESPACE] || exports; })(window); +})(); |