Ticket #35855: 35855.1.diff
File 35855.1.diff, 60.0 KB (added by , 9 years ago) |
---|
-
src/wp-admin/js/customize-widgets.js
diff --git src/wp-admin/js/customize-widgets.js src/wp-admin/js/customize-widgets.js index 91a6516..ded96ef 100644
34 34 multi_number: null, 35 35 name: null, 36 36 id_base: null, 37 transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',37 transport: null, 38 38 params: [], 39 39 width: null, 40 40 height: null, … … 1982 1982 isExistingWidget = api.has( settingId ); 1983 1983 if ( ! isExistingWidget ) { 1984 1984 settingArgs = { 1985 transport: api.Widgets.data.selectiveRefresh ? 'postMessage' : 'refresh',1985 transport: api.Widgets.data.selectiveRefreshWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh', 1986 1986 previewer: this.setting.previewer 1987 1987 }; 1988 1988 setting = api.create( settingId, settingId, '', settingArgs ); -
src/wp-content/themes/twentyeleven/functions.php
diff --git src/wp-content/themes/twentyeleven/functions.php src/wp-content/themes/twentyeleven/functions.php index 3ae1756..bd5fef4 100644
function twentyeleven_setup() { 226 226 'description' => __( 'Hanoi Plant', 'twentyeleven' ) 227 227 ) 228 228 ) ); 229 230 // Indicate widget sidebars can use selective refresh in the Customizer. 231 add_theme_support( 'customize-selective-refresh-widgets' ); 229 232 } 230 233 endif; // twentyeleven_setup 231 234 -
src/wp-content/themes/twentyfifteen/functions.php
diff --git src/wp-content/themes/twentyfifteen/functions.php src/wp-content/themes/twentyfifteen/functions.php index e109a2b..dafdf43 100644
function twentyfifteen_setup() { 117 117 * specifically font, colors, icons, and column width. 118 118 */ 119 119 add_editor_style( array( 'css/editor-style.css', 'genericons/genericons.css', twentyfifteen_fonts_url() ) ); 120 121 // Indicate widget sidebars can use selective refresh in the Customizer. 122 add_theme_support( 'customize-selective-refresh-widgets' ); 120 123 } 121 124 endif; // twentyfifteen_setup 122 125 add_action( 'after_setup_theme', 'twentyfifteen_setup' ); -
src/wp-content/themes/twentyfourteen/functions.php
diff --git src/wp-content/themes/twentyfourteen/functions.php src/wp-content/themes/twentyfourteen/functions.php index 4e65214..9b7cf0b 100644
function twentyfourteen_setup() { 113 113 114 114 // This theme uses its own gallery styles. 115 115 add_filter( 'use_default_gallery_style', '__return_false' ); 116 117 // Indicate widget sidebars can use selective refresh in the Customizer. 118 add_theme_support( 'customize-selective-refresh-widgets' ); 116 119 } 117 120 endif; // twentyfourteen_setup 118 121 add_action( 'after_setup_theme', 'twentyfourteen_setup' ); -
src/wp-content/themes/twentythirteen/functions.php
diff --git src/wp-content/themes/twentythirteen/functions.php src/wp-content/themes/twentythirteen/functions.php index 33c8efd..e1f27ad 100644
function twentythirteen_setup() { 105 105 106 106 // This theme uses its own gallery styles. 107 107 add_filter( 'use_default_gallery_style', '__return_false' ); 108 109 // Indicate widget sidebars can use selective refresh in the Customizer. 110 add_theme_support( 'customize-selective-refresh-widgets' ); 108 111 } 109 112 add_action( 'after_setup_theme', 'twentythirteen_setup' ); 110 113 -
src/wp-content/themes/twentytwelve/functions.php
diff --git src/wp-content/themes/twentytwelve/functions.php src/wp-content/themes/twentytwelve/functions.php index e305447..8ec0755 100644
function twentytwelve_setup() { 74 74 // This theme uses a custom image size for featured images, displayed on "standard" posts. 75 75 add_theme_support( 'post-thumbnails' ); 76 76 set_post_thumbnail_size( 624, 9999 ); // Unlimited height, soft crop 77 78 // Indicate widget sidebars can use selective refresh in the Customizer. 79 add_theme_support( 'customize-selective-refresh-widgets' ); 77 80 } 78 81 add_action( 'after_setup_theme', 'twentytwelve_setup' ); 79 82 -
src/wp-includes/class-wp-customize-manager.php
diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php index 3699b21..3aaeb1c 100644
final class WP_Customize_Manager { 109 109 * @access protected 110 110 * @var array 111 111 */ 112 protected $components = array( 'widgets', 'nav_menus' , 'selective_refresh');112 protected $components = array( 'widgets', 'nav_menus' ); 113 113 114 114 /** 115 115 * Registered instances of WP_Customize_Section. … … final class WP_Customize_Manager { 258 258 */ 259 259 $components = apply_filters( 'customize_loaded_components', $this->components, $this ); 260 260 261 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' ); 262 $this->selective_refresh = new WP_Customize_Selective_Refresh( $this ); 263 261 264 if ( in_array( 'widgets', $components, true ) ) { 262 265 require_once( ABSPATH . WPINC . '/class-wp-customize-widgets.php' ); 263 266 $this->widgets = new WP_Customize_Widgets( $this ); … … final class WP_Customize_Manager { 268 271 $this->nav_menus = new WP_Customize_Nav_Menus( $this ); 269 272 } 270 273 271 if ( in_array( 'selective_refresh', $components, true ) ) {272 require_once( ABSPATH . WPINC . '/customize/class-wp-customize-selective-refresh.php' );273 $this->selective_refresh = new WP_Customize_Selective_Refresh( $this );274 }275 276 274 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler' ) ); 277 275 278 276 add_action( 'setup_theme', array( $this, 'setup_theme' ) ); … … final class WP_Customize_Manager { 1730 1728 'autofocus' => $this->get_autofocus(), 1731 1729 'documentTitleTmpl' => $this->get_document_title_template(), 1732 1730 'previewableDevices' => $this->get_previewable_devices(), 1733 'selectiveRefreshEnabled' => isset( $this->selective_refresh ),1734 1731 ); 1735 1732 1736 1733 // Prepare Customize Section objects to pass to JavaScript. … … final class WP_Customize_Manager { 1979 1976 ), 1980 1977 ) ) ); 1981 1978 1982 if ( isset( $this->selective_refresh ) ) { 1983 $this->selective_refresh->add_partial( 'custom_logo', array( 1984 'settings' => array( 'custom_logo' ), 1985 'selector' => '.custom-logo-link', 1986 'render_callback' => array( $this, '_render_custom_logo_partial' ), 1987 'container_inclusive' => true, 1988 ) ); 1989 } 1979 $this->selective_refresh->add_partial( 'site_logo', array( 1980 'settings' => array( 'site_logo' ), 1981 'selector' => '.site-logo-link', 1982 'render_callback' => array( $this, '_render_site_logo_partial' ), 1983 'container_inclusive' => true, 1984 ) ); 1990 1985 1991 1986 /* Colors */ 1992 1987 -
src/wp-includes/class-wp-customize-nav-menus.php
diff --git src/wp-includes/class-wp-customize-nav-menus.php src/wp-includes/class-wp-customize-nav-menus.php index cb8994c..7784567 100644
final class WP_Customize_Nav_Menus { 393 393 'reorderLabelOn' => esc_attr__( 'Reorder menu items' ), 394 394 'reorderLabelOff' => esc_attr__( 'Close reorder mode' ), 395 395 ), 396 'settingTransport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',396 'settingTransport' => 'postMessage', 397 397 'phpIntMax' => PHP_INT_MAX, 398 398 'defaultSettingValues' => array( 399 399 'nav_menu' => $temp_nav_menu_setting->default, … … final class WP_Customize_Nav_Menus { 445 445 if ( preg_match( WP_Customize_Nav_Menu_Setting::ID_PATTERN, $setting_id ) ) { 446 446 $setting_args = array( 447 447 'type' => WP_Customize_Nav_Menu_Setting::TYPE, 448 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',448 'transport' => 'postMessage', 449 449 ); 450 450 } elseif ( preg_match( WP_Customize_Nav_Menu_Item_Setting::ID_PATTERN, $setting_id ) ) { 451 451 $setting_args = array( 452 452 'type' => WP_Customize_Nav_Menu_Item_Setting::TYPE, 453 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',453 'transport' => 'postMessage', 454 454 ); 455 455 } 456 456 return $setting_args; … … final class WP_Customize_Nav_Menus { 535 535 536 536 $setting = $this->manager->get_setting( $setting_id ); 537 537 if ( $setting ) { 538 $setting->transport = isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh';538 $setting->transport = 'postMessage'; 539 539 remove_filter( "customize_sanitize_{$setting_id}", 'absint' ); 540 540 add_filter( "customize_sanitize_{$setting_id}", array( $this, 'intval_base10' ) ); 541 541 } else { … … final class WP_Customize_Nav_Menus { 543 543 'sanitize_callback' => array( $this, 'intval_base10' ), 544 544 'theme_supports' => 'menus', 545 545 'type' => 'theme_mod', 546 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',546 'transport' => 'postMessage', 547 547 'default' => 0, 548 548 ) ); 549 549 } … … final class WP_Customize_Nav_Menus { 570 570 571 571 $nav_menu_setting_id = 'nav_menu[' . $menu_id . ']'; 572 572 $this->manager->add_setting( new WP_Customize_Nav_Menu_Setting( $this->manager, $nav_menu_setting_id, array( 573 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',573 'transport' => 'postMessage', 574 574 ) ) ); 575 575 576 576 // Add the menu contents. … … final class WP_Customize_Nav_Menus { 585 585 $value['nav_menu_term_id'] = $menu_id; 586 586 $this->manager->add_setting( new WP_Customize_Nav_Menu_Item_Setting( $this->manager, $menu_item_setting_id, array( 587 587 'value' => $value, 588 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',588 'transport' => 'postMessage', 589 589 ) ) ); 590 590 591 591 // Create a control for each menu item. … … final class WP_Customize_Nav_Menus { 988 988 * @access public 989 989 */ 990 990 public function customize_preview_enqueue_deps() { 991 if ( isset( $this->manager->selective_refresh ) ) {992 $script = wp_scripts()->registered['customize-preview-nav-menus'];993 $script->deps[] = 'customize-selective-refresh';994 }995 996 991 wp_enqueue_script( 'customize-preview-nav-menus' ); // Note that we have overridden this. 997 992 wp_enqueue_style( 'customize-preview' ); 998 993 } -
src/wp-includes/class-wp-customize-widgets.php
diff --git src/wp-includes/class-wp-customize-widgets.php src/wp-includes/class-wp-customize-widgets.php index a417c46..eeccce0 100644
final class WP_Customize_Widgets { 69 69 * @var array 70 70 */ 71 71 protected $setting_id_patterns = array( 72 'widget_instance' => '/^ (widget_.+?)(?:\[(\d+)\])?$/',73 'sidebar_widgets' => '/^sidebars_widgets\[( .+?)\]$/',72 'widget_instance' => '/^widget_(?P<id_base>.+?)(?:\[(?P<widget_number>\d+)\])?$/', 73 'sidebar_widgets' => '/^sidebars_widgets\[(?P<sidebar_id>.+?)\]$/', 74 74 ); 75 75 76 76 /** … … final class WP_Customize_Widgets { 109 109 // Selective Refresh. 110 110 add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 ); 111 111 add_action( 'customize_preview_init', array( $this, 'selective_refresh_init' ) ); 112 113 add_filter( 'current_theme_supports-customize-selective-refresh-widgets', array( $this, 'filter_current_theme_supports' ), 1, 3 ); 114 } 115 116 /** 117 * Filters theme support for customize-selective-refresh-widgets to whitelist widget support. 118 * 119 * @since 4.5.0 120 * @access public 121 * 122 * @param bool $theme_supports Whether the current theme supports selective refresh of widgets. 123 * @param array $args Array of arguments for the feature. 124 * @param array $widgets The widgets supported by the feature. 125 * @return bool Whether the theme or widget can use selective refresh in the Customizer. 126 */ 127 public function filter_current_theme_supports( $theme_supports, $args, $widgets ) { 128 global $wp_widget_factory; 129 if ( $theme_supports && isset( $args[0] ) ) { 130 $widget_id_base = $args[0]; 131 132 if ( is_array( $widgets ) && in_array( $widget_id_base, $widgets, true ) ) { 133 $theme_supports = true; 134 } else { 135 foreach ( $wp_widget_factory->widgets as $widget ) { 136 if ( $widget_id_base === $widget->id_base ) { 137 $theme_supports = ! empty( $widget->widget_options['customize_selective_refresh'] ); 138 break; 139 } 140 } 141 } 142 } 143 return $theme_supports; 144 } 145 146 /** 147 * Gets whether each registered widget can be use selective refresh. 148 * 149 * @since 4.5.0 150 * @access private 151 * 152 * @return array Mapping of id_base to support. If theme doesn't support 153 * selective refresh, an empty array is returned. 154 */ 155 protected function get_selective_refreshable_widgets() { 156 global $wp_widget_factory; 157 158 $selective_refreshable_widgets = array(); 159 if ( current_theme_supports( 'customize-selective-refresh-widgets' ) ) { 160 foreach ( $wp_widget_factory->widgets as $wp_widget ) { 161 $selective_refreshable_widgets[ $wp_widget->id_base ] = current_theme_supports( 'customize-selective-refresh-widgets', $wp_widget->id_base ); 162 } 163 } 164 return $selective_refreshable_widgets; 112 165 } 113 166 114 167 /** … … final class WP_Customize_Widgets { 119 172 * 120 173 * @staticvar array $cache 121 174 * 122 * @param $setting_id Setting ID.175 * @param string $setting_id Setting ID. 123 176 * @return string|void Setting type. 124 177 */ 125 178 protected function get_setting_type( $setting_id ) { … … final class WP_Customize_Widgets { 690 743 'widgetReorderNav' => $widget_reorder_nav_tpl, 691 744 'moveWidgetArea' => $move_widget_area_tpl, 692 745 ), 693 'selectiveRefresh ' => isset( $this->manager->selective_refresh),746 'selectiveRefreshWidgets' => $this->get_selective_refreshable_widgets(), 694 747 ); 695 748 696 749 foreach ( $settings['registeredWidgets'] as &$registered_widget ) { … … final class WP_Customize_Widgets { 771 824 $args = array( 772 825 'type' => 'option', 773 826 'capability' => 'edit_theme_options', 774 'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',775 827 'default' => array(), 776 828 ); 777 829 778 830 if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) { 779 831 $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' ); 780 832 $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' ); 833 $args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets' ) ? 'postMessage' : 'refresh'; 781 834 } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) { 782 835 $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' ); 783 836 $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' ); 837 $args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets', $matches['id_base'] ) ? 'postMessage' : 'refresh'; 784 838 } 785 839 786 840 $args = array_merge( $args, $overrides ); … … final class WP_Customize_Widgets { 893 947 'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false, 894 948 'is_disabled' => $is_disabled, 895 949 'id_base' => $id_base, 896 'transport' => isset( $this->manager->selective_refresh) ? 'postMessage' : 'refresh',950 'transport' => current_theme_supports( 'customize-selective-refresh-widgets', $id_base ) ? 'postMessage' : 'refresh', 897 951 'width' => $wp_registered_widget_controls[$widget['id']]['width'], 898 952 'height' => $wp_registered_widget_controls[$widget['id']]['height'], 899 953 'is_wide' => $this->is_wide_widget( $widget['id'] ), … … final class WP_Customize_Widgets { 1060 1114 */ 1061 1115 public function export_preview_data() { 1062 1116 global $wp_registered_sidebars, $wp_registered_widgets; 1117 1063 1118 // Prepare Customizer settings to pass to JavaScript. 1064 1119 $settings = array( 1065 1120 'renderedSidebars' => array_fill_keys( array_unique( $this->rendered_sidebars ), true ), … … final class WP_Customize_Widgets { 1069 1124 'l10n' => array( 1070 1125 'widgetTooltip' => __( 'Shift-click to edit this widget.' ), 1071 1126 ), 1072 'selectiveRefresh ' => isset( $this->manager->selective_refresh),1127 'selectiveRefreshWidgets' => $this->get_selective_refreshable_widgets(), 1073 1128 ); 1074 1129 foreach ( $settings['registeredWidgets'] as &$registered_widget ) { 1075 1130 unset( $registered_widget['callback'] ); // may not be JSON-serializeable … … final class WP_Customize_Widgets { 1479 1534 * @return array (Maybe) modified partial arguments. 1480 1535 */ 1481 1536 public function customize_dynamic_partial_args( $partial_args, $partial_id ) { 1537 if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) { 1538 return $partial_args; 1539 } 1482 1540 1483 1541 if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) { 1484 1542 if ( false === $partial_args ) { … … final class WP_Customize_Widgets { 1506 1564 * @access public 1507 1565 */ 1508 1566 public function selective_refresh_init() { 1509 if ( ! isset( $this->manager->selective_refresh) ) {1567 if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) { 1510 1568 return; 1511 1569 } 1512 1570 … … final class WP_Customize_Widgets { 1524 1582 * @access public 1525 1583 */ 1526 1584 public function customize_preview_enqueue_deps() { 1527 if ( isset( $this->manager->selective_refresh ) ) {1528 $script = wp_scripts()->registered['customize-preview-widgets'];1529 $script->deps[] = 'customize-selective-refresh';1530 }1531 1532 1585 wp_enqueue_script( 'customize-preview-widgets' ); 1533 1586 wp_enqueue_style( 'customize-preview' ); 1534 1587 } -
src/wp-includes/class-wp-widget.php
diff --git src/wp-includes/class-wp-widget.php src/wp-includes/class-wp-widget.php index e8f4a1e..bb0de50 100644
class WP_Widget { 155 155 $this->id_base = empty($id_base) ? preg_replace( '/(wp_)?widget_/', '', strtolower(get_class($this)) ) : strtolower($id_base); 156 156 $this->name = $name; 157 157 $this->option_name = 'widget_' . $this->id_base; 158 $this->widget_options = wp_parse_args( $widget_options, array( 'classname' => $this->option_name) );159 $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base) );158 $this->widget_options = wp_parse_args( $widget_options, array( 'classname' => $this->option_name, 'customize_selective_refresh' => false ) ); 159 $this->control_options = wp_parse_args( $control_options, array( 'id_base' => $this->id_base ) ); 160 160 } 161 161 162 162 /** -
src/wp-includes/js/customize-preview-widgets.js
diff --git src/wp-includes/js/customize-preview-widgets.js src/wp-includes/js/customize-preview-widgets.js index 92e7732..3ce1b1a 100644
wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 12 12 preview: null, 13 13 l10n: { 14 14 widgetTooltip: '' 15 } 15 }, 16 selectiveRefreshWidgets: {} 16 17 }; 17 18 18 19 /** … … wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 24 25 var self = this; 25 26 26 27 self.preview = api.preview; 27 if ( api.selectiveRefresh) {28 if ( ! _.isEmpty( self.selectiveRefreshWidgets ) ) { 28 29 self.addPartials(); 29 30 } 30 31 … … wp.customize.widgetsPreview = wp.customize.WidgetCustomizerPreview = (function( 38 39 } ); 39 40 }; 40 41 41 if ( api.selectiveRefresh ) { 42 /** 43 * Partial representing a widget instance. 44 * 45 * @class 46 * @augments wp.customize.selectiveRefresh.Partial 47 * @since 4.5.0 48 */ 49 self.WidgetPartial = api.selectiveRefresh.Partial.extend({ 42 50 43 51 /** 44 * Partial representing a widget instance.52 * Constructor. 45 53 * 46 * @class47 * @augments wp.customize.selectiveRefresh.Partial48 54 * @since 4.5.0 55 * @param {string} id - Partial ID. 56 * @param {Object} options 57 * @param {Object} options.params 49 58 */ 50 self.WidgetPartial = api.selectiveRefresh.Partial.extend({ 51 52 /** 53 * Constructor. 54 * 55 * @since 4.5.0 56 * @param {string} id - Partial ID. 57 * @param {Object} options 58 * @param {Object} options.params 59 */ 60 initialize: function( id, options ) { 61 var partial = this, matches; 62 matches = id.match( /^widget\[(.+)]$/ ); 63 if ( ! matches ) { 64 throw new Error( 'Illegal id for widget partial.' ); 65 } 59 initialize: function( id, options ) { 60 var partial = this, matches; 61 matches = id.match( /^widget\[(.+)]$/ ); 62 if ( ! matches ) { 63 throw new Error( 'Illegal id for widget partial.' ); 64 } 66 65 67 partial.widgetId = matches[1]; 68 options = options || {}; 69 options.params = _.extend( 70 { 71 /* Note that a selector of ('#' + partial.widgetId) is faster, but jQuery will only return the one result. */ 72 selector: '[id="' + partial.widgetId + '"]', // Alternatively, '[data-customize-widget-id="' + partial.widgetId + '"]' 73 settings: [ self.getWidgetSettingId( partial.widgetId ) ], 74 containerInclusive: true 75 }, 76 options.params || {} 77 ); 66 partial.widgetId = matches[1]; 67 options = options || {}; 68 options.params = _.extend( 69 { 70 /* Note that a selector of ('#' + partial.widgetId) is faster, but jQuery will only return the one result. */ 71 selector: '[id="' + partial.widgetId + '"]', // Alternatively, '[data-customize-widget-id="' + partial.widgetId + '"]' 72 settings: [ self.getWidgetSettingId( partial.widgetId ) ], 73 containerInclusive: true 74 }, 75 options.params || {} 76 ); 77 78 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 79 }, 78 80 79 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 80 }, 81 82 /** 83 * Send widget-updated message to parent so spinner will get removed from widget control. 84 * 85 * @inheritdoc 86 * @param {wp.customize.selectiveRefresh.Placement} placement 87 */ 88 renderContent: function( placement ) { 89 var partial = this; 90 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { 91 api.preview.send( 'widget-updated', partial.widgetId ); 92 api.selectiveRefresh.trigger( 'widget-updated', partial ); 93 } 81 /** 82 * Send widget-updated message to parent so spinner will get removed from widget control. 83 * 84 * @inheritdoc 85 * @param {wp.customize.selectiveRefresh.Placement} placement 86 */ 87 renderContent: function( placement ) { 88 var partial = this; 89 if ( api.selectiveRefresh.Partial.prototype.renderContent.call( partial, placement ) ) { 90 api.preview.send( 'widget-updated', partial.widgetId ); 91 api.selectiveRefresh.trigger( 'widget-updated', partial ); 94 92 } 95 }); 93 } 94 }); 95 96 /** 97 * Partial representing a widget area. 98 * 99 * @class 100 * @augments wp.customize.selectiveRefresh.Partial 101 * @since 4.5.0 102 */ 103 self.SidebarPartial = api.selectiveRefresh.Partial.extend({ 96 104 97 105 /** 98 * Partial representing a widget area.106 * Constructor. 99 107 * 100 * @class101 * @augments wp.customize.selectiveRefresh.Partial102 108 * @since 4.5.0 109 * @param {string} id - Partial ID. 110 * @param {Object} options 111 * @param {Object} options.params 103 112 */ 104 self.SidebarPartial = api.selectiveRefresh.Partial.extend({ 105 106 /** 107 * Constructor. 108 * 109 * @since 4.5.0 110 * @param {string} id - Partial ID. 111 * @param {Object} options 112 * @param {Object} options.params 113 */ 114 initialize: function( id, options ) { 115 var partial = this, matches; 116 matches = id.match( /^sidebar\[(.+)]$/ ); 117 if ( ! matches ) { 118 throw new Error( 'Illegal id for sidebar partial.' ); 119 } 120 partial.sidebarId = matches[1]; 121 122 options = options || {}; 123 options.params = _.extend( 124 { 125 settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ] 126 }, 127 options.params || {} 128 ); 113 initialize: function( id, options ) { 114 var partial = this, matches; 115 matches = id.match( /^sidebar\[(.+)]$/ ); 116 if ( ! matches ) { 117 throw new Error( 'Illegal id for sidebar partial.' ); 118 } 119 partial.sidebarId = matches[1]; 129 120 130 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 121 options = options || {}; 122 options.params = _.extend( 123 { 124 settings: [ 'sidebars_widgets[' + partial.sidebarId + ']' ] 125 }, 126 options.params || {} 127 ); 131 128 132 if ( ! partial.params.sidebarArgs ) { 133 throw new Error( 'The sidebarArgs param was not provided.' ); 134 } 135 if ( partial.params.settings.length > 1 ) { 136 throw new Error( 'Expected SidebarPartial to only have one associated setting' ); 137 } 138 }, 139 140 /** 141 * Set up the partial. 142 * 143 * @since 4.5.0 144 */ 145 ready: function() { 146 var sidebarPartial = this; 147 148 // Watch for changes to the sidebar_widgets setting. 149 _.each( sidebarPartial.settings(), function( settingId ) { 150 api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) ); 151 } ); 129 api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, options ); 152 130 153 // Trigger an event for this sidebar being updated whenever a widget inside is rendered. 154 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { 155 var isAssignedWidgetPartial = ( 156 placement.partial.extended( self.WidgetPartial ) && 157 ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) ) 158 ); 159 if ( isAssignedWidgetPartial ) { 160 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 161 } 162 } ); 131 if ( ! partial.params.sidebarArgs ) { 132 throw new Error( 'The sidebarArgs param was not provided.' ); 133 } 134 if ( partial.params.settings.length > 1 ) { 135 throw new Error( 'Expected SidebarPartial to only have one associated setting' ); 136 } 137 }, 163 138 164 // Make sure that a widget partial has a container in the DOM prior to a refresh. 165 api.bind( 'change', function( widgetSetting ) { 166 var widgetId, parsedId; 167 parsedId = self.parseWidgetSettingId( widgetSetting.id ); 168 if ( ! parsedId ) { 169 return; 170 } 171 widgetId = parsedId.idBase; 172 if ( parsedId.number ) { 173 widgetId += '-' + String( parsedId.number ); 174 } 175 if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) { 176 sidebarPartial.ensureWidgetPlacementContainers( widgetId ); 177 } 178 } ); 179 }, 180 181 /** 182 * Get the before/after boundary nodes for all instances of this sidebar (usually one). 183 * 184 * Note that TreeWalker is not implemented in IE8. 185 * 186 * @since 4.5.0 187 * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>} 188 */ 189 findDynamicSidebarBoundaryNodes: function() { 190 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; 191 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; 192 recursiveCommentTraversal = function( childNodes ) { 193 _.each( childNodes, function( node ) { 194 var matches; 195 if ( 8 === node.nodeType ) { 196 matches = node.nodeValue.match( regExp ); 197 if ( ! matches || matches[2] !== partial.sidebarId ) { 198 return; 199 } 200 if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) { 201 boundaryNodes[ matches[3] ] = { 202 before: null, 203 after: null, 204 instanceNumber: parseInt( matches[3], 10 ) 205 }; 206 } 207 if ( 'dynamic_sidebar_before' === matches[1] ) { 208 boundaryNodes[ matches[3] ].before = node; 209 } else { 210 boundaryNodes[ matches[3] ].after = node; 211 } 212 } else if ( 1 === node.nodeType ) { 213 recursiveCommentTraversal( node.childNodes ); 214 } 215 } ); 216 }; 217 218 recursiveCommentTraversal( document.body.childNodes ); 219 return _.values( boundaryNodes ); 220 }, 221 222 /** 223 * Get the placements for this partial. 224 * 225 * @since 4.5.0 226 * @returns {Array} 227 */ 228 placements: function() { 229 var partial = this; 230 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { 231 return new api.selectiveRefresh.Placement( { 232 partial: partial, 233 container: null, 234 startNode: boundaryNodes.before, 235 endNode: boundaryNodes.after, 236 context: { 237 instanceNumber: boundaryNodes.instanceNumber 238 } 239 } ); 240 } ); 241 }, 242 243 /** 244 * Get the list of widget IDs associated with this widget area. 245 * 246 * @since 4.5.0 247 * 248 * @returns {Array} 249 */ 250 getWidgetIds: function() { 251 var sidebarPartial = this, settingId, widgetIds; 252 settingId = sidebarPartial.settings()[0]; 253 if ( ! settingId ) { 254 throw new Error( 'Missing associated setting.' ); 139 /** 140 * Set up the partial. 141 * 142 * @since 4.5.0 143 */ 144 ready: function() { 145 var sidebarPartial = this; 146 147 // Watch for changes to the sidebar_widgets setting. 148 _.each( sidebarPartial.settings(), function( settingId ) { 149 api( settingId ).bind( _.bind( sidebarPartial.handleSettingChange, sidebarPartial ) ); 150 } ); 151 152 // Trigger an event for this sidebar being updated whenever a widget inside is rendered. 153 api.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) { 154 var isAssignedWidgetPartial = ( 155 placement.partial.extended( self.WidgetPartial ) && 156 ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), placement.partial.widgetId ) ) 157 ); 158 if ( isAssignedWidgetPartial ) { 159 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 160 } 161 } ); 162 163 // Make sure that a widget partial has a container in the DOM prior to a refresh. 164 api.bind( 'change', function( widgetSetting ) { 165 var widgetId, parsedId; 166 parsedId = self.parseWidgetSettingId( widgetSetting.id ); 167 if ( ! parsedId ) { 168 return; 255 169 } 256 if ( ! api.has( settingId ) ) { 257 throw new Error( 'Setting does not exist.' ); 170 widgetId = parsedId.idBase; 171 if ( parsedId.number ) { 172 widgetId += '-' + String( parsedId.number ); 258 173 } 259 widgetIds = api( settingId ).get(); 260 if ( ! _.isArray( widgetIds ) ) { 261 throw new Error( 'Expected setting to be array of widget IDs' ); 174 if ( -1 !== _.indexOf( sidebarPartial.getWidgetIds(), widgetId ) ) { 175 sidebarPartial.ensureWidgetPlacementContainers( widgetId ); 262 176 } 263 return widgetIds.slice( 0 ); 264 }, 265 266 /** 267 * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM. 268 * 269 * @since 4.5.0 270 * 271 * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed. 272 */ 273 reflowWidgets: function() { 274 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; 275 widgetIds = sidebarPartial.getWidgetIds(); 276 sidebarPlacements = sidebarPartial.placements(); 277 278 widgetPartials = {}; 279 _.each( widgetIds, function( widgetId ) { 280 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' ); 281 if ( widgetPartial ) { 282 widgetPartials[ widgetId ] = widgetPartial; 177 } ); 178 }, 179 180 /** 181 * Get the before/after boundary nodes for all instances of this sidebar (usually one). 182 * 183 * Note that TreeWalker is not implemented in IE8. 184 * 185 * @since 4.5.0 186 * @returns {Array.<{before: Comment, after: Comment, instanceNumber: number}>} 187 */ 188 findDynamicSidebarBoundaryNodes: function() { 189 var partial = this, regExp, boundaryNodes = {}, recursiveCommentTraversal; 190 regExp = /^(dynamic_sidebar_before|dynamic_sidebar_after):(.+):(\d+)$/; 191 recursiveCommentTraversal = function( childNodes ) { 192 _.each( childNodes, function( node ) { 193 var matches; 194 if ( 8 === node.nodeType ) { 195 matches = node.nodeValue.match( regExp ); 196 if ( ! matches || matches[2] !== partial.sidebarId ) { 197 return; 198 } 199 if ( _.isUndefined( boundaryNodes[ matches[3] ] ) ) { 200 boundaryNodes[ matches[3] ] = { 201 before: null, 202 after: null, 203 instanceNumber: parseInt( matches[3], 10 ) 204 }; 205 } 206 if ( 'dynamic_sidebar_before' === matches[1] ) { 207 boundaryNodes[ matches[3] ].before = node; 208 } else { 209 boundaryNodes[ matches[3] ].after = node; 210 } 211 } else if ( 1 === node.nodeType ) { 212 recursiveCommentTraversal( node.childNodes ); 283 213 } 284 214 } ); 215 }; 285 216 286 _.each( sidebarPlacements, function( sidebarPlacement ) { 287 var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1; 288 289 // Gather list of widget partial containers in this sidebar, and determine if a sort is needed. 290 _.each( widgetPartials, function( widgetPartial ) { 291 _.each( widgetPartial.placements(), function( widgetPlacement ) { 292 293 if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) { 294 thisPosition = widgetPlacement.container.index(); 295 sidebarWidgets.push( { 296 partial: widgetPartial, 297 placement: widgetPlacement, 298 position: thisPosition 299 } ); 300 if ( thisPosition < lastPosition ) { 301 needsSort = true; 302 } 303 lastPosition = thisPosition; 304 } 305 } ); 306 } ); 307 308 if ( needsSort ) { 309 _.each( sidebarWidgets, function( sidebarWidget ) { 310 sidebarPlacement.endNode.parentNode.insertBefore( 311 sidebarWidget.placement.container[0], 312 sidebarPlacement.endNode 313 ); 217 recursiveCommentTraversal( document.body.childNodes ); 218 return _.values( boundaryNodes ); 219 }, 314 220 315 // @todo Rename partial-placement-moved? 316 api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement ); 317 } ); 318 319 sortedSidebarContainers.push( sidebarPlacement ); 221 /** 222 * Get the placements for this partial. 223 * 224 * @since 4.5.0 225 * @returns {Array} 226 */ 227 placements: function() { 228 var partial = this; 229 return _.map( partial.findDynamicSidebarBoundaryNodes(), function( boundaryNodes ) { 230 return new api.selectiveRefresh.Placement( { 231 partial: partial, 232 container: null, 233 startNode: boundaryNodes.before, 234 endNode: boundaryNodes.after, 235 context: { 236 instanceNumber: boundaryNodes.instanceNumber 320 237 } 321 238 } ); 239 } ); 240 }, 322 241 323 if ( sortedSidebarContainers.length > 0 ) { 324 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 325 } 242 /** 243 * Get the list of widget IDs associated with this widget area. 244 * 245 * @since 4.5.0 246 * 247 * @returns {Array} 248 */ 249 getWidgetIds: function() { 250 var sidebarPartial = this, settingId, widgetIds; 251 settingId = sidebarPartial.settings()[0]; 252 if ( ! settingId ) { 253 throw new Error( 'Missing associated setting.' ); 254 } 255 if ( ! api.has( settingId ) ) { 256 throw new Error( 'Setting does not exist.' ); 257 } 258 widgetIds = api( settingId ).get(); 259 if ( ! _.isArray( widgetIds ) ) { 260 throw new Error( 'Expected setting to be array of widget IDs' ); 261 } 262 return widgetIds.slice( 0 ); 263 }, 326 264 327 return sortedSidebarContainers; 328 }, 329 330 /** 331 * Make sure there is a widget instance container in this sidebar for the given widget ID. 332 * 333 * @since 4.5.0 334 * 335 * @param {string} widgetId 336 * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial. 337 */ 338 ensureWidgetPlacementContainers: function( widgetId ) { 339 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; 340 widgetPartial = api.selectiveRefresh.partial( partialId ); 341 if ( ! widgetPartial ) { 342 widgetPartial = new self.WidgetPartial( partialId, { 343 params: {} 344 } ); 345 api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial ); 265 /** 266 * Reflow widgets in the sidebar, ensuring they have the proper position in the DOM. 267 * 268 * @since 4.5.0 269 * 270 * @return {Array.<wp.customize.selectiveRefresh.Placement>} List of placements that were reflowed. 271 */ 272 reflowWidgets: function() { 273 var sidebarPartial = this, sidebarPlacements, widgetIds, widgetPartials, sortedSidebarContainers = []; 274 widgetIds = sidebarPartial.getWidgetIds(); 275 sidebarPlacements = sidebarPartial.placements(); 276 277 widgetPartials = {}; 278 _.each( widgetIds, function( widgetId ) { 279 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + widgetId + ']' ); 280 if ( widgetPartial ) { 281 widgetPartials[ widgetId ] = widgetPartial; 346 282 } 283 } ); 347 284 348 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder. 349 _.each( sidebarPartial.placements(), function( sidebarPlacement ) { 350 var foundWidgetPlacement, widgetContainerElement; 351 352 foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) { 353 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber ); 285 _.each( sidebarPlacements, function( sidebarPlacement ) { 286 var sidebarWidgets = [], needsSort = false, thisPosition, lastPosition = -1; 287 288 // Gather list of widget partial containers in this sidebar, and determine if a sort is needed. 289 _.each( widgetPartials, function( widgetPartial ) { 290 _.each( widgetPartial.placements(), function( widgetPlacement ) { 291 292 if ( sidebarPlacement.context.instanceNumber === widgetPlacement.context.sidebar_instance_number ) { 293 thisPosition = widgetPlacement.container.index(); 294 sidebarWidgets.push( { 295 partial: widgetPartial, 296 placement: widgetPlacement, 297 position: thisPosition 298 } ); 299 if ( thisPosition < lastPosition ) { 300 needsSort = true; 301 } 302 lastPosition = thisPosition; 303 } 354 304 } ); 355 if ( foundWidgetPlacement ) { 356 return; 357 } 305 } ); 358 306 359 widgetContainerElement = $( 360 sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) + 361 sidebarPartial.params.sidebarArgs.after_widget 362 ); 363 364 widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id ); 365 widgetContainerElement.attr( 'data-customize-partial-type', 'widget' ); 366 widgetContainerElement.attr( 'data-customize-widget-id', widgetId ); 367 368 /* 369 * Make sure the widget container element has the customize-container context data. 370 * The sidebar_instance_number is used to disambiguate multiple instances of the 371 * same sidebar are rendered onto the template, and so the same widget is embedded 372 * multiple times. 373 */ 374 widgetContainerElement.data( 'customize-partial-placement-context', { 375 'sidebar_id': sidebarPartial.sidebarId, 376 'sidebar_instance_number': sidebarPlacement.context.instanceNumber 377 } ); 307 if ( needsSort ) { 308 _.each( sidebarWidgets, function( sidebarWidget ) { 309 sidebarPlacement.endNode.parentNode.insertBefore( 310 sidebarWidget.placement.container[0], 311 sidebarPlacement.endNode 312 ); 378 313 379 sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode );380 wasInserted = true;381 } );314 // @todo Rename partial-placement-moved? 315 api.selectiveRefresh.trigger( 'partial-content-moved', sidebarWidget.placement ); 316 } ); 382 317 383 if ( wasInserted ) { 384 sidebarPartial.reflowWidgets(); 318 sortedSidebarContainers.push( sidebarPlacement ); 385 319 } 320 } ); 386 321 387 return widgetPartial; 388 }, 389 390 /** 391 * Handle change to the sidebars_widgets[] setting. 392 * 393 * @since 4.5.0 394 * 395 * @param {Array} newWidgetIds New widget ids. 396 * @param {Array} oldWidgetIds Old widget ids. 397 */ 398 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { 399 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; 400 401 needsRefresh = ( 402 ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) || 403 ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length ) 404 ); 405 if ( needsRefresh ) { 406 sidebarPartial.fallback(); 407 return; 408 } 322 if ( sortedSidebarContainers.length > 0 ) { 323 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 324 } 409 325 410 // Handle removal of widgets. 411 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds ); 412 _.each( widgetsRemoved, function( removedWidgetId ) { 413 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' ); 414 if ( widgetPartial ) { 415 _.each( widgetPartial.placements(), function( placement ) { 416 var isRemoved = ( 417 placement.context.sidebar_id === sidebarPartial.sidebarId || 418 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId ) 419 ); 420 if ( isRemoved ) { 421 placement.container.remove(); 422 } 423 } ); 424 } 425 } ); 326 return sortedSidebarContainers; 327 }, 426 328 427 // Handle insertion of widgets. 428 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds ); 429 _.each( widgetsAdded, function( addedWidgetId ) { 430 var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId ); 431 addedWidgetPartials.push( widgetPartial ); 329 /** 330 * Make sure there is a widget instance container in this sidebar for the given widget ID. 331 * 332 * @since 4.5.0 333 * 334 * @param {string} widgetId 335 * @returns {wp.customize.selectiveRefresh.Partial} Widget instance partial. 336 */ 337 ensureWidgetPlacementContainers: function( widgetId ) { 338 var sidebarPartial = this, widgetPartial, wasInserted = false, partialId = 'widget[' + widgetId + ']'; 339 widgetPartial = api.selectiveRefresh.partial( partialId ); 340 if ( ! widgetPartial ) { 341 widgetPartial = new self.WidgetPartial( partialId, { 342 params: {} 432 343 } ); 344 api.selectiveRefresh.partial.add( widgetPartial.id, widgetPartial ); 345 } 433 346 434 _.each( addedWidgetPartials, function( widgetPartial ) { 435 widgetPartial.refresh(); 347 // Make sure that there is a container element for the widget in the sidebar, if at least a placeholder. 348 _.each( sidebarPartial.placements(), function( sidebarPlacement ) { 349 var foundWidgetPlacement, widgetContainerElement; 350 351 foundWidgetPlacement = _.find( widgetPartial.placements(), function( widgetPlacement ) { 352 return ( widgetPlacement.context.sidebar_instance_number === sidebarPlacement.context.instanceNumber ); 436 353 } ); 354 if ( foundWidgetPlacement ) { 355 return; 356 } 437 357 438 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 439 }, 440 441 /** 442 * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed. 443 * 444 * @since 4.5.0 445 */ 446 refresh: function() { 447 var partial = this, deferred = $.Deferred(); 448 449 deferred.fail( function() { 450 partial.fallback(); 358 widgetContainerElement = $( 359 sidebarPartial.params.sidebarArgs.before_widget.replace( '%1$s', widgetId ).replace( '%2$s', 'widget' ) + 360 sidebarPartial.params.sidebarArgs.after_widget 361 ); 362 363 widgetContainerElement.attr( 'data-customize-partial-id', widgetPartial.id ); 364 widgetContainerElement.attr( 'data-customize-partial-type', 'widget' ); 365 widgetContainerElement.attr( 'data-customize-widget-id', widgetId ); 366 367 /* 368 * Make sure the widget container element has the customize-container context data. 369 * The sidebar_instance_number is used to disambiguate multiple instances of the 370 * same sidebar are rendered onto the template, and so the same widget is embedded 371 * multiple times. 372 */ 373 widgetContainerElement.data( 'customize-partial-placement-context', { 374 'sidebar_id': sidebarPartial.sidebarId, 375 'sidebar_instance_number': sidebarPlacement.context.instanceNumber 451 376 } ); 452 377 453 if ( 0 === partial.placements().length ) { 454 deferred.reject(); 455 } else { 456 _.each( partial.reflowWidgets(), function( sidebarPlacement ) { 457 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement ); 458 } ); 459 deferred.resolve(); 460 } 378 sidebarPlacement.endNode.parentNode.insertBefore( widgetContainerElement[0], sidebarPlacement.endNode ); 379 wasInserted = true; 380 } ); 461 381 462 return deferred.promise(); 382 if ( wasInserted ) { 383 sidebarPartial.reflowWidgets(); 463 384 } 464 });465 385 466 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial;467 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial;386 return widgetPartial; 387 }, 468 388 469 389 /** 470 * Add partials for the registered widget areas (sidebars).390 * Handle change to the sidebars_widgets[] setting. 471 391 * 472 392 * @since 4.5.0 393 * 394 * @param {Array} newWidgetIds New widget ids. 395 * @param {Array} oldWidgetIds Old widget ids. 473 396 */ 474 self.addPartials = function() { 475 _.each( self.registeredSidebars, function( registeredSidebar ) { 476 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; 477 partial = api.selectiveRefresh.partial( partialId ); 478 if ( ! partial ) { 479 partial = new self.SidebarPartial( partialId, { 480 params: { 481 sidebarArgs: registeredSidebar 397 handleSettingChange: function( newWidgetIds, oldWidgetIds ) { 398 var sidebarPartial = this, needsRefresh, widgetsRemoved, widgetsAdded, addedWidgetPartials = []; 399 400 needsRefresh = ( 401 ( oldWidgetIds.length > 0 && 0 === newWidgetIds.length ) || 402 ( newWidgetIds.length > 0 && 0 === oldWidgetIds.length ) 403 ); 404 if ( needsRefresh ) { 405 sidebarPartial.fallback(); 406 return; 407 } 408 409 // Handle removal of widgets. 410 widgetsRemoved = _.difference( oldWidgetIds, newWidgetIds ); 411 _.each( widgetsRemoved, function( removedWidgetId ) { 412 var widgetPartial = api.selectiveRefresh.partial( 'widget[' + removedWidgetId + ']' ); 413 if ( widgetPartial ) { 414 _.each( widgetPartial.placements(), function( placement ) { 415 var isRemoved = ( 416 placement.context.sidebar_id === sidebarPartial.sidebarId || 417 ( placement.context.sidebar_args && placement.context.sidebar_args.id === sidebarPartial.sidebarId ) 418 ); 419 if ( isRemoved ) { 420 placement.container.remove(); 482 421 } 483 422 } ); 484 api.selectiveRefresh.partial.add( partial.id, partial );485 423 } 486 424 } ); 487 };488 425 489 } 426 // Handle insertion of widgets. 427 widgetsAdded = _.difference( newWidgetIds, oldWidgetIds ); 428 _.each( widgetsAdded, function( addedWidgetId ) { 429 var widgetPartial = sidebarPartial.ensureWidgetPlacementContainers( addedWidgetId ); 430 addedWidgetPartials.push( widgetPartial ); 431 } ); 432 433 _.each( addedWidgetPartials, function( widgetPartial ) { 434 widgetPartial.refresh(); 435 } ); 436 437 api.selectiveRefresh.trigger( 'sidebar-updated', sidebarPartial ); 438 }, 439 440 /** 441 * Note that the meat is handled in handleSettingChange because it has the context of which widgets were removed. 442 * 443 * @since 4.5.0 444 */ 445 refresh: function() { 446 var partial = this, deferred = $.Deferred(); 447 448 deferred.fail( function() { 449 partial.fallback(); 450 } ); 451 452 if ( 0 === partial.placements().length ) { 453 deferred.reject(); 454 } else { 455 _.each( partial.reflowWidgets(), function( sidebarPlacement ) { 456 api.selectiveRefresh.trigger( 'partial-content-rendered', sidebarPlacement ); 457 } ); 458 deferred.resolve(); 459 } 460 461 return deferred.promise(); 462 } 463 }); 464 465 api.selectiveRefresh.partialConstructor.sidebar = self.SidebarPartial; 466 api.selectiveRefresh.partialConstructor.widget = self.WidgetPartial; 467 468 /** 469 * Add partials for the registered widget areas (sidebars). 470 * 471 * @since 4.5.0 472 */ 473 self.addPartials = function() { 474 _.each( self.registeredSidebars, function( registeredSidebar ) { 475 var partial, partialId = 'sidebar[' + registeredSidebar.id + ']'; 476 partial = api.selectiveRefresh.partial( partialId ); 477 if ( ! partial ) { 478 partial = new self.SidebarPartial( partialId, { 479 params: { 480 sidebarArgs: registeredSidebar 481 } 482 } ); 483 api.selectiveRefresh.partial.add( partial.id, partial ); 484 } 485 } ); 486 }; 490 487 491 488 /** 492 489 * Calculate the selector for the sidebar's widgets based on the registered sidebar's info. -
src/wp-includes/script-loader.php
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php index 8f867ec..9ed7737 100644
function wp_default_scripts( &$scripts ) { 453 453 $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 ); 454 454 455 455 $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 ); 456 $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );456 $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 ); 457 457 458 458 $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu' ), false, 1 ); 459 $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );459 $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 ); 460 460 461 461 $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 ); 462 462 -
src/wp-includes/theme.php
diff --git src/wp-includes/theme.php src/wp-includes/theme.php index 1727a17..4737338 100644
function current_theme_supports( $feature ) { 1890 1890 * 1891 1891 * The dynamic portion of the hook name, `$feature`, refers to the specific theme 1892 1892 * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background', 1893 * 'custom-header', 'menus', 'automatic-feed-links', and 'html5'.1893 * 'custom-header', 'menus', 'automatic-feed-links', 'html5', and `customize-selective-refresh-widgets`. 1894 1894 * 1895 1895 * @since 3.4.0 1896 1896 * -
src/wp-includes/widgets/class-wp-widget-calendar.php
diff --git src/wp-includes/widgets/class-wp-widget-calendar.php src/wp-includes/widgets/class-wp-widget-calendar.php index 2a8911a..9969678 100644
class WP_Widget_Calendar extends WP_Widget { 33 33 * @access public 34 34 */ 35 35 public function __construct() { 36 $widget_ops = array('classname' => 'widget_calendar', 'description' => __( 'A calendar of your site’s Posts.') ); 37 parent::__construct('calendar', __('Calendar'), $widget_ops); 36 $widget_ops = array( 37 'classname' => 'widget_calendar', 38 'description' => __( 'A calendar of your site’s Posts.' ), 39 'customize_selective_refresh' => true, 40 ); 41 parent::__construct( 'calendar', __( 'Calendar' ), $widget_ops ); 38 42 } 39 43 40 44 /** -
src/wp-includes/widgets/class-wp-widget-categories.php
diff --git src/wp-includes/widgets/class-wp-widget-categories.php src/wp-includes/widgets/class-wp-widget-categories.php index 058fd90..2fbc62a 100644
class WP_Widget_Categories extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array( 'classname' => 'widget_categories', 'description' => __( "A list or dropdown of categories." ) ); 27 parent::__construct('categories', __('Categories'), $widget_ops); 26 $widget_ops = array( 27 'classname' => 'widget_categories', 28 'description' => __( 'A list or dropdown of categories.' ), 29 'customize_selective_refresh' => true, 30 ); 31 parent::__construct( 'categories', __( 'Categories' ), $widget_ops ); 28 32 } 29 33 30 34 /** -
src/wp-includes/widgets/class-wp-widget-links.php
diff --git src/wp-includes/widgets/class-wp-widget-links.php src/wp-includes/widgets/class-wp-widget-links.php index 96ecc77..68d86f1 100644
class WP_Widget_Links extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('description' => __( "Your blogroll" ) ); 27 parent::__construct('links', __('Links'), $widget_ops); 26 $widget_ops = array( 27 'description' => __( 'Your blogroll' ), 28 'customize_selective_refresh' => true, 29 ); 30 parent::__construct( 'links', __( 'Links' ), $widget_ops ); 28 31 } 29 32 30 33 /** -
src/wp-includes/widgets/class-wp-widget-meta.php
diff --git src/wp-includes/widgets/class-wp-widget-meta.php src/wp-includes/widgets/class-wp-widget-meta.php index c12238f..2de9844 100644
class WP_Widget_Meta extends WP_Widget { 25 25 * @access public 26 26 */ 27 27 public function __construct() { 28 $widget_ops = array('classname' => 'widget_meta', 'description' => __( "Login, RSS, & WordPress.org links.") ); 29 parent::__construct('meta', __('Meta'), $widget_ops); 28 $widget_ops = array( 29 'classname' => 'widget_meta', 30 'description' => __( 'Login, RSS, & WordPress.org links.' ), 31 'customize_selective_refresh' => true, 32 ); 33 parent::__construct( 'meta', __( 'Meta' ), $widget_ops ); 30 34 } 31 35 32 36 /** -
src/wp-includes/widgets/class-wp-widget-pages.php
diff --git src/wp-includes/widgets/class-wp-widget-pages.php src/wp-includes/widgets/class-wp-widget-pages.php index e8737ac..165f334 100644
class WP_Widget_Pages extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('classname' => 'widget_pages', 'description' => __( 'A list of your site’s Pages.') ); 27 parent::__construct('pages', __('Pages'), $widget_ops); 26 $widget_ops = array( 27 'classname' => 'widget_pages', 28 'description' => __( 'A list of your site’s Pages.' ), 29 'customize_selective_refresh' => true, 30 ); 31 parent::__construct( 'pages', __( 'Pages' ), $widget_ops ); 28 32 } 29 33 30 34 /** -
src/wp-includes/widgets/class-wp-widget-recent-comments.php
diff --git src/wp-includes/widgets/class-wp-widget-recent-comments.php src/wp-includes/widgets/class-wp-widget-recent-comments.php index 0f1a3b5..71fb4d2 100644
class WP_Widget_Recent_Comments extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('classname' => 'widget_recent_comments', 'description' => __( 'Your site’s most recent comments.' ) ); 27 parent::__construct('recent-comments', __('Recent Comments'), $widget_ops); 26 $widget_ops = array( 27 'classname' => 'widget_recent_comments', 28 'description' => __( 'Your site’s most recent comments.' ), 29 'customize_selective_refresh' => true, 30 ); 31 parent::__construct( 'recent-comments', __( 'Recent Comments' ), $widget_ops ); 28 32 $this->alt_option_name = 'widget_recent_comments'; 29 33 30 if ( is_active_widget(false, false, $this->id_base) ) 31 add_action( 'wp_head', array($this, 'recent_comments_style') ); 34 if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) { 35 add_action( 'wp_head', array( $this, 'recent_comments_style' ) ); 36 } 32 37 } 33 38 34 39 /** -
src/wp-includes/widgets/class-wp-widget-recent-posts.php
diff --git src/wp-includes/widgets/class-wp-widget-recent-posts.php src/wp-includes/widgets/class-wp-widget-recent-posts.php index 8f92bf3..fdb54a0 100644
class WP_Widget_Recent_Posts extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('classname' => 'widget_recent_entries', 'description' => __( "Your site’s most recent Posts.") ); 27 parent::__construct('recent-posts', __('Recent Posts'), $widget_ops); 26 $widget_ops = array( 27 'classname' => 'widget_recent_entries', 28 'description' => __( 'Your site’s most recent Posts.' ), 29 'customize_selective_refresh' => true, 30 ); 31 parent::__construct( 'recent-posts', __( 'Recent Posts' ), $widget_ops ); 28 32 $this->alt_option_name = 'widget_recent_entries'; 29 33 } 30 34 -
src/wp-includes/widgets/class-wp-widget-rss.php
diff --git src/wp-includes/widgets/class-wp-widget-rss.php src/wp-includes/widgets/class-wp-widget-rss.php index b477a6e..a2548c4 100644
class WP_Widget_RSS extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array( 'description' => __('Entries from any RSS or Atom feed.') ); 26 $widget_ops = array( 27 'description' => __( 'Entries from any RSS or Atom feed.' ), 28 'customize_selective_refresh' => true, 29 ); 27 30 $control_ops = array( 'width' => 400, 'height' => 200 ); 28 parent::__construct( 'rss', __( 'RSS'), $widget_ops, $control_ops );31 parent::__construct( 'rss', __( 'RSS' ), $widget_ops, $control_ops ); 29 32 } 30 33 31 34 /** -
src/wp-includes/widgets/class-wp-widget-search.php
diff --git src/wp-includes/widgets/class-wp-widget-search.php src/wp-includes/widgets/class-wp-widget-search.php index 29bbbf8..f7d67bb 100644
class WP_Widget_Search extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('classname' => 'widget_search', 'description' => __( "A search form for your site.") ); 26 $widget_ops = array( 27 'classname' => 'widget_search', 28 'description' => __( 'A search form for your site.' ), 29 'customize_selective_refresh' => true, 30 ); 27 31 parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops ); 28 32 } 29 33 -
src/wp-includes/widgets/class-wp-widget-tag-cloud.php
diff --git src/wp-includes/widgets/class-wp-widget-tag-cloud.php src/wp-includes/widgets/class-wp-widget-tag-cloud.php index 4115c79..1251e1d 100644
class WP_Widget_Tag_Cloud extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array( 'description' => __( "A cloud of your most used tags.") ); 27 parent::__construct('tag_cloud', __('Tag Cloud'), $widget_ops); 26 $widget_ops = array( 27 'description' => __( 'A cloud of your most used tags.' ), 28 'customize_selective_refresh' => true, 29 ); 30 parent::__construct( 'tag_cloud', __( 'Tag Cloud' ), $widget_ops ); 28 31 } 29 32 30 33 /** -
src/wp-includes/widgets/class-wp-widget-text.php
diff --git src/wp-includes/widgets/class-wp-widget-text.php src/wp-includes/widgets/class-wp-widget-text.php index 5a1a056..a8ec3d4 100644
class WP_Widget_Text extends WP_Widget { 23 23 * @access public 24 24 */ 25 25 public function __construct() { 26 $widget_ops = array('classname' => 'widget_text', 'description' => __('Arbitrary text or HTML.')); 27 $control_ops = array('width' => 400, 'height' => 350); 28 parent::__construct('text', __('Text'), $widget_ops, $control_ops); 26 $widget_ops = array( 27 'classname' => 'widget_text', 28 'description' => __( 'Arbitrary text or HTML.' ), 29 'customize_selective_refresh' => true, 30 ); 31 $control_ops = array( 'width' => 400, 'height' => 350 ); 32 parent::__construct( 'text', __( 'Text' ), $widget_ops, $control_ops ); 29 33 } 30 34 31 35 /** -
tests/phpunit/tests/customize/manager.php
diff --git tests/phpunit/tests/customize/manager.php tests/phpunit/tests/customize/manager.php index 6f5789d..0b86b4c 100644
class Tests_WP_Customize_Manager extends WP_UnitTestCase { 425 425 $data = json_decode( $json, true ); 426 426 $this->assertNotEmpty( $data ); 427 427 428 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices' , 'selectiveRefreshEnabled'), array_keys( $data ) );428 $this->assertEqualSets( array( 'theme', 'url', 'browser', 'panels', 'sections', 'nonce', 'autofocus', 'documentTitleTmpl', 'previewableDevices' ), array_keys( $data ) ); 429 429 $this->assertEquals( $autofocus, $data['autofocus'] ); 430 430 $this->assertArrayHasKey( 'save', $data['nonce'] ); 431 431 $this->assertArrayHasKey( 'preview', $data['nonce'] ); -
tests/phpunit/tests/customize/widgets.php
diff --git tests/phpunit/tests/customize/widgets.php tests/phpunit/tests/customize/widgets.php index c485b36..2e572bf 100644
class Tests_WP_Customize_Widgets extends WP_UnitTestCase { 43 43 remove_action( 'after_setup_theme', 'twentyfifteen_setup' ); 44 44 remove_action( 'after_setup_theme', 'twentysixteen_setup' ); 45 45 remove_action( 'customize_register', 'twentysixteen_customize_register', 11 ); 46 add_theme_support( 'customize-selective-refresh-widgets' ); 46 47 47 48 $this->backup_registered_sidebars = $GLOBALS['wp_registered_sidebars']; 48 49 }