Make WordPress Core

Changeset 40117


Ignore:
Timestamp:
02/24/2017 10:47:47 PM (7 years ago)
Author:
SergeyBiryukov
Message:

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, adamsilverstein, jnylen0.
Merges [40074] and [40109] to the 4.7 branch.
Fixes #39561.

Location:
branches/4.7
Files:
5 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/4.7

  • branches/4.7/src/wp-includes/js/wp-api.js

    r40084 r40117  
    121121
    122122    /**
     123     * Helper function that capitalizes the first word and camel cases any words starting
     124     * after dashes, removing the dashes.
     125     */
     126    wp.api.utils.capitalizeAndCamelCaseDashes = function( str ) {
     127        if ( _.isUndefined( str ) ) {
     128            return str;
     129        }
     130        str = wp.api.utils.capitalize( str );
     131
     132        return wp.api.utils.camelCaseDashes( str );
     133    };
     134
     135    /**
     136     * Helper function to camel case the letter after dashes, removing the dashes.
     137     */
     138    wp.api.utils.camelCaseDashes = function( str ) {
     139        return str.replace( /-([a-z])/g, function( g ) {
     140            return g[ 1 ].toUpperCase();
     141        } );
     142    };
     143
     144    /**
    123145     * Extract a route part based on negative index.
    124146     *
    125      * @param {string} route The endpoint route.
    126      * @param {int}    part  The number of parts from the end of the route to retrieve. Default 1.
    127      *                       Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`.
    128      */
    129     wp.api.utils.extractRoutePart = function( route, part ) {
     147     * @param {string}   route          The endpoint route.
     148     * @param {int}      part           The number of parts from the end of the route to retrieve. Default 1.
     149     *                                  Example route `/a/b/c`: part 1 is `c`, part 2 is `b`, part 3 is `a`.
     150     * @param {string}  [versionString] Version string, defaults to `wp.api.versionString`.
     151     * @param {boolean} [reverse]       Whether to reverse the order when extracting the route part. Optional, default false.
     152     */
     153    wp.api.utils.extractRoutePart = function( route, part, versionString, reverse ) {
    130154        var routeParts;
    131155
    132         part  = part || 1;
     156        part = part || 1;
     157        versionString = versionString || wp.api.versionString;
    133158
    134159        // Remove versions string from route to avoid returning it.
    135         route = route.replace( wp.api.versionString, '' );
    136         routeParts = route.split( '/' ).reverse();
     160        if ( 0 === route.indexOf( '/' + versionString ) ) {
     161            route = route.substr( versionString.length + 1 );
     162        }
     163
     164        routeParts = route.split( '/' );
     165        if ( reverse ) {
     166            routeParts = routeParts.reverse();
     167        }
    137168        if ( _.isUndefined( routeParts[ --part ] ) ) {
    138169            return '';
     
    11271158                // Extract the name and any parent from the route.
    11281159                var modelClassName,
    1129                         routeName  = wp.api.utils.extractRoutePart( modelRoute.index, 2 ),
    1130                         parentName = wp.api.utils.extractRoutePart( modelRoute.index, 4 ),
    1131                         routeEnd   = wp.api.utils.extractRoutePart( modelRoute.index, 1 );
     1160                    routeName  = wp.api.utils.extractRoutePart( modelRoute.index, 2, routeModel.get( 'versionString' ), true ),
     1161                    parentName = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), false ),
     1162                    routeEnd   = wp.api.utils.extractRoutePart( modelRoute.index, 1, routeModel.get( 'versionString' ), true );
     1163
     1164                // Clear the parent part of the rouite if its actually the version string.
     1165                if ( parentName === routeModel.get( 'versionString' ) ) {
     1166                    parentName = '';
     1167                }
    11321168
    11331169                // Handle the special case of the 'me' route.
     
    11381174                // If the model has a parent in its route, add that to its class name.
    11391175                if ( '' !== parentName && parentName !== routeName ) {
    1140                     modelClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName );
     1176                    modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
    11411177                    modelClassName = mapping.models[ modelClassName ] || modelClassName;
    11421178                    loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
     
    11441180                        // Return a constructed url based on the parent and id.
    11451181                        url: function() {
    1146                             var url = routeModel.get( 'apiRoot' ) + routeModel.get( 'versionString' ) +
    1147                                     parentName +  '/' +
     1182                            var url =
     1183                                routeModel.get( 'apiRoot' ) +
     1184                                routeModel.get( 'versionString' ) +
     1185                                parentName +  '/' +
    11481186                                    ( ( _.isUndefined( this.get( 'parent' ) ) || 0 === this.get( 'parent' ) ) ?
    1149                                         this.get( 'parent_post' ) :
    1150                                         this.get( 'parent' ) ) + '/' +
    1151                                     routeName;
     1187                                        ( _.isUndefined( this.get( 'parent_post' ) ) ? '' : this.get( 'parent_post' ) + '/' ) :
     1188                                        this.get( 'parent' ) + '/' ) +
     1189                                routeName;
     1190
    11521191                            if ( ! _.isUndefined( this.get( 'id' ) ) ) {
    11531192                                url +=  '/' + this.get( 'id' );
     
    11651204                        methods: modelRoute.route.methods,
    11661205
    1167                         initialize: function() {
     1206                        initialize: function( attributes, options ) {
     1207                            wp.api.WPApiBaseModel.prototype.initialize.call( this, attributes, options );
    11681208
    11691209                            /**
     
    11851225
    11861226                    // This is a model without a parent in its route
    1187                     modelClassName = wp.api.utils.capitalize( routeName );
     1227                    modelClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
    11881228                    modelClassName = mapping.models[ modelClassName ] || modelClassName;
    11891229                    loadingObjects.models[ modelClassName ] = wp.api.WPApiBaseModel.extend( {
     
    12131253
    12141254                // Add defaults to the new model, pulled form the endpoint.
    1215                 wp.api.utils.decorateFromRoute( modelRoute.route.endpoints, loadingObjects.models[ modelClassName ] );
     1255                wp.api.utils.decorateFromRoute(
     1256                    modelRoute.route.endpoints,
     1257                    loadingObjects.models[ modelClassName ],
     1258                    routeModel.get( 'versionString' )
     1259                );
    12161260
    12171261            } );
     
    12271271                var collectionClassName, modelClassName,
    12281272                        routeName  = collectionRoute.index.slice( collectionRoute.index.lastIndexOf( '/' ) + 1 ),
    1229                         parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 3 );
     1273                        parentName = wp.api.utils.extractRoutePart( collectionRoute.index, 1, routeModel.get( 'versionString' ), false );
    12301274
    12311275                // If the collection has a parent in its route, add that to its class name.
    1232                 if ( '' !== parentName && parentName !== routeName ) {
    1233 
    1234                     collectionClassName = wp.api.utils.capitalize( parentName ) + wp.api.utils.capitalize( routeName );
     1276                if ( '' !== parentName && parentName !== routeName && routeModel.get( 'versionString' ) !== parentName ) {
     1277
     1278                    collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( parentName ) + wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
    12351279                    modelClassName      = mapping.models[ collectionClassName ] || collectionClassName;
    12361280                    collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
     
    12611305
    12621306                    // This is a collection without a parent in its route.
    1263                     collectionClassName = wp.api.utils.capitalize( routeName );
     1307                    collectionClassName = wp.api.utils.capitalizeAndCamelCaseDashes( routeName );
    12641308                    modelClassName      = mapping.models[ collectionClassName ] || collectionClassName;
    12651309                    collectionClassName = mapping.collections[ collectionClassName ] || collectionClassName;
  • branches/4.7/tests/qunit/fixtures/wp-api.js

    r40116 r40117  
    1 /* global mockedApiResponse, Backbone */
     1/* global mockedApiResponse, Backbone, jsWidgetsEndpointSchema */
    22/**
    33 * @var mockedApiResponse defined in wp-api-generated.js
     
    2424    'wp-json/wp/v2/taxonomy': mockedApiResponse.TaxonomyModel,
    2525    'wp-json/wp/v2/status': mockedApiResponse.StatusModel,
    26     'wp-json/wp/v2/type': mockedApiResponse.TypeModel
     26    'wp-json/wp/v2/type': mockedApiResponse.TypeModel,
     27    'wp-json/js-widgets/v1/': jsWidgetsEndpointSchema
    2728};
    2829
  • branches/4.7/tests/qunit/index.html

    r40116 r40117  
    4141            <script src="fixtures/customize-widgets.js"></script>
    4242            <script src="fixtures/wp-api-generated.js"></script>
     43            <script src="fixtures/js-widgets-endpoint.js"></script>
    4344            <script src="fixtures/wp-api.js"></script>
    4445        </div>
  • branches/4.7/tests/qunit/wp-includes/js/wp-api.js

    r40116 r40117  
    193193    } );
    194194
     195// Test the jswidget custom namespace and endpoints.
     196wp.api.init( {
     197    'versionString': 'js-widgets/v1/'
     198} ).done( function() {
     199        var customClasses = [
     200            'WidgetsArchives',
     201            'WidgetsCalendar',
     202            'WidgetsCategories',
     203            'WidgetsMeta',
     204            'WidgetsNav_menu',
     205            'WidgetsPages',
     206            'WidgetsPostCollection',
     207            'WidgetsRecentComments',
     208            'WidgetsRecentPosts',
     209            'WidgetsRss',
     210            'WidgetsSearch',
     211            'WidgetsTag_cloud',
     212            'WidgetsText'
     213        ];
     214
     215        // Check that we have and can get each model type.
     216        _.each( customClasses, function( className ) {
     217            QUnit.test( 'Checking ' + className + ' class name.' , function( assert ) {
     218                var done = assert.async();
     219
     220                assert.expect( 2 );
     221
     222                wp.api.loadPromise.done( function() {
     223                    var theModel = new wp.api.models[ className ]();
     224                    assert.ok( theModel, 'We can instantiate wp.api.models.' + className );
     225                    var theCollection = new wp.api.collections[ className ]();
     226                    assert.ok( theCollection, 'We can instantiate wp.api.collections.' + className );
     227                    // Trigger Qunit async completion.
     228                    done();
     229                } );
     230            } );
     231        } );
     232
     233    } );
     234
    195235} )( window.QUnit );
Note: See TracChangeset for help on using the changeset viewer.