Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/shared/alphanumeric-sort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

/**
* Utility function for sorting arrays of numbers or strings.
*
* @param {String|Number} a The first comparator operand
* @param {String|Number} a The second comparator operand
* @return -1 if the values are backwards, 1 if they're ordered, and 0 if they're the same
*/
function alphaNumericSort( a, b ) {
if ( a > b ) {
return 1;
}
if ( a < b ) {
return -1;
}
return 0;
}

module.exports = alphaNumericSort;
163 changes: 2 additions & 161 deletions lib/shared/collection-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ var WPRequest = require( './wp-request' );
var _ = require( 'lodash' );
var extend = require( 'node.extend' );
var inherit = require( 'util' ).inherits;
var qs = require( 'qs' );

var alphaNumericSort = require( './alphanumeric-sort' );

/**
* CollectionRequest extends WPRequest with properties & methods for filtering collections
Expand Down Expand Up @@ -102,38 +103,6 @@ inherit( CollectionRequest, WPRequest );
// Private helper methods
// ======================

/**
* Process arrays of taxonomy terms into query parameters.
* All terms listed in the arrays will be required (AND behavior).
*
* @example
* prepareTaxonomies({
* tag: [ 'tag1 ', 'tag2' ], // by term slug
* cat: [ 7 ] // by term ID
* }) === {
* tag: 'tag1+tag2',
* cat: '7'
* }
*
* @param {Object} taxonomyFilters An object of taxonomy term arrays, keyed by taxonomy name
* @return {Object} An object of prepareFilters-ready query arg and query param value pairs
*/
function prepareTaxonomies( taxonomyFilters ) {
if ( ! taxonomyFilters ) {
return [];
}

return _.reduce( taxonomyFilters, function( result, terms, key ) {
// Trim whitespace and concatenate multiple terms with +
result[ key ] = terms.map(function( term ) {
// Coerce term into a string so that trim() won't fail
term = term + '';
return term.trim().toLowerCase();
}).join( '+' );
return result;
}, {});
}

/**
* Utility function for sorting arrays of numbers or strings.
*
Expand All @@ -154,95 +123,6 @@ function alphaNumericSort( a, b ) {
// Prototype Methods
// =================

/**
* Process the endpoint query's filter objects into a valid query string.
* Nested objects and Array properties are rendered with indexed array syntax.
*
* @example
* _renderQuery({ p1: 'val1', p2: 'val2' }); // ?p1=val1&p2=val2
* _renderQuery({ obj: { prop: 'val' } }); // ?obj[prop]=val
* _renderQuery({ arr: [ 'val1', 'val2' ] }); // ?arr[0]=val1&arr[1]=val2
*
* @private
*
* @method _renderQuery
* @return {String} A query string representing the specified filter parameters
*/
CollectionRequest.prototype._renderQuery = function() {
// Build the full query parameters object
var queryParams = extend( {}, this._params );

// Prepare the taxonomies and merge with other filter values
var taxonomies = prepareTaxonomies( this._taxonomyFilters );
queryParams.filter = extend( {}, this._filters, taxonomies );

// Parse query parameters object into a query string, sorting the object
// properties by alphabetical order (consistent property ordering can make
// for easier caching of request URIs)
var queryString = qs.stringify( queryParams, { arrayFormat: 'brackets' } )
.split( '&' )
.sort()
.join( '&' );

// Prepend a "?" if a query is present, and return
return ( queryString === '' ) ? '' : '?' + queryString;
};

/**
* Set a parameter to render into the final query URI.
*
* @method param
* @chainable
* @param {String|Object} props The name of the parameter to set, or an object containing
* parameter keys and their corresponding values
* @param {String|Number|Array} [value] The value of the parameter being set
* @param {Boolean} [merge] Whether to merge the value (true) or replace it (false, default)
* @return {CollectionRequest} The CollectionRequest instance (for chaining)
*/
CollectionRequest.prototype.param = function( props, value, merge ) {
merge = merge || false;

// We can use the same iterator function below to handle explicit key-value pairs if we
// convert them into to an object we can iterate over:
if ( _.isString( props ) && value ) {
props = _.zipObject([[ props, value ]]);
}

// Iterate through the properties
_.each( props, function( value, key ) {
var currentVal = this._params[ key ];

// Simple case: setting for the first time, or not merging
if ( ! currentVal || ! merge ) {

// Arrays should be de-duped and sorted
if ( _.isArray( value ) ) {
value = _.unique( value ).sort( alphaNumericSort );
}

// Set the value
this._params[ key ] = value;

// Continue
return;
}

// value and currentVal must both be arrays in order to merge
if ( ! _.isArray( currentVal ) ) {
currentVal = [ currentVal ];
}

if ( ! _.isArray( value ) ) {
value = [ value ];
}

// Concat the new values onto the old (and sort)
this._params[ key ] = _.union( currentVal, value ).sort( alphaNumericSort );
}.bind( this ));

return this;
};

/**
* Set the pagination of a request. Use in conjunction with `.perPage()` for explicit
* pagination handling. (The number of pages in a response can be retrieved from the
Expand All @@ -269,45 +149,6 @@ CollectionRequest.prototype.perPage = function( itemsPerPage ) {
return this.param( 'per_page', itemsPerPage );
};

/**
* Set the context of the request. Used primarily to expose private values on a request
* object, by setting the context to "edit".
*
* @method context
* @chainable
* @param {String} context The context to set on the request
* @return {CollectionRequest} The CollectionRequest instance (for chaining)
*/
CollectionRequest.prototype.context = function( context ) {
if ( context === 'edit' ) {
// Force basic authentication for edit context
this.auth();
}
return this.param( 'context', context );
};

/**
* Convenience wrapper for `.context( 'edit' )`
*
* @method edit
* @chainable
* @return {CollectionRequest} The CollectionRequest instance (for chaining)
*/
CollectionRequest.prototype.edit = function() {
return this.context( 'edit' );
};

/**
* Return embedded resources as part of the response payload.
*
* @method embed
* @chainable
* @return {CollectionRequest} The CollectionRequest instance (for chaining)
*/
CollectionRequest.prototype.embed = function() {
return this.param( '_embed', true );
};

/**
* Specify key-value pairs by which to filter the API results (commonly used
* to retrieve only posts meeting certain criteria, such as posts within a
Expand Down
Loading