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

Commit

Permalink
abstract path helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
johnbender committed Oct 10, 2012
1 parent f0ce8c7 commit cb636c5
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 300 deletions.
4 changes: 3 additions & 1 deletion js/index.php
Expand Up @@ -20,7 +20,9 @@
'widgets/page.js',
'jquery.mobile.core.js',
'widgets/loader.js',
'navigation/event.js',
'navigation/events/navigate.js',
'navigation/path.js',
'navigation/navigate.js',
'jquery.mobile.navigation.js',
'jquery.mobile.navigation.pushstate.js',
'jquery.mobile.transition.js',
Expand Down
286 changes: 3 additions & 283 deletions js/jquery.mobile.navigation.js
Expand Up @@ -6,6 +6,7 @@
define( [
"jquery",
"./jquery.mobile.core",
"./navigation/path",
"./jquery.mobile.events",
"./jquery.mobile.support",
"depend!./jquery.hashchange[jquery]",
Expand All @@ -20,288 +21,7 @@ define( [
$head = $( 'head' ),

//url path helpers for use in relative url management
path = {

// This scary looking regular expression parses an absolute URL or its relative
// variants (protocol, site, document, query, and hash), into the various
// components (protocol, host, path, query, fragment, etc that make up the
// URL as well as some other commonly used sub-parts. When used with RegExp.exec()
// or String.match, it parses the URL into a results array that looks like this:
//
// [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
// [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
// [2]: http://jblas:password@mycompany.com:8080/mail/inbox
// [3]: http://jblas:password@mycompany.com:8080
// [4]: http:
// [5]: //
// [6]: jblas:password@mycompany.com:8080
// [7]: jblas:password
// [8]: jblas
// [9]: password
// [10]: mycompany.com:8080
// [11]: mycompany.com
// [12]: 8080
// [13]: /mail/inbox
// [14]: /mail/
// [15]: inbox
// [16]: ?msg=1234&type=unread
// [17]: #msg-content
//
urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,

// Abstraction to address xss (Issue #4787) by removing the authority in
// browsers that auto decode it. All references to location.href should be
// replaced with a call to this method so that it can be dealt with properly here
getLocation: function( url ) {
var uri = url ? this.parseUrl( url ) : location,
hash = this.parseUrl( url || location.href ).hash;

// mimic the browser with an empty string when the hash is empty
hash = hash === "#" ? "" : hash;

// Make sure to parse the url or the location object for the hash because using location.hash
// is autodecoded in firefox, the rest of the url should be from the object (location unless
// we're testing) to avoid the inclusion of the authority
return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
},

parseLocation: function() {
return this.parseUrl( this.getLocation() );
},

//Parse a URL into a structure that allows easy access to
//all of the URL components by name.
parseUrl: function( url ) {
// If we're passed an object, we'll assume that it is
// a parsed url object and just return it back to the caller.
if ( $.type( url ) === "object" ) {
return url;
}

var matches = path.urlParseRE.exec( url || "" ) || [];

// Create an object that allows the caller to access the sub-matches
// by name. Note that IE returns an empty string instead of undefined,
// like all other browsers do, so we normalize everything so its consistent
// no matter what browser we're running on.
return {
href: matches[ 0 ] || "",
hrefNoHash: matches[ 1 ] || "",
hrefNoSearch: matches[ 2 ] || "",
domain: matches[ 3 ] || "",
protocol: matches[ 4 ] || "",
doubleSlash: matches[ 5 ] || "",
authority: matches[ 6 ] || "",
username: matches[ 8 ] || "",
password: matches[ 9 ] || "",
host: matches[ 10 ] || "",
hostname: matches[ 11 ] || "",
port: matches[ 12 ] || "",
pathname: matches[ 13 ] || "",
directory: matches[ 14 ] || "",
filename: matches[ 15 ] || "",
search: matches[ 16 ] || "",
hash: matches[ 17 ] || ""
};
},

//Turn relPath into an asbolute path. absPath is
//an optional absolute path which describes what
//relPath is relative to.
makePathAbsolute: function( relPath, absPath ) {
if ( relPath && relPath.charAt( 0 ) === "/" ) {
return relPath;
}

relPath = relPath || "";
absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";

var absStack = absPath ? absPath.split( "/" ) : [],
relStack = relPath.split( "/" );
for ( var i = 0; i < relStack.length; i++ ) {
var d = relStack[ i ];
switch ( d ) {
case ".":
break;
case "..":
if ( absStack.length ) {
absStack.pop();
}
break;
default:
absStack.push( d );
break;
}
}
return "/" + absStack.join( "/" );
},

//Returns true if both urls have the same domain.
isSameDomain: function( absUrl1, absUrl2 ) {
return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
},

//Returns true for any relative variant.
isRelativeUrl: function( url ) {
// All relative Url variants have one thing in common, no protocol.
return path.parseUrl( url ).protocol === "";
},

//Returns true for an absolute url.
isAbsoluteUrl: function( url ) {
return path.parseUrl( url ).protocol !== "";
},

//Turn the specified realtive URL into an absolute one. This function
//can handle all relative variants (protocol, site, document, query, fragment).
makeUrlAbsolute: function( relUrl, absUrl ) {
if ( !path.isRelativeUrl( relUrl ) ) {
return relUrl;
}

if ( absUrl === undefined ) {
absUrl = documentBase;
}

var relObj = path.parseUrl( relUrl ),
absObj = path.parseUrl( absUrl ),
protocol = relObj.protocol || absObj.protocol,
doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
authority = relObj.authority || absObj.authority,
hasPath = relObj.pathname !== "",
pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
search = relObj.search || ( !hasPath && absObj.search ) || "",
hash = relObj.hash;

return protocol + doubleSlash + authority + pathname + search + hash;
},

//Add search (aka query) params to the specified url.
addSearchParams: function( url, params ) {
var u = path.parseUrl( url ),
p = ( typeof params === "object" ) ? $.param( params ) : params,
s = u.search || "?";
return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
},

convertUrlToDataUrl: function( absUrl ) {
var u = path.parseUrl( absUrl );
if ( path.isEmbeddedPage( u ) ) {
// For embedded pages, remove the dialog hash key as in getFilePath(),
// and remove otherwise the Data Url won't match the id of the embedded Page.
return u.hash
.split( dialogHashKey )[0]
.replace( /^#/, "" )
.replace( /\?.*$/, "" );
} else if ( path.isSameDomain( u, documentBase ) ) {
return u.hrefNoHash.replace( documentBase.domain, "" ).split( dialogHashKey )[0];
}

return window.decodeURIComponent(absUrl);
},

//get path from current hash, or from a file path
get: function( newPath ) {
if ( newPath === undefined ) {
newPath = path.parseLocation().hash;
}
return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
},

//return the substring of a filepath before the sub-page key, for making a server request
getFilePath: function( path ) {
var splitkey = '&' + $.mobile.subPageUrlKey;
return path && path.split( splitkey )[0].split( dialogHashKey )[0];
},

//set location hash to path
set: function( path ) {
location.hash = path;
},

//test if a given url (string) is a path
//NOTE might be exceptionally naive
isPath: function( url ) {
return ( /\// ).test( url );
},

//return a url path with the window's location protocol/hostname/pathname removed
clean: function( url ) {
return url.replace( documentBase.domain, "" );
},

//just return the url without an initial #
stripHash: function( url ) {
return url.replace( /^#/, "" );
},

//remove the preceding hash, any query params, and dialog notations
cleanHash: function( hash ) {
return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
},

isHashValid: function( hash ) {
return ( /^#[^#]+$/ ).test( hash );
},

//check whether a url is referencing the same domain, or an external domain or different protocol
//could be mailto, etc
isExternal: function( url ) {
var u = path.parseUrl( url );
return u.protocol && u.domain !== documentUrl.domain ? true : false;
},

hasProtocol: function( url ) {
return ( /^(:?\w+:)/ ).test( url );
},

//check if the specified url refers to the first page in the main application document.
isFirstPageUrl: function( url ) {
// We only deal with absolute paths.
var u = path.parseUrl( path.makeUrlAbsolute( url, documentBase ) ),

// Does the url have the same path as the document?
samePath = u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ),

// Get the first page element.
fp = $.mobile.firstPage,

// Get the id of the first page element if it has one.
fpId = fp && fp[0] ? fp[0].id : undefined;

// The url refers to the first page if the path matches the document and
// it either has no hash value, or the hash is exactly equal to the id of the
// first page element.
return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
},

isEmbeddedPage: function( url ) {
var u = path.parseUrl( url );

//if the path is absolute, then we need to compare the url against
//both the documentUrl and the documentBase. The main reason for this
//is that links embedded within external documents will refer to the
//application document, whereas links embedded within the application
//document will be resolved against the document base.
if ( u.protocol !== "" ) {
return ( u.hash && ( u.hrefNoHash === documentUrl.hrefNoHash || ( documentBaseDiffers && u.hrefNoHash === documentBase.hrefNoHash ) ) );
}
return ( /^#/ ).test( u.href );
},


// 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.
isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
return $.mobile.allowCrossDomainPages &&
docUrl.protocol === "file:" &&
reqUrl.search( /^https?:/ ) !== -1;
}
},
path = $.mobile.path,

//will be defined when a link is clicked and given an active class
$activeClickedLink = null,
Expand Down Expand Up @@ -1486,7 +1206,7 @@ define( [

// We should probably fire the "navigate" event from those places that make calls to _handleHashChange,
// and have _handleHashChange hook into the "navigate" event instead of triggering it here
$.mobile.pageContainer.trigger( navEvent );
// $.mobile.pageContainer.trigger( navEvent );
if ( navEvent.isDefaultPrevented() ) {
return;
}
Expand Down
18 changes: 2 additions & 16 deletions js/jquery.mobile.navigation.pushstate.js
Expand Up @@ -42,21 +42,7 @@ define( [ "jquery", "./jquery.mobile.navigation", "depend!./jquery.hashchange[jq
initialHref: self.initialHref
};
},

resetUIKeys: function( url ) {
var dialog = $.mobile.dialogHashKey,
subkey = "&" + $.mobile.subPageUrlKey,
dialogIndex = url.indexOf( dialog );

if ( dialogIndex > -1 ) {
url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex );
} else if ( url.indexOf( subkey ) > -1 ) {
url = url.split( subkey ).join( "#" + subkey );
}

return url;
},


// TODO sort out a single barrier to hashchange functionality
nextHashChangePrevented: function( value ) {
$.mobile.urlHistory.ignoreNextHashChange = value;
Expand Down Expand Up @@ -88,7 +74,7 @@ define( [ "jquery", "./jquery.mobile.navigation", "depend!./jquery.hashchange[jq
href = $.mobile.path.makeUrlAbsolute( hash, resolutionUrl );

if ( isPath ) {
href = self.resetUIKeys( href );
href = $.mobile.path.resetUIKeys( href );
}

// replace the current url with the new href and store the state
Expand Down
14 changes: 14 additions & 0 deletions js/navigation/path.js
Expand Up @@ -289,6 +289,20 @@ define([
return $.mobile.allowCrossDomainPages &&
docUrl.protocol === "file:" &&
reqUrl.search( /^https?:/ ) !== -1;
},

resetUIKeys: function( url ) {
var dialog = $.mobile.dialogHashKey,
subkey = "&" + $.mobile.subPageUrlKey,
dialogIndex = url.indexOf( dialog );

if ( dialogIndex > -1 ) {
url = url.slice( 0, dialogIndex ) + "#" + url.slice( dialogIndex );
} else if ( url.indexOf( subkey ) > -1 ) {
url = url.split( subkey ).join( "#" + subkey );
}

return url;
}
};

Expand Down

0 comments on commit cb636c5

Please sign in to comment.