|
| 1 | +//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| 2 | +//>>description: placeholder |
| 3 | +//>>label: AJAX Navigation System |
| 4 | +//>>group: Navigation |
| 5 | +define([ |
| 6 | + "jquery", |
| 7 | + "./../jquery.mobile.core", |
| 8 | + "./../jquery.mobile.support", |
| 9 | + "./events/navigate", |
| 10 | + "./path"], function( $ ) { |
| 11 | +//>>excludeEnd("jqmBuildExclude"); |
| 12 | + |
| 13 | +(function( $, undefined ) { |
| 14 | + var path = $.mobile.path, history; |
| 15 | + |
| 16 | + // TODO consider queueing navigation activity until previous activities have completed |
| 17 | + // so that end users don't have to think about it. Punting for now |
| 18 | + $.navigate = function( url, data ) { |
| 19 | + var href, state, |
| 20 | + // firefox auto decodes the url when using location.hash but not href |
| 21 | + hash = path.parseUrl(url).hash, |
| 22 | + isPath = path.isPath( hash ), |
| 23 | + resolutionUrl = isPath ? path.getLocation() : $.mobile.getDocumentUrl(); |
| 24 | + |
| 25 | + // #/foo/bar.html => /foo/bar.html |
| 26 | + // #foo => #foo |
| 27 | + hash = isPath ? hash.replace( "#", "" ) : hash; |
| 28 | + |
| 29 | + // make the hash abolute with the current href |
| 30 | + href = path.makeUrlAbsolute( hash, resolutionUrl ); |
| 31 | + |
| 32 | + if ( isPath ) { |
| 33 | + href = path.resetUIKeys( href ); |
| 34 | + } |
| 35 | + |
| 36 | + state = $.extend( data, { |
| 37 | + url: url, |
| 38 | + hash: hash, |
| 39 | + title: document.title |
| 40 | + }); |
| 41 | + |
| 42 | + // NOTE we currently _leave_ the appended hash in the hash in the interest |
| 43 | + // of seeing what happens and if we can support that before the hash is |
| 44 | + // pushed down |
| 45 | + |
| 46 | + // set the hash to be squashed by replace state or picked up by |
| 47 | + // the navigation special event |
| 48 | + window.location.hash = url; |
| 49 | + history.ignoreNextHashChange = true; |
| 50 | + |
| 51 | + if( $.support.pushState ) { |
| 52 | + // replace the current url with the new href and store the state |
| 53 | + // Note that in some cases we might be replacing an url with the |
| 54 | + // same url. We do this anyways because we need to make sure that |
| 55 | + // all of our history entries have a state object associated with |
| 56 | + // them. This allows us to work around the case where $.mobile.back() |
| 57 | + // is called to transition from an external page to an embedded page. |
| 58 | + // In that particular case, a hashchange event is *NOT* generated by the browser. |
| 59 | + // Ensuring each history entry has a state object means that onPopState() |
| 60 | + // will always trigger our hashchange callback even when a hashchange event |
| 61 | + // is not fired. |
| 62 | + window.history.replaceState( state, document.title, href ); |
| 63 | + } |
| 64 | + |
| 65 | + // record the history entry so that the information can be included |
| 66 | + // in hashchange event driven navigate events in a similar fashion to |
| 67 | + // the state that's provided by popstate |
| 68 | + |
| 69 | + history.add( url, state ); |
| 70 | + }; |
| 71 | + |
| 72 | + // NOTE must bind before `navigate` special event hashchange binding otherwise the |
| 73 | + // navigation data won't be attached to the hashchange event in time for those |
| 74 | + // bindings to attach it to the `navigate` special event |
| 75 | + // TODO add a check here that `hashchange.navigate` is bound already otherwise it's |
| 76 | + // broken (exception?) |
| 77 | + $( window ).bind( "hashchange.history", function( event ) { |
| 78 | + // If pushstate is supported the state will be included in the popstate event |
| 79 | + // data and appended to the navigate event. Late check here for late settings (eg tests) |
| 80 | + if( $.support.pushState ) { |
| 81 | + return; |
| 82 | + } |
| 83 | + |
| 84 | + // If the hashchange has been explicitly ignored or we have no history at |
| 85 | + // this point skip the history managment and the addition of the history |
| 86 | + // entry to the event for the `navigate` bindings |
| 87 | + if( history.ignoreNextHashChange || history.stack.length == 0 ) { |
| 88 | + history.ignoreNextHashChange = false; |
| 89 | + return; |
| 90 | + } |
| 91 | + |
| 92 | + |
| 93 | + history.direct({ |
| 94 | + currentUrl: path.parseLocation().hash , |
| 95 | + either: function( historyEntry ) { |
| 96 | + event.hashchangeState = historyEntry; |
| 97 | + } |
| 98 | + }); |
| 99 | + }); |
| 100 | + |
| 101 | + // expose the history on the navigate method in anticipation of full integration with |
| 102 | + // existing navigation functionalty that is tightly coupled to the history information |
| 103 | + $.navigate.history = history = { |
| 104 | + // Array of pages that are visited during a single page load. |
| 105 | + // Each has a url and optional transition, title, and pageUrl (which represents the file path, in cases where URL is obscured, such as dialogs) |
| 106 | + stack: [], |
| 107 | + |
| 108 | + //maintain an index number for the active page in the stack |
| 109 | + activeIndex: 0, |
| 110 | + |
| 111 | + //get active |
| 112 | + getActive: function() { |
| 113 | + return this.stack[ this.activeIndex ]; |
| 114 | + }, |
| 115 | + |
| 116 | + getPrev: function() { |
| 117 | + return this.stack[ this.activeIndex - 1 ]; |
| 118 | + }, |
| 119 | + |
| 120 | + getNext: function() { |
| 121 | + return this.stack[ this.activeIndex + 1 ]; |
| 122 | + }, |
| 123 | + |
| 124 | + // addNew is used whenever a new page is added |
| 125 | + add: function( url, data ){ |
| 126 | + data = data || {}; |
| 127 | + |
| 128 | + //if there's forward history, wipe it |
| 129 | + if ( this.getNext() ) { |
| 130 | + this.clearForward(); |
| 131 | + } |
| 132 | + |
| 133 | + data.url = url; |
| 134 | + this.stack.push( data ); |
| 135 | + this.activeIndex = this.stack.length - 1; |
| 136 | + }, |
| 137 | + |
| 138 | + //wipe urls ahead of active index |
| 139 | + clearForward: function() { |
| 140 | + this.stack = this.stack.slice( 0, this.activeIndex + 1 ); |
| 141 | + }, |
| 142 | + |
| 143 | + direct: function( opts ) { |
| 144 | + var back, forward, newActiveIndex, prev = this.getActive(), a = this.activeIndex; |
| 145 | + |
| 146 | + // check if url is in history and if it's ahead or behind current page |
| 147 | + $.each( this.stack, function( i, historyEntry ) { |
| 148 | + //if the url is in the stack, it's a forward or a back |
| 149 | + if ( decodeURIComponent( opts.currentUrl ) === decodeURIComponent( historyEntry.url ) ) { |
| 150 | + //define back and forward by whether url is older or newer than current page |
| 151 | + back = i < this.activeIndex; |
| 152 | + forward = !back; |
| 153 | + newActiveIndex = i; |
| 154 | + } |
| 155 | + }); |
| 156 | + |
| 157 | + // save new page index, null check to prevent falsey 0 result |
| 158 | + this.activeIndex = newActiveIndex !== undefined ? newActiveIndex : this.activeIndex; |
| 159 | + |
| 160 | + if ( back ) { |
| 161 | + ( opts.either || opts.isBack )( this.getActive() ); |
| 162 | + } else if ( forward ) { |
| 163 | + ( opts.either || opts.isForward )( this.getActive() ); |
| 164 | + } |
| 165 | + }, |
| 166 | + |
| 167 | + //disable hashchange event listener internally to ignore one change |
| 168 | + //toggled internally when location.hash is updated to match the url of a successful page load |
| 169 | + ignoreNextHashChange: false |
| 170 | + }; |
| 171 | + |
| 172 | + // Set the initial url history state |
| 173 | + history.add( path.parseLocation().pathname + path.parseLocation().search, {}); |
| 174 | +})( jQuery ); |
| 175 | + |
| 176 | +//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| 177 | +}); |
| 178 | +//>>excludeEnd("jqmBuildExclude"); |
0 commit comments