/*! jQueryHref - v0.1.0 - 2016-06-26 * https://github.com/cheeserolls/jQueryHref * Copyright (c) 2016 Dan Howard; Licensed MIT */ (function($) { /*** Internal Methods ***/ // Given an element, decompose the link into its component parts var _decomposeLink = function(a) { var temp = { 'protocol':a.protocol.replace(/:?\/?\/?$/,''), // normalise by removing trailing colon and slashes 'host':a.host, 'path':a.pathname.replace(/^\//,''), // normalise by removing leading slash 'query':_deparam(a.search.replace(/^\?/,'')), // normalise by removing question mark, split query string into components as an object 'hash':a.hash.replace(/^#/,'') // normalise by removing leading hash }; temp.href = _composeLink(temp); return temp; }; // Assemble the parts of a link back into a single string var _composeLink = function(parts) { var q = (parts.query ? $.param(parts.query) : false); return parts.protocol + '://' + parts.host + (parts.path ? '/' + parts.path : '') + (q ? '?'+q : '') + (parts.hash ? '#'+parts.hash : '') ; }; // Utility for decomposing a query string into an object containing the name/value pairs // This is an extraction of the deparam method from Ben Alman's jQuery BBQ // http://benalman.com/projects/jquery-bbq-plugin/ var _deparam = function (params, coerce) { var obj = {}, coerce_types = { 'true': !0, 'false': !1, 'null': null }; // Iterate over all name=value pairs. $.each(params.replace(/\+/g, ' ').split('&'), function (j,v) { var param = v.split('='), key = decodeURIComponent(param[0]), val, cur = obj, i = 0; // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it // into its component parts. var keys = key.split(']['), keys_last = keys.length - 1; // If the first keys part contains [ and the last ends with ], then [] // are correctly balanced. if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) { // Remove the trailing ] from the last keys part. keys[keys_last] = keys[keys_last].replace( /\]$/ , ''); // Split first keys part into two parts on the [ and add them back onto // the beginning of the keys array. keys = keys.shift().split('[').concat(keys); keys_last = keys.length - 1; } else { // Basic 'foo' style key. keys_last = 0; } // Are we dealing with a name=value pair, or just a name? if (param.length === 2) { val = decodeURIComponent(param[1]); // Coerce values. if (coerce) { val = val && !isNaN(val) ? +val // number : val === 'undefined' ? undefined // undefined : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null : val; // string } if ( keys_last ) { // Complex key, build deep object structure based on a few rules: // * The 'cur' pointer starts at the object top-level. // * [] = array push (n is set to array length), [n] = array if n is // numeric, otherwise object. // * If at the last keys part, set the value. // * For each keys part, if the current level is undefined create an // object or array based on the type of the next keys part. // * Move the 'cur' pointer to the next level. // * Rinse & repeat. for (; i <= keys_last; i++) { key = keys[i] === '' ? cur.length : keys[i]; cur = cur[key] = i < keys_last ? cur[key] || (keys[i+1] && isNaN(keys[i+1]) ? {} : []) : val; } } else { // Simple key, even simpler rules, since only scalars and shallow // arrays are allowed. if ($.isArray(obj[key])) { // val is already an array, so push on the next value. obj[key].push( val ); } else if (obj[key] !== undefined) { // val isn't an array, but since a second value has been specified, // convert val into an array. obj[key] = [obj[key], val]; } else { // val is a scalar. obj[key] = val; } } } else if (key) { // No value was defined, so set something meaningful. obj[key] = coerce ? undefined : ''; } }); return obj; }; /*** Collection method ***/ // define setter functions var _setters = { 'protocol': function(arg, temp) { if (arg == null) {arg = window.document.location.protocol;} temp.protocol = arg.toString().replace(/:?\/?\/?$/,''); }, 'host': function(arg, temp) { if (arg == null) {arg = window.document.location.hostname;} temp.host = arg.toString(); }, 'path': function(arg, temp) { if (arg == null) {arg = '';} temp.path = arg.toString().replace(/^\//,''); }, 'query': function(arg, temp) { $.extend(temp.query,arg); }, 'query.replace': function(arg, temp) { temp.query = arg; }, 'hash': function(arg, temp) { temp.hash = arg ? arg.toString().replace(/^#/,'') : null; }, 'href': function(arg, temp) { if (arg == null) {arg = window.document.location.href;} this.attr('href',arg.toString()); $.extend(temp, _decomposeLink(this[0])); } }; $.fn.href = function() { // Only works on a elements if (this[0].nodeName!=='A') {return this;} // if no href is set yet for this element, then use the current doc location if (!this[0].href) {this[0].href = window.document.location.href;} // no arguments - return the href if (arguments.length===0) {return this[0].href;} // break apart the sections of the link so that they can be separately manipulated var temp = _decomposeLink(this[0]); // one string argument - assume we're getting a component of the url // If no named component of the url matches, then assume we're setting the entire href if (arguments.length===1 && (typeof arguments[0]==='string')) { if (temp.hasOwnProperty(arguments[0])) { return temp[arguments[0]]; } else { this.attr('href',arguments[0]); return this; } } // 2 arguments, first is a component of the url - set that component if (arguments.length===2 && _setters[arguments[0]]) { _setters[arguments[0]].call(this, arguments[1], temp); } // one argument which is an object - setting multiple components of the url at the same time // keys should be named components of the url if (arguments.length===1 && (typeof arguments[0]==='object')) { for (var i in arguments[0]) { if (_setters[i]) {_setters[i].call(this, arguments[0][i], temp);} } } return this.attr('href',_composeLink(temp)); }; /*** Static method ***/ // Get a jQuery set containing an element with the supplied url as it's href attribute (or the current location if no url supplied) $.href = function() { return $('').attr('href', arguments.length > 0 ? arguments[0] : window.document.location.href); }; /*** Utility methods ****/ // Utility for determining whether 2 URLs are the same $.href.linkEquals = function(a,b) { // both arguments can be strings, elements, or jQuery objects whose first element is an element - anything else, return false if (typeof(a)==='string') {a = $('').attr('href',a);} if (a instanceof $) {a = a[0];} if (a && (a.nodeName==='A')) { a = _decomposeLink(a); } else { return false; } if (typeof(b)==='string') {b = $('').attr('href',b);} if (b instanceof $) {b = b[0];} if (b && (b.nodeName==='A')) { b = _decomposeLink(b); } else { return false; } // all parts have to be equal, but we sort the query params before comparing because the order of the parameters shouldn't matter return (a.protocol===b.protocol) && (a.host===b.host) && (a.path===b.path) && (a.hash===b.hash) && ($.param(a.query).split('&').sort().join('&')===$.param(b.query).split('&').sort().join('&')); }; $.href.home = function() { return $.href().href({path:null,'query.replace':null,hash:null}); }; /*** Expose internal methods - might sometimes be useful in their own right ***/ $.href.decomposeLink = _decomposeLink; $.href.composeLink = _composeLink; $.href.deparam = _deparam; /*** Custom Selector ***/ // Allow matching internal links with :href-internal and external with :href-external $.expr[':']['href-internal'] = function(obj,index,meta,stack) {/* jshint unused: false */ return obj.hostname && obj.hostname===document.location.hostname;}; $.expr[':']['href-external'] = function(obj,index,meta,stack) {/* jshint unused: false */ return obj.hostname && obj.hostname!==document.location.hostname;}; }(jQuery));