WordPress.org

Make WordPress Core

Changeset 40941


Ignore:
Timestamp:
06/25/17 18:47:13 (5 months ago)
Author:
westonruter
Message:

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

Amends [40640], [40631].
Props westonruter, afercia.
See #35243, #32417.
Fixes #40986.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/js/widgets/media-widgets.js

    r40939 r40941  
    436436         * @param {Object}         options - Options. 
    437437         * @param {Backbone.Model} options.model - Model. 
    438          * @param {jQuery}         options.el - Control container element. 
     438         * @param {jQuery}         options.el - Control field container element. 
     439         * @param {jQuery}         options.syncContainer - Container element where fields are synced for the server. 
    439440         * @returns {void} 
    440441         */ 
     
    444445            Backbone.View.prototype.initialize.call( control, options ); 
    445446 
    446             if ( ! control.el ) { 
    447                 throw new Error( 'Missing options.el' ); 
    448             } 
    449447            if ( ! ( control.model instanceof component.MediaWidgetModel ) ) { 
    450448                throw new Error( 'Missing options.model' ); 
    451449            } 
     450            if ( ! options.el ) { 
     451                throw new Error( 'Missing options.el' ); 
     452            } 
     453            if ( ! options.syncContainer ) { 
     454                throw new Error( 'Missing options.syncContainer' ); 
     455            } 
     456 
     457            control.syncContainer = options.syncContainer; 
     458 
     459            control.$el.addClass( 'media-widget-control' ); 
    452460 
    453461            // Allow methods to be passed in with control context preserved. 
     
    554562        syncModelToInputs: function syncModelToInputs() { 
    555563            var control = this; 
    556             control.$el.next( '.widget-content' ).find( '.media-widget-instance-property' ).each( function() { 
     564            control.syncContainer.find( '.media-widget-instance-property' ).each( function() { 
    557565                var input = $( this ), value; 
    558566                value = control.model.get( input.data( 'property' ) ); 
     
    10101018     */ 
    10111019    component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) { 
    1012         var widgetContent, controlContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone; 
     1020        var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone; 
    10131021        widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen. 
    1014         widgetContent = widgetForm.find( '> .widget-content' ); 
    10151022        idBase = widgetForm.find( '> .id_base' ).val(); 
    10161023        widgetId = widgetForm.find( '> .widget-id' ).val(); 
     
    10391046         * container. 
    10401047         */ 
    1041         controlContainer = $( '<div class="media-widget-control"></div>' ); 
    1042         widgetContent.before( controlContainer ); 
     1048        fieldContainer = $( '<div></div>' ); 
     1049        syncContainer = widgetContainer.find( '.widget-content:first' ); 
     1050        syncContainer.before( fieldContainer ); 
    10431051 
    10441052        /* 
     
    10481056         */ 
    10491057        modelAttributes = {}; 
    1050         widgetContent.find( '.media-widget-instance-property' ).each( function() { 
     1058        syncContainer.find( '.media-widget-instance-property' ).each( function() { 
    10511059            var input = $( this ); 
    10521060            modelAttributes[ input.data( 'property' ) ] = input.val(); 
     
    10571065 
    10581066        widgetControl = new ControlConstructor({ 
    1059             el: controlContainer, 
     1067            el: fieldContainer, 
     1068            syncContainer: syncContainer, 
    10601069            model: widgetModel 
    10611070        }); 
     
    10831092        component.modelCollection.add( [ widgetModel ] ); 
    10841093        component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl; 
     1094    }; 
     1095 
     1096    /** 
     1097     * Setup widget in accessibility mode. 
     1098     * 
     1099     * @returns {void} 
     1100     */ 
     1101    component.setupAccessibleMode = function setupAccessibleMode() { 
     1102        var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer; 
     1103        widgetForm = $( '.editwidget > form' ); 
     1104        if ( 0 === widgetForm.length ) { 
     1105            return; 
     1106        } 
     1107 
     1108        idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val(); 
     1109 
     1110        ControlConstructor = component.controlConstructors[ idBase ]; 
     1111        if ( ! ControlConstructor ) { 
     1112            return; 
     1113        } 
     1114 
     1115        widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val(); 
     1116 
     1117        ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel; 
     1118        fieldContainer = $( '<div></div>' ); 
     1119        syncContainer = widgetForm.find( '> .widget-inside' ); 
     1120        syncContainer.before( fieldContainer ); 
     1121 
     1122        modelAttributes = {}; 
     1123        syncContainer.find( '.media-widget-instance-property' ).each( function() { 
     1124            var input = $( this ); 
     1125            modelAttributes[ input.data( 'property' ) ] = input.val(); 
     1126        }); 
     1127        modelAttributes.widget_id = widgetId; 
     1128 
     1129        widgetControl = new ControlConstructor({ 
     1130            el: fieldContainer, 
     1131            syncContainer: syncContainer, 
     1132            model: new ModelConstructor( modelAttributes ) 
     1133        }); 
     1134 
     1135        component.modelCollection.add( [ widgetControl.model ] ); 
     1136        component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl; 
     1137 
     1138        widgetControl.render(); 
    10851139    }; 
    10861140 
     
    11531207                component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer ); 
    11541208            }); 
     1209 
     1210            // Accessibility mode. 
     1211            $( window ).on( 'load', function() { 
     1212                component.setupAccessibleMode(); 
     1213            }); 
    11551214        }); 
    11561215    }; 
  • trunk/src/wp-admin/js/widgets/text-widgets.js

    r40821 r40941  
    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    }; 
  • trunk/tests/qunit/wp-admin/js/widgets/test-media-image-widget.js

    r40640 r40941  
    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        }); 
  • trunk/tests/qunit/wp-admin/js/widgets/test-media-video-widget.js

    r40810 r40941  
    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.