拖放后更新窗口小部件表单(WP保存错误)

时间:2010-12-17 作者:onetrickpony

几个月前我发布了一个关于这个的bug报告(on WordPress trac (Widget Instance Form Update Bug)) 我想我也应该在这里写一写。也许有人能比我更好地解决这个问题。

基本上,问题是如果您将一个小部件放到侧边栏中,小部件表单在您手动按save(或重新加载页面)之前不会得到更新。

这使得来自form() 依赖小部件实例ID执行某些操作的函数(直到您按下保存按钮)。任何像ajax请求、jQuery之类的东西,比如颜色选择器等等,都不能立即工作,因为从这个函数来看,小部件实例似乎还没有初始化。

一个糟糕的修复方法是使用以下命令自动触发保存按钮livequery:

$("#widgets-right .needfix").livequery(function(){
  var widget = $(this).closest(\'div.widget\');
  wpWidgets.save(widget, 0, 1, 0);
  return false;
});
并添加.needfix 上课form() 如果小部件实例看起来没有初始化:

 <div <?php if(!is_numeric($this->number)): ?>class="needfix"<?php endif; ?>
   ...
 </div>
这种解决方案的一个缺点是,如果注册了很多小部件,浏览器将消耗大量的CPU,因为livequery每秒都会检查DOM的变化(虽然我没有专门对此进行测试,但这只是我的假设:)

有什么更好的方法来修复这个bug的建议吗?

4 个回复
最合适的回答,由SO网友:mfields 整理而成

我最近确实遇到过类似的情况。小部件中的Ajax绝非玩笑!需要编写一些非常疯狂的代码才能跨实例工作。我不熟悉live query,但如果您说它每秒钟检查一次DOM,我可能会为您提供一个不那么紧张的解决方案:

var get_widget_id = function ( selector ) {
    var selector, widget_id = false;
    var id_attr = $( selector ).closest( \'form\' ).find( \'input[name="widget-id"]\' ).val();
    if ( typeof( id_attr ) != \'undefined\' ) {
        var parts = id_attr.split( \'-\' );
        widget_id = parts[parts.length-1];
    }
    return parseInt( widget_id );
};
您可以向此函数传递选择器或jQuery对象,它将返回当前实例的实例ID。我找不到其他方法来解决这个问题。很高兴听到我不是唯一一个:)

SO网友:onetrickpony

我不喜欢回答自己的问题,但我觉得这是目前为止最好的解决方案:

$(\'#widgets-right\').ajaxComplete(function(event, XMLHttpRequest, ajaxOptions){

  // determine which ajax request is this (we\'re after "save-widget")
  var request = {}, pairs = ajaxOptions.data.split(\'&\'), i, split, widget;

  for(i in pairs){
    split = pairs[i].split(\'=\');
    request[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);
  }

  // only proceed if this was a widget-save request
  if(request.action && (request.action === \'save-widget\')){

    // locate the widget block
    widget = $(\'input.widget-id[value="\' + request[\'widget-id\'] + \'"]\').parents(\'.widget\');

    // trigger manual save, if this was the save request 
    // and if we didn\'t get the form html response (the wp bug)
    if(!XMLHttpRequest.responseText)
      wpWidgets.save(widget, 0, 1, 0);

    // we got an response, this could be either our request above,
    // or a correct widget-save call, so fire an event on which we can hook our js
    else
      $(document).trigger(\'saved_widget\', widget);

  }

});
这将在小部件保存请求完成后(如果表单html没有响应)触发小部件保存ajax请求。

需要将其添加到jQuery(document).ready() 作用

现在,如果您想轻松地将javascript函数重新附加到小部件表单函数添加的新DOM元素,只需将它们绑定到“saved\\u widget”事件:

$(document).bind(\'saved_widget\', function(event, widget){
  // For example: $(widget).colorpicker() ....
});

SO网友:bonger

最近遇到了这种情况,似乎在传统的“widgets.php”界面中,任何javascript初始化都应该直接为现有的小部件运行(在#widgets-right div),并通过widget-added 新添加小部件的事件;而在customizer“customize.php”界面中,所有小部件(现有的和新的)都会发送widget-added 事件,所以可以在那里初始化。基于此,以下是WP_Widget 类,通过重写一个函数,可以轻松地将javascript初始化添加到小部件的窗体中,form_javascript_init():

class WPSE_JS_Widget extends WP_Widget { // For widgets using javascript in form().
    var $js_ns = \'wpse\'; // Javscript namespace.
    var $js_init_func = \'\'; // Name of javascript init function to call. Initialized in constructor.
    var $is_customizer = false; // Whether in customizer or not. Set on \'load-customize.php\' action (if any).

    public function __construct( $id_base, $name, $widget_options = array(), $control_options = array(), $js_ns = \'\' ) {
        parent::__construct( $id_base, $name, $widget_options, $control_options );
        if ( $js_ns ) {
            $this->js_ns = $js_ns;
        }
        $this->js_init_func = $this->js_ns . \'.\' . $this->id_base . \'_init\';
        add_action( \'load-widgets.php\', array( $this, \'load_widgets_php\' ) );
        add_action( \'load-customize.php\', array( $this, \'load_customize_php\' ) );
    }

    // Called on \'load-widgets.php\' action added in constructor.
    public function load_widgets_php() {
        add_action( \'in_widget_form\', array( $this, \'form_maybe_call_javascript_init\' ) );
        add_action( \'admin_print_scripts\', array( $this, \'admin_print_scripts\' ), PHP_INT_MAX );
    }

    // Called on \'load-customize.php\' action added in constructor.
    public function load_customize_php() {
        $this->is_customizer = true;
        // Don\'t add \'in_widget_form\' action as customizer sends \'widget-added\' event to existing widgets too.
        add_action( \'admin_print_scripts\', array( $this, \'admin_print_scripts\' ), PHP_INT_MAX );
    }

    // Form javascript initialization code here. "widget" and "widget_id" available.
    public function form_javascript_init() {
    }

    // Called on \'in_widget_form\' action (ie directly after form()) when in traditional widgets interface.
    // Run init directly unless we\'re newly added.
    public function form_maybe_call_javascript_init( $callee_this ) {
        if ( $this === $callee_this && \'__i__\' !== $this->number ) {
            ?>
            <script type="text/javascript">
            jQuery(function ($) {
                <?php echo $this->js_init_func; ?>(null, $(\'#widgets-right [id$="<?php echo $this->id; ?>"]\'));
            });
            </script>
            <?php
        }
    }

    // Called on \'admin_print_scripts\' action added in constructor.
    public function admin_print_scripts() {
        ?>
        <script type="text/javascript">
        var <?php echo $this->js_ns; ?> = <?php echo $this->js_ns; ?> || {}; // Our namespace.
        jQuery(function ($) {
            <?php echo $this->js_init_func; ?> = function (e, widget) {
                var widget_id = widget.attr(\'id\');
                if (widget_id.search(/^widget-[0-9]+_<?php echo $this->id_base; ?>-[0-9]+$/) === -1) { // Check it\'s our widget.
                    return;
                }
                <?php $this->form_javascript_init(); ?>
            };
            $(document).on(\'widget-added\', <?php echo $this->js_init_func; ?>); // Call init on widget add.
        });
        </script>
        <?php
    }
}
使用以下内容的示例测试小部件:

class WPSE_Test_Widget extends WPSE_JS_Widget {
    var $defaults; // Form defaults. Initialized in constructor.

    function __construct() {
        parent::__construct( \'wpse_test_widget\', __( \'WPSE: Test Widget\' ), array( \'description\' => __( \'Test init of javascript.\' ) ) );
        $this->defaults = array(
            \'one\' => false,
            \'two\' => false,
            \'color\' => \'#123456\',
        );
        add_action( \'admin_enqueue_scripts\', function ( $hook_suffix ) {
            if ( ! in_array( $hook_suffix, array( \'widgets.php\', \'customize.php\' ) ) ) return;
            wp_enqueue_script( \'wp-color-picker\' ); wp_enqueue_style( \'wp-color-picker\' );
        } );
    }

    function widget( $args, $instance ) {
        extract( $args );
        extract( wp_parse_args( $instance, $this->defaults ) );

        echo $before_widget, \'<p style="color:\', $color, \';">\', $two ? \'Two\' : ( $one ? \'One\' : \'None\' ), \'</p>\', $after_widget;
    }

    function update( $new_instance, $old_instance ) {
        $new_instance[\'one\'] = isset( $new_instance[\'one\'] ) ? 1 : 0;
        $new_instance[\'two\'] = isset( $new_instance[\'two\'] ) ? 1 : 0;
        return $new_instance;
    }

    function form( $instance ) {
        extract( wp_parse_args( $instance, $this->defaults ) );
        ?>
        <div class="wpse_test">
            <p class="one">
                <input class="checkbox" type="checkbox" <?php checked( $one ); disabled( $two ); ?> id="<?php echo $this->get_field_id( \'one\' ); ?>" name="<?php echo $this->get_field_name( \'one\' ); ?>" />
                <label for="<?php echo $this->get_field_id( \'one\' ); ?>"><?php _e( \'One?\' ); ?></label>
            </p>
            <p class="two">
                <input class="checkbox" type="checkbox" <?php checked( $two ); disabled( $one ); ?> id="<?php echo $this->get_field_id( \'two\' ); ?>" name="<?php echo $this->get_field_name( \'two\' ); ?>" />
                <label for="<?php echo $this->get_field_id( \'two\' ); ?>"><?php _e( \'Two?\' ); ?></label>
            </p>
            <p class="color">
                <input type="text" value="<?php echo htmlspecialchars( $color ); ?>" id="<?php echo $this->get_field_id( \'color\' ); ?>" name="<?php echo $this->get_field_name( \'color\' ); ?>" />
            </p>
        </div>
        <?php
    }

    // Form javascript initialization code here. "widget" and "widget_id" available.
    function form_javascript_init() {
        ?>
            $(\'.one input\', widget).change(function (event) { $(\'.two input\', widget).prop(\'disabled\', this.checked); });
            $(\'.two input\', widget).change(function (event) { $(\'.one input\', widget).prop(\'disabled\', this.checked); });
            $(\'.color input\', widget).wpColorPicker({
                <?php if ( $this->is_customizer ) ?> change: _.throttle( function () { $(this).trigger(\'change\'); }, 1000, {leading: false} )
            });
        <?php
    }
}

add_action( \'widgets_init\', function () {
    register_widget( \'WPSE_Test_Widget\' );
} );

SO网友:Tyler Collier

我认为Wordpress 3.9中有一些东西可能会对您有所帮助。这是widget-updated 回调。像这样使用(coffeescript):

$(document).on \'widget-updated\', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)

结束

相关推荐

Why use widgets?

我对使用WordPress很陌生,我想知道使用小部件的好处是什么?看here 这听起来像是为那些不是程序员的人准备的,他们想在他们的网站上添加插件。对吗?或者小部件是否在某种程度上使站点更加健壮?