如何利用`WP_Widget`和`WP_Customize_Control`构建现代化的主题小工具?

构建现代化的 WordPress 主题小工具:WP_WidgetWP_Customize_Control 的深度整合

各位同学,今天我们来深入探讨如何利用 WP_WidgetWP_Customize_Control 构建现代化的 WordPress 主题小工具。我们的目标是创建一个既能在后台小工具管理界面良好运行,又能通过主题定制器实时预览的小工具。

1. 奠定基石:WP_Widget

首先,我们需要继承 WP_Widget 类,这是构建任何 WordPress 小工具的基础。这个类提供了小工具的基本框架,包括注册、表单展示、更新数据和前端显示。

<?php

class My_Modern_Widget extends WP_Widget {

  /**
   * 构造函数.
   */
  public function __construct() {
    parent::__construct(
      'my_modern_widget', // Base ID
      __( 'My Modern Widget', 'textdomain' ), // Name
      array( 'description' => __( 'A modern widget example.', 'textdomain' ) ) // Args
    );
  }

  /**
   * 小工具前端显示.
   *
   * @param array $args     Display arguments defined by the theme.
   * @param array $instance The settings for the particular instance of the widget.
   */
  public function widget( $args, $instance ) {
    $title = apply_filters( 'widget_title', $instance['title'] );

    echo $args['before_widget'];
    if ( ! empty( $title ) ) {
      echo $args['before_title'] . $title . $args['after_title'];
    }
    echo __( 'Hello, Modern Widget!', 'textdomain' );
    echo $args['after_widget'];
  }

  /**
   * 后台小工具设置表单.
   *
   * @param array $instance Previously saved values from database.
   */
  public function form( $instance ) {
    $title = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : '';
    ?>
    <p>
      <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
      <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
    </p>
    <?php
  }

  /**
   * 更新小工具设置.
   *
   * @param array $new_instance Values just sent to be saved.
   * @param array $old_instance Previously saved values from database.
   *
   * @return array Updated safe values to be saved.
   */
  public function update( $new_instance, $old_instance ) {
    $instance = array();
    $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : '';

    return $instance;
  }

} // class My_Modern_Widget

// 注册小工具
function register_my_modern_widget() {
    register_widget( 'My_Modern_Widget' );
}
add_action( 'widgets_init', 'register_my_modern_widget' );

这段代码定义了一个名为 My_Modern_Widget 的小工具,它包含:

  • 构造函数 __construct(): 定义小工具的 ID、名称和描述。
  • widget() 函数: 负责在前端显示小工具的内容。
  • form() 函数: 生成后台小工具设置表单,允许用户配置小工具。
  • update() 函数: 处理用户提交的设置数据,并进行验证和清理。
  • register_my_modern_widget() 函数: 注册小工具,使其在 WordPress 后台可用。

2. 增强体验:WP_Customize_Control 集成

虽然 WP_Widget 提供了基本的小工具框架,但其后台表单界面较为简陋。为了提供更现代、更友好的用户体验,特别是实现实时预览功能,我们需要将 WP_Customize_Control 集成到我们的widget中。

步骤 1: 创建自定义控制类

首先,我们需要创建一个继承自 WP_Customize_Control 的自定义控制类。这个类负责定义在主题定制器中显示的控件类型和行为。

<?php

class My_Modern_Widget_Customize_Control extends WP_Customize_Control {

  public $type = 'my_custom_control'; // 定义控件类型

  public function render_content() {
    ?>
    <label>
      <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
      <input type="text" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); ?> />
    </label>
    <?php
  }
}

这个例子定义了一个简单的文本输入框控件。$type 属性定义了控件的类型,render_content() 函数负责渲染控件的 HTML。$this->label 属性表示控件的标签,$this->value() 函数获取当前控件的值,$this->link() 函数生成 JavaScript 代码,用于将控件的值与主题定制器的设置关联起来,从而实现实时预览功能。

步骤 2: 将自定义控制添加到主题定制器

接下来,我们需要将自定义控制添加到主题定制器。这需要在 customize_register 钩子中完成。

<?php

function my_modern_widget_customize_register( $wp_customize ) {

  // 注册自定义控制
  $wp_customize->register_control_type( 'My_Modern_Widget_Customize_Control' );

  // 为widget添加settings
  $wp_customize->add_setting( 'my_modern_widget_title', array(
    'default' => '',
    'sanitize_callback' => 'sanitize_text_field',
    'transport' => 'postMessage', // 启用实时预览
  ) );

  // 添加控制
  $wp_customize->add_control( new My_Modern_Widget_Customize_Control( $wp_customize, 'my_modern_widget_title', array(
    'label' => __( 'Widget Title (Customize)', 'textdomain' ),
    'section' => 'sidebar-widgets-' . 'my_modern_widget-1', // 重要:将控制关联到widget实例
    'settings' => 'my_modern_widget_title',
  ) ) );
}
add_action( 'customize_register', 'my_modern_widget_customize_register' );

这段代码做了以下几件事:

  • $wp_customize->register_control_type(): 注册我们之前定义的自定义控制类,使其在主题定制器中可用。
  • $wp_customize->add_setting(): 添加一个设置,用于存储文本框的值。sanitize_callback 用于对输入值进行安全过滤。transport 设置为 postMessage 启用实时预览。
  • $wp_customize->add_control(): 添加一个控制,将自定义控制类与设置关联起来。 section 参数非常重要,它指定了控制所属的区域。我们需要将控制关联到特定的小工具实例。这里的 'sidebar-widgets-' . 'my_modern_widget-1' 假设我们将小工具添加到 ID 为 sidebar-widgets 的侧边栏,并且这是该小工具的第一个实例 (ID 为 1)。 这个ID需要动态生成。
  • 关联widget实例 这是最重要的部分. 稍后会讲解如何动态获取widget实例的ID

步骤 3: 更新 WP_Widget 类,使其与主题定制器集成

现在,我们需要修改 WP_Widget 类,使其能够使用主题定制器中的设置。

<?php

class My_Modern_Widget extends WP_Widget {

  // ... (构造函数,widget() 函数)

  /**
   * 后台小工具设置表单.  (废弃)
   *
   * @param array $instance Previously saved values from database.
   */
  public function form( $instance ) {
    // 留空,表单由主题定制器处理
  }

  /**
   * 更新小工具设置. (废弃)
   *
   * @param array $new_instance Values just sent to be saved.
   * @param array $old_instance Previously saved values from database.
   *
   * @return array Updated safe values to be saved.
   */
  public function update( $new_instance, $old_instance ) {
    // 不再处理更新,由主题定制器处理
    return $old_instance;
  }

  /**
   * 动态生成 customize_register 钩子中 section的widget实例ID
   */
  public function id_base_customize_id(){
    return 'sidebar-widgets-' . $this->id;
  }

} // class My_Modern_Widget

关键的修改是:

  • form()update() 函数被留空:我们不再使用 WP_Widget 的表单和更新功能,因为这些功能现在由主题定制器处理。
  • id_base_customize_id()函数: 这个函数动态生成了 customize_register 钩子中 section 需要的 widget实例ID,方便在主题定制器中将控制关联到widget实例.

步骤 4: 在前端使用主题定制器中的设置

最后,我们需要修改 widget() 函数,使其能够从主题定制器中获取设置值,而不是从 $instance 数组中获取。

<?php

  /**
   * 小工具前端显示.
   *
   * @param array $args     Display arguments defined by the theme.
   * @param array $instance The settings for the particular instance of the widget.
   */
  public function widget( $args, $instance ) {
    // 从主题定制器中获取标题
    $title = get_theme_mod( 'my_modern_widget_title' );
    $title = apply_filters( 'widget_title', $title );

    echo $args['before_widget'];
    if ( ! empty( $title ) ) {
      echo $args['before_title'] . $title . $args['after_title'];
    }
    echo __( 'Hello, Modern Widget!', 'textdomain' );
    echo $args['after_widget'];
  }

这里,我们使用 get_theme_mod() 函数从主题定制器中获取 my_modern_widget_title 设置的值。

步骤 5: 启用实时预览

要在主题定制器中启用实时预览,我们需要添加一些 JavaScript 代码。

( function( $ ) {
  wp.customize( 'my_modern_widget_title', function( value ) {
    value.bind( function( newval ) {
      $( '.widget.my_modern_widget .widget-title' ).html( newval ); // 选择器可能需要根据你的主题进行调整
    } );
  } );
} )( jQuery );

这段代码使用 wp.customize() 函数监听 my_modern_widget_title 设置的变化。当设置值发生变化时,它会更新页面上相应元素的 HTML,从而实现实时预览。 你需要根据你的主题结构调整选择器 .widget.my_modern_widget .widget-title。 确保这个选择器能唯一找到你的widget的标题。

步骤 6: 将 Javascript 代码添加到主题

最后,将这段 JavaScript 代码添加到你的主题。最好的方法是将其放在一个单独的 JavaScript 文件中,并通过 wp_enqueue_scripts 函数将其添加到主题。

<?php

function my_theme_enqueue_scripts() {
  wp_enqueue_script( 'my-theme-customizer', get_template_directory_uri() . '/js/customizer.js', array( 'jquery', 'customize-preview' ), false, true );
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_scripts' );

function my_theme_customize_preview_js() {
    wp_enqueue_script( 'my-theme-customizer-preview', get_template_directory_uri() . '/js/customizer-preview.js', array( 'customize-preview' ), false, true );
}
add_action( 'customize_preview_init', 'my_theme_customize_preview_js' );

这里,customize-preview 作为依赖项,确保在主题定制器预览窗口加载时,我们的脚本能够正常运行。

3. 动态生成 Widget 实例 ID

上述代码中,sidebar-widgets-my_modern_widget-1 这样的widget实例ID是硬编码的。为了使我们的widget能够适应不同的侧边栏和多个实例,我们需要动态生成这个ID。

步骤 1: 在 WP_Widget 类中添加方法

我们在 WP_Widget 类中添加一个方法,用于获取 widget 的实例 ID。

<?php

class My_Modern_Widget extends WP_Widget {

  // ... (之前的代码)

  /**
   * 获取 widget 实例 ID
   * @return string
   */
  public function get_customizer_section_id() {
    global $wp_customize;
    $sidebar_id = '';

    // 遍历所有的侧边栏
    foreach ( $wp_registered_sidebars as $sidebar ) {
      // 检查当前 widget 是否在这个侧边栏中
      $found = is_active_widget( false, $this->id_base, $this->id, true );
      if ( $found ) {
        $sidebar_id = $sidebar['id'];
        break;
      }
    }
    return 'sidebar-widgets-' . $this->id;
  }

}

这个函数遍历所有注册的侧边栏,并检查当前 widget 是否在这个侧边栏中。如果找到 widget 所在的侧边栏,它会返回一个形如 sidebar-widgets-my_modern_widget-1 的字符串,这个字符串就是我们在主题定制器中需要的 section ID。

步骤 2: 在 customize_register 钩子中使用动态 ID

customize_register 钩子中,我们使用新添加的 get_customizer_section_id() 方法来获取动态 ID。

<?php

function my_modern_widget_customize_register( $wp_customize ) {

  // 注册自定义控制
  $wp_customize->register_control_type( 'My_Modern_Widget_Customize_Control' );

  // 获取widget 实例
  $widgets = $wp_customize->widgets->widgets;
  foreach($widgets as $widget){
    if($widget->id_base == 'my_modern_widget'){
      // 为widget添加settings
      $wp_customize->add_setting( 'my_modern_widget_title_' . $widget->id, array(
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
        'transport' => 'postMessage', // 启用实时预览
      ) );

      // 添加控制
      $wp_customize->add_control( new My_Modern_Widget_Customize_Control( $wp_customize, 'my_modern_widget_title_' . $widget->id, array(
        'label' => __( 'Widget Title (Customize)', 'textdomain' ),
        'section' => $widget->get_customizer_section_id(), // 使用动态生成的 ID
        'settings' => 'my_modern_widget_title_' . $widget->id,
      ) ) );
    }
  }

}
add_action( 'customize_register', 'my_modern_widget_customize_register' );

我们遍历 $wp_customize->widgets->widgets 数组,找到 id_basemy_modern_widget 的 widget, 然后调用get_customizer_section_id()方法获取动态生成的 section ID,并将它传递给 add_control() 函数。

步骤 3: 更新前端代码

由于我们现在使用了动态生成的设置 ID,我们需要更新前端 JavaScript 代码,使其能够匹配新的 ID。

( function( $ ) {
  $(document).ready(function(){
    $('.widget.my_modern_widget').each(function(){
      var widget_id = $(this).attr('id');
      wp.customize( 'my_modern_widget_title_' + widget_id, function( value ) {
        value.bind( function( newval ) {
          $( '#' + widget_id + ' .widget-title' ).html( newval ); // 选择器可能需要根据你的主题进行调整
        } );
      } );
    });
  });
} )( jQuery );

这段代码首先找到页面上所有 class 为 widget my_modern_widget 的元素,然后遍历这些元素,获取它们的 ID,并使用这个 ID 来监听相应的设置变化。

4. 更复杂的控件

上面的例子只是一个简单的文本输入框。WP_Customize_Control 提供了各种控件类型,例如:

  • WP_Customize_Color_Control: 颜色选择器。
  • WP_Customize_Image_Control: 图片上传器。
  • WP_Customize_Dropdown_Pages_Control: 页面下拉菜单。

你还可以创建自己的自定义控件,以满足更复杂的需求。

示例:颜色选择器

<?php

class My_Modern_Widget_Color_Control extends WP_Customize_Control {
  public $type = 'color';

  public function render_content() {
    ?>
    <label>
      <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
      <input type="color" <?php $this->link(); ?> value="<?php echo esc_attr( $this->value() ); ?>" />
    </label>
    <?php
  }
}

使用方法与之前的文本输入框类似,只需将 type 属性设置为 color,并使用 <input type="color"> 元素。

5. 代码组织与最佳实践

  • 将代码分解为单独的文件: 将 WP_Widget 类、自定义控制类和 JavaScript 代码放在单独的文件中,以提高代码的可维护性。
  • 使用命名空间: 使用命名空间可以避免与其他插件或主题发生命名冲突。
  • 添加注释: 清晰的注释可以帮助你和其他开发者理解代码的功能。
  • 使用安全过滤: 始终对用户输入进行安全过滤,以防止 XSS 攻击。
  • 测试: 充分测试你的小工具,以确保其在各种情况下都能正常运行。

代码示例总结

以下是一个更完整的代码示例,展示了如何使用 WP_WidgetWP_Customize_Control 构建一个带有标题和背景颜色选项的现代化小工具。

my-modern-widget.php (主插件文件)

<?php
/*
Plugin Name: My Modern Widget
Description: A modern widget example using WP_Widget and WP_Customize_Control.
Version: 1.0
Author: Your Name
*/

// 包含 widget 类
require_once plugin_dir_path( __FILE__ ) . 'includes/class-my-modern-widget.php';

// 注册 widget
function register_my_modern_widget() {
    register_widget( 'My_Modern_Widget' );
}
add_action( 'widgets_init', 'register_my_modern_widget' );

// 包含自定义定制器文件
require_once plugin_dir_path( __FILE__ ) . 'includes/my-modern-widget-customizer.php';

// 添加脚本
function my_modern_widget_enqueue_scripts() {
  wp_enqueue_script( 'my-modern-widget-customizer-preview', plugin_dir_url( __FILE__ ) . 'js/customizer-preview.js', array( 'jquery', 'customize-preview' ), false, true );
}
add_action( 'wp_enqueue_scripts', 'my_modern_widget_enqueue_scripts' );

includes/class-my-modern-widget.php (Widget 类)

<?php
class My_Modern_Widget extends WP_Widget {

  public function __construct() {
    parent::__construct(
      'my_modern_widget',
      __( 'My Modern Widget', 'textdomain' ),
      array( 'description' => __( 'A modern widget with customizer integration.', 'textdomain' ) )
    );
  }

  public function widget( $args, $instance ) {
    $title = get_theme_mod( 'my_modern_widget_title_' . $this->id, '' );
    $title = apply_filters( 'widget_title', $title );
    $background_color = get_theme_mod( 'my_modern_widget_background_color_' . $this->id, '#ffffff' );

    echo $args['before_widget'];
    echo '<div style="background-color:' . esc_attr( $background_color ) . ';">';
    if ( ! empty( $title ) ) {
      echo $args['before_title'] . $title . $args['after_title'];
    }
    echo __( 'Hello, Modern Widget!', 'textdomain' );
    echo '</div>';
    echo $args['after_widget'];
  }

  public function form( $instance ) {
    // Empty, handled by customizer
  }

  public function update( $new_instance, $old_instance ) {
    return $old_instance; // Handled by customizer
  }

  public function get_customizer_section_id() {
    global $wp_customize;
    $sidebar_id = '';

    foreach ( $wp_registered_sidebars as $sidebar ) {
      $found = is_active_widget( false, $this->id_base, $this->id, true );
      if ( $found ) {
        $sidebar_id = $sidebar['id'];
        break;
      }
    }
    return 'sidebar-widgets-' . $this->id;
  }
}

includes/my-modern-widget-customizer.php (Customizer 集成)

<?php

class My_Modern_Widget_Customize_Control extends WP_Customize_Control {
  public $type = 'my_custom_control';

  public function render_content() {
    ?>
    <label>
      <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
      <input type="text" value="<?php echo esc_attr( $this->value() ); ?>" <?php $this->link(); ?> />
    </label>
    <?php
  }
}

class My_Modern_Widget_Color_Control extends WP_Customize_Control {
  public $type = 'color';

  public function render_content() {
    ?>
    <label>
      <span class="customize-control-title"><?php echo esc_html( $this->label ); ?></span>
      <input type="color" <?php $this->link(); ?> value="<?php echo esc_attr( $this->value() ); ?>" />
    </label>
    <?php
  }
}

function my_modern_widget_customize_register( $wp_customize ) {

  $wp_customize->register_control_type( 'My_Modern_Widget_Customize_Control' );
  $wp_customize->register_control_type( 'My_Modern_Widget_Color_Control' );

  $widgets = $wp_customize->widgets->widgets;
  foreach($widgets as $widget){
    if($widget->id_base == 'my_modern_widget'){
      $wp_customize->add_setting( 'my_modern_widget_title_' . $widget->id, array(
        'default' => '',
        'sanitize_callback' => 'sanitize_text_field',
        'transport' => 'postMessage',
      ) );

      $wp_customize->add_control( new My_Modern_Widget_Customize_Control( $wp_customize, 'my_modern_widget_title_' . $widget->id, array(
        'label' => __( 'Widget Title (Customize)', 'textdomain' ),
        'section' => $widget->get_customizer_section_id(),
        'settings' => 'my_modern_widget_title_' . $widget->id,
      ) ) );

      $wp_customize->add_setting( 'my_modern_widget_background_color_' . $widget->id, array(
        'default' => '#ffffff',
        'sanitize_callback' => 'sanitize_hex_color',
        'transport' => 'postMessage',
      ) );

      $wp_customize->add_control( new My_Modern_Widget_Color_Control( $wp_customize, 'my_modern_widget_background_color_' . $widget->id, array(
        'label' => __( 'Widget Background Color', 'textdomain' ),
        'section' => $widget->get_customizer_section_id(),
        'settings' => 'my_modern_widget_background_color_' . $widget->id,
      ) ) );
    }
  }
}
add_action( 'customize_register', 'my_modern_widget_customize_register' );

js/customizer-preview.js (JavaScript 预览)

( function( $ ) {
  $(document).ready(function(){
    $('.widget.my_modern_widget').each(function(){
      var widget_id = $(this).attr('id');
      wp.customize( 'my_modern_widget_title_' + widget_id, function( value ) {
        value.bind( function( newval ) {
          $( '#' + widget_id + ' .widget-title' ).html( newval );
        } );
      } );

      wp.customize( 'my_modern_widget_background_color_' + widget_id, function( value ) {
        value.bind( function( newval ) {
          $( '#' + widget_id + ' > div' ).css( 'background-color', newval );
        } );
      } );
    });
  });
} )( jQuery );

这个完整的示例展示了如何创建一个带有标题和背景颜色选项的现代化小工具,并将其集成到 WordPress 主题定制器中,实现实时预览功能。

掌握核心技术,提升用户体验

通过 WP_WidgetWP_Customize_Control 的结合使用,我们可以创建出功能强大、用户体验优秀的 WordPress 小工具。 这种方式不仅提供了更灵活的配置选项,还实现了实时预览功能,极大地提升了用户体验。 掌握这些核心技术,将使你能够构建更现代化的 WordPress 主题。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注