Иногда, по тем или иным причинам, нам придётся создавать свои собственные типы полей. Причин может быть много:
Для всех этих целей в Друпале предусмотрен удобный инструмент, называемый Fields API. В данном уроке мы создадим своё поле «размер», где будем хранить длину, ширину и высоту наших сущностей.
P.S. Предпологается наличие некоторых базовых знаний CMF/CMS Drupal.
Существует несколько основных моментов в создании поля:
Мы создадим модуль dimfield, который будет создавать нам тип поля dimensions. Использование глубины будет опциональным.
Наиболее важной функцией, при создании типа поля, является фунция field_schema. Как можно понять из названия, она возвращает массив с информацией о Drupal Schema.
Объявление поля. Теперь нам необходимо перегрузить хук hook_field_info(), которые возвращает массив с информацией о нашем поле.
Определение пустого значения. Далее необходимо описать случай с пустым значением нашего поля. Мы должны пояснить Друпалу, что мы считаем пустым значением. Когда указана длина, и не указана ширина? Или оба значения пусты?
Настройка поля. Большинство полей может быть настроено из веб-интерфейса Друпала. Мы тоже предоставим эту возможность.
Валидация поля.
Создание виджета. Мы создадим два вида виджета для нашего поля: простой и посложнее. Простой будет состоять из обычных трех текстовых полей и селектора для выбора метрики(сантиметры, дюймы и т.п.). Сложный будет состоять из одного текстового поля. Также нам понадобиться маленький css файл, который мы назовём dimfield-admin.css и положим в папку с модулем.
Форматтер. Настал момент определиться с тем, как мы будем отдавать пользовательским глазам наше поле. Друпал позволяет контролировать обычный вывод на экран, для rss лент, для печати и т.д. с помощью форматтеров. Мы создадим их так же два вида: для вывода одного значения и для комплексного вывода.
- Мы хотим концептуально рассматривать какую-либо информацию, как нечто атомарное, а не составленное из более мелких частей.
- У нас есть комплексная информация, и мы хотим иметь много её значений в каждой сущности.
- Мы хотим предоставлять пользователю удобный интерфейс для редактирования нашего типа.
- Мы хотим отображать пользователю наш тип, в собственном формате.
Для всех этих целей в Друпале предусмотрен удобный инструмент, называемый Fields API. В данном уроке мы создадим своё поле «размер», где будем хранить длину, ширину и высоту наших сущностей.
P.S. Предпологается наличие некоторых базовых знаний CMF/CMS Drupal.
Как работает Fields API?
Существует несколько основных моментов в создании поля:
- Field type(тип поля): определяет имя поля и внутреннюю структуру.
- Field(поле): конкретная конфигурация типа поля.
- Field instance(экземпляр поля): связь конкретного поля с узлом или подклассом сущности.
- Widget(виджет): элемент формы, представляющий поле в редакторе контента. Этот элемент может использовать как простое текстовое поле, так и интерактивный флэш инструмент.
- Formatter(форматтер): часть отвечающая за форматирование данных поля для отображения пользователю.
Мы создадим модуль dimfield, который будет создавать нам тип поля dimensions. Использование глубины будет опциональным.
Создаём новый тип поля.
dimfield.info
name = Dimensions field
description = A Field offering height, width, and depth
package = Drupal 7 Development
core = 7.x
files[] = dimfield.module
dimfield.install
Наиболее важной функцией, при создании типа поля, является фунция field_schema. Как можно понять из названия, она возвращает массив с информацией о Drupal Schema.
function dimfield_field_schema($field) {
if ($field['type'] == 'dimensions') {
$schema['columns']['height'] = array(
'type' => 'int',
'not null' => FALSE,
);
$schema['columns']['width'] = array(
'type' => 'int',
'not null' => FALSE,
);
$schema['indexes'] = array(
'height' => array('height'),
'width' => array('width'),
);
if ($field['settings']['num_dimensions'] == 3) {
$schema['columns']['depth'] = array(
'type' => 'int',
'not null' => FALSE,
);
$schema['indexes']['depth'] = array('depth');
}
$schema['columns']['units'] = array(
'type' => 'varchar',
'length' => 10,
'not null' => FALSE,
);
return $schema;
}
}
dimfield.module
Объявление поля. Теперь нам необходимо перегрузить хук hook_field_info(), которые возвращает массив с информацией о нашем поле.
function dimfield_field_info() {
return array(
'dimensions' => array(
'label' => t('Dimensions'),
'description' => t('This field stores a height and width, and possibly depth.'),
'settings' => array('num_dimensions' => 2),
'instance_settings' => array(
'max_height' => 0,
'max_width' => 0,
'max_depth' => 0,
),
'default_widget' => 'dimfield_combined',
'default_formatter' => 'dimfield_default',
),
);
}
Определение пустого значения. Далее необходимо описать случай с пустым значением нашего поля. Мы должны пояснить Друпалу, что мы считаем пустым значением. Когда указана длина, и не указана ширина? Или оба значения пусты?
function dimfield_field_is_empty($item, $field) {
if ($field['type'] == 'dimensions') {
if (empty($item['height']) && empty($item['width']) && ($field['settings']['num_dimensions'] == 2 || empty($item['depth']))) {
return TRUE;
}
}
return FALSE;
}
Настройка поля. Большинство полей может быть настроено из веб-интерфейса Друпала. Мы тоже предоставим эту возможность.
function dimfield_field_settings_form($field, $instance, $has_data) {
if ($field['type'] == 'dimensions') {
$settings = $field['settings'];
$form['num_dimensions'] = array(
'#type' => 'select',
'#title' => t('How many dimensions'),
'#options' => array(
2 => t('Height and width'),
3 => t('Height, width, and depth'),
),
'#default_value' => $settings['num_dimensions'],
'#required' => FALSE,
'#description' => t('Is this for a 2-dimensional or 3-dimensional object?'),
);
return $form;
}
}
function dimfield_field_instance_settings_form($field, $instance) {
$settings = $instance['settings'];
if ($field['type'] == 'dimensions') {
$form['max_height'] = array(
'#type' => 'textfield',
'#title' => t('Max height'),
'#size' => 10,
'#default_value' => $settings['max_height'],
'#description' => t('The largest allowed value for the height. Use 0 for no limit.'),
);
$form['max_width'] = array(
'#type' => 'textfield',
'#title' => t('Max width'),
'#size' => 10,
'#default_value' => $settings['max_width'],
'#description' => t('The largest allowed value for the width. Use 0 for no limit.'),
);
if ($field['settings']['num_dimensions'] == 3) {
$form['max_depth'] = array(
'#type' => 'textfield',
'#title' => t('Max depth'),
'#size' => 10,
'#default_value' => $settings['max_depth'],
'#description' => t('The largest allowed value for the depth. Use 0 for no limit.'),
);
}
}
return $form;
}
Валидация поля.
function dimfield_field_validate($obj_type, $object, $field, $instance, $langcode, &$items, &$errors) {
if ($field['type'] == 'dimensions') {
$columns = array(
'height' => 'max_height',
'width' => 'max_width',
);
if ($field['settings']['num_dimensions'] == 3) {
$columns['depth'] = 'max_depth';
}
foreach ($items as $delta => $item) {
foreach ($columns as $column => $max_key) {
if ($instance['settings'][$max_key] && !empty($item[$column]) && $item[$column] > $instance['settings'][$max_key]) {
$errors[$field['field_name']][$delta][] = array(
'error' => 'dimfield_' . $max_key,
'message' => t('%name: The %column may not be larger than %max.', array(
'%column' => $column,
'%name' => $instance['label'],
'%max' => $instance['settings'][$max_key],
)),
);
}
}
}
}
}
Создание виджета. Мы создадим два вида виджета для нашего поля: простой и посложнее. Простой будет состоять из обычных трех текстовых полей и селектора для выбора метрики(сантиметры, дюймы и т.п.). Сложный будет состоять из одного текстового поля. Также нам понадобиться маленький css файл, который мы назовём dimfield-admin.css и положим в папку с модулем.
///////////////// Widgets ///////////////////////
/**
* Implements hook_widget_info().
*/
function dimfield_field_widget_info() {
return array(
'dimfield_simple' => array(
'label' => t('Separate text fields'),
'description' => t('Allow the user to enter each dimension separately.'),
'field types' => array('dimensions'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
'dimfield_combined' => array(
'label' => t('Combined text field'),
'description' => t('Allow the user to enter all dimensions together.'),
'field types' => array('dimensions'),
'settings' => array('size' => 10),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
);
}
/**
* Implements hook_field_widget_settings_form().
*/
function dimfield_field_widget_settings_form($field, $instance) {
$form = array();
$widget = $instance['widget'];
$settings = $widget['settings'];
if ($widget['type'] == 'dimfield_combined') {
$form['size'] = array(
'#type' => 'textfield',
'#title' => t('Size of textfield'),
'#default_value' => $settings['size'],
'#required' => TRUE,
'#element_validate' => array('_element_validate_integer_positive'),
);
}
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function dimfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$base = $element;
if ($instance['widget']['type'] == 'dimfield_simple') {
$element['height'] = array(
'#type' => 'textfield',
'#title' => t('Height'),
'#default_value' => isset($items[$delta]['height']) ? $items[$delta]['height'] : NULL,
) + $base;
$element['width'] = array(
'#type' => 'textfield',
'#title' => t('Width'),
'#default_value' => isset($items[$delta]['width']) ? $items[$delta]['width'] : NULL,
) + $base;
if ($field['settings']['num_dimensions'] == 3) {
$element['depth'] = array(
'#type' => 'textfield',
'#title' => t('Depth'),
'#default_value' => isset($items[$delta]['depth']) ? $items[$delta]['depth'] : NULL,
) + $base;
}
$element['units'] = array(
'#type' => 'select',
'#title' => t('Units'),
'#default_value' => isset($items[$delta]['units']) ? $items[$delta]['units'] : NULL,
'#options' => dimfield_units(),
) + $base;
}
elseif ($instance['widget']['type'] == 'dimfield_combined') {
$element['#element_validate'] = array('_dimfield_combined_validate');
$default = NULL;
if (isset($items[$delta])) {
$item = $items[$delta];
if (isset($item['height'], $item['width'])) {
$default = $item['height'] . 'x' . $item['width'];
if ($field['settings']['num_dimensions'] == 3) {
$default .= 'x' . $item['depth'];
}
}
}
$element['dimfield_combined_wrapper']['#theme'] = 'dimfield_combined_wrapper';
$element['dimfield_combined_wrapper']['#attached']['css'][] = drupal_get_path('module', 'dimfield') . '/dimfield-admin.css';
$element['dimfield_combined_wrapper']['height_width_depth'] = array(
'#title' => t('Dimensions (separated by x)'),
'#type' => 'textfield',
'#default_value' => $default,
'#size' => $instance['widget']['settings']['size'],
) + $base;
$element['dimfield_combined_wrapper']['units'] = array(
'#type' => 'select',
'#title' => t('Units'),
'#default_value' => isset($items[$delta]['units']) ? $items[$delta]['units'] : NULL,
'#options' => dimfield_units(),
) + $base;
}
return $element;
}
/**
* Implements hook_theme().
*/
function dimfield_theme() {
return array(
'dimfield_combined_wrapper' => array(
'render element' => 'element',
),
);
}
/**
* Theme function for the combined_wrapper widget.
* @ingroup themeable
*/
function theme_dimfield_combined_wrapper($variables) {
$element = $variables['element'];
$hwd = drupal_render($element['height_width_depth']);
$units = drupal_render($element['units']);
return <<<END
{$hwd}
{$units}
END;
}
function _dimfield_combined_validate($element, &$form_state) {
// This function is also called when submitting the field configuration form.
// If so, skip validation as it won't work anyway.
if ($form_state['complete form']['#form_id'] == 'field_ui_field_edit_form') {
return;
}
$values = $form_state['values'];
$language = $element['#language'];
$field_name = $element['#field_name'];
$item = $values[$field_name][$language][$element['#delta']]['dimfield_combined_wrapper'];
$num_dimensions = 2;
if (array_search('depth', $element['#columns'])) {
$num_dimensions = 3;
}
// If there was nothing entered, we assume it to be an empty record and ignore it.
if (trim($item['height_width_depth'])) {
if (substr_count($item['height_width_depth'], 'x') == $num_dimensions - 1) {
if ($num_dimensions == 2) {
list($height, $width) = explode('x', $item['height_width_depth']);
$new_values = array(
'height' => trim($height),
'width' => trim($width),
'units' => $item['units'],
);
}
elseif ($num_dimensions == 3) {
list($height, $width, $depth) = explode('x', $item['height_width_depth']);
$new_values = array(
'height' => trim($height),
'width' => trim($width),
'depth' => trim($depth),
'units' => $item['units'],
);
}
form_set_value($element, $new_values, $form_state);
}
else {
form_set_error($field_name, t('You must specify all dimensions, separated by an \'x\'.'));
}
}
}
function dimfield_units($unit = NULL) {
static $units;
if (empty($units)) {
$units = array(
'inches' => t('Inches'),
'feet' => t('Feet'),
'meters' => t('Meters'),
);
}
if ($unit) {
return isset($units[$unit]) ? $units[$unit] : '';
}
return $units;
}
Файл: dimfield-admin.css
.dimfield-combined {
float: left;
margin: 0 30px 0 0;
}
Форматтер. Настал момент определиться с тем, как мы будем отдавать пользовательским глазам наше поле. Друпал позволяет контролировать обычный вывод на экран, для rss лент, для печати и т.д. с помощью форматтеров. Мы создадим их так же два вида: для вывода одного значения и для комплексного вывода.
///////////////// Formatters ///////////////////////
/**
* Implements hook_field_formatter_info().
*/
function dimfield_field_formatter_info() {
return array(
'dimfield_default' => array(
'label' => t('Default'),
'field types' => array('dimensions'),
),
'dimfield_table' => array(
'label' => t('Show as table'),
'field types' => array('dimensions'),
'settings' => array('units_as' => 'column'),
),
);
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function dimfield_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = '';
if ($display['type'] == 'dimfield_table') {
if ($settings['units_as'] == 'column') {
$summary = t('Show units as their own column');
}
else if ($settings['units_as'] == 'cell') {
$summary = t('Show units in each cell');
}
else if ($settings['units_as'] == 'none') {
$summary = t('Do not show units');
}
}
return $summary;
}
/**
* Implements hook_field_formatter_settings_form().
*/
function dimfield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$form = array();
if ($display['type'] == 'dimfield_table') {
$form['units_as'] = array(
'#title' => t('Show units'),
'#type' => 'select',
'#options' => array(
'column' => t('As their own column'),
'cell' => t('In each cell'),
'none' => t('Do not show units'),
),
'#default_value' => $settings['units_as'],
'#required' => TRUE,
);
}
return $form;
}
/**
* Implements hook_field_formatter_view().
*/
function dimfield_field_formatter_view($obj_type, $object, $field, $instance, $langcode, $items, $display) {
$element = array();
$settings = $display['settings'];
switch ($display['type']) {
case 'dimfield_default':
foreach ($items as $delta => $item) {
if ($field['settings']['num_dimensions'] == 2) {
$output = t('@height @unit by @width @unit', array(
'@height' => $item['height'],
'@width' => $item['width'],
'@unit' => dimfield_units($item['units']),
));
}
elseif ($field['settings']['num_dimensions'] == 3) {
$output = t('@height @unit by @width @unit by @depth @unit', array(
'@height' => $item['height'],
'@width' => $item['width'],
'@depth' => $item['depth'],
'@unit' => dimfield_units($item['units']),
));
}
$element[$delta] = array('#markup' => $output);
}
break;
case 'dimfield_table':
$rows = array();
foreach ($items as $delta => $item) {
$row = array();
if ($settings['units_as'] == 'cell') {
$row[] = t('@value (%units)', array(
'@value' => $item['height'],
'%units' => dimfield_units($item['units']),
));
$row[] = t('@value (%units)', array(
'@value' => $item['width'],
'%units' => dimfield_units($item['units']),
));
}
else {
$row[] = $item['height'];
$ $row[] = t('@value (%units)', array(
'@value' => $item['depth'],
'%units' => dimfield_units($item['units']),
));
}
else {
$row[] = $item['depth'];
}
}
if ($settings['units_as'] == 'column') {
$row[] = dimfield_units($item['units']);
}
$rows[] = $row;
}
$header = array(t('Height'), t('Width'));
if ($field['settings']['num_dimensions'] == 3) {
$header[] = t('Depth');
}
if ($settings['units_as'] == 'column') {
$header[] = t('Units');
}
$element = array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => $header,
);
break;
}
return $element;
}row[] = $item['width'];
}
if ($field['settings']['num_dimensions'] == 3) {
if ($settings['units_as'] == 'cell') {
$row[] = t('@value (%units)', array(
'@value' => $item['depth'],
'%units' => dimfield_units($item['units']),
));
}
else {
$row[] = $item['depth'];
}
}
if ($settings['units_as'] == 'column') {
$row[] = dimfield_units($item['units']);
}
$rows[] = $row;
}
$header = array(t('Height'), t('Width'));
if ($field['settings']['num_dimensions'] == 3) {
$header[] = t('Depth');
}
if ($settings['units_as'] == 'column') {
$header[] = t('Units');
}
$element = array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => $header,
);
break;
}
return $element;
}