forked from jquery-archive/jquery-mobile
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnavigation.js
419 lines (354 loc) · 13 KB
/
navigation.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
/*!
* jQuery Mobile Navigation @VERSION
* http://jquerymobile.com
*
* Copyright jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
//>>label: Content Management
//>>group: Navigation
//>>description: Applies the AJAX navigation system to links and forms to enable page transitions
//>>demos: http://demos.jquerymobile.com/@VERSION/navigation/
( function( factory ) {
if ( typeof define === "function" && define.amd ) {
// AMD. Register as an anonymous module.
define( [
"jquery",
"./core",
"./navigation/path",
"./events/navigate",
"./navigation/history",
"./navigation/navigator",
"./navigation/method",
"./support",
"./animationComplete",
"./widgets/pagecontainer",
"./widgets/page",
"./transitions/handlers" ], factory );
} else {
// Browser globals
factory( jQuery );
}
} )( function( $ ) {
// resolved on domready
var domreadyDeferred = $.Deferred(),
// resolved and nulled on window.load()
loadDeferred = $.Deferred(),
// function that resolves the above deferred
pageIsFullyLoaded = function() {
// Resolve and null the deferred
loadDeferred.resolve();
loadDeferred = null;
},
path = $.mobile.path,
documentUrl = path.documentUrl,
// used to track last vclicked element to make sure its value is added to form data
$lastVClicked = null;
/* Event Bindings - hashchange, submit, and click */
function findClosestLink( ele ) {
while ( ele ) {
// Look for the closest element with a nodeName of "a".
// Note that we are checking if we have a valid nodeName
// before attempting to access it. This is because the
// node we get called with could have originated from within
// an embedded SVG document where some symbol instance elements
// don't have nodeName defined on them, or strings are of type
// SVGAnimatedString.
if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
break;
}
ele = ele.parentNode;
}
return ele;
}
/* internal utility functions */
// NOTE Issue #4950 Android phonegap doesn't navigate back properly
// when a full page refresh has taken place. It appears that hashchange
// and replacestate history alterations work fine but we need to support
// both forms of history traversal in our code that uses backward history
// movement
$.mobile.back = function() {
var nav = window.navigator;
// if the setting is on and the navigator object is
// available use the phonegap navigation capability
if ( this.phonegapNavigationEnabled &&
nav &&
nav.app &&
nav.app.backHistory ) {
nav.app.backHistory();
} else {
$.mobile.pagecontainers.active.back();
}
};
// No-op implementation of transition degradation
$.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
return transition;
};
// Exposed $.mobile methods
$.mobile._registerInternalEvents = function() {
var getAjaxFormData = function( $form, calculateOnly ) {
var url,
ret = true, formData, vclickedName, method;
if ( !$.mobile.ajaxEnabled ||
// test that the form is, itself, ajax false
$form.is( ":jqmData(ajax='false')" ) ||
// test that $.mobile.ignoreContentEnabled is set and
// the form or one of it's parents is ajax=false
!$form.jqmHijackable().length ||
$form.attr( "target" ) ) {
return false;
}
url = ( $lastVClicked && $lastVClicked.attr( "formaction" ) ) ||
$form.attr( "action" );
method = ( $form.attr( "method" ) || "get" ).toLowerCase();
// If no action is specified, browsers default to using the
// URL of the document containing the form. Since we dynamically
// pull in pages from external documents, the form should submit
// to the URL for the source document of the page containing
// the form.
if ( !url ) {
// Get the @data-url for the page containing the form.
url = $.mobile.getClosestBaseUrl( $form );
// NOTE: If the method is "get", we need to strip off the query string
// because it will get replaced with the new form data. See issue #5710.
if ( method === "get" ) {
url = path.parseUrl( url ).hrefNoSearch;
}
if ( url === path.documentBase.hrefNoHash ) {
// The url we got back matches the document base,
// which means the page must be an internal/embedded page,
// so default to using the actual document url as a browser
// would.
url = documentUrl.hrefNoSearch;
}
}
url = path.makeUrlAbsolute( url, $.mobile.getClosestBaseUrl( $form ) );
if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) {
return false;
}
if ( !calculateOnly ) {
formData = $form.serializeArray();
if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) {
vclickedName = $lastVClicked.attr( "name" );
if ( vclickedName ) {
// Make sure the last clicked element is included in the form
$.each( formData, function( key, value ) {
if ( value.name === vclickedName ) {
// Unset vclickedName - we've found it in the serialized data already
vclickedName = "";
return false;
}
} );
if ( vclickedName ) {
formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } );
}
}
}
ret = {
url: url,
options: {
type: method,
data: $.param( formData ),
transition: $form.jqmData( "transition" ),
reverse: $form.jqmData( "direction" ) === "reverse",
reloadPage: true
}
};
}
return ret;
};
//bind to form submit events, handle with Ajax
$.mobile.document.delegate( "form", "submit", function( event ) {
var formData;
if ( !event.isDefaultPrevented() ) {
formData = getAjaxFormData( $( this ) );
if ( formData ) {
$( this ).closest( ".ui-pagecontainer" )
.pagecontainer( "change", formData.url, formData.options );
event.preventDefault();
}
}
} );
//add active state on vclick
$.mobile.document.bind( "vclick", function( event ) {
var theButton,
target = event.target;
// if this isn't a left click we don't care. Its important to note
// that when the virtual event is generated it will create the which attr
if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
return;
}
// Record that this element was clicked, in case we need it for correct
// form submission during the "submit" handler above
$lastVClicked = $( target );
// Try to find a target element to which the active class will be applied
if ( $.data( target, "ui-button" ) ) {
// If the form will not be submitted via AJAX, do not add active class
if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) {
return;
}
} else {
target = findClosestLink( target );
if ( !target ||
( path.parseUrl( target.getAttribute( "href" ) || "#" ).hash === "#" &&
target.getAttribute( "data-" + $.mobile.ns + "rel" ) !== "back" ) ) {
return;
}
// TODO teach $.mobile.hijackable to operate on raw dom elements so the
// link wrapping can be avoided
if ( !$( target ).jqmHijackable().length ) {
return;
}
}
theButton = $( target ).closest( ".ui-button" );
if ( theButton.length > 0 &&
!( theButton.hasClass( "ui-state-disabled" ||
// DEPRECATED as of 1.4.0 - remove after 1.4.0 release
// only ui-state-disabled should be present thereafter
theButton.hasClass( "ui-disabled" ) ) ) ) {
$.mobile.removeActiveLinkClass( true );
$.mobile.activeClickedLink = theButton;
$.mobile.activeClickedLink.addClass( "ui-button-active" );
}
} );
// click routing - direct to HTTP or Ajax, accordingly
$.mobile.document.bind( "click", function( event ) {
if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) {
return;
}
var link = findClosestLink( event.target ),
$link = $( link ),
//remove active link class if external (then it won't be there if you come back)
httpCleanup = function() {
window.setTimeout( function() {
$.mobile.removeActiveLinkClass( true );
}, 200 );
},
baseUrl, href,
useDefaultUrlHandling, isExternal,
transition, reverse, role;
// If a button was clicked, clean up the active class added by vclick above
if ( $.mobile.activeClickedLink &&
$.mobile.activeClickedLink[ 0 ] === event.target ) {
httpCleanup();
}
// If there is no link associated with the click or its not a left
// click we want to ignore the click
// TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
// can be avoided
if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
return;
}
//if there's a data-rel=back attr, go back in history
if ( $link.is( ":jqmData(rel='back')" ) ) {
$.mobile.back();
return false;
}
baseUrl = $.mobile.getClosestBaseUrl( $link );
//get href, if defined, otherwise default to empty hash
href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
//if ajax is disabled, exit early
if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) {
httpCleanup();
//use default click handling
return;
}
// XXX_jblas: Ideally links to application pages should be specified as
// an url to the application document with a hash that is either
// the site relative path or id to the page. But some of the
// internal code that dynamically generates sub-pages for nested
// lists and select dialogs, just write a hash in the link they
// create. This means the actual URL path is based on whatever
// the current value of the base tag is at the time this code
// is called.
if ( href.search( "#" ) !== -1 &&
!( path.isExternal( href ) && path.isAbsoluteUrl( href ) ) ) {
href = href.replace( /[^#]*#/, "" );
if ( !href ) {
//link was an empty hash meant purely
//for interaction, so we ignore it.
event.preventDefault();
return;
} else if ( path.isPath( href ) ) {
//we have apath so make it the href we want to load.
href = path.makeUrlAbsolute( href, baseUrl );
} else {
//we have a simple id so use the documentUrl as its base.
href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
}
}
// Should we handle this link, or let the browser deal with it?
useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" );
// Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
// requests if the document doing the request was loaded via the file:// protocol.
// This is usually to allow the application to "phone home" and fetch app specific
// data. We normally let the browser handle external/cross-domain urls, but if the
// allowCrossDomainPages option is true, we will allow cross-domain http/https
// requests to go through our page loading logic.
//check for protocol or rel and its not an embedded page
//TODO overlap in logic from isExternal, rel=external check should be
// moved into more comprehensive isExternalLink
isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) );
if ( isExternal ) {
httpCleanup();
//use default click handling
return;
}
//use ajax
transition = $link.jqmData( "transition" );
reverse = $link.jqmData( "direction" ) === "reverse" ||
// deprecated - remove by 1.0
$link.jqmData( "back" );
//this may need to be more specific as we use data-rel more
role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
$link.closest( ".ui-pagecontainer" ).pagecontainer( "change", href, {
transition: transition,
reverse: reverse,
role: role,
link: $link
});
event.preventDefault();
} );
//prefetch pages when anchors with data-prefetch are encountered
$.mobile.document.delegate( ".ui-page", "page.prefetch", function() {
var urls = [],
that = this;
$( this ).find( "a:jqmData(prefetch)" ).each( function() {
var $link = $( this ),
url = $link.attr( "href" );
if ( url && $.inArray( url, urls ) === -1 ) {
urls.push( url );
$( that ).closest( ".ui-pagecontainer" ).pagecontainer( "load", url, {
role: $link.attr( "data-" + $.mobile.ns + "rel" ),
prefetch: true
});
}
} );
} );
//set page min-heights to be device specific
$.mobile.document.bind( "pageshow", function() {
// We need to wait for window.load to make sure that styles have already been rendered,
// otherwise heights of external toolbars will have the wrong value
if ( loadDeferred ) {
loadDeferred.done( $.mobile.resetActivePageHeight );
} else {
$.mobile.resetActivePageHeight();
}
} );
$.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
}; //navreadyDeferred done callback
$( function() {
domreadyDeferred.resolve();
} );
// Account for the possibility that the load event has already fired
if ( document.readyState === "complete" ) {
pageIsFullyLoaded();
} else {
$.mobile.window.on( "load", pageIsFullyLoaded );
}
$.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() {
$.mobile._registerInternalEvents();
} );
return $.mobile;
} );