2015-02-22 02:25:27 -05:00
|
|
|
/*globals wp, _, jQuery */
|
2015-02-22 01:56:27 -05:00
|
|
|
|
2015-02-08 19:43:50 -05:00
|
|
|
/**
|
|
|
|
* wp.media.view.Attachments
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @augments wp.media.View
|
|
|
|
* @augments wp.Backbone.View
|
|
|
|
* @augments Backbone.View
|
|
|
|
*/
|
|
|
|
var View = require( './view.js' ),
|
|
|
|
Attachment = require( './attachment.js' ),
|
|
|
|
$ = jQuery,
|
|
|
|
Attachments;
|
|
|
|
|
|
|
|
Attachments = View.extend({
|
|
|
|
tagName: 'ul',
|
|
|
|
className: 'attachments',
|
|
|
|
|
|
|
|
attributes: {
|
|
|
|
tabIndex: -1
|
|
|
|
},
|
|
|
|
|
|
|
|
initialize: function() {
|
|
|
|
this.el.id = _.uniqueId('__attachments-view-');
|
|
|
|
|
|
|
|
_.defaults( this.options, {
|
|
|
|
refreshSensitivity: wp.media.isTouchDevice ? 300 : 200,
|
|
|
|
refreshThreshold: 3,
|
|
|
|
AttachmentView: Attachment,
|
|
|
|
sortable: false,
|
|
|
|
resize: true,
|
|
|
|
idealColumnWidth: $( window ).width() < 640 ? 135 : 150
|
|
|
|
});
|
|
|
|
|
|
|
|
this._viewsByCid = {};
|
|
|
|
this.$window = $( window );
|
|
|
|
this.resizeEvent = 'resize.media-modal-columns';
|
|
|
|
|
|
|
|
this.collection.on( 'add', function( attachment ) {
|
|
|
|
this.views.add( this.createAttachmentView( attachment ), {
|
|
|
|
at: this.collection.indexOf( attachment )
|
|
|
|
});
|
|
|
|
}, this );
|
|
|
|
|
|
|
|
this.collection.on( 'remove', function( attachment ) {
|
|
|
|
var view = this._viewsByCid[ attachment.cid ];
|
|
|
|
delete this._viewsByCid[ attachment.cid ];
|
|
|
|
|
|
|
|
if ( view ) {
|
|
|
|
view.remove();
|
|
|
|
}
|
|
|
|
}, this );
|
|
|
|
|
|
|
|
this.collection.on( 'reset', this.render, this );
|
|
|
|
|
|
|
|
this.listenTo( this.controller, 'library:selection:add', this.attachmentFocus );
|
|
|
|
|
|
|
|
// Throttle the scroll handler and bind this.
|
|
|
|
this.scroll = _.chain( this.scroll ).bind( this ).throttle( this.options.refreshSensitivity ).value();
|
|
|
|
|
|
|
|
this.options.scrollElement = this.options.scrollElement || this.el;
|
|
|
|
$( this.options.scrollElement ).on( 'scroll', this.scroll );
|
|
|
|
|
|
|
|
this.initSortable();
|
|
|
|
|
|
|
|
_.bindAll( this, 'setColumns' );
|
|
|
|
|
|
|
|
if ( this.options.resize ) {
|
|
|
|
this.on( 'ready', this.bindEvents );
|
|
|
|
this.controller.on( 'open', this.setColumns );
|
|
|
|
|
|
|
|
// Call this.setColumns() after this view has been rendered in the DOM so
|
|
|
|
// attachments get proper width applied.
|
|
|
|
_.defer( this.setColumns, this );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
bindEvents: function() {
|
|
|
|
this.$window.off( this.resizeEvent ).on( this.resizeEvent, _.debounce( this.setColumns, 50 ) );
|
|
|
|
},
|
|
|
|
|
|
|
|
attachmentFocus: function() {
|
|
|
|
this.$( 'li:first' ).focus();
|
|
|
|
},
|
|
|
|
|
|
|
|
restoreFocus: function() {
|
|
|
|
this.$( 'li.selected:first' ).focus();
|
|
|
|
},
|
|
|
|
|
|
|
|
arrowEvent: function( event ) {
|
|
|
|
var attachments = this.$el.children( 'li' ),
|
|
|
|
perRow = this.columns,
|
|
|
|
index = attachments.filter( ':focus' ).index(),
|
|
|
|
row = ( index + 1 ) <= perRow ? 1 : Math.ceil( ( index + 1 ) / perRow );
|
|
|
|
|
|
|
|
if ( index === -1 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Left arrow
|
|
|
|
if ( 37 === event.keyCode ) {
|
|
|
|
if ( 0 === index ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
attachments.eq( index - 1 ).focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Up arrow
|
|
|
|
if ( 38 === event.keyCode ) {
|
|
|
|
if ( 1 === row ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
attachments.eq( index - perRow ).focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Right arrow
|
|
|
|
if ( 39 === event.keyCode ) {
|
|
|
|
if ( attachments.length === index ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
attachments.eq( index + 1 ).focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Down arrow
|
|
|
|
if ( 40 === event.keyCode ) {
|
|
|
|
if ( Math.ceil( attachments.length / perRow ) === row ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
attachments.eq( index + perRow ).focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
dispose: function() {
|
|
|
|
this.collection.props.off( null, null, this );
|
|
|
|
if ( this.options.resize ) {
|
|
|
|
this.$window.off( this.resizeEvent );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* call 'dispose' directly on the parent class
|
|
|
|
*/
|
|
|
|
View.prototype.dispose.apply( this, arguments );
|
|
|
|
},
|
|
|
|
|
|
|
|
setColumns: function() {
|
|
|
|
var prev = this.columns,
|
|
|
|
width = this.$el.width();
|
|
|
|
|
|
|
|
if ( width ) {
|
|
|
|
this.columns = Math.min( Math.round( width / this.options.idealColumnWidth ), 12 ) || 1;
|
|
|
|
|
|
|
|
if ( ! prev || prev !== this.columns ) {
|
|
|
|
this.$el.closest( '.media-frame-content' ).attr( 'data-columns', this.columns );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
initSortable: function() {
|
|
|
|
var collection = this.collection;
|
|
|
|
|
|
|
|
if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$el.sortable( _.extend({
|
|
|
|
// If the `collection` has a `comparator`, disable sorting.
|
|
|
|
disabled: !! collection.comparator,
|
|
|
|
|
|
|
|
// Change the position of the attachment as soon as the
|
|
|
|
// mouse pointer overlaps a thumbnail.
|
|
|
|
tolerance: 'pointer',
|
|
|
|
|
|
|
|
// Record the initial `index` of the dragged model.
|
|
|
|
start: function( event, ui ) {
|
|
|
|
ui.item.data('sortableIndexStart', ui.item.index());
|
|
|
|
},
|
|
|
|
|
|
|
|
// Update the model's index in the collection.
|
|
|
|
// Do so silently, as the view is already accurate.
|
|
|
|
update: function( event, ui ) {
|
|
|
|
var model = collection.at( ui.item.data('sortableIndexStart') ),
|
|
|
|
comparator = collection.comparator;
|
|
|
|
|
|
|
|
// Temporarily disable the comparator to prevent `add`
|
|
|
|
// from re-sorting.
|
|
|
|
delete collection.comparator;
|
|
|
|
|
|
|
|
// Silently shift the model to its new index.
|
|
|
|
collection.remove( model, {
|
|
|
|
silent: true
|
|
|
|
});
|
|
|
|
collection.add( model, {
|
|
|
|
silent: true,
|
|
|
|
at: ui.item.index()
|
|
|
|
});
|
|
|
|
|
|
|
|
// Restore the comparator.
|
|
|
|
collection.comparator = comparator;
|
|
|
|
|
|
|
|
// Fire the `reset` event to ensure other collections sync.
|
|
|
|
collection.trigger( 'reset', collection );
|
|
|
|
|
|
|
|
// If the collection is sorted by menu order,
|
|
|
|
// update the menu order.
|
|
|
|
collection.saveMenuOrder();
|
|
|
|
}
|
|
|
|
}, this.options.sortable ) );
|
|
|
|
|
|
|
|
// If the `orderby` property is changed on the `collection`,
|
|
|
|
// check to see if we have a `comparator`. If so, disable sorting.
|
|
|
|
collection.props.on( 'change:orderby', function() {
|
|
|
|
this.$el.sortable( 'option', 'disabled', !! collection.comparator );
|
|
|
|
}, this );
|
|
|
|
|
|
|
|
this.collection.props.on( 'change:orderby', this.refreshSortable, this );
|
|
|
|
this.refreshSortable();
|
|
|
|
},
|
|
|
|
|
|
|
|
refreshSortable: function() {
|
|
|
|
if ( wp.media.isTouchDevice || ! this.options.sortable || ! $.fn.sortable ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the `collection` has a `comparator`, disable sorting.
|
|
|
|
var collection = this.collection,
|
|
|
|
orderby = collection.props.get('orderby'),
|
|
|
|
enabled = 'menuOrder' === orderby || ! collection.comparator;
|
|
|
|
|
|
|
|
this.$el.sortable( 'option', 'disabled', ! enabled );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {wp.media.model.Attachment} attachment
|
|
|
|
* @returns {wp.media.View}
|
|
|
|
*/
|
|
|
|
createAttachmentView: function( attachment ) {
|
|
|
|
var view = new this.options.AttachmentView({
|
|
|
|
controller: this.controller,
|
|
|
|
model: attachment,
|
|
|
|
collection: this.collection,
|
|
|
|
selection: this.options.selection
|
|
|
|
});
|
|
|
|
|
|
|
|
return this._viewsByCid[ attachment.cid ] = view;
|
|
|
|
},
|
|
|
|
|
|
|
|
prepare: function() {
|
|
|
|
// Create all of the Attachment views, and replace
|
|
|
|
// the list in a single DOM operation.
|
|
|
|
if ( this.collection.length ) {
|
|
|
|
this.views.set( this.collection.map( this.createAttachmentView, this ) );
|
|
|
|
|
|
|
|
// If there are no elements, clear the views and load some.
|
|
|
|
} else {
|
|
|
|
this.views.unset();
|
|
|
|
this.collection.more().done( this.scroll );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
ready: function() {
|
|
|
|
// Trigger the scroll event to check if we're within the
|
|
|
|
// threshold to query for additional attachments.
|
|
|
|
this.scroll();
|
|
|
|
},
|
|
|
|
|
|
|
|
scroll: function() {
|
|
|
|
var view = this,
|
|
|
|
el = this.options.scrollElement,
|
|
|
|
scrollTop = el.scrollTop,
|
|
|
|
toolbar;
|
|
|
|
|
|
|
|
// The scroll event occurs on the document, but the element
|
|
|
|
// that should be checked is the document body.
|
2015-02-09 11:01:29 -05:00
|
|
|
if ( el === document ) {
|
2015-02-08 19:43:50 -05:00
|
|
|
el = document.body;
|
|
|
|
scrollTop = $(document).scrollTop();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! $(el).is(':visible') || ! this.collection.hasMore() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
toolbar = this.views.parent.toolbar;
|
|
|
|
|
|
|
|
// Show the spinner only if we are close to the bottom.
|
|
|
|
if ( el.scrollHeight - ( scrollTop + el.clientHeight ) < el.clientHeight / 3 ) {
|
|
|
|
toolbar.get('spinner').show();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( el.scrollHeight < scrollTop + ( el.clientHeight * this.options.refreshThreshold ) ) {
|
|
|
|
this.collection.more().done(function() {
|
|
|
|
view.scroll();
|
|
|
|
toolbar.get('spinner').hide();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-02-09 11:01:29 -05:00
|
|
|
module.exports = Attachments;
|