balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

media.js (15835B)


      1 ( function ( $, wp, _, rwmb, i18n ) {
      2 	'use strict';
      3 
      4 	var views = rwmb.views = rwmb.views || {},
      5 		models = rwmb.models = rwmb.models || {},
      6 		media = wp.media,
      7 		MediaFrame = media.view.MediaFrame,
      8 		MediaCollection, Controller, MediaField, MediaList, MediaItem, MediaButton, MediaStatus, EditMedia,
      9 		MediaDetails, MediaLibrary, MediaSelect;
     10 
     11 	MediaCollection = Backbone.Collection.extend( {
     12 		model: wp.media.model.Attachment,
     13 
     14 		initialize: function ( models, options ) {
     15 			this.controller = options.controller || new models.Controller;
     16 			this.on( 'add remove reset', function () {
     17 				var max = this.controller.get( 'maxFiles' );
     18 				this.controller.set( 'length', this.length );
     19 				this.controller.set( 'full', max > 0 && this.length >= max );
     20 			} );
     21 		},
     22 
     23 		add: function ( models, options ) {
     24 			var max = this.controller.get( 'maxFiles' ),
     25 				left = max - this.length;
     26 
     27 			if ( ! models || ( max > 0 && left <= 0 ) ) {
     28 				return this;
     29 			}
     30 			if ( ! models.hasOwnProperty( 'length' ) ) {
     31 				models = [models];
     32 			} else if ( models instanceof media.model.Attachments ) {
     33 				models = models.models;
     34 			}
     35 
     36 			models = _.difference( models, this.models );
     37 			if ( left > 0 ) {
     38 				models = _.first( models, left );
     39 			}
     40 
     41 			Backbone.Collection.prototype.add.call( this, models, options );
     42 		},
     43 
     44 		remove: function ( models, options ) {
     45 			// Don't remove models if event is not fired from MB plugin.
     46 			if( ! $( event.target ).closest( '.rwmb-field, [data-class="rwmb-field"]' ).length ) {
     47 				return;
     48 			}
     49 			models = Backbone.Collection.prototype.remove.call( this, models, options );
     50 			if ( this.controller.get( 'forceDelete' ) === true ) {
     51 				models = ! _.isArray( models ) ? [models] : models;
     52 				_.each( models, function ( model ) {
     53 					model.destroy();
     54 				} );
     55 			}
     56 		},
     57 
     58 		destroyAll: function () {
     59 			_.each( _.clone( this.models ), function ( model ) {
     60 				model.destroy();
     61 			} );
     62 		}
     63 	} );
     64 
     65 	/***
     66 	 * Controller Model
     67 	 * Manages data of media field and media models.  Most of the media views will use this to manage the media
     68 	 */
     69 	Controller = models.Controller = Backbone.Model.extend( {
     70 		//Default options
     71 		defaults: {
     72 			maxFiles: 0,
     73 			ids: [],
     74 			mimeType: '',
     75 			forceDelete: false,
     76 			maxStatus: true,
     77 			length: 0
     78 		},
     79 
     80 		//Initialize Controller model
     81 		initialize: function () {
     82 			// All numbers, no 0 ids
     83 			this.set( 'ids', _.without( _.map( this.get( 'ids' ), Number ), 0, - 1 ) );
     84 
     85 			// Create items collection
     86 			this.set( 'items', new MediaCollection( [], {controller: this} ) );
     87 
     88 			// Listen for destroy event on controller, delete all models when triggered
     89 			this.on( 'destroy', function () {
     90 				if ( this.get( 'forceDelete' ) ) {
     91 					this.get( 'items' ).destroyAll();
     92 				}
     93 			} );
     94 		}
     95 	} );
     96 
     97 	/***
     98 	 * MediaField
     99 	 * Sets up media field view and subviews
    100 	 */
    101 	MediaField = views.MediaField = Backbone.View.extend( {
    102 		className: 'rwmb-media-view',
    103 		initialize: function ( options ) {
    104 			var that = this,
    105 				fieldName = options.input.name;
    106 			this.$input = $( options.input );
    107 
    108 			if ( 1 != this.$input.attr( 'data-single-image' ) ) {
    109 				fieldName += '[]';
    110 			}
    111 
    112 			this.controller = new Controller( _.extend(
    113 				{
    114 					fieldName: fieldName,
    115 					ids: this.$input.val().split( ',' )
    116 				},
    117 				this.$input.data( 'options' )
    118 			) );
    119 
    120 			// Create views
    121 			this.createList();
    122 			this.createAddButton();
    123 			this.createStatus();
    124 
    125 			this.render();
    126 			this.loadInitialAttachments();
    127 
    128 			// Listen for destroy event on input
    129 			this.$input.on( 'remove', function () {
    130 				that.controller.destroy();
    131 			} );
    132 
    133 			var collection = this.controller.get( 'items' );
    134 			this.$input.on( 'media:reset', function() {
    135 				collection.reset();
    136 			} );
    137 
    138 			collection.on( 'add remove reset', _.debounce( function () {
    139 				var ids = collection.pluck( 'id' ).join( ',' );
    140 				that.$input.val( ids ).trigger( 'change', [that.$( '.rwmb-media-input' )] );
    141 			}, 500 ) );
    142 		},
    143 
    144 		loadInitialAttachments: function () {
    145 			if ( ! this.$input.val() ) {
    146 				return;
    147 			}
    148 			var models = this.$input.data( 'attachments' ).map( function( attachment ) {
    149 				return wp.media.model.Attachment.create( attachment );
    150 			} );
    151 			this.controller.get( 'items' ).add( models );
    152 		},
    153 
    154 		// Creates media list
    155 		createList: function () {
    156 			this.list = new MediaList( {controller: this.controller} );
    157 		},
    158 
    159 		// Creates button that adds media
    160 		createAddButton: function () {
    161 			this.addButton = new MediaButton( {controller: this.controller} );
    162 		},
    163 
    164 		// Creates status
    165 		createStatus: function () {
    166 			this.status = new MediaStatus( {controller: this.controller} );
    167 		},
    168 
    169 		// Render field and adds sub fields
    170 		render: function () {
    171 			// Empty then add parts
    172 			this.$el.empty().append(
    173 				this.list.el,
    174 				this.status.el,
    175 				this.addButton.el
    176 			);
    177 		}
    178 	} );
    179 
    180 	/***
    181 	 * Media List
    182 	 * lists media
    183 	 */
    184 	MediaList = views.MediaList = Backbone.View.extend( {
    185 		tagName: 'ul',
    186 		className: 'rwmb-media-list',
    187 
    188 		initialize: function ( options ) {
    189 			this.controller = options.controller;
    190 			this.collection = this.controller.get( 'items' );
    191 			this.itemView = options.itemView || MediaItem;
    192 			this.getItemView = _.memoize( function ( item ) {
    193 					var itemView = new this.itemView( {
    194 						model: item,
    195 						controller: this.controller
    196 					} );
    197 
    198 					this.listenToItemView( itemView );
    199 
    200 					return itemView;
    201 				},
    202 				function ( item ) {
    203 					return item.cid;
    204 				}
    205 			);
    206 
    207 			this.listenTo( this.collection, 'add', this.addItemView );
    208 			this.listenTo( this.collection, 'remove', this.removeItemView );
    209 			this.listenTo( this.collection, 'reset', this.resetItemViews );
    210 
    211 			// Sort items using helper 'clone' to prevent trigger click on the image, which means reselect.
    212 			this.$el.sortable( {
    213 				helper : 'clone',
    214 				start: function ( event, ui ) {
    215 					ui.placeholder.height( ui.helper.outerHeight() );
    216 					ui.placeholder.width( ui.helper.outerWidth() );
    217 				},
    218 				update: function( event, ui ) {
    219 					ui.item.find( rwmb.inputSelectors ).first().trigger( 'mb_change' );
    220 				}
    221 			} );
    222 		},
    223 
    224 		listenToItemView: function ( itemView ) {
    225 			this.listenTo( itemView, 'click:remove', this.removeItem );
    226 			this.listenTo( itemView, 'click:switch', this.switchItem );
    227 			this.listenTo( itemView, 'click:edit', this.editItem );
    228 		},
    229 
    230 		addItemView: function ( item ) {
    231 			var index = this.collection.indexOf( item ),
    232 				itemEl = this.getItemView( item ).el,
    233 				$children = this.$el.children();
    234 
    235 			if ( 0 >= index ) {
    236 				this.$el.prepend( itemEl );
    237 			} else if ( $children.length <= index ) {
    238 				this.$el.append( itemEl )
    239 			} else {
    240 				$children.eq( index - 1 ).after( itemEl );
    241 			}
    242 		},
    243 
    244 		// Remove item view
    245 		removeItemView: function ( item ) {
    246 			this.getItemView( item ).$el.detach();
    247 		},
    248 
    249 		removeItem: function ( item ) {
    250 			this.collection.remove( item );
    251 		},
    252 
    253 		resetItemViews: function( items ){
    254 			var that = this;
    255 			_.each( that.models, that.removeItemView );
    256 			items.each( that.addItemView );
    257 		},
    258 
    259 		switchItem: function ( item ) {
    260 			if ( this._switchFrame ) {
    261 				this._switchFrame.dispose();
    262 			}
    263 			this._switchFrame = new MediaSelect( {
    264 				multiple: false,
    265 				editing: true,
    266 				library: {
    267 					type: this.controller.get( 'mimeType' )
    268 				},
    269 				edit: this.collection
    270 			} );
    271 
    272 			// Refresh content when frame opens
    273 			this._switchFrame.on( 'open', function() {
    274 				var frameContent = this._switchFrame.content.get();
    275 				if ( frameContent && frameContent.collection ) {
    276 					frameContent.collection.mirroring._hasMore = true;
    277 					frameContent.collection.more();
    278 				}
    279 			}, this );
    280 
    281 			this._switchFrame.on( 'select', function () {
    282 				var selection = this._switchFrame.state().get( 'selection' ),
    283 					collection = this.collection,
    284 					index = collection.indexOf( item );
    285 
    286 				if ( ! _.isEmpty( selection ) ) {
    287 					collection.remove( item );
    288 					collection.add( selection, {at: index} );
    289 				}
    290 			}, this );
    291 
    292 			this._switchFrame.open();
    293 			return false;
    294 		},
    295 
    296 		editItem: function ( item ) {
    297 			if ( this._editFrame ) {
    298 				this._editFrame.dispose();
    299 			}
    300 
    301 			// Trigger the media frame to open the correct item.
    302 			this._editFrame = new EditMedia( {
    303 				frame: 'edit-attachments',
    304 				controller: {
    305 					gridRouter: new wp.media.view.MediaFrame.Manage.Router()
    306 				},
    307 				library: this.collection,
    308 				model: item
    309 			} );
    310 
    311 			this._editFrame.open();
    312 		}
    313 	} );
    314 
    315 	/***
    316 	 * MediaStatus view.
    317 	 * Show number of selected/uploaded files and number of files remain if "maxStatus" parameter is true.
    318 	 */
    319 	MediaStatus = views.MediaStatus = Backbone.View.extend( {
    320 		tagName: 'div',
    321 		className: 'rwmb-media-status',
    322 		template: wp.template( 'rwmb-media-status' ),
    323 
    324 		initialize: function ( options ) {
    325 			this.controller = options.controller;
    326 
    327 			// Auto hide if maxStatus is false
    328 			if ( ! this.controller.get( 'maxStatus' ) ) {
    329 				this.$el.hide();
    330 				return;
    331 			}
    332 
    333 			// Re-render if changes happen in controller
    334 			this.listenTo( this.controller.get( 'items' ), 'update', this.render );
    335 			this.listenTo( this.controller.get( 'items' ), 'reset', this.render );
    336 
    337 			// Render
    338 			this.render();
    339 		},
    340 
    341 		render: function () {
    342 			this.$el.html( this.template( this.controller.toJSON() ) );
    343 		}
    344 	} );
    345 
    346 	/***
    347 	 * Media Button
    348 	 * Selects and adds media to controller
    349 	 */
    350 	MediaButton = views.MediaButton = Backbone.View.extend( {
    351 		tagName: 'div',
    352 		className: 'rwmb-media-add',
    353 		template: wp.template( 'rwmb-media-button' ),
    354 		events: {
    355 			'click .button': function () {
    356 				if ( this._frame ) {
    357 					this._frame.dispose();
    358 				}
    359 				var maxFiles = this.controller.get( 'maxFiles' );
    360 				this._frame = new MediaSelect( {
    361 					multiple: maxFiles > 1 || maxFiles <= 0 ? 'add' : false,
    362 					editing: true,
    363 					library: {
    364 						type: this.controller.get( 'mimeType' )
    365 					},
    366 					edit: this.collection
    367 				} );
    368 
    369 				// Refresh content when frame opens
    370 				this._frame.on( 'open', function() {
    371 					var frameContent = this._frame.content.get();
    372 					if ( frameContent && frameContent.collection ) {
    373 						frameContent.collection.mirroring._hasMore = true;
    374 						frameContent.collection.more();
    375 					}
    376 				}, this );
    377 
    378 				this._frame.on( 'select', function () {
    379 					var selection = this._frame.state().get( 'selection' );
    380 					if ( this.controller.get( 'addTo' ) === 'beginning' ) {
    381 						this.collection.add( selection.models, {at: 0} );
    382 					} else {
    383 						this.collection.add( selection.models );
    384 					}
    385 				}, this );
    386 
    387 				this._frame.open();
    388 			}
    389 		},
    390 		render: function () {
    391 			this.$el.html( this.template( {text: i18n.add} ) );
    392 			return this;
    393 		},
    394 
    395 		initialize: function ( options ) {
    396 			this.controller = options.controller;
    397 			this.collection = this.controller.get( 'items' );
    398 
    399 			// Auto hide if you reach the max number of media
    400 			this.listenTo( this.controller, 'change:full', function () {
    401 				this.$el.toggle( ! this.controller.get( 'full' ) );
    402 			} );
    403 
    404 			this.render();
    405 		}
    406 	} );
    407 
    408 	/***
    409 	 * MediaItem
    410 	 * View for individual media items
    411 	 */
    412 	MediaItem = views.MediaItem = Backbone.View.extend( {
    413 		tagName: 'li',
    414 		className: 'rwmb-file',
    415 		template: wp.template( 'rwmb-media-item' ),
    416 		initialize: function ( options ) {
    417 			this.controller = options.controller;
    418 			this.collection = this.controller.get( 'items' );
    419 			this.render();
    420 			this.listenTo( this.model, 'change', this.render );
    421 
    422 			this.$el.data( 'id', this.model.cid );
    423 		},
    424 
    425 		events: {
    426 			'click .rwmb-image-overlay': function ( e ) {
    427 				e.preventDefault();
    428 				this.trigger( 'click:switch', this.model );
    429 			},
    430 			'click .rwmb-remove-media': function ( e ) {
    431 				e.preventDefault();
    432 				this.trigger( 'click:remove', this.model );
    433 			},
    434 			'click .rwmb-edit-media': function ( e ) {
    435 				e.preventDefault();
    436 				this.trigger( 'click:edit', this.model );
    437 			}
    438 		},
    439 
    440 		render: function () {
    441 			var data = this.model.toJSON();
    442 			data.controller = this.controller.toJSON();
    443 			this.$el.html( this.template( data ) );
    444 			return this;
    445 		}
    446 	} );
    447 
    448 	/**
    449 	 * Extend media frames to make things work right
    450 	 */
    451 
    452 	/**
    453 	 * MediaDetails
    454 	 * Custom version of TwoColumn view to prevent all video and audio from being unset
    455 	 */
    456 	MediaDetails = views.MediaDetails = media.view.Attachment.Details.TwoColumn.extend( {
    457 		render: function () {
    458 			var that = this;
    459 			media.view.Attachment.Details.prototype.render.apply( this, arguments );
    460 			this.players = this.players || [];
    461 
    462 			media.mixin.unsetPlayers.call( this );
    463 
    464 			this.$( 'audio, video' ).each( function ( i, elem ) {
    465 				var el = media.view.MediaDetails.prepareSrc( elem );
    466 				that.players.push( new window.MediaElementPlayer( el, media.mixin.mejsSettings ) );
    467 			} );
    468 		}
    469 	} );
    470 
    471 	/**
    472 	 * MediaLibrary
    473 	 * Custom version of Library to exclude already selected media in a media frame
    474 	 */
    475 	MediaLibrary = media.controller.Library.extend( {
    476 		defaults: _.defaults( {
    477 			multiple: 'add',
    478 			filterable: 'all',
    479 			priority: 100,
    480 			syncSelection: false
    481 		}, media.controller.Library.prototype.defaults ),
    482 
    483 		activate: function () {
    484 			var library = this.get( 'library' ),
    485 				edit = this.frame.options.edit;
    486 
    487 			if ( this.editLibrary && this.editLibrary !== edit ) {
    488 				library.unobserve( this.editLibrary );
    489 			}
    490 
    491 			// Accepts attachments that exist in the original library and
    492 			// that do not exist in gallery's library.
    493 			library.validator = function ( attachment ) {
    494 				return ! ! this.mirroring.get( attachment.cid ) && ! edit.get( attachment.cid ) && media.model.Selection.prototype.validator.apply( this, arguments );
    495 			};
    496 
    497 			// Reset the library to ensure that all attachments are re-added
    498 			// to the collection. Do so silently, as calling `observe` will
    499 			// trigger the `reset` event.
    500 			library.reset( library.mirroring.models, {silent: true} );
    501 			library.observe( edit );
    502 			this.editLibrary = edit;
    503 
    504 			media.controller.Library.prototype.activate.apply( this, arguments );
    505 		}
    506 	} );
    507 
    508 	/**
    509 	 * MediaSelect
    510 	 * Custom version of Select media frame that uses  MediaLibrary
    511 	 */
    512 	MediaSelect = views.MediaSelect = MediaFrame.Select.extend( {
    513 		/**
    514 		 * Create the default states on the frame.
    515 		 */
    516 		createStates: function () {
    517 			var options = this.options;
    518 
    519 			// Add reference so we know MediaFrame belongs to MB plugin.
    520 			this.$el.attr( 'data-class', 'rwmb-field' );
    521 
    522 			if ( this.options.states ) {
    523 				return;
    524 			}
    525 
    526 			// Add the default states.
    527 			this.states.add( [
    528 				// Main states.
    529 				new MediaLibrary( {
    530 					library: media.query( options.library ),
    531 					multiple: options.multiple,
    532 					priority: 20
    533 				} )
    534 			] );
    535 		}
    536 	} );
    537 
    538 	/***
    539 	 * EditMedia
    540 	 * Custom version of EditAttachments frame to prevent all video and audio from being unset
    541 	 */
    542 	EditMedia = views.EditMedia = MediaFrame.EditAttachments.extend( {
    543 		/**
    544 		 * Content region rendering callback for the `edit-metadata` mode.
    545 		 *
    546 		 * @param {Object} contentRegion Basic object with a `view` property, which
    547 		 *                               should be set with the proper region view.
    548 		 */
    549 		editMetadataMode: function ( contentRegion ) {
    550 			contentRegion.view = new MediaDetails( {
    551 				controller: this,
    552 				model: this.model
    553 			} );
    554 
    555 			/**
    556 			 * Attach a subview to display fields added via the
    557 			 * `attachment_fields_to_edit` filter.
    558 			 */
    559 			contentRegion.view.views.set( '.attachment-compat', new media.view.AttachmentCompat( {
    560 				controller: this,
    561 				model: this.model
    562 			} ) );
    563 		},
    564 		resetRoute: function() {}
    565 	} );
    566 
    567 	function initMediaField() {
    568 		var $this = $( this ),
    569 			view = $this.data( 'view' );
    570 
    571 		if ( view ) {
    572 			return;
    573 		}
    574 
    575 		view = new MediaField( { input: this } );
    576 
    577 		$this.siblings( '.rwmb-media-view' ).remove();
    578 		$this.after( view.el );
    579 		$this.data( 'view', view );
    580 	}
    581 
    582 	function init( e ) {
    583 		$( e.target ).find( '.rwmb-file_advanced' ).each( initMediaField );
    584 	}
    585 
    586 	rwmb.$document
    587 		.on( 'mb_ready', init )
    588 		.on( 'clone', '.rwmb-file_advanced', initMediaField );
    589 } )( jQuery, wp, _, rwmb, i18nRwmbMedia );