diff --git src/wp-admin/css/customize-widgets.css src/wp-admin/css/customize-widgets.css
index 1142a47..a703059 100644
|
|
|
37 | 37 | .customize-control-widget_form.previewer-loading .spinner { |
38 | 38 | opacity: 1.0; |
39 | 39 | } |
| 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 | } |
40 | 48 | |
41 | 49 | .customize-control-widget_form .widget { |
42 | 50 | margin-bottom: 0; |
diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js
index 1307655..f439af7 100644
|
|
var WidgetCustomizer = ( function ($) { |
8 | 8 | Sidebar, |
9 | 9 | SidebarCollection, |
10 | 10 | OldPreviewer, |
| 11 | builtin_form_sync_handlers, |
11 | 12 | customize = wp.customize, self = { |
12 | 13 | nonce: null, |
13 | 14 | i18n: { |
… |
… |
var WidgetCustomizer = ( function ($) { |
135 | 136 | self.registered_sidebars = new SidebarCollection( self.registered_sidebars ); |
136 | 137 | |
137 | 138 | /** |
| 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 | /** |
138 | 165 | * On DOM ready, initialize some meta functionality independent of specific |
139 | 166 | * customizer controls. |
140 | 167 | */ |
… |
… |
var WidgetCustomizer = ( function ($) { |
480 | 507 | addWidget: function ( widget_id ) { |
481 | 508 | var control = this, |
482 | 509 | control_html, |
| 510 | widget_el, |
483 | 511 | customize_control_type = 'widget_form', |
484 | 512 | customize_control, |
485 | 513 | parsed_widget_id = parse_widget_id( widget_id ), |
… |
… |
var WidgetCustomizer = ( function ($) { |
514 | 542 | } else { |
515 | 543 | widget.set( 'is_disabled', true ); // Prevent single widget from being added again now |
516 | 544 | } |
| 545 | widget_el = $( control_html ); |
517 | 546 | |
518 | 547 | customize_control = $( '<li></li>' ); |
519 | 548 | customize_control.addClass( 'customize-control' ); |
520 | 549 | customize_control.addClass( 'customize-control-' + customize_control_type ); |
521 | | customize_control.append( $( control_html ) ); |
| 550 | customize_control.append( widget_el ); |
522 | 551 | customize_control.find( '> .widget-icon' ).remove(); |
523 | 552 | if ( widget.get( 'is_multi' ) ) { |
524 | 553 | customize_control.find( 'input[name="widget_number"]' ).val( widget_number ); |
… |
… |
var WidgetCustomizer = ( function ($) { |
604 | 633 | } |
605 | 634 | } ); |
606 | 635 | |
| 636 | $( document ).trigger( 'widget-added', [ widget_el ] ); |
| 637 | |
607 | 638 | return widget_form_control; |
608 | 639 | } |
609 | 640 | |
… |
… |
var WidgetCustomizer = ( function ($) { |
628 | 659 | control._setupHighlightEffects(); |
629 | 660 | control._setupUpdateUI(); |
630 | 661 | 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 | | } |
672 | 662 | }, |
673 | 663 | |
674 | 664 | /** |
… |
… |
var WidgetCustomizer = ( function ($) { |
686 | 676 | |
687 | 677 | control._update_count = 0; |
688 | 678 | control.is_widget_updating = false; |
| 679 | control.live_update_mode = true; |
689 | 680 | |
690 | 681 | // Update widget whenever model changes |
691 | 682 | control.setting.bind( function( to, from ) { |
… |
… |
var WidgetCustomizer = ( function ($) { |
972 | 963 | */ |
973 | 964 | _setupUpdateUI: function () { |
974 | 965 | var control = this, |
| 966 | widget_root, |
975 | 967 | widget_content, |
976 | 968 | save_btn, |
977 | | update_widget_debounced; |
| 969 | update_widget_debounced, |
| 970 | form_update_event_handler; |
978 | 971 | |
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' ); |
980 | 974 | |
981 | 975 | // Configure update button |
982 | 976 | save_btn = control.container.find( '.widget-control-save' ); |
… |
… |
var WidgetCustomizer = ( function ($) { |
985 | 979 | save_btn.removeClass( 'button-primary' ).addClass( 'button-secondary' ); |
986 | 980 | save_btn.on( 'click', function ( e ) { |
987 | 981 | e.preventDefault(); |
988 | | control.updateWidget(); |
| 982 | control.updateWidget( { disable_form: true } ); |
989 | 983 | } ); |
990 | 984 | |
991 | 985 | update_widget_debounced = _.debounce( function () { |
… |
… |
var WidgetCustomizer = ( function ($) { |
1003 | 997 | |
1004 | 998 | // Handle widgets that support live previews |
1005 | 999 | 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 | } |
1010 | 1006 | } |
1011 | 1007 | } ); |
1012 | 1008 | |
… |
… |
var WidgetCustomizer = ( function ($) { |
1025 | 1021 | var is_rendered = !! rendered_widgets[control.params.widget_id]; |
1026 | 1022 | control.container.toggleClass( 'widget-rendered', is_rendered ); |
1027 | 1023 | } ); |
| 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 | } |
1028 | 1033 | }, |
1029 | 1034 | |
1030 | 1035 | /** |
… |
… |
var WidgetCustomizer = ( function ($) { |
1082 | 1087 | }, |
1083 | 1088 | |
1084 | 1089 | /** |
| 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 | /** |
1085 | 1105 | * Iterate over supplied inputs and create a signature string for all of them together. |
1086 | 1106 | * This string can be used to compare whether or not the form has all of the same fields. |
1087 | 1107 | * |
… |
… |
var WidgetCustomizer = ( function ($) { |
1093 | 1113 | var inputs_signatures = _( inputs ).map( function ( input ) { |
1094 | 1114 | input = $( input ); |
1095 | 1115 | 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' ) ]; |
1100 | 1118 | } 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' ) ]; |
1102 | 1120 | } |
1103 | 1121 | return signature_parts.join( ',' ); |
1104 | 1122 | } ); |
… |
… |
var WidgetCustomizer = ( function ($) { |
1116 | 1134 | input = $( input ); |
1117 | 1135 | if ( input.is( ':radio, :checkbox' ) ) { |
1118 | 1136 | return 'checked'; |
1119 | | } else if ( input.is( 'option' ) ) { |
1120 | | return 'selected'; |
1121 | 1137 | } else { |
1122 | 1138 | return 'value'; |
1123 | 1139 | } |
… |
… |
var WidgetCustomizer = ( function ($) { |
1154 | 1170 | var control = this, |
1155 | 1171 | instance_override, |
1156 | 1172 | complete_callback, |
| 1173 | widget_root, |
1157 | 1174 | update_number, |
1158 | 1175 | widget_content, |
1159 | | element_id_to_refocus = null, |
1160 | | active_input_selection_start = null, |
1161 | | active_input_selection_end = null, |
1162 | 1176 | params, |
1163 | 1177 | data, |
1164 | 1178 | inputs, |
1165 | 1179 | processing, |
1166 | | jqxhr; |
| 1180 | jqxhr, |
| 1181 | is_changed; |
1167 | 1182 | |
1168 | 1183 | args = $.extend( { |
1169 | 1184 | instance: null, |
… |
… |
var WidgetCustomizer = ( function ($) { |
1177 | 1192 | control._update_count += 1; |
1178 | 1193 | update_number = control._update_count; |
1179 | 1194 | |
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' ); |
1181 | 1197 | |
1182 | 1198 | // Remove a previous error message |
1183 | 1199 | widget_content.find( '.widget-error' ).remove(); |
1184 | 1200 | |
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 | | |
1196 | 1201 | control.container.addClass( 'widget-form-loading' ); |
1197 | 1202 | control.container.addClass( 'previewer-loading' ); |
1198 | 1203 | processing = wp.customize.state( 'processing' ); |
1199 | 1204 | processing( processing() + 1 ); |
1200 | 1205 | |
| 1206 | if ( ! control.live_update_mode ) { |
| 1207 | control.container.addClass( 'widget-form-disabled' ); |
| 1208 | } |
| 1209 | |
1201 | 1210 | params = {}; |
1202 | 1211 | params.action = 'update-widget'; |
1203 | 1212 | params.wp_customize = 'on'; |
1204 | 1213 | params.nonce = self.nonce; |
1205 | 1214 | |
1206 | 1215 | data = $.param( params ); |
1207 | | inputs = widget_content.find( ':input, option' ); |
| 1216 | inputs = control._getInputs( widget_content ); |
1208 | 1217 | |
1209 | 1218 | // Store the value we're submitting in data so that when the response comes back, |
1210 | 1219 | // we know if it got sanitized; if there is no difference in the sanitized value, |
… |
… |
var WidgetCustomizer = ( function ($) { |
1227 | 1236 | sanitized_form, |
1228 | 1237 | sanitized_inputs, |
1229 | 1238 | has_same_inputs_in_response, |
1230 | | is_instance_identical; |
| 1239 | is_live_update_aborted = false; |
1231 | 1240 | |
1232 | 1241 | // Check if the user is logged out. |
1233 | 1242 | if ( '0' === r ) { |
… |
… |
var WidgetCustomizer = ( function ($) { |
1247 | 1256 | |
1248 | 1257 | if ( r.success ) { |
1249 | 1258 | 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 ); |
1254 | 1260 | has_same_inputs_in_response = control._getInputsSignature( inputs ) === control._getInputsSignature( sanitized_inputs ); |
1255 | 1261 | |
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 ) { |
1257 | 1271 | inputs.each( function ( i ) { |
1258 | 1272 | var input = $( this ), |
1259 | 1273 | sanitized_input = $( sanitized_inputs[i] ), |
1260 | 1274 | property = control._getInputStatePropertyName( this ), |
1261 | | state, |
1262 | | sanitized_state; |
| 1275 | submitted_state, |
| 1276 | sanitized_state, |
| 1277 | can_update_state; |
1263 | 1278 | |
1264 | | state = input.data( 'state' + update_number ); |
| 1279 | submitted_state = input.data( 'state' + update_number ); |
1265 | 1280 | sanitized_state = sanitized_input.prop( property ); |
1266 | 1281 | input.data( 'sanitized', sanitized_state ); |
1267 | 1282 | |
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 ); |
1280 | 1289 | } |
1281 | 1290 | } ); |
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 |
1283 | 1299 | } 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 ] ); |
1295 | 1303 | } |
1296 | 1304 | |
1297 | 1305 | /** |
… |
… |
var WidgetCustomizer = ( function ($) { |
1299 | 1307 | * needing to be rendered, and so we can preempt the event for the |
1300 | 1308 | * preview finishing loading. |
1301 | 1309 | */ |
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 ) { |
1304 | 1312 | control.is_widget_updating = true; // suppress triggering another updateWidget |
1305 | 1313 | control.setting( r.data.instance ); |
1306 | 1314 | control.is_widget_updating = false; |
1307 | 1315 | } |
1308 | 1316 | |
1309 | 1317 | 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 } ); |
1311 | 1319 | } |
1312 | 1320 | } else { |
1313 | 1321 | message = self.i18n.error; |
diff --git src/wp-admin/js/widgets.js src/wp-admin/js/widgets.js
index 046907b..7390563 100644
|
|
wpWidgets = { |
170 | 170 | |
171 | 171 | wpWidgets.save( $widget, 0, 0, 1 ); |
172 | 172 | $widget.find('input.add_new').val(''); |
| 173 | $( document ).trigger( 'widget-added', [ $widget ] ); |
173 | 174 | } |
174 | 175 | |
175 | 176 | $sidebar = $widget.parent(); |
… |
… |
wpWidgets = { |
374 | 375 | } else { |
375 | 376 | $('.spinner').hide(); |
376 | 377 | if ( r && r.length > 2 ) { |
377 | | $( 'div.widget-content', widget ).html(r); |
| 378 | $( 'div.widget-content', widget ).html( r ); |
378 | 379 | wpWidgets.appendTitle( widget ); |
| 380 | $( document ).trigger( 'widget-updated', [ widget ] ); |
379 | 381 | } |
380 | 382 | } |
381 | 383 | if ( order ) { |
… |
… |
wpWidgets = { |
440 | 442 | // No longer "new" widget |
441 | 443 | widget.find( 'input.add_new' ).val(''); |
442 | 444 | |
| 445 | $( document ).trigger( 'widget-added', [ widget ] ); |
| 446 | |
443 | 447 | /* |
444 | 448 | * Check if any part of the sidebar is visible in the viewport. If it is, don't scroll. |
445 | 449 | * Otherwise, scroll up to so the sidebar is in view. |