Widget-Formular nach Drag & Drop aktualisieren (WP-Speicherfehler)

15

Ich habe vor ein paar Monaten einen Fehlerbericht darüber gepostet ( auf WordPress trac (Widget Instance Form Update Bug) ) und ich dachte, ich würde versuchen, hier auch darüber zu schreiben. Vielleicht hat jemand eine bessere Lösung für dieses Problem als ich.

Grundsätzlich besteht das Problem darin, dass beim Ablegen eines Widgets in einer Seitenleiste das Widget-Formular erst aktualisiert wird, wenn Sie manuell auf Speichern klicken (oder die Seite neu laden).

Dies macht den gesamten Code aus der form()Funktion, die sich auf die Widget-Instanz-ID stützt, unbrauchbar , um etwas zu tun (bis Sie auf die Schaltfläche Speichern klicken). Jegliche Dinge wie Ajax-Anfragen, jQuery-Dinge wie Farbwähler usw. funktionieren nicht sofort, da es den Anschein hat, dass die Widget-Instanz aufgrund dieser Funktion noch nicht initialisiert wurde.

Ein schmutziger Fix wäre, den Speichern-Button automatisch auszulösen, indem man so etwas wie livequery benutzt :

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

und fügen Sie die .needfixKlasse hinzu, form()wenn die Widget-Instanz nicht initialisiert aussieht:

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

Ein Nachteil dieser Lösung ist, dass wenn Sie viele Widgets registriert haben, der Browser viel CPU verbraucht, da sich die Live-Abfrage nach DOM-Änderungen jede Sekunde ändert (obwohl ich dies nicht speziell getestet habe, ist es nur meine Annahme :)

Irgendwelche Vorschläge für einen besseren Weg, um den Fehler zu beheben?

ein Trickpony
quelle
Wäre es nicht sinnvoller, einen Blick in das zu werfen, was durch Drücken der Schaltfläche zum Speichern ausgelöst wird, um die erforderliche ID bereitzustellen, anstatt einen vollständigen Speichervorgang auszulösen, diesen Code zu trennen und ihn stattdessen am Ende des Löschvorgangs aufzurufen?
Hakre

Antworten:

5

Ich habe in letzter Zeit mit einer ähnlichen Situation gekämpft. Ajax in Widgets ist kein Witz! Sie müssen einen ziemlich verrückten Code schreiben, damit die Dinge instanzenübergreifend funktionieren. Ich bin mit Live-Abfragen nicht vertraut, aber wenn Sie sagen, dass das DOM jede Sekunde überprüft wird, könnte ich eine weniger intensive Lösung für Sie haben:

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

Sie können dieser Funktion einen Selektor oder ein jQuery-Objekt übergeben und es wird die Instanz-ID der aktuellen Instanz zurückgegeben. Ich konnte dieses Problem nicht anders lösen. Freut mich zu hören, dass ich nicht der einzige bin :)

Mfields
quelle
7

Ich beantworte meine eigene Frage nicht gern, aber ich halte dies für die beste Lösung:

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

  }

});

Dadurch wird die Ajax-Anforderung zum Speichern von Widgets ausgelöst, unmittelbar nachdem eine Anforderung zum Speichern von Widgets abgeschlossen wurde (falls keine Antwort mit dem Formular "html" eingegangen ist).

Es muss in der jQuery(document).ready()Funktion hinzugefügt werden.

Wenn Sie Ihre Javascript-Funktionen jetzt einfach wieder an die neuen DOM-Elemente anhängen möchten, die von der Widget-Formularfunktion hinzugefügt wurden, binden Sie sie einfach an das Ereignis "saved_widget":

$(document).bind('saved_widget', function(event, widget){
  // For example: $(widget).colorpicker() ....
});
ein Trickpony
quelle
3
Beachten Sie, dass ab jQuery 1.8 die Methode .ajaxComplete () nur an das Dokument angehängt werden sollte. - api.jquery.com/ajaxComplete So sollte die erste Zeile Ihres Snippets lauten: $ (document) .ajaxComplete (function (event, XMLHttpRequest, ajaxOptions) {Zumindest für WP 3.6+
David Laing
3

Stieß in letzter Zeit darauf und es scheint, dass in der traditionellen "widgets.php" -Schnittstelle jede JavaScript-Initialisierung direkt für vorhandene Widgets (die in der #widgets-rightdiv) und indirekt über das widget-addedEreignis für neu hinzugefügte Widgets ausgeführt werden sollte; wohingegen in der customizer "customise.php" -Schnittstelle alle Widgets - existierende und neue - das widget-addedEreignis gesendet werden und nur dort initialisiert werden können. Darauf aufbauend ist das Folgende eine Erweiterung der WP_WidgetKlasse, die es einfach macht, eine JavaScript-Initialisierung zum Formular eines Widgets hinzuzufügen, indem eine Funktion überschrieben wird 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
    }
}

Ein Beispiel für ein Test-Widget, das dies verwendet:

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
quelle
2

Ich denke, in Wordpress 3.9 gibt es etwas, das Ihnen helfen könnte. Es ist der Widget-aktualisierte Rückruf. Benutze es so (coffeescript):

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