/*globals wp, _, jQuery */ /** * wp.media.view.MediaFrame.EditAttachments * * A frame for editing the details of a specific media item. * * Opens in a modal by default. * * Requires an attachment model to be passed in the options hash under `model`. * * @class * @augments wp.media.view.Frame * @augments wp.media.View * @augments wp.Backbone.View * @augments Backbone.View * @mixes wp.media.controller.StateMachine */ var Frame = require( '../frame.js' ), MediaFrame = require( '../media-frame.js' ), Modal = require( '../modal.js' ), EditAttachmentMetadata = require( '../../controllers/edit-attachment-metadata.js' ), TwoColumn = require( '../attachment/details-two-column.js' ), AttachmentCompat = require( '../attachment-compat.js' ), EditImageController = require( '../../controllers/edit-image.js' ), DetailsView = require( '../edit-image-details.js' ), $ = jQuery, EditAttachments; EditAttachments = MediaFrame.extend({ className: 'edit-attachment-frame', template: wp.template( 'edit-attachment-frame' ), regions: [ 'title', 'content' ], events: { 'click .left': 'previousMediaItem', 'click .right': 'nextMediaItem' }, initialize: function() { Frame.prototype.initialize.apply( this, arguments ); _.defaults( this.options, { modal: true, state: 'edit-attachment' }); this.controller = this.options.controller; this.gridRouter = this.controller.gridRouter; this.library = this.options.library; if ( this.options.model ) { this.model = this.options.model; } this.bindHandlers(); this.createStates(); this.createModal(); this.title.mode( 'default' ); this.toggleNav(); }, bindHandlers: function() { // Bind default title creation. this.on( 'title:create:default', this.createTitle, this ); // Close the modal if the attachment is deleted. this.listenTo( this.model, 'change:status destroy', this.close, this ); this.on( 'content:create:edit-metadata', this.editMetadataMode, this ); this.on( 'content:create:edit-image', this.editImageMode, this ); this.on( 'content:render:edit-image', this.editImageModeRender, this ); this.on( 'close', this.detach ); }, createModal: function() { // Initialize modal container view. if ( this.options.modal ) { this.modal = new Modal({ controller: this, title: this.options.title }); this.modal.on( 'open', _.bind( function () { $( 'body' ).on( 'keydown.media-modal', _.bind( this.keyEvent, this ) ); }, this ) ); // Completely destroy the modal DOM element when closing it. this.modal.on( 'close', _.bind( function() { this.modal.remove(); $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ // Restore the original focus item if possible $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); this.resetRoute(); }, this ) ); // Set this frame as the modal's content. this.modal.content( this ); this.modal.open(); } }, /** * Add the default states to the frame. */ createStates: function() { this.states.add([ new EditAttachmentMetadata( { model: this.model } ) ]); }, /** * Content region rendering callback for the `edit-metadata` mode. * * @param {Object} contentRegion Basic object with a `view` property, which * should be set with the proper region view. */ editMetadataMode: function( contentRegion ) { contentRegion.view = new TwoColumn({ controller: this, model: this.model }); /** * Attach a subview to display fields added via the * `attachment_fields_to_edit` filter. */ contentRegion.view.views.set( '.attachment-compat', new AttachmentCompat({ controller: this, model: this.model }) ); // Update browser url when navigating media details if ( this.model ) { this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); } }, /** * Render the EditImage view into the frame's content region. * * @param {Object} contentRegion Basic object with a `view` property, which * should be set with the proper region view. */ editImageMode: function( contentRegion ) { var editImageController = new EditImageController( { model: this.model, frame: this } ); // Noop some methods. editImageController._toolbar = function() {}; editImageController._router = function() {}; editImageController._menu = function() {}; contentRegion.view = new DetailsView( { model: this.model, frame: this, controller: editImageController } ); }, editImageModeRender: function( view ) { view.on( 'ready', view.loadEditor ); }, toggleNav: function() { this.$('.left').toggleClass( 'disabled', ! this.hasPrevious() ); this.$('.right').toggleClass( 'disabled', ! this.hasNext() ); }, /** * Rerender the view. */ rerender: function() { // Only rerender the `content` region. if ( this.content.mode() !== 'edit-metadata' ) { this.content.mode( 'edit-metadata' ); } else { this.content.render(); } this.toggleNav(); }, /** * Click handler to switch to the previous media item. */ previousMediaItem: function() { if ( ! this.hasPrevious() ) { this.$( '.left' ).blur(); return; } this.model = this.library.at( this.getCurrentIndex() - 1 ); this.rerender(); this.$( '.left' ).focus(); }, /** * Click handler to switch to the next media item. */ nextMediaItem: function() { if ( ! this.hasNext() ) { this.$( '.right' ).blur(); return; } this.model = this.library.at( this.getCurrentIndex() + 1 ); this.rerender(); this.$( '.right' ).focus(); }, getCurrentIndex: function() { return this.library.indexOf( this.model ); }, hasNext: function() { return ( this.getCurrentIndex() + 1 ) < this.library.length; }, hasPrevious: function() { return ( this.getCurrentIndex() - 1 ) > -1; }, /** * Respond to the keyboard events: right arrow, left arrow, except when * focus is in a textarea or input field. */ keyEvent: function( event ) { if ( ( 'INPUT' === event.target.nodeName || 'TEXTAREA' === event.target.nodeName ) && ! ( event.target.readOnly || event.target.disabled ) ) { return; } // The right arrow key if ( 39 === event.keyCode ) { this.nextMediaItem(); } // The left arrow key if ( 37 === event.keyCode ) { this.previousMediaItem(); } }, resetRoute: function() { this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); } }); module.exports = EditAttachments;