問題描述
幾個月前我已經發布了一個 bug-report(on WordPress trac (Widget Instance Form Update Bug)),我以為我也會在這裏嘗試寫。也許有人比我更好地解決這個問題。
基本上問題是,如果您將一個小工具放入側欄中,則在您手動按保存 (或重新加載頁面) 之前,窗口小工具表單才會更新。
這使得所有來自 form()函數的所有代碼都不可用,這些函數依賴於窗口小工具實例 ID 來執行某些操作 (直到按下保存按鈕) 。任何像 ajax 請求的東西,像 jQuery 這樣的顏色等等都不會立即工作,因為從這個功能來看,這個 widget 實例還沒有被初始化。
骯髒的修復將是使用類似 livequery 的方式自動觸發保存按鈕:
$("#widgets-right .needfix").livequery(function(){
var widget = $(this).closest('div.widget');
wpWidgets.save(widget, 0, 1, 0);
return false;
});
並在 form()中添加.needfix 類,如果該窗口小工具實例未初始化:
<div <?php if(!is_numeric($this->number)): ?>class="needfix"<?php endif; ?>
...
</div>
該解決方案的一個缺點是,如果您註冊了很多小工具,瀏覽器將會吃很多 CPU,因為 DOM 的每次更新都會進行 DOM 查詢檢查 (我沒有專門測試這個,這只是我的假設:)
任何建議,以更好的方式來修復錯誤?
最佳解決方案
最近我做了類似的事情的戰鬥。 Ajax 在小工具中不是笑話!需要編寫一些非常瘋狂的代碼來使事情能夠跨實例。我不熟悉即時查詢,但如果您每秒鐘檢查 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 。我可以找不到其他方法來解決這個問題。很高興聽到我不是唯一的一個:)
次佳解決方案
我不喜歡回答自己的問題,但我覺得這是迄今為止最好的解決方案:
$('#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);
}
});
這將觸發 widget-save ajax 請求,就在 widget-save 請求完成之後 (如果沒有 html 格式的響應) 。
需要在 jQuery(document).ready()功能中添加。
現在,如果你想輕鬆地將 re-attach 的 JavaScript 函數添加到窗口小工具窗體函數添加的新 DOM 元素中,就可以將它們綁定到”saved_widget” 事件中:
$(document).bind('saved_widget', function(event, widget){
// For example: $(widget).colorpicker() ....
});
第三種解決方案
最近進入這個過程,似乎在傳統的”widgets.php” 界面中,任何 javascript 初始化都應該直接運行到現有的小工具 (#widgets-right div) 中,並通過新添加的小工具的 widget-added 事件間接運行; 而在定製器”customize.php” 接口中,所有的 widget(即現有的和新的) 被髮送到 widget-added 事件,所以可以在那裏進行初始化。基於此,以下是 WP_Widget 類的擴展,它可以通過覆蓋一個函數 form_javascript_init()來輕鬆地將 JavaScript 初始化添加到窗口小工具的窗體中:
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' );
} );
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。