Aggiorna il modulo del widget dopo il trascinamento della selezione (bug di salvataggio WP)


15

Ho pubblicato un bug report su questo alcuni mesi fa ( su trac di WordPress (Widget Instance Form Update Bug) ) e ho pensato di provare a scriverne anche qui. Forse qualcuno ha una soluzione migliore a questo problema di me.

Fondamentalmente il problema è che se si rilascia un widget in una barra laterale, il modulo del widget non viene aggiornato fino a quando non si preme manualmente salva (o si ricarica la pagina).

Ciò rende inutilizzabile tutto il codice della form()funzione che si basa sull'ID istanza del widget per fare qualcosa (fino a quando non si preme il pulsante Salva). Qualsiasi cosa come richieste Ajax, cose jQuery come colorpicker e così via non funzioneranno immediatamente, perché da quella funzione sembrerebbe che l'istanza del widget non sia stata ancora inizializzata.

Una soluzione sporca sarebbe quella di attivare automaticamente il pulsante Salva usando qualcosa come livequery :

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

e aggiungi la .needfixclasse form()se l'istanza del widget non sembra inizializzata:

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

Uno svantaggio di questa soluzione è che se hai un sacco di widget registrati, il browser consumerà molta CPU, perché livequery verifica i cambiamenti del DOM ogni secondo (anche se non l'ho testato specificamente, è solo una mia ipotesi :)

Qualche suggerimento per un modo migliore per correggere il bug?


Invece di innescare un invio completo di salvataggio, non avrebbe più senso guardare all'interno di ciò che viene attivato il pulsante Salva per fornire l'ID necessario, separare quel codice e chiamarlo al termine dell'operazione di rilascio?
Hakre,

Risposte:


5

Di recente ho combattuto con una situazione simile. Ajax nei widget non è uno scherzo! È necessario scrivere un codice piuttosto pazzo per far funzionare le cose tra le istanze. Non ho familiarità con la query live, ma se dici che controlla il DOM ogni secondo, potrei avere una soluzione meno intensa per te:

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

È possibile passare a questa funzione un selettore o un oggetto jQuery e verrà restituito l'ID istanza dell'istanza corrente. Non ho trovato altro modo per aggirare questo problema. Sono contento di sentire che non sono l'unico :)


7

Non mi piace rispondere alla mia domanda, ma credo che questa sia la soluzione migliore finora:

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

  }

});

Ciò genererà la richiesta Ajax di salvataggio del widget, subito dopo il completamento di una richiesta di salvataggio del widget (se non vi è stata risposta con il modulo html).

Deve essere aggiunto nella jQuery(document).ready()funzione.

Ora, se vuoi ricollegare facilmente le tue funzioni javascript ai nuovi elementi DOM aggiunti dalla funzione del modulo widget, semplicemente collegali all'evento "save_widget":

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

3
Si noti che a partire da jQuery 1.8, il metodo .ajaxComplete () deve essere allegato solo al documento. - api.jquery.com/ajaxComplete Quindi la prima riga dello snippet dovrebbe essere: $ (document) .ajaxComplete (function (event, XMLHttpRequest, ajaxOptions) {Almeno per WP 3.6+
David Laing

3

Ne ho parlato recentemente e sembra che nella tradizionale interfaccia "widgets.php" qualsiasi inizializzazione javascript debba essere eseguita direttamente per i widget esistenti (quelli nel #widgets-rightdiv) e indirettamente tramite l' widget-addedevento per i widget appena aggiunti; mentre nell'interfaccia di personalizzazione "custom.php" vengono inviati tutti i widget - esistenti e nuovi - in widget-addedmodo che possano essere inizializzati lì. Sulla base di ciò, la seguente è un'estensione della WP_Widgetclasse che semplifica l'aggiunta di inizializzazioni javascript al modulo di un widget sovrascrivendo una funzione 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
    }
}

Un esempio di widget di test che utilizza questo:

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

Penso che esista qualcosa in Wordpress 3.9 che potrebbe aiutarti. È il callback aggiornato dal widget . Usalo in questo modo (coffeescript):

$(document).on 'widget-updated', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.