WordPress.org

Make WordPress Core

Ticket #27491: 27491.6.diff

File 27491.6.diff, 16.5 KB (added by westonruter, 5 years ago)

Restore live update mode if sanitized fields are now aligned with the existing fields. Changes from previous patch: https://github.com/x-team/wordpress-develop/commit/439d8d9c03c3985510e38feb4d3dbcfe27defad0

  • src/wp-admin/css/customize-widgets.css

    diff --git src/wp-admin/css/customize-widgets.css src/wp-admin/css/customize-widgets.css
    index 1142a47..a703059 100644
     
    3737.customize-control-widget_form.previewer-loading .spinner {
    3838        opacity: 1.0;
    3939}
     40.customize-control-widget_form.widget-form-disabled .widget-content {
     41        opacity: 0.7;
     42        pointer-events: none;
     43        -moz-user-select: none;
     44        -webkit-user-select: none;
     45        -ms-user-select: none;
     46        user-select: none;
     47}
    4048
    4149.customize-control-widget_form .widget {
    4250        margin-bottom: 0;
  • src/wp-admin/js/customize-widgets.js

    diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js
    index 1307655..f439af7 100644
    var WidgetCustomizer = ( function ($) { 
    88                Sidebar,
    99                SidebarCollection,
    1010                OldPreviewer,
     11                builtin_form_sync_handlers,
    1112                customize = wp.customize, self = {
    1213                nonce: null,
    1314                i18n: {
    var WidgetCustomizer = ( function ($) { 
    135136        self.registered_sidebars = new SidebarCollection( self.registered_sidebars );
    136137
    137138        /**
     139         * Handlers for the widget-synced event, organized by widget ID base.
     140         * Other widgets may provide their own update handlers by adding
     141         * listeners for the widget-synced event.
     142         */
     143        builtin_form_sync_handlers = {
     144
     145                /**
     146                 * @param {jQuery.Event} e
     147                 * @param {jQuery} widget_el
     148                 * @param {String} new_form
     149                 */
     150                rss: function ( e, widget_el, new_form ) {
     151                        var old_widget_error = widget_el.find( '.widget-error:first' ),
     152                                new_widget_error = $( '<div>' + new_form + '</div>' ).find( '.widget-error:first' );
     153
     154                        if ( old_widget_error.length && new_widget_error.length ) {
     155                                old_widget_error.replaceWith( new_widget_error );
     156                        } else if ( old_widget_error.length ) {
     157                                old_widget_error.remove();
     158                        } else if ( new_widget_error.length ) {
     159                                widget_el.find( '.widget-content:first' ).prepend( new_widget_error );
     160                        }
     161                }
     162        };
     163
     164        /**
    138165         * On DOM ready, initialize some meta functionality independent of specific
    139166         * customizer controls.
    140167         */
    var WidgetCustomizer = ( function ($) { 
    480507                addWidget: function ( widget_id ) {
    481508                        var control = this,
    482509                                control_html,
     510                                widget_el,
    483511                                customize_control_type = 'widget_form',
    484512                                customize_control,
    485513                                parsed_widget_id = parse_widget_id( widget_id ),
    var WidgetCustomizer = ( function ($) { 
    514542                        } else {
    515543                                widget.set( 'is_disabled', true ); // Prevent single widget from being added again now
    516544                        }
     545                        widget_el = $( control_html );
    517546
    518547                        customize_control = $( '<li></li>' );
    519548                        customize_control.addClass( 'customize-control' );
    520549                        customize_control.addClass( 'customize-control-' + customize_control_type );
    521                         customize_control.append( $( control_html ) );
     550                        customize_control.append( widget_el );
    522551                        customize_control.find( '> .widget-icon' ).remove();
    523552                        if ( widget.get( 'is_multi' ) ) {
    524553                                customize_control.find( 'input[name="widget_number"]' ).val( widget_number );
    var WidgetCustomizer = ( function ($) { 
    604633                                }
    605634                        } );
    606635
     636                        $( document ).trigger( 'widget-added', [ widget_el ] );
     637
    607638                        return widget_form_control;
    608639                }
    609640
    var WidgetCustomizer = ( function ($) { 
    628659                        control._setupHighlightEffects();
    629660                        control._setupUpdateUI();
    630661                        control._setupRemoveUI();
    631                         control.hook( 'init' );
    632                 },
    633 
    634                 /**
    635                  * Hooks for widgets to support living in the customizer control
    636                  */
    637                 hooks: {
    638                         _default: {},
    639                         rss: {
    640                                 formUpdated: function ( serialized_form ) {
    641                                         var control = this,
    642                                                 old_widget_error = control.container.find( '.widget-error:first' ),
    643                                                 new_widget_error = serialized_form.find( '.widget-error:first' );
    644 
    645                                         if ( old_widget_error.length && new_widget_error.length ) {
    646                                                 old_widget_error.replaceWith( new_widget_error );
    647                                         } else if ( old_widget_error.length ) {
    648                                                 old_widget_error.remove();
    649                                         } else if ( new_widget_error.length ) {
    650                                                 control.container.find( '.widget-content' ).prepend( new_widget_error );
    651                                         }
    652                                 }
    653                         }
    654                 },
    655 
    656                 /**
    657                  * Trigger an 'action' which a specific widget type can handle
    658                  *
    659                  * @param name
    660                  */
    661                 hook: function ( name ) {
    662                         var args = Array.prototype.slice.call( arguments, 1 ), handler;
    663 
    664                         if ( this.hooks[this.params.widget_id_base] && this.hooks[this.params.widget_id_base][name] ) {
    665                                 handler = this.hooks[this.params.widget_id_base][name];
    666                         } else if ( this.hooks._default[name] ) {
    667                                 handler = this.hooks._default[name];
    668                         }
    669                         if ( handler ) {
    670                                 handler.apply( this, args );
    671                         }
    672662                },
    673663
    674664                /**
    var WidgetCustomizer = ( function ($) { 
    686676
    687677                        control._update_count = 0;
    688678                        control.is_widget_updating = false;
     679                        control.live_update_mode = true;
    689680
    690681                        // Update widget whenever model changes
    691682                        control.setting.bind( function( to, from ) {
    var WidgetCustomizer = ( function ($) { 
    972963                 */
    973964                _setupUpdateUI: function () {
    974965                        var control = this,
     966                                widget_root,
    975967                                widget_content,
    976968                                save_btn,
    977                                 update_widget_debounced;
     969                                update_widget_debounced,
     970                                form_update_event_handler;
    978971
    979                         widget_content = control.container.find( '.widget-content' );
     972                        widget_root = control.container.find( '.widget:first' );
     973                        widget_content = widget_root.find( '.widget-content:first' );
    980974
    981975                        // Configure update button
    982976                        save_btn = control.container.find( '.widget-control-save' );
    var WidgetCustomizer = ( function ($) { 
    985979                        save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' );
    986980                        save_btn.on( 'click', function ( e ) {
    987981                                e.preventDefault();
    988                                 control.updateWidget();
     982                                control.updateWidget( { disable_form: true } );
    989983                        } );
    990984
    991985                        update_widget_debounced = _.debounce( function () {
    var WidgetCustomizer = ( function ($) { 
    1003997
    1004998                        // Handle widgets that support live previews
    1005999                        widget_content.on( 'change input propertychange', ':input', function ( e ) {
    1006                                 if ( e.type === 'change' ) {
    1007                                         control.updateWidget();
    1008                                 } else if ( this.checkValidity && this.checkValidity() ) {
    1009                                         update_widget_debounced();
     1000                                if ( control.live_update_mode ) {
     1001                                        if ( e.type === 'change' ) {
     1002                                                control.updateWidget();
     1003                                        } else if ( this.checkValidity && this.checkValidity() ) {
     1004                                                update_widget_debounced();
     1005                                        }
    10101006                                }
    10111007                        } );
    10121008
    var WidgetCustomizer = ( function ($) { 
    10251021                                var is_rendered = !! rendered_widgets[control.params.widget_id];
    10261022                                control.container.toggleClass( 'widget-rendered', is_rendered );
    10271023                        } );
     1024
     1025                        form_update_event_handler = builtin_form_sync_handlers[ control.params.widget_id_base ];
     1026                        if ( form_update_event_handler ) {
     1027                                $( document ).on( 'widget-synced', function ( e, widget_el ) {
     1028                                        if ( widget_root.is( widget_el ) ) {
     1029                                                form_update_event_handler.apply( document, arguments );
     1030                                        }
     1031                                } );
     1032                        }
    10281033                },
    10291034
    10301035                /**
    var WidgetCustomizer = ( function ($) { 
    10821087                },
    10831088
    10841089                /**
     1090                 * Find all inputs in a widget container that should be considered when
     1091                 * comparing the loaded form with the sanitized form, whose fields will
     1092                 * be aligned to copy the sanitized over. The elements returned by this
     1093                 * are passed into this._getInputsSignature(), and they are iterated
     1094                 * over when copying sanitized values over to the the form loaded.
     1095                 *
     1096                 * @param {jQuery} container element in which to look for inputs
     1097                 * @returns {jQuery} inputs
     1098                 * @private
     1099                 */
     1100                _getInputs: function ( container ) {
     1101                        return $( container ).find( ':input[name]' );
     1102                },
     1103
     1104                /**
    10851105                 * Iterate over supplied inputs and create a signature string for all of them together.
    10861106                 * This string can be used to compare whether or not the form has all of the same fields.
    10871107                 *
    var WidgetCustomizer = ( function ($) { 
    10931113                        var inputs_signatures = _( inputs ).map( function ( input ) {
    10941114                                input = $( input );
    10951115                                var signature_parts;
    1096                                 if ( input.is( 'option' ) ) {
    1097                                         signature_parts = [ input.prop( 'nodeName' ), input.prop( 'value' ) ];
    1098                                 } else if ( input.is( ':checkbox, :radio' ) ) {
    1099                                         signature_parts = [ input.prop( 'type' ), input.attr( 'id' ), input.attr( 'name' ), input.prop( 'value' ) ];
     1116                                if ( input.is( ':checkbox, :radio' ) ) {
     1117                                        signature_parts = [ input.attr( 'id' ), input.attr( 'name' ), input.prop( 'value' ) ];
    11001118                                } else {
    1101                                         signature_parts = [ input.prop( 'nodeName' ), input.attr( 'id' ), input.attr( 'name' ), input.attr( 'type' ) ];
     1119                                        signature_parts = [ input.attr( 'id' ), input.attr( 'name' ) ];
    11021120                                }
    11031121                                return signature_parts.join( ',' );
    11041122                        } );
    var WidgetCustomizer = ( function ($) { 
    11161134                        input = $( input );
    11171135                        if ( input.is( ':radio, :checkbox' ) ) {
    11181136                                return 'checked';
    1119                         } else if ( input.is( 'option' ) ) {
    1120                                 return 'selected';
    11211137                        } else {
    11221138                                return 'value';
    11231139                        }
    var WidgetCustomizer = ( function ($) { 
    11541170                        var control = this,
    11551171                                instance_override,
    11561172                                complete_callback,
     1173                                widget_root,
    11571174                                update_number,
    11581175                                widget_content,
    1159                                 element_id_to_refocus = null,
    1160                                 active_input_selection_start = null,
    1161                                 active_input_selection_end = null,
    11621176                                params,
    11631177                                data,
    11641178                                inputs,
    11651179                                processing,
    1166                                 jqxhr;
     1180                                jqxhr,
     1181                                is_changed;
    11671182
    11681183                        args = $.extend( {
    11691184                                instance: null,
    var WidgetCustomizer = ( function ($) { 
    11771192                        control._update_count += 1;
    11781193                        update_number = control._update_count;
    11791194
    1180                         widget_content = control.container.find( '.widget-content' );
     1195                        widget_root = control.container.find( '.widget:first' );
     1196                        widget_content = widget_root.find( '.widget-content:first' );
    11811197
    11821198                        // Remove a previous error message
    11831199                        widget_content.find( '.widget-error' ).remove();
    11841200
    1185                         // @todo Support more selectors than IDs?
    1186                         if ( $.contains( control.container[0], document.activeElement ) && $( document.activeElement ).is( '[id]' ) ) {
    1187                                 element_id_to_refocus = $( document.activeElement ).prop( 'id' );
    1188                                 // @todo IE8 support: http://stackoverflow.com/a/4207763/93579
    1189                                 try {
    1190                                         active_input_selection_start = document.activeElement.selectionStart;
    1191                                         active_input_selection_end = document.activeElement.selectionEnd;
    1192                                 }
    1193                                 catch( e ) {} // catch InvalidStateError in case of checkboxes
    1194                         }
    1195 
    11961201                        control.container.addClass( 'widget-form-loading' );
    11971202                        control.container.addClass( 'previewer-loading' );
    11981203                        processing = wp.customize.state( 'processing' );
    11991204                        processing( processing() + 1 );
    12001205
     1206                        if ( ! control.live_update_mode ) {
     1207                                control.container.addClass( 'widget-form-disabled' );
     1208                        }
     1209
    12011210                        params = {};
    12021211                        params.action = 'update-widget';
    12031212                        params.wp_customize = 'on';
    12041213                        params.nonce = self.nonce;
    12051214
    12061215                        data = $.param( params );
    1207                         inputs = widget_content.find( ':input, option' );
     1216                        inputs = control._getInputs( widget_content );
    12081217
    12091218                        // Store the value we're submitting in data so that when the response comes back,
    12101219                        // we know if it got sanitized; if there is no difference in the sanitized value,
    var WidgetCustomizer = ( function ($) { 
    12271236                                        sanitized_form,
    12281237                                        sanitized_inputs,
    12291238                                        has_same_inputs_in_response,
    1230                                         is_instance_identical;
     1239                                        is_live_update_aborted = false;
    12311240
    12321241                                // Check if the user is logged out.
    12331242                                if ( '0' === r ) {
    var WidgetCustomizer = ( function ($) { 
    12471256
    12481257                                if ( r.success ) {
    12491258                                        sanitized_form = $( '<div>' + r.data.form + '</div>' );
    1250 
    1251                                         control.hook( 'formUpdate', sanitized_form );
    1252 
    1253                                         sanitized_inputs = sanitized_form.find( ':input, option' );
     1259                                        sanitized_inputs = control._getInputs( sanitized_form );
    12541260                                        has_same_inputs_in_response = control._getInputsSignature( inputs ) === control._getInputsSignature( sanitized_inputs );
    12551261
    1256                                         if ( has_same_inputs_in_response ) {
     1262                                        // Restore live update mode if sanitized fields are now aligned with the existing fields
     1263                                        if ( has_same_inputs_in_response && ! control.live_update_mode ) {
     1264                                                control.live_update_mode = true;
     1265                                                control.container.removeClass( 'widget-form-disabled' );
     1266                                                control.container.find( 'input[name="savewidget"]' ).hide();
     1267                                        }
     1268
     1269                                        // Sync sanitized field states to existing fields if they are aligned
     1270                                        if ( has_same_inputs_in_response && control.live_update_mode ) {
    12571271                                                inputs.each( function ( i ) {
    12581272                                                        var input = $( this ),
    12591273                                                                sanitized_input = $( sanitized_inputs[i] ),
    12601274                                                                property = control._getInputStatePropertyName( this ),
    1261                                                                 state,
    1262                                                                 sanitized_state;
     1275                                                                submitted_state,
     1276                                                                sanitized_state,
     1277                                                                can_update_state;
    12631278
    1264                                                         state = input.data( 'state' + update_number );
     1279                                                        submitted_state = input.data( 'state' + update_number );
    12651280                                                        sanitized_state = sanitized_input.prop( property );
    12661281                                                        input.data( 'sanitized', sanitized_state );
    12671282
    1268                                                         if ( state !== sanitized_state ) {
    1269 
    1270                                                                 // Only update now if not currently focused on it,
    1271                                                                 // so that we don't cause the cursor
    1272                                                                 // it will be updated upon the change event
    1273                                                                 if ( args.ignore_active_element || ! input.is( document.activeElement ) ) {
    1274                                                                         input.prop( property, sanitized_state );
    1275                                                                 }
    1276                                                                 control.hook( 'unsanitaryField', input, sanitized_state, state );
    1277 
    1278                                                         } else {
    1279                                                                 control.hook( 'sanitaryField', input, state );
     1283                                                        can_update_state = (
     1284                                                                submitted_state !== sanitized_state &&
     1285                                                                ( args.ignore_active_element || ! input.is( document.activeElement ) )
     1286                                                        );
     1287                                                        if ( can_update_state ) {
     1288                                                                input.prop( property, sanitized_state );
    12801289                                                        }
    12811290                                                } );
    1282                                                 control.hook( 'formUpdated', sanitized_form );
     1291                                                $( document ).trigger( 'widget-synced', [ widget_root, r.data.form ] );
     1292
     1293                                        // Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled
     1294                                        } else if ( control.live_update_mode ) {
     1295                                                control.live_update_mode = false;
     1296                                                control.container.find( 'input[name="savewidget"]' ).show();
     1297                                                is_live_update_aborted = true;
     1298                                        // Otherwise, replace existing form with the sanitized form
    12831299                                        } else {
    1284                                                 widget_content.html( sanitized_form.html() );
    1285                                                 if ( element_id_to_refocus ) {
    1286                                                         // not using jQuery selector so we don't have to worry about escaping IDs with brackets and other characters
    1287                                                         $( document.getElementById( element_id_to_refocus ) )
    1288                                                                 .prop( {
    1289                                                                         selectionStart: active_input_selection_start,
    1290                                                                         selectionEnd: active_input_selection_end
    1291                                                                 } )
    1292                                                                 .focus();
    1293                                                 }
    1294                                                 control.hook( 'formRefreshed' );
     1300                                                widget_content.html( r.data.form );
     1301                                                control.container.removeClass( 'widget-form-disabled' );
     1302                                                $( document ).trigger( 'widget-updated', [ widget_root ] );
    12951303                                        }
    12961304
    12971305                                        /**
    var WidgetCustomizer = ( function ($) { 
    12991307                                         * needing to be rendered, and so we can preempt the event for the
    13001308                                         * preview finishing loading.
    13011309                                         */
    1302                                         is_instance_identical = _( control.setting() ).isEqual( r.data.instance );
    1303                                         if ( ! is_instance_identical ) {
     1310                                        is_changed = ! is_live_update_aborted && ! _( control.setting() ).isEqual( r.data.instance );
     1311                                        if ( is_changed ) {
    13041312                                                control.is_widget_updating = true; // suppress triggering another updateWidget
    13051313                                                control.setting( r.data.instance );
    13061314                                                control.is_widget_updating = false;
    13071315                                        }
    13081316
    13091317                                        if ( complete_callback ) {
    1310                                                 complete_callback.call( control, null, { no_change: is_instance_identical, ajax_finished: true } );
     1318                                                complete_callback.call( control, null, { no_change: ! is_changed, ajax_finished: true } );
    13111319                                        }
    13121320                                } else {
    13131321                                        message = self.i18n.error;
  • src/wp-admin/js/widgets.js

    diff --git src/wp-admin/js/widgets.js src/wp-admin/js/widgets.js
    index 046907b..7390563 100644
    wpWidgets = { 
    170170
    171171                                        wpWidgets.save( $widget, 0, 0, 1 );
    172172                                        $widget.find('input.add_new').val('');
     173                                        $( document ).trigger( 'widget-added', [ $widget ] );
    173174                                }
    174175
    175176                                $sidebar = $widget.parent();
    wpWidgets = { 
    374375                        } else {
    375376                                $('.spinner').hide();
    376377                                if ( r && r.length > 2 ) {
    377                                         $( 'div.widget-content', widget ).html(r);
     378                                        $( 'div.widget-content', widget ).html( r );
    378379                                        wpWidgets.appendTitle( widget );
     380                                        $( document ).trigger( 'widget-updated', [ widget ] );
    379381                                }
    380382                        }
    381383                        if ( order ) {
    wpWidgets = { 
    440442                // No longer "new" widget
    441443                widget.find( 'input.add_new' ).val('');
    442444
     445                $( document ).trigger( 'widget-added', [ widget ] );
     446
    443447                /*
    444448                 * Check if any part of the sidebar is visible in the viewport. If it is, don't scroll.
    445449                 * Otherwise, scroll up to so the sidebar is in view.