--- a/wp/wp-admin/js/widgets.js Tue Jun 09 11:14:17 2015 +0000
+++ b/wp/wp-admin/js/widgets.js Mon Oct 14 17:39:30 2019 +0200
@@ -1,8 +1,35 @@
/*global ajaxurl, isRtl */
var wpWidgets;
(function($) {
+ var $document = $( document );
wpWidgets = {
+ /**
+ * A closed Sidebar that gets a Widget dragged over it.
+ *
+ * @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,
@@ -12,32 +39,112 @@
sidebars = $('div.widgets-sortables'),
isRTL = !! ( 'undefined' !== typeof isRtl && isRtl );
- $('#widgets-right .sidebar-name').click( function() {
- var $this = $(this),
- $wrap = $this.closest('.widgets-holder-wrap');
+ // Handle the widgets containers in the right column.
+ $( '#widgets-right .sidebar-name' )
+ /*
+ * Toggle the widgets containers when clicked and update the toggle
+ * button `aria-expanded` attribute value.
+ */
+ .click( function() {
+ var $this = $( this ),
+ $wrap = $this.closest( '.widgets-holder-wrap '),
+ $toggle = $this.find( '.handlediv' );
+
+ if ( $wrap.hasClass( 'closed' ) ) {
+ $wrap.removeClass( 'closed' );
+ $toggle.attr( 'aria-expanded', 'true' );
+ // Refresh the jQuery UI sortable items.
+ $this.parent().sortable( 'refresh' );
+ } else {
+ $wrap.addClass( 'closed' );
+ $toggle.attr( 'aria-expanded', 'false' );
+ }
+
+ // Update the admin menu "sticky" state.
+ $document.triggerHandler( 'wp-pin-menu' );
+ })
+ /*
+ * Set the initial `aria-expanded` attribute value on the widgets
+ * containers toggle button. The first one is expanded by default.
+ */
+ .find( '.handlediv' ).each( function( index ) {
+ if ( 0 === index ) {
+ // jQuery equivalent of `continue` within an `each()` loop.
+ return;
+ }
- if ( $wrap.hasClass('closed') ) {
- $wrap.removeClass('closed');
- $this.parent().sortable('refresh');
- } else {
- $wrap.addClass('closed');
+ $( this ).attr( 'aria-expanded', 'false' );
+ });
+
+ // 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');
+ // Handle the widgets containers in the left column.
+ $( '#widgets-left .sidebar-name' ).click( function() {
+ var $wrap = $( this ).closest( '.widgets-holder-wrap' );
+
+ $wrap
+ .toggleClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', ! $wrap.hasClass( 'closed' ) );
+
+ // Update the admin menu "sticky" state.
+ $document.triggerHandler( 'wp-pin-menu' );
});
$(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 ) {
@@ -50,10 +157,21 @@
css[ margin ] = widgetWidth - ( targetWidth + 30 ) + 'px';
widget.css( css );
}
- widget.addClass( 'open' );
- inside.slideDown('fast');
+ /*
+ * Don't change the order of attributes changes and animation:
+ * it's important for screen readers, see ticket #31476.
+ */
+ toggleBtn.attr( 'aria-expanded', 'true' );
+ inside.slideDown( 'fast', function() {
+ widget.addClass( 'open' );
+ });
} else {
- inside.slideUp('fast', function() {
+ /*
+ * Don't change the order of attributes changes and animation:
+ * it's important for screen readers, see ticket #31476.
+ */
+ toggleBtn.attr( 'aria-expanded', 'false' );
+ inside.slideUp( 'fast', function() {
widget.attr( 'style', '' );
widget.removeClass( 'open' );
});
@@ -68,8 +186,12 @@
} else if ( target.hasClass('widget-control-close') ) {
widget = target.closest('div.widget');
widget.removeClass( 'open' );
+ toggleBtn.attr( 'aria-expanded', 'false' );
wpWidgets.close( widget );
e.preventDefault();
+ } else if ( target.attr( 'id' ) === 'inactive-widgets-control-remove' ) {
+ wpWidgets.removeInactiveWidgets();
+ e.preventDefault();
}
});
@@ -79,7 +201,7 @@
wpWidgets.appendTitle( this );
if ( $this.find( 'p.widget-error' ).length ) {
- $this.find( 'a.widget-action' ).trigger('click');
+ $this.find( '.widget-action' ).trigger( 'click' ).attr( 'aria-expanded', 'true' );
}
});
@@ -89,7 +211,8 @@
distance: 2,
helper: 'clone',
zIndex: 100,
- containment: 'document',
+ containment: '#wpwrap',
+ refreshPositions: true,
start: function( event, ui ) {
var chooser = $(this).find('.widgets-chooser');
@@ -113,19 +236,64 @@
}
});
+ /**
+ * Opens and closes previously closed Sidebars when Widgets are dragged over/out of them.
+ */
+ sidebars.droppable( {
+ tolerance: 'intersect',
+
+ /**
+ * Open Sidebar when a Widget gets dragged over it.
+ *
+ * @param {object} event jQuery event object.
+ */
+ over: function( event ) {
+ var $wrap = $( event.target ).parent();
+
+ if ( wpWidgets.hoveredSidebar && ! $wrap.is( wpWidgets.hoveredSidebar ) ) {
+ // Close the previous Sidebar as the Widget has been dragged onto another Sidebar.
+ wpWidgets.closeSidebar( event );
+ }
+
+ if ( $wrap.hasClass( 'closed' ) ) {
+ wpWidgets.hoveredSidebar = $wrap;
+ $wrap
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
+ }
+
+ $( this ).sortable( 'refresh' );
+ },
+
+ /**
+ * Close Sidebar when the Widget gets dragged out of it.
+ *
+ * @param {object} event jQuery event object.
+ */
+ out: function( event ) {
+ if ( wpWidgets.hoveredSidebar ) {
+ wpWidgets.closeSidebar( event );
+ }
+ }
+ } );
+
sidebars.sortable({
placeholder: 'widget-placeholder',
items: '> .widget',
handle: '> .widget-top > .widget-title',
cursor: 'move',
distance: 2,
- containment: 'document',
+ containment: '#wpwrap',
+ tolerance: 'pointer',
+ refreshPositions: true,
start: function( event, ui ) {
var height, $this = $(this),
$wrap = $this.parent(),
inside = ui.item.children('.widget-inside');
if ( inside.css('display') === 'block' ) {
+ ui.item.removeClass('open');
+ ui.item.find( '.widget-top button.widget-action' ).attr( 'aria-expanded', 'false' );
inside.hide();
$(this).sortable('refreshPositions');
}
@@ -143,6 +311,9 @@
$widget = ui.item,
id = the_id;
+ // Reset the var to hold a previously closed sidebar.
+ wpWidgets.hoveredSidebar = null;
+
if ( $widget.hasClass('deleting') ) {
wpWidgets.save( $widget, 1, 0, 1 ); // delete widget
$widget.remove();
@@ -174,13 +345,16 @@
wpWidgets.save( $widget, 0, 0, 1 );
$widget.find('input.add_new').val('');
- $( document ).trigger( 'widget-added', [ $widget ] );
+ $document.trigger( 'widget-added', [ $widget ] );
}
$sidebar = $widget.parent();
if ( $sidebar.parent().hasClass('closed') ) {
- $sidebar.parent().removeClass('closed');
+ $sidebar.parent()
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
+
$children = $sidebar.children('.widget');
// Make sure the dropped widget is at the top
@@ -195,7 +369,7 @@
}
if ( addNew ) {
- $widget.find( 'a.widget-action' ).trigger('click');
+ $widget.find( '.widget-action' ).trigger( 'click' );
} else {
wpWidgets.saveOrder( $sidebar.attr('id') );
}
@@ -241,7 +415,7 @@
if ( ui.draggable.hasClass('ui-sortable-helper') ) {
$('#removing-widget').show().children('span')
- .html( ui.draggable.find('div.widget-title').children('h4').html() );
+ .html( ui.draggable.find( 'div.widget-title' ).children( 'h3' ).html() );
}
},
out: function(e,ui) {
@@ -254,7 +428,7 @@
// Area Chooser
$( '#widgets-right .widgets-holder-wrap' ).each( function( index, element ) {
var $element = $( element ),
- name = $element.find( '.sidebar-name h3' ).text(),
+ name = $element.find( '.sidebar-name h2' ).text(),
id = $element.find( '.widgets-sortables' ).attr( 'id' ),
li = $('<li tabindex="0">').text( $.trim( name ) );
@@ -295,12 +469,12 @@
if ( $target.hasClass('button-primary') ) {
self.addWidget( chooser );
self.closeChooser();
- } else if ( $target.hasClass('button-secondary') ) {
+ } else if ( $target.hasClass( 'widgets-chooser-cancel' ) ) {
self.closeChooser();
}
}).on( 'keyup.widgets-chooser', function( event ) {
if ( event.which === $.ui.keyCode.ENTER ) {
- if ( $( event.target ).hasClass('button-secondary') ) {
+ if ( $( event.target ).hasClass( 'widgets-chooser-cancel' ) ) {
// Close instead of adding when pressing Enter on the Cancel button
self.closeChooser();
} else {
@@ -331,13 +505,22 @@
});
$.post( ajaxurl, data, function() {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
$( '.spinner' ).removeClass( 'is-active' );
});
},
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' ),
+ isAdd = widget.find( 'input.add_new' ).val();
+
+ if ( ! del && ! isAdd && form.prop( 'checkValidity' ) && ! form[0].checkValidity() ) {
+ return;
+ }
+
+ data = form.serialize();
widget = $(widget);
$( '.spinner', widget ).addClass( 'is-active' );
@@ -355,11 +538,10 @@
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();
@@ -369,27 +551,70 @@
if ( animate ) {
order = 0;
- widget.slideUp('fast', function(){
- $(this).remove();
+ widget.slideUp( 'fast', function() {
+ $( this ).remove();
wpWidgets.saveOrder();
+ delete self.dirtyWidgets[ id ];
});
} else {
widget.remove();
+ delete self.dirtyWidgets[ id ];
+
+ if ( sidebarId === 'wp_inactive_widgets' ) {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
+ }
}
} else {
$( '.spinner' ).removeClass( 'is-active' );
if ( r && r.length > 2 ) {
$( 'div.widget-content', widget ).html( r );
wpWidgets.appendTitle( widget );
- $( document ).trigger( 'widget-updated', [ 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' ) {
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled' , ! $( '#wp_inactive_widgets .widget' ).length );
+ }
}
}
+
if ( order ) {
wpWidgets.saveOrder();
}
});
},
+ removeInactiveWidgets : function() {
+ var $element = $( '.remove-inactive-widgets' ), self = this, a, data;
+
+ $( '.spinner', $element ).addClass( 'is-active' );
+
+ a = {
+ action : 'delete-inactive-widgets',
+ removeinactivewidgets : $( '#_wpnonce_remove_inactive_widgets' ).val()
+ };
+
+ data = $.param( a );
+
+ $.post( ajaxurl, data, function() {
+ $( '#wp_inactive_widgets .widget' ).each(function() {
+ var $widget = $( this );
+ delete self.dirtyWidgets[ $widget.find( 'input.widget-id' ).val() ];
+ $widget.remove();
+ });
+ $( '#inactive-widgets-control-remove' ).prop( 'disabled', true );
+ $( '.spinner', $element ).removeClass( 'is-active' );
+ } );
+ },
+
appendTitle : function(widget) {
var title = $('input[id*="-title"]', widget).val() || '';
@@ -404,7 +629,10 @@
close : function(widget) {
widget.children('.widget-inside').slideUp('fast', function() {
- widget.attr( 'style', '' );
+ widget.attr( 'style', '' )
+ .find( '.widget-top button.widget-action' )
+ .attr( 'aria-expanded', 'false' )
+ .focus();
});
},
@@ -436,8 +664,10 @@
$( '#' + widgetId ).hide();
}
- // Open the widgets container
- sidebar.closest( '.widgets-holder-wrap' ).removeClass('closed');
+ // Open the widgets container.
+ sidebar.closest( '.widgets-holder-wrap' )
+ .removeClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'true' );
sidebar.append( widget );
sidebar.sortable('refresh');
@@ -446,7 +676,7 @@
// No longer "new" widget
widget.find( 'input.add_new' ).val('');
- $( document ).trigger( 'widget-added', [ widget ] );
+ $document.trigger( 'widget-added', [ widget ] );
/*
* Check if any part of the sidebar is visible in the viewport. If it is, don't scroll.
@@ -486,9 +716,25 @@
clearWidgetSelection: function() {
$( '#widgets-left' ).removeClass( 'chooser' );
$( '.widget-in-question' ).removeClass( 'widget-in-question' );
+ },
+
+ /**
+ * Closes a Sidebar that was previously closed, but opened by dragging a Widget over it.
+ *
+ * Used when a Widget gets dragged in/out of the Sidebar and never dropped.
+ *
+ * @param {object} event jQuery event object.
+ */
+ closeSidebar: function( event ) {
+ this.hoveredSidebar
+ .addClass( 'closed' )
+ .find( '.handlediv' ).attr( 'aria-expanded', 'false' );
+
+ $( event.target ).css( 'min-height', '' );
+ this.hoveredSidebar = null;
}
};
-$(document).ready( function(){ wpWidgets.init(); } );
+$document.ready( function(){ wpWidgets.init(); } );
})(jQuery);