Issue #1743952 by joachim, quicksketch: Use farbtastic as default colorpicker.
[project/colorfield.git] / colorfield.module
1 <?php
2 /**
3 * @file
4 * A simple color field module - with picker - based closely on the module
5 * in Examples.
6 */
7
8 /**
9 * Implements hook_theme().
10 */
11 function colorfield_theme() {
12 return array(
13 'colorfield_simpletext' => array(
14 'variables' => array(
15 'text_color' => 'black', 'text_message' => '',
16 ),
17 'template' => 'templates/colorfield-simpletext',
18 ),
19 );
20 }
21
22
23 /***************************************************************
24 * Field Type API hooks
25 ***************************************************************/
26
27 /**
28 * Implements hook_field_info().
29 *
30 * Provides the description of the field.
31 */
32 function colorfield_field_info() {
33 return array(
34 // We name our field as the associative name of the array.
35 'colorfield_rgb' => array(
36 'label' => t('Color Picker (RGB)'),
37 'description' => t('A field composed of an RGB color.'),
38 'default_widget' => 'colorfield_text',
39 'default_formatter' => 'colorfield_simple_text',
40 'instance_settings' => array('text_processing' => 0),
41 ),
42 );
43 }
44
45 /**
46 * Implements hook_field_validate().
47 *
48 * This hook gives us a chance to validate content that's in our
49 * field. We're really only interested in the $items parameter, since
50 * it holds arrays representing content in the field we've defined.
51 * We want to verify that the items only contain RGB hex values like
52 * this: #RRGGBB. If the item validates, we do nothing. If it doesn't
53 * validate, we add our own error notification to the $errors parameter.
54 *
55 * @see colorfield_field_widget_error()
56 */
57 function colorfield_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
58 foreach ($items as $delta => $item) {
59 if (!empty($item['rgb'])) {
60 if (!preg_match('@^#[0-9a-fA-F]{6}$@', $item['rgb'])) {
61 $errors[$field['field_name']][$langcode][$delta][] = array(
62 'error' => 'colorfield_invalid',
63 'message' => t('Color must be in the HTML format #abcdef.'),
64 );
65 }
66 }
67 }
68 }
69
70 /**
71 * Implements hook_field_is_empty().
72 *
73 * hook_field_is_emtpy() is where Drupal asks us if this field is empty.
74 * Return TRUE if it does not contain data, FALSE if it does. This lets
75 * the form API flag an error when required fields are empty.
76 */
77 function colorfield_field_is_empty($item, $field) {
78 return empty($item['rgb']);
79 }
80
81 /**
82 * Implements hook_field_formatter_info().
83 *
84 * We need to tell Drupal that we have two different types of formatters
85 * for this field. One will change the text color, and the other will
86 * change the background color.
87 *
88 * @see colorfield_field_formatter_view()
89 */
90 function colorfield_field_formatter_info() {
91 return array(
92 // This formatter displays the raw value of the color.
93 'colorfield_raw_rgb' => array(
94 'label' => t('Raw RGB value'),
95 'field types' => array('colorfield_rgb'),
96 'settings' => array('display_hash' => TRUE),
97 ),
98 // This formatter displays a DIV of the specified color
99 'colorfield_color_swatch' => array(
100 'label' => t('Color swatch'),
101 'field types' => array('colorfield_rgb'),
102 'settings' => array('width' => 20, 'height' => 20),
103 ),
104 // This formatter just displays the hex value in the color indicated.
105 'colorfield_simple_text' => array(
106 'label' => t('Simple text-based formatter'),
107 'field types' => array('colorfield_rgb'),
108 'settings' => array('message' => t('The color code in this field is @code')),
109 ),
110 // This formatter changes the background color of the content region.
111 'colorfield_color_background' => array(
112 'label' => t('Background of the output text'),
113 'field types' => array('colorfield_rgb'),
114 ),
115 );
116 }
117
118
119 /**
120 * Implements hook_field_formatter_settings_form().
121 */
122 function colorfield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
123 $display = $instance['display'][$view_mode];
124 $settings = $display['settings'];
125
126 // Expose the message to display as a setting.
127 if ($display['type'] == 'colorfield_simple_text') {
128 $element['message'] = array(
129 '#type' => 'textfield',
130 '#title' => t('Message to display'),
131 '#default_value' => $settings['message'],
132 '#description' => t('Note that you can use @code to display the value of the code in the message.'),
133 );
134 }
135 // Expose the the block width and height.
136 else if ($display['type'] == 'colorfield_color_swatch') {
137 $element['width'] = array(
138 '#type' => 'textfield',
139 '#title' => t('Width of the block'),
140 '#size' => 3,
141 '#required' => TRUE,
142 '#element_validate' => array('element_validate_number'),
143 '#default_value' => $settings['width'],
144 );
145
146 $element['height'] = array(
147 '#type' => 'textfield',
148 '#title' => t('Height of the block'),
149 '#size' => 3,
150 '#required' => TRUE,
151 '#element_validate' => array('element_validate_number'),
152 '#default_value' => $settings['height'],
153 );
154 }
155 else if ($display['type'] == 'colorfield_raw_rgb') {
156 $element['display_hash'] = array(
157 '#type' => 'checkbox',
158 '#title' => t('Display the # in the output of the color'),
159 '#default_value' => $settings['display_hash'],
160 );
161 }
162
163 return $element;
164 }
165
166 /**
167 * Implements hook_field_formatter_settings_summary().
168 */
169 function colorfield_field_formatter_settings_summary($field, $instance, $view_mode) {
170 $display = $instance['display'][$view_mode];
171 $settings = $display['settings'];
172
173 $summary = array();
174
175 // Displays the dynamic message and replace @code by the value of the color
176 // code if it exists in the message.
177 if ($display['type'] == 'colorfield_simple_text') {
178 $summary[] = t('Message displayed: %message', array('%message' => $settings['message']));
179 if (strpos($settings['message'], '@code')) {
180 $summary[] = '<small>' . t('Note that @code will be replaced by the color picked.') . '</small>';
181 }
182 }
183 // Displays the width & height of the block as summary.
184 else if ($display['type'] == 'colorfield_color_swatch') {
185 $summary[] = t('Width: @width px', array('@width' => $settings['width']));
186 $summary[] = t('Height: @height px', array('@height' => $settings['height']));
187 }
188 // Displays if the hash is displayed or not.
189 else if ($display['type'] == 'colorfield_raw_rgb') {
190 $summary[] = ($settings['display_hash']) ? t('The raw will be prefixed with #.') : t('The raw will not be prefixed with #.');
191 }
192
193 return implode('<br />', $summary);
194 }
195
196 /**
197 * Implements hook_field_formatter_view().
198 *
199 * Two formatters are implemented.
200 * - colorfield_simple_text just outputs markup indicating the color that
201 * was entered and uses an inline style to set the text color to that value.
202 * - colorfield_color_background does the same but also changes the
203 * background color of div.region-content.
204 *
205 * @see colorfield_field_formatter_info()
206 */
207 function colorfield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
208 $element = array();
209
210 switch ($display['type']) {
211 // This formatter simply outputs the field as text and with a color.
212 case 'colorfield_simple_text':
213 foreach ($items as $delta => $item) {
214 $element[$delta] = array(
215 '#theme' => 'colorfield_simpletext',
216 '#text_color' => $item['rgb'],
217 '#text_message' => t($display['settings']['message'], array('@code' => $item['rgb'])),
218 );
219 }
220 break;
221 // This formatter simply outputs the raw RGB value prefixed or not with
222 // the hash.
223 case 'colorfield_raw_rgb':
224 foreach ($items as $delta => $item) {
225 $color = ($display['settings']['display_hash'])? $item['rgb'] : substr($item['rgb'], 1);
226 $element[$delta] = array('#markup' => $color);
227 }
228 break;
229 // This formatter adds css to the page changing the '.region-content' area's
230 // background color. If there are many fields, the last one will win.
231 case 'colorfield_color_background':
232 foreach ($items as $delta => $item) {
233 $element[$delta] = array(
234 '#type' => 'html_tag',
235 '#tag' => 'p',
236 '#value' => t('The content area color has been changed to @code', array('@code' => $item['rgb'])),
237 '#attached' => array(
238 'css' => array(
239 array(
240 'data' => 'div.region-content { background-color:' . $item['rgb'] . ';}',
241 'type' => 'inline',
242 ),
243 ),
244 ),
245 );
246 }
247 break;
248 // Adds an empty DIV, the background of which uses the selected colour.
249 // Could be used, for example, to display a swatch of the color.
250 case 'colorfield_color_swatch':
251 foreach ($items as $delta => $item) {
252 $element[$delta] = array(
253 '#type' => 'html_tag',
254 '#tag' => 'div',
255 '#attributes' => array(
256 'class' => array('colorfield-color-swatch'),
257 'style' => 'width: ' . $display['settings']['width'] . 'px; height: ' . $display['settings']['height'] . 'px; background-color:' . $item['rgb'] . ';'
258 ),
259 );
260 }
261 break;
262 }
263
264 return $element;
265 }
266
267 /**
268 * Implements hook_field_widget_info().
269 *
270 * Three widgets are provided.
271 * - A simple text-only widget where the user enters the '#ffffff'.
272 * - A 3-textfield widget that gathers the red, green, and blue values
273 * separately.
274 * - A farbtastic colorpicker widget that chooses the value graphically.
275 *
276 * These widget types will eventually show up in hook_field_widget_form,
277 * where we will have to flesh them out.
278 *
279 * @see colorfield_field_widget_form()
280 */
281 function colorfield_field_widget_info() {
282 return array(
283 'colorfield_text' => array(
284 'label' => t('RGB value as #ffffff'),
285 'field types' => array('colorfield_rgb'),
286 ),
287 'colorfield_3text' => array(
288 'label' => t('RGB text field'),
289 'field types' => array('colorfield_rgb'),
290 ),
291 );
292 }
293
294 /**
295 * Implements hook_field_widget_form().
296 *
297 * hook_widget_form() is where Drupal tells us to create form elements for
298 * our field's widget.
299 *
300 * We provide one of three different forms, depending on the widget type of
301 * the Form API item provided.
302 *
303 * The 'colorfield_colorpicker' and 'colorfield_text' are essentially
304 * the same, but colorfield_colorpicker adds a javascript colorpicker
305 * helper.
306 *
307 * colorfield_3text displays three text fields, one each for red, green,
308 * and blue. However, the field type defines a single text column,
309 * rgb, which needs an HTML color spec. Define an element validate
310 * handler that converts our r, g, and b fields into a simulated single
311 * 'rgb' form element.
312 */
313 function colorfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
314 $value = isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '';
315
316 $widget = $element;
317 $widget['#delta'] = $delta;
318 switch ($instance['widget']['type']) {
319 // DELIBERATE fall-through: From here on the colorfield_text and
320 // colorfield_colorpicker are exactly the same.
321 case 'colorfield_text':
322 $widget += array(
323 '#type' => 'textfield',
324 '#default_value' => $value,
325 // Allow a slightly larger size that the field length to allow for some
326 // configurations where all characters won't fit in input field.
327 '#size' => 7,
328 '#maxlength' => 7,
329 '#suffix' => '<div class="colorfield-colorpicker"></div>',
330 '#attributes' => array('class' => array('edit-colorfield-colorpicker')),
331 '#attached' => array(
332 // Add Farbtastic color picker.
333 'library' => array(
334 array('system', 'farbtastic'),
335 ),
336 // Add javascript to trigger the colorpicker.
337 'js' => array(
338 drupal_get_path('module', 'colorfield') . '/lib/js/colorfield-farbtastic.js',
339 ),
340 ),
341 );
342 break;
343
344 case 'colorfield_3text':
345 // Convert rgb value into r, g, and b for #default_value.
346 if (!empty($value)) {
347 preg_match_all('@..@', substr($value, 1), $match);
348 }
349 else {
350 $match = array(array());
351 }
352
353 // Make this a fieldset with the three text fields.
354 $widget += array(
355 '#type' => 'fieldset',
356 '#element_validate' => array('colorfield_3text_validate'),
357 // #delta is set so that the validation function will be able
358 // to access external value information which otherwise would be
359 // unavailable.
360 '#delta' => $delta,
361 '#attached' => array(
362 'css' => array(drupal_get_path('module', 'colorfield') . '/colorfield.css'),
363 ),
364 );
365
366 // Create a textfield for saturation values for Red, Green, and Blue.
367 foreach (array('r' => t('Red'), 'g' => t('Green'), 'b' => t('Blue')) as $key => $title) {
368 $widget[$key] = array(
369 '#type' => 'textfield',
370 '#title' => $title,
371 '#size' => 2,
372 '#default_value' => array_shift($match[0]),
373 '#attributes' => array('class' => array('rgb-entry')),
374 // '#description' => t('The 2-digit hexadecimal representation of the @color saturation, like "a1" or "ff"', array('@color' => $title)),
375 );
376 }
377 break;
378
379 }
380
381 $element['rgb'] = $widget;
382 return $element;
383 }
384
385
386 /**
387 * Validate the individual fields and then convert them into a single HTML RGB
388 * value as text.
389 */
390 function colorfield_3text_validate($element, &$form_state) {
391 $delta = $element['#delta']; // TODO: Isn't there a better way to find out which element?
392 $field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
393 $field_name = $field['field_name'];
394 if (isset($form_state['values'][$field_name][$element['#language']][$delta]['rgb'])) {
395 $values = $form_state['values'][$field_name][$element['#language']][$delta]['rgb'];
396 foreach (array('r', 'g', 'b') as $colorfield) {
397 $colorfield_value = hexdec($values[$colorfield]);
398 // If they left any empty, we'll set the value empty and quit.
399 if (strlen($values[$colorfield]) == 0) {
400 form_set_value($element, '', $form_state);
401 return;
402 }
403 // If they gave us anything that's not hex, reject it.
404 if ((strlen($values[$colorfield]) != 2) || $colorfield_value < 0 || $colorfield_value > 255) {
405 form_error($element[$colorfield], t("Saturation value must be a 2-digit hexadecimal value between 00 and ff."));
406 }
407 }
408
409 $value = sprintf('#%02s%02s%02s', $values['r'], $values['g'], $values['b']);
410 form_set_value($element, $value, $form_state);
411 }
412 }
413
414 /**
415 * Implements hook_field_widget_error().
416 *
417 * hook_field_widget_error() lets us figure out what to do with errors
418 * we might have generated in hook_field_validate(). Generally, we'll just
419 * call form_error().
420 *
421 * @see colorfield_field_validate()
422 * @see form_error()
423 */
424 function colorfield_field_widget_error($element, $error, $form, &$form_state) {
425 switch ($error['error']) {
426 case 'colorfield_invalid':
427 form_error($element, $error['message']);
428 break;
429 }
430 }