Вы вызываете .pointer( 'open' );
функцию javascript для всех объектов указателей, поэтому неудивительно, что все указатели появляются одновременно ...
Тем не менее, я не понимаю, почему вы возвращаете все указатели (даже неактивные) из, custom_admin_pointers()
а затем добавляете дополнительную функцию для проверки наличия каких-либо активных указателей и проверки внутри цикла указателей ( if ( $array['active'] ) {
), чтобы выбрать добавление указателя javascript или нет. Не проще ли просто вернуть только активные указатели?
Более того, вы добавляете этот javascript на все страницы администратора, не слишком ли много? Также учтите, что некоторые элементы, такие как "# save-post", доступны только на новой странице поста, так что не лучше ли добавлять указатели только на новую страницу банка?
Наконец, насколько беспорядочно этот javascript перепутан с PHP, я думаю, вам стоит подумать об использовании wp_localize_script
для передачи данных в javascript.
План:
- Переместите определения указателей в PHP в отдельный файл, таким образом, его легко редактировать, а также удалять разметку из кода PHP, все становится более читабельным и понятным
- В указателях конфигурации добавить свойство «где» , который будет использоваться для набора , в котором администратор страницы должно появиться всплывающее окно:
post-new.php
, index.php
...
- Напишите класс, который будет обрабатывать загрузку, разбор и фильтрацию информации указателей.
- Напишите некоторые js goodness, которые помогут нам изменить стандартную кнопку «Удалить» на «Далее»
# 4 может (вероятно) легко сделать , зная указатель плагин хорошо, но это не мой случай. Поэтому я буду использовать общий код jQuery для получения результата, если кто-то может улучшить мой код, я буду признателен.
редактировать
Я отредактировал код (в основном js), потому что есть разные вещи, которые я не учел: некоторые указатели могут быть добавлены к одной и той же привязке, или одинаковые указатели могут быть добавлены к несуществующим или невидимым привязкам. Во всем этом случае предыдущий код не работал, новая версия, кажется, хорошо решает эти проблемы.
Я также настроил Gist со всем кодом, который использовал для тестирования.
Давайте начнем с пунктов # 1 и # 2 : создайте файл с именем pointers.php
и напишите там:
<?php
$pointers = array();
$pointers['new-items'] = array(
'title' => sprintf( '<h3>%s</h3>', esc_html__( 'Add New Item' ) ),
'content' => sprintf( '<p>%s</p>', esc_html__( 'Easily add a new post..' ) ),
'anchor_id' => '#wp-admin-bar-new-content',
'edge' => 'top',
'align' => 'left',
'where' => array( 'index.php', 'post-new.php' ) // <-- Please note this
);
$pointers['story_cover_help'] = array(
'title' => sprintf( '<h3>%s</h3>', esc_html__( 'Another info' ) ),
'content' => sprintf( '<p>%s</p>', esc_html__( 'Lore ipsum....' ) ),
'anchor_id' => '#save-post',
'edge' => 'top',
'align' => 'right',
'where' => array( 'post-new.php' ) // <-- Please note this
);
// more pointers here...
return $pointers;
Все настройки указателей здесь. Когда вам нужно что-то изменить, просто откройте этот файл и отредактируйте его.
Обратите внимание на свойство where, которое представляет собой массив страниц, где указатель должен быть доступен.
Если вы хотите отобразить указатели на странице, сгенерированной плагином, найдите эту строку, обрисованную в общих чертах ниже, public function filter( $page ) {
и добавьте die($page);
сразу под ней. Затем откройте соответствующую страницу плагина и используйте эту строку в where
свойстве.
Хорошо, теперь точка № 3 .
Прежде чем писать класс, я просто хочу написать интерфейс: там я добавлю комментарии, чтобы вы могли лучше понять, что будет делать класс.
<?php
interface PointersManagerInterface {
/**
* Load pointers from file and setup id with prefix and version.
* Cast pointers to objects.
*/
public function parse();
/**
* Remove from parse pointers dismissed ones and pointers
* that should not be shown on given page
*
* @param string $page Current admin page file
*/
public function filter( $page );
}
Я думаю, должно быть довольно ясно. Теперь давайте напишем класс, он будет содержать 2 метода из интерфейса плюс конструктор.
<?php namespace GM;
class PointersManager implements PointersManagerInterface {
private $pfile;
private $version;
private $prefix;
private $pointers = array();
public function __construct( $file, $version, $prefix ) {
$this->pfile = file_exists( $file ) ? $file : FALSE;
$this->version = str_replace( '.', '_', $version );
$this->prefix = $prefix;
}
public function parse() {
if ( empty( $this->pfile ) ) return;
$pointers = (array) require_once $this->pfile;
if ( empty($pointers) ) return;
foreach ( $pointers as $i => $pointer ) {
$pointer['id'] = "{$this->prefix}{$this->version}_{$i}";
$this->pointers[$pointer['id']] = (object) $pointer;
}
}
public function filter( $page ) {
if ( empty( $this->pointers ) ) return array();
$uid = get_current_user_id();
$no = explode( ',', (string) get_user_meta( $uid, 'dismissed_wp_pointers', TRUE ) );
$active_ids = array_diff( array_keys( $this->pointers ), $no );
$good = array();
foreach( $this->pointers as $i => $pointer ) {
if (
in_array( $i, $active_ids, TRUE ) // is active
&& isset( $pointer->where ) // has where
&& in_array( $page, (array) $pointer->where, TRUE ) // current page is in where
) {
$good[] = $pointer;
}
}
$count = count( $good );
if ( $good === 0 ) return array();
foreach( array_values( $good ) as $i => $pointer ) {
$good[$i]->next = $i+1 < $count ? $good[$i+1]->id : '';
}
return $good;
}
}
Код очень прост и выполняет именно то, что ожидает интерфейс.
Однако сам по себе класс ничего не делает, нам нужен хук, чтобы создать экземпляр класса и запустить 2 метода, передавая соответствующие аргументы.
'admin_enqueue_scripts'
Идеально подходит для нашей области: там мы будем иметь доступ к текущей странице администратора , и мы можем также Епдиеие скрипты и стили необходимы.
add_action( 'admin_enqueue_scripts', function( $page ) {
$file = plugin_dir_path( __FILE__ ) . 'pointers.php';
// Arguments: pointers php file, version (dots will be replaced), prefix
$manager = new PointersManager( $file, '5.0', 'custom_admin_pointers' );
$manager->parse();
$pointers = $manager->filter( $page );
if ( empty( $pointers ) ) { // nothing to do if no pointers pass the filter
return;
}
wp_enqueue_style( 'wp-pointer' );
$js_url = plugins_url( 'pointers.js', __FILE__ );
wp_enqueue_script( 'custom_admin_pointers', $js_url, array('wp-pointer'), NULL, TRUE );
// data to pass to javascript
$data = array(
'next_label' => __( 'Next' ),
'close_label' => __('Close'),
'pointers' => $pointers
);
wp_localize_script( 'custom_admin_pointers', 'MyAdminPointers', $data );
} );
Ничего особенного: просто использование класса для получения данных указателей и, если некоторые указатели проходят фильтры, ставят в очередь стили и сценарии. Затем передайте данные указателей в скрипт вместе с локализованной меткой «Далее» для кнопки.
Хорошо, теперь самая сложная часть: JS. Опять же, я хочу подчеркнуть, что я не знаю, какой плагин-указатель использует WordPress, поэтому то, что я делаю в своем коде, можно сделать лучше, если кто-то это знает, однако мой код работает и, вообще говоря, это не так уж плохо.
( function($, MAP) {
$(document).on( 'MyAdminPointers.setup_done', function( e, data ) {
e.stopImmediatePropagation();
MAP.setPlugin( data ); // open first popup
} );
$(document).on( 'MyAdminPointers.current_ready', function( e ) {
e.stopImmediatePropagation();
MAP.openPointer(); // open a popup
} );
MAP.js_pointers = {}; // contain js-parsed pointer objects
MAP.first_pointer = false; // contain first pointer anchor jQuery object
MAP.current_pointer = false; // contain current pointer jQuery object
MAP.last_pointer = false; // contain last pointer jQuery object
MAP.visible_pointers = []; // contain ids of pointers whose anchors are visible
MAP.hasNext = function( data ) { // check if a given pointer has valid next property
return typeof data.next === 'string'
&& data.next !== ''
&& typeof MAP.js_pointers[data.next].data !== 'undefined'
&& typeof MAP.js_pointers[data.next].data.id === 'string';
};
MAP.isVisible = function( data ) { // check if anchor for given pointer is visible
return $.inArray( data.id, MAP.visible_pointers ) !== -1;
};
// given a pointer object, return its the anchor jQuery object if available
// otherwise return first available, lookin at next property of subsequent pointers
MAP.getPointerData = function( data ) {
var $target = $( data.anchor_id );
if ( $.inArray(data.id, MAP.visible_pointers) !== -1 ) {
return { target: $target, data: data };
}
$target = false;
while( MAP.hasNext( data ) && ! MAP.isVisible( data ) ) {
data = MAP.js_pointers[data.next].data;
if ( MAP.isVisible( data ) ) {
$target = $(data.anchor_id);
}
}
return MAP.isVisible( data )
? { target: $target, data: data }
: { target: false, data: false };
};
// take pointer data and setup pointer plugin for anchor element
MAP.setPlugin = function( data ) {
if ( typeof MAP.last_pointer === 'object') {
MAP.last_pointer.pointer('destroy');
MAP.last_pointer = false;
}
MAP.current_pointer = false;
var pointer_data = MAP.getPointerData( data );
if ( ! pointer_data.target || ! pointer_data.data ) {
return;
}
$target = pointer_data.target;
data = pointer_data.data;
$pointer = $target.pointer({
content: data.title + data.content,
position: { edge: data.edge, align: data.align },
close: function() {
// open next pointer if it exists
if ( MAP.hasNext( data ) ) {
MAP.setPlugin( MAP.js_pointers[data.next].data );
}
$.post( ajaxurl, { pointer: data.id, action: 'dismiss-wp-pointer' } );
}
});
MAP.current_pointer = { pointer: $pointer, data: data };
$(document).trigger( 'MyAdminPointers.current_ready' );
};
// scroll the page to current pointer then open it
MAP.openPointer = function() {
var $pointer = MAP.current_pointer.pointer;
if ( ! typeof $pointer === 'object' ) {
return;
}
$('html, body').animate({ // scroll page to pointer
scrollTop: $pointer.offset().top - 30
}, 300, function() { // when scroll complete
MAP.last_pointer = $pointer;
var $widget = $pointer.pointer('widget');
MAP.setNext( $widget, MAP.current_pointer.data );
$pointer.pointer( 'open' ); // open
});
};
// if there is a next pointer set button label to "Next", to "Close" otherwise
MAP.setNext = function( $widget, data ) {
if ( typeof $widget === 'object' ) {
var $buttons = $widget.find('.wp-pointer-buttons').eq(0);
var $close = $buttons.find('a.close').eq(0);
$button = $close.clone(true, true).removeClass('close');
$buttons.find('a.close').remove();
$button.addClass('button').addClass('button-primary');
has_next = false;
if ( MAP.hasNext( data ) ) {
has_next_data = MAP.getPointerData(MAP.js_pointers[data.next].data);
has_next = has_next_data.target && has_next_data.data;
}
var label = has_next ? MAP.next_label : MAP.close_label;
$button.html(label).appendTo($buttons);
}
};
$(MAP.pointers).each(function(index, pointer) { // loop pointers data
if( ! $().pointer ) return; // do nothing if pointer plugin isn't available
MAP.js_pointers[pointer.id] = { data: pointer };
var $target = $(pointer.anchor_id);
if ( $target.length && $target.is(':visible') ) { // anchor exists and is visible?
MAP.visible_pointers.push(pointer.id);
if ( ! MAP.first_pointer ) {
MAP.first_pointer = pointer;
}
}
if ( index === ( MAP.pointers.length - 1 ) && MAP.first_pointer ) {
$(document).trigger( 'MyAdminPointers.setup_done', MAP.first_pointer );
}
});
} )(jQuery, MyAdminPointers); // MyAdminPointers is passed by `wp_localize_script`
С помощью комментариев код должен быть довольно понятным, по крайней мере, я на это надеюсь.
Хорошо, мы закончили. Наш PHP проще и лучше организован, наш JavaScript более читабелен, указатели легче редактировать и, что более важно, все работает.