[ Index ]

WordPress Cross Reference

title

Body

[close]

/wp-includes/js/ -> media-models.js (source)

   1  /* global _wpMediaModelsL10n:false */
   2  window.wp = window.wp || {};
   3  
   4  (function($){
   5      var Attachment, Attachments, Query, compare, l10n, media;
   6  
   7      /**
   8       * wp.media( attributes )
   9       *
  10       * Handles the default media experience. Automatically creates
  11       * and opens a media frame, and returns the result.
  12       * Does nothing if the controllers do not exist.
  13       *
  14       * @param  {object} attributes The properties passed to the main media controller.
  15       * @return {object}            A media workflow.
  16       */
  17      media = wp.media = function( attributes ) {
  18          var MediaFrame = media.view.MediaFrame,
  19              frame;
  20  
  21          if ( ! MediaFrame )
  22              return;
  23  
  24          attributes = _.defaults( attributes || {}, {
  25              frame: 'select'
  26          });
  27  
  28          if ( 'select' === attributes.frame && MediaFrame.Select )
  29              frame = new MediaFrame.Select( attributes );
  30          else if ( 'post' === attributes.frame && MediaFrame.Post )
  31              frame = new MediaFrame.Post( attributes );
  32  
  33          delete attributes.frame;
  34  
  35          return frame;
  36      };
  37  
  38      _.extend( media, { model: {}, view: {}, controller: {}, frames: {} });
  39  
  40      // Link any localized strings.
  41      l10n = media.model.l10n = typeof _wpMediaModelsL10n === 'undefined' ? {} : _wpMediaModelsL10n;
  42  
  43      // Link any settings.
  44      media.model.settings = l10n.settings || {};
  45      delete l10n.settings;
  46  
  47      /**
  48       * ========================================================================
  49       * UTILITIES
  50       * ========================================================================
  51       */
  52  
  53      /**
  54       * A basic comparator.
  55       *
  56       * @param  {mixed}  a  The primary parameter to compare.
  57       * @param  {mixed}  b  The primary parameter to compare.
  58       * @param  {string} ac The fallback parameter to compare, a's cid.
  59       * @param  {string} bc The fallback parameter to compare, b's cid.
  60       * @return {number}    -1: a should come before b.
  61       *                      0: a and b are of the same rank.
  62       *                      1: b should come before a.
  63       */
  64      compare = function( a, b, ac, bc ) {
  65          if ( _.isEqual( a, b ) )
  66              return ac === bc ? 0 : (ac > bc ? -1 : 1);
  67          else
  68              return a > b ? -1 : 1;
  69      };
  70  
  71      _.extend( media, {
  72          /**
  73           * media.template( id )
  74           *
  75           * Fetches a template by id.
  76           * See wp.template() in `wp-includes/js/wp-util.js`.
  77           */
  78          template: wp.template,
  79  
  80          /**
  81           * media.post( [action], [data] )
  82           *
  83           * Sends a POST request to WordPress.
  84           * See wp.ajax.post() in `wp-includes/js/wp-util.js`.
  85           */
  86          post: wp.ajax.post,
  87  
  88          /**
  89           * media.ajax( [action], [options] )
  90           *
  91           * Sends an XHR request to WordPress.
  92           * See wp.ajax.send() in `wp-includes/js/wp-util.js`.
  93           */
  94          ajax: wp.ajax.send,
  95  
  96          // Scales a set of dimensions to fit within bounding dimensions.
  97          fit: function( dimensions ) {
  98              var width     = dimensions.width,
  99                  height    = dimensions.height,
 100                  maxWidth  = dimensions.maxWidth,
 101                  maxHeight = dimensions.maxHeight,
 102                  constraint;
 103  
 104              // Compare ratios between the two values to determine which
 105              // max to constrain by. If a max value doesn't exist, then the
 106              // opposite side is the constraint.
 107              if ( ! _.isUndefined( maxWidth ) && ! _.isUndefined( maxHeight ) ) {
 108                  constraint = ( width / height > maxWidth / maxHeight ) ? 'width' : 'height';
 109              } else if ( _.isUndefined( maxHeight ) ) {
 110                  constraint = 'width';
 111              } else if (  _.isUndefined( maxWidth ) && height > maxHeight ) {
 112                  constraint = 'height';
 113              }
 114  
 115              // If the value of the constrained side is larger than the max,
 116              // then scale the values. Otherwise return the originals; they fit.
 117              if ( 'width' === constraint && width > maxWidth ) {
 118                  return {
 119                      width : maxWidth,
 120                      height: Math.round( maxWidth * height / width )
 121                  };
 122              } else if ( 'height' === constraint && height > maxHeight ) {
 123                  return {
 124                      width : Math.round( maxHeight * width / height ),
 125                      height: maxHeight
 126                  };
 127              } else {
 128                  return {
 129                      width : width,
 130                      height: height
 131                  };
 132              }
 133          },
 134  
 135          // Truncates a string by injecting an ellipsis into the middle.
 136          // Useful for filenames.
 137          truncate: function( string, length, replacement ) {
 138              length = length || 30;
 139              replacement = replacement || '…';
 140  
 141              if ( string.length <= length )
 142                  return string;
 143  
 144              return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 );
 145          }
 146      });
 147  
 148  
 149      /**
 150       * ========================================================================
 151       * MODELS
 152       * ========================================================================
 153       */
 154  
 155      /**
 156       * wp.media.attachment
 157       */
 158      media.attachment = function( id ) {
 159          return Attachment.get( id );
 160      };
 161  
 162      /**
 163       * wp.media.model.Attachment
 164       */
 165      Attachment = media.model.Attachment = Backbone.Model.extend({
 166          sync: function( method, model, options ) {
 167              // If the attachment does not yet have an `id`, return an instantly
 168              // rejected promise. Otherwise, all of our requests will fail.
 169              if ( _.isUndefined( this.id ) )
 170                  return $.Deferred().rejectWith( this ).promise();
 171  
 172              // Overload the `read` request so Attachment.fetch() functions correctly.
 173              if ( 'read' === method ) {
 174                  options = options || {};
 175                  options.context = this;
 176                  options.data = _.extend( options.data || {}, {
 177                      action: 'get-attachment',
 178                      id: this.id
 179                  });
 180                  return media.ajax( options );
 181  
 182              // Overload the `update` request so properties can be saved.
 183              } else if ( 'update' === method ) {
 184                  // If we do not have the necessary nonce, fail immeditately.
 185                  if ( ! this.get('nonces') || ! this.get('nonces').update )
 186                      return $.Deferred().rejectWith( this ).promise();
 187  
 188                  options = options || {};
 189                  options.context = this;
 190  
 191                  // Set the action and ID.
 192                  options.data = _.extend( options.data || {}, {
 193                      action:  'save-attachment',
 194                      id:      this.id,
 195                      nonce:   this.get('nonces').update,
 196                      post_id: media.model.settings.post.id
 197                  });
 198  
 199                  // Record the values of the changed attributes.
 200                  if ( model.hasChanged() ) {
 201                      options.data.changes = {};
 202  
 203                      _.each( model.changed, function( value, key ) {
 204                          options.data.changes[ key ] = this.get( key );
 205                      }, this );
 206                  }
 207  
 208                  return media.ajax( options );
 209  
 210              // Overload the `delete` request so attachments can be removed.
 211              // This will permanently delete an attachment.
 212              } else if ( 'delete' === method ) {
 213                  options = options || {};
 214  
 215                  if ( ! options.wait )
 216                      this.destroyed = true;
 217  
 218                  options.context = this;
 219                  options.data = _.extend( options.data || {}, {
 220                      action:   'delete-post',
 221                      id:       this.id,
 222                      _wpnonce: this.get('nonces')['delete']
 223                  });
 224  
 225                  return media.ajax( options ).done( function() {
 226                      this.destroyed = true;
 227                  }).fail( function() {
 228                      this.destroyed = false;
 229                  });
 230  
 231              // Otherwise, fall back to `Backbone.sync()`.
 232              } else {
 233                  return Backbone.Model.prototype.sync.apply( this, arguments );
 234              }
 235          },
 236  
 237          parse: function( resp ) {
 238              if ( ! resp )
 239                  return resp;
 240  
 241              // Convert date strings into Date objects.
 242              resp.date = new Date( resp.date );
 243              resp.modified = new Date( resp.modified );
 244              return resp;
 245          },
 246  
 247          saveCompat: function( data, options ) {
 248              var model = this;
 249  
 250              // If we do not have the necessary nonce, fail immeditately.
 251              if ( ! this.get('nonces') || ! this.get('nonces').update )
 252                  return $.Deferred().rejectWith( this ).promise();
 253  
 254              return media.post( 'save-attachment-compat', _.defaults({
 255                  id:      this.id,
 256                  nonce:   this.get('nonces').update,
 257                  post_id: media.model.settings.post.id
 258              }, data ) ).done( function( resp, status, xhr ) {
 259                  model.set( model.parse( resp, xhr ), options );
 260              });
 261          }
 262      }, {
 263          create: function( attrs ) {
 264              return Attachments.all.push( attrs );
 265          },
 266  
 267          get: _.memoize( function( id, attachment ) {
 268              return Attachments.all.push( attachment || { id: id } );
 269          })
 270      });
 271  
 272      /**
 273       * wp.media.model.Attachments
 274       */
 275      Attachments = media.model.Attachments = Backbone.Collection.extend({
 276          model: Attachment,
 277  
 278          initialize: function( models, options ) {
 279              options = options || {};
 280  
 281              this.props   = new Backbone.Model();
 282              this.filters = options.filters || {};
 283  
 284              // Bind default `change` events to the `props` model.
 285              this.props.on( 'change', this._changeFilteredProps, this );
 286  
 287              this.props.on( 'change:order',   this._changeOrder,   this );
 288              this.props.on( 'change:orderby', this._changeOrderby, this );
 289              this.props.on( 'change:query',   this._changeQuery,   this );
 290  
 291              // Set the `props` model and fill the default property values.
 292              this.props.set( _.defaults( options.props || {} ) );
 293  
 294              // Observe another `Attachments` collection if one is provided.
 295              if ( options.observe )
 296                  this.observe( options.observe );
 297          },
 298  
 299          // Automatically sort the collection when the order changes.
 300          _changeOrder: function() {
 301              if ( this.comparator )
 302                  this.sort();
 303          },
 304  
 305          // Set the default comparator only when the `orderby` property is set.
 306          _changeOrderby: function( model, orderby ) {
 307              // If a different comparator is defined, bail.
 308              if ( this.comparator && this.comparator !== Attachments.comparator )
 309                  return;
 310  
 311              if ( orderby && 'post__in' !== orderby )
 312                  this.comparator = Attachments.comparator;
 313              else
 314                  delete this.comparator;
 315          },
 316  
 317          // If the `query` property is set to true, query the server using
 318          // the `props` values, and sync the results to this collection.
 319          _changeQuery: function( model, query ) {
 320              if ( query ) {
 321                  this.props.on( 'change', this._requery, this );
 322                  this._requery();
 323              } else {
 324                  this.props.off( 'change', this._requery, this );
 325              }
 326          },
 327  
 328          _changeFilteredProps: function( model ) {
 329              // If this is a query, updating the collection will be handled by
 330              // `this._requery()`.
 331              if ( this.props.get('query') )
 332                  return;
 333  
 334              var changed = _.chain( model.changed ).map( function( t, prop ) {
 335                  var filter = Attachments.filters[ prop ],
 336                      term = model.get( prop );
 337  
 338                  if ( ! filter )
 339                      return;
 340  
 341                  if ( term && ! this.filters[ prop ] )
 342                      this.filters[ prop ] = filter;
 343                  else if ( ! term && this.filters[ prop ] === filter )
 344                      delete this.filters[ prop ];
 345                  else
 346                      return;
 347  
 348                  // Record the change.
 349                  return true;
 350              }, this ).any().value();
 351  
 352              if ( ! changed )
 353                  return;
 354  
 355              // If no `Attachments` model is provided to source the searches
 356              // from, then automatically generate a source from the existing
 357              // models.
 358              if ( ! this._source )
 359                  this._source = new Attachments( this.models );
 360  
 361              this.reset( this._source.filter( this.validator, this ) );
 362          },
 363  
 364          validateDestroyed: false,
 365  
 366          validator: function( attachment ) {
 367              if ( ! this.validateDestroyed && attachment.destroyed )
 368                  return false;
 369              return _.all( this.filters, function( filter ) {
 370                  return !! filter.call( this, attachment );
 371              }, this );
 372          },
 373  
 374          validate: function( attachment, options ) {
 375              var valid = this.validator( attachment ),
 376                  hasAttachment = !! this.get( attachment.cid );
 377  
 378              if ( ! valid && hasAttachment )
 379                  this.remove( attachment, options );
 380              else if ( valid && ! hasAttachment )
 381                  this.add( attachment, options );
 382  
 383              return this;
 384          },
 385  
 386          validateAll: function( attachments, options ) {
 387              options = options || {};
 388  
 389              _.each( attachments.models, function( attachment ) {
 390                  this.validate( attachment, { silent: true });
 391              }, this );
 392  
 393              if ( ! options.silent )
 394                  this.trigger( 'reset', this, options );
 395  
 396              return this;
 397          },
 398  
 399          observe: function( attachments ) {
 400              this.observers = this.observers || [];
 401              this.observers.push( attachments );
 402  
 403              attachments.on( 'add change remove', this._validateHandler, this );
 404              attachments.on( 'reset', this._validateAllHandler, this );
 405              this.validateAll( attachments );
 406              return this;
 407          },
 408  
 409          unobserve: function( attachments ) {
 410              if ( attachments ) {
 411                  attachments.off( null, null, this );
 412                  this.observers = _.without( this.observers, attachments );
 413  
 414              } else {
 415                  _.each( this.observers, function( attachments ) {
 416                      attachments.off( null, null, this );
 417                  }, this );
 418                  delete this.observers;
 419              }
 420  
 421              return this;
 422          },
 423  
 424          _validateHandler: function( attachment, attachments, options ) {
 425              // If we're not mirroring this `attachments` collection,
 426              // only retain the `silent` option.
 427              options = attachments === this.mirroring ? options : {
 428                  silent: options && options.silent
 429              };
 430  
 431              return this.validate( attachment, options );
 432          },
 433  
 434          _validateAllHandler: function( attachments, options ) {
 435              return this.validateAll( attachments, options );
 436          },
 437  
 438          mirror: function( attachments ) {
 439              if ( this.mirroring && this.mirroring === attachments )
 440                  return this;
 441  
 442              this.unmirror();
 443              this.mirroring = attachments;
 444  
 445              // Clear the collection silently. A `reset` event will be fired
 446              // when `observe()` calls `validateAll()`.
 447              this.reset( [], { silent: true } );
 448              this.observe( attachments );
 449  
 450              return this;
 451          },
 452  
 453          unmirror: function() {
 454              if ( ! this.mirroring )
 455                  return;
 456  
 457              this.unobserve( this.mirroring );
 458              delete this.mirroring;
 459          },
 460  
 461          more: function( options ) {
 462              var deferred = $.Deferred(),
 463                  mirroring = this.mirroring,
 464                  attachments = this;
 465  
 466              if ( ! mirroring || ! mirroring.more )
 467                  return deferred.resolveWith( this ).promise();
 468  
 469              // If we're mirroring another collection, forward `more` to
 470              // the mirrored collection. Account for a race condition by
 471              // checking if we're still mirroring that collection when
 472              // the request resolves.
 473              mirroring.more( options ).done( function() {
 474                  if ( this === attachments.mirroring )
 475                      deferred.resolveWith( this );
 476              });
 477  
 478              return deferred.promise();
 479          },
 480  
 481          hasMore: function() {
 482              return this.mirroring ? this.mirroring.hasMore() : false;
 483          },
 484  
 485          parse: function( resp, xhr ) {
 486              if ( ! _.isArray( resp ) )
 487                  resp = [resp];
 488  
 489              return _.map( resp, function( attrs ) {
 490                  var id, attachment, newAttributes;
 491  
 492                  if ( attrs instanceof Backbone.Model ) {
 493                      id = attrs.get( 'id' );
 494                      attrs = attrs.attributes;
 495                  } else {
 496                      id = attrs.id;
 497                  }
 498  
 499                  attachment = Attachment.get( id );
 500                  newAttributes = attachment.parse( attrs, xhr );
 501  
 502                  if ( ! _.isEqual( attachment.attributes, newAttributes ) )
 503                      attachment.set( newAttributes );
 504  
 505                  return attachment;
 506              });
 507          },
 508  
 509          _requery: function() {
 510              if ( this.props.get('query') )
 511                  this.mirror( Query.get( this.props.toJSON() ) );
 512          },
 513  
 514          // If this collection is sorted by `menuOrder`, recalculates and saves
 515          // the menu order to the database.
 516          saveMenuOrder: function() {
 517              if ( 'menuOrder' !== this.props.get('orderby') )
 518                  return;
 519  
 520              // Removes any uploading attachments, updates each attachment's
 521              // menu order, and returns an object with an { id: menuOrder }
 522              // mapping to pass to the request.
 523              var attachments = this.chain().filter( function( attachment ) {
 524                  return ! _.isUndefined( attachment.id );
 525              }).map( function( attachment, index ) {
 526                  // Indices start at 1.
 527                  index = index + 1;
 528                  attachment.set( 'menuOrder', index );
 529                  return [ attachment.id, index ];
 530              }).object().value();
 531  
 532              if ( _.isEmpty( attachments ) )
 533                  return;
 534  
 535              return media.post( 'save-attachment-order', {
 536                  nonce:       media.model.settings.post.nonce,
 537                  post_id:     media.model.settings.post.id,
 538                  attachments: attachments
 539              });
 540          }
 541      }, {
 542          comparator: function( a, b, options ) {
 543              var key   = this.props.get('orderby'),
 544                  order = this.props.get('order') || 'DESC',
 545                  ac    = a.cid,
 546                  bc    = b.cid;
 547  
 548              a = a.get( key );
 549              b = b.get( key );
 550  
 551              if ( 'date' === key || 'modified' === key ) {
 552                  a = a || new Date();
 553                  b = b || new Date();
 554              }
 555  
 556              // If `options.ties` is set, don't enforce the `cid` tiebreaker.
 557              if ( options && options.ties )
 558                  ac = bc = null;
 559  
 560              return ( 'DESC' === order ) ? compare( a, b, ac, bc ) : compare( b, a, bc, ac );
 561          },
 562  
 563          filters: {
 564              // Note that this client-side searching is *not* equivalent
 565              // to our server-side searching.
 566              search: function( attachment ) {
 567                  if ( ! this.props.get('search') )
 568                      return true;
 569  
 570                  return _.any(['title','filename','description','caption','name'], function( key ) {
 571                      var value = attachment.get( key );
 572                      return value && -1 !== value.search( this.props.get('search') );
 573                  }, this );
 574              },
 575  
 576              type: function( attachment ) {
 577                  var type = this.props.get('type');
 578                  return ! type || -1 !== type.indexOf( attachment.get('type') );
 579              },
 580  
 581              uploadedTo: function( attachment ) {
 582                  var uploadedTo = this.props.get('uploadedTo');
 583                  if ( _.isUndefined( uploadedTo ) )
 584                      return true;
 585  
 586                  return uploadedTo === attachment.get('uploadedTo');
 587              }
 588          }
 589      });
 590  
 591      Attachments.all = new Attachments();
 592  
 593      /**
 594       * wp.media.query
 595       */
 596      media.query = function( props ) {
 597          return new Attachments( null, {
 598              props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
 599          });
 600      };
 601  
 602      /**
 603       * wp.media.model.Query
 604       *
 605       * A set of attachments that corresponds to a set of consecutively paged
 606       * queries on the server.
 607       *
 608       * Note: Do NOT change this.args after the query has been initialized.
 609       *       Things will break.
 610       */
 611      Query = media.model.Query = Attachments.extend({
 612          initialize: function( models, options ) {
 613              var allowed;
 614  
 615              options = options || {};
 616              Attachments.prototype.initialize.apply( this, arguments );
 617  
 618              this.args     = options.args;
 619              this._hasMore = true;
 620              this.created  = new Date();
 621  
 622              this.filters.order = function( attachment ) {
 623                  var orderby = this.props.get('orderby'),
 624                      order = this.props.get('order');
 625  
 626                  if ( ! this.comparator )
 627                      return true;
 628  
 629                  // We want any items that can be placed before the last
 630                  // item in the set. If we add any items after the last
 631                  // item, then we can't guarantee the set is complete.
 632                  if ( this.length ) {
 633                      return 1 !== this.comparator( attachment, this.last(), { ties: true });
 634  
 635                  // Handle the case where there are no items yet and
 636                  // we're sorting for recent items. In that case, we want
 637                  // changes that occurred after we created the query.
 638                  } else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
 639                      return attachment.get( orderby ) >= this.created;
 640  
 641                  // If we're sorting by menu order and we have no items,
 642                  // accept any items that have the default menu order (0).
 643                  } else if ( 'ASC' === order && 'menuOrder' === orderby ) {
 644                      return attachment.get( orderby ) === 0;
 645                  }
 646  
 647                  // Otherwise, we don't want any items yet.
 648                  return false;
 649              };
 650  
 651              // Observe the central `wp.Uploader.queue` collection to watch for
 652              // new matches for the query.
 653              //
 654              // Only observe when a limited number of query args are set. There
 655              // are no filters for other properties, so observing will result in
 656              // false positives in those queries.
 657              allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ];
 658              if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() )
 659                  this.observe( wp.Uploader.queue );
 660          },
 661  
 662          hasMore: function() {
 663              return this._hasMore;
 664          },
 665  
 666          more: function( options ) {
 667              var query = this;
 668  
 669              if ( this._more && 'pending' === this._more.state() )
 670                  return this._more;
 671  
 672              if ( ! this.hasMore() )
 673                  return $.Deferred().resolveWith( this ).promise();
 674  
 675              options = options || {};
 676              options.remove = false;
 677  
 678              return this._more = this.fetch( options ).done( function( resp ) {
 679                  if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page )
 680                      query._hasMore = false;
 681              });
 682          },
 683  
 684          sync: function( method, model, options ) {
 685              var args, fallback;
 686  
 687              // Overload the read method so Attachment.fetch() functions correctly.
 688              if ( 'read' === method ) {
 689                  options = options || {};
 690                  options.context = this;
 691                  options.data = _.extend( options.data || {}, {
 692                      action:  'query-attachments',
 693                      post_id: media.model.settings.post.id
 694                  });
 695  
 696                  // Clone the args so manipulation is non-destructive.
 697                  args = _.clone( this.args );
 698  
 699                  // Determine which page to query.
 700                  if ( -1 !== args.posts_per_page )
 701                      args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
 702  
 703                  options.data.query = args;
 704                  return media.ajax( options );
 705  
 706              // Otherwise, fall back to Backbone.sync()
 707              } else {
 708                  fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone;
 709                  return fallback.sync.apply( this, arguments );
 710              }
 711          }
 712      }, {
 713          defaultProps: {
 714              orderby: 'date',
 715              order:   'DESC'
 716          },
 717  
 718          defaultArgs: {
 719              posts_per_page: 40
 720          },
 721  
 722          orderby: {
 723              allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
 724              valuemap: {
 725                  'id':         'ID',
 726                  'uploadedTo': 'parent',
 727                  'menuOrder':  'menu_order ID'
 728              }
 729          },
 730  
 731          propmap: {
 732              'search':    's',
 733              'type':      'post_mime_type',
 734              'perPage':   'posts_per_page',
 735              'menuOrder': 'menu_order',
 736              'uploadedTo': 'post_parent'
 737          },
 738  
 739          // Caches query objects so queries can be easily reused.
 740          get: (function(){
 741              var queries = [];
 742  
 743              return function( props, options ) {
 744                  var args     = {},
 745                      orderby  = Query.orderby,
 746                      defaults = Query.defaultProps,
 747                      query;
 748  
 749                  // Remove the `query` property. This isn't linked to a query,
 750                  // this *is* the query.
 751                  delete props.query;
 752  
 753                  // Fill default args.
 754                  _.defaults( props, defaults );
 755  
 756                  // Normalize the order.
 757                  props.order = props.order.toUpperCase();
 758                  if ( 'DESC' !== props.order && 'ASC' !== props.order )
 759                      props.order = defaults.order.toUpperCase();
 760  
 761                  // Ensure we have a valid orderby value.
 762                  if ( ! _.contains( orderby.allowed, props.orderby ) )
 763                      props.orderby = defaults.orderby;
 764  
 765                  // Generate the query `args` object.
 766                  // Correct any differing property names.
 767                  _.each( props, function( value, prop ) {
 768                      if ( _.isNull( value ) )
 769                          return;
 770  
 771                      args[ Query.propmap[ prop ] || prop ] = value;
 772                  });
 773  
 774                  // Fill any other default query args.
 775                  _.defaults( args, Query.defaultArgs );
 776  
 777                  // `props.orderby` does not always map directly to `args.orderby`.
 778                  // Substitute exceptions specified in orderby.keymap.
 779                  args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
 780  
 781                  // Search the query cache for matches.
 782                  query = _.find( queries, function( query ) {
 783                      return _.isEqual( query.args, args );
 784                  });
 785  
 786                  // Otherwise, create a new query and add it to the cache.
 787                  if ( ! query ) {
 788                      query = new Query( [], _.extend( options || {}, {
 789                          props: props,
 790                          args:  args
 791                      } ) );
 792                      queries.push( query );
 793                  }
 794  
 795                  return query;
 796              };
 797          }())
 798      });
 799  
 800      /**
 801       * wp.media.model.Selection
 802       *
 803       * Used to manage a selection of attachments in the views.
 804       */
 805      media.model.Selection = Attachments.extend({
 806          initialize: function( models, options ) {
 807              Attachments.prototype.initialize.apply( this, arguments );
 808              this.multiple = options && options.multiple;
 809  
 810              // Refresh the `single` model whenever the selection changes.
 811              // Binds `single` instead of using the context argument to ensure
 812              // it receives no parameters.
 813              this.on( 'add remove reset', _.bind( this.single, this, false ) );
 814          },
 815  
 816          // Override the selection's add method.
 817          // If the workflow does not support multiple
 818          // selected attachments, reset the selection.
 819          add: function( models, options ) {
 820              if ( ! this.multiple )
 821                  this.remove( this.models );
 822  
 823              return Attachments.prototype.add.call( this, models, options );
 824          },
 825  
 826          single: function( model ) {
 827              var previous = this._single;
 828  
 829              // If a `model` is provided, use it as the single model.
 830              if ( model )
 831                  this._single = model;
 832  
 833              // If the single model isn't in the selection, remove it.
 834              if ( this._single && ! this.get( this._single.cid ) )
 835                  delete this._single;
 836  
 837              this._single = this._single || this.last();
 838  
 839              // If single has changed, fire an event.
 840              if ( this._single !== previous ) {
 841                  if ( previous ) {
 842                      previous.trigger( 'selection:unsingle', previous, this );
 843  
 844                      // If the model was already removed, trigger the collection
 845                      // event manually.
 846                      if ( ! this.get( previous.cid ) )
 847                          this.trigger( 'selection:unsingle', previous, this );
 848                  }
 849                  if ( this._single )
 850                      this._single.trigger( 'selection:single', this._single, this );
 851              }
 852  
 853              // Return the single model, or the last model as a fallback.
 854              return this._single;
 855          }
 856      });
 857  
 858      // Clean up. Prevents mobile browsers caching
 859      $(window).on('unload', function(){
 860          window.wp = null;
 861      });
 862  
 863  }(jQuery));


Generated: Tue Mar 25 01:41:18 2014 WordPress honlapkészítés: online1.hu