อัปเดตฟอร์มวิดเจ็ตหลังการลากและวาง (ข้อผิดพลาดการบันทึก WP)


15

ฉันโพสต์รายงานข้อผิดพลาดเกี่ยวกับเรื่องนี้เมื่อไม่กี่เดือนที่ผ่านมา ( บน WordPress trac (ข้อผิดพลาดการอัปเดตอินสแตนซ์ของ Widget อินสแตนซ์) ) และฉันคิดว่าฉันจะลองเขียนมันที่นี่ด้วย อาจมีบางคนมีทางออกที่ดีกว่าในเรื่องนี้มากกว่าฉัน

โดยทั่วไปปัญหาคือถ้าคุณวางวิดเจ็ตในแถบด้านข้างแบบฟอร์มวิดเจ็ตจะไม่ได้รับการอัปเดตจนกว่าคุณจะกดบันทึกด้วยตนเอง (หรือโหลดหน้าซ้ำ)

สิ่งนี้ทำให้ไม่สามารถใช้รหัสทั้งหมดจากform()ฟังก์ชันที่ใช้ ID อินสแตนซ์ของวิดเจ็ตเพื่อทำอะไรบางอย่าง (จนกว่าคุณจะกดปุ่มบันทึก) สิ่งใด ๆ เช่นคำขอ 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>

ข้อเสียเปรียบอย่างหนึ่งของการแก้ปัญหานี้คือถ้าคุณลงทะเบียนวิดเจ็ตจำนวนมากเบราว์เซอร์จะกิน CPU มากเพราะ livequery ตรวจสอบการเปลี่ยนแปลง DOM ทุกวินาที (ที่ฉันไม่ได้ทดสอบเฉพาะนี้เป็นเพียงข้อสันนิษฐานของฉัน :)

คำแนะนำใด ๆ สำหรับวิธีที่ดีกว่าในการแก้ไขข้อบกพร่อง?


แทนที่จะเรียกใช้การบันทึกแบบเต็มมันจะไม่เหมาะสมกว่าหรือไม่ที่จะมองเข้าไปในสิ่งที่กดปุ่มบันทึกเพื่อให้รหัสที่ต้องการแยกรหัสนั้นและเรียกมันในตอนท้ายของการดำเนินการดร็อปแทน?
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 และมันจะส่งกลับ ID อินสแตนซ์ของอินสแตนซ์ปัจจุบัน ฉันไม่พบวิธีอื่นในการแก้ไขปัญหานี้ ดีใจที่ได้ยินฉันไม่ใช่คนเดียว :)


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 ของ widget-save หลังจากคำร้องขอ widget-save ได้เสร็จสิ้นแล้ว (หากไม่มีการตอบกลับด้วยแบบฟอร์ม html)

จะต้องมีการเพิ่มในjQuery(document).ready()ฟังก์ชั่น

ตอนนี้หากคุณต้องการแนบฟังก์ชั่นจาวาสคริปต์ของคุณเข้ากับองค์ประกอบ DOM ใหม่ที่เพิ่มโดยฟังก์ชั่นรูปแบบวิดเจ็ตเพียงผูกไว้กับเหตุการณ์ "hidden_widget":

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

3
โปรดทราบว่าตั้งแต่ jQuery 1.8 ควรแนบเมธอด. jaxComplete () เข้ากับเอกสารเท่านั้น - api.jquery.com/ajaxComplete ดังนั้นบรรทัดแรกของตัวอย่างของคุณควรอ่าน: $ (document) .ajaxComplete (ฟังก์ชัน (เหตุการณ์, XMLHttpRequest, ajaxOptions) {อย่างน้อยสำหรับ WP 3.6+
David Laing

3

วิ่งเข้าไปหาสิ่งนี้เมื่อเร็ว ๆ นี้และดูเหมือนว่าในอินเทอร์เฟซ "widgets.php" การกำหนดค่าเริ่มต้นใด ๆ ของจาวาสคริปต์ควรจะเรียกใช้โดยตรงสำหรับวิดเจ็ตที่มีอยู่ (ใน#widgets-rightdiv) และทางอ้อมผ่านทางwidget-addedเหตุการณ์ ในขณะที่ในเครื่องมือปรับแต่ง "ปรับแต่ง. php" อินเทอร์เฟซวิดเจ็ตทั้งหมด - ที่มีอยู่และใหม่ - ถูกส่งwidget-addedเหตุการณ์เพื่อที่จะสามารถเริ่มต้นได้ที่นั่น ตามนี้ต่อไปนี้เป็นส่วนขยายของWP_Widgetชั้นเรียนซึ่งทำให้ง่ายต่อการเพิ่มการเริ่มต้นจาวาสคริปต์ในรูปแบบของวิดเจ็ตโดยแทนที่หนึ่งฟังก์ชัน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' );
} );

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.