nodulator
Version:
Complete NodeJS Framework for Restfull APIs
230 lines (200 loc) • 6.3 kB
JavaScript
/*
* Copyright 2012-2013 the original author or authors
* @license MIT, see LICENSE.txt for details
*
* @author Scott Andrews
*/
(function (define, location) {
'use strict';
var undef;
define(function (require) {
var mixin, origin, urlRE, absoluteUrlRE, fullyQualifiedUrlRE;
mixin = require('./util/mixin');
urlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?(\/[^?#]*)?(\?[^#]*)?(#\S*)?/i;
absoluteUrlRE = /^([a-z][a-z0-9\-\+\.]*:\/\/|\/)/i;
fullyQualifiedUrlRE = /([a-z][a-z0-9\+\-\.]*:)\/\/([^@]+@)?(([^:\/]+)(:([0-9]+))?)?\//i;
/**
* Apply params to the template to create a URL.
*
* Parameters that are not applied directly to the template, are appended
* to the URL as query string parameters.
*
* @param {string} template the URI template
* @param {Object} params parameters to apply to the template
* @return {string} the resulting URL
*/
function buildUrl(template, params) {
// internal builder to convert template with params.
var url, name, queryStringParams, re;
url = template;
queryStringParams = {};
if (params) {
for (name in params) {
/*jshint forin:false */
re = new RegExp('\\{' + name + '\\}');
if (re.test(url)) {
url = url.replace(re, encodeURIComponent(params[name]), 'g');
}
else {
queryStringParams[name] = params[name];
}
}
for (name in queryStringParams) {
url += url.indexOf('?') === -1 ? '?' : '&';
url += encodeURIComponent(name);
if (queryStringParams[name] !== null && queryStringParams[name] !== undefined) {
url += '=';
url += encodeURIComponent(queryStringParams[name]);
}
}
}
return url;
}
function startsWith(str, test) {
return str.indexOf(test) === 0;
}
/**
* Create a new URL Builder
*
* @param {string|UrlBuilder} template the base template to build from, may be another UrlBuilder
* @param {Object} [params] base parameters
* @constructor
*/
function UrlBuilder(template, params) {
if (!(this instanceof UrlBuilder)) {
// invoke as a constructor
return new UrlBuilder(template, params);
}
if (template instanceof UrlBuilder) {
this._template = template.template;
this._params = mixin({}, this._params, params);
}
else {
this._template = (template || '').toString();
this._params = params || {};
}
}
UrlBuilder.prototype = {
/**
* Create a new UrlBuilder instance that extends the current builder.
* The current builder is unmodified.
*
* @param {string} [template] URL template to append to the current template
* @param {Object} [params] params to combine with current params. New params override existing params
* @return {UrlBuilder} the new builder
*/
append: function (template, params) {
// TODO consider query strings and fragments
return new UrlBuilder(this._template + template, mixin({}, this._params, params));
},
/**
* Create a new UrlBuilder with a fully qualified URL based on the
* window's location or base href and the current templates relative URL.
*
* Path variables are preserved.
*
* *Browser only*
*
* @return {UrlBuilder} the fully qualified URL template
*/
fullyQualify: function () {
if (!location) { return this; }
if (this.isFullyQualified()) { return this; }
var template = this._template;
if (startsWith(template, '//')) {
template = origin.protocol + template;
}
else if (startsWith(template, '/')) {
template = origin.origin + template;
}
else if (!this.isAbsolute()) {
template = origin.origin + origin.pathname.substring(0, origin.pathname.lastIndexOf('/') + 1);
}
if (template.indexOf('/', 8) === -1) {
// default the pathname to '/'
template = template + '/';
}
return new UrlBuilder(template, this._params);
},
/**
* True if the URL is absolute
*
* @return {boolean}
*/
isAbsolute: function () {
return absoluteUrlRE.test(this.build());
},
/**
* True if the URL is fully qualified
*
* @return {boolean}
*/
isFullyQualified: function () {
return fullyQualifiedUrlRE.test(this.build());
},
/**
* True if the URL is cross origin. The protocol, host and port must not be
* the same in order to be cross origin,
*
* @return {boolean}
*/
isCrossOrigin: function () {
if (!origin) {
return true;
}
var url = this.parts();
return url.protocol !== origin.protocol ||
url.hostname !== origin.hostname ||
url.port !== origin.port;
},
/**
* Split a URL into its consituent parts following the naming convention of
* 'window.location'. One difference is that the port will contain the
* protocol default if not specified.
*
* @see https://developer.mozilla.org/en-US/docs/DOM/window.location
*
* @returns {Object} a 'window.location'-like object
*/
parts: function () {
/*jshint maxcomplexity:20 */
var url, parts;
url = this.fullyQualify().build().match(urlRE);
parts = {
href: url[0],
protocol: url[1],
host: url[3] || '',
hostname: url[4] || '',
port: url[6],
pathname: url[7] || '',
search: url[8] || '',
hash: url[9] || ''
};
parts.origin = parts.protocol + '//' + parts.host;
parts.port = parts.port || (parts.protocol === 'https:' ? '443' : parts.protocol === 'http:' ? '80' : '');
return parts;
},
/**
* Expand the template replacing path variables with parameters
*
* @param {Object} [params] params to combine with current params. New params override existing params
* @return {string} the expanded URL
*/
build: function (params) {
return buildUrl(this._template, mixin({}, this._params, params));
},
/**
* @see build
*/
toString: function () {
return this.build();
}
};
origin = location ? new UrlBuilder(location.href).parts() : undef;
return UrlBuilder;
});
}(
typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); },
typeof window !== 'undefined' ? window.location : void 0
// Boilerplate for AMD and Node
));