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..c2e40ea --- /dev/null +++ b/field_example/config/schema/field_example.schema.yml @@ -0,0 +1,12 @@ +# @see Configuration schema/metadata at https://drupal.org/node/1905070 +# +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.menu_links.yml b/field_example/field_example.menu_links.yml new file mode 100644 index 0000000..07b81b3 --- /dev/null +++ b/field_example/field_example.menu_links.yml @@ -0,0 +1,3 @@ +field_example.description: + title: Field Example + route_name: field_example.description diff --git a/field_example/field_example.module b/field_example/field_example.module new file mode 100644 index 0000000..7f005ab --- /dev/null +++ b/field_example/field_example.module @@ -0,0 +1,34 @@ +administratorAccount = $this->drupalCreateUser($permissions); + parent::drupalLogin($this->administratorAccount); + + // Prepare a new content type where the field will be added. + $this->contentTypeName = strtolower($this->randomName(10)); + $this->drupalGet('admin/structure/types/add'); + $edit = array( + 'name' => $this->contentTypeName, + 'type' => $this->contentTypeName, + ); + $this->drupalPostForm(NULL, $edit, t('Save and manage fields')); + $this->assertText(t('The content type @name has been added.', array('@name' => $this->contentTypeName))); + + // Reset the permission cache. + $create_permission = 'create ' . $this->contentTypeName . ' content'; + $this->checkPermissions(array($create_permission), TRUE); + + // Now that we have a new content type, create a user that has privileges + // on the content type. + $this->authorAccount = $this->drupalCreateUser(array($create_permission)); + } + + /** + * Create a field on the content type created during setUp(). + * + * @param string $type + * The storage field type to create + * @param string $widget_type + * The widget to use when editing this field + * @param int|string $cardinality + * Cardinality of the field. + * + * @return string + * Name of the field, like field_something + */ + protected function createField($type = 'field_example_rgb', $widget_type = 'field_example_text', $cardinality = 'number') { + $this->drupalGet('admin/structure/types/manage/' . $this->contentTypeName . '/fields'); + + $field_name = strtolower($this->randomName(10)); + // 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]' => $type, + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + + $edit = array('field[cardinality]' => (string) $cardinality); + + // Using all the default settings, so press the button. + $this->drupalPostForm(NULL, $edit, t('Save field 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('Updated field @name field settings.', array('@name' => $field_name))); + + // Set the widget type for the newly created field. + $this->drupalGet('admin/structure/types/manage/' . $this->contentTypeName . '/form-display'); + $edit = array( + 'fields[field_' . $field_name . '][type]' => $widget_type, + ); + $this->drupalPostForm(NULL, $edit, t('Save')); + + return $field_name; + } +} diff --git a/field_example/src/Plugin/Field/FieldFormatter/ColorBackgroudFormatter.php b/field_example/src/Plugin/Field/FieldFormatter/ColorBackgroudFormatter.php new file mode 100644 index 0000000..7e1e2ec --- /dev/null +++ b/field_example/src/Plugin/Field/FieldFormatter/ColorBackgroudFormatter.php @@ -0,0 +1,51 @@ + $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/src/Plugin/Field/FieldFormatter/SimpleTextFormatter.php b/field_example/src/Plugin/Field/FieldFormatter/SimpleTextFormatter.php new file mode 100644 index 0000000..2d65454 --- /dev/null +++ b/field_example/src/Plugin/Field/FieldFormatter/SimpleTextFormatter.php @@ -0,0 +1,50 @@ + $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/src/Plugin/Field/FieldType/RgbItem.php b/field_example/src/Plugin/Field/FieldType/RgbItem.php new file mode 100644 index 0000000..f7096b3 --- /dev/null +++ b/field_example/src/Plugin/Field/FieldType/RgbItem.php @@ -0,0 +1,60 @@ + array( + 'value' => array( + 'type' => 'text', + 'size' => 'tiny', + 'not null' => FALSE, + ), + ), + ); + } + + /** + * {@inheritdoc} + */ + public function isEmpty() { + $value = $this->get('value')->getValue(); + return $value === NULL || $value === ''; + } + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['value'] = DataDefinition::create('string') + ->setLabel(t('Hex value')); + + return $properties; + } +} diff --git a/field_example/src/Plugin/Field/FieldWidget/ColorPickerWidget.php b/field_example/src/Plugin/Field/FieldWidget/ColorPickerWidget.php new file mode 100644 index 0000000..bf86a62 --- /dev/null +++ b/field_example/src/Plugin/Field/FieldWidget/ColorPickerWidget.php @@ -0,0 +1,46 @@ + '
', + '#attributes' => array('class' => array('edit-field-example-colorpicker')), + '#attached' => array( + // Add Farbtastic color picker. + 'library' => array( + 'core/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/src/Plugin/Field/FieldWidget/Text3Widget.php b/field_example/src/Plugin/Field/FieldWidget/Text3Widget.php new file mode 100644 index 0000000..c55aaa2 --- /dev/null +++ b/field_example/src/Plugin/Field/FieldWidget/Text3Widget.php @@ -0,0 +1,91 @@ +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' => 'details', + '#element_validate' => array( + array($this, '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 array('value' => $element); + } + + /** + * Validate the fields and convert them into a single value as text. + */ + public function validate($element, &$form_state) { + // Validate each of the textfield entries. + $values = array(); + 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], $form_state, 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); + } + +} diff --git a/field_example/src/Plugin/Field/FieldWidget/TextWidget.php b/field_example/src/Plugin/Field/FieldWidget/TextWidget.php new file mode 100644 index 0000000..985d166 --- /dev/null +++ b/field_example/src/Plugin/Field/FieldWidget/TextWidget.php @@ -0,0 +1,45 @@ +value) ? $items[$delta]->value : ''; + $element += array( + '#type' => 'color', + '#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( + 'form_validate_color', + ), + ); + return array('value' => $element); + } +} diff --git a/field_example/src/Tests/FieldExampleMenuTest.php b/field_example/src/Tests/FieldExampleMenuTest.php new file mode 100644 index 0000000..03cc821 --- /dev/null +++ b/field_example/src/Tests/FieldExampleMenuTest.php @@ -0,0 +1,62 @@ + 'Field Example Menu Test', + 'description' => 'Test the user-facing menus in Field Example.', + 'group' => 'Examples', + ); + } + + /** + * Test for a link to the block example in the Tools menu. + */ + public function testFieldExampleLink() { + $this->drupalGet(''); + $this->assertLinkByHref('examples/field_example'); + } + + /** + * Tests field_example menus. + */ + public function testBlockExampleMenu() { + $this->drupalGet('examples/field_example'); + $this->assertResponse(200, 'Description page exists.'); + } + +} diff --git a/field_example/src/Tests/Text3WidgetTest.php b/field_example/src/Tests/Text3WidgetTest.php new file mode 100644 index 0000000..691a703 --- /dev/null +++ b/field_example/src/Tests/Text3WidgetTest.php @@ -0,0 +1,110 @@ + 'Field Example with Text3Widget', + 'description' => 'Create a content type with a example_field_rgb field, configure it with the field_example_text-widget, create a node and 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. + */ + public function testSingleValueField() { + // Add a single field as administrator user. + $this->drupalLogin($this->administratorAccount); + $this->fieldName = $this->createField('field_example_rgb', 'field_example_3text', 'number'); + // Post-condition: Content type now has the desired field. + + // Switch to the author user to create content with this type and field. + $this->drupalLogin($this->authorAccount); + $this->drupalGet('node/add/' . $this->contentTypeName); + + // Fill the create form. + $title = $this->randomName(20); + $edit = array( + 'title[0][value]' => $title, + 'field_' . $this->fieldName . '[0][value][r]' => '00', + 'field_' . $this->fieldName . '[0][value][g]' => '0a', + 'field_' . $this->fieldName . '[0][value][b]' => '01', + ); + + // Create the content. + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(t('@type @title has been created', array('@type' => $this->contentTypeName, '@title' => $title))); + + // Verify the value is shown when viewing this node. + $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 #000a01"); + } + + /** + * 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. + */ + public function testMultiValueField() { + // Add a single field as administrator user. + $this->drupalLogin($this->administratorAccount); + $this->fieldName = $this->createField('field_example_rgb', 'field_example_3text', '-1'); + // Post-condition: Content type now has the desired field. + + // Switch to the author user to create content with this type and field. + $this->drupalLogin($this->authorAccount); + $this->drupalGet('node/add/' . $this->contentTypeName); + + // Fill the create form. + $title = $this->randomName(20); + $edit = array( + 'title[0][value]' => $title, + 'field_' . $this->fieldName . '[0][value][r]' => '00', + 'field_' . $this->fieldName . '[0][value][g]' => 'ff', + 'field_' . $this->fieldName . '[0][value][b]' => '00', + ); + + // Add a 2nd item to the multivalue field, so hit "add another". + $this->drupalPostForm(NULL, $edit, t('Add another item')); + $edit = array( + 'field_' . $this->fieldName . '[1][value][r]' => 'ff', + 'field_' . $this->fieldName . '[1][value][g]' => 'ff', + 'field_' . $this->fieldName . '[1][value][b]' => 'ff', + ); + + // Create the content. + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(t('@type @title has been created', array('@type' => $this->contentTypeName, '@title' => $title))); + + // Verify the values are shown when viewing this node. + $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 #00ff00"); + $this->assertEqual((string) $output_strings[1], "The color code in this field is #ffffff"); + } +} diff --git a/field_example/src/Tests/TextWidgetTest.php b/field_example/src/Tests/TextWidgetTest.php new file mode 100644 index 0000000..2f25743 --- /dev/null +++ b/field_example/src/Tests/TextWidgetTest.php @@ -0,0 +1,105 @@ + 'Field Example with Text Widget', + 'description' => 'Create a content type with a example_field_rgb field, configure it with the field_example_text-widget, create a node and 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. + */ + public function testSingleValueField() { + // Add a single field as administrator user. + $this->drupalLogin($this->administratorAccount); + $this->fieldName = $this->createField('field_example_rgb', 'field_example_text', 'number'); + + // Now that we have a content type with the desired field, switch to the + // author user to create content with it. + $this->drupalLogin($this->authorAccount); + $this->drupalGet('node/add/' . $this->contentTypeName); + + // Add a node. + $title = $this->randomName(20); + $edit = array( + 'title[0][value]' => $title, + 'field_' . $this->fieldName . '[0][value]' => '#000001', + ); + + // Create the content. + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(t('@type @title has been created', array('@type' => $this->contentTypeName, '@title' => $title))); + + // Verify the value is shown when viewing this node. + $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"); + } + + /** + * 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. + */ + public function testMultiValueField() { + // Add a single field as administrator user. + $this->drupalLogin($this->administratorAccount); + $this->fieldName = $this->createField('field_example_rgb', 'field_example_text', '-1'); + + // Now that we have a content type with the desired field, switch to the + // author user to create content with it. + $this->drupalLogin($this->authorAccount); + $this->drupalGet('node/add/' . $this->contentTypeName); + + // Add a node. + $title = $this->randomName(20); + $edit = array( + 'title[0][value]' => $title, + 'field_' . $this->fieldName . '[0][value]' => '#00ff00', + ); + + // 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_' . $this->fieldName . '[1][value]' => '#ffffff', + ); + + // Now we can fill in the second item in the multivalue field and save. + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText(t('@type @title has been created', array('@type' => $this->contentTypeName, '@title' => $title))); + + // Verify the value is shown when viewing this node. + $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 #00ff00"); + $this->assertEqual((string) $output_strings[1], "The color code in this field is #ffffff"); + } +}