UNPKG

apostrophe

Version:

Apostrophe is a user-friendly content management system. You'll need more than this core module. See apostrophenow.org to get started.

168 lines (157 loc) 5.36 kB
var _ = require('lodash'); var qs = require('qs'); /** * build * @augments Adds a method providing a convenient means of building URLs with * query string parameters as well as attractive URLs with path components */ module.exports = function(self) { // Add and modify query parameters of a url. data is an object whose properties // become new query parameters. These parameters override any existing // parameters of the same name in the URL. If you pass a property with // a value of undefined, null or an empty string, that parameter is removed from the // URL if already present (note that the number 0 does not do this). This is very // useful for maintaining filter parameters in a query string without redundant code. // // PRETTY URLS // // If the optional `path` argument is present, it must be an array. (You // may skip this argument if you are just adding query parameters.) Any // properties of `data` whose names appear in `path` are concatenated // to the URL directly, separated by slashes, in the order they appear in that // array. The first missing or empty value for a property in `path` stops // this process to prevent an ambiguous URL. // // Note that there is no automatic detection that this has // already happened in an existing URL, so you can't override existing // components of the path. Typically this is used with a snippet index page, // on which the URL of the page is available as a starting point for // building the next URL. // // If a property's value is not equal to the slugification of itself // (apos.slugify), then a query parameter is set instead. This ensures your // URLs are not rejected by the browser. If you don't want to handle a // property as a query parameter, make sure it is always slug-safe. // // OVERRIDES: MULTIPLE DATA OBJECTS // // You may pass additional data objects. The last one wins, so you can // pass your existing parameters first and pass new parameters you are changing // as a second data object. self.build = function(url, path, data) { var hash; // Preserve hash separately var matches = url.match(/^(.*)?\#(.*)$/); if (matches) { url = matches[1]; hash = matches[2]; if (url === undefined) { // Why, JavaScript? Why? -Tom url = ''; } } // Sometimes necessary with nunjucks, we may otherwise be // exposed to a SafeString object and throw an exception. Not // able to boil this down to a simple test case for jlongster so far url = url.toString(); var qat = url.indexOf('?'); var base = url; var dataObjects = []; var pathKeys; var original; var query = {}; var i, j; var key; if (qat !== -1) { original = qs.parse(url.substr(qat + 1)); base = url.substr(0, qat); } var dataStart = 1; if (path && Array.isArray(path)) { pathKeys = path; dataStart = 2; } else { pathKeys = []; } // Process data objects in reverse order so the last override wins for (i = arguments.length - 1; (i >= dataStart); i--) { dataObjects.push(arguments[i]); } if (original) { dataObjects.push(original); } var done = {}; var stop = false; var dataObject; var value; for (i = 0; (i < pathKeys.length); i++) { if (stop) { break; } key = pathKeys[i]; for (j = 0; (j < dataObjects.length); j++) { dataObject = dataObjects[j]; if (dataObject[key] !== undefined) { value = dataObject[key]; // If we hit an empty value we need to stop all path processing to avoid // ambiguous URLs if ((value === undefined) || (value === null) || (value === '')) { done[key] = true; stop = true; break; } // If the value is an object it can't be stored in the path, // so stop path processing, but don't mark this key 'done' // because we can still store it as a query parameter if (typeof(value) === 'object') { stop = true; break; } var s = dataObject[key].toString(); if (s === self.slugify(s)) { // Don't append double / if (base !== '/') { base += '/' + s; } else { base += s; } done[key] = true; break; } else { // A value that cannot be slugified also forces an end to // path processing stop = true; break; } } } } for (i = 0; (i < dataObjects.length); i++) { dataObject = dataObjects[i]; for (key in dataObject) { value = dataObject[key]; if (done[key]) { continue; } done[key] = true; if ((value === undefined) || (value === null) || (value === '')) { delete query[key]; } else { query[key] = value; } } } function restoreHash(url) { if (hash !== undefined) { return url + '#' + hash; } else { return url; } } if (_.size(query)) { return restoreHash(base + '?' + qs.stringify(query)); } else { return restoreHash(base); } }; };