| 1 |
<?php
|
| 2 |
// $Id: duration_element.module,v 1.4 2008/04/30 10:53:01 jpetso Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* A form element for entering time durations.
|
| 7 |
*
|
| 8 |
* Copyright 2007, 2008 by Jakob Petsovits <jpetso@gmx.at>
|
| 9 |
* Distributed under the GNU General Public Licence version 2 or higher,
|
| 10 |
* as published by the FSF on http://www.gnu.org/copyleft/gpl.html
|
| 11 |
*/
|
| 12 |
|
| 13 |
/**
|
| 14 |
* Implementation of hook_elements():
|
| 15 |
* Register the duration widget with the Forms API and set default values.
|
| 16 |
*/
|
| 17 |
function duration_element_elements() {
|
| 18 |
return array(
|
| 19 |
'duration_combo' => array(
|
| 20 |
'#input' => TRUE,
|
| 21 |
'#largest_metric' => 'years',
|
| 22 |
'#smallest_metric' => 'seconds',
|
| 23 |
'#display_inline' => TRUE,
|
| 24 |
'#size' => 3, // size of each input textbox
|
| 25 |
'#maxlength' => 4, // maxlength of each input textbox
|
| 26 |
'#process' => array('duration_element_combo_process'),
|
| 27 |
),
|
| 28 |
'duration_select' => array(
|
| 29 |
'#input' => TRUE,
|
| 30 |
'#options' => array(),
|
| 31 |
'#format_callback' => 'duration_format_list',
|
| 32 |
'#format_callback_arguments' => array(),
|
| 33 |
'#process' => array('duration_element_select_process'),
|
| 34 |
),
|
| 35 |
);
|
| 36 |
}
|
| 37 |
|
| 38 |
|
| 39 |
/**
|
| 40 |
* Value callback, so that the #default_value is not directly assigned
|
| 41 |
* but transformed into an array for the nested "$metric" elements.
|
| 42 |
* form.inc produces a nasty error if we don't do that.
|
| 43 |
*/
|
| 44 |
function form_type_duration_combo_value($element, $edit = FALSE) {
|
| 45 |
if (func_num_args() == 1) {
|
| 46 |
if (is_object($element['#default_value'])) {
|
| 47 |
$duration = $element['#default_value'];
|
| 48 |
// Cut off metrics that are not provided as input fields.
|
| 49 |
$duration->set_granularity(
|
| 50 |
$element['#smallest_metric'], $element['#largest_metric']
|
| 51 |
);
|
| 52 |
}
|
| 53 |
else { // no default value, use a brand new duration object instead
|
| 54 |
$duration = duration_create();
|
| 55 |
}
|
| 56 |
|
| 57 |
$values = array(
|
| 58 |
'type' => $duration->type(),
|
| 59 |
);
|
| 60 |
foreach ($duration->to_array() as $metric => $value) {
|
| 61 |
$values[$metric] = $value;
|
| 62 |
}
|
| 63 |
}
|
| 64 |
else {
|
| 65 |
$values = $edit;
|
| 66 |
$values['type'] = isset($values['months']) ? 'months' : 'weeks';
|
| 67 |
}
|
| 68 |
return $values;
|
| 69 |
}
|
| 70 |
|
| 71 |
/**
|
| 72 |
* The 'process' callback for 'duration_combo' form elements.
|
| 73 |
* Called after defining the form and while building it.
|
| 74 |
*/
|
| 75 |
function duration_element_combo_process($element) {
|
| 76 |
if (isset($element['#element_validate'])) {
|
| 77 |
// Before the element user gets to do his validation, make sure we do ours.
|
| 78 |
array_unshift($element['#element_validate'], 'duration_element_combo_validate');
|
| 79 |
}
|
| 80 |
else {
|
| 81 |
$element['#element_validate'] = array('duration_element_combo_validate');
|
| 82 |
}
|
| 83 |
$type = $element['#value']['type']; // 'weeks' or 'months'
|
| 84 |
|
| 85 |
// Use the format that the duration object itself is using
|
| 86 |
// (e.g. 'P1D' for 'months', 'P0W1D' for 'weeks') - even if that overrides
|
| 87 |
// the element creator's setting. We might lose data otherwise, and anyways
|
| 88 |
// the element creator should make sure to pass an appropriately typed object.
|
| 89 |
if ($type == 'weeks' && $element['#smallest_metric'] == 'months') {
|
| 90 |
$element['#smallest_metric'] = 'years'; // are larger than months, weeks are not
|
| 91 |
}
|
| 92 |
if ($type == 'months' && $element['#smallest_metric'] == 'weeks') {
|
| 93 |
$element['#smallest_metric'] = 'months';
|
| 94 |
}
|
| 95 |
if ($type == 'weeks' && $element['#largest_metric'] == 'months') {
|
| 96 |
$element['#largest_metric'] = 'weeks';
|
| 97 |
}
|
| 98 |
if ($type == 'months' && $element['#largest_metric'] == 'weeks') {
|
| 99 |
$element['#largest_metric'] = 'days'; // are smaller than weeks, months are not
|
| 100 |
}
|
| 101 |
|
| 102 |
$metrics = duration_metrics();
|
| 103 |
$used_metrics = array();
|
| 104 |
$encountered_largest_metric = FALSE;
|
| 105 |
|
| 106 |
foreach ($metrics as $metric) {
|
| 107 |
if ($metric == $element['#largest_metric']) {
|
| 108 |
$encountered_largest_metric = TRUE;
|
| 109 |
}
|
| 110 |
if ($encountered_largest_metric) {
|
| 111 |
if (($metric == 'months' && $type == 'weeks')
|
| 112 |
|| ($metric == 'weeks' && $type == 'months')) {
|
| 113 |
// doesn't belong in here, don't include this metric
|
| 114 |
}
|
| 115 |
else {
|
| 116 |
$used_metrics[$metric] = duration_metric_t($metric);
|
| 117 |
}
|
| 118 |
}
|
| 119 |
if ($metric == $element['#smallest_metric']) {
|
| 120 |
break;
|
| 121 |
}
|
| 122 |
}
|
| 123 |
|
| 124 |
$element['#tree'] = TRUE; // so that we can nest child input elements
|
| 125 |
$element['#used_metrics'] = $used_metrics; // save for the validation callback
|
| 126 |
|
| 127 |
foreach ($used_metrics as $metric => $metric_t) {
|
| 128 |
if ($metric != $element['#smallest_metric']) {
|
| 129 |
// Some additional space between the text and the next textfield.
|
| 130 |
$metric_t = '<span style="margin-right: 0.8em;">' . $metric_t . '</span>';
|
| 131 |
}
|
| 132 |
$element[$metric] = array(
|
| 133 |
'#type' => 'textfield',
|
| 134 |
'#default_value' => $element['#value'][$metric],
|
| 135 |
'#size' => $element['#size'],
|
| 136 |
'#maxlength' => $element['#maxlength'],
|
| 137 |
'#prefix' => '<div class="container-inline">',
|
| 138 |
'#suffix' => $metric_t . '</div>',
|
| 139 |
);
|
| 140 |
}
|
| 141 |
|
| 142 |
// Don't have a maxlength in the parent element, as Form API tries to
|
| 143 |
// perform string operations if we've got that property.
|
| 144 |
unset($element['#maxlength']);
|
| 145 |
|
| 146 |
return $element;
|
| 147 |
}
|
| 148 |
|
| 149 |
/**
|
| 150 |
* The 'validate' callback for 'duration_combo' form elements.
|
| 151 |
* Called after values are assigned, before form validate and submit are called.
|
| 152 |
*/
|
| 153 |
function duration_element_combo_validate(&$element, &$form_state) {
|
| 154 |
$duration = duration_create();
|
| 155 |
|
| 156 |
foreach ($element['#used_metrics'] as $metric => $metric_t) {
|
| 157 |
$value = $element[$metric]['#value'];
|
| 158 |
|
| 159 |
if ($value === '') {
|
| 160 |
$value = 0;
|
| 161 |
}
|
| 162 |
if (!is_numeric($value) || $value < 0) {
|
| 163 |
form_error($element[$metric],
|
| 164 |
t('The "@metric" value in %widget must be greater or equal 0.',
|
| 165 |
array('@metric' => $metric_t, '%widget' => $element['#title']))
|
| 166 |
);
|
| 167 |
$error_set = TRUE;
|
| 168 |
}
|
| 169 |
else {
|
| 170 |
$duration->set_value($metric, $value);
|
| 171 |
}
|
| 172 |
}
|
| 173 |
if ($element['#required'] && $duration->to_single_metric('seconds') == 0) {
|
| 174 |
form_error($element,
|
| 175 |
t('A non-zero duration is required for %widget.',
|
| 176 |
array('%widget' => $element['#title']))
|
| 177 |
);
|
| 178 |
}
|
| 179 |
form_set_value($element, $duration, $form_state);
|
| 180 |
|
| 181 |
// Altering the element itself is slightly non-standard (..."a hack"),
|
| 182 |
// but allows access to the modified value in subsequent element validators.
|
| 183 |
$element['#value'] = $duration;
|
| 184 |
}
|
| 185 |
|
| 186 |
|
| 187 |
/**
|
| 188 |
* Value callback, so that the #default_value is not directly assigned
|
| 189 |
* but transformed into an array for the nested 'select' element.
|
| 190 |
* form.inc produces a nasty error if we don't do that.
|
| 191 |
*/
|
| 192 |
function form_type_duration_select_value($element, $edit = FALSE) {
|
| 193 |
if (func_num_args() == 1) {
|
| 194 |
return array('select' => $element['#default_value']);
|
| 195 |
}
|
| 196 |
}
|
| 197 |
|
| 198 |
/**
|
| 199 |
* The 'process' callback for the 'duration_select' element.
|
| 200 |
* Called after defining the form and while building it.
|
| 201 |
*/
|
| 202 |
function duration_element_select_process($element) {
|
| 203 |
if (isset($element['#element_validate'])) {
|
| 204 |
// Before the element user gets to do his validation, make sure we do ours.
|
| 205 |
array_unshift($element['#element_validate'], 'duration_element_select_validate');
|
| 206 |
}
|
| 207 |
else {
|
| 208 |
$element['#element_validate'] = array('duration_element_select_validate');
|
| 209 |
}
|
| 210 |
$options = array();
|
| 211 |
|
| 212 |
foreach ($element['#options'] as $key => $duration) {
|
| 213 |
if (!is_object($duration)) {
|
| 214 |
continue;
|
| 215 |
}
|
| 216 |
$function = $element['#format_callback'];
|
| 217 |
$args = $element['#format_callback_arguments'];
|
| 218 |
array_unshift($args, $duration); // $duration as first argument
|
| 219 |
$options[$key] = call_user_func_array($function, $args);
|
| 220 |
}
|
| 221 |
$element['#tree'] = TRUE; // so that we can nest child input elements
|
| 222 |
|
| 223 |
$element['select'] = array(
|
| 224 |
'#type' => 'select',
|
| 225 |
'#default_value' => isset($element['#default_value'])
|
| 226 |
? $element['#default_value']
|
| 227 |
: reset(array_keys($options)),
|
| 228 |
'#options' => $options,
|
| 229 |
'#required' => $element['#required'],
|
| 230 |
);
|
| 231 |
return $element;
|
| 232 |
}
|
| 233 |
|
| 234 |
/**
|
| 235 |
* The 'validate' callback for the 'duration_select' element.
|
| 236 |
* Called after values are assigned, before form validate and submit are called.
|
| 237 |
*/
|
| 238 |
function duration_element_select_validate(&$element, &$form_state) {
|
| 239 |
// Altering the element itself is slightly non-standard (..."a hack"),
|
| 240 |
// but allows access to the modified value in subsequent element validators.
|
| 241 |
$element['#value'] = $element['select']['#value'];
|
| 242 |
form_set_value($element, $element['#value'], $form_state);
|
| 243 |
}
|
| 244 |
|
| 245 |
|
| 246 |
/**
|
| 247 |
* Implementation of hook_theme().
|
| 248 |
*/
|
| 249 |
function duration_element_theme() {
|
| 250 |
return array(
|
| 251 |
'duration_combo' => array('arguments' => array('element' => NULL)),
|
| 252 |
'duration_select' => array('arguments' => array('element' => NULL)),
|
| 253 |
);
|
| 254 |
}
|
| 255 |
|
| 256 |
/**
|
| 257 |
* Theme the duration element.
|
| 258 |
*/
|
| 259 |
function theme_duration_combo($element) {
|
| 260 |
// class="container-inline" makes child widgets align horizontally.
|
| 261 |
$children = ($element['#display_inline'])
|
| 262 |
? '<div class="container-inline">' . $element['#children'] . '</div>'
|
| 263 |
: $element['#children'];
|
| 264 |
|
| 265 |
return theme('form_element', $element, $children);
|
| 266 |
}
|
| 267 |
|
| 268 |
function theme_duration_select($element) {
|
| 269 |
// class="container-inline" not only makes child widgets align horizontally,
|
| 270 |
// it also reduces the unnecessarily large space between title and element.
|
| 271 |
return theme('form_element', $element,
|
| 272 |
'<div class="container-inline">' . $element['#children'] . '</div>'
|
| 273 |
);
|
| 274 |
}
|