Make WordPress Core

Changeset 21898


Ignore:
Timestamp:
09/18/2012 09:42:29 PM (12 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.