В первой части статьи я рассказал о прекрасном фреймворке Omega для Drupal, который позволяет создавать собственные темы оформления, основанные на сетке (grid), произвольно настраивать и менять расположение и размеры регионов без каких-либо модификаций HTML-кода шаблонов. На этом можно было бы и остановиться, но существует прекрасный мощный CSS-фреймворк Bootstrap, имеющий собственную реализацию сетки, большое количество готовых CSS и JS компонентов, которые очень легко стилизовать, а также огромное сообщество, благодаря которому мы получаем наиболее качественный и «вылизанный» код по сравнению с конкурентами.
В этой части статьи я расскажу, как привязать Bootsrap к Omega. Но если вас по какой-либо причине не устраивает Bootstrap, то подобным образом можно прикрутить к Omega любой другой фреймворк на ваш вкус. Как и в первой части статьи, я не претендую на какую-то уникальность, просто описываю те вещи, которые можно почерпнуть из документации. Здесь будет много кода и мало картинок.
Сначала опять немного теории, чтобы получить представление о том, с чем нам предстоит работать. Итак, структура фреймворка Omega:
- Базовая тема Alpha, в которой заложен тот самый функционал, позволяющий задавать расположение зон и регионов через настройки темы, а также вводящий в наше распоряжение базовую сетку (grid) разных типов (Fixed и Fluid) и количества колонок (12, 16, 24).
- Базовая тема Omega, являющаяся субтемой базовой темы Alpha, в которой переопределены основные шаблоны Drupal для HTML5.
- Стартовые наборы (Starter kit), на основе которых нам предлагается создавать субтемы на базе Omega либо вручную, либо автоматически с использованием модуля Omega Tools.
Таким образом, для того, чтобы использовать Bootstrap совместно с Omega, необходимо сделать следующее:
- Создать базовую тему, являющуюся субтемой Omega, которая будет подключать Bootstrap и переопределять CSS-классы сетки Omega (grid-*) в Bootstrap (span*), а также добавлять необходимые CSS-классы для стандарных компонентов Drupal (например для кнопок, системных сообщений, «хлебных крошек», нумерации страниц и прочего).
- Создать стартовый набор для нашей базовой темы, чтобы быстро создавать субтемы с помощью Omega Tools.
Базовую тему будем создавать из стартового набора Omega HTML5 Startkit.
Подготовка базовой темы и стартового набора
- Копируем директорию
omega/starterkits/omega-html5
рядом с директориейomega
и переименовываем ее вomega_bootstrap
. - Переименовываем файлы
starterkit_omega_html5.info
→omega_bootstrap.info
,css/global.css
→css/omega-bootstrap.css
и удаляемYOURTHEME-alpha-default-narrow.css, YOURTHEME-alpha-default-normal.css, YOURTHEME-alpha-default-wide.css, YOURTHEME-alpha-default.css
. - Скачиваем Bootsrtap и складываем файлы
bootstrap.css
иbootstrap-responsive.css
в директориюcss
, а для файлаbootstrap.js
создаем директориюjs
. Использовать минифицированные версии нет необходимости, сжать CSS и JS мы сможем в дальнейшем стандартными средствами Drupal.
Далее начинаем редактировать файл настроек темы omega_bootstrap.info
. Сначала меняем параметры name = Omega with Bootstrap
и starterkit = FALSE
.
Для удобства параметры в этом файле разделены на разделы.
В разделах ADDITIONAL REGIONS
и ZONES
можем добавить необходимые нам зоны или регионы.
В разделе OPTIONAL STYLESHEETS
меняем все [global.css]
на [omega-bootstrap.css]
и задаем в параметре name
что-то вроде Omega Bootstrap custom styles
. Сюда мы также можем добавить дополнительные CSS (например, подключить FontAwesome), которые сможем потом включать-выключать в настройках темы в разделе Toggle styles (включение/отключение стилей).
Ниже раздела OPTIONAL STYLESHEETS
создаем разделы OPTIONAL LIBRARIES
и CSS GRID SYSTEMS
:
; OPTIONAL LIBRARIES
libraries[bootstrap][name] = 'Bootstrap'
libraries[bootstrap][description] = 'Sleek, intuitive, and powerful front-end framework for faster and easier web development.'
libraries[bootstrap][css][0][file] = bootstrap.css
libraries[bootstrap][css][0][options][weight] = 1
libraries[bootstrap][css][1][file] = bootstrap-responsive.css
libraries[bootstrap][css][1][options][weight] = 2
libraries[bootstrap][js][0][file] = bootstrap.js
libraries[bootstrap][js][0][options][weight] = -1
; CSS GRID SYSTEMS
grids[bootstrap][name] = Bootstrap
grids[bootstrap][layouts][normal] = Normal
grids[bootstrap][columns][12] = 12 Columns
Формат описания этих разделов мы взяли из alpha.info
и omega.info
.
Стартовый набор создаем также из Omega HTML5 Startkit:
- Копируем директорию
omega/starterkits/omega-html5
рядом сomega_bootstrap
, переименовываем вomega-html5-bootstrap
. - Переименовываем
starterkit_omega_html5.info
вstarterkit_omega_html5_bootstrap.info
, удаляемYOURTHEME-alpha-default-narrow.css, YOURTHEME-alpha-default-normal.css, YOURTHEME-alpha-default-wide.css, YOURTHEME-alpha-default.css
. - В файле
starterkit_omega_html5_bootstrap.info
меняем параметрыname = Omega HTML5 Starterkit with Bootstrap
иbase theme = omega_bootstrap
.
Наши базовая тема и стартовый набор готовы к использованию. Но это еще не конец, а скорее только начало.
Создание субтемы
Для более наглядного представления процесса, который будет происходить дальше, создадим новую тему способом, описанным в первой части статьи, с помощью Omega Tools, только в Base theme (базовая тема) выбираем Omega with Bootstrap, а в Starterkit (стартовый набор) — наш Omega HTML5 Starterkit with Bootstrap (рис. 1).
Устанавливаем нашу тему по умолчанию и переходим в настройки.
- На вкладке Grid settings (настройка сетки) в Grid system (тип сетки) появился новый тип Bootstrap, его и выбираем (рис. 2).
- На вкладке Zone and region configuration (конфигурация зон и регионов) добавляем классы Bootstrap к зонам и регионам. В разделе Configuration зоны в поле Additional zone classes задаем значение
row
, а в поле Additional wrapper classes — значениеcontainer
. В настройках регионов в поле Additional region classes задаем нужный классspan*
по необходимому нам количеству колонок. Omega позволяет задавать произвольные классы зонам и регионам, чем мы и воспользовались сейчас, но в дальнейшем мы автоматизируем этот процесс (рис. 3). - На вкладке Toggle libraries (включение/отключение библиотек) отключаем все стандартные библиотеки Omega и включаем нашу Bootstrap (рис. 4).
- На вкладке Toggle styles (включение/отключение стилей) также отключаем все стили в разделе Enable optional stylesheets (включить дополнительные стили), кроме Omega Bootstrap custom styles (all) — omega-bootstrap.css и Your custom global styles (all) — global.css (рис. 5).
Все остальные настройки задаем в соответствии с рекомендациями из первой части статьи и сохраняем их. Затем мы можем нажать кнопку Export theme settings, скопировать настройки и вставить их в файл omega-html5-bootstrap/starterkit_omega_html5_bootstrap.info
, заменив ими настройки в разделе THEME SETTINGS (DEFAULTS)
. В этом случае все новые темы из нашего стартового набора сразу будут настроены нужным нам образом.
Темизация
Bootstrap для стилизации элементов использует классы, которые отличаются от стандартных классов Drupal. Поэтому необходимо добавить классы Bootsrtap в вывод стандартных элементов Drupal. Для этого будем использовать стандартные возможности темизации Drupal, а именно переопределим нужные нам theme_
-функции. Подробнее о возможностях темизации Drupal можно узнать из раздела документации Using the theme layer (http://drupal.org/node/933976).
Функции темизации будем переопределять в файле template.php
в корне нашей базовой темы omega_bootstrap
, в этом случае они будут наследоваться всеми нашими субтемами.
Для начала избавимся от необходимости вручную добавлять классы container
и row
зонам, а также классы span*
регионам. Для этого добавим в template.php
функцию omega_bootstrap_process()
:
/**
* Implements hook_process().
*/
function omega_bootstrap_process(&$vars, $hook) {
if (!empty($vars['elements']['#grid']) || !empty($vars['elements']['#data']['wrapper_css'])) {
if (!empty($vars['elements']['#grid'])) {
foreach (array('prefix', 'suffix', 'push', 'pull') as $quality) {
if (!empty($vars['elements']['#grid'][$quality])) {
array_unshift($vars['attributes_array']['class'], 'offset' . $vars['elements']['#grid'][$quality]); # Добавляем класс offset* региону
}
}
array_unshift($vars['attributes_array']['class'], 'span' . $vars['elements']['#grid']['columns']); # Добавляем класс span* региону
}
$vars['attributes'] = $vars['attributes_array'] ? drupal_attributes($vars['attributes_array']) : '';
}
if (!empty($vars['elements']['#grid_container']) || !empty($vars['elements']['#data']['css'])) {
if (!empty($vars['elements']['#grid_container'])) {
$vars['content_attributes_array']['class'][] = 'container'; # Добавляем класс container зоне
}
$vars['content_attributes'] = $vars['content_attributes_array'] ? drupal_attributes($vars['content_attributes_array']) : '';
}
alpha_invoke('process', $hook, $vars);
}
Код мы позаимствовали из функции alpha_process()
, которую можно найти в файле omega/alpha/template.php
. Как видите, еще нужно добавить row
, для этого скопируем файлы шаблонов из omega/alpha/templates/zone.tpl.php
и omega/omega/templates/zone--content.tpl.php
в директорию omega_bootstrap/templates
и отредактируем следующим образом.
<?php
/**
* @file
* Alpha's theme implementation to display a zone.
*/
?>
<?php if ($wrapper): ?><div<?php print $attributes; ?>><?php endif; ?>
<div<?php print $content_attributes; ?>><div class="row">
<?php print $content; ?>
</div></div>
<?php if ($wrapper): ?></div><?php endif; ?>
<?php if ($wrapper): ?><div<?php print $attributes; ?>><?php endif; ?>
<div<?php print $content_attributes; ?>><div class="row">
<?php if ($breadcrumb): ?>
<div id="breadcrumb" class="grid-<?php print $columns; ?>"><?php print $breadcrumb; ?></div>
<?php endif; ?>
<?php if ($messages): ?>
<div id="messages" class="grid-<?php print $columns; ?>"><?php print $messages; ?></div>
<?php endif; ?>
<?php print $content; ?>
</div></div>
<?php if ($wrapper): ?></div><?php endif; ?>
Теперь можно убрать ненужные классы из настроек темы и задавать количество колонок в регионе обычным способом.
Теперь приступим к украшательствам. Код берем из соответствующих функций, немного модифицируем его под наши нужды и добавляем классы Bootstrap.
/**
* Implements theme_delta_blocks_breadcrumb().
*/
function omega_bootstrap_delta_blocks_breadcrumb($variables) {
$output = '';
if (!empty($variables['breadcrumb'])) {
if ($variables['breadcrumb_current']) {
$variables['breadcrumb'][] = l(drupal_get_title(), current_path(), array('html' => TRUE));
}
$output = '<div id="breadcrumb" class="clearfix"><ul class="breadcrumb">';
$switch = array('odd' => 'even', 'even' => 'odd');
$zebra = 'even';
$last = count($variables['breadcrumb']) - 1;
foreach ($variables['breadcrumb'] as $key => $item) {
$zebra = $switch[$zebra];
$attributes['class'] = array('depth-' . ($key + 1), $zebra);
if ($key == 0) {
$attributes['class'][] = 'first';
}
if ($key == $last) {
$attributes['class'][] = 'last';
}
else {
$item .= '<span class="divider">/</span>';
}
$output .= '<li' . drupal_attributes($attributes) . '>' . $item . '</li>';
}
$output .= '</ul></div>';
}
return $output;
}
messages
меняем на alert
.
/**
* Implements theme_status_messages().
*/
function omega_bootstrap_status_messages($variables) {
$display = $variables['display'];
$output = '';
$status_heading = array(
'status' => t('Status message'),
'error' => t('Error message'),
'warning' => t('Warning message'),
);
$class = array(
'status' => 'alert alert-success',
'error' => 'alert alert-error',
'warning' => 'alert',
);
foreach (drupal_get_messages($display) as $type => $messages) {
$output .= "<div class="{$class[$type]}">n";
if (!empty($status_heading[$type])) {
$output .= '<h2 class="element-invisible">' . $status_heading[$type] . "</h2>n";
}
if (count($messages) > 1) {
$output .= " <ul>n";
foreach ($messages as $message) {
$output .= ' <li>' . $message . "</li>n";
}
$output .= " </ul>n";
}
else {
$output .= $messages[0];
}
$output .= "</div>n";
}
return $output;
}
nav-pills
, можно заменить на nav-tabs
.
/**
* Implements theme_menu_local_tasks().
*/
function omega_bootstrap_menu_local_tasks(&$variables) {
$output = '';
if (!empty($variables['primary'])) {
$variables['primary']['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2>';
$variables['primary']['#prefix'] .= '<ul class="nav nav-pills">';
$variables['primary']['#suffix'] = '</ul>';
$output .= drupal_render($variables['primary']);
}
if (!empty($variables['secondary'])) {
$variables['secondary']['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2>';
$variables['secondary']['#prefix'] .= '<ul class="nav nav-pills">';
$variables['secondary']['#suffix'] = '</ul>';
$output .= drupal_render($variables['secondary']);
}
return $output;
}
btn
.
/**
* Implements theme_button().
*/
function omega_bootstrap_button($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'submit';
element_set_attributes($element, array('id', 'name', 'value'));
$element['#attributes']['class'][] = 'btn';
switch($element['#id']) { # Разукрашиваем основные кнопки
case strpos($element['#id'], 'edit-submit') === 0: $element['#attributes']['class'][] = 'btn-primary'; break;
case 'edit-preview': $element['#attributes']['class'][] = 'btn-warning'; break;
case 'edit-delete': $element['#attributes']['class'][] = 'btn-danger'; break;
}
$element['#attributes']['class'][] = 'form-' . $element['#button_type'];
if (!empty($element['#attributes']['disabled'])) {
$element['#attributes']['class'][] = 'form-button-disabled btn-disabled';
}
return ' <input' . drupal_attributes($element['#attributes']) . ' /> ';
}
pagination
.
/**
* Implements theme_pager().
*/
function omega_bootstrap_pager($variables) {
$tags = $variables['tags'];
$element = $variables['element'];
$parameters = $variables['parameters'];
$quantity = $variables['quantity'];
global $pager_page_array, $pager_total;
// Calculate various markers within this pager piece:
// Middle is used to "center" pages around the current page.
$pager_middle = ceil($quantity / 2);
// current is the page we are currently paged to
$pager_current = $pager_page_array[$element] + 1;
// first is the first page listed by this pager piece (re quantity)
$pager_first = $pager_current - $pager_middle + 1;
// last is the last page listed by this pager piece (re quantity)
$pager_last = $pager_current + $quantity - $pager_middle;
// max is the maximum page number
$pager_max = $pager_total[$element];
// End of marker calculations.
// Prepare for generation loop.
$i = $pager_first;
if ($pager_last > $pager_max) {
// Adjust "center" if at end of query.
$i = $i + ($pager_max - $pager_last);
$pager_last = $pager_max;
}
if ($i <= 0) {
// Adjust "center" if at start of query.
$pager_last = $pager_last + (1 - $i);
$i = 1;
}
// End of generation loop preparation.
$li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
$li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t('‹ previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ›')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
if ($pager_total[$element] > 1) {
if ($li_first) {
$items[] = array(
'data' => $li_first,
);
}
if ($li_previous) {
$items[] = array(
'data' => $li_previous,
);
}
// When there is more than one page, create the pager list.
if ($i != $pager_max) {
if ($i > 1) {
$items[] = array(
'data' => '<span>…</span>',
);
}
// Now generate the actual pager piece.
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
if ($i < $pager_current) {
$items[] = array(
'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
);
}
if ($i == $pager_current) {
$items[] = array(
'class' => array('active'),
'data' => "<span>$i</span>",
);
}
if ($i > $pager_current) {
$items[] = array(
'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
);
}
}
if ($i < $pager_max) {
$items[] = array(
'data' => '<span>…</span>',
);
}
}
// End generation.
if ($li_next) {
$items[] = array(
'data' => $li_next,
);
}
if ($li_last) {
$items[] = array(
'data' => $li_last,
);
}
return '<h2 class="element-invisible">' . t('Pages') . '</h2><div class="pagination pagination-centered">' . theme('item_list', array(
'items' => $items,
)) . '</div>';
}
}
И так далее по такому же принципу. Если нужно в стандартный вывод Drupal добавить классы Bootstrap — просто переопределяем функцию темизации (или шаблон), все это делается стандартными средствами Drupal. Какие-то дополнительные функции можете позаимствовать из Drupal-темы Bootstrap. Также можно дополнить эти функции дополнительными параметрами, которые можно будет менять в настройках темы. Примеры настроек: символ-разделитель в «хлебных крошках», nav-tabs
или nav-pills
, размер и расположение нумератора страниц (pagination-large
/ pagination-small
/ pagination-mini
и pagination-centered
/ pagination-right
). Подробнее об этом можно почитать в документации Creating advanced theme settings (http://drupal.org/node/177868).
Использование
Ну а теперь перейдем к практической части и посмотрим, как всем этим можно пользоваться. Для начала я бы рекомендовал настроить вспомогательную сетку-оверлей под ширину колонок Bootstrap. Для этого нужно отыскать PNG от базовой темы Alpha, модифицировать в их в графическом редакторе и переопределить класс alpha-grid-debug
в нашем omega-bootstrap.css
.
Обратите внимание, что для использования JS-компонентов Bootstrap необходимо подключить jQuery 1.7.x с помощью модуля jQuery Update.
С помощью модуля Block Class (который упоминался в первой части статьи) очень удобно добавлять необходимые классы блокам, например стандартный well
(см. рис. 6).
В настройках отображений модуля Views есть стандартная возможность задавать произвольные классы практически всем компонентам, что открывает очень широкие возможности для использования Bootstrap.
Пример 1: вывод материалов сеткой. Для этого достаточно добавить лишь добавить класс row
всему представлению Other — CSS class, а в настройках класса строки форма вывода Format — Unformatted list — Settings — Row class задать класс с нужным количеством колонок, например span2
.
Пример 2: вывод новостей по помощью Bootstrap-компонента Media object. Для этого добавляем в Format — Unformatted list — Settings — Row class класс media
, в параметрах поля с изображением Style settings — Customize field and label wrapper HTML — Create a CSS class добавить класс pull-left
, и в таких же параметрах поля с текстом добавить класс media-body
. Другие поля можно добавить в это же поле через Rewrite results — Rewrite the output of this field, не забыв при этом сами эти поля отключить для вывода (Exclude from display).
Также с помощью модуля Views Bootstrap (спасибо mrded) для оформления Views можно использовать такие компоненты Bootstrap как Thumbnails и Carousel.
Ну и напоследок небольшая демонстрация некоторых возможностей получившегося у нас заготовка темы.
Рисунок 6. Готовая тема с Bootstrap с включенными отладочными блоками.
Спасибо за внимание! Надеюсь, статья окажется полезной и расширит границы вашего использования Drupal.
Автор: Nilard