From bedc7ca41b250487d24e0f748ed47d08365a5978 Mon Sep 17 00:00:00 2001 From: Sarah Mei Date: Mon, 22 Nov 2010 15:02:25 -0800 Subject: [PATCH] Made landing page and log in views content-type agnostic. Added an un-minified version of jquery-mobile for debugging. --- app/views/aspects/index.mobile.haml | 12 +- .../sessions/{new.html.haml => new.haml} | 0 app/views/home/{show.html.haml => show.haml} | 0 config/assets.yml | 1 - public/javascripts/custom-mobile-scripting.js | 14 +- public/javascripts/vendor/jquery_mobile_a2.js | 3541 +++++++++++++++++ 6 files changed, 3552 insertions(+), 16 deletions(-) rename app/views/devise/sessions/{new.html.haml => new.haml} (100%) rename app/views/home/{show.html.haml => show.haml} (100%) create mode 100644 public/javascripts/vendor/jquery_mobile_a2.js diff --git a/app/views/aspects/index.mobile.haml b/app/views/aspects/index.mobile.haml index 95c790844..51b700fe0 100644 --- a/app/views/aspects/index.mobile.haml +++ b/app/views/aspects/index.mobile.haml @@ -1,13 +1,9 @@ +-# Copyright (c) 2010, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + %div{:data => {:role => 'content'}} %div{:data => {:role => 'fieldcontain'}} =render 'shared/publisher', :aspect => @aspect = render 'shared/stream', :posts => @posts - -/%div{:data => {:role => 'footer', :id => 'footer-toolbar', :position => 'fixed'}} -/ %div{:data => {:role => 'navbar'}} -/ %ul -/ %li{:class => 'ui-btn-active'} -/ = link_to 'posts', '#' -/ %li -/ =link_to 'contacts', '#' diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.haml similarity index 100% rename from app/views/devise/sessions/new.html.haml rename to app/views/devise/sessions/new.haml diff --git a/app/views/home/show.html.haml b/app/views/home/show.haml similarity index 100% rename from app/views/home/show.html.haml rename to app/views/home/show.haml diff --git a/config/assets.yml b/config/assets.yml index 4a194cd3f..5a14264ae 100644 --- a/config/assets.yml +++ b/config/assets.yml @@ -17,7 +17,6 @@ javascripts: mobile: - public/javascripts/vendor/jquery144.min.js - public/javascripts/custom-mobile-scripting.js - - public/javascripts/rails.js - public/javascripts/vendor/jquery-ui-1.8.6.custom.min.js - public/javascripts/vendor/jquery_mobile_a2.min.js - public/javascripts/mobile.js diff --git a/public/javascripts/custom-mobile-scripting.js b/public/javascripts/custom-mobile-scripting.js index e435105d3..6d7583b6a 100644 --- a/public/javascripts/custom-mobile-scripting.js +++ b/public/javascripts/custom-mobile-scripting.js @@ -1,7 +1,7 @@ -$(document).bind("mobileinit", function(){ - $.extend( $.mobile , { - - ajaxLinksEnabled : false - ajaxFormsEnabled : false - }); -}); +// $(document).bind("mobileinit", function(){ +// $.extend( $.mobile , { +// +// ajaxLinksEnabled : false +// ajaxFormsEnabled : false +// }); +// }); diff --git a/public/javascripts/vendor/jquery_mobile_a2.js b/public/javascripts/vendor/jquery_mobile_a2.js new file mode 100644 index 000000000..6b9484d23 --- /dev/null +++ b/public/javascripts/vendor/jquery_mobile_a2.js @@ -0,0 +1,3541 @@ +/*! + * jQuery Mobile v1.0a2 + * http://jquerymobile.com/ + * + * Copyright 2010, jQuery Project + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + */ +/*! + * jQuery UI Widget @VERSION + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ +(function( $, undefined ) { + +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + $( elem ).triggerHandler( "remove" ); + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + $( this ).triggerHandler( "remove" ); + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} + +$.widget = function( name, base, prototype ) { + var namespace = name.split( "." )[ 0 ], + fullName; + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); + }; + + $[ namespace ] = $[ namespace ] || {}; + $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + var basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + namespace: namespace, + widgetName: name, + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); + + $.widget.bridge( name, $[ namespace ][ name ] ); +}; + +$.widget.bridge = function( name, object ) { + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = Array.prototype.slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.extend.apply( null, [ true, options ].concat(args) ) : + options; + + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + + if ( isMethodCall ) { + this.each(function() { + var instance = $.data( this, name ); + if ( !instance ) { + throw "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'"; + } + if ( !$.isFunction( instance[options] ) ) { + throw "no such method '" + options + "' for " + name + " widget instance"; + } + var methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, name ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, name, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + options: { + disabled: false + }, + _createWidget: function( options, element ) { + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); + this.element = $( element ); + this.options = $.extend( true, {}, + this.options, + this._getCreateOptions(), + options ); + + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); + + this._create(); + this._trigger( "create" ); + this._init(); + }, + _getCreateOptions: function() { + var options = {}; + if ( $.metadata ) { + options = $.metadata.get( element )[ this.widgetName ]; + } + return options; + }, + _create: function() {}, + _init: function() {}, + + destroy: function() { + this.element + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); + this.widget() + .unbind( "." + this.widgetName ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetBaseClass + "-disabled " + + "ui-state-disabled" ); + }, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.extend( {}, this.options ); + } + + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; + } + options = {}; + options[ key ] = value; + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) + .attr( "aria-disabled", value ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _trigger: function( type, event, data ) { + var callback = this.options[ type ]; + + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + data = data || {}; + + // copy original event properties over to the new event + // this would happen if we could call $.event.fix instead of $.Event + // but we don't have a way to force an event to be fixed multiple times + if ( event.originalEvent ) { + for ( var i = $.event.props.length, prop; i; ) { + prop = $.event.props[ --i ]; + event[ prop ] = event.originalEvent[ prop ]; + } + } + + this.element.trigger( event, data ); + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || + event.isDefaultPrevented() ); + } +}; + +})( jQuery ); +/* +* jQuery Mobile Framework : widget factory extentions for mobile +* Copyright (c) jQuery Project +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* Note: Code is in draft form and is subject to change +*/ +(function($, undefined ) { + +$.widget( "mobile.widget", { + _getCreateOptions: function() { + var elem = this.element, + options = {}; + $.each( this.options, function( option ) { + var value = elem.data( option.replace( /[A-Z]/g, function( c ) { + return "-" + c.toLowerCase(); + } ) ); + if ( value !== undefined ) { + options[ option ] = value; + } + }); + return options; + } +}); + +})( jQuery ); +/* +* jQuery Mobile Framework : support tests +* Copyright (c) jQuery Project +* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. +* Note: Code is in draft form and is subject to change +*/ +(function($, undefined ) { + +// test whether a CSS media type or query applies +$.media = (function() { + // TODO: use window.matchMedia once at least one UA implements it + var cache = {}, + $html = $( "html" ), + testDiv = $( "
" ), + fakeBody = $( "" ).append( testDiv ); + + return function( query ) { + if ( !( query in cache ) ) { + var styleBlock = $( "" ); + $html.prepend( fakeBody ).prepend( styleBlock ); + cache[ query ] = testDiv.css( "position" ) === "absolute"; + fakeBody.add( styleBlock ).remove(); + } + return cache[ query ]; + }; +})(); + +var fakeBody = $( "" ).prependTo( "html" ), + fbCSS = fakeBody[0].style, + vendors = ['webkit','moz','o'], + webos = window.palmGetResource || window.PalmServiceBridge, //only used to rule out scrollTop + bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB + +//thx Modernizr +function propExists( prop ){ + var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), + props = (prop + ' ' + vendors.join(uc_prop + ' ') + uc_prop).split(' '); + for(var v in props){ + if( fbCSS[ v ] !== undefined ){ + return true; + } + } +}; + +//test for dynamic-updating base tag support (allows us to avoid href,src attr rewriting) +function baseTagTest(){ + var fauxBase = location.protocol + '//' + location.host + location.pathname + "ui-dir/", + base = $("", {"href": fauxBase}).appendTo("head"), + link = $( "" ).prependTo( fakeBody ), + rebase = link[0].href; + base.remove(); + return rebase.indexOf(fauxBase) === 0; +}; + +$.extend( $.support, { + orientation: "orientation" in window, + touch: "ontouchend" in document, + cssTransitions: "WebKitTransitionEvent" in window, + pushState: !!history.pushState, + mediaquery: $.media('only all'), + cssPseudoElement: !!propExists('content'), + boxShadow: !!propExists('boxShadow') && !bb, + scrollTop: ("pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[0]) && !webos, + dynamicBaseTag: baseTagTest() +}); + +fakeBody.remove(); + +//for ruling out shadows via css +if( !$.support.boxShadow ){ $('html').addClass('ui-mobile-nosupport-boxshadow'); } + +})( jQuery );(function($, undefined ) { + +// add new event shortcuts +$.each( "touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) { + $.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + $.attrFn[ name ] = true; +}); + +var supportTouch = $.support.touch, + scrollEvent = "touchmove scroll", + touchStartEvent = supportTouch ? "touchstart" : "mousedown", + touchStopEvent = supportTouch ? "touchend" : "mouseup", + touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; + +// also handles scrollstop +$.event.special.scrollstart = { + enabled: true, + + setup: function() { + var thisObject = this, + $this = $( thisObject ), + scrolling, + timer; + + function trigger( event, state ) { + scrolling = state; + var originalType = event.type; + event.type = scrolling ? "scrollstart" : "scrollstop"; + $.event.handle.call( thisObject, event ); + event.type = originalType; + } + + // iPhone triggers scroll after a small delay; use touchmove instead + $this.bind( scrollEvent, function( event ) { + if ( !$.event.special.scrollstart.enabled ) { + return; + } + + if ( !scrolling ) { + trigger( event, true ); + } + + clearTimeout( timer ); + timer = setTimeout(function() { + trigger( event, false ); + }, 50 ); + }); + } +}; + +// also handles taphold +$.event.special.tap = { + setup: function() { + var thisObject = this, + $this = $( thisObject ); + + $this + .bind( touchStartEvent, function( event ) { + if ( event.which && event.which !== 1 ) { + return; + } + + var moved = false, + touching = true, + origPos = [ event.pageX, event.pageY ], + originalType, + timer; + + function moveHandler() { + if ((Math.abs(origPos[0] - event.pageX) > 10) || + (Math.abs(origPos[1] - event.pageY) > 10)) { + moved = true; + } + } + + timer = setTimeout(function() { + if ( touching && !moved ) { + originalType = event.type; + event.type = "taphold"; + $.event.handle.call( thisObject, event ); + event.type = originalType; + } + }, 750 ); + + $this + .one( touchMoveEvent, moveHandler) + .one( touchStopEvent, function( event ) { + $this.unbind( touchMoveEvent, moveHandler ); + clearTimeout( timer ); + touching = false; + + if ( !moved ) { + originalType = event.type; + event.type = "tap"; + $.event.handle.call( thisObject, event ); + event.type = originalType; + } + }); + }); + } +}; + +// also handles swipeleft, swiperight +$.event.special.swipe = { + setup: function() { + var thisObject = this, + $this = $( thisObject ); + + $this + .bind( touchStartEvent, function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : + event, + start = { + time: (new Date).getTime(), + coords: [ data.pageX, data.pageY ], + origin: $( event.target ) + }, + stop; + + function moveHandler( event ) { + if ( !start ) { + return; + } + + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : + event; + stop = { + time: (new Date).getTime(), + coords: [ data.pageX, data.pageY ] + }; + + // prevent scrolling + if ( Math.abs( start.coords[0] - stop.coords[0] ) > 10 ) { + event.preventDefault(); + } + } + + $this + .bind( touchMoveEvent, moveHandler ) + .one( touchStopEvent, function( event ) { + $this.unbind( touchMoveEvent, moveHandler ); + if ( start && stop ) { + if ( stop.time - start.time < 1000 && + Math.abs( start.coords[0] - stop.coords[0]) > 30 && + Math.abs( start.coords[1] - stop.coords[1]) < 75 ) { + start.origin + .trigger( "swipe" ) + .trigger( start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight" ); + } + } + start = stop = undefined; + }); + }); + } +}; + +(function($){ + // "Cowboy" Ben Alman + + var win = $(window), + special_event, + get_orientation, + last_orientation; + + $.event.special.orientationchange = special_event = { + setup: function(){ + // If the event is supported natively, return false so that jQuery + // will bind to the event using DOM methods. + if ( $.support.orientation ) { return false; } + + // Get the current orientation to avoid initial double-triggering. + last_orientation = get_orientation(); + + // Because the orientationchange event doesn't exist, simulate the + // event by testing window dimensions on resize. + win.bind( "resize", handler ); + }, + teardown: function(){ + // If the event is not supported natively, return false so that + // jQuery will unbind the event using DOM methods. + if ( $.support.orientation ) { return false; } + + // Because the orientationchange event doesn't exist, unbind the + // resize event handler. + win.unbind( "resize", handler ); + }, + add: function( handleObj ) { + // Save a reference to the bound event handler. + var old_handler = handleObj.handler; + + handleObj.handler = function( event ) { + // Modify event object, adding the .orientation property. + event.orientation = get_orientation(); + + // Call the originally-bound event handler and return its result. + return old_handler.apply( this, arguments ); + }; + } + }; + + // If the event is not supported natively, this handler will be bound to + // the window resize event to simulate the orientationchange event. + function handler() { + // Get the current orientation. + var orientation = get_orientation(); + + if ( orientation !== last_orientation ) { + // The orientation has changed, so trigger the orientationchange event. + last_orientation = orientation; + win.trigger( "orientationchange" ); + } + }; + + // Get the current page orientation. This method is exposed publicly, should it + // be needed, as jQuery.event.special.orientationchange.orientation() + special_event.orientation = get_orientation = function() { + var elem = document.documentElement; + return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape"; + }; + +})(jQuery); + +$.each({ + scrollstop: "scrollstart", + taphold: "tap", + swipeleft: "swipe", + swiperight: "swipe" +}, function( event, sourceEvent ) { + $.event.special[ event ] = { + setup: function() { + $( this ).bind( sourceEvent, $.noop ); + } + }; +}); + +})( jQuery ); +/*! + * jQuery hashchange event - v1.3 - 7/21/2010 + * http://benalman.com/projects/jquery-hashchange-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Script: jQuery hashchange event +// +// *Version: 1.3, Last updated: 7/21/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and +// robust, there are a few unfortunate browser bugs surrounding expected +// hashchange event-based behaviors, independent of any JavaScript +// window.onhashchange abstraction. See the following examples for more +// information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// Also note that should a browser natively support the window.onhashchange +// event, but not report that it does, the fallback polling loop will be used. +// +// About: Release History +// +// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more +// "removable" for mobile-only development. Added IE6/7 document.title +// support. Attempted to make Iframe as hidden as possible by using +// techniques from http://www.paciellogroup.com/blog/?p=604. Added +// support for the "shortcut" format $(window).hashchange( fn ) and +// $(window).hashchange() like jQuery provides for built-in events. +// Renamed jQuery.hashchangeDelay to and +// lowered its default value to 50. Added +// and properties plus document-domain.html +// file to address access denied issues when setting document.domain in +// IE6/7. +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // Reused string. + var str_hashchange = 'hashchange', + + // Method / object references. + doc = document, + fake_onhashchange, + special = $.event.special, + + // Does the browser support window.onhashchange? Note that IE8 running in + // IE7 compatibility mode reports true for 'onhashchange' in window, even + // though the event isn't supported, so also test document.documentMode. + doc_mode = doc.documentMode, + supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || location.href; + return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Method: jQuery.fn.hashchange + // + // Bind a handler to the window.onhashchange event or trigger all bound + // window.onhashchange event handlers. This behavior is consistent with + // jQuery's built-in event handlers. + // + // Usage: + // + // > jQuery(window).hashchange( [ handler ] ); + // + // Arguments: + // + // handler - (Function) Optional handler to be bound to the hashchange + // event. This is a "shortcut" for the more verbose form: + // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, + // all bound window.onhashchange event handlers will be triggered. This + // is a shortcut for the more verbose + // jQuery(window).trigger( 'hashchange' ). These forms are described in + // the section. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements. + + // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and + // $(elem).hashchange() for triggering, like jQuery does for built-in events. + $.fn[ str_hashchange ] = function( fn ) { + return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); + }; + + // Property: jQuery.fn.hashchange.delay + // + // The numeric interval (in milliseconds) at which the + // polling loop executes. Defaults to 50. + + // Property: jQuery.fn.hashchange.domain + // + // If you're setting document.domain in your JavaScript, and you want hash + // history to work in IE6/7, not only must this property be set, but you must + // also set document.domain BEFORE jQuery is loaded into the page. This + // property is only applicable if you are supporting IE6/7 (or IE8 operating + // in "IE7 compatibility" mode). + // + // In addition, the property must be set to the + // path of the included "document-domain.html" file, which can be renamed or + // modified if necessary (note that the document.domain specified must be the + // same in both your main JavaScript as well as in this file). + // + // Usage: + // + // jQuery.fn.hashchange.domain = document.domain; + + // Property: jQuery.fn.hashchange.src + // + // If, for some reason, you need to specify an Iframe src file (for example, + // when setting document.domain as in ), you can + // do so using this property. Note that when using this property, history + // won't be recorded in IE6/7 until the Iframe src file loads. This property + // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 + // compatibility" mode). + // + // Usage: + // + // jQuery.fn.hashchange.src = 'path/to/file.html'; + + $.fn[ str_hashchange ].delay = 50; + /* + $.fn[ str_hashchange ].domain = null; + $.fn[ str_hashchange ].src = null; + */ + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // HTML5 window.onhashchange event is used, otherwise a polling loop is + // initialized, running every milliseconds to + // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 + // compatibility" mode), a hidden Iframe is created to allow the back button + // and hash-based history to work. + // + // Usage as described in : + // + // > // Bind an event handler. + // > jQuery(window).hashchange( function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).hashchange(); + // + // A more verbose usage that allows for event namespacing: + // + // > // Bind an event handler. + // > jQuery(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).trigger( 'hashchange' ); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one handler + // is actually bound to the 'hashchange' event. + // * If you need the bound handler(s) to execute immediately, in cases where + // a location.hash exists on page load, via bookmark or page refresh for + // example, use jQuery(window).hashchange() or the more verbose + // jQuery(window).trigger( 'hashchange' ). + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a DOM ready handler. + + // Override existing $.event.special.hashchange methods (allowing this plugin + // to be defined after jQuery BBQ in BBQ's source code). + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function(){ + var self = {}, + timeout_id, + + // Remember the initial hash so it doesn't get triggered immediately. + last_hash = get_fragment(), + + fn_retval = function(val){ return val; }, + history_set = fn_retval, + history_get = fn_retval; + + // Start the polling loop. + self.start = function() { + timeout_id || poll(); + }; + + // Stop the polling loop. + self.stop = function() { + timeout_id && clearTimeout( timeout_id ); + timeout_id = undefined; + }; + + // This polling loop checks every $.fn.hashchange.delay milliseconds to see + // if location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + function poll() { + var hash = get_fragment(), + history_hash = history_get( last_hash ); + + if ( hash !== last_hash ) { + history_set( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + location.href = location.href.replace( /#.*/, '' ) + history_hash; + } + + timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); + }; + + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + $.browser.msie && !supports_onhashchange && (function(){ + // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 + // when running in "IE7 compatibility" mode. + + var iframe, + iframe_src; + + // When the event is bound and polling starts in IE 6/7, create a hidden + // Iframe for history handling. + self.start = function(){ + if ( !iframe ) { + iframe_src = $.fn[ str_hashchange ].src; + iframe_src = iframe_src && iframe_src + get_fragment(); + + // Create hidden Iframe. Attempt to make Iframe as hidden as possible + // by using techniques from http://www.paciellogroup.com/blog/?p=604. + iframe = $('