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({
routes: {
'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search'
'upload.php?item=:slug&mode=edit': 'editItem',
'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search',
'upload.php': 'reset'
},
// Map routes against the page URL
@ -59,6 +61,14 @@ var Router = Backbone.Router.extend({
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
search: function( query ) {
jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
@ -67,21 +77,30 @@ var Router = Backbone.Router.extend({
// Show the modal with a specific item
showItem: function( query ) {
var media = wp.media,
library = media.frame.state().get('library'),
frame = media.frames.browse,
library = frame.state().get('library'),
item;
// Trigger the media frame to open the correct item
item = library.findWhere( { id: parseInt( query, 10 ) } );
item.set( 'skipHistory', true );
if ( item ) {
media.frame.trigger( 'edit:attachment', item );
frame.trigger( 'edit:attachment', item );
} else {
item = media.attachment( query );
media.frame.listenTo( item, 'change', function( model ) {
media.frame.stopListening( item );
media.frame.trigger( 'edit:attachment', model );
frame.listenTo( item, 'change', function( model ) {
frame.stopListening( item );
frame.trigger( 'edit:attachment', model );
} );
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({
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 ) {
event.preventDefault();
if ( event ) {
event.preventDefault();
}
this.controller.content.mode( 'edit-image' );
},
@ -401,13 +428,19 @@ EditAttachments = MediaFrame.extend({
// 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( 'refresh', this.rerender, this );
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() {
@ -424,7 +457,6 @@ EditAttachments = MediaFrame.extend({
// 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();
@ -442,7 +474,10 @@ EditAttachments = MediaFrame.extend({
*/
createStates: function() {
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
}) );
// Update browser url when navigating media details
if ( this.model ) {
// Update browser url when navigating media details, except on load.
if ( this.model && ! this.model.get( 'skipHistory' ) ) {
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
}
},
@ -494,6 +529,9 @@ EditAttachments = MediaFrame.extend({
frame: this,
controller: editImageController
} );
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
},
editImageModeRender: function( view ) {
@ -508,7 +546,13 @@ EditAttachments = MediaFrame.extend({
/**
* Rerender the view.
*/
rerender: function() {
rerender: function( model ) {
this.stopListening( this.model );
this.model = model;
this.bindModelHandlers();
// Only rerender the `content` region.
if ( this.content.mode() !== 'edit-metadata' ) {
this.content.mode( 'edit-metadata' );
@ -527,8 +571,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.left' ).blur();
return;
}
this.model = this.library.at( this.getCurrentIndex() - 1 );
this.rerender();
this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
this.$( '.left' ).focus();
},
@ -540,8 +583,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.right' ).blur();
return;
}
this.model = this.library.at( this.getCurrentIndex() + 1 );
this.rerender();
this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
this.$( '.right' ).focus();
},
@ -576,7 +618,9 @@ EditAttachments = MediaFrame.extend({
},
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.render();
this.bindSearchHandler();
wp.media.frames.browse = this;
},
bindSearchHandler: function() {
var search = this.$( '#media-search-input' ),
currentSearch = this.options.container.data( 'search' ),
searchView = this.browserView.toolbar.get( 'search' ).$el,
listMode = this.$( '.view-list' ),
input = _.debounce( function (e) {
input = _.throttle( function (e) {
var val = $( e.currentTarget ).val(),
url = '';
if ( val ) {
url += '?search=' + val;
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
}
this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
}, 1000 );
// Update the URL when entering search string (at most once per second)
search.on( 'input', _.bind( input, this ) );
searchView.val( currentSearch ).trigger( 'input' );
this.gridRouter.on( 'route:search', function () {
var href = window.location.href;
if ( href.indexOf( 'mode=' ) > -1 ) {
href = href.replace( /mode=[^&]+/g, 'mode=list' );
} else {
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
}
href = href.replace( 'search=', 's=' );
listMode.prop( 'href', href );
} );
this.gridRouter
.on( 'route:search', function () {
var href = window.location.href;
if ( href.indexOf( 'mode=' ) > -1 ) {
href = href.replace( /mode=[^&]+/g, 'mode=list' );
} else {
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
}
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 ) {
// Create a new EditAttachment frame, passing along the library and the attachment model.
wp.media( {
frame: 'edit-attachments',
controller: this,
library: this.state().get('library'),
model: model
} );
if ( wp.media.frames.edit ) {
wp.media.frames.edit.open().trigger( 'refresh', model );
} else {
wp.media.frames.edit = wp.media( {
frame: 'edit-attachments',
controller: this,
library: this.state().get('library'),
model: model
} );
}
},
/**

View File

@ -8,8 +8,10 @@
*/
var Router = Backbone.Router.extend({
routes: {
'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search'
'upload.php?item=:slug&mode=edit': 'editItem',
'upload.php?item=:slug': 'showItem',
'upload.php?search=:query': 'search',
'upload.php': 'reset'
},
// Map routes against the page URL
@ -17,6 +19,14 @@ var Router = Backbone.Router.extend({
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
search: function( query ) {
jQuery( '#media-search-input' ).val( query ).trigger( 'input' );
@ -25,21 +35,30 @@ var Router = Backbone.Router.extend({
// Show the modal with a specific item
showItem: function( query ) {
var media = wp.media,
library = media.frame.state().get('library'),
frame = media.frames.browse,
library = frame.state().get('library'),
item;
// Trigger the media frame to open the correct item
item = library.findWhere( { id: parseInt( query, 10 ) } );
item.set( 'skipHistory', true );
if ( item ) {
media.frame.trigger( 'edit:attachment', item );
frame.trigger( 'edit:attachment', item );
} else {
item = media.attachment( query );
media.frame.listenTo( item, 'change', function( model ) {
media.frame.stopListening( item );
media.frame.trigger( 'edit:attachment', model );
frame.listenTo( item, 'change', function( model ) {
frame.stopListening( item );
frame.trigger( 'edit:attachment', model );
} );
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({
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 ) {
event.preventDefault();
if ( event ) {
event.preventDefault();
}
this.controller.content.mode( 'edit-image' );
},

View File

@ -59,13 +59,19 @@ EditAttachments = MediaFrame.extend({
// 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( 'refresh', this.rerender, this );
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() {
@ -82,7 +88,6 @@ EditAttachments = MediaFrame.extend({
// 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();
@ -100,7 +105,10 @@ EditAttachments = MediaFrame.extend({
*/
createStates: function() {
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
}) );
// Update browser url when navigating media details
if ( this.model ) {
// Update browser url when navigating media details, except on load.
if ( this.model && ! this.model.get( 'skipHistory' ) ) {
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
}
},
@ -152,6 +160,9 @@ EditAttachments = MediaFrame.extend({
frame: this,
controller: editImageController
} );
this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
},
editImageModeRender: function( view ) {
@ -166,7 +177,13 @@ EditAttachments = MediaFrame.extend({
/**
* Rerender the view.
*/
rerender: function() {
rerender: function( model ) {
this.stopListening( this.model );
this.model = model;
this.bindModelHandlers();
// Only rerender the `content` region.
if ( this.content.mode() !== 'edit-metadata' ) {
this.content.mode( 'edit-metadata' );
@ -185,8 +202,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.left' ).blur();
return;
}
this.model = this.library.at( this.getCurrentIndex() - 1 );
this.rerender();
this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
this.$( '.left' ).focus();
},
@ -198,8 +214,7 @@ EditAttachments = MediaFrame.extend({
this.$( '.right' ).blur();
return;
}
this.model = this.library.at( this.getCurrentIndex() + 1 );
this.rerender();
this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
this.$( '.right' ).focus();
},
@ -234,7 +249,9 @@ EditAttachments = MediaFrame.extend({
},
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.render();
this.bindSearchHandler();
wp.media.frames.browse = this;
},
bindSearchHandler: function() {
var search = this.$( '#media-search-input' ),
currentSearch = this.options.container.data( 'search' ),
searchView = this.browserView.toolbar.get( 'search' ).$el,
listMode = this.$( '.view-list' ),
input = _.debounce( function (e) {
input = _.throttle( function (e) {
var val = $( e.currentTarget ).val(),
url = '';
if ( val ) {
url += '?search=' + val;
this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
}
this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
}, 1000 );
// Update the URL when entering search string (at most once per second)
search.on( 'input', _.bind( input, this ) );
searchView.val( currentSearch ).trigger( 'input' );
this.gridRouter.on( 'route:search', function () {
var href = window.location.href;
if ( href.indexOf( 'mode=' ) > -1 ) {
href = href.replace( /mode=[^&]+/g, 'mode=list' );
} else {
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
}
href = href.replace( 'search=', 's=' );
listMode.prop( 'href', href );
} );
this.gridRouter
.on( 'route:search', function () {
var href = window.location.href;
if ( href.indexOf( 'mode=' ) > -1 ) {
href = href.replace( /mode=[^&]+/g, 'mode=list' );
} else {
href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
}
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 ) {
// Create a new EditAttachment frame, passing along the library and the attachment model.
wp.media( {
frame: 'edit-attachments',
controller: this,
library: this.state().get('library'),
model: model
} );
if ( wp.media.frames.edit ) {
wp.media.frames.edit.open().trigger( 'refresh', model );
} else {
wp.media.frames.edit = wp.media( {
frame: 'edit-attachments',
controller: this,
library: this.state().get('library'),
model: model
} );
}
},
/**