Cập nhật biểu mẫu widget sau khi kéo và thả (lỗi lưu WP)


15

Tôi đã đăng một báo cáo lỗi về vấn đề này vài tháng trước ( trên WordPress trac (Lỗi cập nhật biểu mẫu cá thể ) và tôi nghĩ tôi cũng sẽ thử viết về nó ở đây. Có lẽ ai đó có một giải pháp tốt hơn cho vấn đề này hơn tôi.

Về cơ bản vấn đề là nếu bạn thả một widget vào một thanh bên, biểu mẫu widget không được cập nhật cho đến khi bạn nhấn lưu thủ công (hoặc tải lại trang).

Điều này làm cho không thể sử dụng tất cả các mã từ form()chức năng dựa trên ID cá thể của widget để làm điều gì đó (cho đến khi bạn nhấn nút lưu). Bất kỳ nội dung nào như yêu cầu ajax, những thứ jQuery như bộ tô màu, v.v. sẽ không hoạt động ngay lập tức, bởi vì từ chức năng đó, có vẻ như thể hiện của tiện ích con chưa được khởi tạo.

Một sửa chữa bẩn sẽ là tự động kích hoạt nút lưu bằng cách sử dụng một cái gì đó như livequery :

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

và thêm .needfixlớp vào form()nếu thể hiện của widget không được khởi tạo:

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

Một nhược điểm của giải pháp này là nếu bạn có nhiều widget được đăng ký, trình duyệt sẽ ăn rất nhiều CPU, bởi vì kiểm tra trực tiếp cho DOM thay đổi mỗi giây (tho tôi không kiểm tra cụ thể điều này, đó chỉ là giả định của tôi :)

Bất kỳ đề xuất cho một cách tốt hơn để sửa lỗi?


Thay vì kích hoạt một lần gửi lưu đầy đủ, sẽ không có ý nghĩa hơn khi nhìn vào bên trong việc nhấn nút kích hoạt lưu để cung cấp ID cần thiết, thay vào đó hãy tách mã đó và gọi nó vào cuối hoạt động thả?
hakre

Câu trả lời:


5

Tôi đã chiến đấu với một tình huống tương tự gần đây. Ajax trong các widget không phải là trò đùa! Cần phải viết một số mã khá điên để làm cho mọi thứ hoạt động trên các trường hợp. Tôi không quen với truy vấn trực tiếp, nhưng nếu bạn nói nó kiểm tra DOM mỗi giây, tôi có thể có một giải pháp ít mãnh liệt hơn cho bạn:

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

Bạn có thể truyền cho hàm này một bộ chọn hoặc đối tượng jQuery và nó sẽ trả về ID cá thể của cá thể hiện tại. Tôi không thể tìm thấy cách nào khác xung quanh vấn đề này. Rất vui khi biết tôi không phải là người duy nhất :)


7

Tôi không thích trả lời câu hỏi của riêng mình, nhưng tôi cảm thấy đây là giải pháp tốt nhất cho đến nay:

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

  }

});

Điều này sẽ kích hoạt yêu cầu ajax lưu widget, ngay sau khi yêu cầu lưu widget đã hoàn thành (nếu không có phản hồi với biểu mẫu html).

Nó cần phải được thêm vào trong jQuery(document).ready()chức năng.

Bây giờ, nếu bạn muốn dễ dàng đính kèm lại các chức năng javascript của mình với các thành phần DOM mới được thêm bởi chức năng biểu mẫu tiện ích, chỉ cần liên kết chúng với sự kiện "yet_widget":

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

3
Lưu ý rằng kể từ jQuery 1.8, phương thức .ajaxComplete () chỉ nên được đính kèm vào tài liệu. - api.jquery.com/ajaxComplete Do đó, dòng đầu tiên của đoạn trích của bạn nên đọc: $ (tài liệu) .ajaxComplete (hàm (sự kiện, XMLHttpRequest, ajaxOptions) {Ít nhất là cho WP 3.6+
David Laing

3

Gần đây, có vẻ như trong giao diện "widget.php" truyền thống, mọi khởi tạo javascript nên được chạy trực tiếp cho các widget hiện có (những thứ trong #widgets-rightdiv) và gián tiếp thông qua widget-addedsự kiện cho các widget mới được thêm vào; trong khi trong giao diện "custom.php" tùy biến, tất cả các widget - hiện có và mới - được gửi widget-addedsự kiện để có thể được khởi tạo ở đó. Dựa vào đây, đây là phần mở rộng của WP_Widgetlớp giúp dễ dàng thêm khởi tạo javascript vào biểu mẫu của tiện ích bằng cách ghi đè một hàm , 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
    }
}

Một tiện ích thử nghiệm ví dụ sử dụng điều này:

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

Tôi nghĩ rằng một cái gì đó tồn tại trong Wordpress 3.9 có thể giúp bạn. Đó là cuộc gọi lại được cập nhật widget . Sử dụng nó như thế này (coffeescript):

$(document).on 'widget-updated', (event, widget) ->
    doWhatINeed() if widget[0].id.match(/my_widget_name/)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.