Skip to content
This repository was archived by the owner on Oct 8, 2021. It is now read-only.

Patch for issue #1024 (Duplicate pages created in DOM) #1456

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion js/jquery.mobile.init.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@

//define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
$.mobile.firstPage = $pages.first();
$.mobile.firstPageUrl = "#" + $.mobile.firstPage.attr("data-" + $.mobile.ns + "url");

//define page container
$.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" );
Expand All @@ -91,7 +92,7 @@

// if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM
if( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ){
$.mobile.changePage( $.mobile.firstPage, false, true, false, true );
$.mobile.changePage( $.mobile.firstPage, false, true, true, true );
}
// otherwise, trigger a hashchange to load a deeplink
else {
Expand Down
197 changes: 170 additions & 27 deletions js/jquery.mobile.navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@

//get path from current hash, or from a file path
get: function( newPath ){
var segments, i;
if( newPath === undefined ){
newPath = location.hash;
}
return path.stripHash( newPath ).replace(/[^\/]*\.[^\/*]+$/, '');
// Making a regexp that returns the path
// (and does not have obscure failure cases)
// is not so simple - try a simpler approach instead
i = newPath.indexOf("?");
if (i > -1) { // Remove any parameters (which might contain slashes!)
newPath = newPath.slice(0, i);
}
segments = path.stripHash( newPath ).split("/");
segments.pop();
return segments.join("/") + (segments.length > 0 ? "/" : "");
},

//return the substring of a filepath before the sub-page key, for making a server request
Expand All @@ -30,7 +40,11 @@

//set location hash to path
set: function( path ){
location.hash = path;
if (path === "#" && location.href.indexOf("#") === -1) {
location.href += "#"; // Only way to set empty hash for non-hash url
} else {
location.hash = path;
}
},

//location pathname from intial directory request
Expand Down Expand Up @@ -68,13 +82,139 @@
return /^\?/.test(url);
},

//return a url path with the window's location protocol/hostname/pathname removed
// There are several supported url formats, which are cleaned (converted to canonical form) in this function.
// The returned value will contain protocol and hostname part only if the url is an external url which should
// not be loaded with XHR.
// Note that the entry page (the page which loads the jQuery Mobile library) should preferably always be an
// index.* file, so that you can refer to it with path that ends in a slash.
// E.g. example.com/a/#b/c.html rather than example.com/a/app.html#b/c.html
// This makes the hashed urls look more clean and intuitive.
// Common use case examples are given below.
// Note: For brevity protocol has been omitted from urls, but it would be present whenever hostname is present.
// Syntax: current_location + url => resulting_location (returned canonical form)

// 01) Relative: example.com/a/#b/c.html + ../d/e.html => example.com/a/#d/e.html (#d/e.html)
// 02) Hash-relative: example.com/a/#b/c.html + #d/e.html => example.com/a/#d/e.html (#d/e.html)
// 03) Hash-root: example.com/a/#b/c.html + #/d/e.html => example.com/a/#d/e.html (#d/e.html)
// 04) Absolute path: example.com/a/#b/c.html + /d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 05) Host-absolute (same host): example.com/a/#b/c.html + example.com/d/e.html => example.com/a/#../d/e.html (#../d/e.html)
// 06) Host-absolute (diff host): example.com/a/#b/c.html + other.com/d/e.html => other.com/d/e.html (other.com/d/e.html)
// 07) Hashed relative (same path): example.com/a/#b/c.html + ../#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 08) Hashed relative (diff path): example.com/a/#b/c.html + ../d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 09) Hashed absolute (same path): example.com/a/#b/c.html + /a/#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 10) Hashed absolute (diff path): example.com/a/#b/c.html + /d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 11) Hashed host-abs (same path): example.com/a/#b/c.html + example.com/a/#d/e.html => example.com/a/#d/e.html (#d/e.html)
// 12) Hashed host-abs (diff path): example.com/a/#b/c.html + example.com/d/#e.html => example.com/d/#e.html (example.com/d/#e.html)
// 13) Hashed host-abs (diff host): example.com/a/#b/c.html + other.com/d/#e.html => other.com/d/#e.html (other.com/d/#e.html)

// The canonical form is hash-relative (e.g. #d/e.html) for urls on the same host
// and original form (no change) for urls on a different host.
// A hashed url (cases 07-13) must always use the canonical form in the hash part.
// E.g. example.com/a/#/b/c.html is illegal, you must use example.com/a/#b/c.html instead.
// In non-external urls index.* is never explicitly included at the end (ends in slash instead).
// Hash-root form is same as hash-relative, just with a slash prefix. The reason it is supported
// is to allow you to write "#/" to refer to $.mobile.firstPage (and to write other hash urls in
// a way consistent with that).
// You can refer to $.mobile.firstPage in several ways:
// 1) Hash-root: example.com/a/#b/c.html + #/ => example.com/a/# (#)
// 2) Relative: example.com/a/#b/c.html + .. => example.com/a/# (#)
// 3) Absolute path: example.com/a/#b/c.html + /a/ => example.com/a/# (#)
// 4) Hashed relative: example.com/a/#b/c.html + ../# => example.com/a/# (#)
// 5) Hashed absolute: example.com/a/#b/c.html + /a/# => example.com/a/# (#)
// The recommended way is hash-root (#/), because it does not depend on current url.
// You can of course also refer to $.mobile.firstPage using its data-url attribute
// (=== ID if not defined), e.g. "#MyFirstPageId".
// This will be handled equivalently to "#/".
// Note that the location (url bar) will always have a hash character, event when on first page.

clean: function( url ){
if(path.isQuery(url)){
return "#" + path.cleanHash(location.hash) + url;
}

// Replace the protocol, host, and pathname only once at the beginning of the url to avoid
// problems when it's included as a part of a param
// Also, since all urls are absolute in IE, we need to remove the pathname as well.
var leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname);
return url.replace(leadingUrlRootRegex, "");
var leadingUrlRootRegex;
var hashIndex = url.indexOf("#");
if (hashIndex > 0) { // Hash, but not at beginning
// Add protocol and host to absolute and relative hashed urls so that they will be considered external
// if the pathname (before hash) does not match current pathname.
if (url.charAt(0) === "/") { // hashed absolute
url = location.protocol + "//" + location.host + url;
} else if (url.slice(0, location.protocol.length) !== location.protocol) { // hashed relative
url = location.protocol + "//" + location.host +
path.canonize(location.pathname + path.get() + url.slice(0, hashIndex)) +
url.slice(hashIndex);
}
// Absolute (with hostname) urls that contain a hash are converted to canonical form
// only if the pathname (before hash) matches current location.pathname.
// Otherwise it is considered an external URL and returned as-is
// (because regexp will not match and protocol is thus not removed).
// Such an URL should always be loaded as external, and isExternal will return true for it
// since protocol remains in the URL after path.clean.
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + location.pathname + "(#.*)$");
} else {
leadingUrlRootRegex = new RegExp("^" + location.protocol + "//" + location.host + "(.*)$");
}
var checkForRoot = leadingUrlRootRegex.exec(url);
if (checkForRoot) {
url = checkForRoot[1];
}
url = url.replace(leadingUrlRootRegex, "");
if (path.hasProtocol(url)) {
return url; // External
} else {
// canonize url
var urlSegments, locSegments, i, canonPath = [];
var checkForIndex = /^(.*\/)index\.[^\/?]*(\?.*)?$/i.exec(url);
if (checkForIndex) {
url = checkForIndex[1] + (checkForIndex[2] ? checkForIndex[2] : "");
}
if (url.charAt(0) === "#") {
if (url.charAt(1) === "/") { // Hash-absolute
return "#" + url.slice(2);
} else if (url === $.mobile.firstPageUrl) { // First page
return "#";
} else { // Hash-relative
return url;
}
} else if (url.charAt(0) === "/") { // Absolute
locSegments = location.pathname.split("/");
urlSegments = url.split("/");
locSegments.pop(); // Remove filename part
for (i=0; locSegments[i] === urlSegments[i]; i++); // Count common elements
urlSegments.splice(0, i); // Remove common elements from url
for (;i < locSegments.length; i++) {
canonPath.push("..");
}
canonPath = canonPath.concat(urlSegments);
return "#" + canonPath.join("/");
} else { // Relative
return "#" + path.canonize(path.get() + url);
}
}
},

// Remove dot-dot segments where possible by removing both the dot-dot segment and the preceding segment.
// E.g. a/b/../c becomes a/c, but ../a/b remains the same.
// Dot or empty segments are simply removed.
// e.g. a/./b/c or a//b/c becomes a/b/c
canonize: function( path ) {
var canonPath = path.split("/"), i = 0;
while(i < canonPath.length) {
if (canonPath[i] === ".." && i > 0 && canonPath[i-1] !== "..") {
i--;
canonPath.splice(i,2);
} else if (canonPath[i] === "." || // remove same-dir (dot) segments
// Remove empty segments - cannot remove first/last because they mark beginning/trailing slash
(canonPath[i] === "" && i > 0 && i < canonPath.length-1)) {
canonPath.splice(i,1);
} else {
i++;
}
}
return canonPath.join("/");

},

//just return the url without an initial #
Expand Down Expand Up @@ -319,12 +459,19 @@

// changepage function
$.mobile.changePage = function( to, transition, reverse, changeHash, fromHashChange ){
if ($.type(to) === "string") {
to = path.clean(to);
if (to === "#") {
to = $.mobile.firstPage;
}
}

//from is always the currently viewed page
var toIsArray = $.type(to) === "array",
toIsObject = $.type(to) === "object",
from = toIsArray ? to[0] : $.mobile.activePage;

to = toIsArray ? to[1] : to;
to = toIsArray ? to[1] : to;

var url = $.type(to) === "string" ? path.stripHash( to ) : "",
fileUrl = url,
Expand Down Expand Up @@ -371,7 +518,7 @@
}

if( toIsObject && to.url ){
url = to.url;
url = path.clean(to.url);
data = to.data;
type = to.type;
isFormRequest = true;
Expand Down Expand Up @@ -430,12 +577,17 @@
to.data( "page" )._trigger( "beforeshow", null, { prevPage: from || $("") } );

function pageChangeComplete(){

if( changeHash !== false && url ){
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = false;
//update hash and history
path.set( url );
if (changeHash !== false) {
if (to === $.mobile.firstPage) {
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = false;
path.set("#");
} else if(url) {
//disable hash listening temporarily
urlHistory.ignoreNextHashChange = false;
//update hash and history
path.set( url );
}
}

//if title element wasn't found, try the page div data attr too
Expand Down Expand Up @@ -760,7 +912,6 @@

//if data-ajax attr is set to false, use the default behavior of a link
hasAjaxDisabled = $this.is( ":jqmData(ajax='false')" );

//if there's a data-rel=back attr, go back in history
if( $this.is( ":jqmData(rel='back')" ) ){
window.history.back();
Expand All @@ -769,7 +920,7 @@

//prevent # urls from bubbling
//path.get() is replaced to combat abs url prefixing in IE
if( url.replace(path.get(), "") == "#" ){
if( href == "#" ){
//for links created purely for interaction - ignore
event.preventDefault();
return;
Expand All @@ -796,22 +947,14 @@

//this may need to be more specific as we use data-rel more
nextPageRole = $this.attr( "data-" + $.mobile.ns + "rel" );

//if it's a relative href, prefix href with base url
if( path.isRelative( url ) && !hadProtocol ){
url = path.makeAbsolute( url );
}

url = path.stripHash( url );

$.mobile.changePage( url, transition, reverse);
event.preventDefault();
});

//hashchange event handler
$window.bind( "hashchange", function( e, triggered ) {
//find first page via hash
var to = path.stripHash( location.hash ),
var to = location.hash,
//transition is false if it's the first page, undefined otherwise (and may be overridden by default)
transition = $.mobile.urlHistory.stack.length === 0 ? false : undefined;

Expand All @@ -834,7 +977,7 @@
//determine if we're heading forward or backward and continue accordingly past
//the current dialog
urlHistory.directHashChange({
currentUrl: to,
currentUrl: path.stripHash(to),
isBack: function(){ window.history.back(); },
isForward: function(){ window.history.forward(); }
});
Expand All @@ -845,7 +988,7 @@
var setTo = function(){ to = $.mobile.urlHistory.getActive().page; };
// if the current active page is a dialog and we're navigating
// to a dialog use the dialog objected saved in the stack
urlHistory.directHashChange({ currentUrl: to, isBack: setTo, isForward: setTo });
urlHistory.directHashChange({ currentUrl: path.stripHash(to), isBack: setTo, isForward: setTo });
}
}

Expand Down
14 changes: 14 additions & 0 deletions tests/unit/navigation/domcopies.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">

</head>
<body>

<div data-nstest-role="page" id="domcopies">
<p class="domcopy_count">Dom copies test</p>
</div>

</body>
</html>
5 changes: 5 additions & 0 deletions tests/unit/navigation/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,13 @@ <h1>Dialog</h1>
<div data-nstest-role="header"><h1>Title Heading</h1></div>
</div>

<div id="canonicaltest" data-nstest-role="page">
<a href="#/"></a>
</div>

<div data-nstest-role="page" id="self-link">
<a href="#self-link">self!</a>
</div>

</body>
</html>
Loading