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. |