Ticket #26870: 26870-media-models.diff
File 26870-media-models.diff, 24.0 KB (added by , 11 years ago) |
---|
-
src/wp-includes/js/media-models.js
12 12 * Does nothing if the controllers do not exist. 13 13 * 14 14 * @param {object} attributes The properties passed to the main media controller. 15 * @return { object}A media workflow.15 * @return {wp.media.view.MediaFrame} A media workflow. 16 16 */ 17 17 media = wp.media = function( attributes ) { 18 18 var MediaFrame = media.view.MediaFrame, 19 19 frame; 20 20 21 if ( ! MediaFrame ) 21 if ( ! MediaFrame ) { 22 22 return; 23 } 23 24 24 25 attributes = _.defaults( attributes || {}, { 25 26 frame: 'select' 26 27 }); 27 28 28 if ( 'select' === attributes.frame && MediaFrame.Select ) 29 if ( 'select' === attributes.frame && MediaFrame.Select ) { 29 30 frame = new MediaFrame.Select( attributes ); 30 else if ( 'post' === attributes.frame && MediaFrame.Post )31 } else if ( 'post' === attributes.frame && MediaFrame.Post ) { 31 32 frame = new MediaFrame.Post( attributes ); 33 } 32 34 33 35 delete attributes.frame; 34 36 … … 62 64 * 1: b should come before a. 63 65 */ 64 66 compare = function( a, b, ac, bc ) { 65 if ( _.isEqual( a, b ) ) 67 if ( _.isEqual( a, b ) ) { 66 68 return ac === bc ? 0 : (ac > bc ? -1 : 1); 67 else69 } else { 68 70 return a > b ? -1 : 1; 71 } 69 72 }; 70 73 71 74 _.extend( media, { … … 93 96 */ 94 97 ajax: wp.ajax.send, 95 98 96 // Scales a set of dimensions to fit within bounding dimensions. 99 /** 100 * Scales a set of dimensions to fit within bounding dimensions. 101 * 102 * @param {Object} dimensions 103 * @returns {Object} 104 */ 97 105 fit: function( dimensions ) { 98 106 var width = dimensions.width, 99 107 height = dimensions.height, … … 131 139 }; 132 140 } 133 141 }, 134 135 // Truncates a string by injecting an ellipsis into the middle. 136 // Useful for filenames. 142 /** 143 * Truncates a string by injecting an ellipsis into the middle. 144 * Useful for filenames. 145 * 146 * @param {String} string 147 * @param {Number} [length=30] 148 * @param {String} [replacement=…] 149 * @returns {String} 150 */ 137 151 truncate: function( string, length, replacement ) { 138 152 length = length || 30; 139 153 replacement = replacement || '…'; 140 154 141 if ( string.length <= length ) 155 if ( string.length <= length ) { 142 156 return string; 157 } 143 158 144 159 return string.substr( 0, length / 2 ) + replacement + string.substr( -1 * length / 2 ); 145 160 } 146 161 }); 147 162 148 149 163 /** 150 164 * ======================================================================== 151 165 * MODELS 152 166 * ======================================================================== 153 167 */ 154 155 168 /** 156 169 * wp.media.attachment 170 * 171 * @static 172 * @param {String} id 173 * @returns {wp.media.model.Attachment} 157 174 */ 158 175 media.attachment = function( id ) { 159 176 return Attachment.get( id ); … … 161 178 162 179 /** 163 180 * wp.media.model.Attachment 181 * 182 * @constructor 183 * @augments Backbone.Model 164 184 */ 165 185 Attachment = media.model.Attachment = Backbone.Model.extend({ 186 /** 187 * @param {string} method 188 * @param {Backbone.Model} model 189 * @param {Object} [options={}] 190 * @returns {Promise} 191 */ 166 192 sync: function( method, model, options ) { 167 193 // If the attachment does not yet have an `id`, return an instantly 168 194 // rejected promise. Otherwise, all of our requests will fail. 169 if ( _.isUndefined( this.id ) ) 195 if ( _.isUndefined( this.id ) ) { 170 196 return $.Deferred().rejectWith( this ).promise(); 197 } 171 198 172 199 // Overload the `read` request so Attachment.fetch() functions correctly. 173 200 if ( 'read' === method ) { … … 182 209 // Overload the `update` request so properties can be saved. 183 210 } else if ( 'update' === method ) { 184 211 // If we do not have the necessary nonce, fail immeditately. 185 if ( ! this.get('nonces') || ! this.get('nonces').update ) 212 if ( ! this.get('nonces') || ! this.get('nonces').update ) { 186 213 return $.Deferred().rejectWith( this ).promise(); 214 } 187 215 188 216 options = options || {}; 189 217 options.context = this; … … 212 240 } else if ( 'delete' === method ) { 213 241 options = options || {}; 214 242 215 if ( ! options.wait ) 243 if ( ! options.wait ) { 216 244 this.destroyed = true; 245 } 217 246 218 247 options.context = this; 219 248 options.data = _.extend( options.data || {}, { … … 230 259 231 260 // Otherwise, fall back to `Backbone.sync()`. 232 261 } else { 262 /** 263 * Call `sync` directly on Backbone.Model 264 */ 233 265 return Backbone.Model.prototype.sync.apply( this, arguments ); 234 266 } 235 267 }, 236 268 /** 269 * @param {Object} resp 270 * @returns {Object} 271 */ 237 272 parse: function( resp ) { 238 if ( ! resp ) 273 if ( ! resp ) { 239 274 return resp; 275 } 240 276 241 277 // Convert date strings into Date objects. 242 278 resp.date = new Date( resp.date ); 243 279 resp.modified = new Date( resp.modified ); 244 280 return resp; 245 281 }, 246 282 /** 283 * @param {Object} data 284 * @param {Object} options 285 * @returns {Promise} 286 */ 247 287 saveCompat: function( data, options ) { 248 288 var model = this; 249 289 250 290 // If we do not have the necessary nonce, fail immeditately. 251 if ( ! this.get('nonces') || ! this.get('nonces').update ) 291 if ( ! this.get('nonces') || ! this.get('nonces').update ) { 252 292 return $.Deferred().rejectWith( this ).promise(); 293 } 253 294 254 295 return media.post( 'save-attachment-compat', _.defaults({ 255 296 id: this.id, … … 260 301 }); 261 302 } 262 303 }, { 304 /** 305 * @param {Object} attrs 306 * @returns {wp.media.model.Attachment} 307 */ 263 308 create: function( attrs ) { 264 309 return Attachments.all.push( attrs ); 265 310 }, 266 311 /** 312 * @param {string} id 313 * @param {Backbone.Model} attachment 314 * @returns {wp.media.model.Attachment} 315 */ 267 316 get: _.memoize( function( id, attachment ) { 268 317 return Attachments.all.push( attachment || { id: id } ); 269 318 }) … … 271 320 272 321 /** 273 322 * wp.media.model.Attachments 323 * 324 * @constructor 325 * @augments Backbone.Collection 274 326 */ 275 327 Attachments = media.model.Attachments = Backbone.Collection.extend({ 328 /** 329 * @type {wp.media.model.Attachment} 330 */ 276 331 model: Attachment, 277 332 /** 333 * @param {Array|Object} models 334 * @param {Object} [options={}] 335 */ 278 336 initialize: function( models, options ) { 279 337 options = options || {}; 280 338 … … 292 350 this.props.set( _.defaults( options.props || {} ) ); 293 351 294 352 // Observe another `Attachments` collection if one is provided. 295 if ( options.observe ) 353 if ( options.observe ) { 296 354 this.observe( options.observe ); 355 } 297 356 }, 298 299 // Automatically sort the collection when the order changes. 357 /** 358 * Automatically sort the collection when the order changes. 359 * 360 * @access private 361 */ 300 362 _changeOrder: function() { 301 if ( this.comparator ) 363 if ( this.comparator ) { 302 364 this.sort(); 365 } 303 366 }, 304 305 // Set the default comparator only when the `orderby` property is set. 367 /** 368 * Set the default comparator only when the `orderby` property is set. 369 * 370 * @access private 371 * 372 * @param {Backbone.Model} model 373 * @param {string} orderby 374 */ 306 375 _changeOrderby: function( model, orderby ) { 307 376 // If a different comparator is defined, bail. 308 if ( this.comparator && this.comparator !== Attachments.comparator ) 377 if ( this.comparator && this.comparator !== Attachments.comparator ) { 309 378 return; 379 } 310 380 311 if ( orderby && 'post__in' !== orderby ) 381 if ( orderby && 'post__in' !== orderby ) { 312 382 this.comparator = Attachments.comparator; 313 else383 } else { 314 384 delete this.comparator; 385 } 315 386 }, 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. 387 /** 388 * If the `query` property is set to true, query the server using 389 * the `props` values, and sync the results to this collection. 390 * 391 * @access private 392 * 393 * @param {Backbone.Model} model 394 * @param {Boolean} query 395 */ 319 396 _changeQuery: function( model, query ) { 320 397 if ( query ) { 321 398 this.props.on( 'change', this._requery, this ); … … 324 401 this.props.off( 'change', this._requery, this ); 325 402 } 326 403 }, 327 404 /** 405 * @access private 406 * 407 * @param {Backbone.Model} model 408 */ 328 409 _changeFilteredProps: function( model ) { 329 410 // If this is a query, updating the collection will be handled by 330 411 // `this._requery()`. 331 if ( this.props.get('query') ) 412 if ( this.props.get('query') ) { 332 413 return; 414 } 333 415 334 416 var changed = _.chain( model.changed ).map( function( t, prop ) { 335 417 var filter = Attachments.filters[ prop ], 336 418 term = model.get( prop ); 337 419 338 if ( ! filter ) 420 if ( ! filter ) { 339 421 return; 422 } 340 423 341 if ( term && ! this.filters[ prop ] ) 424 if ( term && ! this.filters[ prop ] ) { 342 425 this.filters[ prop ] = filter; 343 else if ( ! term && this.filters[ prop ] === filter )426 } else if ( ! term && this.filters[ prop ] === filter ) { 344 427 delete this.filters[ prop ]; 345 else428 } else { 346 429 return; 430 } 347 431 348 432 // Record the change. 349 433 return true; 350 434 }, this ).any().value(); 351 435 352 if ( ! changed ) 436 if ( ! changed ) { 353 437 return; 438 } 354 439 355 440 // If no `Attachments` model is provided to source the searches 356 441 // from, then automatically generate a source from the existing 357 442 // models. 358 if ( ! this._source ) 443 if ( ! this._source ) { 359 444 this._source = new Attachments( this.models ); 445 } 360 446 361 447 this.reset( this._source.filter( this.validator, this ) ); 362 448 }, 363 449 364 450 validateDestroyed: false, 365 451 /** 452 * @param {wp.media.model.Attachment} attachment 453 * @returns {Boolean} 454 */ 366 455 validator: function( attachment ) { 367 if ( ! this.validateDestroyed && attachment.destroyed ) 456 if ( ! this.validateDestroyed && attachment.destroyed ) { 368 457 return false; 458 } 369 459 return _.all( this.filters, function( filter ) { 370 460 return !! filter.call( this, attachment ); 371 461 }, this ); 372 462 }, 373 463 /** 464 * @param {wp.media.model.Attachment} attachment 465 * @param {Object} options 466 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 467 */ 374 468 validate: function( attachment, options ) { 375 469 var valid = this.validator( attachment ), 376 470 hasAttachment = !! this.get( attachment.cid ); 377 471 378 if ( ! valid && hasAttachment ) 472 if ( ! valid && hasAttachment ) { 379 473 this.remove( attachment, options ); 380 else if ( valid && ! hasAttachment )474 } else if ( valid && ! hasAttachment ) { 381 475 this.add( attachment, options ); 476 } 382 477 383 478 return this; 384 479 }, 385 480 481 /** 482 * @param {Backbone.Collection} attachments 483 * @param {object} [options={}] 484 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 485 */ 386 486 validateAll: function( attachments, options ) { 387 487 options = options || {}; 388 488 … … 390 490 this.validate( attachment, { silent: true }); 391 491 }, this ); 392 492 393 if ( ! options.silent ) 493 if ( ! options.silent ) { 394 494 this.trigger( 'reset', this, options ); 395 495 } 396 496 return this; 397 497 }, 398 498 /** 499 * @param {Backbone.Collection} attachments 500 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 501 */ 399 502 observe: function( attachments ) { 400 503 this.observers = this.observers || []; 401 504 this.observers.push( attachments ); … … 405 508 this.validateAll( attachments ); 406 509 return this; 407 510 }, 408 511 /** 512 * @param {Backbone.Collection} attachments 513 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 514 */ 409 515 unobserve: function( attachments ) { 410 516 if ( attachments ) { 411 517 attachments.off( null, null, this ); … … 420 526 421 527 return this; 422 528 }, 423 529 /** 530 * @access private 531 * 532 * @param {Backbone.Model} attachment 533 * @param {Backbone.Collction} attachments 534 * @param {Object} options 535 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 536 */ 424 537 _validateHandler: function( attachment, attachments, options ) { 425 538 // If we're not mirroring this `attachments` collection, 426 539 // only retain the `silent` option. … … 430 543 431 544 return this.validate( attachment, options ); 432 545 }, 433 546 /** 547 * @access private 548 * 549 * @param {Backbone.Collction} attachments 550 * @param {Object} options 551 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 552 */ 434 553 _validateAllHandler: function( attachments, options ) { 435 554 return this.validateAll( attachments, options ); 436 555 }, 437 556 /** 557 * @param {Backbone.Collction} attachments 558 * @returns {wp.media.model.Attachments} Returns itself to allow chaining 559 */ 438 560 mirror: function( attachments ) { 439 if ( this.mirroring && this.mirroring === attachments ) 561 if ( this.mirroring && this.mirroring === attachments ) { 440 562 return this; 563 } 441 564 442 565 this.unmirror(); 443 566 this.mirroring = attachments; … … 449 572 450 573 return this; 451 574 }, 452 453 575 unmirror: function() { 454 if ( ! this.mirroring ) 576 if ( ! this.mirroring ) { 455 577 return; 578 } 456 579 457 580 this.unobserve( this.mirroring ); 458 581 delete this.mirroring; 459 582 }, 460 583 /** 584 * @param {Object} options 585 * @returns {Promise} 586 */ 461 587 more: function( options ) { 462 588 var deferred = $.Deferred(), 463 589 mirroring = this.mirroring, 464 590 attachments = this; 465 591 466 if ( ! mirroring || ! mirroring.more ) 592 if ( ! mirroring || ! mirroring.more ) { 467 593 return deferred.resolveWith( this ).promise(); 468 594 } 469 595 // If we're mirroring another collection, forward `more` to 470 596 // the mirrored collection. Account for a race condition by 471 597 // checking if we're still mirroring that collection when … … 477 603 478 604 return deferred.promise(); 479 605 }, 480 606 /** 607 * @returns {Boolean} 608 */ 481 609 hasMore: function() { 482 610 return this.mirroring ? this.mirroring.hasMore() : false; 483 611 }, 484 612 /** 613 * @param {Object} resp 614 * @param {XMLHttpRequest} xhr 615 * @returns {Array} 616 */ 485 617 parse: function( resp, xhr ) { 486 if ( ! _.isArray( resp ) ) 618 if ( ! _.isArray( resp ) ) { 487 619 resp = [resp]; 620 } 488 621 489 622 return _.map( resp, function( attrs ) { 490 623 var id, attachment, newAttributes; … … 499 632 attachment = Attachment.get( id ); 500 633 newAttributes = attachment.parse( attrs, xhr ); 501 634 502 if ( ! _.isEqual( attachment.attributes, newAttributes ) ) 635 if ( ! _.isEqual( attachment.attributes, newAttributes ) ) { 503 636 attachment.set( newAttributes ); 637 } 504 638 505 639 return attachment; 506 640 }); 507 641 }, 508 642 /** 643 * @access private 644 */ 509 645 _requery: function() { 510 if ( this.props.get('query') ) 646 if ( this.props.get('query') ) { 511 647 this.mirror( Query.get( this.props.toJSON() ) ); 648 } 512 649 }, 513 514 // If this collection is sorted by `menuOrder`, recalculates and saves 515 // the menu order to the database. 650 /** 651 * If this collection is sorted by `menuOrder`, recalculates and saves 652 * the menu order to the database. 653 * 654 * @returns {undefined|Promise} 655 */ 516 656 saveMenuOrder: function() { 517 if ( 'menuOrder' !== this.props.get('orderby') ) 657 if ( 'menuOrder' !== this.props.get('orderby') ) { 518 658 return; 659 } 519 660 520 661 // Removes any uploading attachments, updates each attachment's 521 662 // menu order, and returns an object with an { id: menuOrder } … … 529 670 return [ attachment.id, index ]; 530 671 }).object().value(); 531 672 532 if ( _.isEmpty( attachments ) ) 673 if ( _.isEmpty( attachments ) ) { 533 674 return; 675 } 534 676 535 677 return media.post( 'save-attachment-order', { 536 678 nonce: media.model.settings.post.nonce, … … 539 681 }); 540 682 } 541 683 }, { 684 /** 685 * @param {Backbone.Model} a 686 * @param {Backbone.Model} b 687 * @param {Object} options 688 * @returns {Number} 689 */ 542 690 comparator: function( a, b, options ) { 543 691 var key = this.props.get('orderby'), 544 692 order = this.props.get('order') || 'DESC', … … 554 702 } 555 703 556 704 // If `options.ties` is set, don't enforce the `cid` tiebreaker. 557 if ( options && options.ties ) 705 if ( options && options.ties ) { 558 706 ac = bc = null; 707 } 559 708 560 709 return ( 'DESC' === order ) ? compare( a, b, ac, bc ) : compare( b, a, bc, ac ); 561 710 }, 562 711 563 712 filters: { 564 // Note that this client-side searching is *not* equivalent 565 // to our server-side searching. 713 /** 714 * Note that this client-side searching is *not* equivalent 715 * to our server-side searching. 716 * 717 * @param {wp.media.model.Attachment} attachment 718 * @returns {Boolean} 719 */ 566 720 search: function( attachment ) { 567 if ( ! this.props.get('search') ) 721 if ( ! this.props.get('search') ) { 568 722 return true; 723 } 569 724 570 725 return _.any(['title','filename','description','caption','name'], function( key ) { 571 726 var value = attachment.get( key ); 572 727 return value && -1 !== value.search( this.props.get('search') ); 573 728 }, this ); 574 729 }, 575 730 /** 731 * @param {wp.media.model.Attachment} attachment 732 * @returns {Boolean} 733 */ 576 734 type: function( attachment ) { 577 735 var type = this.props.get('type'); 578 736 return ! type || -1 !== type.indexOf( attachment.get('type') ); 579 737 }, 580 738 /** 739 * @param {wp.media.model.Attachment} attachment 740 * @returns {Boolean} 741 */ 581 742 uploadedTo: function( attachment ) { 582 743 var uploadedTo = this.props.get('uploadedTo'); 583 if ( _.isUndefined( uploadedTo ) ) 744 if ( _.isUndefined( uploadedTo ) ) { 584 745 return true; 746 } 585 747 586 748 return uploadedTo === attachment.get('uploadedTo'); 587 749 } 588 750 } 589 751 }); 590 752 753 /** 754 * @member {wp.media.model.Attachments} 755 */ 591 756 Attachments.all = new Attachments(); 592 757 593 758 /** 594 759 * wp.media.query 760 * 761 * @static 762 * @returns {Attachments} 595 763 */ 596 764 media.query = function( props ) { 597 765 return new Attachments( null, { … … 607 775 * 608 776 * Note: Do NOT change this.args after the query has been initialized. 609 777 * Things will break. 778 * 779 * @constructor 780 * @augments wp.media.model.Attachments 781 * @augments Backbone.Collection 610 782 */ 611 783 Query = media.model.Query = Attachments.extend({ 784 /** 785 * @global wp.Uploader 786 * 787 * @param {Array} models 788 * @param {Object} [options={}] 789 */ 612 790 initialize: function( models, options ) { 613 791 var allowed; 614 792 … … 623 801 var orderby = this.props.get('orderby'), 624 802 order = this.props.get('order'); 625 803 626 if ( ! this.comparator ) 804 if ( ! this.comparator ) { 627 805 return true; 806 } 628 807 629 808 // We want any items that can be placed before the last 630 809 // item in the set. If we add any items after the last … … 655 834 // are no filters for other properties, so observing will result in 656 835 // false positives in those queries. 657 836 allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent' ]; 658 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) 837 if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) { 659 838 this.observe( wp.Uploader.queue ); 839 } 660 840 }, 661 841 /** 842 * @returns {Boolean} 843 */ 662 844 hasMore: function() { 663 845 return this._hasMore; 664 846 }, 665 847 /** 848 * @param {Object} [options={}] 849 * @returns {Promise} 850 */ 666 851 more: function( options ) { 667 852 var query = this; 668 853 669 if ( this._more && 'pending' === this._more.state() ) 854 if ( this._more && 'pending' === this._more.state() ) { 670 855 return this._more; 856 } 671 857 672 if ( ! this.hasMore() ) 858 if ( ! this.hasMore() ) { 673 859 return $.Deferred().resolveWith( this ).promise(); 860 } 674 861 675 862 options = options || {}; 676 863 options.remove = false; 677 864 678 865 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 ) 866 if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) { 680 867 query._hasMore = false; 868 } 681 869 }); 682 870 }, 683 871 /** 872 * @param {String} method 873 * @param {Backbone.Model} model 874 * @param {Object} [options={}] 875 * @returns {Promise} 876 */ 684 877 sync: function( method, model, options ) { 685 878 var args, fallback; 686 879 … … 697 890 args = _.clone( this.args ); 698 891 699 892 // Determine which page to query. 700 if ( -1 !== args.posts_per_page ) 893 if ( -1 !== args.posts_per_page ) { 701 894 args.paged = Math.floor( this.length / args.posts_per_page ) + 1; 895 } 702 896 703 897 options.data.query = args; 704 898 return media.ajax( options ); 705 899 706 900 // Otherwise, fall back to Backbone.sync() 707 901 } else { 902 /** 903 * Call wp.media.model.Attachments.sync or Backbone.sync 904 */ 708 905 fallback = Attachments.prototype.sync ? Attachments.prototype : Backbone; 709 906 return fallback.sync.apply( this, arguments ); 710 907 } … … 738 935 739 936 // Caches query objects so queries can be easily reused. 740 937 get: (function(){ 938 /** 939 * @static 940 * @type Array 941 */ 741 942 var queries = []; 742 943 944 /** 945 * @param {Object} props 946 * @param {Object} options 947 * @returns {Query} 948 */ 743 949 return function( props, options ) { 744 950 var args = {}, 745 951 orderby = Query.orderby, … … 755 961 756 962 // Normalize the order. 757 963 props.order = props.order.toUpperCase(); 758 if ( 'DESC' !== props.order && 'ASC' !== props.order ) 964 if ( 'DESC' !== props.order && 'ASC' !== props.order ) { 759 965 props.order = defaults.order.toUpperCase(); 966 } 760 967 761 968 // Ensure we have a valid orderby value. 762 if ( ! _.contains( orderby.allowed, props.orderby ) ) 969 if ( ! _.contains( orderby.allowed, props.orderby ) ) { 763 970 props.orderby = defaults.orderby; 971 } 764 972 765 973 // Generate the query `args` object. 766 974 // Correct any differing property names. 767 975 _.each( props, function( value, prop ) { 768 if ( _.isNull( value ) ) 976 if ( _.isNull( value ) ) { 769 977 return; 978 } 770 979 771 980 args[ Query.propmap[ prop ] || prop ] = value; 772 981 }); … … 801 1010 * wp.media.model.Selection 802 1011 * 803 1012 * Used to manage a selection of attachments in the views. 1013 * 1014 * @constructor 1015 * @augments wp.media.model.Attachments 1016 * @augments Backbone.Collection 804 1017 */ 805 1018 media.model.Selection = Attachments.extend({ 1019 /** 1020 * Refresh the `single` model whenever the selection changes. 1021 * Binds `single` instead of using the context argument to ensure 1022 * it receives no parameters. 1023 * 1024 * @param {Array} models 1025 * @param {Object} options 1026 */ 806 1027 initialize: function( models, options ) { 1028 /** 1029 * call 'initialize' directly on the parent class 1030 */ 807 1031 Attachments.prototype.initialize.apply( this, arguments ); 808 1032 this.multiple = options && options.multiple; 809 1033 810 // Refresh the `single` model whenever the selection changes.811 // Binds `single` instead of using the context argument to ensure812 // it receives no parameters.813 1034 this.on( 'add remove reset', _.bind( this.single, this, false ) ); 814 1035 }, 815 1036 816 // Override the selection's add method. 817 // If the workflow does not support multiple 818 // selected attachments, reset the selection. 1037 /** 1038 * Override the selection's add method. 1039 * If the workflow does not support multiple 1040 * selected attachments, reset the selection. 1041 * 1042 * @param {Array} models 1043 * @param {Object} options 1044 * @returns {wp.media.model.Attachment[]} 1045 */ 819 1046 add: function( models, options ) { 820 if ( ! this.multiple ) 1047 if ( ! this.multiple ) { 821 1048 this.remove( this.models ); 822 1049 } 1050 /** 1051 * call 'add' directly on the parent class 1052 */ 823 1053 return Attachments.prototype.add.call( this, models, options ); 824 1054 }, 825 1055 1056 /** 1057 * 1058 * @param {Backbone.Model} model 1059 * 1060 * @fires wp.media.model.Selection#selection:single 1061 * @fires wp.media.model.Selection#selection:unsingle 1062 * 1063 * @returns {Backbone.Model} 1064 */ 826 1065 single: function( model ) { 827 1066 var previous = this._single; 828 1067 829 1068 // If a `model` is provided, use it as the single model. 830 if ( model ) 1069 if ( model ) { 831 1070 this._single = model; 832 1071 } 833 1072 // If the single model isn't in the selection, remove it. 834 if ( this._single && ! this.get( this._single.cid ) ) 1073 if ( this._single && ! this.get( this._single.cid ) ) { 835 1074 delete this._single; 1075 } 836 1076 837 1077 this._single = this._single || this.last(); 838 1078 … … 843 1083 844 1084 // If the model was already removed, trigger the collection 845 1085 // event manually. 846 if ( ! this.get( previous.cid ) ) 1086 if ( ! this.get( previous.cid ) ) { 847 1087 this.trigger( 'selection:unsingle', previous, this ); 1088 } 848 1089 } 849 if ( this._single ) 1090 if ( this._single ) { 850 1091 this._single.trigger( 'selection:single', this._single, this ); 1092 } 851 1093 } 852 1094 853 1095 // Return the single model, or the last model as a fallback.