Почему при добавлении изображения в виджете wordpress дважды вызывается медиатека?
Всем привет! Есть сайт на wordpress.
Я создал виджет, который принимает на вход и выводит текстовые поля и картинки. И теперь возник вопрос следующего плана: при вызове медитатеки изображений по какой-то причине она вызывается дважды: в первом случае с параметрами, которые задаю сам
var frame = wp.media({ title: 'Добавить изображение', multiple: false, library: { type: 'image' }, button : { text : 'Вставить' } }); |
var frame = wp.media({ title: 'Добавить изображение', multiple: false, library: { type: 'image' }, button : { text : 'Вставить' } });
а когда выбираю подходящее фото и нажимаю вставить, то вызывается повторная медиатека с дефолтными значениями (надписи select image и insert). Если второй попап с медиатекой закрываю, то изображение вставляется корректно и отрабатывает ивент
frame.on('close', function() { console.log('triggered close'); }); |
frame.on('close', function() { console.log('triggered close'); });
А, если выбираю изображение и нажимаю insert, то вместо url в img src добавляется id. По какой причине получаю такое поведение и как его поправить?
Привожу код
jQuery(document).ready(function($) { $('body').on('click', '.tf-upload i.remove', function(event) { event.preventDefault(); var _input = $(this).parents('.tf-upload').find('input'); var _preview = $(this).parents('.tf-upload').find('div.thumbnail'); _preview.find('img').remove().end().find('i').remove(); _input.val('').trigger('change'); return false; }); $('body').on('click', '.tf-upload .thumbnail, .tf-upload img', function(e) { e.preventDefault(); if ($(this).is('.thumbnail')) { if ($(this).parents('.tf-upload').find('img').length != 0) { $(this).parents('.tf-upload').find('img').trigger('click'); return true; } } var _input = $(this).parents('.tf-upload').find('.repeatable-image'); var _width = $(this).parents('.tf-upload').find('.repeatable-image-width'); var _height = $(this).parents('.tf-upload').find('.repeatable-image-height'); var _alt = $(this).parents('.tf-upload').find('.repeatable-image-alt'); var _preview = $(this).parents('.tf-upload').find('div.thumbnail'); var _remove = $(this).siblings('.tf-upload-image-remove'); if (frame === undefined) { var frame = wp.media({ title: 'Добавить изображение', multiple: false, library: { type: 'image' }, button: { text: 'Вставить' } }); frame.on('select', function() { var selection = frame.state().get('selection').first().toJSON(); if (_input.length > 0) { _input.val(selection.url); } if (_width.length > 0) { _width.val(selection.width); } if (_height.length > 0) { _height.val(selection.height); } if (_alt.length > 0) { _alt.val(selection.alt); } if (_preview.length > 0) { if (_preview.find('img').length > 0) { _preview.find('img').remove(); } if (_preview.find('i.remove').length > 0) { _preview.find('i.remove').remove(); } if (typeof selection.sizes != 'undefined') { var image = selection.sizes.full; if (typeof selection.sizes.thumbnail != 'undefined') { image = selection.sizes.thumbnail; } } else { var image = selection; //svg } var url = image.url; $("<img src='" + url + "'/>").appendTo(_preview); $("<i class='dashicons dashicons-no-alt remove'></i>").prependTo(_preview); } _input.trigger('change'); _remove.show(); frame.off('select'); }); //wp.media.editor.close(); } frame.open(); frame.on('close', function() { console.log('triggered close'); }); return false; }); }); |
jQuery(document).ready(function($) { $('body').on('click', '.tf-upload i.remove', function(event) { event.preventDefault(); var _input = $(this).parents('.tf-upload').find('input'); var _preview = $(this).parents('.tf-upload').find('div.thumbnail'); _preview.find('img').remove().end().find('i').remove(); _input.val('').trigger('change'); return false; }); $('body').on('click', '.tf-upload .thumbnail, .tf-upload img', function(e) { e.preventDefault(); if ($(this).is('.thumbnail')) { if ($(this).parents('.tf-upload').find('img').length != 0) { $(this).parents('.tf-upload').find('img').trigger('click'); return true; } } var _input = $(this).parents('.tf-upload').find('.repeatable-image'); var _width = $(this).parents('.tf-upload').find('.repeatable-image-width'); var _height = $(this).parents('.tf-upload').find('.repeatable-image-height'); var _alt = $(this).parents('.tf-upload').find('.repeatable-image-alt'); var _preview = $(this).parents('.tf-upload').find('div.thumbnail'); var _remove = $(this).siblings('.tf-upload-image-remove'); if (frame === undefined) { var frame = wp.media({ title: 'Добавить изображение', multiple: false, library: { type: 'image' }, button: { text: 'Вставить' } }); frame.on('select', function() { var selection = frame.state().get('selection').first().toJSON(); if (_input.length > 0) { _input.val(selection.url); } if (_width.length > 0) { _width.val(selection.width); } if (_height.length > 0) { _height.val(selection.height); } if (_alt.length > 0) { _alt.val(selection.alt); } if (_preview.length > 0) { if (_preview.find('img').length > 0) { _preview.find('img').remove(); } if (_preview.find('i.remove').length > 0) { _preview.find('i.remove').remove(); } if (typeof selection.sizes != 'undefined') { var image = selection.sizes.full; if (typeof selection.sizes.thumbnail != 'undefined') { image = selection.sizes.thumbnail; } } else { var image = selection; //svg } var url = image.url; $("<img src='" + url + "'/>").appendTo(_preview); $("<i class='dashicons dashicons-no-alt remove'></i>").prependTo(_preview); } _input.trigger('change'); _remove.show(); frame.off('select'); }); //wp.media.editor.close(); } frame.open(); frame.on('close', function() { console.log('triggered close'); }); return false; }); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class='tf-upload'> <div class='thumbnail tf-image-preview'> <?php if(!empty($image[$k])) { echo '<i class="dashicons dashicons-no-alt remove"></i><img class="img-fluid" src="'.$image[$k].'" />'; } ?> </div> <input type="hidden" class="repeatable-image" name="<?php echo $this->get_field_name( 'image' ); ?>[]" value="<?php echo esc_attr( $image[$k] ); ?>" /> <input type="hidden" class="repeatable-image-width" name="<?php echo $this->get_field_name( 'image-width' ); ?>[]" value="<?php echo esc_attr( $width[$k] ); ?>" /> <input type="hidden" class="repeatable-image-height" name="<?php echo $this->get_field_name( 'image-height' ); ?>[]" value="<?php echo esc_attr( $height[$k] ); ?>" /> <input type="hidden" class="repeatable-image-alt" name="<?php echo $this->get_field_name( 'image-alt' ); ?>[]" value="<?php echo esc_attr( $alt[$k] ); ?>" /> </div> |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class='tf-upload'> <div class='thumbnail tf-image-preview'> <?php if(!empty($image[$k])) { echo '<i class="dashicons dashicons-no-alt remove"></i><img class="img-fluid" src="'.$image[$k].'" />'; } ?> </div> <input type="hidden" class="repeatable-image" name="<?php echo $this->get_field_name( 'image' ); ?>[]" value="<?php echo esc_attr( $image[$k] ); ?>" /> <input type="hidden" class="repeatable-image-width" name="<?php echo $this->get_field_name( 'image-width' ); ?>[]" value="<?php echo esc_attr( $width[$k] ); ?>" /> <input type="hidden" class="repeatable-image-height" name="<?php echo $this->get_field_name( 'image-height' ); ?>[]" value="<?php echo esc_attr( $height[$k] ); ?>" /> <input type="hidden" class="repeatable-image-alt" name="<?php echo $this->get_field_name( 'image-alt' ); ?>[]" value="<?php echo esc_attr( $alt[$k] ); ?>" /> </div>
.tf-upload .tf-image-preview { line-height: 0; position: relative; display: inline-block; min-width: 150px; min-height: 150px; background: #FAFAFA; box-shadow: inset 0 0 0 1px #EEE; cursor: pointer; } .tf-upload .tf-image-preview:before { content: 'f132'; font-family: 'dashicons'; position: absolute; top: 60px; left: 60px; width: 30px; text-align: center; line-height: 30px; font-size: 30px; color: #ddd; } .tf-upload .tf-image-preview, .tf-upload .tf-image-preview:before { -webkit-transition: all .3s; -moz-transition: all .3s; -ms-transition: all .3s; -o-transition: all .3s; transition: all .3s; } |
.tf-upload .tf-image-preview { line-height: 0; position: relative; display: inline-block; min-width: 150px; min-height: 150px; background: #FAFAFA; box-shadow: inset 0 0 0 1px #EEE; cursor: pointer; } .tf-upload .tf-image-preview:before { content: 'f132'; font-family: 'dashicons'; position: absolute; top: 60px; left: 60px; width: 30px; text-align: center; line-height: 30px; font-size: 30px; color: #ddd; } .tf-upload .tf-image-preview, .tf-upload .tf-image-preview:before { -webkit-transition: all .3s; -moz-transition: all .3s; -ms-transition: all .3s; -o-transition: all .3s; transition: all .3s; }
Дополнительно:
Приведу пример как работает у меня
jQuery( document ).ready( function ( $ ) { function mediaUploader( buttonClass ) { // Trigger the Media Uploader dialog. $( document ).on( 'click', buttonClass, function( e ) { var mediaUploader; form = $( buttonClass ).closest( 'form' ) // If the uploader object has already been created, reopen the dialog. if ( mediaUploader ) { mediaUploader.open(); return; } // Extend the wp.media object. var mediaUploader = wp.media.frames.file_frame = wp.media( { title: 'Select Image', button: { text: 'Select Image' }, multiple: false, library: { type: 'image' }, } ); // When a file is selected, grab the URL and set it as the text field's value. mediaUploader.on( 'select', function () { var attachment = mediaUploader.state().get( 'selection' ).first().toJSON(); form.find( '.image-upload' ).val( attachment.url ); form.find( '.media-image-container' ).empty(); form.find( '.media-image-container' ).append( '<img src="' + attachment.url + '" class="media-image" alt="Image Preview" style="max-width:100%;margin-bottom:10px" />' ); $( '.media-modal:visible' ).find( '.media-modal-close' ).trigger( 'click' ); } ); // Open the uploader dialog. mediaUploader.open(); } ); } // Initialize media uploader. mediaUploader( '.button-add-adv-media' ); } ); |
jQuery( document ).ready( function ( $ ) { function mediaUploader( buttonClass ) { // Trigger the Media Uploader dialog. $( document ).on( 'click', buttonClass, function( e ) { var mediaUploader; form = $( buttonClass ).closest( 'form' ) // If the uploader object has already been created, reopen the dialog. if ( mediaUploader ) { mediaUploader.open(); return; } // Extend the wp.media object. var mediaUploader = wp.media.frames.file_frame = wp.media( { title: 'Select Image', button: { text: 'Select Image' }, multiple: false, library: { type: 'image' }, } ); // When a file is selected, grab the URL and set it as the text field's value. mediaUploader.on( 'select', function () { var attachment = mediaUploader.state().get( 'selection' ).first().toJSON(); form.find( '.image-upload' ).val( attachment.url ); form.find( '.media-image-container' ).empty(); form.find( '.media-image-container' ).append( '<img src="' + attachment.url + '" class="media-image" alt="Image Preview" style="max-width:100%;margin-bottom:10px" />' ); $( '.media-modal:visible' ).find( '.media-modal-close' ).trigger( 'click' ); } ); // Open the uploader dialog. mediaUploader.open(); } ); } // Initialize media uploader. mediaUploader( '.button-add-adv-media' ); } );
Код вывода формы написан на основе файла woocommerce/includes/abstracts/abstract-wc-widget.php
<?php case 'image': ?> <div class="media-widget-control"> <p> <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo wp_kses_post( $setting['label'] ); ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?> <input class="widefat image-upload <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="hidden" value="<?php echo esc_attr( $value ); ?>" /> </p> <div class="media-widget-preview"> <div class="media-image-container"> <?php if ( ! empty( $value ) ) : ?> <img src="<?php echo esc_url( $value ) ; ?>" alt="Image Preview" style="max-width:100%;margin-bottom:10px" /> <?php endif; ?> </div> <div class="attachment-media-view"> <button type="button" class="select-media button-add-media button-add-adv-media not-selected"><?php esc_html_e( 'Add image', 'aesthetix' ); ?></button> </div> </div> <?php if ( isset( $setting['desc'] ) ) : ?> <small><?php echo esc_html( $setting['desc'] ); ?></small> <?php endif; ?> </div> <?php break; |
<?php case 'image': ?> <div class="media-widget-control"> <p> <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo wp_kses_post( $setting['label'] ); ?></label><?php // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?> <input class="widefat image-upload <?php echo esc_attr( $class ); ?>" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="hidden" value="<?php echo esc_attr( $value ); ?>" /> </p> <div class="media-widget-preview"> <div class="media-image-container"> <?php if ( ! empty( $value ) ) : ?> <img src="<?php echo esc_url( $value ) ; ?>" alt="Image Preview" style="max-width:100%;margin-bottom:10px" /> <?php endif; ?> </div> <div class="attachment-media-view"> <button type="button" class="select-media button-add-media button-add-adv-media not-selected"><?php esc_html_e( 'Add image', 'aesthetix' ); ?></button> </div> </div> <?php if ( isset( $setting['desc'] ) ) : ?> <small><?php echo esc_html( $setting['desc'] ); ?></small> <?php endif; ?> </div> <?php break;
- Оказалось, что плагин дублировал ивент, но за ваше решение спасибо!
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос

Двойной вызов медиатеки почти всегда означает, что обработчик клика навешивается два раза или событие всплывает до стандартного WordPress-обработчика. В виджетах это особенно частая проблема: админка может переинициализировать JS при сохранении/добавлении виджета, и Ваш
clickподписывается повторно.Первое, что нужно сделать: использовать делегированный обработчик с namespace и перед открытием медиатеки отменять стандартное действие.
jQuery(function ($) { $(document) .off('click.myWidgetMedia') .on('click.myWidgetMedia', '.my-widget-upload', function (e) { e.preventDefault(); e.stopPropagation(); const button = $(this); const field = button.closest('.my-widget-field'); const frame = wp.media({ title: 'Добавить изображение', button: { text: 'Вставить' }, library: { type: 'image' }, multiple: false }); frame.on('select', function () { const attachment = frame.state().get('selection').first().toJSON(); field.find('.my-widget-image-url').val(attachment.url).trigger('change'); field.find('.my-widget-preview').attr('src', attachment.url); }); frame.open(); }); });jQuery(function ($) { $(document) .off('click.myWidgetMedia') .on('click.myWidgetMedia', '.my-widget-upload', function (e) { e.preventDefault(); e.stopPropagation(); const button = $(this); const field = button.closest('.my-widget-field'); const frame = wp.media({ title: 'Добавить изображение', button: { text: 'Вставить' }, library: { type: 'image' }, multiple: false }); frame.on('select', function () { const attachment = frame.state().get('selection').first().toJSON(); field.find('.my-widget-image-url').val(attachment.url).trigger('change'); field.find('.my-widget-preview').attr('src', attachment.url); }); frame.open(); }); });
Второй момент: не используйте
closeдля получения выбранной картинки. Правильное событие —select.closeсрабатывает и при закрытии окна без выбора, и после выбора, поэтому поведение может быть нестабильным.Если вместо URL вставляется ID, значит в какой-то части кода Вы берёте
attachment.idили сохраняете поле, где ожидается ID. Определитесь: если в поле нужен URL, сохраняйтеattachment.url; если нужен ID, сохраняйтеattachment.id, а на фронте получайте URL черезwp_get_attachment_image_url().Ещё проверьте, что скрипт подключается один раз и только на нужных экранах админки:
add_action('admin_enqueue_scripts', function ($hook) { if ($hook !== 'widgets.php') { return; } wp_enqueue_media(); wp_enqueue_script('my-widget-admin', get_template_directory_uri() . '/js/widget-admin.js', ['jquery'], '1.0', true); });
Итог: уберите повторные подписки, используйте
select, отменяйте всплытие клика и сохраняйте ровно тот тип значения, который потом ожидает Ваш виджет.