Обновить форму виджета после перетаскивания (ошибка сохранения WP)


Я опубликовал сообщение об ошибке об этом несколько месяцев назад ( на WordPress trac (Ошибка обновления формы экземпляра виджета)), и я подумал, что тоже попробую написать об этом здесь. Может быть, у кого-то есть лучшее решение этой проблемы, чем у меня.

В основном проблема заключается в том, что если вы поместите виджет на боковую панель, форма виджета не будет обновлена, пока вы вручную не нажмете сохранить (или не перезагрузите страницу).

Это делает непригодным для использования весь код из функции form(), которая полагается на идентификатор экземпляра виджета, чтобы что-то сделать (пока вы не нажмете кнопку сохранить). Любые вещи, такие как запросы 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>

Одним из недостатков этого решения является то, что если у вас зарегистрировано много виджетов, браузер будет потреблять много ресурсов процессора, потому что livequery проверяет изменения DOM каждую секунду (хотя я специально не проверял это, это просто мое предположение:)

Есть какие-либо предложения по лучшему способу исправления ошибки?

Author: hakre, 2010-12-17

4 answers

Недавно я столкнулся с подобной ситуацией. Ajax в виджетах - это не шутка! Нужно написать довольно сумасшедший код, чтобы все работало в разных экземплярах. Я не знаком с живым запросом, но если вы скажете, что он проверяет 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, и он вернет идентификатор экземпляра текущего экземпляра. Я не мог найти другого способа обойти эту проблему. Рад слышать, что я не тот только один:)

 5
Author: mfields, 2011-02-27 12:03:17

Мне не нравится отвечать на свой собственный вопрос, но я чувствую, что это лучшее решение на данный момент:

$('#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);

  }

});

Это вызовет ajax-запрос на сохранение виджета сразу после завершения запроса на сохранение виджета (если не было ответа с помощью html-формы).

Его необходимо добавить в функцию jQuery(document).ready().

Теперь, если вы хотите легко повторно присоединить свои функции javascript к новым элементам DOM, добавленным функцией формы виджета, просто привяжите их к "saved_widget". событие:

$(document).bind('saved_widget', function(event, widget){
  // For example: $(widget).colorpicker() ....
});
 7
Author: onetrickpony, 2012-01-02 12:49:59

Столкнулся с этим недавно, и кажется, что в традиционном"widgets.php "интерфейс любая инициализация javascript должна выполняться непосредственно для существующих виджетов (тех, что находятся в разделе #widgets-right), и косвенно через событие widget-added для вновь добавленных виджетов; в то время как в настройщике"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' );
} );
 4
Author: bonger, 2015-12-23 12:44:09

Я думаю, что в Wordpress 3.9 есть что-то, что может вам помочь. Это обновленный виджет обратный вызов. Используйте его так (coffeescript):

$(document).on 'widget-updated', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)
 2
Author: Tyler Collier, 2014-08-08 01:09:14