WordPress.org

Make WordPress Core

Changeset 41051


Ignore:
Timestamp:
07/14/17 17:16:43 (9 days ago)
Author:
westonruter
Message:

Widgets: Add accessibility mode support for TinyMCE-enhanced Text and Media widgets (Video, Audio, Images).

Merges [40941] to 4.8 branch.
Amends [40640], [40631].
Props westonruter, afercia.
See #35243, #32417.
Fixes #40986 for 4.8.1.

Location:
branches/4.8
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/4.8

  • branches/4.8/src/wp-admin/js/widgets/media-widgets.js

    r40836 r41051  
    430430         * @param {Object}         options - Options. 
    431431         * @param {Backbone.Model} options.model - Model. 
    432          * @param {jQuery}         options.el - Control container element. 
     432         * @param {jQuery}         options.el - Control field container element. 
     433         * @param {jQuery}         options.syncContainer - Container element where fields are synced for the server. 
    433434         * @returns {void} 
    434435         */ 
     
    438439            Backbone.View.prototype.initialize.call( control, options ); 
    439440 
    440             if ( ! control.el ) { 
    441                 throw new Error( 'Missing options.el' ); 
    442             } 
    443441            if ( ! ( control.model instanceof component.MediaWidgetModel ) ) { 
    444442                throw new Error( 'Missing options.model' ); 
    445443            } 
     444            if ( ! options.el ) { 
     445                throw new Error( 'Missing options.el' ); 
     446            } 
     447            if ( ! options.syncContainer ) { 
     448                throw new Error( 'Missing options.syncContainer' ); 
     449            } 
     450 
     451            control.syncContainer = options.syncContainer; 
     452 
     453            control.$el.addClass( 'media-widget-control' ); 
    446454 
    447455            // Allow methods to be passed in with control context preserved. 
     
    548556        syncModelToInputs: function syncModelToInputs() { 
    549557            var control = this; 
    550             control.$el.next( '.widget-content' ).find( '.media-widget-instance-property' ).each( function() { 
     558            control.syncContainer.find( '.media-widget-instance-property' ).each( function() { 
    551559                var input = $( this ), value; 
    552560                value = control.model.get( input.data( 'property' ) ); 
     
    10041012     */ 
    10051013    component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { 
    1006         var widgetContent, controlContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone; 
     1014        var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone; 
    10071015        widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. 
    1008         widgetContent = widgetForm.find( '> .widget-content' ); 
    10091016        idBase = widgetForm.find( '> .id_base' ).val(); 
    10101017        widgetId = widgetForm.find( '> .widget-id' ).val(); 
     
    10331040         * container. 
    10341041         */ 
    1035         controlContainer = $( '<div class="media-widget-control"></div>' ); 
    1036         widgetContent.before( controlContainer ); 
     1042        fieldContainer = $( '<div></div>' ); 
     1043        syncContainer = widgetContainer.find( '.widget-content:first' ); 
     1044        syncContainer.before( fieldContainer ); 
    10371045 
    10381046        /* 
     
    10421050         */ 
    10431051        modelAttributes = {}; 
    1044         widgetContent.find( '.media-widget-instance-property' ).each( function() { 
     1052        syncContainer.find( '.media-widget-instance-property' ).each( function() { 
    10451053            var input = $( this ); 
    10461054            modelAttributes[ input.data( 'property' ) ] = input.val(); 
     
    10511059 
    10521060        widgetControl = new ControlConstructor({ 
    1053             el: controlContainer, 
     1061            el: fieldContainer, 
     1062            syncContainer: syncContainer, 
    10541063            model: widgetModel 
    10551064        }); 
     
    10771086        component.modelCollection.add( [ widgetModel ] ); 
    10781087        component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl; 
     1088    }; 
     1089 
     1090    /** 
     1091     * Setup widget in accessibility mode. 
     1092     * 
     1093     * @returns {void} 
     1094     */ 
     1095    component.setupAccessibleMode = function setupAccessibleMode() { 
     1096        var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer; 
     1097        widgetForm = $( '.editwidget > form' ); 
     1098        if ( 0 === widgetForm.length ) { 
     1099            return; 
     1100        } 
     1101 
     1102        idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); 
     1103 
     1104        ControlConstructor = component.controlConstructors[ idBase ]; 
     1105        if ( ! ControlConstructor ) { 
     1106            return; 
     1107        } 
     1108 
     1109        widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val(); 
     1110 
     1111        ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; 
     1112        fieldContainer = $( '<div></div>' ); 
     1113        syncContainer = widgetForm.find( '> .widget-inside' ); 
     1114        syncContainer.before( fieldContainer ); 
     1115 
     1116        modelAttributes = {}; 
     1117        syncContainer.find( '.media-widget-instance-property' ).each( function() { 
     1118            var input = $( this ); 
     1119            modelAttributes[ input.data( 'property' ) ] = input.val(); 
     1120        }); 
     1121        modelAttributes.widget_id = widgetId; 
     1122 
     1123        widgetControl = new ControlConstructor({ 
     1124            el: fieldContainer, 
     1125            syncContainer: syncContainer, 
     1126            model: new ModelConstructor( modelAttributes ) 
     1127        }); 
     1128 
     1129        component.modelCollection.add( [ widgetControl.model ] ); 
     1130        component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl; 
     1131 
     1132        widgetControl.render(); 
    10791133    }; 
    10801134 
     
    11471201                component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); 
    11481202            }); 
     1203 
     1204            // Accessibility mode. 
     1205            $( window ).on( 'load', function() { 
     1206                component.setupAccessibleMode(); 
     1207            }); 
    11491208        }); 
    11501209    }; 
  • branches/4.8/src/wp-admin/js/widgets/text-widgets.js

    r40821 r41051  
    2525         * Initialize. 
    2626         * 
    27          * @param {Object}         options - Options. 
    28          * @param {Backbone.Model} options.model - Model. 
    29          * @param {jQuery}         options.el - Control container element. 
     27         * @param {Object} options - Options. 
     28         * @param {jQuery} options.el - Control field container element. 
     29         * @param {jQuery} options.syncContainer - Container element where fields are synced for the server. 
    3030         * @returns {void} 
    3131         */ 
     
    3636                throw new Error( 'Missing options.el' ); 
    3737            } 
     38            if ( ! options.syncContainer ) { 
     39                throw new Error( 'Missing options.syncContainer' ); 
     40            } 
    3841 
    3942            Backbone.View.prototype.initialize.call( control, options ); 
    40  
    41             /* 
    42              * Create a container element for the widget control fields. 
    43              * This is inserted into the DOM immediately before the the .widget-content 
    44              * element because the contents of this element are essentially "managed" 
    45              * by PHP, where each widget update cause the entire element to be emptied 
    46              * and replaced with the rendered output of WP_Widget::form() which is 
    47              * sent back in Ajax request made to save/update the widget instance. 
    48              * To prevent a "flash of replaced DOM elements and re-initialized JS 
    49              * components", the JS template is rendered outside of the normal form 
    50              * container. 
    51              */ 
    52             control.fieldContainer = $( '<div class="text-widget-fields"></div>' ); 
    53             control.fieldContainer.html( wp.template( 'widget-text-control-fields' ) ); 
    54             control.widgetContentContainer = control.$el.find( '.widget-content:first' ); 
    55             control.widgetContentContainer.before( control.fieldContainer ); 
     43            control.syncContainer = options.syncContainer; 
     44 
     45            control.$el.addClass( 'text-widget-fields' ); 
     46            control.$el.html( wp.template( 'widget-text-control-fields' ) ); 
    5647 
    5748            control.fields = { 
    58                 title: control.fieldContainer.find( '.title' ), 
    59                 text: control.fieldContainer.find( '.text' ) 
     49                title: control.$el.find( '.title' ), 
     50                text: control.$el.find( '.text' ) 
    6051            }; 
    6152 
     
    6354            _.each( control.fields, function( fieldInput, fieldName ) { 
    6455                fieldInput.on( 'input change', function updateSyncField() { 
    65                     var syncInput = control.widgetContentContainer.find( 'input[type=hidden].' + fieldName ); 
     56                    var syncInput = control.syncContainer.find( 'input[type=hidden].' + fieldName ); 
    6657                    if ( syncInput.val() !== $( this ).val() ) { 
    6758                        syncInput.val( $( this ).val() ); 
     
    7162 
    7263                // Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event. 
    73                 fieldInput.val( control.widgetContentContainer.find( 'input[type=hidden].' + fieldName ).val() ); 
     64                fieldInput.val( control.syncContainer.find( 'input[type=hidden].' + fieldName ).val() ); 
    7465            }); 
    7566        }, 
     
    8879 
    8980            if ( ! control.fields.title.is( document.activeElement ) ) { 
    90                 syncInput = control.widgetContentContainer.find( 'input[type=hidden].title' ); 
     81                syncInput = control.syncContainer.find( 'input[type=hidden].title' ); 
    9182                control.fields.title.val( syncInput.val() ); 
    9283            } 
    9384 
    94             syncInput = control.widgetContentContainer.find( 'input[type=hidden].text' ); 
     85            syncInput = control.syncContainer.find( 'input[type=hidden].text' ); 
    9586            if ( control.fields.text.is( ':visible' ) ) { 
    9687                if ( ! control.fields.text.is( document.activeElement ) ) { 
     
    220211     */ 
    221212    component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { 
    222         var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, widgetInside, renderWhenAnimationDone; 
     213        var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, widgetInside, renderWhenAnimationDone, fieldContainer, syncContainer; 
    223214        widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. 
    224215 
     
    229220 
    230221        // Prevent initializing already-added widgets. 
    231         widgetId = widgetForm.find( '> .widget-id' ).val(); 
     222        widgetId = widgetForm.find( '.widget-id' ).val(); 
    232223        if ( component.widgetControls[ widgetId ] ) { 
    233224            return; 
    234225        } 
    235226 
     227        /* 
     228         * Create a container element for the widget control fields. 
     229         * This is inserted into the DOM immediately before the the .widget-content 
     230         * element because the contents of this element are essentially "managed" 
     231         * by PHP, where each widget update cause the entire element to be emptied 
     232         * and replaced with the rendered output of WP_Widget::form() which is 
     233         * sent back in Ajax request made to save/update the widget instance. 
     234         * To prevent a "flash of replaced DOM elements and re-initialized JS 
     235         * components", the JS template is rendered outside of the normal form 
     236         * container. 
     237         */ 
     238        fieldContainer = $( '<div></div>' ); 
     239        syncContainer = widgetContainer.find( '.widget-content:first' ); 
     240        syncContainer.before( fieldContainer ); 
     241 
    236242        widgetControl = new component.TextWidgetControl({ 
    237             el: widgetContainer 
     243            el: fieldContainer, 
     244            syncContainer: syncContainer 
    238245        }); 
    239246 
     
    255262        }; 
    256263        renderWhenAnimationDone(); 
     264    }; 
     265 
     266    /** 
     267     * Setup widget in accessibility mode. 
     268     * 
     269     * @returns {void} 
     270     */ 
     271    component.setupAccessibleMode = function setupAccessibleMode() { 
     272        var widgetForm, idBase, widgetControl, fieldContainer, syncContainer; 
     273        widgetForm = $( '.editwidget > form' ); 
     274        if ( 0 === widgetForm.length ) { 
     275            return; 
     276        } 
     277 
     278        idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); 
     279        if ( 'text' !== idBase ) { 
     280            return; 
     281        } 
     282 
     283        fieldContainer = $( '<div></div>' ); 
     284        syncContainer = widgetForm.find( '> .widget-inside' ); 
     285        syncContainer.before( fieldContainer ); 
     286 
     287        widgetControl = new component.TextWidgetControl({ 
     288            el: fieldContainer, 
     289            syncContainer: syncContainer 
     290        }); 
     291 
     292        widgetControl.initializeEditor(); 
    257293    }; 
    258294 
     
    320356                component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); 
    321357            }); 
     358 
     359            // Accessibility mode. 
     360            $( window ).on( 'load', function() { 
     361                component.setupAccessibleMode(); 
     362            }); 
    322363        }); 
    323364    }; 
  • branches/4.8/tests/qunit/wp-admin/js/widgets/test-media-image-widget.js

    r40640 r41051  
    1818        imageWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_image(); 
    1919        imageWidgetControlInstance = new ImageWidgetControl({ 
     20            el: jQuery( '<div></div>' ), 
     21            syncContainer: jQuery( '<div></div>' ), 
    2022            model: imageWidgetModelInstance 
    2123        }); 
     
    8587        imageWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_image(); 
    8688        imageWidgetControlInstance = new wp.mediaWidgets.controlConstructors.media_image({ 
     89            el: jQuery( '<div></div>' ), 
     90            syncContainer: jQuery( '<div></div>' ), 
    8791            model: imageWidgetModelInstance 
    8892        }); 
  • branches/4.8/tests/qunit/wp-admin/js/widgets/test-media-video-widget.js

    r40810 r41051  
    1818        videoWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_video(); 
    1919        videoWidgetControlInstance = new VideoWidgetControl({ 
     20            el: jQuery( '<div></div>' ), 
     21            syncContainer: jQuery( '<div></div>' ), 
    2022            model: videoWidgetModelInstance 
    2123        }); 
     
    4749        videoWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_video(); 
    4850        videoWidgetControlInstance = new wp.mediaWidgets.controlConstructors.media_video({ 
     51            el: jQuery( '<div></div>' ), 
     52            syncContainer: jQuery( '<div></div>' ), 
    4953            model: videoWidgetModelInstance 
    5054        }); 
Note: See TracChangeset for help on using the changeset viewer.