上下文相关控制、区块与面板

WordPress 4.0与4.1还新增了功能,可根据用户在自定义器预览窗口中查看的网站部分,决定显示或隐藏自定义器界面中的某些元素。一个简单的上下文相关控制示例是:主题在首页仅显示标题图片和站点标语。这正是自定义器管理器中get_方法的理想应用场景,因为我们可以直接修改这些设置的默认控制项,使其与首页的上下文相匹配:

// 当当前页面未使用某些核心区块/控制项时将其隐藏。
$wp_customize->get_section( 'header_image' )->active_callback = 'is_front_page';
$wp_customize->get_control( 'blogdescription' )->active_callback = 'is_front_page';
在这个上下文相关控制示例中,主题在首页仅显示站点标语,因此当用户在预览窗口中切换到其他页面时,自定义器中的对应字段会被隐藏。

对于面板、区块和控制项而言,active_callback参数接受一个回调函数名称,可以是核心自带的函数,也可以是用户自定义的函数。在注册自定义对象时也可以设置该参数。以下是Twenty Fourteen主题的示例:

$wp_customize->add_section( 'featured_content', array(
  'title'       => __( '特色内容', 'twentyfourteen' ),
  'description' => //...
  'priority'        => 130,
  'active_callback' => 'is_front_page',
) );

在上面的示例中,直接使用了is_front_page函数。但对于更复杂的逻辑,比如判断当前视图是否为某个页面(甚至是特定ID的页面),则可以使用自定义函数(详情可参见#30251)。如果无需支持PHP 5.2,也可以将相关逻辑写在代码内部:

'active_callback' => function () { return is_page(); }

若需要支持PHP 5.2,只需创建一个命名函数,然后通过active_callback参数引用该函数即可:

//...
'active_callback' => 'prefix_return_is_page';
//...
function prefix_return_is_page() {
  return is_page();
}

在自定义控制项、区块和面板中,还可以直接在自定义的自定义器对象类内部覆盖active_callback函数:

class WP_Customize_Greeting_Control extends WP_Customize_Control {
  // ...
  function active_callback() {
    return is_front_page();
  }
}

最后,还有一个过滤器可用于覆盖所有其他active_callback相关的行为:

// 在预览单个文章时,隐藏没有描述信息的所有控制项。
function title_tagline_control_filter( $active, $control ) {
  if ( '' === $control->description ) {
    $active = is_singular();
  }
  return $active;
}
add_filter( 'customize_control_active', 'title_tagline_control_filter', 10, 2 );

需要注意的是,active_callback API对于所有类型的自定义器对象(控制项、区块以及面板)都适用相同的机制。此外,如果某个区块内的所有控制项都被设置为根据上下文隐藏,那么该区块也会自动被隐藏,面板也是如此。

选择性刷新:快速、精准的更新

选择性刷新功能于WordPress 4.5引入,它只在自定义器“预览”界面中更新那些设置值发生变动的区域。由于仅更新实际发生变化的元素,因此相比完整的iframe刷新方式,它的速度更快,对正常使用的干扰也更小。正如《自定义器中的选择性刷新》一文所述,它的其他优势还包括:

  • 遵循DRY原则,减少代码重复
  • 实现精准的预览更新
  • 将预览的不同部分与对应的设置和控制项建立关联,同时从WordPress 4.7开始还支持显示编辑快捷方式

对于纯JavaScript实现的postMessage更新方式来说,存在代码重复的问题。自定义器中的JavaScript代码必须与生成标记的PHP代码保持一致,或者采用简化方式来近似实现相同功能。而选择性刷新则不存在这种重复问题,因为JavaScript和PHP代码都没有重复。它通过Ajax请求来获取预览所需的新标记内容。

由于采用了Ajax调用,因此这种刷新方式是精准的。它会利用各种过滤器来调整标记内容,从而显示出与前端完全一致的结果。

此外,选择性刷新功能还能在预览的不同区域与对应的设置之间建立关联。自定义器会利用这种关联关系,提供可见的编辑快捷方式,帮助用户快速找到与网站特定部分相关的控制项。未来,这部分API还有可能进一步扩展,以便在预览界面内直接编辑设置,同时提供结构化的JS API来辅助使用这些部分进行设置预览。

基于以上原因,强烈建议所有主题都采用选择性刷新机制来提升用户体验,同时也可以选择额外的基于JavaScript的传输方式,进一步优化设置预览的效果。

注册自定义部件

若要使用选择性刷新功能对主题侧边栏中的部件进行部分刷新,就需要先注册相应的自定义部件。以下示例主要取自Twenty Sixteen主题,它是通过为blogdescription设置添加一个同名自定义部件来实现选择性刷新的。

function foo_theme_customize_register( WP_Customize_Manager $wp_customize ) {
    $wp_customize->selective_refresh->add_partial( 'blogdescription', array(
        'selector' => '.site-description',
        'container_inclusive' => false,
        'render_callback' => function() {
            bloginfo( 'description' );
        },
    ) );
}
add_action( 'customize_register', 'foo_theme_customize_register' );

如果未指定settings参数,它将会默认与自定义部件的ID相同,就像控制项的设置默认使用控制项的ID一样。以下是自定义部件的一些关键参数:

变量名类型描述
settings数组与该自定义部件关联的设置ID。
selector字符串用于指定页面标记中需要刷新的元素。
container_inclusive布尔值如果设置为true,刷新操作将会替换整个容器;否则仅替换容器内的子元素。默认值为false。
render_callback函数用于指定刷新时需要生成的标记内容。
fallback_refresh布尔值如果文档中找不到该自定义部件,则是否需要执行整个页面的刷新操作。

选择性刷新的JavaScript事件

以下事件会在wp.customize.selectiveRefresh触发:

  • partial-content-rendered
    当对应元素被渲染时触发。如前所述,基于JavaScript的插件可以在此事件触发时重新生成内容。
  • render-partials-response
    在请求部分内容渲染之后,数据返回时触发。服务器会通过‘customize_render_partials_response’过滤器来处理这些数据。
  • partial-content-moved
    当侧边栏中的某个插件位置发生变化时触发。同样,基于JavaScript的插件可以在此事件触发时刷新自身。
  • widget-updated
    当WidgetPartial通过其renderContent方法被刷新时触发。
  • sidebar-updated
    当侧边栏中的某个插件被刷新或更新,或者侧边栏中的插件顺序被调整时触发,这一操作是通过reflowWidgets()函数实现的。

插件:选择是否启用选择性刷新

无论是主题还是插件,都必须主动选择才能使用选择性刷新功能。所有的核心插件和主题都已经启用了这一功能。

侧边栏中的主题支持

若要让主题侧边栏中的插件支持部分刷新功能,需要执行以下操作:

add_theme_support( 'customize-selective-refresh-widgets' );

重要提示:要为插件启用选择性刷新,主题必须为每个包含该插件的元素添加before_widget>/after_widget包装元素,这些元素中需要包含该插件的ID。当你使用register_sidebar()函数时,这些包装元素就是默认存在的。例如:

function example_widgets_init() {
	register_sidebar(
		array(
			'name'          => esc_html__( '侧边栏', 'example' ),
			'id'            => 'sidebar-1',
			'description'   => esc_html__( '在此处添加插件。', 'example' ),
			'before_widget' => '<section id="%1$s" class="widget %2$s">', // <= 用于选择性刷新的关键元素。
			'after_widget'  => '</section>',
			'before_title'  => '<h2 class="widget-title">',
			'after_title'   => '</h2>',
		)
	);
}
add_action( 'widgets_init', 'example_widgets_init' );

插件的支持情况

即便某个主题支持选择性刷新功能,插件也必须主动启用该功能。所有的核心插件都已经启用了这一功能。以下是一个为插件添加选择性刷新支持的示例:

class Foo_Widget extends WP_Widget {

    public function __construct() {
        parent::__construct(
            ‘foo’,
            __( '示例插件', 'bar-plugin' ),
            array(
                'description' => __( ‘这是一个示例插件’, ‘bar-plugin’ ),
                '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' ) );
        }
    }
    ...

第9行代码用于启用选择性刷新功能:

'customize_selective_refresh' => true,

第13行代码则确保插件的样式表会在自定义器预览界面中始终被加载。添加该插件时无需通过完整页面刷新来获取样式表:

if ( is_active_widget( false, false, $this->id_base ) || is_customize_preview() ) {
    add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}

更多相关内容可参阅《为插件实现选择性刷新支持》这篇文章。

基于JavaScript的插件支持

那些依赖JavaScript来生成标记的插件需要额外处理,相关说明可见《为插件实现选择性刷新支持》一文:

    1. 需要根据is_customize_preview()的结果,像处理样式表那样加载相应的JavaScript文件。
    2. 需要为partial-content-rendered事件添加处理函数,以便在需要时刷新插件内容:
wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
    // 用于刷新的逻辑
} );
    1. 如果插件内部使用了iframe,还需要为partial-content-moved事件添加处理函数,以便在相应元素位置变化时刷新插件内容:
wp.customize.selectiveRefresh.bind( 'partial-content-moved', function( placement ) {
    // 用于刷新的逻辑,可以是条件判断式的刷新逻辑
}

使用postMessage提升设置预览效果

自定义器默认就能实现所有设置的预览功能。它是通过悄悄重新加载整个预览窗口来实现的,而设置值的过滤工作则由该Ajax请求中的PHP代码来完成。虽然这种方式也能正常使用,但由于每次设置值发生变化时都需要重新加载整个前端界面,因此速度较慢。选择性刷新功能通过仅刷新实际发生变化的元素来提升体验,不过由于仍需通过Ajax调用,因此在预览界面中看到更改仍会存在一定的延迟。

为了进一步提升用户体验,自定义器还提供了一个API,允许在JavaScript层面直接管理设置变更,从而实现近乎实时的预览效果。下图展示了使用基于postMessage技术的自定义CSS设置选项与默认刷新选项的对比效果:

使用postMessage传输方式的自定义CSS设置选项。
使用默认刷新方式的自定义CSS设置选项。

若要使用postMessage功能,首先在添加设置时将传输方式设置为postMessage即可。许多主题还会通过修改相关设置的transport属性,来启用postMessage功能,从而对标题和站点标语等设置进行优化:

$wp_customize->get_setting( 'blogname' )->transport        = 'postMessage';
$wp_customize->get_setting( 'blogdescription' )->transport = 'postMessage';

一旦某个设置的传输方式被设置为postMessage,那么当该设置的值发生变化时,就不会再触发预览界面的刷新。若要在预览界面中实时更新该设置的内容,首先需要创建并加载一个JavaScript文件:

function my_preview_js() {
  wp_enqueue_script( 'custom_css_preview', 'path/to/file.js', array( 'customize-preview', 'jquery' ) );
}
add_action( 'customize_preview_init', 'my_preview_js' );

你的JavaScript文件大致应如下所示:

( function( $ ) {
  wp.customize( 'setting_id', function( value ) {
    value.bind( function( to ) {
      $( '#custom-theme-css' ).html( to );
    } );
  } );
  wp.customize( 'custom_plugin_css', function( value ) {
    value.bind( function( to ) {
      $( '#custom-plugin-css' ).html( to );
    } );
  } );
} )( jQuery );

需要注意的是,使用postMessage功能并不要求使用者必须精通JavaScript——大部分代码都是标准模板代码。那些最能从postMessage传输方式中受益的设置,只需通过简单的JavaScript操作即可实现,比如使用jQuery的.html()或.text()方法,或者通过修改

元素或其他元素的类名来触发不同的CSS规则。通过这种方式,或者通过选择性刷新功能实现更精准的实时预览,都可以提升用户体验,而无需在JavaScript中重复实现所有的PHP逻辑。

通知功能

错误通知
通知功能主要用于向用户提供反馈,这些反馈通常是基于某个控制项的设置值而产生的。当某个设置的验证流程返回一个WP_Error实例时,该设置对应的notifications集合中就会添加一条错误通知。在PHP中,每个WP_Error实例对应的错误信息,在JavaScript中则表现为一个wp.customize.Notification对象:

  • WP_Error对象的code属性,在JavaScript中对应为notification.code。
  • WP_Error对象的message属性,在JavaScript中对应为notification.message。需要注意的是,如果在PHP中为同一个错误代码添加了多条错误信息,那么在JavaScript中这些信息会合并成一条消息显示。
  • WP_Error对象的data属性,在JavaScript中对应为notification.data。这一属性可用于将服务器端的额外错误上下文传递给客户端。

每当服务器端的验证流程返回一个WP_Error实例时,就会对应生成一个type属性值为“error”的wp.customize.Notification对象。

虽然目前PHP还不支持设置非错误类型的通知(参见#37281),但也可以通过JavaScript来添加非错误类型的通知,具体实现方式如下:

wp.customize( 'blogname', function( setting ) {
    setting.bind( function( value ) {
        var code = 'long_title';
        if ( value.length > 20 ) {
            setting.notifications.add( code, new wp.customize.Notification(
                code,
                {
                    type: 'warning',
                    message: '该主题建议标题长度不超过20个字符。'
                }
            ) );
        } else {
            setting.notifications.remove( code );
        }
    } );
} );

你还可以将“info”作为通知的type值。默认的type值为“error”。当然也可以自定义其他类型,而且这些通知可以通过CSS选择器进行样式设置,样式类名格式为notice.notice-类型名,其中“类型名”就是用户自定义的类型值。某个控制项还可以通过覆盖wp.customize.Control.renderNotifications方法,来自定义通知的显示方式。