WordPress.org

Make WordPress Core

Changeset 40117


Ignore:
Timestamp:
02/24/17 22:47:47 (5 weeks 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.