modules/Location.js
var d = require('describe-property');
var mergeQuery = require('./utils/mergeQuery');
var stringifyQuery = require('./utils/stringifyQuery');
var parseQuery = require('./utils/parseQuery');
var parseURL = require('./utils/parseURL');
/**
* Standard ports for HTTP protocols.
*/
var STANDARD_PORTS = {
'http:': '80',
'https:': '443'
};
function propertyAlias(propertyName, defaultValue) {
return d.gs(function () {
return this.properties[propertyName] || (defaultValue == null ? null : defaultValue);
}, function (value) {
this.properties[propertyName] = value;
});
}
// Order is important here. Later properties take priority.
var PROPERTY_NAMES = [ 'protocol', 'auth', 'hostname', 'port', 'host', 'pathname', 'search', 'queryString', 'query', 'path' ];
function setProperties(location, properties) {
var propertyName;
for (var i = 0, len = PROPERTY_NAMES.length; i < len; ++i) {
propertyName = PROPERTY_NAMES[i];
if (properties.hasOwnProperty(propertyName) && propertyName in location)
location[propertyName] = properties[propertyName];
}
}
/**
* A URL location, analogous to window.location.
*
* Options may be any of the following:
*
* - protocol
* - auth
* - hostname
* - port
* - host (overrides hostname and port)
* - pathname
* - search
* - queryString (overrides search)
* - query (overrides queryString/search)
* - path (overrides pathname and query/queryString/search)
*
* Alternatively, options may be a URL string.
*/
function Location(options) {
this.properties = {};
if (typeof options === 'string') {
this.href = options;
} else if (options) {
setProperties(this, options);
}
}
Object.defineProperties(Location.prototype, {
/**
* Creates and returns a new Location with the path and query of
* the given location appended.
*/
concat: d(function (location) {
if (!(location instanceof Location))
location = new Location(location);
var pathname = this.pathname;
var extraPathname = location.pathname;
if (extraPathname !== '/')
pathname = pathname.replace(/\/*$/, '/') + extraPathname.replace(/^\/*/, '');
var query = mergeQuery(this.query, location.query);
return new Location({
protocol: location.protocol || this.protocol,
auth: location.auth || this.auth,
hostname: location.hostname || this.hostname,
port: location.port || this.port,
pathname: pathname,
query: query
});
}),
/**
* The full URL.
*/
href: d.gs(function () {
var auth = this.auth;
var host = this.host;
var path = this.path;
return host ? (this.protocol + '//' + (auth ? auth + '@' : '') + host + path) : path;
}, function (value) {
var parsed = parseURL(value);
setProperties(this, {
protocol: parsed.protocol,
auth: parsed.auth,
hostname: parsed.hostname,
port: parsed.port,
pathname: parsed.pathname,
search: parsed.search
});
}),
/**
* The portion of the URL that denotes the protocol, including the
* trailing colon (e.g. "http:" or "https:").
*/
protocol: propertyAlias('protocol'),
/**
* The username:password used in the URL, if any.
*/
auth: propertyAlias('auth', ''),
/**
* The full name of the host, including the port number when using
* a non-standard port.
*/
host: d.gs(function () {
var protocol = this.protocol;
var host = this.hostname;
var port = this.port;
if (port != null && port !== STANDARD_PORTS[protocol])
host += ':' + port;
return host;
}, function (value) {
var index;
if (typeof value === 'string' && (index = value.indexOf(':')) !== -1) {
this.hostname = value.substring(0, index);
this.port = value.substring(index + 1);
} else {
this.hostname = value;
this.port = null;
}
}),
/**
* The name of the host without the port.
*/
hostname: propertyAlias('hostname'),
/**
* The port number as a string.
*/
port: d.gs(function () {
return this.properties.port || (this.protocol ? STANDARD_PORTS[this.protocol] : null);
}, function (value) {
this.properties.port = value ? String(value) : null;
}),
/**
* The URL path without the query string.
*/
pathname: propertyAlias('pathname', '/'),
/**
* The URL path with query string.
*/
path: d.gs(function () {
return this.pathname + this.search;
}, function (value) {
var index;
if (typeof value === 'string' && (index = value.indexOf('?')) !== -1) {
this.pathname = value.substring(0, index);
this.search = value.substring(index);
} else {
this.pathname = value;
this.search = null;
}
}),
/**
* The query string, including the preceeding ?.
*/
search: propertyAlias('search', ''),
/**
* The query string of the URL, without the preceeding ?.
*/
queryString: d.gs(function () {
return this.search.substring(1);
}, function (value) {
this.search = value && '?' + value;
}),
/**
* An object of data in the query string.
*/
query: d.gs(function () {
return parseQuery(this.queryString);
}, function (value) {
this.queryString = stringifyQuery(value);
}),
toJSON: d(function () {
return this.href;
}),
toString: d(function () {
return this.href;
})
});
module.exports = Location;