REST API: JavaScript client - improve route discovery for custom namespaces.

Fix parsing of custom namespace routes. Transform class names, removing dashes and capitalizing each word/route part so a route path of `widgets/recent-posts` becomes a collection with the name `WidgetsRecentPosts`. Correct parent route part when routes are longer than expected, reversing parse direction.

Props westonruter, jazbek.
Fixes #39561.


git-svn-id: https://develop.svn.wordpress.org/trunk@40074 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Adam Silverstein 2017-02-17 20:53:57 +00:00
parent c39fa0ff22
commit 0efaf5ddc5
1 changed files with 67 additions and 23 deletions

View File

@ -119,21 +119,52 @@
return str.charAt( 0 ).toUpperCase() + str.slice( 1 ); return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
}; };
/**
* Helper function that capitalizes the first word and camel cases any words starting
* after dashes, removing the dashes.
*/
wp.api.utils.capitalizeAndCamelCaseDashes = function( str ) {
if ( _.isUndefined( str ) ) {
return str;
}
str = wp.api.utils.capitalize( str );
return wp.api.utils.camelCaseDashes( str );
};
/**
* Helper function to camel case the letter after dashes, removing the dashes.
*/
wp.api.utils.camelCaseDashes = function( str ) {
return str.replace( /-([a-z])/g, function( g ) {
return g[ 1 ].toUpperCase();
} );
};
/** /**
* Extract a route part based on negative index. * Extract a route part based on negative index.
* *
* @param {string} route The endpoint route. * @param {string} route The endpoint route.
* @param {int} part The number of parts from the end of the route to retrieve. Default 1. * @param {int} part The number of parts from the end of the route to retrieve. Default 1.
* Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`. * Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`.
* @param {string} [versionString] Version string, defaults to `wp.api.versionString`.
* @param {boolean} [reverse] Whether to reverse the order when extracting the route part. Optional, default false.
*/ */
wp.api.utils.extractRoutePart = function( route, part ) { wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) {
var routeParts; var routeParts;
part = part || 1; part = part || 1;
versionString = versionString || wp.api.versionString;
// Remove versions string from route to avoid returning it. // Remove versions string from route to avoid returning it.
route = route.replace( wp.api.versionString, '' ); if ( 0 === route.indexOf( '/' + versionString ) ) {
routeParts = route.split( '/' ).reverse(); route = route.substr( versionString.length + 1 );
}
routeParts = route.split( '/' );
if ( reverse ) {
routeParts = routeParts.reverse();
}
if ( _.isUndefined( routeParts[ --part ] ) ) { if ( _.isUndefined( routeParts[ --part ] ) ) {
return ''; return '';
} }
@ -1126,9 +1157,14 @@
// Extract the name and any parent from the route. // Extract the name and any parent from the route.
var modelClassName, var modelClassName,
routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2 ), routeName = wp.api.utils.extractRoutePart( modelRoute.index, 2, routeModel.get( 'versionString' ), true ),
parentName = wp.api.utils.extractRoutePart( modelRoute.index, 4 ), parentName = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), false ),
routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1 ); routeEnd = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), true );
// Clear the parent part of the rouite if its actually the version string.
if ( parentName === routeModel.get( 'versionString' ) ) {
parentName = '';
}
// Handle the special case of the 'me' route. // Handle the special case of the 'me' route.
if ( 'me' === routeEnd ) { if ( 'me' === routeEnd ) {
@ -1137,18 +1173,21 @@
// If the model has a parent in its route, add that to its class name. // If the model has a parent in its route, add that to its class name.
if ( '' !== parentName && parentName !== routeName ) { if ( '' !== parentName && parentName !== routeName ) {
modelClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
modelClassName = mapping.models[ modelClassName ] || modelClassName; modelClassName = mapping.models[ modelClassName ] || modelClassName;
loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
// Return a constructed url based on the parent and id. // Return a constructed url based on the parent and id.
url: function() { url: function() {
var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) + var url =
parentName + '/' + routeModel.get( 'apiRoot' ) +
routeModel.get( 'versionString' ) +
parentName + '/' +
( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ? ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ?
this.get( 'parent_post' ) : ( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) :
this.get( 'parent' ) ) + '/' + this.get( 'parent' ) + '/' ) +
routeName; routeName;
if ( ! _.isUndefined( this.get( 'id' ) ) ) { if ( ! _.isUndefined( this.get( 'id' ) ) ) {
url += '/' + this.get( 'id' ); url += '/' + this.get( 'id' );
} }
@ -1164,7 +1203,8 @@
// Include the array of route methods for easy reference. // Include the array of route methods for easy reference.
methods: modelRoute.route.methods, methods: modelRoute.route.methods,
initialize: function() { initialize: function( attributes, options ) {
wp.api.WPApiBaseModel.prototype.initialize.call( this, attributes, options );
/** /**
* Posts and pages support trashing, other types don't support a trash * Posts and pages support trashing, other types don't support a trash
@ -1184,7 +1224,7 @@
} else { } else {
// This is a model without a parent in its route // This is a model without a parent in its route
modelClassName = wp.api.utils.capitalize( routeName ); modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
modelClassName = mapping.models[ modelClassName ] || modelClassName; modelClassName = mapping.models[ modelClassName ] || modelClassName;
loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( { loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
@ -1212,7 +1252,11 @@
} }
// Add defaults to the new model, pulled form the endpoint. // Add defaults to the new model, pulled form the endpoint.
wp.api.utils.decorateFromRoute( modelRoute.route.endpoints, loadingObjects.models[ modelClassName ] ); wp.api.utils.decorateFromRoute(
modelRoute.route.endpoints,
loadingObjects.models[ modelClassName ],
routeModel.get( 'versionString' )
);
} ); } );
@ -1226,12 +1270,12 @@
// Extract the name and any parent from the route. // Extract the name and any parent from the route.
var collectionClassName, modelClassName, var collectionClassName, modelClassName,
routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ), routeName = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ),
parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 3 ); parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 1, routeModel.get( 'versionString' ), false );
// If the collection has a parent in its route, add that to its class name. // If the collection has a parent in its route, add that to its class name.
if ( '' !== parentName && parentName !== routeName ) { if ( '' !== parentName && parentName !== routeName && routeModel.get( 'versionString' ) !== parentName ) {
collectionClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName ); collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
modelClassName = mapping.models[ collectionClassName ] || collectionClassName; modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {
@ -1260,7 +1304,7 @@
} else { } else {
// This is a collection without a parent in its route. // This is a collection without a parent in its route.
collectionClassName = wp.api.utils.capitalize( routeName ); collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
modelClassName = mapping.models[ collectionClassName ] || collectionClassName; modelClassName = mapping.models[ collectionClassName ] || collectionClassName;
collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName; collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( { loadingObjects.collections[ collectionClassName ] = wp.api.WPApiBaseCollection.extend( {