Make WordPress Core

Changeset 21898


Ignore:
Timestamp:
09/18/2012 09:42:29 PM (14 years ago)
Author:
koopersmith
Message:

Allow JS Attachments models to be searchable and sortable.

Moves wp.media.model.Query sorting and searching to the parent wp.media.model.Attachments.

Query parameters are stored in attachments.props, which is a Backbone.Model, and supports order ('ASC' or 'DESC'), orderby (any Attachment model property name), search (a search term), and query (a boolean value that ties the Attachments collection to the server).

wp.media.query( args ) now returns an Attachments set that is mapped to a Query collection instead of the Query collection itself. This allows you to change the query arguments by updating attachments.props instead of fetching the mirrored arguments, changing them, and passing them to wp.media.query() again.

fixes #21921, see #21390, #21809.

Location:
trunk/wp-includes/js
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/wp-includes/js/media-models.js

    r21820 r21898  
    33
    44(function($){
    5         var Attachment, Attachments, Query;
     5        var Attachment, Attachments, Query, compare;
    66
    77        /**
    … …  
    2727         * ========================================================================
    2828         */
     29
     30        /**
     31         * A basic comparator.
     32         *
     33         * @param  {mixed}  a  The primary parameter to compare.
     34         * @param  {mixed}  b  The primary parameter to compare.
     35         * @param  {string} ac The fallback parameter to compare, a's cid.
     36         * @param  {string} bc The fallback parameter to compare, b's cid.
     37         * @return {number}    -1: a should come before b.
     38         *                      0: a and b are of the same rank.
     39         *                      1: b should come before a.
     40         */
     41        compare = function( a, b, ac, bc ) {
     42                if ( _.isEqual( a, b ) )
     43                        return ac === bc ? 0 : (ac > bc ? -1 : 1);
     44                else
     45                        return a > b ? -1 : 1;
     46        };
    2947
    3048        _.extend( media, {
    … …  
    160178                        options = options || {};
    161179
     180                        this.props   = new Backbone.Model();
    162181                        this.filters = options.filters || {};
    163182
     183                        // Bind default `change` events to the `props` model.
     184                        this.props.on( 'change:order',   this._changeOrder,   this );
     185                        this.props.on( 'change:orderby', this._changeOrderby, this );
     186                        this.props.on( 'change:query',   this._changeQuery,   this );
     187                        this.props.on( 'change:search',  this._changeSearch,  this );
     188
     189                        // Set the `props` model and fill the default property values.
     190                        this.props.set( _.defaults( options.props || {}, {
     191                                order: 'DESC'
     192                        }) );
     193
     194                        // Observe another `Attachments` collection if one is provided.
    164195                        if ( options.observe )
    165196                                this.observe( options.observe );
    166 
    167                         if ( options.mirror )
    168                                 this.mirror( options.mirror );
     197                },
     198
     199                // Automatically sort the collection when the order changes.
     200                _changeOrder: function( model, order ) {
     201                        if ( this.comparator )
     202                                this.sort();
     203                },
     204
     205                // Set the default comparator only when the `orderby` property is set.
     206                _changeOrderby: function( model, orderby ) {
     207                        // If a different comparator is defined, bail.
     208                        if ( this.comparator && this.comparator !== Attachments.comparator )
     209                                return;
     210
     211                        if ( orderby )
     212                                this.comparator = Attachments.comparator;
     213                        else
     214                                delete this.comparator;
     215                },
     216
     217                // If the `query` property is set to true, query the server using
     218                // the `props` values, and sync the results to this collection.
     219                _changeQuery: function( model, query ) {
     220                        if ( query ) {
     221                                this.props.on( 'change', this._requery, this );
     222                                this._requery();
     223                        } else {
     224                                this.props.off( 'change', this._requery, this );
     225                        }
     226                },
     227
     228                _changeSearch: function( model, term ) {
     229                        // Bail if we're currently searching for the same term.
     230                        if ( this.props.get('search') === term )
     231                                return;
     232
     233                        if ( term && ! this.filters.search )
     234                                this.filters.search = Attachments.filters.search;
     235                        else if ( ! term && this.filters.search === Attachments.filters.search )
     236                                delete this.filters.search;
     237
     238                        // If no `Attachments` model is provided to source the searches
     239                        // from, then automatically generate a source from the existing
     240                        // models.
     241                        if ( ! this.props.get('source') )
     242                                this.props.set( 'source', new Attachments( this.models ) );
     243
     244                        this.reset( this.props.get('source').filter( this.validator ) );
    169245                },
    170246
    171247                validator: function( attachment ) {
    172                         return _.all( this.filters, function( filter ) {
     248                        return _.all( this.filters, function( filter, key ) {
    173249                                return !! filter.call( this, attachment );
    174250                        }, this );
    … …  
    231307                                return attachment.set( attachment.parse( attrs, xhr ) );
    232308                        });
     309                },
     310
     311                _requery: function() {
     312                        if ( this.props.get('query') )
     313                                this.mirror( Query.get( this.props.toJSON() ) );
     314                }
     315        }, {
     316                comparator: function( a, b ) {
     317                        var key   = this.props.get('orderby'),
     318                                order = this.props.get('order'),
     319                                ac    = a.cid,
     320                                bc    = b.cid;
     321
     322                        a = a.get( key );
     323                        b = b.get( key );
     324
     325                        if ( 'date' === key || 'modified' === key ) {
     326                                a = a || new Date();
     327                                b = b || new Date();
     328                        }
     329
     330                        return ( 'DESC' === order ) ? compare( a, b, ac, bc ) : compare( b, a, bc, ac );
     331                },
     332
     333                filters: {
     334                        // Note that this client-side searching is *not* equivalent
     335                        // to our server-side searching.
     336                        search: function( attachment ) {
     337                                if ( ! this.searching )
     338                                        return true;
     339
     340                                return _.any(['title','filename','description','caption','name'], function( key ) {
     341                                        var value = attachment.get( key );
     342                                        return value && -1 !== value.search( this.searching );
     343                                }, this );
     344                        }
    233345                }
    234346        });
    … …  
    239351         * wp.media.query
    240352         */
    241         media.query = (function(){
    242                 var queries = [];
    243 
    244                 return function( args, options ) {
    245                         args = _.defaults( args || {}, Query.defaultArgs );
    246 
    247                         var query = _.find( queries, function( query ) {
    248                                 return _.isEqual( query.args, args );
    249                         });
    250 
    251                         if ( ! query ) {
    252                                 query = new Query( [], _.extend( options || {}, { args: args } ) );
    253                                 queries.push( query );
    254                         }
    255 
    256                         return query;
    257                 };
    258         }());
     353        media.query = function( props ) {
     354                return new Attachments( null, {
     355                        props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
     356                });
     357        };
    259358
    260359        /**
    … …  
    269368        Query = media.model.Query = Attachments.extend({
    270369                initialize: function( models, options ) {
    271                         var orderby,
    272                                 defaultArgs = Query.defaultArgs;
    273 
    274370                        options = options || {};
    275371                        Attachments.prototype.initialize.apply( this, arguments );
    276372
    277                         // Generate this.args. Don't mess with them.
    278                         this.args = _.defaults( options.args || {}, defaultArgs );
    279 
    280                         // Normalize the order.
    281                         this.args.order = this.args.order.toUpperCase();
    282                         if ( 'DESC' !== this.args.order && 'ASC' !== this.args.order )
    283                                 this.args.order = defaultArgs.order.toUpperCase();
    284 
    285                         // Set allowed orderby values.
    286                         // These map directly to attachment keys in most scenarios.
    287                         // Exceptions are specified in orderby.keymap.
    288                         orderby = {
    289                                 allowed: [ 'name', 'author', 'date', 'title', 'modified', 'parent', 'ID' ],
    290                                 keymap:  {
    291                                         'ID':     'id',
    292                                         'parent': 'uploadedTo'
    293                                 }
    294                         };
    295 
    296                         if ( ! _.contains( orderby.allowed, this.args.orderby ) )
    297                                 this.args.orderby = defaultArgs.orderby;
    298                         this.orderkey = orderby.keymap[ this.args.orderby ] || this.args.orderby;
    299 
     373                        this.args    = options.args;
    300374                        this.hasMore = true;
    301375                        this.created = new Date();
    302376
    303377                        this.filters.order = function( attachment ) {
     378                                if ( ! this.comparator )
     379                                        return true;
     380
    304381                                // We want any items that can be placed before the last
    305382                                // item in the set. If we add any items after the last
    … …  
    311388                                // we're sorting for recent items. In that case, we want
    312389                                // changes that occurred after we created the query.
    313                                 } else if ( 'DESC' === this.args.order && ( 'date' === this.orderkey || 'modified' === this.orderkey ) ) {
    314                                         return attachment.get( this.orderkey ) >= this.created;
     390                                } else if ( 'DESC' === this.args.order && ( 'date' === this.args.orderby || 'modified' === this.args.orderby ) ) {
     391                                        return attachment.get( this.args.orderby ) >= this.created;
    315392                                }
    316393
    … …  
    318395                                return false;
    319396                        };
    320 
    321                         if ( this.args.s ) {
    322                                 // Note that this client-side searching is *not* equivalent
    323                                 // to our server-side searching.
    324                                 this.filters.search = function( attachment ) {
    325                                         return _.any(['title','filename','description','caption','name'], function( key ) {
    326                                                 var value = attachment.get( key );
    327                                                 return value && -1 !== value.search( this.args.s );
    328                                         }, this );
    329                                 };
    330                         }
    331397
    332398                        this.observe( Attachments.all );
    … …  
    373439                                return fallback.sync.apply( this, arguments );
    374440                        }
    375                 },
    376 
    377                 comparator: (function(){
    378                         /**
    379                          * A basic comparator.
    380                          *
    381                          * @param  {mixed}  a  The primary parameter to compare.
    382                          * @param  {mixed}  b  The primary parameter to compare.
    383                          * @param  {string} ac The fallback parameter to compare, a's cid.
    384                          * @param  {string} bc The fallback parameter to compare, b's cid.
    385                          * @return {number}    -1: a should come before b.
    386                          *                      0: a and b are of the same rank.
    387                          *                      1: b should come before a.
    388                          */
    389                         var compare = function( a, b, ac, bc ) {
    390                                 if ( _.isEqual( a, b ) )
    391                                         return ac === bc ? 0 : (ac > bc ? -1 : 1);
    392                                 else
    393                                         return a > b ? -1 : 1;
    394                         };
    395 
    396                         return function( a, b ) {
    397                                 var key   = this.orderkey,
    398                                         order = this.args.order,
    399                                         ac    = a.cid,
    400                                         bc    = b.cid;
    401 
    402                                 a = a.get( key );
    403                                 b = b.get( key );
    404 
    405                                 if ( 'date' === key || 'modified' === key ) {
    406                                         a = a || new Date();
    407                                         b = b || new Date();
    408                                 }
    409 
    410                                 return ( 'DESC' === order ) ? compare( a, b, ac, bc ) : compare( b, a, bc, ac );
    411                         };
    412                 }())
     441                }
    413442        }, {
    414443                defaultArgs: {
    … …  
    416445                        orderby:       'date',
    417446                        order:         'DESC'
    418                 }
     447                },
     448
     449                orderby: {
     450                        allowed: [ 'name', 'author', 'date', 'title', 'modified', 'parent', 'ID' ],
     451                        keymap:  {
     452                                'id':         'ID',
     453                                'uploadedTo': 'parent'
     454                        }
     455                },
     456
     457                propmap: {
     458                        'search': 's'
     459                },
     460
     461                // Caches query objects so queries can be easily reused.
     462                get: (function(){
     463                        var queries = [];
     464
     465                        return function( props, options ) {
     466                                var args     = {},
     467                                        orderby  = Query.orderby,
     468                                        defaults = Query.defaultArgs,
     469                                        query;
     470
     471                                // Correct any differing property names.
     472                                _.each( props, function( value, prop ) {
     473                                        args[ Query.propmap[ prop ] || prop ] = value;
     474                                });
     475
     476                                // Fill default args.
     477                                _.defaults( args, defaults );
     478
     479                                // Normalize the order.
     480                                args.order = args.order.toUpperCase();
     481                                if ( 'DESC' !== args.order && 'ASC' !== args.order )
     482                                        args.order = defaults.order.toUpperCase();
     483
     484                                // Set allowed orderby values.
     485                                // These map directly to attachment keys in most scenarios.
     486                                // Substitute exceptions specified in orderby.keymap.
     487                                args.orderby = orderby.keymap[ args.orderby ] || args.orderby;
     488
     489                                // Ensure we have a valid orderby value.
     490                                if ( ! _.contains( orderby.allowed, args.orderby ) )
     491                                        args.orderby = defaults.orderby;
     492
     493                                // Search the query cache.
     494                                query = _.find( queries, function( query ) {
     495                                        return _.isEqual( query.args, args );
     496                                });
     497
     498                                // Otherwise, create a new query and add it to the cache.
     499                                if ( ! query ) {
     500                                        query = new Query( [], _.extend( options || {}, { args: args } ) );
     501                                        queries.push( query );
     502                                }
     503
     504                                return query;
     505                        };
     506                }())
    419507        });
    420508
  • trunk/wp-includes/js/media-views.js

    r21821 r21898  
    349349                                controller: this.controller,
    350350                                directions: 'Select stuff.',
    351                                 collection: new Attachments( null, {
    352                                         mirror: media.query()
    353                                 })
     351                                collection: media.query()
    354352                        });
    355353
    … …  
    533531
    534532                search: function( event ) {
    535                         var args = _.clone( this.collection.mirroring.args );
    536 
    537                         // Bail if we're currently searching for the same string.
    538                         if ( args.s === event.target.value )
    539                                 return;
     533                        var props = this.collection.props;
    540534
    541535                        if ( event.target.value )
    542                                 args.s = event.target.value;
     536                                props.set( 'search', event.target.value );
    543537                        else
    544                                 delete args.s;
    545 
    546                         this.collection.mirror( media.query( args ) );
     538                                props.unset('search');
    547539                }
    548540        });
Note: See TracChangeset for help on using the changeset viewer.