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. git-svn-id: https://develop.svn.wordpress.org/trunk@40941 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
d7fc80ca43
commit
f25d9d7909
@ -435,7 +435,8 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
*
|
||||
* @param {Object} options - Options.
|
||||
* @param {Backbone.Model} options.model - Model.
|
||||
* @param {jQuery} options.el - Control container element.
|
||||
* @param {jQuery} options.el - Control field container element.
|
||||
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
|
||||
* @returns {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
@ -443,12 +444,19 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
|
||||
Backbone.View.prototype.initialize.call( control, options );
|
||||
|
||||
if ( ! control.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! ( control.model instanceof component.MediaWidgetModel ) ) {
|
||||
throw new Error( 'Missing options.model' );
|
||||
}
|
||||
if ( ! options.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! options.syncContainer ) {
|
||||
throw new Error( 'Missing options.syncContainer' );
|
||||
}
|
||||
|
||||
control.syncContainer = options.syncContainer;
|
||||
|
||||
control.$el.addClass( 'media-widget-control' );
|
||||
|
||||
// Allow methods to be passed in with control context preserved.
|
||||
_.bindAll( control, 'syncModelToInputs', 'render', 'updateSelectedAttachment', 'renderPreview' );
|
||||
@ -553,7 +561,7 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
*/
|
||||
syncModelToInputs: function syncModelToInputs() {
|
||||
var control = this;
|
||||
control.$el.next( '.widget-content' ).find( '.media-widget-instance-property' ).each( function() {
|
||||
control.syncContainer.find( '.media-widget-instance-property' ).each( function() {
|
||||
var input = $( this ), value;
|
||||
value = control.model.get( input.data( 'property' ) );
|
||||
if ( _.isUndefined( value ) ) {
|
||||
@ -1009,9 +1017,8 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
* @returns {void}
|
||||
*/
|
||||
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
|
||||
var widgetContent, controlContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone;
|
||||
var fieldContainer, syncContainer, widgetForm, idBase, ControlConstructor, ModelConstructor, modelAttributes, widgetControl, widgetModel, widgetId, widgetInside, animatedCheckDelay = 50, renderWhenAnimationDone;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
|
||||
widgetContent = widgetForm.find( '> .widget-content' );
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
widgetId = widgetForm.find( '> .widget-id' ).val();
|
||||
|
||||
@ -1038,8 +1045,9 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
* components", the JS template is rendered outside of the normal form
|
||||
* container.
|
||||
*/
|
||||
controlContainer = $( '<div class="media-widget-control"></div>' );
|
||||
widgetContent.before( controlContainer );
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetContainer.find( '.widget-content:first' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
/*
|
||||
* Sync the widget instance model attributes onto the hidden inputs that widgets currently use to store the state.
|
||||
@ -1047,7 +1055,7 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
* from the start, without having to sync with hidden fields. See <https://core.trac.wordpress.org/ticket/33507>.
|
||||
*/
|
||||
modelAttributes = {};
|
||||
widgetContent.find( '.media-widget-instance-property' ).each( function() {
|
||||
syncContainer.find( '.media-widget-instance-property' ).each( function() {
|
||||
var input = $( this );
|
||||
modelAttributes[ input.data( 'property' ) ] = input.val();
|
||||
});
|
||||
@ -1056,7 +1064,8 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
widgetModel = new ModelConstructor( modelAttributes );
|
||||
|
||||
widgetControl = new ControlConstructor({
|
||||
el: controlContainer,
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer,
|
||||
model: widgetModel
|
||||
});
|
||||
|
||||
@ -1084,6 +1093,51 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
component.widgetControls[ widgetModel.get( 'widget_id' ) ] = widgetControl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup widget in accessibility mode.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
component.setupAccessibleMode = function setupAccessibleMode() {
|
||||
var widgetForm, widgetId, idBase, widgetControl, ControlConstructor, ModelConstructor, modelAttributes, fieldContainer, syncContainer;
|
||||
widgetForm = $( '.editwidget > form' );
|
||||
if ( 0 === widgetForm.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
|
||||
|
||||
ControlConstructor = component.controlConstructors[ idBase ];
|
||||
if ( ! ControlConstructor ) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetId = widgetForm.find( '> .widget-control-actions > .widget-id' ).val();
|
||||
|
||||
ModelConstructor = component.modelConstructors[ idBase ] || component.MediaWidgetModel;
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetForm.find( '> .widget-inside' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
modelAttributes = {};
|
||||
syncContainer.find( '.media-widget-instance-property' ).each( function() {
|
||||
var input = $( this );
|
||||
modelAttributes[ input.data( 'property' ) ] = input.val();
|
||||
});
|
||||
modelAttributes.widget_id = widgetId;
|
||||
|
||||
widgetControl = new ControlConstructor({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer,
|
||||
model: new ModelConstructor( modelAttributes )
|
||||
});
|
||||
|
||||
component.modelCollection.add( [ widgetControl.model ] );
|
||||
component.widgetControls[ widgetControl.model.get( 'widget_id' ) ] = widgetControl;
|
||||
|
||||
widgetControl.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync widget instance data sanitized from server back onto widget model.
|
||||
*
|
||||
@ -1152,6 +1206,11 @@ wp.mediaWidgets = ( function( $ ) {
|
||||
var widgetContainer = $( this );
|
||||
component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
|
||||
});
|
||||
|
||||
// Accessibility mode.
|
||||
$( window ).on( 'load', function() {
|
||||
component.setupAccessibleMode();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -24,9 +24,9 @@ wp.textWidgets = ( function( $ ) {
|
||||
/**
|
||||
* Initialize.
|
||||
*
|
||||
* @param {Object} options - Options.
|
||||
* @param {Backbone.Model} options.model - Model.
|
||||
* @param {jQuery} options.el - Control container element.
|
||||
* @param {Object} options - Options.
|
||||
* @param {jQuery} options.el - Control field container element.
|
||||
* @param {jQuery} options.syncContainer - Container element where fields are synced for the server.
|
||||
* @returns {void}
|
||||
*/
|
||||
initialize: function initialize( options ) {
|
||||
@ -35,34 +35,25 @@ wp.textWidgets = ( function( $ ) {
|
||||
if ( ! options.el ) {
|
||||
throw new Error( 'Missing options.el' );
|
||||
}
|
||||
if ( ! options.syncContainer ) {
|
||||
throw new Error( 'Missing options.syncContainer' );
|
||||
}
|
||||
|
||||
Backbone.View.prototype.initialize.call( control, options );
|
||||
control.syncContainer = options.syncContainer;
|
||||
|
||||
/*
|
||||
* Create a container element for the widget control fields.
|
||||
* This is inserted into the DOM immediately before the the .widget-content
|
||||
* element because the contents of this element are essentially "managed"
|
||||
* by PHP, where each widget update cause the entire element to be emptied
|
||||
* and replaced with the rendered output of WP_Widget::form() which is
|
||||
* sent back in Ajax request made to save/update the widget instance.
|
||||
* To prevent a "flash of replaced DOM elements and re-initialized JS
|
||||
* components", the JS template is rendered outside of the normal form
|
||||
* container.
|
||||
*/
|
||||
control.fieldContainer = $( '<div class="text-widget-fields"></div>' );
|
||||
control.fieldContainer.html( wp.template( 'widget-text-control-fields' ) );
|
||||
control.widgetContentContainer = control.$el.find( '.widget-content:first' );
|
||||
control.widgetContentContainer.before( control.fieldContainer );
|
||||
control.$el.addClass( 'text-widget-fields' );
|
||||
control.$el.html( wp.template( 'widget-text-control-fields' ) );
|
||||
|
||||
control.fields = {
|
||||
title: control.fieldContainer.find( '.title' ),
|
||||
text: control.fieldContainer.find( '.text' )
|
||||
title: control.$el.find( '.title' ),
|
||||
text: control.$el.find( '.text' )
|
||||
};
|
||||
|
||||
// Sync input fields to hidden sync fields which actually get sent to the server.
|
||||
_.each( control.fields, function( fieldInput, fieldName ) {
|
||||
fieldInput.on( 'input change', function updateSyncField() {
|
||||
var syncInput = control.widgetContentContainer.find( 'input[type=hidden].' + fieldName );
|
||||
var syncInput = control.syncContainer.find( 'input[type=hidden].' + fieldName );
|
||||
if ( syncInput.val() !== $( this ).val() ) {
|
||||
syncInput.val( $( this ).val() );
|
||||
syncInput.trigger( 'change' );
|
||||
@ -70,7 +61,7 @@ wp.textWidgets = ( function( $ ) {
|
||||
});
|
||||
|
||||
// Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
|
||||
fieldInput.val( control.widgetContentContainer.find( 'input[type=hidden].' + fieldName ).val() );
|
||||
fieldInput.val( control.syncContainer.find( 'input[type=hidden].' + fieldName ).val() );
|
||||
});
|
||||
},
|
||||
|
||||
@ -87,11 +78,11 @@ wp.textWidgets = ( function( $ ) {
|
||||
var control = this, syncInput;
|
||||
|
||||
if ( ! control.fields.title.is( document.activeElement ) ) {
|
||||
syncInput = control.widgetContentContainer.find( 'input[type=hidden].title' );
|
||||
syncInput = control.syncContainer.find( 'input[type=hidden].title' );
|
||||
control.fields.title.val( syncInput.val() );
|
||||
}
|
||||
|
||||
syncInput = control.widgetContentContainer.find( 'input[type=hidden].text' );
|
||||
syncInput = control.syncContainer.find( 'input[type=hidden].text' );
|
||||
if ( control.fields.text.is( ':visible' ) ) {
|
||||
if ( ! control.fields.text.is( document.activeElement ) ) {
|
||||
control.fields.text.val( syncInput.val() );
|
||||
@ -219,7 +210,7 @@ wp.textWidgets = ( function( $ ) {
|
||||
* @returns {void}
|
||||
*/
|
||||
component.handleWidgetAdded = function handleWidgetAdded( event, widgetContainer ) {
|
||||
var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, widgetInside, renderWhenAnimationDone;
|
||||
var widgetForm, idBase, widgetControl, widgetId, animatedCheckDelay = 50, widgetInside, renderWhenAnimationDone, fieldContainer, syncContainer;
|
||||
widgetForm = widgetContainer.find( '> .widget-inside > .form, > .widget-inside > form' ); // Note: '.form' appears in the customizer, whereas 'form' on the widgets admin screen.
|
||||
|
||||
idBase = widgetForm.find( '> .id_base' ).val();
|
||||
@ -228,13 +219,29 @@ wp.textWidgets = ( function( $ ) {
|
||||
}
|
||||
|
||||
// Prevent initializing already-added widgets.
|
||||
widgetId = widgetForm.find( '> .widget-id' ).val();
|
||||
widgetId = widgetForm.find( '.widget-id' ).val();
|
||||
if ( component.widgetControls[ widgetId ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a container element for the widget control fields.
|
||||
* This is inserted into the DOM immediately before the the .widget-content
|
||||
* element because the contents of this element are essentially "managed"
|
||||
* by PHP, where each widget update cause the entire element to be emptied
|
||||
* and replaced with the rendered output of WP_Widget::form() which is
|
||||
* sent back in Ajax request made to save/update the widget instance.
|
||||
* To prevent a "flash of replaced DOM elements and re-initialized JS
|
||||
* components", the JS template is rendered outside of the normal form
|
||||
* container.
|
||||
*/
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetContainer.find( '.widget-content:first' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.TextWidgetControl({
|
||||
el: widgetContainer
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
component.widgetControls[ widgetId ] = widgetControl;
|
||||
@ -256,6 +263,35 @@ wp.textWidgets = ( function( $ ) {
|
||||
renderWhenAnimationDone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup widget in accessibility mode.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
component.setupAccessibleMode = function setupAccessibleMode() {
|
||||
var widgetForm, idBase, widgetControl, fieldContainer, syncContainer;
|
||||
widgetForm = $( '.editwidget > form' );
|
||||
if ( 0 === widgetForm.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
idBase = widgetForm.find( '> .widget-control-actions > .id_base' ).val();
|
||||
if ( 'text' !== idBase ) {
|
||||
return;
|
||||
}
|
||||
|
||||
fieldContainer = $( '<div></div>' );
|
||||
syncContainer = widgetForm.find( '> .widget-inside' );
|
||||
syncContainer.before( fieldContainer );
|
||||
|
||||
widgetControl = new component.TextWidgetControl({
|
||||
el: fieldContainer,
|
||||
syncContainer: syncContainer
|
||||
});
|
||||
|
||||
widgetControl.initializeEditor();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync widget instance data sanitized from server back onto widget model.
|
||||
*
|
||||
@ -319,6 +355,11 @@ wp.textWidgets = ( function( $ ) {
|
||||
var widgetContainer = $( this );
|
||||
component.handleWidgetAdded( new jQuery.Event( 'widget-added' ), widgetContainer );
|
||||
});
|
||||
|
||||
// Accessibility mode.
|
||||
$( window ).on( 'load', function() {
|
||||
component.setupAccessibleMode();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
imageWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_image();
|
||||
imageWidgetControlInstance = new ImageWidgetControl({
|
||||
el: jQuery( '<div></div>' ),
|
||||
syncContainer: jQuery( '<div></div>' ),
|
||||
model: imageWidgetModelInstance
|
||||
});
|
||||
|
||||
@ -84,6 +86,8 @@
|
||||
|
||||
imageWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_image();
|
||||
imageWidgetControlInstance = new wp.mediaWidgets.controlConstructors.media_image({
|
||||
el: jQuery( '<div></div>' ),
|
||||
syncContainer: jQuery( '<div></div>' ),
|
||||
model: imageWidgetModelInstance
|
||||
});
|
||||
equal( imageWidgetControlInstance.$el.find( 'img' ).length, 0, 'No images should be rendered' );
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
videoWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_video();
|
||||
videoWidgetControlInstance = new VideoWidgetControl({
|
||||
el: jQuery( '<div></div>' ),
|
||||
syncContainer: jQuery( '<div></div>' ),
|
||||
model: videoWidgetModelInstance
|
||||
});
|
||||
|
||||
@ -46,6 +48,8 @@
|
||||
|
||||
videoWidgetModelInstance = new wp.mediaWidgets.modelConstructors.media_video();
|
||||
videoWidgetControlInstance = new wp.mediaWidgets.controlConstructors.media_video({
|
||||
el: jQuery( '<div></div>' ),
|
||||
syncContainer: jQuery( '<div></div>' ),
|
||||
model: videoWidgetModelInstance
|
||||
});
|
||||
equal( videoWidgetControlInstance.$el.find( 'a' ).length, 0, 'No video links should be rendered' );
|
||||
|
Loading…
Reference in New Issue
Block a user