Widgets: Add dirty state tracking for widgets on admin screen.
* Mark a widget as dirty when a field input triggers a `change` or `input` event; clear dirty state when widget is successfully saved. * Disable Save button and re-label "Saved" when widget not dirty. * Show AYS dialog when leaving widgets admin screen with unsaved changes. * When widgets are dirty, expand all unsaved widgets at AYS check and focus on first one. * Change "Close" link to "Done"; hide link when widget is dirty and reveal when saved. * The "Done" link persistently appears in the Customizer even after making a change (when the widget is dirty) because changes are autosaved into the changeset. * Prevent saving widget when form fails `checkValidity`. * Fix frequency of triggering of `change` event on the rich Text widget's `textarea` limited now to when there are actual changes. * Add a class of `widget-dirty` to widget containers when the widget has unsaved changes. Props westonruter, timmydcrawford, melchoyce. Fixes #41610, #23120. Built from https://develop.svn.wordpress.org/trunk@41352 git-svn-id: http://core.svn.wordpress.org/trunk@41185 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
8a23b80b56
commit
9b5d71bad0
|
@ -42,6 +42,10 @@
|
|||
line-height: 16px;
|
||||
}
|
||||
|
||||
.widget.widget-dirty .widget-control-close-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.in-widget-title,
|
||||
#widgets-right a.widget-control-edit,
|
||||
#available-widgets .widget-description {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -42,6 +42,10 @@
|
|||
line-height: 16px;
|
||||
}
|
||||
|
||||
.widget.widget-dirty .widget-control-close-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.in-widget-title,
|
||||
#widgets-right a.widget-control-edit,
|
||||
#available-widgets .widget-description {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -253,8 +253,11 @@ function wp_widget_control( $sidebar_args ) {
|
|||
|
||||
<div class="widget-control-actions">
|
||||
<div class="alignleft">
|
||||
<button type="button" class="button-link button-link-delete widget-control-remove"><?php _e( 'Delete' ); ?></button> |
|
||||
<button type="button" class="button-link widget-control-close"><?php _e( 'Close' ); ?></button>
|
||||
<button type="button" class="button-link button-link-delete widget-control-remove"><?php _e( 'Delete' ); ?></button>
|
||||
<span class="widget-control-close-wrapper">
|
||||
|
|
||||
<button type="button" class="button-link widget-control-close"><?php _e( 'Done' ); ?></button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="alignright<?php if ( 'noform' === $has_form ) echo ' widget-control-noform'; ?>">
|
||||
<?php submit_button( __( 'Save' ), 'primary widget-control-save right', 'savewidget', false, array( 'id' => 'widget-' . esc_attr( $id_format ) . '-savewidget' ) ); ?>
|
||||
|
|
|
@ -7,10 +7,30 @@ wpWidgets = {
|
|||
/**
|
||||
* A closed Sidebar that gets a Widget dragged over it.
|
||||
*
|
||||
* @var element|null
|
||||
* @var {element|null}
|
||||
*/
|
||||
hoveredSidebar: null,
|
||||
|
||||
/**
|
||||
* Translations.
|
||||
*
|
||||
* Exported from PHP in wp_default_scripts().
|
||||
*
|
||||
* @var {object}
|
||||
*/
|
||||
l10n: {
|
||||
save: '{save}',
|
||||
saved: '{saved}',
|
||||
saveAlert: '{saveAlert}'
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup of which widgets have had change events triggered.
|
||||
*
|
||||
* @var {object}
|
||||
*/
|
||||
dirtyWidgets: {},
|
||||
|
||||
init : function() {
|
||||
var rem, the_id,
|
||||
self = this,
|
||||
|
@ -33,6 +53,39 @@ wpWidgets = {
|
|||
$document.triggerHandler( 'wp-pin-menu' );
|
||||
});
|
||||
|
||||
// Show AYS dialog when there are unsaved widget changes.
|
||||
$( window ).on( 'beforeunload.widgets', function( event ) {
|
||||
var dirtyWidgetIds = [], unsavedWidgetsElements;
|
||||
$.each( self.dirtyWidgets, function( widgetId, dirty ) {
|
||||
if ( dirty ) {
|
||||
dirtyWidgetIds.push( widgetId );
|
||||
}
|
||||
});
|
||||
if ( 0 !== dirtyWidgetIds.length ) {
|
||||
unsavedWidgetsElements = $( '#widgets-right' ).find( '.widget' ).filter( function() {
|
||||
return -1 !== dirtyWidgetIds.indexOf( $( this ).prop( 'id' ).replace( /^widget-\d+_/, '' ) );
|
||||
});
|
||||
unsavedWidgetsElements.each( function() {
|
||||
if ( ! $( this ).hasClass( 'open' ) ) {
|
||||
$( this ).find( '.widget-title-action:first' ).click();
|
||||
}
|
||||
});
|
||||
|
||||
// Bring the first unsaved widget into view and focus on the first tabbable field.
|
||||
unsavedWidgetsElements.first().each( function() {
|
||||
if ( this.scrollIntoViewIfNeeded ) {
|
||||
this.scrollIntoViewIfNeeded();
|
||||
} else {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
$( this ).find( '.widget-inside :tabbable:first' ).focus();
|
||||
} );
|
||||
|
||||
event.returnValue = wpWidgets.l10n.saveAlert;
|
||||
return event.returnValue;
|
||||
}
|
||||
});
|
||||
|
||||
$('#widgets-left .sidebar-name').click( function() {
|
||||
$(this).closest('.widgets-holder-wrap').toggleClass('closed');
|
||||
$document.triggerHandler( 'wp-pin-menu' );
|
||||
|
@ -41,14 +94,27 @@ wpWidgets = {
|
|||
$(document.body).bind('click.widgets-toggle', function(e) {
|
||||
var target = $(e.target),
|
||||
css = { 'z-index': 100 },
|
||||
widget, inside, targetWidth, widgetWidth, margin,
|
||||
widget, inside, targetWidth, widgetWidth, margin, saveButton, widgetId,
|
||||
toggleBtn = target.closest( '.widget' ).find( '.widget-top button.widget-action' );
|
||||
|
||||
if ( target.parents('.widget-top').length && ! target.parents('#available-widgets').length ) {
|
||||
widget = target.closest('div.widget');
|
||||
inside = widget.children('.widget-inside');
|
||||
targetWidth = parseInt( widget.find('input.widget-width').val(), 10 ),
|
||||
targetWidth = parseInt( widget.find('input.widget-width').val(), 10 );
|
||||
widgetWidth = widget.parent().width();
|
||||
widgetId = inside.find( '.widget-id' ).val();
|
||||
|
||||
// Save button is initially disabled, but is enabled when a field is changed.
|
||||
if ( ! widget.data( 'dirty-state-initialized' ) ) {
|
||||
saveButton = inside.find( '.widget-control-save' );
|
||||
saveButton.prop( 'disabled', true ).val( wpWidgets.l10n.saved );
|
||||
inside.on( 'input change', function() {
|
||||
self.dirtyWidgets[ widgetId ] = true;
|
||||
widget.addClass( 'widget-dirty' );
|
||||
saveButton.prop( 'disabled', false ).val( wpWidgets.l10n.save );
|
||||
});
|
||||
widget.data( 'dirty-state-initialized', true );
|
||||
}
|
||||
|
||||
if ( inside.is(':hidden') ) {
|
||||
if ( targetWidth > 250 && ( targetWidth + 30 > widgetWidth ) && widget.closest('div.widgets-sortables').length ) {
|
||||
|
@ -410,8 +476,15 @@ wpWidgets = {
|
|||
},
|
||||
|
||||
save : function( widget, del, animate, order ) {
|
||||
var sidebarId = widget.closest('div.widgets-sortables').attr('id'),
|
||||
data = widget.find('form').serialize(), a;
|
||||
var self = this, data, a,
|
||||
sidebarId = widget.closest( 'div.widgets-sortables' ).attr( 'id' ),
|
||||
form = widget.find( 'form' );
|
||||
|
||||
if ( form.prop( 'checkValidity' ) && ! form[0].checkValidity() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = form.serialize();
|
||||
|
||||
widget = $(widget);
|
||||
$( '.spinner', widget ).addClass( 'is-active' );
|
||||
|
@ -429,11 +502,10 @@ wpWidgets = {
|
|||
data += '&' + $.param(a);
|
||||
|
||||
$.post( ajaxurl, data, function(r) {
|
||||
var id;
|
||||
var id = $('input.widget-id', widget).val();
|
||||
|
||||
if ( del ) {
|
||||
if ( ! $('input.widget_number', widget).val() ) {
|
||||
id = $('input.widget-id', widget).val();
|
||||
$('#available-widgets').find('input.widget-id').each(function(){
|
||||
if ( $(this).val() === id ) {
|
||||
$(this).closest('div.widget').show();
|
||||
|
@ -459,6 +531,15 @@ wpWidgets = {
|
|||
if ( r && r.length > 2 ) {
|
||||
$( 'div.widget-content', widget ).html( r );
|
||||
wpWidgets.appendTitle( widget );
|
||||
|
||||
// Re-disable the save button.
|
||||
widget.find( '.widget-control-save' ).prop( 'disabled', true ).val( wpWidgets.l10n.saved );
|
||||
|
||||
widget.removeClass( 'widget-dirty' );
|
||||
|
||||
// Clear the dirty flag from the widget.
|
||||
delete self.dirtyWidgets[ id ];
|
||||
|
||||
$document.trigger( 'widget-updated', [ widget ] );
|
||||
|
||||
if ( sidebarId === 'wp_inactive_widgets' ) {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -165,9 +165,10 @@ wp.textWidgets = ( function( $ ) {
|
|||
* @returns {void}
|
||||
*/
|
||||
initializeEditor: function initializeEditor() {
|
||||
var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false;
|
||||
var control = this, changeDebounceDelay = 1000, id, textarea, triggerChangeIfDirty, restoreTextMode = false, needsTextareaChangeTrigger = false, previousValue;
|
||||
textarea = control.fields.text;
|
||||
id = textarea.attr( 'id' );
|
||||
previousValue = textarea.val();
|
||||
|
||||
/**
|
||||
* Trigger change if dirty.
|
||||
|
@ -202,10 +203,11 @@ wp.textWidgets = ( function( $ ) {
|
|||
}
|
||||
}
|
||||
|
||||
// Trigger change on textarea when it is dirty for sake of widgets in the Customizer needing to sync form inputs to setting models.
|
||||
if ( needsTextareaChangeTrigger ) {
|
||||
// Trigger change on textarea when it has changed so the widget can enter a dirty state.
|
||||
if ( needsTextareaChangeTrigger && previousValue !== textarea.val() ) {
|
||||
textarea.trigger( 'change' );
|
||||
needsTextareaChangeTrigger = false;
|
||||
previousValue = textarea.val();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -672,6 +672,12 @@ function wp_default_scripts( &$scripts ) {
|
|||
$scripts->add( 'admin-gallery', "/wp-admin/js/gallery$suffix.js", array( 'jquery-ui-sortable' ) );
|
||||
|
||||
$scripts->add( 'admin-widgets', "/wp-admin/js/widgets$suffix.js", array( 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable' ), false, 1 );
|
||||
$scripts->add_inline_script( 'admin-widgets', sprintf( 'wpWidgets.l10n = %s;', wp_json_encode( array(
|
||||
'save' => __( 'Save' ),
|
||||
'saved' => __( 'Saved' ),
|
||||
'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
|
||||
) ) ) );
|
||||
|
||||
$scripts->add( 'media-widgets', "/wp-admin/js/widgets/media-widgets$suffix.js", array( 'jquery', 'media-models', 'media-views', 'wp-api-request' ) );
|
||||
$scripts->add_inline_script( 'media-widgets', 'wp.mediaWidgets.init();', 'after' );
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* @global string $wp_version
|
||||
*/
|
||||
$wp_version = '4.9-alpha-41351';
|
||||
$wp_version = '4.9-alpha-41352';
|
||||
|
||||
/**
|
||||
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
|
||||
|
|
Loading…
Reference in New Issue