Javascript LOCATION constructor

Airshow 0 Tallied Votes 919 Views Share

INTRODUCTION
The W3C's Window Object 1.0 specification (Working Draft dated 07 April 2006 at the time of writing) defines in it's Location Interface section a set of attributes (or fragments) by which a Location object may be addressed. Despite the draft status of this document, many implementations of javascript have provided such an interface for many years.

The Location attributes .href and its fragments [.protocol, .hostname, .port, .pathname, .search, .hash] are all read/write. Whilst reading is reasonably reliable across various browsers (there are some small differences in the detail of what is returned), some (principally IE6) fail to implement the writing of fragments correctly. A garbled href can result!
For example: var foo = window.location.search; is (reasonably) reliable. window.location.search = 'a=500'; is not reliable in all browsers.

Some fragments are more reliably written than others and success can depend on the presence/absence of other fragments.

SOLUTION
The javascript constructor (class) LOCATION, below, maintains its own public location fragments and composite href and an underlying instance of javascript's built-in Location object. The public fragments can be read and written independently of the underlying Location until such time that an optional "commit" instruction causes a full href to be built from the fragments and sent to the underlying Location. Apart from "commit", a range of methods is included for setting the whole href, building/amending a location by individual fragment(s), for returning a complete href string and for returning a link, complete with href, ready for insertion into a document with or without further processing (such as adding a classname or target using DOM methods).

INSTALLATION
Save the code to a file, location.js , then include it in the head of any page that needs it with: <script src="/path/to/script/location.js" type="text/javascript"></script> .

INSTANTIATION
There are four forms:

  1. new LOCATION(locationObject);
    In this form, LOCATION seeds its public fragments from the native Location object passed as a parameter. When committed, changes to the internal href or its fragments directly affect the external Location object. This form can be used for directly manipulating the window.location object, for example to go to a named anchor by setting window.location.hash.
    Examples:
    • var foo = new LOCATION(window.location);
    • var foo = new LOCATION(opener.location);
  2. new LOCATION(DOM_link);
    In this form, LOCATION uses the passed hyperlink (<a>) object as its underlying Location. When committed, changes to the internal href or its fragments directly affect the external hyperlink. This form can be used for building/modifying the href of a pre-created link.
    Examples:
    • var foo = new LOCATION(document.links[i]); .
    • var foo = document.createElement('a'); foo.href="http://www.abc.com"; var bar = new LOCATION(foo);
  3. new LOCATION(href);
    In this form, LOCATION creates its own Location object and seeds it and the public fragments with the href string passed as a parameter. The new instance of LOCATION is thus initially independent of any external representation of the underlying Location.
    Examples:
    • var foo = new LOCATION(window.location.href);
    • var foo = new LOCATION("http://www.abc.com");
    • var foo = new LOCATION("subfolder/page.html");
    • var foo = new LOCATION("../subdirectory/page.html");
    • var foo = new LOCATION("/directory/page.html");
  4. new LOCATION();
    Example:
    • var foo = new LOCATION();

    In this form, LOCATION creates its own Location object and seeds the public fragments with nulls. This form can be used to build from scratch an href string or DOM hyperlink (<a>) element.

Whichever form is used, the current state of a LOCATION instance can be externalised by enquiring the property LOCATION.href, or calling the methods LOCATION.getLink() or LOCATION.inspect() (see below).

API

Properties
LOCATION.href
LOCATION.protocol
LOCATION.host
LOCATION.hostname
LOCATION.port
LOCATION.pathname
LOCATION.search
LOCATION.hash

All are public properties and can thus be enquired with eg. foo.protocol .

Methods
LOCATION.setHREF(href[, commitIt])
Description : Sets a LOCATION's href to href. href must be a string. It is not checked for validity. When true, the optional parameter commitIt causes .commit() to be called.
Returns : true if committed, false if not committed.
Example : foo.setHREF("http://www.abc.com"); .

LOCATION.setFragments(frags[, commitIt])
Description : Sets those parts of a LOCATION's href specified in frags, which must be of the form { prop1:value1[, propN:valueN] }. When true, the optional parameter commitIt causes .commit() to be called.
Returns : true if committed, false if not committed.
Example : foo.setFragments({search:'q=17&name=Smith', hash:'education'}); .

LOCATION.compose()
Description : Builds a full href from LOCATION's stored fragments (.protocol, .hostname, etc). Called internally by other methods. Not normally called externally but can be called instead of enquiring the .href property.
Returns : The composed href (string).
Example : var bar = foo.compose(); .

LOCATION.decompose()
Description : Breaks an href down into its component fragments which are stored internally as public properties. Called internally by other methods. Not normally called externally.
Returns : nothing
Example : foo.decompose(); .

LOCATION.commit()
Description : Calls .compose() then commits the returned href to the underlying Location object. For instances of LOCATION created with form 1 or form 2 (see above) and those from which a link has been returned using .getLink(), the external Location object will be affected.
Returns : True if committed; false if not.
Example : var bar = foo.commit(); .

LOCATION.getLink([commitIt])
Description : Returns a link in a form suitable for inserting into a document with eg. document.appendChild(). When true, the optional parameter commitIt causes .commit() to be called before returning the link. After returning a link, any subsequent call of .commit() will affect the returned Location object.
Returns : A DOM element of the type document.createElement('a'), with its href set to the underlying Location object's href. Other attributes need to be set externally.
Example : var bar = foo.getLink(true); LOCATION.inspect([wrapperID])
Description : Builds a tabulated representation of all fragments, the composed href and the underlying Location's href. When true, the optional parameter wrapperID causes the composed table to be displayed in an HTML block element (eg. div) identified with id="wrapperID". The table is given the attribute class="locationInspect" in case it needs to be styled with css. This method was originally included to help debug the constructor but left in as it may be useful to developers.
Returns : (string) an HTML table.
Examples : foo.inspect("myDiv"); var HTMLTableString = foo.inspect(); LIMITATIONS
Like javascript's built-in Location class, LOCATION addresses only HTTP. It is not universally applicable to other URL protocols; finger, mailto, ftp etc.

LOCATION is only semi-intelligent. When building urls, .compose() and .getLink() omit delimiters before null fragments. Other than that, it's rubbish-in-rubbish-out.

In the (unlikely?) event, at some time in the future, that javascript should incorporate a LOCATION object (capitalized) in is native code, then this constructor will need to be renamed.

Only medium-intensity tested at the time of posting. May therefore be buggy. Suggest rigorous testing in target environment before deploying.

RELATED SNIPPETS
Javascript Query Parser

CONCLUSION
LOCATION offers a convenient way to build and manipulate Location objects, overcoming (subject to testing) shortcomings in some browsers' implementation of the "Location Interface".

If you are developing uniquely for a browser version which correctly implements the "Location Interface" (ie. reliably writes url fragments), then there is no need to use LOCATION, though it may still be a handy tool for client-side dynamic url/link building rather than having to code up a lot of string handling for yourself.

Comments and improvements welcome.

Airshow

function LOCATION(locObj){
   /*** URL ***********************************************
    *** A constructor function ****************************
    *** by Airshow ****************************************
    *** http://www.daniweb.com/forums/member512379.html ***
    *** Please keep this attribution intact ***************/
	if(!locObj || typeof locObj === 'string') {
		var temp = (!locObj) ? '' : locObj;
		var locObj = document.createElement('a');
		locObj.href = temp;
	}
	this.href = locObj.href;
	this.protocol = '';
	this.hostname = '';
	this.port = '';
	this.host = '';
	this.pathname = '';
	this.search = '';
	this.hash = '';
	this.setFragments = function(frags, commitIt) {
		frags = (!frags || typeof frags !== 'object') ? null : frags;
		commitIt = (!commitIt) ? false : true;
		if(frags) {
			for(prop in frags) {
				if('protocol,hostname,port,pathname,search,hash'.indexOf(prop) == -1) { continue; }//Reject irrelevant props
				// Note: For simplicity, 'host' is excluded as URL.host comprises ( URL.hostname + : + URL.port ), which can be set separately.
				this[prop] = frags[prop];
			}
			this.host = [this.hostname, this.port].join(':'); //In case .hostname or .port have changed.
		}
		(commitIt) ? this.commit() : (this.href = this.compose());
		return commitIt;
	};
	this.setHREF = function(href, commitIt) {
		href = (!href || typeof href !== 'string') ? '' : href;
		commitIt = (!commitIt) ? false : true;
		this.decompose();
		(commitIt) ? this.commit() : (this.href = this.compose());
		return commitIt;
	};
	this.commit = function() { return locObj.href = this.href = this.compose(); };
	this.compose = function() {
		// Build full href from the component fragments, including delimiter characters between each fragment.
		// For most fragments we check that delimiters are not already included, though decompose() should have already ensured thier removal. (belt-and-braces)
		var href = this.protocol + ((this.protocol !== '') ? ":\/\/" : '') +
			   this.hostname +
			   ((this.port     !== '' && !this.pathname.match(/^[:]/))  ? ':' : '') + this.port +
			   (((this.protocol || this.hostname || this.port) && this.pathname && !this.pathname.match(/^[./]/)) ? '/' : '') + this.pathname +
			   ((this.search   !== '' && !this.pathname.match(/^[?]/))  ? '?' : '') + this.search +
			   ((this.hash     !== '' && !this.pathname.match(/^[#]/))  ? '#' : '') + this.hash;
		return href;
	};
	this.decompose = function() {
		this.protocol = locObj.protocol.replace(/:$/, '');//All tested browsers leave the terminal : but let's strip it in case some don't.
		this.hostname = locObj.hostname;//Mmm, nothing to strip.
		this.port     = locObj.port.replace(/^:/, '');//All tested browsers strip the leading : but let's strip it in case some don't.
		this.host     = [this.hostname, this.port].join(':');//Build the composite fragment .host, in case someone expects it.
		this.pathname = locObj.pathname.replace(/^[/]/, '').replace(/\\/g, '/'); //Differences (flaws?) in Firefox and IE6 respectively, so let's standardise.
		this.search   = locObj.search.replace(/^[?]/, '');//All tested browsers leave the leading ? but let's strip it in case some don't
		this.hash     = locObj.hash.replace(/^[#]/, '');//All tested browsers leave the leading # but let's strip it in case some don't
		if(this.compose() === '') { this.pathname = locObj.href; }//Special case for relative urls
	};
	this.getLink = function() {
		if(!locObj.tagName) {// Allow a link to be returned even when a window.location object was passed in as locObj.
			var L = new LOCATION(this.compose());//single level recursion to create a "type 2" link.
			return L.getLink();
		}
		return locObj;
	};
	this.inspect = function(wrapperID) {//included to assist debugging
		// If href and compose() strings are differnt then somthing is wrong; 
		// your url is of an unsupported type and you will need to modify decompose() and/or compose().
		strArray = [];
		strArray.push('<b>protocol</b></td><td>'  + this.protocol);
		strArray.push('<b>hostname</b></td><td>'  + this.hostname);
		strArray.push('<b>port</b></td><td>'      + this.port);
		strArray.push('<b>pathname</b></td><td>'  + this.pathname);
		strArray.push('<b>search</b></td><td>'    + this.search);
		strArray.push('<b>hash</b></td><td>'      + this.hash);
		strArray.push('<b>href</b></td><td>'      + this.href);
		strArray.push('<b>committed</b></td><td>' + locObj.href);
		var str = '<table class="locationInspect" border cellpadding="3"><tr><td>' + strArray.join("</td></tr><tr><td>") + '</td></tr></table>';
		var wrapper = (document.getElementById) ? document.getElementById(wrapperID) : document.all[wrapperID];
		if(wrapper) { wrapper.innerHTML = str; }
		return str;
	};
	this.decompose();
}