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

15

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

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

Это делает неиспользуемым весь код form()функции, которая использует идентификатор экземпляра виджета для выполнения чего-либо (пока вы не нажмете кнопку сохранения). Любые вещи, такие как ajax-запросы, jQuery, такие как colorpickers и т. Д., Не будут работать сразу, потому что из этой функции может показаться, что экземпляр виджета еще не был инициализирован.

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

Любые предложения для лучшего способа исправить ошибку?

onetrickpony
источник
Вместо того, чтобы инициировать полное сохранение, разве не имеет смысла заглянуть внутрь, что вызывает нажатие кнопки сохранения, чтобы предоставить необходимый идентификатор, отделить этот код и вместо этого вызвать его в конце операции удаления?
hakre

Ответы:

5

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

mfields
источник
7

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

$('#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, добавленным функцией формы виджета, просто свяжите их с событием «сохраненный_виджет»:

$(document).bind('saved_widget', function(event, widget){
  // For example: $(widget).colorpicker() ....
});
onetrickpony
источник
3
Обратите внимание, что начиная с jQuery 1.8, метод .ajaxComplete () должен быть прикреплен только к документу. - api.jquery.com/ajaxComplete Таким образом, первая строка вашего сниппета следует читать: $ (документ) .ajaxComplete (функция (событие, XMLHttpRequest, ajaxOptions) {По крайней мере , для WP 3.6 +
David Laing
3

Недавно столкнулся с этим, и кажется, что в традиционном интерфейсе «widgets.php» любая инициализация javascript должна выполняться непосредственно для существующих виджетов (в #widgets-rightdiv) и косвенно через 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' );
} );
Бонгер
источник
2

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

$(document).on 'widget-updated', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)
Тайлер Кольер
источник