Zaktualizuj formularz widżetu po przeciągnięciu i upuszczeniu (błąd zapisu WP)

15

Kilka miesięcy temu opublikowałem raport o tym błędzie ( na WordPress trac (błąd aktualizacji formularza wystąpienia widżetu) ) i pomyślałem, że też spróbuję o tym napisać tutaj. Może ktoś ma lepsze rozwiązanie tego problemu niż ja.

Zasadniczo problem polega na tym, że jeśli upuścisz widżet na pasku bocznym, formularz widżetu nie zostanie zaktualizowany, dopóki nie naciśniesz ręcznie przycisku Zapisz (lub przeładuj stronę).

To sprawia, że ​​nie można użyć całego kodu z form()funkcji, która polega na identyfikatorze instancji widgetu, aby coś zrobić (dopóki nie naciśniesz przycisku Zapisz). Wszelkie rzeczy, takie jak żądania ajax, rzeczy jQuery, takie jak dobór kolorów itp., Nie będą od razu działać, ponieważ z tej funkcji mogłoby się wydawać, że instancja widgetu nie została jeszcze zainicjowana.

Brudną poprawką byłoby automatyczne uruchamianie przycisku zapisywania przy użyciu czegoś takiego jak zapytanie na żywo :

$("#widgets-right .needfix").livequery(function(){
  var widget = $(this).closest('div.widget');
  wpWidgets.save(widget, 0, 1, 0);
  return false;
});

i dodaj .needfixklasę, form()jeśli instancja widgetu nie wygląda na zainicjowaną:

 <div <?php if(!is_numeric($this->number)): ?>class="needfix"<?php endif; ?>
   ...
 </div>

Wadą tego rozwiązania jest to, że jeśli masz zarejestrowanych wiele widżetów, przeglądarka zje dużo procesora, ponieważ kwerendy sprawdzają zmiany DOM co sekundę (chociaż nie testowałem tego specjalnie, to tylko moje przypuszczenie :)

Wszelkie sugestie dotyczące lepszego sposobu naprawienia błędu?

onetrickpony
źródło
Zamiast uruchamiać przesyłanie pełnego zapisu, czy nie byłoby sensowniej zajrzeć do wnętrza, co powoduje naciśnięcie przycisku zapisu w celu zapewnienia wymaganego identyfikatora, oddzielenia tego kodu i wywołania go pod koniec operacji usuwania?
hakre

Odpowiedzi:

5

Ostatnio walczyłem z podobną sytuacją. Ajax w widżetach to nie żart! Musisz napisać całkiem zwariowany kod, aby wszystko działało w różnych instancjach. Nie jestem zaznajomiony z zapytaniem na żywo, ale jeśli powiesz, że sprawdza DOM co sekundę, mogę mieć dla ciebie mniej intensywne rozwiązanie:

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 );
};

Możesz przekazać tej funkcji selektor lub obiekt jQuery, który zwróci identyfikator instancji bieżącej instancji. Nie mogłem znaleźć innego rozwiązania tego problemu. Cieszę się, że nie jestem jedyny :)

pola
źródło
7

Nie lubię odpowiadać na własne pytanie, ale wydaje mi się, że jest to jak dotąd najlepsze rozwiązanie:

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

  }

});

Spowoduje to uruchomienie żądania zapisania widgetu zaraz po zakończeniu żądania zapisania widgetu (jeśli nie było odpowiedzi w formularzu HTML).

Należy dodać w jQuery(document).ready()funkcji.

Teraz, jeśli chcesz łatwo ponownie dołączyć funkcje javascript do nowych elementów DOM dodanych przez funkcję formularza widgetu, po prostu powiąż je ze zdarzeniem „save_widget”:

$(document).bind('saved_widget', function(event, widget){
  // For example: $(widget).colorpicker() ....
});
onetrickpony
źródło
3
Zauważ, że od jQuery 1.8 metoda .ajaxComplete () powinna być dołączana tylko do dokumentu. - api.jquery.com/ajaxComplete Zatem pierwszy wiersz fragmentu powinien brzmieć: $ (dokument) .ajaxComplete (funkcja (event, XMLHttpRequest, ajaxOptions) {Przynajmniej dla WP 3.6+
David Laing
3

Natknąłem się na to niedawno i wydaje się, że w tradycyjnym interfejsie „widgets.php” wszelkie inicjalizacje javascript powinny być uruchamiane bezpośrednio dla istniejących widgetów (tych w #widgets-rightdiv) i pośrednio przez widget-addedzdarzenie dla nowo dodanych widgetów; podczas gdy w dostosowywanym interfejsie „customize.php” wszystkie widżety - istniejące i nowe - są wysyłane do widget-addedzdarzenia, więc można je tam po prostu zainicjować. W oparciu o to, poniższe rozszerzenie jest rozszerzeniem WP_Widgetklasy, co ułatwia dodanie inicjalizacji javascript do formularza widżetu poprzez przesłanianie jednej funkcji 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
    }
}

Przykładowy widget testowy wykorzystujący to:

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' );
} );
bonger
źródło
2

Myślę, że w Wordpress 3.9 istnieje coś, co może ci pomóc. Jest to wywołanie zwrotne zaktualizowane przez widget . Użyj tego w następujący sposób (coffeescript):

$(document).on 'widget-updated', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)
Tyler Collier
źródło