diff --git a/src/wp-admin/includes/widgets.php b/src/wp-admin/includes/widgets.php
index 356970f076..19fd39023c 100644
--- a/src/wp-admin/includes/widgets.php
+++ b/src/wp-admin/includes/widgets.php
@@ -181,6 +181,11 @@ function wp_widget_control( $sidebar_args ) {
$multi_number = isset($sidebar_args['_multi_num']) ? $sidebar_args['_multi_num'] : '';
$add_new = isset($sidebar_args['_add']) ? $sidebar_args['_add'] : '';
+ $before_form = isset( $sidebar_args['before_form'] ) ? $sidebar_args['before_form'] : '
';
+ $after_widget_content = isset( $sidebar_args['after_widget_content'] ) ? $sidebar_args['after_widget_content'] : '
';
+
$query_arg = array( 'editwidget' => $widget['id'] );
if ( $add_new ) {
$query_arg['addnew'] = 1;
@@ -225,14 +230,16 @@ function wp_widget_control( $sidebar_args ) {
diff --git a/src/wp-admin/js/customize-widgets.js b/src/wp-admin/js/customize-widgets.js
index 6a640e0efa..8edc02c12f 100644
--- a/src/wp-admin/js/customize-widgets.js
+++ b/src/wp-admin/js/customize-widgets.js
@@ -417,37 +417,104 @@
/**
* @since 4.1.0
*/
- initialize: function ( id, options ) {
+ initialize: function( id, options ) {
var control = this;
- api.Control.prototype.initialize.call( control, id, options );
- control.expanded = new api.Value();
+
+ control.widgetControlEmbedded = false;
+ control.widgetContentEmbedded = false;
+ control.expanded = new api.Value( false );
control.expandedArgumentsQueue = [];
- control.expanded.bind( function ( expanded ) {
+ control.expanded.bind( function( expanded ) {
var args = control.expandedArgumentsQueue.shift();
args = $.extend( {}, control.defaultExpandedArguments, args );
control.onChangeExpanded( expanded, args );
});
- control.expanded.set( false );
+
+ api.Control.prototype.initialize.call( control, id, options );
},
/**
- * Set up the control
+ * Set up the control.
+ *
+ * @since 3.9.0
*/
ready: function() {
- this._setupModel();
- this._setupWideWidget();
- this._setupControlToggle();
- this._setupWidgetTitle();
- this._setupReorderUI();
- this._setupHighlightEffects();
- this._setupUpdateUI();
- this._setupRemoveUI();
+ var control = this;
+
+ /*
+ * Embed a placeholder once the section is expanded. The full widget
+ * form content will be embedded once the control itself is expanded,
+ * and at this point the widget-added event will be triggered.
+ */
+ if ( ! control.section() ) {
+ control.embedWidgetControl();
+ } else {
+ api.section( control.section(), function( section ) {
+ var onExpanded = function( isExpanded ) {
+ if ( isExpanded ) {
+ control.embedWidgetControl();
+ section.expanded.unbind( onExpanded );
+ }
+ };
+ if ( section.expanded() ) {
+ onExpanded( true );
+ } else {
+ section.expanded.bind( onExpanded );
+ }
+ } );
+ }
+ },
+
+ /**
+ * Embed the .widget element inside the li container.
+ *
+ * @since 4.4.0
+ */
+ embedWidgetControl: function() {
+ var control = this, widgetControl;
+
+ if ( control.widgetControlEmbedded ) {
+ return;
+ }
+ control.widgetControlEmbedded = true;
+
+ widgetControl = $( control.params.widget_control );
+ control.container.append( widgetControl );
+
+ control._setupModel();
+ control._setupWideWidget();
+ control._setupControlToggle();
+
+ control._setupWidgetTitle();
+ control._setupReorderUI();
+ control._setupHighlightEffects();
+ control._setupUpdateUI();
+ control._setupRemoveUI();
+ },
+
+ /**
+ * Embed the actual widget form inside of .widget-content and finally trigger the widget-added event.
+ *
+ * @since 4.4.0
+ */
+ embedWidgetContent: function() {
+ var control = this, widgetContent;
+
+ control.embedWidgetControl();
+ if ( control.widgetContentEmbedded ) {
+ return;
+ }
+ control.widgetContentEmbedded = true;
+
+ widgetContent = $( control.params.widget_content );
+ control.container.find( '.widget-content:first' ).append( widgetContent );
/*
* Trigger widget-added event so that plugins can attach any event
* listeners and dynamic UI elements.
*/
- $( document ).trigger( 'widget-added', [ this.container.find( '.widget:first' ) ] );
+ $( document ).trigger( 'widget-added', [ control.container.find( '.widget:first' ) ] );
+
},
/**
@@ -1008,6 +1075,9 @@
var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent,
updateNumber, params, data, $inputs, processing, jqxhr, isChanged;
+ // The updateWidget logic requires that the form fields to be fully present.
+ self.embedWidgetContent();
+
args = $.extend( {
instance: null,
complete: null,
@@ -1255,6 +1325,11 @@
onChangeExpanded: function ( expanded, args ) {
var self = this, $widget, $inside, complete, prevComplete;
+ self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI.
+ if ( expanded ) {
+ self.embedWidgetContent();
+ }
+
// If the expanded state is unchanged only manipulate container expanded states
if ( args.unchanged ) {
if ( expanded ) {
diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php
index f105ab1860..b6ffa1a84c 100644
--- a/src/wp-includes/class-wp-customize-control.php
+++ b/src/wp-includes/class-wp-customize-control.php
@@ -1487,20 +1487,21 @@ class WP_Widget_Form_Customize_Control extends WP_Customize_Control {
public $height;
public $is_wide = false;
+ /**
+ * Gather control params for exporting to JavaScript.
+ *
+ * @global array $wp_registered_widgets
+ */
public function to_json() {
+ global $wp_registered_widgets;
+
parent::to_json();
$exported_properties = array( 'widget_id', 'widget_id_base', 'sidebar_id', 'width', 'height', 'is_wide' );
foreach ( $exported_properties as $key ) {
$this->json[ $key ] = $this->$key;
}
- }
- /**
- *
- * @global array $wp_registered_widgets
- */
- public function render_content() {
- global $wp_registered_widgets;
+ // Get the widget_control and widget_content.
require_once ABSPATH . '/wp-admin/includes/widgets.php';
$widget = $wp_registered_widgets[ $this->widget_id ];
@@ -1514,9 +1515,17 @@ class WP_Widget_Form_Customize_Control extends WP_Customize_Control {
);
$args = wp_list_widget_controls_dynamic_sidebar( array( 0 => $args, 1 => $widget['params'][0] ) );
- echo $this->manager->widgets->get_widget_control( $args );
+ $widget_control_parts = $this->manager->widgets->get_widget_control_parts( $args );
+
+ $this->json['widget_control'] = $widget_control_parts['control'];
+ $this->json['widget_content'] = $widget_control_parts['content'];
}
+ /**
+ * Override render_content to be no-op since content is exported via to_json for deferred embedding.
+ */
+ public function render_content() {}
+
/**
* Whether the current widget is rendered on the page.
*
diff --git a/src/wp-includes/class-wp-customize-widgets.php b/src/wp-includes/class-wp-customize-widgets.php
index 8569228e46..a003393298 100644
--- a/src/wp-includes/class-wp-customize-widgets.php
+++ b/src/wp-includes/class-wp-customize-widgets.php
@@ -898,21 +898,47 @@ final class WP_Customize_Widgets {
* @return string Widget control form HTML markup.
*/
public function get_widget_control( $args ) {
+ $args[0]['before_form'] = '
';
+ $args[0]['after_form'] = '
';
+ $args[0]['before_widget_content'] = '
';
+ $args[0]['after_widget_content'] = '
';
ob_start();
-
call_user_func_array( 'wp_widget_control', $args );
- $replacements = array(
- '