Return to Snippet

Revision: 42805
at March 10, 2011 22:49 by miohtama


Initial Code
/**
 * HTML5 offline manifest preloader.
 * 
 * Load all manifest cached entries, so that they are immediately available during the web app execution.
 * Display some nice JQuery progress while loading.
 * 
 * @copyright 2010 mFabrik Research
 * 
 * @author Mikko Ohtamaa <[email protected]>
 */

/**
 * Preloader class constructor.
 * 
 * Manifest is retrieved via HTTP GET and parsed.
 * All cache entries are loaded using HTTP GET.
 * 
 * Local storage attribute "preloaded" is used to check whether loading needs to be performed,
 * as it is quite taxing operation.
 * 
 * To debug this code and force retrieving of all manifest URLs, add reloaded=true HTTP GET query parameter:
 * 
 * 
 * 
 * @param {Function} endCallback will be called when all offline entries are loaded
 * 
 * @param {Object} progressMonitor ProgressMonitor object for which the status of the loading is reported.
 */
function ApplicationCachePreloader(endCallback, progressMonitor, debug) {
	
	if(!progressMonitor) {
		throw "progressMonitor must be defined";
	}
	
	this.endCallback = endCallback;
	this.progressMonitor = progressMonitor;
	this.logging = debug; // Flag to control console.log() output
	
	this.eventCount = 0;	
}

ApplicationCachePreloader.prototype = {
	
	translateEventCode : function(eventCode) {
		var cacheStatusValues = [];
		cacheStatusValues[0] = 'uncached';
		cacheStatusValues[1] = 'idle';
		cacheStatusValues[2] = 'checking';
		cacheStatusValues[3] = 'downloading';
		cacheStatusValues[4] = 'updateready';
		cacheStatusValues[5] = 'obsolete';
		return cacheStatusValues[eventCode];		
	},	
	
	trapEvents : function(cache, handler) {		
	
		cache.addEventListener('progress', handler, false);
		
		try {
			cache.addEventListener('cached', handler, false);
		} catch(e) {
			// FF 3.5 won't allow
		}
		
		cache.addEventListener('checking', handler, false);
		cache.addEventListener('downloading', handler, false);
		cache.addEventListener('error', handler, false);
		cache.addEventListener('noupdate', handler, false);
		cache.addEventListener('obsolete', handler, false);		
		cache.addEventListener('updateready', handler, false);		
	},
	
	
	waitUntilAllowed : function() {
		try {
			cache.addEventListener('cached', handler, false);
		} catch(e) {
			// FF 3.5 needs user permission "Allow" in pop-up dialog			
			this.debug("Not allowed yet");
			setTimeout(jQuery.proxy(this.waitUntilAllowed, this), 1000);	
		}
		this.start();		
	},
	
	deferEndCallback : function(success) {
		
		function timemout() {
			this.endCallback(success);
		}
				
		setTimeout(jQuery.proxy(timemout, this), 100);
	},
	
	/**
	 * Do everything necessary to set-up offline application
	 * 
	 * @return true if applicationCache loading performed succesfully
	 */
	load : function() {
		
		//alert("load");
		
		if (window.applicationCache) {
			this.debug("ApplicationCache status " + window.applicationCache.status);
			this.debug("Please see http://www.w3.org/TR/html5/offline.html#applicationcache");
		} else {
			// This could be probably worked around on Google Chrome using Gears extension somehow
			this.silentError("applicationCache object not supported");
			this.deferEndCallback(true);
			return true;
		}	
		
		// Can't update
		if(!navigator.onLine) {
			this.debug("Not online");
			this.deferEndCallback(true);			
			return true;			
		}		
		
		if(jQuery.browser.mozilla) {
			// Mozilla won't work until user answers "Allow" to pop-up dialog,
			// put dialog seems to be broken
			this.deferEndCallback(true);
			return false;
			//this.waitUntilAllowed();
		} else {			
			this.start();
		}		
		
		return true;								
	},
	
	start : function() {
		
		//alert("Started");
		
		this.debug("Preloading starting - xxxx");
		
		this.progressMonitor.start();
		
		this.trapEvents(window.applicationCache, jQuery.proxy(this.handleEvent, this));
		var cache = window.applicationCache;
		
		if(cache.status == 4) {
			this.debug("Update complete");
			this.end(true);
		}
		
		if (cache.status == 1) {
			this.debug("Idle - manifest not changed");
			this.end(true);
		}
		
		if (cache.status == 0) {
			this.debug("Uncached - offline mode not enabled (prolly not saved on desktop/mainscreen and running straight from the browser)");
			this.end(true);
		}
	
		//alert("Trapping");
		this.debug("Trapping and waiting events, status:"+ cache.status);
	},
	
	end : function(success) {
		//alert("Ended:" + success);
		
		// Trigger activation of new cache if available
		if(status) {
			if(!window.applicationCache) {
				// blaa... not supported
			} else {
				window.applicationCache.swapCache();
			}
			
		}
		
		this.debug("Preloading ending to " + success);
		this.progressMonitor.end(success);
		this.endCallback(success);
	},
	
	
	handleEvent : function(e) {
		
		var online, status, type, message;
		
		var cache = window.applicationCache;
		
    	online = (navigator.onLine) ? 'yes' : 'no';
    	status = this.translateEventCode(cache.status);
    	type = e.type;
    	message = 'online: ' + online;
    	message+= ', event: ' + type;
    	message+= ', status: ' + status;
    	if (type == 'error' && navigator.onLine) {
	        message+= ' (prolly a syntax error in manifest)';
	    }
	    
		// this.debug(message);
		// $("body").append("<p>" + message + "</p>");
		
		this.eventCount++;

		if(status == "downloading" || status == "checking") {
			this.progressMonitor.update(type, this.eventCount, this.eventCount);
		} else {
			console.log("Got event " + this.eventCount + " " + status);
			//alert("event:" + status);
		} 
	
		if(status == "error") {
			this.end(false);
		}
		
		// Google Chrome reports "uncached" when the application offline
		// mode is not enabled - allow still to run in the browser 
		// without doing resource caching
		
		if(status == "cached" || status == "updateready" || status == "idle" || status == "uncached") {
			this.end(true);
		}
	},
	
	/**
	 * Write to debug console
	 * 
	 * @param {String} msg
	 */
	debug : function(msg) {
		if(this.logging) {
			console.log(msg);
		}
	},
	
	/**
	 * Non-end user visible error message
	 *
	 * @param {Object} msg
	 */
	silentError : function(msg) {
		console.log(msg);
	}
};

function ProgressMonitor() {	
};

ProgressMonitor.prototype = {
	
	/**
	 * Start progress bar... initialize as 0 / 0
	 */
	start : function(status, coldVirgin) {		
		$("#web-app-updating").show();		
		
		if(window.navigator.standlone) {
			$("#web-app-updating .standalone").show();
		}
	},
	
	end: function(status) {		
		$("#web-app-updating").hide();
	},
	
	update : function(status, currentEntry, maxEntries) {		
		$("#web-app-updating .event-count").text(currentEntry);	
	},
	
	/**
	 * Called when application has not been saved on the desktop.
	 * 
	 * ...problems with Mobile Safari cache size.
	 */
	needWebAppMode : function() {
		$("#web-app-no-browser").show();	
	}
};

Initial URL


Initial Description


Initial Title
Application cache loading status reporter for HTML5 / Javascript

Initial Tags
javascript, cache, html5

Initial Language
JavaScript