Customize: Require opt-in for selective refresh of widgets.

* Introduces `customize-selective-refresh-widgets` theme support feature and adds to themes.
* Introduces `customize_selective_refresh` arg for `WP_Widget::$widget_options` and adds to all core widgets.
* Remove `selective_refresh` from being a component that can be removed via `customize_loaded_components` filter.
* Add `WP_Customize_Widgets::get_selective_refreshable_widgets()` and `WP_Customize_Widgets::is_widget_selective_refreshable()`.
* Fix default `selector` for `Partial` instances.
* Implement and improve Masronry sidebar refresh logic in Twenty Thirteen and Twenty Fourteen, including preservation of initial widget position after refresh.
* Re-initialize ME.js when refreshing `Twenty_Fourteen_Ephemera_Widget`.

See #27355.
Fixes #35855.

Built from https://develop.svn.wordpress.org/trunk@37040


git-svn-id: http://core.svn.wordpress.org/trunk@37007 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter 2016-03-21 21:59:29 +00:00
parent 8e41746cb1
commit f3f84d2f21
36 changed files with 695 additions and 513 deletions

View File

@ -34,7 +34,7 @@
multi_number: null,
name: null,
id_base: null,
transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
transport: null,
params: [],
width: null,
height: null,
@ -1982,7 +1982,7 @@
isExistingWidget = api.has( settingId );
if ( ! isExistingWidget ) {
settingArgs = {
transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',
transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh',
previewer: this.setting.previewer
};
setting = api.create( settingId, settingId, '', settingArgs );

File diff suppressed because one or more lines are too long

View File

@ -226,6 +226,9 @@ function twentyeleven_setup() {
'description' => __( 'Hanoi Plant', 'twentyeleven' )
)
) );
// Indicate widget sidebars can use selective refresh in the Customizer.
add_theme_support( 'customize-selective-refresh-widgets' );
}
endif; // twentyeleven_setup

View File

@ -21,6 +21,7 @@ class Twenty_Eleven_Ephemera_Widget extends WP_Widget {
parent::__construct( 'widget_twentyeleven_ephemera', __( 'Twenty Eleven Ephemera', 'twentyeleven' ), array(
'classname' => 'widget_twentyeleven_ephemera',
'description' => __( 'Use this widget to list your recent Aside, Status, Quote, and Link posts', 'twentyeleven' ),
'customize_selective_refresh' => true,
) );
$this->alt_option_name = 'widget_twentyeleven_ephemera';

View File

@ -125,6 +125,9 @@ function twentyfifteen_setup() {
* specifically font, colors, icons, and column width.
*/
add_editor_style( array( 'css/editor-style.css', 'genericons/genericons.css', twentyfifteen_fonts_url() ) );
// Indicate widget sidebars can use selective refresh in the Customizer.
add_theme_support( 'customize-selective-refresh-widgets' );
}
endif; // twentyfifteen_setup
add_action( 'after_setup_theme', 'twentyfifteen_setup' );

View File

@ -113,6 +113,9 @@ function twentyfourteen_setup() {
// This theme uses its own gallery styles.
add_filter( 'use_default_gallery_style', '__return_false' );
// Indicate widget sidebars can use selective refresh in the Customizer.
add_theme_support( 'customize-selective-refresh-widgets' );
}
endif; // twentyfourteen_setup
add_action( 'after_setup_theme', 'twentyfourteen_setup' );

View File

@ -34,7 +34,28 @@ class Twenty_Fourteen_Ephemera_Widget extends WP_Widget {
parent::__construct( 'widget_twentyfourteen_ephemera', __( 'Twenty Fourteen Ephemera', 'twentyfourteen' ), array(
'classname' => 'widget_twentyfourteen_ephemera',
'description' => __( 'Use this widget to list your recent Aside, Quote, Video, Audio, Image, Gallery, and Link posts.', 'twentyfourteen' ),
'customize_selective_refresh' => true,
) );
if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
}
/**
* Enqueue scripts.
*
* @since Twenty Fourteen 1.7
*/
public function enqueue_scripts() {
/** This filter is documented in wp-includes/media.php */
$audio_library = apply_filters( 'wp_audio_shortcode_library', 'mediaelement' );
/** This filter is documented in wp-includes/media.php */
$video_library = apply_filters( 'wp_video_shortcode_library', 'mediaelement' );
if ( in_array( 'mediaelement', array( $video_library, $audio_library ), true ) ) {
wp_enqueue_style( 'wp-mediaelement' );
wp_enqueue_script( 'wp-mediaelement' );
}
}
/**

View File

@ -146,9 +146,13 @@
} );
_window.load( function() {
var footerSidebar,
isCustomizeSelectiveRefresh = ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh );
// Arrange footer widgets vertically.
if ( $.isFunction( $.fn.masonry ) ) {
$( '#footer-sidebar' ).masonry( {
footerSidebar = $( '#footer-sidebar' );
footerSidebar.masonry( {
itemSelector: '.widget',
columnWidth: function( containerWidth ) {
return containerWidth / 4;
@ -157,6 +161,41 @@
isResizable: true,
isRTL: $( 'body' ).is( '.rtl' )
} );
if ( isCustomizeSelectiveRefresh ) {
// Retain previous masonry-brick initial position.
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
var copyPosition = (
placement.partial.extended( wp.customize.widgetsPreview.WidgetPartial ) &&
placement.removedNodes instanceof jQuery &&
placement.removedNodes.is( '.masonry-brick' ) &&
placement.container instanceof jQuery
);
if ( copyPosition ) {
placement.container.css( {
position: placement.removedNodes.css( 'position' ),
top: placement.removedNodes.css( 'top' ),
left: placement.removedNodes.css( 'left' )
} );
}
} );
// Re-arrange footer widgets after selective refresh event.
wp.customize.selectiveRefresh.bind( 'sidebar-updated', function( sidebarPartial ) {
if ( 'sidebar-3' === sidebarPartial.sidebarId ) {
footerSidebar.masonry( 'reloadItems' );
footerSidebar.masonry( 'layout' );
}
} );
}
}
// Initialize audio and video players in Twenty_Fourteen_Ephemera_Widget widget when selectively refreshed in Customizer.
if ( isCustomizeSelectiveRefresh && wp.mediaelement ) {
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function() {
wp.mediaelement.initialize();
} );
}
// Initialize Featured Content slider.

View File

@ -105,6 +105,9 @@ function twentythirteen_setup() {
// This theme uses its own gallery styles.
add_filter( 'use_default_gallery_style', '__return_false' );
// Indicate widget sidebars can use selective refresh in the Customizer.
add_theme_support( 'customize-selective-refresh-widgets' );
}
add_action( 'after_setup_theme', 'twentythirteen_setup' );

View File

@ -120,13 +120,42 @@
* Arranges footer widgets vertically.
*/
if ( $.isFunction( $.fn.masonry ) ) {
var columnWidth = body.is( '.sidebar' ) ? 228 : 245;
var columnWidth = body.is( '.sidebar' ) ? 228 : 245,
widgetArea = $( '#secondary .widget-area' );
$( '#secondary .widget-area' ).masonry( {
widgetArea.masonry( {
itemSelector: '.widget',
columnWidth: columnWidth,
gutterWidth: 20,
isRTL: body.is( '.rtl' )
} );
if ( 'undefined' !== typeof wp && wp.customize && wp.customize.selectiveRefresh ) {
// Retain previous masonry-brick initial position.
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
var copyPosition = (
placement.partial.extended( wp.customize.widgetsPreview.WidgetPartial ) &&
placement.removedNodes instanceof jQuery &&
placement.removedNodes.is( '.masonry-brick' ) &&
placement.container instanceof jQuery
);
if ( copyPosition ) {
placement.container.css( {
position: placement.removedNodes.css( 'position' ),
top: placement.removedNodes.css( 'top' ),
left: placement.removedNodes.css( 'left' )
} );
}
} );
// Re-arrange footer widgets when sidebar is updated via selective refresh in the Customizer.
wp.customize.selectiveRefresh.bind( 'sidebar-updated', function( sidebarPartial ) {
if ( 'sidebar-1' === sidebarPartial.sidebarId ) {
widgetArea.masonry( 'reloadItems' );
widgetArea.masonry( 'layout' );
}
} );
}
}
} )( jQuery );

View File

@ -38,16 +38,4 @@
}
} );
} );
if ( wp.customize.selectiveRefresh ) {
wp.customize.selectiveRefresh.bind( 'sidebar-updated', function( sidebarPartial ) {
var widgetArea;
if ( 'sidebar-1' === sidebarPartial.sidebarId && $.isFunction( $.fn.masonry ) ) {
widgetArea = $( '#secondary .widget-area' );
widgetArea.masonry( 'destroy' );
widgetArea.masonry();
}
} );
}
} )( jQuery );

View File

@ -74,6 +74,9 @@ function twentytwelve_setup() {
// This theme uses a custom image size for featured images, displayed on "standard" posts.
add_theme_support( 'post-thumbnails' );
set_post_thumbnail_size( 624, 9999 ); // Unlimited height, soft crop
// Indicate widget sidebars can use selective refresh in the Customizer.
add_theme_support( 'customize-selective-refresh-widgets' );
}
add_action( 'after_setup_theme', 'twentytwelve_setup' );

View File

@ -109,7 +109,7 @@ final class WP_Customize_Manager {
* @access protected
* @var array
*/
protected $components = array( 'widgets', 'nav_menus', 'selective_refresh' );
protected $components = array( 'widgets', 'nav_menus' );
/**
* Registered instances of WP_Customize_Section.
@ -258,6 +258,9 @@ final class WP_Customize_Manager {
*/
$components = apply_filters( 'customize_loaded_components', $this->components, $this );
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
$this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
if ( in_array( 'widgets', $components, true ) ) {
require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' );
$this->widgets = new WP_Customize_Widgets( $this );
@ -268,11 +271,6 @@ final class WP_Customize_Manager {
$this->nav_menus = new WP_Customize_Nav_Menus( $this );
}
if ( in_array( 'selective_refresh', $components, true ) ) {
require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );
$this->selective_refresh = new WP_Customize_Selective_Refresh( $this );
}
add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) );
add_action( 'setup_theme', array( $this, 'setup_theme' ) );
@ -1730,7 +1728,6 @@ final class WP_Customize_Manager {
'autofocus' => $this->get_autofocus(),
'documentTitleTmpl' => $this->get_document_title_template(),
'previewableDevices' => $this->get_previewable_devices(),
'selectiveRefreshEnabled' => isset( $this->selective_refresh ),
);
// Prepare Customize Section objects to pass to JavaScript.
@ -1978,14 +1975,12 @@ final class WP_Customize_Manager {
),
) ) );
if ( isset( $this->selective_refresh ) ) {
$this->selective_refresh->add_partial( 'custom_logo', array(
'settings' => array( 'custom_logo' ),
'selector' => '.custom-logo-link',
'render_callback' => array( $this, '_render_custom_logo_partial' ),
'container_inclusive' => true,
) );
}
$this->selective_refresh->add_partial( 'site_logo', array(
'settings' => array( 'site_logo' ),
'selector' => '.site-logo-link',
'render_callback' => array( $this, '_render_site_logo_partial' ),
'container_inclusive' => true,
) );
/* Colors */

View File

@ -393,7 +393,7 @@ final class WP_Customize_Nav_Menus {
'reorderLabelOn' => esc_attr__( 'Reorder menu items' ),
'reorderLabelOff' => esc_attr__( 'Close reorder mode' ),
),
'settingTransport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'settingTransport' => 'postMessage',
'phpIntMax' => PHP_INT_MAX,
'defaultSettingValues' => array(
'nav_menu' => $temp_nav_menu_setting->default,
@ -445,12 +445,12 @@ final class WP_Customize_Nav_Menus {
if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) {
$setting_args = array(
'type' => WP_Customize_Nav_Menu_Setting::TYPE,
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => 'postMessage',
);
} elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) {
$setting_args = array(
'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE,
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => 'postMessage',
);
}
return $setting_args;
@ -535,7 +535,7 @@ final class WP_Customize_Nav_Menus {
$setting = $this->manager->get_setting( $setting_id );
if ( $setting ) {
$setting->transport = isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh';
$setting->transport = 'postMessage';
remove_filter( "customize_sanitize_{$setting_id}", 'absint' );
add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) );
} else {
@ -543,7 +543,7 @@ final class WP_Customize_Nav_Menus {
'sanitize_callback' => array( $this, 'intval_base10' ),
'theme_supports' => 'menus',
'type' => 'theme_mod',
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => 'postMessage',
'default' => 0,
) );
}
@ -570,7 +570,7 @@ final class WP_Customize_Nav_Menus {
$nav_menu_setting_id = 'nav_menu[' . $menu_id . ']';
$this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id, array(
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => 'postMessage',
) ) );
// Add the menu contents.
@ -585,7 +585,7 @@ final class WP_Customize_Nav_Menus {
$value['nav_menu_term_id'] = $menu_id;
$this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id, array(
'value' => $value,
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => 'postMessage',
) ) );
// Create a control for each menu item.
@ -988,11 +988,6 @@ final class WP_Customize_Nav_Menus {
* @access public
*/
public function customize_preview_enqueue_deps() {
if ( isset( $this->manager->selective_refresh ) ) {
$script = wp_scripts()->registered['customize-preview-nav-menus'];
$script->deps[] = 'customize-selective-refresh';
}
wp_enqueue_script( 'customize-preview-nav-menus' ); // Note that we have overridden this.
wp_enqueue_style( 'customize-preview' );
}

View File

@ -61,6 +61,15 @@ final class WP_Customize_Widgets {
*/
protected $old_sidebars_widgets = array();
/**
* Mapping of widget ID base to whether it supports selective refresh.
*
* @since 4.5.0
* @access protected
* @var array
*/
protected $selective_refreshable_widgets;
/**
* Mapping of setting type to setting ID pattern.
*
@ -69,8 +78,8 @@ final class WP_Customize_Widgets {
* @var array
*/
protected $setting_id_patterns = array(
'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
'widget_instance' => '/^widget_(?P<id_base>.+?)(?:\[(?P<widget_number>\d+)\])?$/',
'sidebar_widgets' => '/^sidebars_widgets\[(?P<sidebar_id>.+?)\]$/',
);
/**
@ -111,6 +120,46 @@ final class WP_Customize_Widgets {
add_action( 'customize_preview_init', array( $this, 'selective_refresh_init' ) );
}
/**
* List whether each registered widget can be use selective refresh.
*
* If the theme does not support the customize-selective-refresh-widgets feature,
* then this will always return an empty array.
*
* @since 4.5.0
* @access public
*
* @return array Mapping of id_base to support. If theme doesn't support
* selective refresh, an empty array is returned.
*/
public function get_selective_refreshable_widgets() {
global $wp_widget_factory;
if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
return array();
}
if ( ! isset( $this->selective_refreshable_widgets ) ) {
$this->selective_refreshable_widgets = array();
foreach ( $wp_widget_factory->widgets as $wp_widget ) {
$this->selective_refreshable_widgets[ $wp_widget->id_base ] = ! empty( $wp_widget->widget_options['customize_selective_refresh'] );
}
}
return $this->selective_refreshable_widgets;
}
/**
* Determines if a widget supports selective refresh.
*
* @since 4.5.0
* @access public
*
* @param string $id_base Widget ID Base.
* @return bool Whether the widget can be selective refreshed.
*/
public function is_widget_selective_refreshable( $id_base ) {
$selective_refreshable_widgets = $this->get_selective_refreshable_widgets();
return ! empty( $selective_refreshable_widgets[ $id_base ] );
}
/**
* Retrieves the widget setting type given a setting ID.
*
@ -119,7 +168,7 @@ final class WP_Customize_Widgets {
*
* @staticvar array $cache
*
* @param $setting_id Setting ID.
* @param string $setting_id Setting ID.
* @return string|void Setting type.
*/
protected function get_setting_type( $setting_id ) {
@ -690,7 +739,7 @@ final class WP_Customize_Widgets {
'widgetReorderNav' => $widget_reorder_nav_tpl,
'moveWidgetArea' => $move_widget_area_tpl,
),
'selectiveRefresh' => isset( $this->manager->selective_refresh ),
'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
);
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
@ -771,16 +820,17 @@ final class WP_Customize_Widgets {
$args = array(
'type' => 'option',
'capability' => 'edit_theme_options',
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'default' => array(),
);
if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
$args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
$args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
$args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets' ) ? 'postMessage' : 'refresh';
} elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
$args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
$args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
$args['transport'] = $this->is_widget_selective_refreshable( $matches['id_base'] ) ? 'postMessage' : 'refresh';
}
$args = array_merge( $args, $overrides );
@ -893,7 +943,7 @@ final class WP_Customize_Widgets {
'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
'is_disabled' => $is_disabled,
'id_base' => $id_base,
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
'transport' => $this->is_widget_selective_refreshable( $id_base ) ? 'postMessage' : 'refresh',
'width' => $wp_registered_widget_controls[$widget['id']]['width'],
'height' => $wp_registered_widget_controls[$widget['id']]['height'],
'is_wide' => $this->is_wide_widget( $widget['id'] ),
@ -1025,6 +1075,7 @@ final class WP_Customize_Widgets {
*/
public function customize_preview_enqueue() {
wp_enqueue_script( 'customize-preview-widgets' );
wp_enqueue_style( 'customize-preview' );
}
/**
@ -1060,6 +1111,7 @@ final class WP_Customize_Widgets {
*/
public function export_preview_data() {
global $wp_registered_sidebars, $wp_registered_widgets;
// Prepare Customizer settings to pass to JavaScript.
$settings = array(
'renderedSidebars' => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
@ -1069,7 +1121,7 @@ final class WP_Customize_Widgets {
'l10n' => array(
'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
),
'selectiveRefresh' => isset( $this->manager->selective_refresh ),
'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
);
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
unset( $registered_widget['callback'] ); // may not be JSON-serializeable
@ -1479,6 +1531,9 @@ final class WP_Customize_Widgets {
* @return array (Maybe) modified partial arguments.
*/
public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
return $partial_args;
}
if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) {
if ( false === $partial_args ) {
@ -1506,33 +1561,15 @@ final class WP_Customize_Widgets {
* @access public
*/
public function selective_refresh_init() {
if ( ! isset( $this->manager->selective_refresh ) ) {
if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
return;
}
add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
}
/**
* Enqueues scripts for the Customizer preview.
*
* @since 4.5.0
* @access public
*/
public function customize_preview_enqueue_deps() {
if ( isset( $this->manager->selective_refresh ) ) {
$script = wp_scripts()->registered['customize-preview-widgets'];
$script->deps[] = 'customize-selective-refresh';
}
wp_enqueue_script( 'customize-preview-widgets' );
wp_enqueue_style( 'customize-preview' );
}
/**
* Inject selective refresh data attributes into widget container elements.
*

View File

@ -155,8 +155,8 @@ class WP_Widget {
$this->id_base = empty($id_base) ? preg_replace( '/(wp_)?widget_/', '', strtolower(get_class($this)) ) : strtolower($id_base);
$this->name = $name;
$this->option_name = 'widget_' . $this->id_base;
$this->widget_options = wp_parse_args( $widget_options, array('classname' => $this->option_name) );
$this->control_options = wp_parse_args( $control_options, array('id_base' => $this->id_base) );
$this->widget_options = wp_parse_args( $widget_options, array( 'classname' => $this->option_name, 'customize_selective_refresh' => false ) );
$this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) );
}
/**

View File

@ -12,7 +12,8 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
preview: null,
l10n: {
widgetTooltip: ''
}
},
selectiveRefreshableWidgets: {}
};
/**
@ -24,7 +25,7 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
var self = this;
self.preview = api.preview;
if ( api.selectiveRefresh ) {
if ( ! _.isEmpty( self.selectiveRefreshableWidgets ) ) {
self.addPartials();
}
@ -38,455 +39,467 @@ wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function(
} );
};
if ( api.selectiveRefresh ) {
/**
* Partial representing a widget instance.
*
* @class
* @augments wp.customize.selectiveRefresh.Partial
* @since 4.5.0
*/
self.WidgetPartial = api.selectiveRefresh.Partial.extend({
/**
* Partial representing a widget instance.
* Constructor.
*
* @class
* @augments wp.customize.selectiveRefresh.Partial
* @since 4.5.0
* @param {string} id - Partial ID.
* @param {Object} options
* @param {Object} options.params
*/
self.WidgetPartial = api.selectiveRefresh.Partial.extend({
/**
* Constructor.
*
* @since 4.5.0
* @param {string} id - Partial ID.
* @param {Object} options
* @param {Object} options.params
*/
initialize: function( id, options ) {
var partial = this, matches;
matches = id.match( /^widget\[(.+)]$/ );
if ( ! matches ) {
throw new Error( 'Illegal id for widget partial.' );
}
partial.widgetId = matches[1];
options = options || {};
options.params = _.extend(
{
/* Note that a selector of ('#' + partial.widgetId) is faster, but jQuery will only return the one result. */
selector: '[id="' + partial.widgetId + '"]', // Alternatively, '[data-customize-widget-id="' + partial.widgetId + '"]'
settings: [ self.getWidgetSettingId( partial.widgetId ) ],
containerInclusive: true
},
options.params || {}
);
api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
},
/**
* Send widget-updated message to parent so spinner will get removed from widget control.
*
* @inheritdoc
* @param {wp.customize.selectiveRefresh.Placement} placement
*/
renderContent: function( placement ) {
var partial = this;
if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
api.preview.send( 'widget-updated', partial.widgetId );
api.selectiveRefresh.trigger( 'widget-updated', partial );
}
initialize: function( id, options ) {
var partial = this, matches;
matches = id.match( /^widget\[(.+)]$/ );
if ( ! matches ) {
throw new Error( 'Illegal id for widget partial.' );
}
});
partial.widgetId = matches[1];
partial.widgetIdParts = self.parseWidgetId( partial.widgetId );
options = options || {};
options.params = _.extend(
{
settings: [ self.getWidgetSettingId( partial.widgetId ) ],
containerInclusive: true
},
options.params || {}
);
api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
},
/**
* Partial representing a widget area.
* Refresh widget partial.
*
* @returns {Promise}
*/
refresh: function() {
var partial = this, refreshDeferred;
if ( ! self.selectiveRefreshableWidgets[ partial.widgetIdParts.idBase ] ) {
refreshDeferred = $.Deferred();
refreshDeferred.reject();
partial.fallback();
return refreshDeferred.promise();
} else {
return api.selectiveRefresh.Partial.prototype.refresh.call( partial );
}
},
/**
* Send widget-updated message to parent so spinner will get removed from widget control.
*
* @inheritdoc
* @param {wp.customize.selectiveRefresh.Placement} placement
*/
renderContent: function( placement ) {
var partial = this;
if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) {
api.preview.send( 'widget-updated', partial.widgetId );
api.selectiveRefresh.trigger( 'widget-updated', partial );
}
}
});
/**
* Partial representing a widget area.
*
* @class
* @augments wp.customize.selectiveRefresh.Partial
* @since 4.5.0
*/
self.SidebarPartial = api.selectiveRefresh.Partial.extend({
/**
* Constructor.
*
* @since 4.5.0
* @param {string} id - Partial ID.
* @param {Object} options
* @param {Object} options.params
*/
initialize: function( id, options ) {
var partial = this, matches;
matches = id.match( /^sidebar\[(.+)]$/ );
if ( ! matches ) {
throw new Error( 'Illegal id for sidebar partial.' );
}
partial.sidebarId = matches[1];
options = options || {};
options.params = _.extend(
{
settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
},
options.params || {}
);
api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
if ( ! partial.params.sidebarArgs ) {
throw new Error( 'The sidebarArgs param was not provided.' );
}
if ( partial.params.settings.length > 1 ) {
throw new Error( 'Expected SidebarPartial to only have one associated setting' );
}
},
/**
* Set up the partial.
*
* @class
* @augments wp.customize.selectiveRefresh.Partial
* @since 4.5.0
*/
self.SidebarPartial = api.selectiveRefresh.Partial.extend({
ready: function() {
var sidebarPartial = this;
/**
* Constructor.
*
* @since 4.5.0
* @param {string} id - Partial ID.
* @param {Object} options
* @param {Object} options.params
*/
initialize: function( id, options ) {
var partial = this, matches;
matches = id.match( /^sidebar\[(.+)]$/ );
if ( ! matches ) {
throw new Error( 'Illegal id for sidebar partial.' );
}
partial.sidebarId = matches[1];
// Watch for changes to the sidebar_widgets setting.
_.each( sidebarPartial.settings(), function( settingId ) {
api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
} );
options = options || {};
options.params = _.extend(
{
settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ]
},
options.params || {}
// Trigger an event for this sidebar being updated whenever a widget inside is rendered.
api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
var isAssignedWidgetPartial = (
placement.partial.extended( self.WidgetPartial ) &&
( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
);
api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options );
if ( ! partial.params.sidebarArgs ) {
throw new Error( 'The sidebarArgs param was not provided.' );
}
if ( partial.params.settings.length > 1 ) {
throw new Error( 'Expected SidebarPartial to only have one associated setting' );
}
},
/**
* Set up the partial.
*
* @since 4.5.0
*/
ready: function() {
var sidebarPartial = this;
// Watch for changes to the sidebar_widgets setting.
_.each( sidebarPartial.settings(), function( settingId ) {
api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) );
} );
// Trigger an event for this sidebar being updated whenever a widget inside is rendered.
api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
var isAssignedWidgetPartial = (
placement.partial.extended( self.WidgetPartial ) &&
( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) )
);
if ( isAssignedWidgetPartial ) {
api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
}
} );
// Make sure that a widget partial has a container in the DOM prior to a refresh.
api.bind( 'change', function( widgetSetting ) {
var widgetId, parsedId;
parsedId = self.parseWidgetSettingId( widgetSetting.id );
if ( ! parsedId ) {
return;
}
widgetId = parsedId.idBase;
if ( parsedId.number ) {
widgetId += '-' + String( parsedId.number );
}
if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
sidebarPartial.ensureWidgetPlacementContainers( widgetId );
}
} );
},
/**
* Get the before/after boundary nodes for all instances of this sidebar (usually one).
*
* Note that TreeWalker is not implemented in IE8.
*
* @since 4.5.0
* @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
*/
findDynamicSidebarBoundaryNodes: function() {
var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
recursiveCommentTraversal = function( childNodes ) {
_.each( childNodes, function( node ) {
var matches;
if ( 8 === node.nodeType ) {
matches = node.nodeValue.match( regExp );
if ( ! matches || matches[2] !== partial.sidebarId ) {
return;
}
if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
boundaryNodes[ matches[3] ] = {
before: null,
after: null,
instanceNumber: parseInt( matches[3], 10 )
};
}
if ( 'dynamic_sidebar_before' === matches[1] ) {
boundaryNodes[ matches[3] ].before = node;
} else {
boundaryNodes[ matches[3] ].after = node;
}
} else if ( 1 === node.nodeType ) {
recursiveCommentTraversal( node.childNodes );
}
} );
};
recursiveCommentTraversal( document.body.childNodes );
return _.values( boundaryNodes );
},
/**
* Get the placements for this partial.
*
* @since 4.5.0
* @returns {Array}
*/
placements: function() {
var partial = this;
return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
return new api.selectiveRefresh.Placement( {
partial: partial,
container: null,
startNode: boundaryNodes.before,
endNode: boundaryNodes.after,
context: {
instanceNumber: boundaryNodes.instanceNumber
}
} );
} );
},
/**
* Get the list of widget IDs associated with this widget area.
*
* @since 4.5.0
*
* @returns {Array}
*/
getWidgetIds: function() {
var sidebarPartial = this, settingId, widgetIds;
settingId = sidebarPartial.settings()[0];
if ( ! settingId ) {
throw new Error( 'Missing associated setting.' );
}
if ( ! api.has( settingId ) ) {
throw new Error( 'Setting does not exist.' );
}
widgetIds = api( settingId ).get();
if ( ! _.isArray( widgetIds ) ) {
throw new Error( 'Expected setting to be array of widget IDs' );
}
return widgetIds.slice( 0 );
},
/**
* Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
*
* @since 4.5.0
*
* @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
*/
reflowWidgets: function() {
var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
widgetIds = sidebarPartial.getWidgetIds();
sidebarPlacements = sidebarPartial.placements();
widgetPartials = {};
_.each( widgetIds, function( widgetId ) {
var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
if ( widgetPartial ) {
widgetPartials[ widgetId ] = widgetPartial;
}
} );
_.each( sidebarPlacements, function( sidebarPlacement ) {
var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
// Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
_.each( widgetPartials, function( widgetPartial ) {
_.each( widgetPartial.placements(), function( widgetPlacement ) {
if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
thisPosition = widgetPlacement.container.index();
sidebarWidgets.push( {
partial: widgetPartial,
placement: widgetPlacement,
position: thisPosition
} );
if ( thisPosition < lastPosition ) {
needsSort = true;
}
lastPosition = thisPosition;
}
} );
} );
if ( needsSort ) {
_.each( sidebarWidgets, function( sidebarWidget ) {
sidebarPlacement.endNode.parentNode.insertBefore(
sidebarWidget.placement.container[0],
sidebarPlacement.endNode
);
// @todo Rename partial-placement-moved?
api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
} );
sortedSidebarContainers.push( sidebarPlacement );
}
} );
if ( sortedSidebarContainers.length > 0 ) {
if ( isAssignedWidgetPartial ) {
api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
}
} );
return sortedSidebarContainers;
},
/**
* Make sure there is a widget instance container in this sidebar for the given widget ID.
*
* @since 4.5.0
*
* @param {string} widgetId
* @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
*/
ensureWidgetPlacementContainers: function( widgetId ) {
var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
widgetPartial = api.selectiveRefresh.partial( partialId );
if ( ! widgetPartial ) {
widgetPartial = new self.WidgetPartial( partialId, {
params: {}
} );
api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
// Make sure that a widget partial has a container in the DOM prior to a refresh.
api.bind( 'change', function( widgetSetting ) {
var widgetId, parsedId;
parsedId = self.parseWidgetSettingId( widgetSetting.id );
if ( ! parsedId ) {
return;
}
widgetId = parsedId.idBase;
if ( parsedId.number ) {
widgetId += '-' + String( parsedId.number );
}
if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) {
sidebarPartial.ensureWidgetPlacementContainers( widgetId );
}
} );
},
// Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
_.each( sidebarPartial.placements(), function( sidebarPlacement ) {
var foundWidgetPlacement, widgetContainerElement;
foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
} );
if ( foundWidgetPlacement ) {
return;
/**
* Get the before/after boundary nodes for all instances of this sidebar (usually one).
*
* Note that TreeWalker is not implemented in IE8.
*
* @since 4.5.0
* @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>}
*/
findDynamicSidebarBoundaryNodes: function() {
var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal;
regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/;
recursiveCommentTraversal = function( childNodes ) {
_.each( childNodes, function( node ) {
var matches;
if ( 8 === node.nodeType ) {
matches = node.nodeValue.match( regExp );
if ( ! matches || matches[2] !== partial.sidebarId ) {
return;
}
if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) {
boundaryNodes[ matches[3] ] = {
before: null,
after: null,
instanceNumber: parseInt( matches[3], 10 )
};
}
if ( 'dynamic_sidebar_before' === matches[1] ) {
boundaryNodes[ matches[3] ].before = node;
} else {
boundaryNodes[ matches[3] ].after = node;
}
} else if ( 1 === node.nodeType ) {
recursiveCommentTraversal( node.childNodes );
}
} );
};
widgetContainerElement = $(
sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) +
sidebarPartial.params.sidebarArgs.after_widget
);
recursiveCommentTraversal( document.body.childNodes );
return _.values( boundaryNodes );
},
widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
/**
* Get the placements for this partial.
*
* @since 4.5.0
* @returns {Array}
*/
placements: function() {
var partial = this;
return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) {
return new api.selectiveRefresh.Placement( {
partial: partial,
container: null,
startNode: boundaryNodes.before,
endNode: boundaryNodes.after,
context: {
instanceNumber: boundaryNodes.instanceNumber
}
} );
} );
},
/*
* Make sure the widget container element has the customize-container context data.
* The sidebar_instance_number is used to disambiguate multiple instances of the
* same sidebar are rendered onto the template, and so the same widget is embedded
* multiple times.
*/
widgetContainerElement.data( 'customize-partial-placement-context', {
'sidebar_id': sidebarPartial.sidebarId,
'sidebar_instance_number': sidebarPlacement.context.instanceNumber
/**
* Get the list of widget IDs associated with this widget area.
*
* @since 4.5.0
*
* @returns {Array}
*/
getWidgetIds: function() {
var sidebarPartial = this, settingId, widgetIds;
settingId = sidebarPartial.settings()[0];
if ( ! settingId ) {
throw new Error( 'Missing associated setting.' );
}
if ( ! api.has( settingId ) ) {
throw new Error( 'Setting does not exist.' );
}
widgetIds = api( settingId ).get();
if ( ! _.isArray( widgetIds ) ) {
throw new Error( 'Expected setting to be array of widget IDs' );
}
return widgetIds.slice( 0 );
},
/**
* Reflow widgets in the sidebar, ensuring they have the proper position in the DOM.
*
* @since 4.5.0
*
* @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed.
*/
reflowWidgets: function() {
var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = [];
widgetIds = sidebarPartial.getWidgetIds();
sidebarPlacements = sidebarPartial.placements();
widgetPartials = {};
_.each( widgetIds, function( widgetId ) {
var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' );
if ( widgetPartial ) {
widgetPartials[ widgetId ] = widgetPartial;
}
} );
_.each( sidebarPlacements, function( sidebarPlacement ) {
var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1;
// Gather list of widget partial containers in this sidebar, and determine if a sort is needed.
_.each( widgetPartials, function( widgetPartial ) {
_.each( widgetPartial.placements(), function( widgetPlacement ) {
if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) {
thisPosition = widgetPlacement.container.index();
sidebarWidgets.push( {
partial: widgetPartial,
placement: widgetPlacement,
position: thisPosition
} );
if ( thisPosition < lastPosition ) {
needsSort = true;
}
lastPosition = thisPosition;
}
} );
sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
wasInserted = true;
} );
if ( wasInserted ) {
sidebarPartial.reflowWidgets();
if ( needsSort ) {
_.each( sidebarWidgets, function( sidebarWidget ) {
sidebarPlacement.endNode.parentNode.insertBefore(
sidebarWidget.placement.container[0],
sidebarPlacement.endNode
);
// @todo Rename partial-placement-moved?
api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement );
} );
sortedSidebarContainers.push( sidebarPlacement );
}
} );
return widgetPartial;
},
if ( sortedSidebarContainers.length > 0 ) {
api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
}
/**
* Handle change to the sidebars_widgets[] setting.
*
* @since 4.5.0
*
* @param {Array} newWidgetIds New widget ids.
* @param {Array} oldWidgetIds Old widget ids.
*/
handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
return sortedSidebarContainers;
},
needsRefresh = (
( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
);
if ( needsRefresh ) {
sidebarPartial.fallback();
/**
* Make sure there is a widget instance container in this sidebar for the given widget ID.
*
* @since 4.5.0
*
* @param {string} widgetId
* @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial.
*/
ensureWidgetPlacementContainers: function( widgetId ) {
var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']';
widgetPartial = api.selectiveRefresh.partial( partialId );
if ( ! widgetPartial ) {
widgetPartial = new self.WidgetPartial( partialId, {
params: {}
} );
api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial );
}
// Make sure that there is a container element for the widget in the sidebar, if at least a placeholder.
_.each( sidebarPartial.placements(), function( sidebarPlacement ) {
var foundWidgetPlacement, widgetContainerElement;
foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) {
return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber );
} );
if ( foundWidgetPlacement ) {
return;
}
// Handle removal of widgets.
widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
_.each( widgetsRemoved, function( removedWidgetId ) {
var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
if ( widgetPartial ) {
_.each( widgetPartial.placements(), function( placement ) {
var isRemoved = (
placement.context.sidebar_id === sidebarPartial.sidebarId ||
( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
);
if ( isRemoved ) {
placement.container.remove();
}
} );
}
widgetContainerElement = $(
sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) +
sidebarPartial.params.sidebarArgs.after_widget
);
widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id );
widgetContainerElement.attr( 'data-customize-partial-type', 'widget' );
widgetContainerElement.attr( 'data-customize-widget-id', widgetId );
/*
* Make sure the widget container element has the customize-container context data.
* The sidebar_instance_number is used to disambiguate multiple instances of the
* same sidebar are rendered onto the template, and so the same widget is embedded
* multiple times.
*/
widgetContainerElement.data( 'customize-partial-placement-context', {
'sidebar_id': sidebarPartial.sidebarId,
'sidebar_instance_number': sidebarPlacement.context.instanceNumber
} );
// Handle insertion of widgets.
widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
_.each( widgetsAdded, function( addedWidgetId ) {
var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
addedWidgetPartials.push( widgetPartial );
} );
sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );
wasInserted = true;
} );
_.each( addedWidgetPartials, function( widgetPartial ) {
widgetPartial.refresh();
} );
api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
},
/**
* Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
*
* @since 4.5.0
*/
refresh: function() {
var partial = this, deferred = $.Deferred();
deferred.fail( function() {
partial.fallback();
} );
if ( 0 === partial.placements().length ) {
deferred.reject();
} else {
_.each( partial.reflowWidgets(), function( sidebarPlacement ) {
api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
} );
deferred.resolve();
}
return deferred.promise();
if ( wasInserted ) {
sidebarPartial.reflowWidgets();
}
});
api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
return widgetPartial;
},
/**
* Add partials for the registered widget areas (sidebars).
* Handle change to the sidebars_widgets[] setting.
*
* @since 4.5.0
*
* @param {Array} newWidgetIds New widget ids.
* @param {Array} oldWidgetIds Old widget ids.
*/
handleSettingChange: function( newWidgetIds, oldWidgetIds ) {
var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = [];
needsRefresh = (
( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) ||
( newWidgetIds.length > 0 && 0 === oldWidgetIds.length )
);
if ( needsRefresh ) {
sidebarPartial.fallback();
return;
}
// Handle removal of widgets.
widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds );
_.each( widgetsRemoved, function( removedWidgetId ) {
var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' );
if ( widgetPartial ) {
_.each( widgetPartial.placements(), function( placement ) {
var isRemoved = (
placement.context.sidebar_id === sidebarPartial.sidebarId ||
( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId )
);
if ( isRemoved ) {
placement.container.remove();
}
} );
}
} );
// Handle insertion of widgets.
widgetsAdded = _.difference( newWidgetIds, oldWidgetIds );
_.each( widgetsAdded, function( addedWidgetId ) {
var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId );
addedWidgetPartials.push( widgetPartial );
} );
_.each( addedWidgetPartials, function( widgetPartial ) {
widgetPartial.refresh();
} );
api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial );
},
/**
* Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed.
*
* @since 4.5.0
*/
self.addPartials = function() {
_.each( self.registeredSidebars, function( registeredSidebar ) {
var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
partial = api.selectiveRefresh.partial( partialId );
if ( ! partial ) {
partial = new self.SidebarPartial( partialId, {
params: {
sidebarArgs: registeredSidebar
}
} );
api.selectiveRefresh.partial.add( partial.id, partial );
}
} );
};
refresh: function() {
var partial = this, deferred = $.Deferred();
}
deferred.fail( function() {
partial.fallback();
} );
if ( 0 === partial.placements().length ) {
deferred.reject();
} else {
_.each( partial.reflowWidgets(), function( sidebarPlacement ) {
api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement );
} );
deferred.resolve();
}
return deferred.promise();
}
});
api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;
api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;
/**
* Add partials for the registered widget areas (sidebars).
*
* @since 4.5.0
*/
self.addPartials = function() {
_.each( self.registeredSidebars, function( registeredSidebar ) {
var partial, partialId = 'sidebar[' + registeredSidebar.id + ']';
partial = api.selectiveRefresh.partial( partialId );
if ( ! partial ) {
partial = new self.SidebarPartial( partialId, {
params: {
sidebarArgs: registeredSidebar
}
} );
api.selectiveRefresh.partial.add( partial.id, partial );
}
} );
};
/**
* Calculate the selector for the sidebar's widgets based on the registered sidebar's info.

File diff suppressed because one or more lines are too long

View File

@ -109,7 +109,7 @@ wp.customize.selectiveRefresh = ( function( $, api ) {
placements: function() {
var partial = this, selector;
selector = partial.params.selector;
selector = partial.params.selector || '';
if ( selector ) {
selector += ', ';
}

File diff suppressed because one or more lines are too long

View File

@ -455,10 +455,10 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
$scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
$scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
$scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
$scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 );
$scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
$scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
$scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );

View File

@ -1914,7 +1914,7 @@ function current_theme_supports( $feature ) {
*
* The dynamic portion of the hook name, `$feature`, refers to the specific theme
* feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
* 'custom-header', 'menus', 'automatic-feed-links', and 'html5'.
* 'custom-header', 'menus', 'automatic-feed-links', 'html5', and `customize-selective-refresh-widgets`.
*
* @since 3.4.0
*

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.5-beta4-37039';
$wp_version = '4.5-beta4-37040';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.

View File

@ -23,7 +23,10 @@
* @access public
*/
public function __construct() {
$widget_ops = array( 'description' => __('Add a custom menu to your sidebar.') );
$widget_ops = array(
'description' => __( 'Add a custom menu to your sidebar.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'nav_menu', __('Custom Menu'), $widget_ops );
}

View File

@ -23,7 +23,11 @@ class WP_Widget_Archives extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_archive', 'description' => __( 'A monthly archive of your site&#8217;s Posts.') );
$widget_ops = array(
'classname' => 'widget_archive',
'description' => __( 'A monthly archive of your site&#8217;s Posts.' ),
'customize_selective_refresh' => true,
);
parent::__construct('archives', __('Archives'), $widget_ops);
}

View File

@ -33,8 +33,12 @@ class WP_Widget_Calendar extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_calendar', 'description' => __( 'A calendar of your site&#8217;s Posts.') );
parent::__construct('calendar', __('Calendar'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_calendar',
'description' => __( 'A calendar of your site&#8217;s Posts.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'calendar', __( 'Calendar' ), $widget_ops );
}
/**

View File

@ -23,8 +23,12 @@ class WP_Widget_Categories extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array( 'classname' => 'widget_categories', 'description' => __( "A list or dropdown of categories." ) );
parent::__construct('categories', __('Categories'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_categories',
'description' => __( 'A list or dropdown of categories.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'categories', __( 'Categories' ), $widget_ops );
}
/**

View File

@ -23,8 +23,11 @@ class WP_Widget_Links extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('description' => __( "Your blogroll" ) );
parent::__construct('links', __('Links'), $widget_ops);
$widget_ops = array(
'description' => __( 'Your blogroll' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'links', __( 'Links' ), $widget_ops );
}
/**

View File

@ -25,8 +25,12 @@ class WP_Widget_Meta extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_meta', 'description' => __( "Login, RSS, &amp; WordPress.org links.") );
parent::__construct('meta', __('Meta'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_meta',
'description' => __( 'Login, RSS, &amp; WordPress.org links.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'meta', __( 'Meta' ), $widget_ops );
}
/**

View File

@ -23,8 +23,12 @@ class WP_Widget_Pages extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_pages', 'description' => __( 'A list of your site&#8217;s Pages.') );
parent::__construct('pages', __('Pages'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_pages',
'description' => __( 'A list of your site&#8217;s Pages.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'pages', __( 'Pages' ), $widget_ops );
}
/**

View File

@ -23,12 +23,17 @@ class WP_Widget_Recent_Comments extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_recent_comments', 'description' => __( 'Your site&#8217;s most recent comments.' ) );
parent::__construct('recent-comments', __('Recent Comments'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_recent_comments',
'description' => __( 'Your site&#8217;s most recent comments.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'recent-comments', __( 'Recent Comments' ), $widget_ops );
$this->alt_option_name = 'widget_recent_comments';
if ( is_active_widget(false, false, $this->id_base) )
add_action( 'wp_head', array($this, 'recent_comments_style') );
if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
add_action( 'wp_head', array( $this, 'recent_comments_style' ) );
}
}
/**

View File

@ -23,8 +23,12 @@ class WP_Widget_Recent_Posts extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_recent_entries', 'description' => __( "Your site&#8217;s most recent Posts.") );
parent::__construct('recent-posts', __('Recent Posts'), $widget_ops);
$widget_ops = array(
'classname' => 'widget_recent_entries',
'description' => __( 'Your site&#8217;s most recent Posts.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'recent-posts', __( 'Recent Posts' ), $widget_ops );
$this->alt_option_name = 'widget_recent_entries';
}

View File

@ -23,9 +23,12 @@ class WP_Widget_RSS extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array( 'description' => __('Entries from any RSS or Atom feed.') );
$widget_ops = array(
'description' => __( 'Entries from any RSS or Atom feed.' ),
'customize_selective_refresh' => true,
);
$control_ops = array( 'width' => 400, 'height' => 200 );
parent::__construct( 'rss', __('RSS'), $widget_ops, $control_ops );
parent::__construct( 'rss', __( 'RSS' ), $widget_ops, $control_ops );
}
/**

View File

@ -23,7 +23,11 @@ class WP_Widget_Search extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_search', 'description' => __( "A search form for your site.") );
$widget_ops = array(
'classname' => 'widget_search',
'description' => __( 'A search form for your site.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops );
}

View File

@ -23,8 +23,11 @@ class WP_Widget_Tag_Cloud extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array( 'description' => __( "A cloud of your most used tags.") );
parent::__construct('tag_cloud', __('Tag Cloud'), $widget_ops);
$widget_ops = array(
'description' => __( 'A cloud of your most used tags.' ),
'customize_selective_refresh' => true,
);
parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops );
}
/**

View File

@ -23,9 +23,13 @@ class WP_Widget_Text extends WP_Widget {
* @access public
*/
public function __construct() {
$widget_ops = array('classname' => 'widget_text', 'description' => __('Arbitrary text or HTML.'));
$control_ops = array('width' => 400, 'height' => 350);
parent::__construct('text', __('Text'), $widget_ops, $control_ops);
$widget_ops = array(
'classname' => 'widget_text',
'description' => __( 'Arbitrary text or HTML.' ),
'customize_selective_refresh' => true,
);
$control_ops = array( 'width' => 400, 'height' => 350 );
parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops );
}
/**