О проекте

Данный сайт попытка повысить коммуникацию самых активных разработчиков на CMS Drupal - блоггеров. Если Вы ведете свой блог о Друпале, значит Вы готовы делиться вашими знаниями, помочь другим. Не всегда ваши знания доходят до потребителя. Задача данного сайта агрегировать знания различных блогов в единую ленту и привести на Ваши блоги активных пользователей.

Активность пользователей на Вашем сайте будет дополнительным стимулом к дальнейшей работе.

Удачи во всех Ваших начинаниях!

Создание сложного поля CCK с диаграммой (ч2)

-10 votes
+
-

Создание сложного поля CCK с диаграммой. Часть 1
В первой части статьи мы создали составное CCK-поле, которое позволяет пользователю вводить данные в виде таблицы и также показывает эти данные на странице ноды в виде красивой таблицы.
Напомню, что нужно ещё прикрутить отрисовку на графике данных, вводимых пользователем, в реальном времени. Точно такой же график должен быть показан на странице ноды под таблицей с данными. Прототип:

Виджет формы на данный момент выгдялит так:

На странице ноды таблица с данными выводится в таком виде:

Самое время воспользоваться jQuery и добавить динамики в наше составное поле.
Реализация динамической диаграммы
Drupal использует jQuery и мы выбрали jQuery-плагин, который предназначен для создания диаграмм — Flot. Он создает графики на основании наборов данных «на лету» на стороне клиента.
Именно эта библиотека очень проста в использовании, хорошо выглядит и предоставляем много интересных штучек — например, зум или слежение за курсором.
Плагин работает в Internet Explorer 6/7/8, Firefox 2.x+, Safari 3.0+, Opera 9.5+ и Konqueror 4.x+ с HTML-тегом canvas (для IE используется эмуляция на JavaScript — excanvas).
Нам нужно показывать графики в 2х разных местах — на форме при создании или изменении ноды и на странице показа ноды. На странице показа ноды мы также использовали динамическое создание диаграмм, чтобы, во-первых — делать это на стороне клиента, а не нагружать сервер, а, во-вторых, — не тратить лишнего времени на разработку и тестирование системы кеширования изображений и повторно использовать код, который уже работает.
Диаграмма на форме
Сначала подключим код нашей библиотеки. Делаем мы это в hook_nodeapi(), а не в hook_init(), потому что нам нужно подгружать код только для страницы ноды или формы ноды, а hook_init() выполняется для каждой страницы. В случае произвольной формы нужно было бы использовать hook_init().
/**
* Реализация hook_nodeapi().
*/
function financial_table_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
if ($op == 'prepare' || $op == 'view' || $op == 'validate') {
//Подключаем библиотеку excanvas для тупого ИЕ:
financial_table_flot_add_js();
 
//Выполняется просмотр ноды или добавление/редактирование ноды.
//Добавляем загрузку сжатой версии плагина Flot:
drupal_add_js('sites/all/libraries/flot/jquery.flot.min.js');
//Подключаем стили:
drupal_add_css(drupal_get_path('module', 'financial_table') . '/financial_table.css');
}
//Добавляем загрузку нашего JS-кода, который и строит график:
if ($op == 'view') {
drupal_add_js(drupal_get_path('module', 'financial_table') . '/financial_table_view.js');
} elseif ($op == 'prepare' || $op == 'validate') {
drupal_add_js(drupal_get_path('module', 'financial_table') . '/financial_table_edit.js');
}
}
 
/**
* Вспомогательная функция для добавления excanvas для IE
*/
function financial_table_flot_add_js() {
static $added;
if ($added !== true) {
$path = 'sites/all/libraries/flot';
// Different versions of flot have used different packing methods. Attempt to support both.
$excanvas = file_exists("{$path}/excanvas.min.js") ? "{$path}/excanvas.min.js" : "{$path}/excanvas.pack.js";
drupal_set_html_head('<!--[if IE]><script language="javascript" type="text/javascript" src="'. base_path() . $excanvas . '"></script><![endif]-->');
 
$added = true;
}
}
Место для диаграммы в виджете формы
Для работы нашего виджета мы в первой части статьи уже создали в файле template.php текущей темы функцию THEME_NAME_content_multiple_values($element). Теперь её нужно изменить.
Добавляем специальный класс к нашему элементу. Это поможет с помощью jQuery быстро найти все поля на странице и добавить к ним отрисовку графиков.
//Add a class for financial table fields.
if ($field['type'] == 'financial_table') {
$financial_table_class = ' financial-table-field';
}
Добавляем место для нашей диаграммы:
//Add a placeholder for Flot diagram.
if ($field['type'] == 'financial_table') {
$output .= '<div id="' . $table_id . '_placeholder" class="financal_table_placeholder" style="width:840px;height:350px;"></div>';
}
Со всеми изменениями функция выглядит так:
/**
* Overriding of content module default rendering for multiple values
* Used for node creation form.
* Combine multiple values into a table with drag-n-drop reordering.
*/
function THEME_NAME_content_multiple_values($element) {
$field_name = $element['#field_name'];
$field = content_fields($field_name);
$output = '';
// here is the definition of fields don't need to be reordered manually
$fields_no_manual_reordering = array('field_employment', 'field_education');
$noReordering = in_array($field_name, $fields_no_manual_reordering);
 
if ($field['multiple'] >= 1) {
$table_id = $element['#field_name'] .'_values';
$order_class = $element['#field_name'] .'-delta-order';
$required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
 
if ($field['type'] == 'financial_table') {
if(function_exists('financial_table_get_widget_header')) {
$header = financial_table_get_widget_header($field, $required);
}
}
else {
$header = array(
array(
'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
'colspan' => 2
),
$noReordering ? null : t('Order'),
);
}
 
$rows = array();
 
// Sort items according to '_weight' (needed when the form comes back after
// preview or failed validation)
$items = array();
foreach (element_children($element) as $key) {
if ($key !== $element['#field_name'] .'_add_more') {
$items[] = &$element[$key];
}
}
usort($items, '_content_sort_items_value_helper');
 
// Add the items as table rows.
foreach ($items as $key => $item) {
$item['_weight']['#attributes']['class'] = $order_class;
$delta_element = drupal_render($item['_weight']);
$cells = array(
array('data' => '', 'class' => 'content-multiple-drag'),
drupal_render($item),
$noReordering ? null : array('data' => $delta_element, 'class' => 'delta-order'),
);
$rows[] = array(
'data' => $cells,
'class' => 'draggable',
);
}
//Add a class for financial table fields.
if ($field['type'] == 'financial_table') {
$financial_table_class = ' financial-table-field';
}
$output .= theme('table', $header, $rows,
array('id' => $table_id,
'class' => 'content-multiple-table' . $financial_table_class)
);
$output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
$output .= drupal_render($element[$element['#field_name'] .'_add_more']);
if (!$noReordering) {
drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
}
 
}
else {
foreach (element_children($element) as $key) {
$output .= drupal_render($element[$key]);
}
}
if ($output) {
//Add a placeholder for Flot diagram.
if ($field['type'] == 'financial_table') {
$output .= '<div id="' . $table_id . '_placeholder" class="financal_table_placeholder" style="width:840px;height:350px;"></div>';
}
if (($field['type'] == 'financial_table' || $field['type'] == 'funding_history_table')
&& $field['display_settings ']['label']['format'] == 'above')
{
$output = '<label for="' . $table_id .'">' . $element['#title'] . ': </label>' . $output;
}
}
return $output;
}
Вы подготовили HTML-код для отрисовки графиков — добавили место и дополнительные стили. Теперь нужно передать данные из формы в библиотеку Flot, которая по ним нарисует диаграмму.
Добавление JS-кода, рисующего график на форме
Теперь под таблицей для ввода данных есть место для графика, но нам нужно передавать данные библиотеке, чтобы отрисовать то, что пользователь ввел. Делаем мы это с помощью JS-кода, который размещаем в файле financial_table_edit.js. Раньше мы уже добавили подключение этого файла в hook_nodeapi().
Drupal.behaviors.financial_table = function(context) {
//Рисуем график в первый раз с данными, которые уже есть на форме:
$('.financial-table-field').each(function () {
Drupal.financial_table.redrawPlot($(this).attr('id'));
});
//Перерисовываем график, если пользователь изменил значения полей.
//ССK-полей этого типа на странице может быть несколько и мы ищем их по классу,
//а не по ID. Если что-то изменилось — функции отрисовки графика
//передается ID плейсхолдера для отрисовки графика.
$('.financial_table_year').change(function(){
Drupal.financial_table.redrawPlot($(this).parents().filter('.financial-table-field').attr('id'));
});
$('.financial_table_revenues').change(function(){
Drupal.financial_table.redrawPlot($(this).parents().filter('.financial-table-field').attr('id'));
});
$('.financial_table_expenditures').change(function(){
Drupal.financial_table.redrawPlot($(this).parents().filter('.financial-table-field').attr('id'));
});
$('.financial_table_net').change(function(){
Drupal.financial_table.redrawPlot($(this).parents().filter('.financial-table-field').attr('id'));
});
};
 
/**
* Создаем собственную область имен функций.
*/
Drupal.financial_table = {}
 
Drupal.financial_table.redrawPlot = function (placeholderID) {
var Rows = $('#' + placeholderID + ' tbody tr');
var revenues = new Array();
var expenditures = new Array();
var net = new Array();
Rows.each(function () {
var FieldCell = $(this).find('td').eq(1);
var year = FieldCell.find('.financial_table_year').val();
//Отрисовываем как только указан год
if (year>0) {
revenues.push([year, FieldCell.find('.financial_table_revenues').val()]);
expenditures.push([year, FieldCell.find('.financial_table_expenditures').val()]);
net.push([year, FieldCell.find('.financial_table_net').val()]);
}
});
//Сортируем данные, чтобы показывать красивый график независимо от того,
//как отсортированы данные пользователем.
var placeholder = $('#' + placeholderID + '_placeholder');
// Рисуем!
var plot = $.plot(placeholder, [
{
data: revenues.sort(Drupal.financial_table.sort),
label: "Revenues"
},
{
data: expenditures.sort(Drupal.financial_table.sort),
label: "Expenditures"
},
{
data: net.sort(Drupal.financial_table.sort),
label: "Net"
}
], {
xaxis: {
tickDecimals: 0},//Ось лет — показываем только целые числа
yaxis: {
//Ось сумм — добавляем тысячные разделители и знак «$» впереди
tickDecimals: 0,
tickFormatter: function (nStr) {
//Источник: http://www.mredkj.com/javascript/nfbasic.html
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return "$" + x1 + x2;
}
}, //Amount
series: {
lines: { show: true },
points: { show: true }
}
});
// Добавляем подписи к осям
placeholder.append('<div style="left:-60px;top:160px" class="axis-legend">Amount</div>');
placeholder.append('<div style="left:405px;bottom:-10px" class="axis-legend">Years</div>');
 
};
 
// Функция сортировки для правильной последовательности лет на графике
Drupal.financial_table.sort = function (a, b) {
return (a[0] - b[0]);
}
Вот как выглядит теперь виджет:

Отлично! При изменении данных в ячейках — меняется диаграмма. Осталось вывести график на странице просмотра ноды.
Динамическая диаграмма на странице просмотра ноды
В первой части мы создали в текущей теме файл content-field.tpl.php и добавили туда свой код. Пришло время разместить там график:
<?php
if ($field['type'] == 'financial_table') {
if ($field['rendered_table']) {
?>
<div class="field field-type-<?php print $field_type_css ?> field-<?php print $field_name_css ?>">
<?php if ($label_display == 'above') : ?>
<div class="field-label"><?php print t($label) ?>:&nbsp;</div>
<?php endif;?>
<div class="field-items">
<?php echo $field['rendered_table'] ?>
<?php if ($field['type'] == 'financial_table'): ?>
<div id="<?php print $field['field_name'] ?>_placeholder" class="financal_table_placeholder" style="width:640px;height:350px;"></div>
<?php endif; ?>
</div>
</div>
<?php
}
}
else {
?>
<?php if (!$field_empty) : ?>
<div class="field field-type-<?php print $field_type_css ?> field-<?php print $field_name_css ?>">
<?php if ($label_display == 'above') : ?>
<div class="field-label"><?php print t($label) ?>:&nbsp;</div>
<?php endif;?>
<div class="field-items">
<?php $count = 1;
foreach ($items as $delta => $item) :
if (!$item['empty']) : ?>
<div class="field-item <?php print ($count % 2 ? 'odd' : 'even') ?>">
<?php if ($label_display == 'inline') { ?>
<div class="field-label-inline<?php print($delta ? '' : '-first')?>">
<?php print t($label) ?>:&nbsp;</div>
<?php } ?>
<?php print $item['view'] ?>
</div>
<?php $count++;
endif;
endforeach;
?>
</div>
</div>
<?php endif;
}
Добавлено место для диаграммы. Его размеры должны быть сразу заданы, чтобы Plot смог правильно масштабировать элементы графика.
Данные для графика
Подготовим данные для графика. Делаем мы это в функции THEME_NAME_preprocess_content_field(&$vars)) и передаем нашему JS-скрипту через Drupal.settings.
function green_evolution_preprocess_content_field (&$vars) {
if ($vars['field']['type'] == 'financial_table') {
if (!$vars['field_empty']) {
$header = array(t('Year'), t('Revenues'), t('Expenditures'), t('Net'));
$rows = array();
foreach ($vars['items'] as $delta => $item) {
if (!$item['empty']) {
if(isset($item['financial_table_year']) &&
isset($item['financial_table_revenues']) &&
isset($item['financial_table_expenditures']) &&
isset($item['financial_table_net']))
{
$rows[] = array(
$item['financial_table_year'],
$item['financial_table_revenues'],
$item['financial_table_expenditures'],
$item['financial_table_net'],
);
$js_revenues[$item['financial_table_year']] = array($item['financial_table_year'], $item['financial_table_revenues']);
$js_expenditures[$item['financial_table_year']] = array($item['financial_table_year'], $item['financial_table_expenditures']);
$js_net[$item['financial_table_year']] = array($item['financial_table_year'], $item['financial_table_net']);
}
}
}
if(count($rows) > 0) {
$vars['field']['rendered_table'] = theme('table', $header, $rows, array('id' => 'field-' . $field_name_css, 'class' => $field['type']));
//Sort all data to have a nice diagram
ksort($js_revenues);
ksort($js_net);
ksort($js_expenditures);
 
//Удаляем ключи и готовим массив для использования в качестве объекта JS
$plot_data = array(
$vars['field']['field_name'] . '_placeholder' => array(
'revenues' => array_values($js_revenues),
'expenditures' => array_values($js_expenditures),
'net' => array_values($js_net),
),
);
//Передаем данные для использования на странице через Drupal.settings
drupal_add_js($plot_data, 'setting');
}
}
}
}
Добавляем JS-кода для рисования графика на странице ноды
Осталось на основании данных, которые уже переданы в JS построить график, на месте, которое мы определили раньше. Для этого на странице просмотра ноды у нас подгружается скрипт financial_table_view.js:
/**
* @file financial_table_view.js
**/
Drupal.behaviors.financial_table = function(context) {
//Find all financal_table placeholders
$('.financal_table_placeholder').each(function () {
var placeholderID = $(this).attr('id');
var placeholder = $("#" + placeholderID);
// plot it
var plotData = eval('Drupal.settings.' + placeholderID);
var plot = $.plot(placeholder, [
{
data: eval('Drupal.settings.' + placeholderID + '.revenues'),
label: "Revenues"
},
{
data: eval('Drupal.settings.' + placeholderID + '.expenditures'),
label: "Expenditures"
},
{
data: eval('Drupal.settings.' + placeholderID +'.net'),
label: "Net"
},
], {
yaxis: {
tickDecimals: 0,
tickFormatter: function (nStr) {
//Source: http://www.mredkj.com/javascript/nfbasic.html
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return "$" + x1 + x2;
}
},
xaxis: { tickDecimals: 0},
series: {
lines: { show: true },
points: { show: true }
}
});
// add axis labels
placeholder.append('<div style="left:-60px;top:160px" class="axis-legend">Amount</div>');
placeholder.append('<div style="left:300px;bottom:-10px" class="axis-legend">Years</div>');
 
});
}
Код подобен тому, что используется для виджета, за исключением того, что нам не нужно следить за изменением данных формы и перерисовывать график. Код ищет все места размещения гшрафика и для них ищет данные в Drupal.settings. Далее они используются, чтобы построить диаграмму:

Составное CCK-поле с динамическим графиком готово!
Библиология
Обзоры библиотек

JS-библиотеки для создания SVG изображений

SVG

Построение графиков на основании HTML-таблиц

Полный оригинальный материал:

ShvetsGroup