UNPKG

nodulator

Version:

Complete NodeJS Framework for Restfull APIs

230 lines (200 loc) 6.3 kB
/* * 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 ));