1 /**
  2  * @fileoverview Pure Javascript mobilization solution
  3  * 
  4  * @module mobilize.core
  5  *   
  6  */
  7 
  8 /**
  9  * @namespace Mobilization core
 10  * <p>
 11  * <i>mobilize</i> is a core class of mobilize.js. 
 12  * <p>
 13  * You only want to use this class directly if you are developing mobilization
 14  * for your own framework. Otherwise use one of the stock CMS classes supplied with
 15  * mobilize.js. 
 16  * <p>
 17  * mobilize does not need to be instiated and it's a static singleton. Instead,
 18  * the functionality is extended by an <i>extender</i>. 
 19  * Extender is a Javascript which directly overrides mobilize member functions
 20  * in mobilize namespace.
 21  * <p> 
 22  * @author Mikko Ohtamaa, Jussi Toivola
 23  *
 24  */
 25 var mobilize = {
 26 	
 27 	/**
 28 	 * @class 
 29 	 * 
 30 	 * <p>
 31 	 * Options and their default values.
 32 	 * <p>
 33 	 * These default values can be overriden by extender getExtendedOptions()
 34 	 * or user supplied parameters to init().
 35 	 * <p>
 36 	 * 
 37 	 * 
 38 	 * @see mobilize.init
 39 	 * 
 40 	 * @see mobilize.getExtendedOptions
 41 	 */
 42 	options : {
 43                 
 44 		/**
 45 		 * You need to set this value in order to 
 46 		 * cache mobile page template in localStorage.
 47 		 * <p>
 48 		 * Every time template is upgraded this values must
 49 		 * be changed within your construction script tag.	
 50 		 * <p>
 51 		 * If value is null caching is not used.
 52 		 * <p>
 53 		 * @default null		 
 54 		 */
 55 	    templateCacheVersion : null,
 56 	    
 57 	    /**
 58 	     * 
 59 	     * <script src=""> whitelist for filtering web specific 
 60          * elements from <head>
 61          * <p>
 62 	     * If src attribute has substring match of any list element,
 63 	     * the tag is left to mobile version also. 
 64 	     * <p>
 65 	     * @default allow scripts which have mobilize in their name.
 66 	     */
 67 	    whitelistScriptSrc : ["mobilize"],
 68 	    
 69 	    /** 
 70 	     * <style type="text/css> @import whitelist for filtering web specific 
 71          * elements from <head>
 72          * <p>
 73          * If CSS @import content has substring match of any list element,
 74          * the tag is left to mobile version also. 
 75          * <p>	     
 76          * 
 77          * @see mobilize.options.inlineStyleMaxCheckLength
 78          * 
 79          * @default empty list
 80 	     */
 81 	    whitelistStyleImport : [],
 82 	    
 83 	    // <link rel="stylesheet"> href whitelist 
 84 	    whitelistCSSLinks : [],
 85 	    
 86 	    // Which template file to use - relative file or URL
 87 	    template : "template.html",
 88 	    
 89 	    /** How many characters <style> inner text may contain it to be run through inline CSS importer check */
 90 	    inlineStyleMaxCheckLength : 256,
 91 	    
 92 	    // Go always with mobile rendering path (useful for testing)
 93 	    forceMobilize : false,
 94 	    
 95 	    // Force user agent
 96 	    forceUserAgent : null,
 97 	    
 98 	    // Which URL load jQuery from.
 99 	    // Default to Google CDN version.
100 	    //jQueryURL : "http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js",
101 	    //jQueryURL : "http://code.jquery.com/jquery-1.5.1.min.js",
102 	    
103 	    // TODO: Add cdn.mobilizejs.com URL
104 	    jQueryURL : null,
105 	     
106 	     // Which HTTP GET parameter we can use to forc mobilization
107 	    mobilizeQueryParameter : "mobilize"
108     },
109 
110     /** Async flag indicating that jQuery Mobile has been loaded */
111     jQueryMobileLoaded : false,
112 
113     /** Async flag indicating that mobile page transform is complete */
114     transformComplete : false,
115 
116 	
117 	/**
118 	 * Initialize mobilize class.
119 	 * 
120 	 * <h2>Options<h2>
121 	 * 
122 	 * <table><tbody>
123 	 * 
124 	 * <tr><th>resourceWhitelist</th>
125 	 * <td>String tags which mark head tag JS and CSS resources not to be purged</td></tr>
126 	 * 
127 	 * 
128 	 * @static
129 	 * 
130 	 * @param options 
131 	 */
132 	init : function(options) {
133 	    	    
134 	    // Override default parameters with user supplied versions
135 	    
136 	    if(!options) {
137 	        options = {};
138 	    }
139 		
140 
141         // Extend global options with subclass supplied ones
142         var extendedOptions = mobilize.getExtendedOptions();
143 		mobilize.extend(mobilize.options, extendedOptions);
144 		
145 		// Extend global options with user supplied ones
146 		mobilize.extend(mobilize.options, options);
147 		
148 		if(!mobilize.options.jQueryURL) {
149 			throw "options.jQueryURL must be given to init()";
150 		}
151 		
152 	},
153 	
154 	/**
155 	 * Simple shallow copy from an object to another.
156 	 * <p>
157 	 * 
158 	 * @tag utility
159 	 * 
160 	 * @param {Object} target Javascript object to receive new members
161 	 * @param {Object} source Javascript object to source new members
162 	 */
163 	extend : function(target, source) {
164         for(name in source) {
165             var val = source[name];
166             target[name] = val;
167         }   		
168 	},
169 	
170 	/**
171 	 * Return plug-in specific options overrides
172 	 * 
173 	 * @static
174 	 * 
175 	 * @returns Object containing mobilize options to override
176 	 */
177 	getExtendedOptions : function() {
178 		return {}
179 	},
180 	
181     /**
182      * Entry point to mobilize.js machinery.
183      * <p>
184      * Stop loading current HTML resources, start async processes
185      * to get the page mobilized.
186      * 
187      * @static
188      */
189     bootstrap : function() {
190         
191 		mobilize.log("Bootstrap");
192         if(mobilize.checkMobileBrowser(mobilize.options)) {
193             mobilize.enableMobileRendering();
194         } else {
195 			mobilize.log("Web mode wanted");
196 		}
197     },
198 
199 	
200 	/** 
201 	 * Utility for internal debug logging 
202 	 * 
203 	 * @param msg: message to log
204 	 * */
205 	log : function(msg) {
206 		if(window.console) {
207 			if(console.log) {
208 				console.log(msg);
209 			}
210 		}
211 	},
212 	
213 	/** 
214 	 * <p>Get baseurl from url by ignoring file and url parameters</p> 
215 	 * 
216 	 * <b>Example</b>
217 	 * <pre>
218 	 * mobilize.baseurl(window.location.href)
219 	 * </pre>
220 	 * @param url : Url to parse
221 	 * 
222 	 * */
223 	baseurl : function (aUrl) {
224 		
225 		var end;
226 		var url;
227 		
228 		end = aUrl.indexOf('?');
229 		
230 		if(end <= 0) {
231 			end = aUrl.length-1;
232 		}
233 		
234 		url = aUrl.slice(0, end);
235 		// Ignore slash at the end of url
236 		if(url[url.length-1] == "/" ) {
237 			url = url.slice(0,url.length-2);
238 		}
239 		
240 		// But add the slash to result for convenient concat
241 		end = url.lastIndexOf("/") + 1;
242 		url = url.slice(0,end);
243 		
244 		return url;
245 	},
246 	
247 	/** Make a function call and report possible exceptions back to a centralized server.
248 	 * 
249 	 * We use this to track problems with possible not-so-well-implemented mobile browsers.
250 	 */
251 	callWithErrorReporting : function(func) {
252 	    try {
253 	        func();
254 	    } catch(e) {
255 	        // 
256 	    }
257 	},
258 	    
259 	/** 
260 	 * Read URL parameters to dict.
261 	 * 
262 	 * See: http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html
263 	 */
264 	getUrlVars : function ()
265 	{
266 		// Cache this call results
267 		if(this._urlvars) {
268 			return this._urlvars;
269 		}
270 	    var vars = [], hash;
271 	    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
272 	    
273 	    for(var i = 0; i < hashes.length; i++)
274 	    {
275 	        hash = hashes[i].split('=');
276 	        vars.push(hash[0]);
277 	        vars[hash[0]] = hash[1];
278 	    }
279 	    
280 	    this._urlvars = vars;
281 	    return vars;
282 	},
283 
284 	/** 
285 	 * See: http://www.quirksmode.org/js/cookies.html 	
286 	 */
287 	createCookie : function(name,value,days) {
288 		if (days) {
289 			var date = new Date();
290 			date.setTime(date.getTime()+(days*24*60*60*1000));
291 			var expires = "; expires="+date.toGMTString();
292 		}
293 		else var expires = "";
294 		document.cookie = name+"="+value+expires+"; path=/";
295 	},
296 	
297 	/** 
298 	 * See: http://www.quirksmode.org/js/cookies.html 	
299 	 */
300 	readCookie : function(name) {
301 		var nameEQ = name + "=";
302 		var ca = document.cookie.split(';');
303 		for(var i=0;i < ca.length;i++) {
304 			var c = ca[i];
305 			while (c.charAt(0)==' ') c = c.substring(1,c.length);
306 			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
307 		}
308 		return null;
309 	},
310 	/** 
311 	 * See: http://www.quirksmode.org/js/cookies.html 	
312 	 */
313 	eraseCookie : function(name) {
314 		createCookie(name,"",-1);
315 	},
316 	
317 	/** Check if browser is running on mobile platform
318 	 * 
319 	 * @param userAgent   = userAgent name. Uses browser's userAgent by default
320 	 *  
321 	 * @param forceMobilize = Force detection to mobile to true or false regardless of real type
322 	 * 
323 	 * URL parameter mobilize=<true,1> can also be used to force mobile.
324 	 * 
325 	 * The state is also stored to 'mobilize-mobile' cookie this
326 	 * information is passed to server for the following requests. 
327 	 * URL and options.force paremeters 
328 	 * override cookie and detection.
329 	 * 
330 	 * @return: true if browser is considered as mobile browser.
331 	 * 
332 	 * @see: http://detectmobilebrowser.com/ for the detection code.
333 	 */ 
334 	checkMobileBrowser : function (opts)
335 	{
336 		var forced;
337 		var name;
338 		if(!opts) {
339 			opts = {};
340 		}
341 		
342 		// Using cookie by default
343 		// forced = mobilize.readCookie("mobilize-mobile");
344 
345         // For user agent testing
346 		name = opts.forceUserAgent;
347 		
348 		// Note: URL parameter and option overrides cookie		
349 		// Get URL var to mobilize page
350 		forced = mobilize.getUrlVars()[mobilize.options.mobilizeQueryParameter];
351 		
352 		// Javascript option to always render in mobile mode
353 		if(opts.forceMobilize) {
354 			forced = true;
355 		}
356 		
357 		if(forced !== undefined && forced !== null ) {
358 			result = (forced == "true" || forced == "1" || forced === true || forced === 1);
359 		}
360 		else {
361 			if(!name) {
362 				name = (navigator.userAgent || navigator.vendor || window.opera);
363 			}
364 			
365 			result = (function(a) {
366 				if (/android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
367 						.test(a)
368 						|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i
369 								.test(a.substr(0, 4)))
370 					return true;
371 				else
372 					return false;
373 			})(name);
374 		}
375 		
376 		// Update cookie
377 		var cookie = result ? "1" : "0";
378 		mobilize.createCookie("mobilize-mobile", cookie);
379 		
380 		return result;
381 	},
382 	    	
383 	/**
384 	 * 
385 	 * Stop loading all web page resources until mobile template is properly placed
386 	 * and template transformation has taken place.
387 	 * 
388 	 */
389 	enableMobileRendering : function() {
390 	    
391 		mobilize.log("Enabling mobile rendering");
392 		
393 	    this.suspendRendering();
394 	    
395 	    this.cleanHead();
396 	    
397 		mobilize.log("Syncronous boostrap done");
398 	    
399 		// We cannot directly load template, since <body> has not been constructed
400 	    var self = this;
401 		
402 		function onJQueryLoad() {
403 			mobilize.log("jQuery load success");
404 			
405 			if(!jQuery) {
406 				throw "jQuery object construction failed";
407 			}
408 			
409 			jQuery(document).ready(function() { self.loadMobileTemplate(); } );
410 			
411 			// TODO: Handle case when document is already ready in this point
412 		}
413 		
414 		// Clear conflicting jQuery objects
415 		// - if two jQuery instances are loaded then event handlers do not function 
416 		// properly
417 		
418 		if(window.jQuery !== undefined) {
419 			delete window.jQuery;			
420 		}
421 		
422 		if(window.$ !== undefined) {
423 			delete window.$;		
424 		}
425 		
426 		mobilize.loadScript(mobilize.options.jQueryURL, onJQueryLoad);
427 	    //
428 	},
429 
430     /**
431      * Helper function to do AJAXy requests before jQuery has been loaded.
432      * 
433      * @param {String} url
434      * 
435      * @param callback(payload)
436      */
437     getAJAX : function(url, callback) {
438 		var req = new XMLHttpRequest();
439         req.open('GET', url, true);
440         req.onreadystatechange = function (aEvt) {
441            if(req.readyState == 4) {
442 		       if (req.status == 200) {
443 			   	callback(req.responseText);
444 			   }  else {
445 			   	mobilize.log("Could not AJAX url:" + url + " got status:" + req.status);
446 			   }
447 		   }
448 	    };
449         req.send(null);
450 	},
451 	
452 	/**
453 	 * Magical script loader.
454 	 * 
455 	 * Use AJAX to load Javascript code, then eval() it.
456 	 * This ensures that code is executed (not just loaded)
457 	 * when triggering the callback.
458 	 * 
459 	 * http://blog.client9.com/2008/11/javascript-eval-in-global-scope.html
460 	 * 
461 	 * @param {String} url
462 	 * 
463 	 * @param {Object} callbacl
464 	 */
465 	loadScript : function(url, callback) {
466         
467 		function loaded(javascript) {
468 			mobilize.log("Loaded payload for " + url + ", now evaling() it ");
469 			eval.call(null, javascript);
470 			callback();
471 		}
472 		
473 		mobilize.getAJAX(url, loaded);
474 	},
475 		
476 	/**
477 	 * Check if a given link is on resource whitelist.
478 	 * 
479 	 * @param src URL
480 	 * 
481 	 * @param list List of substring matches. If matches do not remove the element.
482 	 * 
483 	 * @returns true if the src string has substring match of any list element
484 	 */
485 	checkResourceWhitelist : function(src, list) {
486         for(var i=0; i<list.length; i++) {
487             var matcher = list[i];
488 			if(src.indexOf(matcher) >= 0) {
489 				return true;
490 			}
491         }	
492 		return false;
493 	},
494 	
495 	/**
496 	 * Remove unnecessary script tags if not needed for mobile.
497 	 * 
498 	 * Use options.resourceWhitelist matching.
499 	 */
500 	cleanJavascript : function() {
501 		
502 		mobilize.log("Cleaning <script>s");
503 		
504 		var tags = document.getElementsByTagName("script");
505 
506 		for(var i=0; i<tags.length; i++) {
507 		    var script = tags[i];
508 			var src = script.getAttribute("src");
509 			
510 			if(!src) {
511 				// TODO: Inline script
512 				continue;
513 			}
514 			
515 		    if(!mobilize.checkResourceWhitelist(src, mobilize.options.whitelistScriptSrc)) {
516 				mobilize.log("Cleaning script tag");
517 				mobilize.log(script);
518 				var parent = script.parentNode;
519 				parent.removeChild(script);
520 			}
521 		}
522 	},
523 
524     /**
525      * Remove web only <link rel="stylesheet"> tags
526      * 
527      */
528     cleanCSSLink : function() {
529         
530         mobilize.log("Cleaning <link rel='stylesheet'>s");
531         
532         var tags = document.getElementsByTagName("link");
533 
534         for(var i=0; i<tags.length; i++) {
535             script = tags[i];
536 			
537 			var rel = script.getAttribute("rel");
538 			if(rel != "stylesheet") {
539 				continue;
540 			}          
541 			
542             var src = script.getAttribute("href");
543             if(!mobilize.checkResourceWhitelist(src, mobilize.options.whitelistCSSLinks)) {
544                 var parent = script.parentNode;
545                 parent.removeChild(script);
546             }
547         }
548     },
549 
550     /**
551      * Remove unnecessary CSS links from <head> if not needed for mobile.
552      * 
553      * Supports syntaxes
554      * 
555      * <style type="text/css">
556      *  @import url(http://plone.org/portal_css/Sunburst%20Theme/newplone-cachekey7531.css);
557      * </style>
558      * 
559      * Use options.resourceWhitelist matching.
560      */	
561 	cleanCSSStyle : function() {
562 		
563 		mobilize.log("Cleaning <style>s");
564 		
565 		var tags = document.getElementsByTagName("style");
566 		
567 		for(var i=0; i<tags.length; i++) {
568 		  var style = tags[i];
569 		  
570 		  // http://bytes.com/topic/javascript/answers/600139-get-file-name-style-tag
571 
572           function remove() {
573 		      var parent = style.parentNode;
574 			  parent.removeChild(style);	
575 		  }
576 		  
577           var text = style.textContent;
578 		  
579 		  if(!text) {
580 		  	mobilize.log("Interesting style node:");
581 		  	mobilize.log(style);
582 		  	continue;
583 		  }
584           
585           // Make sure we don't start searching through very long
586 		  // inline CSS
587 		  if(text.length < mobilize.options.inlineStyleMaxCheckLength) {
588 		  	
589 			// This is inline CSS import
590 			var matches = text.match(/@import url\(.*\);?/mg);
591 			
592 		  	if(matches != null) {
593 			  	for(var i=0; i<matches.length; i++) {
594 					if(!mobilize.checkResourceWhitelist(matches[i], mobilize.options.whitelistStyleImport)) {
595 						remove();
596 						break;
597 					}
598 				} 
599 		  	}
600 		  } else {
601 			// too long CSS snippet, drop unconditionally
602 			remove();
603 		  }
604 		}
605 	},
606 
607     /**
608      * Stop loading Javascripts and CSS we do not need in mobile mode.
609      */
610     cleanHead : function() {
611         mobilize.cleanJavascript();    
612 		mobilize.cleanCSSLink();    
613         mobilize.cleanCSSStyle();    
614 
615     },
616 
617 
618     /**
619      * Make sure the browser does not load anything extra before mobile transform has taken place
620      */
621 	suspendRendering : function() {
622     	
623 		mobilize.log("Suspending page rendering");
624 		
625 	    if(!document.body) {
626 	        // DOM tree loading, couldn't get hang off it
627 	        throw "Could not find body while loading?";
628 	    }
629 	    
630 	    document.body.style.display = "none";
631 	},
632 	
633 	/**
634 	 * Must be called before template loading,
635 	 * as immediately when jQuery Mobile script tag is inserted to DOM,
636 	 * some of its event handlers are run.
637 	 */
638 	bindTemplateEventHandlers : function() {
639 		 // Assign jQuery Mobile event handlers 
640         $(window.document).bind("mobileinit", mobilize.onMobileInit);
641 	},
642 	
643 	/**
644 	 * Start loading mobile template to DOM tree.
645 	 * 
646 	 * Check possible mobile template cache places.
647 	 */
648 	loadMobileTemplate : function() {
649 	    
650 	    var self = this;
651 	    
652 	    // Create the element which will hold the mobile template
653 	    // + transformation result
654 	    $("body").append("<div id='mobile-template-holder'>");
655 	    
656 		mobilize.bindTemplateEventHandlers();
657 		
658 	    $("#mobile-template-holder").load(mobilize.options.template, function() {
659 	        self.transform();
660 	    });
661 	},
662 	
663 	/**
664 	 * Put mobile template to DOM tree
665 	 */
666 	prepareMobileTemplate : function() {
667 	    
668 	},
669 	
670 	/**
671 	 * Get rid of mobile template
672 	 */
673 	closeMobileTemplate : function() {
674 	},
675 	
676 	
677 	prepareTransform : function() {
678 
679 	    if(!jQuery) {
680             throw "jQuery needed in order to run content transform";
681         }
682     	
683 	},
684 	
685 	/**
686 	 * Transform the web page content to mobile frame.
687 	 * 
688 	 * Subclasses must override this.
689 	 * 
690 	 * After the function has been finished mobilize.completeTransform() must
691 	 * be called to allow async handlers to proceed. 
692 	 */
693 	transform : function() {
694         mobilize.constructHead();		    
695 		mobilize.constructBody();
696 	    mobilize.completeTransform();
697 	},
698 	
699 	/**
700 	 * We can proceed with the page visual enhancements
701 	 */
702 	completeTransform : function() {
703 		mobilize.transformComplete = true;
704 		mobilize.prepareFinish();
705 	},
706 	
707 	/**
708 	 * @param href Link as a string
709 	 * 
710 	 * @returns New link target as string or null if the link should be removed 
711 	 */
712 	rewriteLinkTarget : function(href) {
713 	   return href;
714 	},
715 
716     /**
717      * Based on mobilize options, rewrite link targets with mobile ones 
718      * or hide links.
719      * 
720      * @param callback to be called if the link is to be removed
721      */
722     remapLinks : function(selection, removeCallback) {
723 		selection.each(function() {
724 			 var input = $(this);
725 			 output = mobilize.rewriteLink(input);
726 			 if(!output) {
727 			 	if(removeCallback) {
728 					removeCallback(input);
729 				}
730 			 }
731 		});
732 	},
733 	
734 	/**
735 	 * 
736 	 * @param {Object} a DOM node or jQuery object of <a>
737 	 * 
738 	 * @returns null if the link is to be discarded
739 	 */
740 	rewriteLink : function(a) {
741 		var a = $(a);
742 		
743 		var href = a.attr("href");
744 	
745 	    if(!href) {
746             // Not a link
747             return null;  
748         } 
749           
750 		href = mobilize.rewriteLinkTarget(href);
751 		a.attr("href", href);
752 		
753 		return a;
754 	},
755    
756 	/**
757 	 * Create <head> section of a mobile rendered version.
758 	 * 
759 	 * The default transform is just to copy everything
760 	 * in #mobile-head from template to <head> of the page.
761 	 */
762 	constructHead : function() {    
763 	    mobilize.log("constructHead");
764 	    $("head").append($("#mobile-head").children());
765 	    // Make events to be fired when each CSS/Javascript has been loadeds
766 	},
767 	
768 	
769 	/**
770 	 * Create jQuery Mobile navigation links out of arbitary link list.
771 	 * 
772 	 * Creates navigation or news box from existing jQuery content selection.
773 	 * The selection can be list or arbitary elements or list of a a hrefs.
774 	 * 
775 	 * @param {Object} selection jQuery selection which to transform
776 	 * 
777 	 * @param title If present add a link box with a title using ui-list
778 	 * 
779 	 * @returns Constructed jQuery tree, ready to place to the document
780 	 */
781 	createNavigationBox : function(selection, title, outputter) {
782 		
783 		var list;
784 
785 		list = $("<ul data-inset='true' data-role='listview'>");
786 		selection.each(function() {
787 			 
788 			 var input = $(this);
789 			 var a;
790 			 var contentish;
791 			 
792 			 mobilize.log("Creating navigation box link " + this);
793 			 
794 			 // We can be iterating through <a> or <li> element
795 			 if(this.tagName.toLowerCase() == 'a') {
796                 a = input;         
797 				contentish = false;          			 	
798 			 } else {
799 			 	var content = input;
800 				
801 				// Assume we have 0 or 1 links in the content HTML
802 			 	a = content.find("a");
803 				if(a.size() == 0) {
804 					a = null;
805 				}
806 				contentish = true;
807 			 }
808 			 
809 			 if (a) {
810 			 	var a = mobilize.rewriteLink(a);
811 			 }
812 			 
813 			 if (outputter) {
814 			     outputter(list, input, a);	 
815 			 } else {
816 			 	
817 				// Create normal bulleted lists
818 				var output = $("<li role='option'>");
819 				
820 			 	if (a) {
821 			 		output.append(a).appendTo(list);
822 			 	}
823 			 	
824 			 	if (contentish) {
825 			 		// Format link content
826 					output.appendTo(content.children());
827 				}
828 			}
829 		});
830 		
831 		if(title) {
832 			list.prepend("<li data-role='list-divider'>" + title + "</li>");
833 		}
834 		
835 		return list;
836 	},
837 	
838 	/**
839 	 * Create <body> section of a mobile rendered version.
840 	 * 
841 	 * This transformation is always CMS specific 
842 	 * and your subclass must override this function.
843 	 */
844 	constructBody : function() {        
845 	},
846 	
847 	/**
848 	 * Make the transformed mobile template body visible and remove the other body data.
849 	 */
850 	swapBody : function() { 
851 	    var mobileBody = $("#mobile-body").detach();
852 	    $("body").empty();
853 	    $("body").append(mobileBody.children());
854 	},
855 	
856 	/**
857 	 * Check that all async conditions have been completed allowing us to finish the page.
858 	 */
859 	prepareFinish : function() {
860 		
861 		mobilize.log("prepareFinish()");
862 		if(!mobilize.jQueryMobileLoaded) {
863 			mobilize.log("Waiting for jQuery Mobile to load");			
864 		}
865 		
866 		if(!mobilize.transformComplete) {
867 			mobilize.log("Waiting transform() to complete");
868 		}
869 		
870 		if(mobilize.jQueryMobileLoaded && mobilize.transformComplete) {
871 			mobilize.finish();
872 		}
873 	},
874 
875 	/**
876 	 * Mobile transformation is done. Show mobile site to the user.
877 	 */
878 	finish : function() {
879 	   
880 	    this.swapBody();
881 	   
882         // Draw jQuery Mobile chrome
883 		try{
884 			$.mobile.initializePage();
885 		}catch(e){
886 			mobilize.log("mobilize::finish initializePage failed>" + e);
887 		}
888 
889         // Show constructed page to the user
890 	    $("body").show();
891 		
892 		// Execute handlers which can be run
893 		// after jQuery Mobile has completed its internal transforms
894 		mobilize.log("Triggering mobilizefinish");
895 		//$(document).trigger("mobilizefinish");
896 
897         mobilize.bindEventHandlers();
898 	},
899 	
900 	/**
901 	 * jQuery mobile initializer handler 
902 	 * 
903 	 * @param {Object} e
904 	 */
905 	onMobileInit : function(e) {
906 		
907 		// We'll manage our own workflow and don't want to jQuery Mobile
908 		// start doing things instantly when the script is loaded
909 		mobilize.log("Disabling autoInitialize, was:" + $.mobile.autoInitialize);
910 		$.mobile.autoInitialize = false;
911 		$.mobile.ajaxEnabled = false;
912 		
913 		mobilize.jQueryMobileLoaded = true;
914 		mobilize.prepareFinish();
915 		
916 	},
917 	
918 	/**
919 	 * Subclass may override.
920 	 * 
921 	 * This is called after jQuery Mobile has been set up.
922 	 * You can now attach event handlers to jQuery UI elements.
923 	 * 
924 	 */
925 	bindEventHandlers : function() {
926 		
927 	}
928 };
929 
930 if(typeof(exports) !== "undefined")
931 	exports.mobilize = mobilize;
932