Make WordPress Core

Changeset 40117


Ignore:
Timestamp:
02/24/2017 10:47:47 PM (9 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.