WordPress.org

Make WordPress Core

Changeset 41051


Ignore:
Timestamp:
07/14/2017 05:16:43 PM (12 months 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.