/** * PeriodicalUpdater - jQuery plugin for timed, decaying ajax calls * * http://www.360innovate.co.uk/blog/2009/03/periodicalupdater-for-jquery/ * http://enfranchisedmind.com/blog/posts/jquery-periodicalupdater-ajax-polling/ * https://github.com/RobertFischer/JQuery-PeriodicalUpdater/ * * Copyright (c) 2009-2012 by the following: * Frank White (http://customcode.info) * Robert Fischer (http://smokejumperit.com) * 360innovate (http://www.360innovate.co.uk) * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * */ (function ($) { // The free version $.PeriodicalUpdater = function(url, options, callback){ if(!options) options = {}; var settings = jQuery.extend(true, { url: url, // URL of ajax request cache: false, // By default, don't allow caching method: 'GET', // method; get or post data: '', // array of values to be passed to the page - e.g. {name: "John", greeting: "hello"} minTimeout: 1000, // starting value for the timeout in milliseconds maxTimeout:64000, // maximum length of time between requests multiplier: 2, // if set to 2, timerInterval will double each time the response hasn't changed (up to maxTimeout) maxCalls: 0, // maximum number of calls. 0 = no limit. autoStop: 0, // automatically stop requests after this many returns of the same data. 0 = disabled autoStopCallback: null, // The callback to execute when we autoStop cookie: false, // whether (and how) to store a cookie runatonce: false, // Whether to fire initially or wait verbose: 0 // The level to be logging at: 0 = none; 1 = some; 2 = all }, options); var pu_log = function (msg, lvl) { lvl = lvl || 1; if(settings.verbose >= lvl) { try { console.log(msg); } catch (err) { } } }; // set some initial values, then begin var timer = null; var remoteData = null; var prevData = null; var timerInterval = settings.minTimeout; var maxCalls = settings.maxCalls; var autoStop = settings.autoStop; var calls = 0; var noChange = 0; var originalMaxCalls = maxCalls; // Function to reset the timer to a given time var reset_timer = function (interval) { $(function() { // Ensure we're live if (timer !== null) { clearTimeout(timer); } timerInterval = interval; pu_log('resetting timer to ' + timerInterval + '.', 2); if(settings.cookie && $.cookie) { $.cookie(settings.cookie.name, timerInterval, settings.cookie); } timer = setTimeout(getdata, timerInterval); }); }; // Function to boost the timer var boostPeriod = function () { if (settings.multiplier > 1) { var before = timerInterval; timerInterval = timerInterval * settings.multiplier; if (timerInterval > settings.maxTimeout) { timerInterval = settings.maxTimeout; } pu_log('adjusting timer from ' + before + ' to ' + timerInterval + '.', 2); } reset_timer(timerInterval); }; // Handle the cookie config if(settings.cookie) { if(typeof(settings.cookie) == 'boolean') { settings.cookie = { name: url }; } else if(typeof(settings.cookie) != 'object') { settings.cookie = { name: settings.cookie.toString() }; } if(!settings.cookie.name) { settings.cookie.name = url; } if(!$.cookie) { $.getScript("https://raw.github.com/carhartl/jquery-cookie/master/jquery.cookie.js", function() { pu_log("Loaded the cookies handler script", 2); if($.cookie(settings.cookie.name)) { pu_log("Not runatonce and have cookie value", 2); reset_timer($.cookie(settings.cookie.name)); } else { pu_log("Not runatonce, but no cookie value", 2); reset_timer(timerInterval); } }).fail(function() { pu_log("Could not load the cookies handler script", 1); reset_timer(timerInterval); }); } else { if($.cookie(settings.cookie.name)) { reset_timer($.cookie(settings.cookie.name)); } } } // Construct the settings for $.ajax based on settings var ajaxSettings = jQuery.extend(true, {}, settings); if (settings.type && !ajaxSettings.dataType) { ajaxSettings.dataType = settings.type; } if (settings.sendData) { ajaxSettings.data = settings.sendData; } ajaxSettings.type = settings.method; // 'type' is used internally for jQuery. Who knew? ajaxSettings.ifModified = true; // Create the function to get data function getdata(force) { var toSend = jQuery.extend(true, {}, ajaxSettings); // jQuery screws with what you pass in if (typeof (options.data) == 'function') { toSend.data = options.data(); } if (toSend.data) { // Handle transformations (only strings and objects are understood) if (typeof (toSend.data) == "number") { toSend.data = toSend.data.toString(); } } if (force || maxCalls === 0) { pu_log("Sending data"); $(function() { $.ajax(toSend); }); } else if (maxCalls > 0 && calls < maxCalls) { pu_log("Sending data because we are at " + calls + " of " + maxCalls + " calls"); $(function() { $.ajax(toSend); }); calls++; } else if(maxCalls == -1) { pu_log("NOT sending data: stop has been called", 1); } else { pu_log("NOT sending data: maximum number of calls reached - " + maxCalls, 1); } } var handle = { boostTimer: function(mag) { if(mag > 0 && mag != 1) { pu_log("Boosting timer by a factor of " + mag); timerInterval = timerInterval * mag; } else { pu_log("Cannot boost timer by a factor of " + mag); } reset_timer(timerInterval); return; }, restart: function (newInterval) { pu_log("Calling restart"); maxCalls = originalMaxCalls; calls = 0; noChange = 0; reset_timer(newInterval || timerInterval); return; }, send: function() { pu_log("Explicit call to send"); getdata(true); return; }, stop: function () { pu_log("Calling stop"); maxCalls = -1; if(settings.cookie && $.cookie) { $.cookie(settings.cookie.name, null, settings.cookie); } return; } }; // Implement the tricky comparison logic ajaxSettings.success = function (data) { pu_log("Successful run! (In 'success')", 2); remoteData = data; }; ajaxSettings.complete = function (xhr, success) { pu_log("Status of call: " + success + " (In 'complete')", 2); if (success == "success" || success == "notmodified") { var rawData = $.trim(xhr.responseText); if (prevData == rawData) { if (autoStop > 0) { noChange++; if (noChange == autoStop) { handle.stop(); if (settings.autoStopCallback) { settings.autoStopCallback(noChange); } return; } } boostPeriod(); } else { noChange = 0; reset_timer(settings.minTimeout); prevData = rawData; if (settings.cookie) $.cookie(settings.cookie.name, prevData, settings.cookie); if (remoteData === null) { remoteData = rawData; } // jQuery 1.4+ $.ajax() automatically converts "data" into a JS Object for "type:json" requests now // For compatibility with 1.4+ and pre1.4 jQuery only try to parse actual strings, skip when remoteData is already an Object if ((ajaxSettings.dataType === 'json') && (typeof (remoteData) === 'string') && (success == "success")) { remoteData = JSON.parse(remoteData); } if (settings.success) { settings.success(remoteData, success, xhr, handle); } if (callback) { callback(remoteData, success, xhr, handle); } } } if (settings.complete) { settings.complete(xhr, success); } remoteData = null; }; ajaxSettings.error = function (xhr, textStatus) { pu_log("Error message: " + textStatus + " (In 'error')", 2); if(textStatus != "notmodified") { prevData = null; if(settings.cookie) $.cookie(settings.cookie.name, null, settings.cookie); reset_timer(settings.minTimeout); } if(settings.error) { settings.error(xhr, textStatus); } }; // Make the first call if (settings.runatonce) { pu_log("Executing a call immediately", 1); getdata(true); } else if($.cookie && $.cookie(settings.cookie.name)) { // Do nothing (already handled above) } else { pu_log("Enqueing a the call for after " + timerInterval, 1); reset_timer(timerInterval); } return handle; }; // The bound version $.fn.PeriodicalUpdater = function(url, options, callback) { var me = this; return $.PeriodicalUpdater(url, options, function() { return callback.apply(me, arguments); }); }; })(jQuery);