- 11.05.15
- 15:47
- 2411
- 0
Ситуация: имеется готовая вёрстка, виджеты в которой выполнены не совсем стандартно для WordPress: их содержимое заключено в блок, следующий за блоком заголовка. В общих чертах это выглядит так:
1 2 3 4 5 6 7 8 |
<div id="..." class="widget"> <div class="widget-title"> Заголовок виджета </div> <div class="widget-body"> Содержимое виджета </div> </div> |
Вёрстка такова, что перенос div.widget-title
в div.widget-body
невозможен. Отсутствие любого из них дизайн также не предусматривает.
Проблема: когда заголовок пуст, обрамляющий его код обычно не выдаётся, и параметры сайдбара before_widget
и after_widget
оказываются несбалансированными — первый открывает один блок, второй закрывает два:
1 2 3 4 5 6 7 8 9 |
<div id="..." class="widget"> <!-- before_widget --> <div class="widget-title"> <!-- before_title --> </div> <!-- after_title --> <div class="widget-body"> <!-- after_title --> </div> <!-- after_widget --> </div> <!-- after_widget --> |
Задача: заставить виджеты с пустыми заголовками отображаться корректно.
Теория
Каждый виджет в WordPress формируется четырьмя фрагментами кода, задаваемыми темой:
before_widget
иafter_widget
обрамляют весь виджет;before_title
иafter_title
обрамляют заголовок и находятся внутри обрамления виджета.
Перед выводом заголовок фильтруется хуком widget_title
. Если в итоге он оказывается пуст — его обрамление не выдаётся. Некоторые виджеты этим правилам не следуют: их заголовок может не фильтроваться или не выводиться, что в данном случае способно нарушить структуру всей страницы.
Поэтому параметры before_widget
и after_widget
необходимо сбалансировать. Также желательно обеспечить приемлемый вид для «нестандартных» виджетов.
Решение
Пустые заголовки следует заменять пробелами при помощи фильтра 'widget_title'
:
1 2 3 4 5 6 7 8 |
add_filter('widget_title', function ($title) { if ($title === '') return ' '; return $title; }, 256 ); |
Здесь предполагается, что приоритет остальных обработчиков фильтра не превышает 255. Число взято наугад, значение по умолчанию — 10.
Такой подход затрагивает все виджеты без исключения. Если же он допустим не во всех сайдбарах темы, потребуются провести дополнительные манипуляции:
- по хуку
'widgets_init'
с приоритетом не ниже 100 необходимо создать глобальный хеш вида$id_base => 'Class_Name'
для всех зарегистрированных классов виджетов; - в фильтре
'widget_title'
:- взять идентификатор обрабатываемого виджета из экземпляра его класса:
1$widget_id = $wp_widget_factory->widgets['Class_Name']->id;
- получить идентификатор сайдбара:
1$sidebar_id = is_active_widget(false, $widget_id, $id_base);
- из идентификатора сайдбара определить, должен ли изменяться пустой заголовок.
- взять идентификатор обрабатываемого виджета из экземпляра его класса:
Т. е. код может быть таким:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
global $myplugin_some_sidebars; $myplugin_some_sidebars = array('sidebar_1', 'sidebar_2', ...); add_action('widgets_init', function () { global $wp_widget_factory; global $myplugin_wclass_names; $myplugin_wclass_names = array(); foreach ($wp_widget_factory->widgets as $class_name => $w) { $myplugin_wclass_names[$w->id_base] = $class_name; } }, 101); add_filter('widget_title', function ($title, $instance, $id_base) { global $wp_widget_factory; global $myplugin_wclass_names, $myplugin_some_sidebars; if ($title !== '') return $title; $class_name = $myplugin_wclass_names[$id_base]; $widget_id = $wp_widget_factory->widgets[$class_name]->id; $sidebar_id = is_active_widget(false, $widget_id, $id_base); if (in_array($sidebar_id, $myplugin_some_sidebars)) return ' '; return $title; }, 256, 3); |
Нестандартные виджеты и балансировка
В отношении виджетов, не следующих стандартам, опасность представляет отсутствие фильтрации заголовка, так как before_widget
и after_widget
не сбалансированы. Балансировка достигается добавлением пустого блока (или пустых блоков) между фрагментами before_widget
и before_title
: открывающие теги относятся к before_widget
, закрывающие — к before_title
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div id="%1$s" class="widget %2$s"> <!-- before_widget --> <div class="blank-block"> <!-- before_widget - добавлен пустой блок --> </div> <!-- before_title - закрывающий тег пустого блока --> <div class="widget-title"> <!-- before_title --> <!-- заголовок виджета --> </div> <!-- after_title --> <div class="widget-body"> <!-- after_title --> <!-- тело виджета --> </div> <!-- after_widget --> </div> <!-- after_widget --> |
В общем случае, когда after_widget
закрывает на N блоков больше, чем открывает before_widget
, следует добавлять вложенные N блоков.
Теперь без заголовка виджет, не проводящий фильтрацию, будет выглядеть так:
1 2 3 4 5 |
<div id="%1$s" class="widget %2$s"> <!-- before_widget --> <div class="blank-block"> <!-- before_widget --> <!-- тело виджета --> </div> <!-- after_widget --> </div> <!-- after_widget --> |
Смягчение отсутствия блоков тела и заголовка виджета
Добавленный блок div.blank-block
должен быть стилизован таким образом, чтобы его высота, рамки, вертикальные поля и отступы проявлялись только в нестандартных виджетах. В полноценных же эти свойства должны отсутствовать, иначе блок будет заметен. Отвечающий таким требованиям селектор можно записать как минимум двумя способами:
- при помощи псевдо-класса
:last-child
, если блок тела виджета является последним в блоке виджетаdiv.widget
(как в приведённом выше примере); - с использованием псевдо-классов
:not(:empty)
, если за блоком тела виджета следуют другие блоки — футер виджета, например;
Второй вариант срабатывает на блоках, содержащих хотя бы один символ — любой, включая пробельный (пробел, табуляция, перенос строки). Поэтому в виджете с заголовком пустой блок должен быть абсолютно пуст. Это надо учитывать в параметрах сайдбара: before_widget
должен завершаться открывающим тегом холостого блока, а before_title
— начинаться закрывающим:
1 2 3 4 5 6 7 |
register_sidebar(array( ... // никаких пробелов, табов, PHP_EOL и т.п. в конце строки: 'before_widget' => '<div id="%1$s" class="widget %2$s"><div class="blank-block">', 'before_title' => '</div><div class="widget-title">', // никаких пробелов в начале строки; ... )); |
Штатные виджеты WordPress между этими параметрами ничего не выводят, а остальные, как правило, используют их в качестве эталона. Тем не менее исключения опять-таки возможны. Чтобы с ними справиться, можно предусмотреть, напр., такой js-костыль:
1 2 3 4 5 6 7 |
jQuery(function ($) { $('.blank-block').each(function () { if ($(this).text().match(/^\s+$/)) { $(this).text(''); } }); }); |
В общем случае, когда добавлены N вложенных блоков, псевдо-класс :last-child
следует относить к самому внешнему, а :not(:empty)
— к самому внутреннему.
Оба псевдо-класса относятся к CSS3. Селекторы с ними могут выглядеть некорректно для устаревших или проблемных браузеров. Поэтому к таким селекторам следует отнести только те стили, что влияют на размеры и видимость пустого блока и потому не могут фигурировать среди заданных непосредственно для div.blank-block
.
По той же причине нельзя встраивать селектор CSS3 в наборы селекторов более ранних стандартов: если браузер его не поймёт, весь набор будет проигнорирован. (Такое желание может возникнуть при стилизации непустого «холостого блока» стилями блока тела виджета).
1 2 3 4 5 |
/* не следует добавлять селектор CSS3 в серию простых селекторов: */ .sidebar-X .blank-block:not(:empty), .sidebar-X .widget-body { /* исходные стили тела виджета */ } |
Вместо этого стили .widget-body
должны быть продублированы и разнесены по селекторам div.blank-block
с псевдо-классом и без. К первому относятся вертикальные отступы, поля и рамки, ко второму — всё остальное, а также удаление рамок и обнуление вертикальных полей, отступов и минимальной высоты.
Замечание 1 (для пользователей LESS): часто используемая в подобных случаях директива :extend
добавляет дополняемый селектор в набор к дополняющему. Чтобы этого избежать, вместо :extend(... .widget-body)
следует использовать селектор .widget-body
в качестве миксина:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.sidebar-X { .widget-body { /* стили */ } // неправильно: такой подход добавит селектор ".blank-block:not(:empty)" // к селектору ".sidebar-X .widget-body"; .blank-block:not(:empty):extend(.sidebar-X .widget-body) { /* остальные стили */ } // правильно: такой подход продублирует стили ".sidebar-X .widget-body"; .blank-block:not(:empty) { .sidebar-X >.widget-body(); /* остальные стили */ } } |
Замечание 2: вариант, когда div.blank-block
полностью наследует div.widget-body
, а div.blank-block:empty
содержит обнуление отступов, рамок и т. п., очень не удобен зависимостью от CSS3: в браузерах без поддержки псевдо-класса :empty
«пустые блоки» скрываться не будут. Поэтому такой вариант не рассматривается.
Замечание 3: эти предостережения могут не иметь значения для ресурсов, не предусматривающих работу без поддержки CSS3.
Ниже приведены два варианта вёрстки виджета, подобного рассмотренному в задаче: слева (синий) использует псевдо-класс :last-child
, справа (красный) — псевдо-класс :not(:empty)
.
Пустой блок не заметен.
Пустой блок не заметен.
Код примера доступен здесь.