Media: library grid view - improve browser history support.

Set view state properly when navigating history using the browser back/next button in the media library (grid view). Correctly handle navigating, search, image detail view and image edit mode. Also handle bookmarking/reloading.

Props kucrut, joemcgill, afercia.
Fixes #31846.



git-svn-id: https://develop.svn.wordpress.org/trunk@41021 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Adam Silverstein 2017-07-10 19:07:27 +00:00
parent 34c3d7d044
commit 2d5a59194d
5 changed files with 186 additions and 82 deletions

View File

@ -50,8 +50,10 @@ media.view.DeleteSelectedPermanentlyButton = require( './views/button/delete-sel
*/ */
var Router = Backbone.Router.extend({ var Router = Backbone.Router.extend({
routes: { routes: {
'upload.php?item=:slug': 'showItem', 'upload.php?item=:slug&mode=edit': 'editItem',
'upload.php?search=:query': 'search' 'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search',
'upload.php': 'reset'
}, },
// Map routes against the page URL // Map routes against the page URL
@ -59,6 +61,14 @@ var Router = Backbone.Router.extend({
return 'upload.php' + url; return 'upload.php' + url;
}, },
reset: function() {
var frame = wp.media.frames.edit;
if ( frame ) {
frame.close();
}
},
// Respond to the search route by filling the search field and trigggering the input event // Respond to the search route by filling the search field and trigggering the input event
search: function( query ) { search: function( query ) {
jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
@ -67,21 +77,30 @@ var Router = Backbone.Router.extend({
// Show the modal with a specific item // Show the modal with a specific item
showItem: function( query ) { showItem: function( query ) {
var media = wp.media, var media = wp.media,
library = media.frame.state().get('library'), frame = media.frames.browse,
library = frame.state().get('library'),
item; item;
// Trigger the media frame to open the correct item // Trigger the media frame to open the correct item
item = library.findWhere( { id: parseInt( query, 10 ) } ); item = library.findWhere( { id: parseInt( query, 10 ) } );
item.set( 'skipHistory', true );
if ( item ) { if ( item ) {
media.frame.trigger( 'edit:attachment', item ); frame.trigger( 'edit:attachment', item );
} else { } else {
item = media.attachment( query ); item = media.attachment( query );
media.frame.listenTo( item, 'change', function( model ) { frame.listenTo( item, 'change', function( model ) {
media.frame.stopListening( item ); frame.stopListening( item );
media.frame.trigger( 'edit:attachment', model ); frame.trigger( 'edit:attachment', model );
} ); } );
item.fetch(); item.fetch();
} }
},
// Show the modal in edit mode with a specific item.
editItem: function( query ) {
this.showItem( query );
wp.media.frames.edit.content.mode( 'edit-details' );
} }
}); });
@ -107,8 +126,16 @@ var Details = wp.media.view.Attachment.Details,
TwoColumn = Details.extend({ TwoColumn = Details.extend({
template: wp.template( 'attachment-details-two-column' ), template: wp.template( 'attachment-details-two-column' ),
initialize: function() {
this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
Details.prototype.initialize.apply( this, arguments );
},
editAttachment: function( event ) { editAttachment: function( event ) {
event.preventDefault(); if ( event ) {
event.preventDefault();
}
this.controller.content.mode( 'edit-image' ); this.controller.content.mode( 'edit-image' );
}, },
@ -401,13 +428,19 @@ EditAttachments = MediaFrame.extend({
// Bind default title creation. // Bind default title creation.
this.on( 'title:create:default', this.createTitle, this ); 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-metadata', this.editMetadataMode, this );
this.on( 'content:create:edit-image', this.editImageMode, this ); this.on( 'content:create:edit-image', this.editImageMode, this );
this.on( 'content:render:edit-image', this.editImageModeRender, this ); this.on( 'content:render:edit-image', this.editImageModeRender, this );
this.on( 'refresh', this.rerender, this );
this.on( 'close', this.detach ); this.on( 'close', this.detach );
this.bindModelHandlers();
this.listenTo( this.gridRouter, 'route:search', this.close, this );
},
bindModelHandlers: function() {
// Close the modal if the attachment is deleted.
this.listenTo( this.model, 'change:status destroy', this.close, this );
}, },
createModal: function() { createModal: function() {
@ -424,7 +457,6 @@ EditAttachments = MediaFrame.extend({
// Completely destroy the modal DOM element when closing it. // Completely destroy the modal DOM element when closing it.
this.modal.on( 'close', _.bind( function() { this.modal.on( 'close', _.bind( function() {
this.modal.remove();
$( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
// Restore the original focus item if possible // Restore the original focus item if possible
$( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
@ -442,7 +474,10 @@ EditAttachments = MediaFrame.extend({
*/ */
createStates: function() { createStates: function() {
this.states.add([ this.states.add([
new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) new wp.media.controller.EditAttachmentMetadata({
model: this.model,
library: this.library
})
]); ]);
}, },
@ -467,8 +502,8 @@ EditAttachments = MediaFrame.extend({
model: this.model model: this.model
}) ); }) );
// Update browser url when navigating media details // Update browser url when navigating media details, except on load.
if ( this.model ) { if ( this.model && ! this.model.get( 'skipHistory' ) ) {
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
} }
}, },
@ -494,6 +529,9 @@ EditAttachments = MediaFrame.extend({
frame: this, frame: this,
controller: editImageController controller: editImageController
} ); } );
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
}, },
editImageModeRender: function( view ) { editImageModeRender: function( view ) {
@ -508,7 +546,13 @@ EditAttachments = MediaFrame.extend({
/** /**
* Rerender the view. * Rerender the view.
*/ */
rerender: function() { rerender: function( model ) {
this.stopListening( this.model );
this.model = model;
this.bindModelHandlers();
// Only rerender the `content` region. // Only rerender the `content` region.
if ( this.content.mode() !== 'edit-metadata' ) { if ( this.content.mode() !== 'edit-metadata' ) {
this.content.mode( 'edit-metadata' ); this.content.mode( 'edit-metadata' );
@ -527,8 +571,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.left' ).blur(); this.$( '.left' ).blur();
return; return;
} }
this.model = this.library.at( this.getCurrentIndex() - 1 ); this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
this.rerender();
this.$( '.left' ).focus(); this.$( '.left' ).focus();
}, },
@ -540,8 +583,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.right' ).blur(); this.$( '.right' ).blur();
return; return;
} }
this.model = this.library.at( this.getCurrentIndex() + 1 ); this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
this.rerender();
this.$( '.right' ).focus(); this.$( '.right' ).focus();
}, },
@ -576,7 +618,9 @@ EditAttachments = MediaFrame.extend({
}, },
resetRoute: function() { resetRoute: function() {
this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(),
url = '' !== searchTerm ? '?search=' + searchTerm : '';
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
} }
}); });
@ -666,38 +710,42 @@ Manage = MediaFrame.extend({
this.bindRegionModeHandlers(); this.bindRegionModeHandlers();
this.render(); this.render();
this.bindSearchHandler(); this.bindSearchHandler();
wp.media.frames.browse = this;
}, },
bindSearchHandler: function() { bindSearchHandler: function() {
var search = this.$( '#media-search-input' ), var search = this.$( '#media-search-input' ),
currentSearch = this.options.container.data( 'search' ),
searchView = this.browserView.toolbar.get( 'search' ).$el, searchView = this.browserView.toolbar.get( 'search' ).$el,
listMode = this.$( '.view-list' ), listMode = this.$( '.view-list' ),
input = _.debounce( function (e) { input = _.throttle( function (e) {
var val = $( e.currentTarget ).val(), var val = $( e.currentTarget ).val(),
url = ''; url = '';
if ( val ) { if ( val ) {
url += '?search=' + val; url += '?search=' + val;
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
} }
this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
}, 1000 ); }, 1000 );
// Update the URL when entering search string (at most once per second) // Update the URL when entering search string (at most once per second)
search.on( 'input', _.bind( input, this ) ); search.on( 'input', _.bind( input, this ) );
searchView.val( currentSearch ).trigger( 'input' );
this.gridRouter.on( 'route:search', function () { this.gridRouter
var href = window.location.href; .on( 'route:search', function () {
if ( href.indexOf( 'mode=' ) > -1 ) { var href = window.location.href;
href = href.replace( /mode=[^&]+/g, 'mode=list' ); if ( href.indexOf( 'mode=' ) > -1 ) {
} else { href = href.replace( /mode=[^&]+/g, 'mode=list' );
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; } else {
} href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
href = href.replace( 'search=', 's=' ); }
listMode.prop( 'href', href ); href = href.replace( 'search=', 's=' );
} ); listMode.prop( 'href', href );
})
.on( 'route:reset', function() {
searchView.val( '' ).trigger( 'input' );
});
}, },
/** /**
@ -789,12 +837,16 @@ Manage = MediaFrame.extend({
*/ */
openEditAttachmentModal: function( model ) { openEditAttachmentModal: function( model ) {
// Create a new EditAttachment frame, passing along the library and the attachment model. // Create a new EditAttachment frame, passing along the library and the attachment model.
wp.media( { if ( wp.media.frames.edit ) {
frame: 'edit-attachments', wp.media.frames.edit.open().trigger( 'refresh', model );
controller: this, } else {
library: this.state().get('library'), wp.media.frames.edit = wp.media( {
model: model frame: 'edit-attachments',
} ); controller: this,
library: this.state().get('library'),
model: model
} );
}
}, },
/** /**

View File

@ -8,8 +8,10 @@
*/ */
var Router = Backbone.Router.extend({ var Router = Backbone.Router.extend({
routes: { routes: {
'upload.php?item=:slug': 'showItem', 'upload.php?item=:slug&mode=edit': 'editItem',
'upload.php?search=:query': 'search' 'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search',
'upload.php': 'reset'
}, },
// Map routes against the page URL // Map routes against the page URL
@ -17,6 +19,14 @@ var Router = Backbone.Router.extend({
return 'upload.php' + url; return 'upload.php' + url;
}, },
reset: function() {
var frame = wp.media.frames.edit;
if ( frame ) {
frame.close();
}
},
// Respond to the search route by filling the search field and trigggering the input event // Respond to the search route by filling the search field and trigggering the input event
search: function( query ) { search: function( query ) {
jQuery( '#media-search-input' ).val( query ).trigger( 'input' ); jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
@ -25,21 +35,30 @@ var Router = Backbone.Router.extend({
// Show the modal with a specific item // Show the modal with a specific item
showItem: function( query ) { showItem: function( query ) {
var media = wp.media, var media = wp.media,
library = media.frame.state().get('library'), frame = media.frames.browse,
library = frame.state().get('library'),
item; item;
// Trigger the media frame to open the correct item // Trigger the media frame to open the correct item
item = library.findWhere( { id: parseInt( query, 10 ) } ); item = library.findWhere( { id: parseInt( query, 10 ) } );
item.set( 'skipHistory', true );
if ( item ) { if ( item ) {
media.frame.trigger( 'edit:attachment', item ); frame.trigger( 'edit:attachment', item );
} else { } else {
item = media.attachment( query ); item = media.attachment( query );
media.frame.listenTo( item, 'change', function( model ) { frame.listenTo( item, 'change', function( model ) {
media.frame.stopListening( item ); frame.stopListening( item );
media.frame.trigger( 'edit:attachment', model ); frame.trigger( 'edit:attachment', model );
} ); } );
item.fetch(); item.fetch();
} }
},
// Show the modal in edit mode with a specific item.
editItem: function( query ) {
this.showItem( query );
wp.media.frames.edit.content.mode( 'edit-details' );
} }
}); });

View File

@ -17,8 +17,16 @@ var Details = wp.media.view.Attachment.Details,
TwoColumn = Details.extend({ TwoColumn = Details.extend({
template: wp.template( 'attachment-details-two-column' ), template: wp.template( 'attachment-details-two-column' ),
initialize: function() {
this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
Details.prototype.initialize.apply( this, arguments );
},
editAttachment: function( event ) { editAttachment: function( event ) {
event.preventDefault(); if ( event ) {
event.preventDefault();
}
this.controller.content.mode( 'edit-image' ); this.controller.content.mode( 'edit-image' );
}, },

View File

@ -59,13 +59,19 @@ EditAttachments = MediaFrame.extend({
// Bind default title creation. // Bind default title creation.
this.on( 'title:create:default', this.createTitle, this ); 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-metadata', this.editMetadataMode, this );
this.on( 'content:create:edit-image', this.editImageMode, this ); this.on( 'content:create:edit-image', this.editImageMode, this );
this.on( 'content:render:edit-image', this.editImageModeRender, this ); this.on( 'content:render:edit-image', this.editImageModeRender, this );
this.on( 'refresh', this.rerender, this );
this.on( 'close', this.detach ); this.on( 'close', this.detach );
this.bindModelHandlers();
this.listenTo( this.gridRouter, 'route:search', this.close, this );
},
bindModelHandlers: function() {
// Close the modal if the attachment is deleted.
this.listenTo( this.model, 'change:status destroy', this.close, this );
}, },
createModal: function() { createModal: function() {
@ -82,7 +88,6 @@ EditAttachments = MediaFrame.extend({
// Completely destroy the modal DOM element when closing it. // Completely destroy the modal DOM element when closing it.
this.modal.on( 'close', _.bind( function() { this.modal.on( 'close', _.bind( function() {
this.modal.remove();
$( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */ $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
// Restore the original focus item if possible // Restore the original focus item if possible
$( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus(); $( 'li.attachment[data-id="' + this.model.get( 'id' ) +'"]' ).focus();
@ -100,7 +105,10 @@ EditAttachments = MediaFrame.extend({
*/ */
createStates: function() { createStates: function() {
this.states.add([ this.states.add([
new wp.media.controller.EditAttachmentMetadata( { model: this.model } ) new wp.media.controller.EditAttachmentMetadata({
model: this.model,
library: this.library
})
]); ]);
}, },
@ -125,8 +133,8 @@ EditAttachments = MediaFrame.extend({
model: this.model model: this.model
}) ); }) );
// Update browser url when navigating media details // Update browser url when navigating media details, except on load.
if ( this.model ) { if ( this.model && ! this.model.get( 'skipHistory' ) ) {
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) ); this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
} }
}, },
@ -152,6 +160,9 @@ EditAttachments = MediaFrame.extend({
frame: this, frame: this,
controller: editImageController controller: editImageController
} ); } );
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
}, },
editImageModeRender: function( view ) { editImageModeRender: function( view ) {
@ -166,7 +177,13 @@ EditAttachments = MediaFrame.extend({
/** /**
* Rerender the view. * Rerender the view.
*/ */
rerender: function() { rerender: function( model ) {
this.stopListening( this.model );
this.model = model;
this.bindModelHandlers();
// Only rerender the `content` region. // Only rerender the `content` region.
if ( this.content.mode() !== 'edit-metadata' ) { if ( this.content.mode() !== 'edit-metadata' ) {
this.content.mode( 'edit-metadata' ); this.content.mode( 'edit-metadata' );
@ -185,8 +202,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.left' ).blur(); this.$( '.left' ).blur();
return; return;
} }
this.model = this.library.at( this.getCurrentIndex() - 1 ); this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
this.rerender();
this.$( '.left' ).focus(); this.$( '.left' ).focus();
}, },
@ -198,8 +214,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.right' ).blur(); this.$( '.right' ).blur();
return; return;
} }
this.model = this.library.at( this.getCurrentIndex() + 1 ); this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
this.rerender();
this.$( '.right' ).focus(); this.$( '.right' ).focus();
}, },
@ -234,7 +249,9 @@ EditAttachments = MediaFrame.extend({
}, },
resetRoute: function() { resetRoute: function() {
this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) ); var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(),
url = '' !== searchTerm ? '?search=' + searchTerm : '';
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
} }
}); });

View File

@ -81,38 +81,42 @@ Manage = MediaFrame.extend({
this.bindRegionModeHandlers(); this.bindRegionModeHandlers();
this.render(); this.render();
this.bindSearchHandler(); this.bindSearchHandler();
wp.media.frames.browse = this;
}, },
bindSearchHandler: function() { bindSearchHandler: function() {
var search = this.$( '#media-search-input' ), var search = this.$( '#media-search-input' ),
currentSearch = this.options.container.data( 'search' ),
searchView = this.browserView.toolbar.get( 'search' ).$el, searchView = this.browserView.toolbar.get( 'search' ).$el,
listMode = this.$( '.view-list' ), listMode = this.$( '.view-list' ),
input = _.debounce( function (e) { input = _.throttle( function (e) {
var val = $( e.currentTarget ).val(), var val = $( e.currentTarget ).val(),
url = ''; url = '';
if ( val ) { if ( val ) {
url += '?search=' + val; url += '?search=' + val;
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
} }
this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
}, 1000 ); }, 1000 );
// Update the URL when entering search string (at most once per second) // Update the URL when entering search string (at most once per second)
search.on( 'input', _.bind( input, this ) ); search.on( 'input', _.bind( input, this ) );
searchView.val( currentSearch ).trigger( 'input' );
this.gridRouter.on( 'route:search', function () { this.gridRouter
var href = window.location.href; .on( 'route:search', function () {
if ( href.indexOf( 'mode=' ) > -1 ) { var href = window.location.href;
href = href.replace( /mode=[^&]+/g, 'mode=list' ); if ( href.indexOf( 'mode=' ) > -1 ) {
} else { href = href.replace( /mode=[^&]+/g, 'mode=list' );
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list'; } else {
} href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
href = href.replace( 'search=', 's=' ); }
listMode.prop( 'href', href ); href = href.replace( 'search=', 's=' );
} ); listMode.prop( 'href', href );
})
.on( 'route:reset', function() {
searchView.val( '' ).trigger( 'input' );
});
}, },
/** /**
@ -204,12 +208,16 @@ Manage = MediaFrame.extend({
*/ */
openEditAttachmentModal: function( model ) { openEditAttachmentModal: function( model ) {
// Create a new EditAttachment frame, passing along the library and the attachment model. // Create a new EditAttachment frame, passing along the library and the attachment model.
wp.media( { if ( wp.media.frames.edit ) {
frame: 'edit-attachments', wp.media.frames.edit.open().trigger( 'refresh', model );
controller: this, } else {
library: this.state().get('library'), wp.media.frames.edit = wp.media( {
model: model frame: 'edit-attachments',
} ); controller: this,
library: this.state().get('library'),
model: model
} );
}
}, },
/** /**