WordPress.org

Make WordPress Core

Changeset 40941


Ignore:
Timestamp:
06/25/2017 06:47:13 PM (11 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.