Skip to content

Commit 466659c

Browse files
committed
Media grid attachment modal:
* Keyboard navigation. * History and routes for single items and search results. props adamsilverstein. see #24716. git-svn-id: https://develop.svn.wordpress.org/trunk@29057 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 2e0c881 commit 466659c

2 files changed

Lines changed: 135 additions & 5 deletions

File tree

src/wp-admin/upload.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
wp_enqueue_media();
2525
wp_enqueue_script( 'media-grid' );
2626
wp_enqueue_script( 'media' );
27+
wp_localize_script( 'media-grid', 'mediaGridSettings', array( 'adminUrl' => parse_url( self_admin_url(), PHP_URL_PATH ) ) );
2728

2829
require_once( ABSPATH . 'wp-admin/admin-header.php' );
2930
include( ABSPATH . 'wp-admin/admin-footer.php' );

src/wp-includes/js/media-grid.js

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer */
1+
/* global _wpMediaViewsL10n, setUserSetting, deleteUserSetting, MediaElementPlayer, mediaGridSettings*/
22
(function($, _, Backbone, wp) {
33
var media = wp.media, l10n;
44

@@ -91,6 +91,7 @@
9191
* @global wp.Uploader
9292
*/
9393
initialize: function() {
94+
var self = this;
9495
_.defaults( this.options, {
9596
title: l10n.mediaLibraryTitle,
9697
modal: false,
@@ -140,6 +141,24 @@
140141
this.createStates();
141142
this.bindHandlers();
142143
this.render();
144+
145+
// Set up the Backbone router after a brief delay
146+
_.delay( function(){
147+
wp.media.mediarouter = new media.view.Frame.Router( self );
148+
// Verify pushState support and activate
149+
if ( window.history && window.history.pushState ) {
150+
Backbone.history.start({
151+
root: mediaGridSettings.adminUrl,
152+
pushState: true
153+
});
154+
}
155+
}, 250);
156+
157+
// Update the URL when entering search string (at most once per second)
158+
$( '#media-search-input' ).on( 'input', _.debounce( function() {
159+
var $val = $( this ).val();
160+
wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( ( '' === $val ) ? '' : ( '?search=' + $val ) ) );
161+
}, 1000 ) );
143162
},
144163

145164
createSelection: function() {
@@ -218,17 +237,22 @@
218237
* Open the Edit Attachment modal.
219238
*/
220239
editAttachment: function( model ) {
221-
var library = this.state().get('library');
240+
var self = this,
241+
library = this.state().get('library');
222242

223243
// Create a new EditAttachment frame, passing along the library and the attachment model.
224-
this.editAttachmentFrame = new media.view.Frame.EditAttachment({
244+
this.editAttachmentFrame = new media.view.Frame.EditAttachments({
225245
library: library,
226246
model: model
227247
});
228248

229249
// Listen to events on the edit attachment frame for triggering pagination callback handlers.
230250
this.listenTo( this.editAttachmentFrame, 'edit:attachment:next', this.editNextAttachment );
231251
this.listenTo( this.editAttachmentFrame, 'edit:attachment:previous', this.editPreviousAttachment );
252+
// Listen to keyboard events on the modal
253+
$( 'body' ).on( 'keydown.media-modal', function( e ) {
254+
self.editAttachmentFrame.keyEvent( e );
255+
} );
232256
},
233257

234258
/**
@@ -299,14 +323,67 @@
299323
}
300324
});
301325

326+
/**
327+
* A router for handling the browser history and application state
328+
*/
329+
media.view.Frame.Router = Backbone.Router.extend({
330+
331+
mediaFrame: '',
332+
333+
initialize: function( mediaFrame ){
334+
this.mediaFrame = mediaFrame;
335+
},
336+
337+
routes: {
338+
'upload.php?item=:slug': 'showitem',
339+
'upload.php?search=:query': 'search',
340+
':default': 'defaultRoute'
341+
},
342+
343+
// Map routes against the page URL
344+
baseUrl: function( url ) {
345+
return 'upload.php' + url;
346+
},
347+
348+
// Respond to the search route by filling the search field and trigggering the input event
349+
search: function( query ) {
350+
// Ensure modal closed, see back button
351+
this.closeModal();
352+
$( '#media-search-input' ).val( query ).trigger( 'input' );
353+
},
354+
355+
// Show the modal with a specific item
356+
showitem: function( query ) {
357+
var library = this.mediaFrame.state().get('library');
358+
359+
// Remove existing modal if present
360+
this.closeModal();
361+
// Trigger the media frame to open the correct item
362+
this.mediaFrame.trigger( 'edit:attachment', library.findWhere( { id: parseInt( query, 10 ) } ) );
363+
},
364+
365+
// Close the modal if set up
366+
closeModal: function() {
367+
if ( 'undefined' !== typeof this.mediaFrame.editAttachmentFrame ) {
368+
this.mediaFrame.editAttachmentFrame.modal.close();
369+
}
370+
},
371+
372+
// Default route: make sure the modal and search are reset
373+
defaultRoute: function() {
374+
this.closeModal();
375+
$( '#media-search-input' ).val( '' ).trigger( 'input' );
376+
}
377+
});
378+
302379
/**
303380
* A frame for editing the details of a specific media item.
304381
*
305382
* Opens in a modal by default.
306383
*
307384
* Requires an attachment model to be passed in the options hash under `model`.
308385
*/
309-
media.view.Frame.EditAttachment = media.view.Frame.extend({
386+
media.view.Frame.EditAttachments = media.view.Frame.extend({
310387

311388
className: 'edit-attachment-frame',
312389
template: media.template( 'edit-attachment-frame' ),
@@ -328,13 +405,20 @@
328405
state: 'edit-attachment'
329406
});
330407

408+
this.library = this.options.library;
409+
if ( this.options.model ) {
410+
this.model = this.options.model;
411+
} else {
412+
this.model = this.library.at( 0 );
413+
}
414+
331415
this.createStates();
332416

333417
this.on( 'content:render:edit-metadata', this.editMetadataContent, this );
334418
this.on( 'content:render:edit-image', this.editImageContentUgh, this );
335419

336420
// Only need a tab to Edit Image for images.
337-
if ( this.model.get( 'type' ) === 'image' ) {
421+
if ( 'undefined' !== typeof this.model && this.model.get( 'type' ) === 'image' ) {
338422
this.on( 'router:create', this.createRouter, this );
339423
this.on( 'router:render', this.browseRouter, this );
340424
}
@@ -352,6 +436,7 @@
352436
// Completely destroy the modal DOM element when closing it.
353437
this.modal.close = function() {
354438
self.modal.remove();
439+
$( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
355440
};
356441

357442
this.modal.content( this );
@@ -391,6 +476,8 @@
391476
model: this.model
392477
});
393478
this.content.set( view );
479+
// Update browser url when navigating media details
480+
wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '?item=' + this.model.id ) );
394481
},
395482

396483
/**
@@ -461,6 +548,48 @@
461548
return;
462549
this.modal.close();
463550
this.trigger( 'edit:attachment:next', this.model );
551+
},
552+
553+
getCurrentIndex: function() {
554+
return this.library.indexOf( this.model );
555+
},
556+
557+
hasNext: function() {
558+
return ( this.getCurrentIndex() + 1 ) < this.library.length;
559+
},
560+
561+
hasPrevious: function() {
562+
return ( this.getCurrentIndex() - 1 ) > -1;
563+
},
564+
/**
565+
* Respond to the keyboard events: right arrow, left arrow, escape.
566+
*/
567+
keyEvent: function( event ) {
568+
var $target = $( event.target );
569+
// Pressing the escape key routes back to main url
570+
if ( event.keyCode === 27 ) {
571+
this.resetRoute();
572+
return event;
573+
}
574+
//Don't go left/right if we are in a textarea or input field
575+
if ( $target.is( 'input' ) || $target.is( 'textarea' ) ) {
576+
return event;
577+
}
578+
// The right arrow key
579+
if ( event.keyCode === 39 ) {
580+
if ( ! this.hasNext ) { return; }
581+
_.debounce( this.nextMediaItem(), 250 );
582+
}
583+
// The left arrow key
584+
if ( event.keyCode === 37 ) {
585+
if ( ! this.hasPrevious ) { return; }
586+
_.debounce( this.previousMediaItem(), 250 );
587+
}
588+
},
589+
590+
resetRoute: function() {
591+
wp.media.mediarouter.navigate( wp.media.mediarouter.baseUrl( '' ) );
592+
return;
464593
}
465594
});
466595

0 commit comments

Comments
 (0)