WordPress.org

Make WordPress Core

Changeset 41021


Ignore:
Timestamp:
07/10/17 19:07:27 (5 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.