提高用户体验工具

上下文控件、板块和面板

WordPress 4.0和4.1还增加了支持自定义界面的部分可见或隐藏,这取决用户在自定义预览窗口中预览网站的某部分。一个简单的上下文控件例子是:你的主题只在首页显示页眉图片和网站标语。这是定制器管理的 get_ 方法的一个完美用例,因为我们可以直接修改这些设置的核心控件,使它们与前台页面相关联。

// Hide core sections/controls when they aren't used on the current page.
$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'       => __( 'Featured Content', 'twentyfourteen' ),
  'description' => //...
  'priority'        => 130,
  'active_callback' => 'is_front_page',
) );

在前面的例子中,is_front_page被直接使用。但对于更复杂的逻辑,例如检查当前视图是否是一个页面(甚至是一个特定的页面,通过id),可以使用自定义函数。如果你不需要支持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行为:

// Hide all controls without a description when previewing single posts.
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中引入的选择性刷新,在定制器的"预览"中只刷新相关设置被改变的区域。通过只更新发生了变化的元素,它比全框架刷新要快得多,而且破坏性小。定制器中的选择性刷新的其他一些好处是:

  • 不要重复自己(DRY)的逻辑
  • 准确的预览更新
  • 预览部分与相关设置和控件之间的关联,以及自WordPress 4.7起可见的编辑快捷方式

纯JavaScript的postMessage更新中的逻辑是重复的。定制器中的JavaScript必须反映产生标记的PHP,或者采取捷径来接近它。但选择性刷新是DRY的,因为没有重复的JavaScript和PHP。一个Ajax请求为预览检索了新的标记。

而且由于这个Ajax调用,刷新是准确的。它使用可以改变标记的过滤器,显示的结果与出现在前端的结果相同。

此外,选择性的刷新参数在预览区域和它们相应的设置之间提供了一种关联。定制器利用这种关系来提供可见的编辑快捷方式,帮助用户找到与他们网站的特定部分相关的控制。在未来,参数API可以扩展,以方便直接在预览中编辑设置,并包括一个结构化的JS API,用于预览参数的设置。

由于这些原因,强烈建议所有设置利用选择性刷新传输,以改善用户体验,并可选择提供额外的基于JavaScript的传输,以进一步加强设置预览。

 

注册 Partials

设置预览需要通过注册必要的partials来使用选择性刷新。这个例子来自Twenty Sixteen,为blogdescription这个“设置”,添加一个同名的partial来为其添加选择性刷新。

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参数,则默认为与partial ID相同,这与控件的settings默认为控件ID的方式相同。下面是partial参数的一些关键参数。

Variable Type Description
settings array 与partial相关联的设置ID
selector string 目标是要刷新的页面标记中的元素
container_inclusive boolean 如果true,刷新会替换整个容器。否则,它只替换容器的子元素。默认为false
render_callback function 产生刷新时要渲染的标记
fallback_refresh bool 如果在文档中没有找到partial内容,是否进行全页面刷新

 

选择性刷新的JavaScript事件

这些都是在wp.customize.selectiveRefresh上启动的:

  • partial-content-rendered
    当放置的位置被渲染。如前所述,JavaScript驱动的小工具可以在这个事件上重新构建。
  • render-partials-response
    在请求partial渲染后,当数据被返回时,服务器用‘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__( 'Sidebar', 'example' ),
            'id'            => 'sidebar-1',
            'description'   => esc_html__( 'Add widgets here.', 'example' ),
            'before_widget' => '<section id="%1$s" class="widget %2$s">', // <= Key for selective refresh.
            '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’,
            __( 'Example', 'bar-plugin' ),
            array(
                'description' => __( ‘An example widget’, ‘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 ) {
    // logic to refresh
} );
  1. 如果小工具包含一个iframe,添加一个处理程序来局部刷新:
wp.customize.selectiveRefresh.bind( 'partial-content-moved', function( placement ) {
    // logic to refresh, perhaps conditionally
}

 

使用PostMessage来改进设置预览

定制器自动处理预览所有的设置,即开即用。这是通过静默地重新加载整个预览窗口来完成的,在这个Ajax调用中,设置被PHP过滤了。虽然这样做很好,但可能会很慢,因为整个前端必须为每一个设置的变化重新加载。选择性刷新通过只刷新发生变化的元素来改善这种体验,但由于Ajax调用,在预览中看到这些变化仍然有延迟。

为了进一步改善用户体验,定制器提供了一个API,可以直接在JavaScript中管理设置的变化,允许真正的实时预览。下面的图片显示了利用这一技术的自定义CSS选项(称为postMessage)与标准刷新选项的比较:

自定义CSS设置与postMessage设置交互。

自定义CSS设置与默认刷新设置交互。

要使用postMessage,首先在添加设置时将transport参数设置为postMessage。许多主题还修改了核心设置,如标题和标语,通过修改这些设置的transport属性来使用postMessage:

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

一旦设置的transport被设为postMessage,当其值发生变化时,设置将不再触发预览的刷新。要实现在前端预览中更新设置的JavaScript,首先要创建并引入一个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 );

请注意,你不一定需要精通JavaScript来使用postMessage,大部分的代码都是模板。从postMessage传输中受益最多的设置类型需要简单的JS变化,如使用jQuery的.html()或.text()方法,或在<body>或其他元素上交换一个类,以触发一组不同的CSS规则。做到这一点,或者用选择性刷新更新的完全准确的变化来简化即时预览逻辑,用户体验可以很快,而不用在JS中重复所有的PHP逻辑。

 

设置验证

WordPress 4.6包含与验证定制器设置值有关的新API。自从定制器被引入以来,它已经对设置值进行了净化处理。净化涉及到将一个值强制转化为安全的东西以插入到数据库中:常见的例子是将一个值转化为整数或从一些文本输入中剥离标签。因此,随着设置验证的增加,净化是一个有损失的操作。

  1. 所有修改过的设置在保存之前都会被预先验证。
  2. 如果有任何设置是无效的,定制器的保存请求就会被拒绝:这样一来,保存就变成了事务性的,所有的设置都是未净化的,可以再次尝试保存。(定制器事务建议在这里与设置验证密切相关)。
  3. 验证错误信息会显示给用户,提示他们修正错误并再次尝试。

通过WP_REST_Request::sanitize_params()WP_REST_Request::validate_params(),净化和验证也都是REST API基础的一部分。一个设置的值在经过净化之前要经过验证。

关于验证行为的更多信息,以及更多的代码示例,请参见功能公告文章

 

在PHP中验证设置

就像你在注册设置时可以提供sanitize_callback一样,你也可以提供一个validate_callback参数。

$wp_customize->add_setting( 'established_year', array(
    'sanitize_callback' => 'absint',
    'validate_callback' => 'validate_established_year'
) );
function validate_established_year( $validity, $value ) {
    $value = intval( $value );
    if ( empty( $value ) || ! is_numeric( $value ) ) {
        $validity->add( 'required', __( 'You must supply a valid year.' ) );
    } elseif ( $value &lt; 1900 ) {
        $validity->add( 'year_too_small', __( 'Year is too old.' ) );
    } elseif ( $value > gmdate( 'Y' ) ) {
        $validity->add( 'year_too_big', __( 'Year is too new.' ) );
    }
    return $validity;
}

就像提供一个sanitize_callback参数为customize_sanitize_{$setting_id}增加一个过滤器一样,提供一个validate_callback参数也会为customize_validate_{$setting_id}增加一个过滤器。假设WP_Customize_Setting实例在它们的validate方法中应用了这些过滤器,如果你需要为之前添加的设置添加验证,你可以添加这个过滤器。

validate_callback和任何customize_validate_{$setting_id}过滤器回调的第一个参数是WP_Error实例(最初是空的,没有添加任何错误),其次是被净化的$value,最后是被验证的WP_Customize_Setting实例。

自定义设置类也可以直接覆盖设置类的validate方法。

 

客户端验证

如果你有一个纯粹通过JavaScript预览的设置(以及没有选择性刷新的postMessage交互),你也应该添加客户端验证。否则,任何验证错误都会持续存在,直到发生完全刷新或尝试保存。客户端验证不能取代服务器端验证,因为如果没有相应的服务器端验证,恶意的用户可以绕过客户端验证来保存一个无效的值。

wp.customize.Setting JS类(实际上是wp.customize.Value基类)中,有一个validate方法。它的名字有点误导,因为它的行为实际上与WP_Customize_Setting::sanitize() PHP方法非常相似,但它可以用来在JS中对一个值进行净化和验证。请注意,这个JS是在定制器窗体中运行的,而不是在预览中,所以任何这样的JS都应该把customize-controls作为一个依赖项(而非customize-preview),并在customize_controls_enqueue_scripts动作中被引入。一些JS验证的例子:

wp.customize( 'established_year', function ( setting ) {
    setting.validate = function ( value ) {
        var code, notification;
        var year = parseInt( value, 10 );

        code = 'required';
        if ( isNaN( year ) ) {
            notification = new wp.customize.Notification( code, {message: myPlugin.l10n.yearRequired} );
            setting.notifications.add( code, notification );
        } else {
            setting.notifications.remove( code );
        }

        code = 'year_too_small';
        if ( year &lt; 1900 ) {
            notification = new wp.customize.Notification( code, {message: myPlugin.l10n.yearTooSmall} );
            setting.notifications.add( code, notification );
        } else {
            setting.notifications.remove( code );
        }

        code = 'year_too_big';
        if ( year > new Date().getFullYear() ) {
            notification = new wp.customize.Notification( code, {message: myPlugin.l10n.yearTooBig} );
            setting.notifications.add( code, notification );
        } else {
            setting.notifications.remove( code );
        }

        return value;
    };
} );

 

通知

Error notification

通知提供用户反馈,通常是基于一个控件的设置值。当一个设置的验证程序返回一个WP_Error实例时,一个错误通知会被添加到设置的通知集合中。每个添加到PHP WP_Error实例的错误在JavaScript中被表示为一个wp.customize.Notification

  • WP_Errorcode可以在JS中作为notification.code使用
  • WP_Errormessage可以在JS中作为notification.message使用。请注意,如果在PHP中为一个给定的错误代码添加了多个信息,那么在JS中它们将被串联成一个信息。
  • WP_Errordata可以在JS中作为notification.data使用。这对于从服务器到客户端传递额外的错误上下文是非常有用的。

任何时候,如果服务器上的验证程序返回WP_Error,都会导致创建一个wp.customize.Notification,其type属性为 "error"。

虽然目前不支持从PHP中设置非错误通知(见#37281),但你也可以用JS添加非错误通知,如下所示:

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: 'This theme prefers title with max 20 chars.'
                }
            ) );
        } else {
            setting.notifications.remove( code );
        }
    } );
} );

你也可以提供"info"作为通知的type。默认的type是"error"。也可以提供自定义的类型,通知可以使用与notice.notice-foo相匹配的CSS选择器进行样式设计,其中"foo"是提供的类型。控件也可以通过覆盖wp.customize.Control.renderNotifications方法来重写通知的默认行为。