WordPress.org

Make WordPress Core

Changeset 41021


Ignore:
Timestamp:
07/10/2017 07:07:27 PM (12 months ago)
Author:
adamsilverstein
Message:

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.

Location:
trunk/src/wp-includes/js
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/js/media-grid.js

    r40359 r41021  
    5151var Router = Backbone.Router.extend({
    5252    routes: {
    53         'upload.php?item=:slug':    'showItem',
    54         'upload.php?search=:query': 'search'
     53        'upload.php?item=:slug&mode=edit': 'editItem',
     54        'upload.php?item=:slug':           'showItem',
     55        'upload.php?search=:query':        'search',
     56        'upload.php':                      'reset'
    5557    },
    5658
     
    6062    },
    6163
     64    reset: function() {
     65        var frame = wp.media.frames.edit;
     66
     67        if ( frame ) {
     68            frame.close();
     69        }
     70    },
     71
    6272    // Respond to the search route by filling the search field and trigggering the input event
    6373    search: function( query ) {
     
    6878    showItem: function( query ) {
    6979        var media = wp.media,
    70             library = media.frame.state().get('library'),
     80            frame = media.frames.browse,
     81            library = frame.state().get('library'),
    7182            item;
    7283
    7384        // Trigger the media frame to open the correct item
    7485        item = library.findWhere( { id: parseInt( query, 10 ) } );
     86        item.set( 'skipHistory', true );
     87
    7588        if ( item ) {
    76             media.frame.trigger( 'edit:attachment', item );
     89            frame.trigger( 'edit:attachment', item );
    7790        } else {
    7891            item = media.attachment( query );
    79             media.frame.listenTo( item, 'change', function( model ) {
    80                 media.frame.stopListening( item );
    81                 media.frame.trigger( 'edit:attachment', model );
     92            frame.listenTo( item, 'change', function( model ) {
     93                frame.stopListening( item );
     94                frame.trigger( 'edit:attachment', model );
    8295            } );
    8396            item.fetch();
    8497        }
     98    },
     99
     100    // Show the modal in edit mode with a specific item.
     101    editItem: function( query ) {
     102        this.showItem( query );
     103        wp.media.frames.edit.content.mode( 'edit-details' );
    85104    }
    86105});
     
    108127    template: wp.template( 'attachment-details-two-column' ),
    109128
     129    initialize: function() {
     130        this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
     131
     132        Details.prototype.initialize.apply( this, arguments );
     133    },
     134
    110135    editAttachment: function( event ) {
    111         event.preventDefault();
     136        if ( event ) {
     137            event.preventDefault();
     138        }
    112139        this.controller.content.mode( 'edit-image' );
    113140    },
     
    402429        this.on( 'title:create:default', this.createTitle, this );
    403430
    404         // Close the modal if the attachment is deleted.
    405         this.listenTo( this.model, 'change:status destroy', this.close, this );
    406 
    407431        this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
    408432        this.on( 'content:create:edit-image', this.editImageMode, this );
    409433        this.on( 'content:render:edit-image', this.editImageModeRender, this );
     434        this.on( 'refresh', this.rerender, this );
    410435        this.on( 'close', this.detach );
     436
     437        this.bindModelHandlers();
     438        this.listenTo( this.gridRouter, 'route:search', this.close, this );
     439    },
     440
     441    bindModelHandlers: function() {
     442        // Close the modal if the attachment is deleted.
     443        this.listenTo( this.model, 'change:status destroy', this.close, this );
    411444    },
    412445
     
    425458            // Completely destroy the modal DOM element when closing it.
    426459            this.modal.on( 'close', _.bind( function() {
    427                 this.modal.remove();
    428460                $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
    429461                // Restore the original focus item if possible
     
    443475    createStates: function() {
    444476        this.states.add([
    445             new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
     477            new wp.media.controller.EditAttachmentMetadata({
     478                model:   this.model,
     479                library: this.library
     480            })
    446481        ]);
    447482    },
     
    468503        }) );
    469504
    470         // Update browser url when navigating media details
    471         if ( this.model ) {
     505        // Update browser url when navigating media details, except on load.
     506        if ( this.model && ! this.model.get( 'skipHistory' ) ) {
    472507            this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    473508        }
     
    495530            controller: editImageController
    496531        } );
     532
     533        this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
     534
    497535    },
    498536
     
    509547     * Rerender the view.
    510548     */
    511     rerender: function() {
     549    rerender: function( model ) {
     550        this.stopListening( this.model );
     551
     552        this.model = model;
     553
     554        this.bindModelHandlers();
     555
    512556        // Only rerender the `content` region.
    513557        if ( this.content.mode() !== 'edit-metadata' ) {
     
    528572            return;
    529573        }
    530         this.model = this.library.at( this.getCurrentIndex() - 1 );
    531         this.rerender();
     574        this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
    532575        this.$( '.left' ).focus();
    533576    },
     
    541584            return;
    542585        }
    543         this.model = this.library.at( this.getCurrentIndex() + 1 );
    544         this.rerender();
     586        this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
    545587        this.$( '.right' ).focus();
    546588    },
     
    577619
    578620    resetRoute: function() {
    579         this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
     621        var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(),
     622            url = '' !== searchTerm ? '?search=' + searchTerm : '';
     623        this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
    580624    }
    581625});
     
    667711        this.render();
    668712        this.bindSearchHandler();
     713
     714        wp.media.frames.browse = this;
    669715    },
    670716
    671717    bindSearchHandler: function() {
    672718        var search = this.$( '#media-search-input' ),
    673             currentSearch = this.options.container.data( 'search' ),
    674719            searchView = this.browserView.toolbar.get( 'search' ).$el,
    675720            listMode = this.$( '.view-list' ),
    676721
    677             input  = _.debounce( function (e) {
     722            input  = _.throttle( function (e) {
    678723                var val = $( e.currentTarget ).val(),
    679724                    url = '';
     
    681726                if ( val ) {
    682727                    url += '?search=' + val;
     728                    this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
    683729                }
    684                 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
    685730            }, 1000 );
    686731
    687732        // Update the URL when entering search string (at most once per second)
    688733        search.on( 'input', _.bind( input, this ) );
    689         searchView.val( currentSearch ).trigger( 'input' );
    690 
    691         this.gridRouter.on( 'route:search', function () {
    692             var href = window.location.href;
    693             if ( href.indexOf( 'mode=' ) > -1 ) {
    694                 href = href.replace( /mode=[^&]+/g, 'mode=list' );
    695             } else {
    696                 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    697             }
    698             href = href.replace( 'search=', 's=' );
    699             listMode.prop( 'href', href );
    700         } );
     734
     735        this.gridRouter
     736            .on( 'route:search', function () {
     737                var href = window.location.href;
     738                if ( href.indexOf( 'mode=' ) > -1 ) {
     739                    href = href.replace( /mode=[^&]+/g, 'mode=list' );
     740                } else {
     741                    href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     742                }
     743                href = href.replace( 'search=', 's=' );
     744                listMode.prop( 'href', href );
     745            })
     746            .on( 'route:reset', function() {
     747                searchView.val( '' ).trigger( 'input' );
     748            });
    701749    },
    702750
     
    790838    openEditAttachmentModal: function( model ) {
    791839        // Create a new EditAttachment frame, passing along the library and the attachment model.
    792         wp.media( {
    793             frame:       'edit-attachments',
    794             controller:  this,
    795             library:     this.state().get('library'),
    796             model:       model
    797         } );
     840        if ( wp.media.frames.edit ) {
     841            wp.media.frames.edit.open().trigger( 'refresh', model );
     842        } else {
     843            wp.media.frames.edit = wp.media( {
     844                frame:       'edit-attachments',
     845                controller:  this,
     846                library:     this.state().get('library'),
     847                model:       model
     848            } );
     849        }
    798850    },
    799851
  • trunk/src/wp-includes/js/media/routers/manage.js

    r33342 r41021  
    99var Router = Backbone.Router.extend({
    1010    routes: {
    11         'upload.php?item=:slug':    'showItem',
    12         'upload.php?search=:query': 'search'
     11        'upload.php?item=:slug&mode=edit': 'editItem',
     12        'upload.php?item=:slug':           'showItem',
     13        'upload.php?search=:query':        'search',
     14        'upload.php':                      'reset'
    1315    },
    1416
     
    1618    baseUrl: function( url ) {
    1719        return 'upload.php' + url;
     20    },
     21
     22    reset: function() {
     23        var frame = wp.media.frames.edit;
     24
     25        if ( frame ) {
     26            frame.close();
     27        }
    1828    },
    1929
     
    2636    showItem: function( query ) {
    2737        var media = wp.media,
    28             library = media.frame.state().get('library'),
     38            frame = media.frames.browse,
     39            library = frame.state().get('library'),
    2940            item;
    3041
    3142        // Trigger the media frame to open the correct item
    3243        item = library.findWhere( { id: parseInt( query, 10 ) } );
     44        item.set( 'skipHistory', true );
     45
    3346        if ( item ) {
    34             media.frame.trigger( 'edit:attachment', item );
     47            frame.trigger( 'edit:attachment', item );
    3548        } else {
    3649            item = media.attachment( query );
    37             media.frame.listenTo( item, 'change', function( model ) {
    38                 media.frame.stopListening( item );
    39                 media.frame.trigger( 'edit:attachment', model );
     50            frame.listenTo( item, 'change', function( model ) {
     51                frame.stopListening( item );
     52                frame.trigger( 'edit:attachment', model );
    4053            } );
    4154            item.fetch();
    4255        }
     56    },
     57
     58    // Show the modal in edit mode with a specific item.
     59    editItem: function( query ) {
     60        this.showItem( query );
     61        wp.media.frames.edit.content.mode( 'edit-details' );
    4362    }
    4463});
  • trunk/src/wp-includes/js/media/views/attachment/details-two-column.js

    r33337 r41021  
    1818    template: wp.template( 'attachment-details-two-column' ),
    1919
     20    initialize: function() {
     21        this.controller.on( 'content:activate:edit-details', _.bind( this.editAttachment, this ) );
     22
     23        Details.prototype.initialize.apply( this, arguments );
     24    },
     25
    2026    editAttachment: function( event ) {
    21         event.preventDefault();
     27        if ( event ) {
     28            event.preventDefault();
     29        }
    2230        this.controller.content.mode( 'edit-image' );
    2331    },
  • trunk/src/wp-includes/js/media/views/frame/edit-attachments.js

    r33337 r41021  
    6060        this.on( 'title:create:default', this.createTitle, this );
    6161
    62         // Close the modal if the attachment is deleted.
    63         this.listenTo( this.model, 'change:status destroy', this.close, this );
    64 
    6562        this.on( 'content:create:edit-metadata', this.editMetadataMode, this );
    6663        this.on( 'content:create:edit-image', this.editImageMode, this );
    6764        this.on( 'content:render:edit-image', this.editImageModeRender, this );
     65        this.on( 'refresh', this.rerender, this );
    6866        this.on( 'close', this.detach );
     67
     68        this.bindModelHandlers();
     69        this.listenTo( this.gridRouter, 'route:search', this.close, this );
     70    },
     71
     72    bindModelHandlers: function() {
     73        // Close the modal if the attachment is deleted.
     74        this.listenTo( this.model, 'change:status destroy', this.close, this );
    6975    },
    7076
     
    8389            // Completely destroy the modal DOM element when closing it.
    8490            this.modal.on( 'close', _.bind( function() {
    85                 this.modal.remove();
    8691                $( 'body' ).off( 'keydown.media-modal' ); /* remove the keydown event */
    8792                // Restore the original focus item if possible
     
    101106    createStates: function() {
    102107        this.states.add([
    103             new wp.media.controller.EditAttachmentMetadata( { model: this.model } )
     108            new wp.media.controller.EditAttachmentMetadata({
     109                model:   this.model,
     110                library: this.library
     111            })
    104112        ]);
    105113    },
     
    126134        }) );
    127135
    128         // Update browser url when navigating media details
    129         if ( this.model ) {
     136        // Update browser url when navigating media details, except on load.
     137        if ( this.model && ! this.model.get( 'skipHistory' ) ) {
    130138            this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id ) );
    131139        }
     
    153161            controller: editImageController
    154162        } );
     163
     164        this.gridRouter.navigate( this.gridRouter.baseUrl( '?item=' + this.model.id + '&mode=edit' ) );
     165
    155166    },
    156167
     
    167178     * Rerender the view.
    168179     */
    169     rerender: function() {
     180    rerender: function( model ) {
     181        this.stopListening( this.model );
     182
     183        this.model = model;
     184
     185        this.bindModelHandlers();
     186
    170187        // Only rerender the `content` region.
    171188        if ( this.content.mode() !== 'edit-metadata' ) {
     
    186203            return;
    187204        }
    188         this.model = this.library.at( this.getCurrentIndex() - 1 );
    189         this.rerender();
     205        this.trigger( 'refresh', this.library.at( this.getCurrentIndex() - 1 ) );
    190206        this.$( '.left' ).focus();
    191207    },
     
    199215            return;
    200216        }
    201         this.model = this.library.at( this.getCurrentIndex() + 1 );
    202         this.rerender();
     217        this.trigger( 'refresh', this.library.at( this.getCurrentIndex() + 1 ) );
    203218        this.$( '.right' ).focus();
    204219    },
     
    235250
    236251    resetRoute: function() {
    237         this.gridRouter.navigate( this.gridRouter.baseUrl( '' ) );
     252        var searchTerm = this.controller.browserView.toolbar.get( 'search' ).$el.val(),
     253            url = '' !== searchTerm ? '?search=' + searchTerm : '';
     254        this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
    238255    }
    239256});
  • trunk/src/wp-includes/js/media/views/frame/manage.js

    r40359 r41021  
    8282        this.render();
    8383        this.bindSearchHandler();
     84
     85        wp.media.frames.browse = this;
    8486    },
    8587
    8688    bindSearchHandler: function() {
    8789        var search = this.$( '#media-search-input' ),
    88             currentSearch = this.options.container.data( 'search' ),
    8990            searchView = this.browserView.toolbar.get( 'search' ).$el,
    9091            listMode = this.$( '.view-list' ),
    9192
    92             input  = _.debounce( function (e) {
     93            input  = _.throttle( function (e) {
    9394                var val = $( e.currentTarget ).val(),
    9495                    url = '';
     
    9697                if ( val ) {
    9798                    url += '?search=' + val;
     99                    this.gridRouter.navigate( this.gridRouter.baseUrl( url ), { replace: true } );
    98100                }
    99                 this.gridRouter.navigate( this.gridRouter.baseUrl( url ) );
    100101            }, 1000 );
    101102
    102103        // Update the URL when entering search string (at most once per second)
    103104        search.on( 'input', _.bind( input, this ) );
    104         searchView.val( currentSearch ).trigger( 'input' );
    105 
    106         this.gridRouter.on( 'route:search', function () {
    107             var href = window.location.href;
    108             if ( href.indexOf( 'mode=' ) > -1 ) {
    109                 href = href.replace( /mode=[^&]+/g, 'mode=list' );
    110             } else {
    111                 href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
    112             }
    113             href = href.replace( 'search=', 's=' );
    114             listMode.prop( 'href', href );
    115         } );
     105
     106        this.gridRouter
     107            .on( 'route:search', function () {
     108                var href = window.location.href;
     109                if ( href.indexOf( 'mode=' ) > -1 ) {
     110                    href = href.replace( /mode=[^&]+/g, 'mode=list' );
     111                } else {
     112                    href += href.indexOf( '?' ) > -1 ? '&mode=list' : '?mode=list';
     113                }
     114                href = href.replace( 'search=', 's=' );
     115                listMode.prop( 'href', href );
     116            })
     117            .on( 'route:reset', function() {
     118                searchView.val( '' ).trigger( 'input' );
     119            });
    116120    },
    117121
     
    205209    openEditAttachmentModal: function( model ) {
    206210        // Create a new EditAttachment frame, passing along the library and the attachment model.
    207         wp.media( {
    208             frame:       'edit-attachments',
    209             controller:  this,
    210             library:     this.state().get('library'),
    211             model:       model
    212         } );
     211        if ( wp.media.frames.edit ) {
     212            wp.media.frames.edit.open().trigger( 'refresh', model );
     213        } else {
     214            wp.media.frames.edit = wp.media( {
     215                frame:       'edit-attachments',
     216                controller:  this,
     217                library:     this.state().get('library'),
     218                model:       model
     219            } );
     220        }
    213221    },
    214222
Note: See TracChangeset for help on using the changeset viewer.