diff --git a/field_example/config/schema/field_example.schema.yml b/field_example/config/schema/field_example.schema.yml new file mode 100644 index 0000000..6b63036 --- /dev/null +++ b/field_example/config/schema/field_example.schema.yml @@ -0,0 +1,10 @@ +field.field_example_rgb.value: + type: sequence + label: 'Default value' + sequence: + - type: mapping + label: 'Default' + mapping: + value: + type: string + label: 'Value' diff --git a/field_example/field_example.info.yml b/field_example/field_example.info.yml new file mode 100644 index 0000000..2ba15b6 --- /dev/null +++ b/field_example/field_example.info.yml @@ -0,0 +1,5 @@ +name: Field Example +type: module +description: An implementation of a field to show the Field API +package: Example modules +core: 8.x diff --git a/field_example/field_example.js b/field_example/field_example.js new file mode 100644 index 0000000..58e5339 --- /dev/null +++ b/field_example/field_example.js @@ -0,0 +1,24 @@ +/** + * @file + * Javascript for Field Example. + */ + +/** + * Provides a farbtastic colorpicker for the fancier widget. + */ +(function($) { + Drupal.behaviors.field_example_colorpicker = { + attach: function() { + $(".edit-field-example-colorpicker").on("focus", function(event) { + var edit_field = this; + var picker = $(this).closest('div').parent().find(".field-example-colorpicker"); + // Hide all color pickers except this one. + $(".field-example-colorpicker").hide(); + $(picker).show(); + $.farbtastic(picker, function(color) { + edit_field.value = color; + }).setColor(edit_field.value); + }); + } + }; +})(jQuery); diff --git a/field_example/field_example.module b/field_example/field_example.module new file mode 100755 index 0000000..d430c13 --- /dev/null +++ b/field_example/field_example.module @@ -0,0 +1,107 @@ +setValue($element, '', $form_state); + return; + } + + // Strip preceding pound sign for analysis. + if (substr($value, 0, 1) == '#') { + $value = substr($value, 1); + } + + // Convert to lower-case for consistency. + $value = strtolower($value); + + // Keeping it simple, only 6-digit hex values accepted. + if (strlen($value) != 6) { + \Drupal::formBuilder()->setError($element, t("Please enter a 6-digit hexadecimal value.")); + return; + } + + // Validate that this is a legit hex value. + if (!ctype_xdigit($value)) { + \Drupal::formBuilder()->setError($element, t("Please enter a valid hexadecimal value.")); + } + + // Add a preceding pound sign. + $value = "#$value"; + \Drupal::formBuilder()->setValue($element, $value, $form_state); +} + +/** + * Validate the individual fields and then convert them into a single HTML RGB + * value as text. + */ +function field_example_3text_validate($element, &$form_state) { + // Validate each of the textfield entries. + foreach (array('r', 'g', 'b') as $colorfield) { + $values[$colorfield] = $element[$colorfield]['#value']; + // If they left any empty, we'll set the value empty and quit. + if (strlen($values[$colorfield]) == 0) { + \Drupal::formBuilder()->setValue($element, '', $form_state); + return; + } + // If they gave us anything that's not hex, reject it. + if ((strlen($values[$colorfield]) != 2) || !ctype_xdigit($values[$colorfield])) { + \Drupal::formBuilder()->setError($element[$colorfield], t("Saturation value must be a 2-digit hexadecimal value between 00 and ff.")); + } + } + + // Set the value of the entire form element. + $value = strtolower(sprintf('#%02s%02s%02s', $values['r'], $values['g'], $values['b'])); + \Drupal::formBuilder()->setValue($element, $value, $form_state); +} + +/** + * @} End of "defgroup field_example". + */ diff --git a/field_example/field_example.routing.yml b/field_example/field_example.routing.yml new file mode 100644 index 0000000..35225a6 --- /dev/null +++ b/field_example/field_example.routing.yml @@ -0,0 +1,7 @@ +# Provides a simple user interface that tells the developer where to go. +field_example_page: + path: 'examples/field_example' + defaults: + _content: '\Drupal\field_example\Controller\FieldExampleController::page' + requirements: + _access: 'TRUE' diff --git a/field_example/lib/Drupal/field_example/Controller/FieldExampleController.php b/field_example/lib/Drupal/field_example/Controller/FieldExampleController.php new file mode 100644 index 0000000..c1c4b11 --- /dev/null +++ b/field_example/lib/Drupal/field_example/Controller/FieldExampleController.php @@ -0,0 +1,33 @@ + $item) { + $elements[$delta] = array( + '#type' => 'html_tag', + '#tag' => 'p', + '#value' => t('The content area color has been changed to @code', array('@code' => $item->value)), + '#attached' => array( + 'css' => array( + array( + 'data' => 'main { background-color:' . $item->value . ';}', + 'type' => 'inline', + ), + ), + ), + ); + } + + return $elements; + } + +} diff --git a/field_example/lib/Drupal/field_example/Plugin/Field/FieldFormatter/SimpleTextFormatter.php b/field_example/lib/Drupal/field_example/Plugin/Field/FieldFormatter/SimpleTextFormatter.php new file mode 100755 index 0000000..2df1c07 --- /dev/null +++ b/field_example/lib/Drupal/field_example/Plugin/Field/FieldFormatter/SimpleTextFormatter.php @@ -0,0 +1,49 @@ + $item) { + $elements[$delta] = array( + // We create a render array to produce the desired markup, + // "

The color code ... #hexcolor

". + // See theme_html_tag(). + '#type' => 'html_tag', + '#tag' => 'p', + '#attributes' => array( + 'style' => 'color: ' . $item->value, + ), + '#value' => t('The color code in this field is @code', array('@code' => $item->value)), + ); + } + + return $elements; + } + +} diff --git a/field_example/lib/Drupal/field_example/Plugin/Field/FieldType/RgbItem.php b/field_example/lib/Drupal/field_example/Plugin/Field/FieldType/RgbItem.php new file mode 100755 index 0000000..139904f --- /dev/null +++ b/field_example/lib/Drupal/field_example/Plugin/Field/FieldType/RgbItem.php @@ -0,0 +1,68 @@ + 'string', + 'label' => t('Hex value'), + ); + } + return static::$propertyDefinitions; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldInterface $field) { + return array( + 'columns' => array( + 'value' => array( + 'type' => 'text', + 'size' => 'tiny', + 'not null' => FALSE, + ), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->get('value')->getValue(); + return $value === NULL || $value === ''; + } +} diff --git a/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/ColorPickerWidget.php b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/ColorPickerWidget.php new file mode 100755 index 0000000..354305b --- /dev/null +++ b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/ColorPickerWidget.php @@ -0,0 +1,44 @@ + '
', + '#attributes' => array('class' => array('edit-field-example-colorpicker')), + '#attached' => array( + // Add Farbtastic color picker. + 'library' => array( + array('system', 'jquery.farbtastic'), + ), + // Add javascript to trigger the colorpicker. + 'js' => array(drupal_get_path('module', 'field_example') . '/field_example.js'), + ), + ); + return $element; + } + +} diff --git a/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/Text3Widget.php b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/Text3Widget.php new file mode 100755 index 0000000..130b77d --- /dev/null +++ b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/Text3Widget.php @@ -0,0 +1,64 @@ +value) ? $items[$delta]->value : ''; + // Parse the single hex string into RBG values. + if (!empty($value)) { + preg_match_all('@..@', substr($value, 1), $match); + } + else { + $match = array(array()); + } + + // Set up the form element for this widget. + $element += array( + '#type' => 'fieldset', + '#element_validate' => array('field_example_3text_validate'), + ); + + // Add in the RGB textfield elements. + foreach (array('r' => t('Red'), 'g' => t('Green'), 'b' => t('Blue')) as $key => $title) { + $element[$key] = array( + '#type' => 'textfield', + '#title' => $title, + '#size' => 2, + '#default_value' => array_shift($match[0]), + '#attributes' => array('class' => array('rgb-entry')), + '#description' => t('The 2-digit hexadecimal representation of @color saturation, like "a1" or "ff"', array('@color' => $title)), + ); + // Since Form API doesn't allow a fieldset to be required, we + // have to require each field element individually. + if ($element['#required']) { + $element[$key]['#required'] = TRUE; + } + } + return $element; + } + +} diff --git a/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/TextWidget.php b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/TextWidget.php new file mode 100755 index 0000000..68fad7b --- /dev/null +++ b/field_example/lib/Drupal/field_example/Plugin/Field/FieldWidget/TextWidget.php @@ -0,0 +1,43 @@ +value) ? $items[$delta]->value : ''; + $element += array( + '#type' => 'textfield', + '#default_value' => $value, + // Allow a slightly larger size that the field length to allow for some + // configurations where all characters won't fit in input field. + '#size' => 7, + '#maxlength' => 7, + '#element_validate' => array('field_example_text_validate'), + ); + return $element; + } + +} diff --git a/field_example/lib/Drupal/field_example/Tests/FieldExampleTest.php b/field_example/lib/Drupal/field_example/Tests/FieldExampleTest.php new file mode 100644 index 0000000..feb913b --- /dev/null +++ b/field_example/lib/Drupal/field_example/Tests/FieldExampleTest.php @@ -0,0 +1,165 @@ + 'Field Example', + 'description' => 'Create a content type with example_field_rgb fields, create a node, check for correct values.', + 'group' => 'Examples', + ); + } + + /** + * Test basic functionality of the example field. + * + * - Creates a content type. + * - Adds a single-valued field_example_rgb to it. + * - Adds a multivalued field_example_rgb to it. + * - Creates a node of the new type. + * - Populates the single-valued field. + * - Populates the multivalued field with two items. + * - Tests the result. + */ + function testExampleFieldBasic() { + $content_type_machine = strtolower($this->randomName(10)); + $title = $this->randomName(20); + + // Create and login user. + $account = $this->drupalCreateUser(array('administer content types')); + $this->drupalLogin($account); + + // Add a content type. + $this->drupalGet('admin/structure/types/add'); + $edit = array( + 'name' => $content_type_machine, + 'type' => $content_type_machine, + ); + $this->drupalPostForm(NULL, $edit, t('Save and manage fields')); + $this->assertText(t('The content type @name has been added.', array('@name' => $content_type_machine))); + + $single_text_field = strtolower($this->randomName(10)); + $single_colorpicker_field = strtolower($this->randomName(10)); + $single_3text_field = strtolower($this->randomName(10)); + $multivalue_3text_field = strtolower($this->randomName(10)); + + // Description of fields to be created; + $fields[$single_text_field] = array( + 'widget' => 'field_example_text', + 'cardinality' => '1', + ); + $fields[$single_colorpicker_field] = array( + 'widget' => 'field_example_colorpicker', + 'cardinality' => 1, + ); + $fields[$single_3text_field] = array( + 'widget' => 'field_example_3text', + 'cardinality' => 1, + ); + $fields[$multivalue_3text_field] = array( + 'widget' => 'field_example_3text', + 'cardinality' => -1, + ); + + foreach ($fields as $fieldname => $details) { + $this->create_field($fieldname, $details['widget'], $details['cardinality']); + } + + menu_router_rebuild(); + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $content_type_machine))->fetchField(); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + + $permission = 'create ' . $content_type_machine . ' content'; + // Reset the permissions cache. + $this->checkPermissions(array($permission), TRUE); + + // Now that we have a new content type, create a user that has privileges + // on the content type. + $account = $this->drupalCreateUser(array($permission)); + $this->drupalLogin($account); + + $this->drupalGet('node/add/' . $content_type_machine); + + // Add a node. + $edit = array( + 'title' => $title, + 'field_' . $single_text_field . '[und][0][rgb]' => '#000001', + 'field_' . $single_colorpicker_field . '[und][0][rgb]' => '#000002', + + 'field_' . $single_3text_field . '[und][0][rgb][r]' => '00', + 'field_' . $single_3text_field . '[und][0][rgb][g]' => '00', + 'field_' . $single_3text_field . '[und][0][rgb][b]' => '03', + + 'field_' . $multivalue_3text_field . '[und][0][rgb][r]' => '00', + 'field_' . $multivalue_3text_field . '[und][0][rgb][g]' => '00', + 'field_' . $multivalue_3text_field . '[und][0][rgb][b]' => '04', + + ); + // We want to add a 2nd item to the multivalue field, so hit "add another". + $this->drupalPostForm(NULL, $edit, t('Add another item')); + + $edit = array( + 'field_' . $multivalue_3text_field . '[und][1][rgb][r]' => '00', + 'field_' . $multivalue_3text_field . '[und][1][rgb][g]' => '00', + 'field_' . $multivalue_3text_field . '[und][1][rgb][b]' => '05', + ); + // Now we can fill in the second item in the multivalue field and save. + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(t('@content_type_machine @title has been created', array('@content_type_machine' => $content_type_machine, '@title' => $title))); + + + $output_strings = $this->xpath("//div[contains(@class,'field-type-field-example-rgb')]/div/div/p/text()"); + + $this->assertEqual((string)$output_strings[0], "The color code in this field is #000001"); + $this->assertEqual((string)$output_strings[1], "The color code in this field is #000002"); + $this->assertEqual((string)$output_strings[2], "The color code in this field is #000003"); + $this->assertEqual((string)$output_strings[3], "The color code in this field is #000004"); + $this->assertEqual((string)$output_strings[4], "The color code in this field is #000005"); + } + + /** + * Utility function to create fields on a content type. + * @param $field_name + * Name of the field, like field_something + * @param $widget_type + * Widget type, like field_example_3text + * @param $cardinality + * Cardinality + */ + protected function create_field($field_name, $widget_type, $cardinality) { + // Add a singleton field_example_text field. + $edit = array( + 'fields[_add_new_field][label]' => $field_name, + 'fields[_add_new_field][field_name]' => $field_name, + 'fields[_add_new_field][type]' => 'field_example_rgb', + //'fields[_add_new_field][widget_type]' => $widget_type, + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + + // There are no settings for this, so just press the button. + $this->drupalPostForm(NULL, array(), t('Save field settings')); + + $edit = array('field[cardinality]' => (string)$cardinality); + + // Using all the default settings, so press the button. + $this->drupalPostForm(NULL, $edit, t('Save settings')); + debug(t('Saved settings for field %field_name with widget %widget_type and cardinality %cardinality', array('%field_name' => $field_name, '%widget_type' => $widget_type, '%cardinality' => $cardinality))); + $this->assertText(t('Saved @name configuration.', array('@name' => $field_name))); + } +}