From 17dd03f23c76a964cb3011f39dae3f7ebdbbe1ea Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Tue, 14 Jun 2022 18:20:36 -0700 Subject: [PATCH 01/21] Add farm_map_wkt form element. --- modules/core/map/src/Element/FarmMapWkt.php | 110 ++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 modules/core/map/src/Element/FarmMapWkt.php diff --git a/modules/core/map/src/Element/FarmMapWkt.php b/modules/core/map/src/Element/FarmMapWkt.php new file mode 100644 index 000000000..86da2edf6 --- /dev/null +++ b/modules/core/map/src/Element/FarmMapWkt.php @@ -0,0 +1,110 @@ + TRUE, + // @todo Does this have to return a tree structure? + '#tree' => TRUE, + '#process' => [ + [$class, 'processElement'], + ], + '#pre_render' => [ + [$class, 'preRenderGroup'], + ], + // @todo Add validation. +// '#element_validate' => [ +// [$class, 'elementValidate'], +// ], + '#theme_wrappers' => ['fieldset'], + // Display descriptions above the map by default. + '#description_display' => 'before', + '#map_type' => 'geofield_widget', + '#display_raw_geometry' => TRUE, + ]; + } + + /** + * Generates the FarmMapWktform element. + * + * @param array $element + * An associative array containing the properties and children of the + * element. Note that $element must be taken by reference here, so processed + * child elements are taken over into $form_state. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $complete_form + * The complete form structure. + * + * @return array + * The processed element. + */ + public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { + + // Define the map render array. + // @todo Does this have to return a tree structure? + $element['#tree'] = TRUE; + $element['map'] = [ + '#type' => 'farm_map', + '#map_type' => $element['#map_type'], + '#map_settings' => [ + 'behaviors' => [ + 'wkt' => [ + 'edit' => TRUE, + 'zoom' => TRUE, + ], + ], + ], + ]; + + // Add a textarea for the WKT value. + $display_raw_geometry = $element['#display_raw_geometry']; + $element['value'] = [ + '#type' => $display_raw_geometry ? 'textarea' : 'hidden', + '#title' => t('Geometry'), + '#attributes' => [ + 'data-map-geometry-field' => TRUE, + ], + ]; + + // Add default value if provided. + if (!empty($element['#default_value'])) { + $element['map']['#map_settings']['wkt'] = $element['#default_value']; + $element['value']['#default_value'] = $element['#default_value']; + } + + // Return the element. + return $element; + } + + /** + * {@inheritdoc} + */ + public static function valueCallback(&$element, $input, FormStateInterface $form_state) { + if ($input === FALSE) { + return $element['#default_value'] ?: []; + } + + if ($input['value']) { + return $input['value']; + } + + return NULL; + } + +} From 6e6c96eeefb35659a684815b14fe468452a0bea3 Mon Sep 17 00:00:00 2001 From: Paul Weidner Date: Tue, 14 Jun 2022 18:22:45 -0700 Subject: [PATCH 02/21] Update GeofieldWidget to use farm_map_wkt. --- .../Field/FieldWidget/GeofieldWidget.php | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php index 13849d72a..49b101d2e 100644 --- a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php +++ b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php @@ -133,12 +133,12 @@ class GeofieldWidget extends GeofieldBaseWidget { */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - // Wrap the map in a collapsible details element. + // Use the farm_map_wkt form element. + $element['#type'] = 'farm_map_wkt'; + + // Wrap the map with a unique id for populating from files. $field_name = $this->fieldDefinition->getName(); $field_wrapper_id = Html::getUniqueId($field_name . '_wrapper'); - $element['#type'] = 'details'; - $element['#title'] = $this->t('Geometry'); - $element['#open'] = TRUE; $element['#prefix'] = '
'; $element['#suffix'] = '
'; @@ -146,32 +146,11 @@ class GeofieldWidget extends GeofieldBaseWidget { $form_value = $form_state->getValue([$field_name, $delta, 'value']); $field_value = $items[$delta]->value; $current_value = $form_value ?? $field_value; + $element['#default_value'] = $current_value; - // Define the map render array. - $element['map'] = [ - '#type' => 'farm_map', - '#map_type' => 'geofield_widget', - '#map_settings' => [ - 'wkt' => $current_value, - 'behaviors' => [ - 'wkt' => [ - 'edit' => TRUE, - 'zoom' => TRUE, - ], - ], - ], - ]; - - // Add a textarea for the WKT value. + // Configure to display raw geometry. $display_raw_geometry = $this->getSetting('display_raw_geometry'); - $element['value'] = [ - '#type' => $display_raw_geometry ? 'textarea' : 'hidden', - '#title' => $this->t('Geometry'), - '#default_value' => $current_value, - '#attributes' => [ - 'data-map-geometry-field' => TRUE, - ], - ]; + $element['#display_raw_geometry'] = $display_raw_geometry; // Add an option to populate geometry using files field. // The "populate_file_field" field setting must be configured and the @@ -192,6 +171,7 @@ class GeofieldWidget extends GeofieldBaseWidget { ':input[name="' . $populate_file_field . '[0][fids]"]' => ['empty' => TRUE], ], ], + '#weight' => 10, ]; } From 0516346d23a90f66e83bfedd74ff764d3b5bd387 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Wed, 15 Jun 2022 16:51:49 -0400 Subject: [PATCH 03/21] Fix Notice: Undefined index: #default_value --- modules/core/map/src/Element/FarmMapWkt.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapWkt.php b/modules/core/map/src/Element/FarmMapWkt.php index 86da2edf6..35668039b 100644 --- a/modules/core/map/src/Element/FarmMapWkt.php +++ b/modules/core/map/src/Element/FarmMapWkt.php @@ -35,6 +35,7 @@ class FarmMapWkt extends FormElement { // Display descriptions above the map by default. '#description_display' => 'before', '#map_type' => 'geofield_widget', + '#default_value' => '', '#display_raw_geometry' => TRUE, ]; } @@ -97,7 +98,7 @@ class FarmMapWkt extends FormElement { */ public static function valueCallback(&$element, $input, FormStateInterface $form_state) { if ($input === FALSE) { - return $element['#default_value'] ?: []; + return $element['#default_value'] ?: ''; } if ($input['value']) { From 26c9746681e2054705051ab4478ff454e908fe91 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 10:58:37 -0400 Subject: [PATCH 04/21] Add automated tests for map form element. --- .../config/install/log.type.test.yml | 8 +++ .../farm_map_test/farm_map_test.info.yml | 10 +++ .../src/Plugin/QuickForm/Test.php | 67 +++++++++++++++++++ .../map/tests/src/Functional/MapFormTest.php | 62 +++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 modules/core/map/tests/modules/farm_map_test/config/install/log.type.test.yml create mode 100644 modules/core/map/tests/modules/farm_map_test/farm_map_test.info.yml create mode 100644 modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php create mode 100644 modules/core/map/tests/src/Functional/MapFormTest.php diff --git a/modules/core/map/tests/modules/farm_map_test/config/install/log.type.test.yml b/modules/core/map/tests/modules/farm_map_test/config/install/log.type.test.yml new file mode 100644 index 000000000..d88475de0 --- /dev/null +++ b/modules/core/map/tests/modules/farm_map_test/config/install/log.type.test.yml @@ -0,0 +1,8 @@ +langcode: en +status: true +id: test +label: Test +description: '' +name_pattern: 'Test log [log:id]' +workflow: log_default +new_revision: true diff --git a/modules/core/map/tests/modules/farm_map_test/farm_map_test.info.yml b/modules/core/map/tests/modules/farm_map_test/farm_map_test.info.yml new file mode 100644 index 000000000..f48fb9a88 --- /dev/null +++ b/modules/core/map/tests/modules/farm_map_test/farm_map_test.info.yml @@ -0,0 +1,10 @@ +name: farmOS map tests +type: module +description: Support module for farmOS map testing. +package: Testing +core_version_requirement: ^8.8 || ^9 +dependencies: + - farm:farm_location + - farm:farm_map + - farm:farm_quick + - log:log diff --git a/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php b/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php new file mode 100644 index 000000000..eb02f94fd --- /dev/null +++ b/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php @@ -0,0 +1,67 @@ + 'farm_map_wkt', + '#title' => $this->t('Geometry 1'), + '#display_raw_geometry' => FALSE, + '#default_value' => 'POINT(-42.689862437640826 32.621823310499934)', + ]; + + // Geometry field without a default value and a raw data field. + $form['geometry2'] = [ + '#type' => 'farm_map_wkt', + '#title' => $this->t('Geometry 2'), + '#display_raw_geometry' => TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + + // Create two logs. + $this->createLog([ + 'type' => 'test', + 'name' => 'Test 1', + 'geometry' => $form_state->getValue('geometry1'), + ]); + $this->createLog([ + 'type' => 'test', + 'name' => 'Test 2', + 'geometry' => $form_state->getValue('geometry2'), + ]); + } + +} diff --git a/modules/core/map/tests/src/Functional/MapFormTest.php b/modules/core/map/tests/src/Functional/MapFormTest.php new file mode 100644 index 000000000..bafa342b2 --- /dev/null +++ b/modules/core/map/tests/src/Functional/MapFormTest.php @@ -0,0 +1,62 @@ +createUser(['create test log']); + $this->drupalLogin($user); + + // Go to the test quick form and confirm that both of the geometry fields + // are visible. + $this->drupalGet('quick/test'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->pageTextContains($this->t('Geometry 1')); + $this->assertSession()->pageTextContains($this->t('Geometry 2')); + + // Submit the form with a value for the second geometry. + $edit = ['geometry2[value]' => 'POINT(-45.967095060886315 32.77503850904169)']; + $this->submitForm($edit, 'Submit'); + + // Load logs. + $logs = \Drupal::entityTypeManager()->getStorage('log')->loadMultiple(); + + // Confirm that two logs were created. + $this->assertCount(2, $logs); + + // Check that the first log's geometry was populated with the form field's + // default value. + $log = $logs[1]; + $this->assertEquals('POINT(-42.689862437640826 32.621823310499934)', $log->get('geometry')->value); + + // Check that the second log's geometry field was populated with the value + // entered into the form. + $log = $logs[2]; + $this->assertEquals('POINT(-45.967095060886315 32.77503850904169)', $log->get('geometry')->value); + + } + +} From 36cc27bbf1f584b4f40d83df9e77c7c9e98905d1 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 13:56:15 -0400 Subject: [PATCH 05/21] Rename farm_map_wkt element to farm_map_input. --- .../map/src/Element/{FarmMapWkt.php => FarmMapInput.php} | 6 +++--- .../map/src/Plugin/Field/FieldWidget/GeofieldWidget.php | 4 ++-- .../modules/farm_map_test/src/Plugin/QuickForm/Test.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename modules/core/map/src/Element/{FarmMapWkt.php => FarmMapInput.php} (96%) diff --git a/modules/core/map/src/Element/FarmMapWkt.php b/modules/core/map/src/Element/FarmMapInput.php similarity index 96% rename from modules/core/map/src/Element/FarmMapWkt.php rename to modules/core/map/src/Element/FarmMapInput.php index 35668039b..2ac62348e 100644 --- a/modules/core/map/src/Element/FarmMapWkt.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -8,9 +8,9 @@ use Drupal\Core\Render\Element\FormElement; /** * Form element that returns WKT rendered in a map. * - * @FormElement("farm_map_wkt") + * @FormElement("farm_map_input") */ -class FarmMapWkt extends FormElement { +class FarmMapInput extends FormElement { /** * {@inheritdoc} @@ -41,7 +41,7 @@ class FarmMapWkt extends FormElement { } /** - * Generates the FarmMapWktform element. + * Generates the form element. * * @param array $element * An associative array containing the properties and children of the diff --git a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php index 49b101d2e..1e10717af 100644 --- a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php +++ b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php @@ -133,8 +133,8 @@ class GeofieldWidget extends GeofieldBaseWidget { */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { - // Use the farm_map_wkt form element. - $element['#type'] = 'farm_map_wkt'; + // Use the farm_map_input form element. + $element['#type'] = 'farm_map_input'; // Wrap the map with a unique id for populating from files. $field_name = $this->fieldDefinition->getName(); diff --git a/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php b/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php index eb02f94fd..2138f812b 100644 --- a/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php +++ b/modules/core/map/tests/modules/farm_map_test/src/Plugin/QuickForm/Test.php @@ -30,7 +30,7 @@ class Test extends QuickFormBase { // Geometry field with a default value and no raw data textfield. $form['geometry1'] = [ - '#type' => 'farm_map_wkt', + '#type' => 'farm_map_input', '#title' => $this->t('Geometry 1'), '#display_raw_geometry' => FALSE, '#default_value' => 'POINT(-42.689862437640826 32.621823310499934)', @@ -38,7 +38,7 @@ class Test extends QuickFormBase { // Geometry field without a default value and a raw data field. $form['geometry2'] = [ - '#type' => 'farm_map_wkt', + '#type' => 'farm_map_input', '#title' => $this->t('Geometry 2'), '#display_raw_geometry' => TRUE, ]; From a5d442ba63cddb245a91d68d5cfd174c6c32daae Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 14:06:47 -0400 Subject: [PATCH 06/21] Rename the geofield map behavior to input. --- .../farm_map.map_behavior.geofield.yml | 11 ------ .../install/farm_map.map_behavior.input.yml | 11 ++++++ modules/core/map/farm_map.libraries.yml | 4 +-- modules/core/map/farm_map.post_update.php | 35 +++++++++++++++++++ ...field.js => farmOS.map.behaviors.input.js} | 2 +- .../MapRenderEventSubscriber.php | 4 +-- 6 files changed, 51 insertions(+), 16 deletions(-) delete mode 100644 modules/core/map/config/install/farm_map.map_behavior.geofield.yml create mode 100644 modules/core/map/config/install/farm_map.map_behavior.input.yml create mode 100644 modules/core/map/farm_map.post_update.php rename modules/core/map/js/{farmOS.map.behaviors.geofield.js => farmOS.map.behaviors.input.js} (91%) diff --git a/modules/core/map/config/install/farm_map.map_behavior.geofield.yml b/modules/core/map/config/install/farm_map.map_behavior.geofield.yml deleted file mode 100644 index f4f0897ca..000000000 --- a/modules/core/map/config/install/farm_map.map_behavior.geofield.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - enforced: - module: - - farm_map -id: geofield -label: Geofield -description: 'Copies map layer data into a form textarea.' -library: 'farm_map/behavior_geofield' -settings: { } diff --git a/modules/core/map/config/install/farm_map.map_behavior.input.yml b/modules/core/map/config/install/farm_map.map_behavior.input.yml new file mode 100644 index 000000000..df1acdd0b --- /dev/null +++ b/modules/core/map/config/install/farm_map.map_behavior.input.yml @@ -0,0 +1,11 @@ +langcode: en +status: true +dependencies: + enforced: + module: + - farm_map +id: input +label: Input +description: 'Syncs editable map layer data into a form input.' +library: 'farm_map/behavior_input' +settings: { } diff --git a/modules/core/map/farm_map.libraries.yml b/modules/core/map/farm_map.libraries.yml index f286062c4..50db0321e 100644 --- a/modules/core/map/farm_map.libraries.yml +++ b/modules/core/map/farm_map.libraries.yml @@ -30,9 +30,9 @@ behavior_wkt: dependencies: - core/drupalSettings - farm_map/farm_map -behavior_geofield: +behavior_input: js: - js/farmOS.map.behaviors.geofield.js: { } + js/farmOS.map.behaviors.input.js: { } dependencies: - farm_map/farm_map behavior_enable_side_panel: diff --git a/modules/core/map/farm_map.post_update.php b/modules/core/map/farm_map.post_update.php new file mode 100644 index 000000000..4d66458a7 --- /dev/null +++ b/modules/core/map/farm_map.post_update.php @@ -0,0 +1,35 @@ + 'input', + 'label' => 'Input', + 'description' => 'Syncs editable map layer data into a form input.', + 'library' => 'farm_map/behavior_input', + 'settings' => [], + 'dependencies' => [ + 'enforced' => [ + 'module' => [ + 'farm_map', + ], + ], + ], + ]); + $input_behavior->save(); + + // Delete the geofield behavior. + $geofield_behavior = MapBehavior::load('geofield'); + $geofield_behavior->delete(); +} diff --git a/modules/core/map/js/farmOS.map.behaviors.geofield.js b/modules/core/map/js/farmOS.map.behaviors.input.js similarity index 91% rename from modules/core/map/js/farmOS.map.behaviors.geofield.js rename to modules/core/map/js/farmOS.map.behaviors.input.js index 6b4b1665a..127de36e1 100644 --- a/modules/core/map/js/farmOS.map.behaviors.geofield.js +++ b/modules/core/map/js/farmOS.map.behaviors.input.js @@ -1,5 +1,5 @@ (function () { - farmOS.map.behaviors.geofield = { + farmOS.map.behaviors.input = { attach: function (instance) { instance.editAttached.then(() => { instance.edit.wktOn('featurechange', function(wkt) { diff --git a/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php b/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php index 71ef0de42..053cbab6c 100644 --- a/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php +++ b/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php @@ -61,10 +61,10 @@ class MapRenderEventSubscriber implements EventSubscriberInterface { $event->addBehavior('wkt'); } - // Add the wkt and geofield behavior to the geofield_widget map. + // Add the wkt and input behavior to the geofield_widget map. if (in_array($event->getMapType()->id(), ['geofield_widget'])) { $event->addBehavior('wkt'); - $event->addBehavior('geofield'); + $event->addBehavior('input'); } // Get whether the side panel should be enabled. From ba6000ebc614b805b3278d5bb2a76cf2dd6a3e77 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 14:42:04 -0400 Subject: [PATCH 07/21] Add map element validation to check for valid geometry. --- modules/core/map/src/Element/FarmMapInput.php | 36 ++++++++++++++++--- .../map/tests/src/Functional/MapFormTest.php | 5 +++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 2ac62348e..2f12e4a09 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -4,6 +4,7 @@ namespace Drupal\farm_map\Element; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; +use Drupal\geofield\GeoPHP\GeoPHPWrapper; /** * Form element that returns WKT rendered in a map. @@ -27,10 +28,9 @@ class FarmMapInput extends FormElement { '#pre_render' => [ [$class, 'preRenderGroup'], ], - // @todo Add validation. -// '#element_validate' => [ -// [$class, 'elementValidate'], -// ], + '#element_validate' => [ + [$class, 'elementValidate'], + ], '#theme_wrappers' => ['fieldset'], // Display descriptions above the map by default. '#description_display' => 'before', @@ -93,6 +93,34 @@ class FarmMapInput extends FormElement { return $element; } + /** + * Validates the form element. + */ + public static function elementValidate(&$element, FormStateInterface $form_state, &$complete_form) { + + // Validate that the geometry data is valid by attempting to load it into + // GeoPHP. This uses the same logic and error message as the geofield + // module's validation constraint. + // @see Drupal\geofield\Plugin\Validation\Constraint\GeoConstraint + // @see Drupal\geofield\Plugin\Validation\Constraint\GeoConstraintValidator + $value = $element['value']['#value']; + if (!empty($value)) { + $geophp = new GeoPHPWrapper(); + $valid_geometry = TRUE; + try { + if (!$geophp->load($value)) { + $valid_geometry = FALSE; + } + } + catch (\Exception $e) { + $valid_geometry = FALSE; + } + if (!$valid_geometry) { + $form_state->setError($element, t('"@value" is not a valid geospatial content.', ['@value' => $value])); + } + } + } + /** * {@inheritdoc} */ diff --git a/modules/core/map/tests/src/Functional/MapFormTest.php b/modules/core/map/tests/src/Functional/MapFormTest.php index bafa342b2..c78d9ca71 100644 --- a/modules/core/map/tests/src/Functional/MapFormTest.php +++ b/modules/core/map/tests/src/Functional/MapFormTest.php @@ -57,6 +57,11 @@ class MapFormTest extends FarmBrowserTestBase { $log = $logs[2]; $this->assertEquals('POINT(-45.967095060886315 32.77503850904169)', $log->get('geometry')->value); + // Test that submitting an invalid geometry throws a form validation error. + $this->drupalGet('quick/test'); + $edit = ['geometry2[value]' => 'POLYGON()']; + $this->submitForm($edit, 'Submit'); + $this->assertSession()->pageTextContains($this->t('"POLYGON()" is not a valid geospatial content.')); } } From 70e8b1f9ea86f5d98ce21633937432734a88a6c8 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 07:13:05 -0400 Subject: [PATCH 08/21] Always default #map_settings to an empty array. --- modules/core/map/src/Element/FarmMap.php | 6 ++---- modules/core/map/src/Element/FarmMapInput.php | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/core/map/src/Element/FarmMap.php b/modules/core/map/src/Element/FarmMap.php index 7f2b795fa..d4321a620 100644 --- a/modules/core/map/src/Element/FarmMap.php +++ b/modules/core/map/src/Element/FarmMap.php @@ -24,6 +24,7 @@ class FarmMap extends RenderElement { ], '#theme' => 'farm_map', '#map_type' => 'default', + '#map_settings' => [], ]; } @@ -66,14 +67,11 @@ class FarmMap extends RenderElement { $element['#attached']['library'][] = 'farm_map/farmOS-map'; $element['#attached']['library'][] = 'farm_map/farm_map'; - // Include map settings. - $map_settings = !empty($element['#map_settings']) ? $element['#map_settings'] : []; - // Include the map options. $map_options = $map->getMapOptions(); // Add the instance settings under the map id key. - $instance_settings = array_merge_recursive($map_settings, $map_options); + $instance_settings = array_merge_recursive($element['#map_settings'], $map_options); $element['#attached']['drupalSettings']['farm_map'][$map_id] = $instance_settings; // Create and dispatch a MapRenderEvent. diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 2f12e4a09..0ce685a37 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -35,6 +35,7 @@ class FarmMapInput extends FormElement { // Display descriptions above the map by default. '#description_display' => 'before', '#map_type' => 'geofield_widget', + '#map_settings' => [], '#default_value' => '', '#display_raw_geometry' => TRUE, ]; From ebeda67be42f3851367ff3eb1c9a982c7eb411bd Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 07:23:54 -0400 Subject: [PATCH 09/21] Recursively merge provided map settings into defaults. --- modules/core/map/src/Element/FarmMapInput.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 0ce685a37..027119f03 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -58,20 +58,23 @@ class FarmMapInput extends FormElement { */ public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { + // Recursively merge provided map settings into defaults. + $map_settings = array_merge_recursive([ + 'behaviors' => [ + 'wkt' => [ + 'edit' => TRUE, + 'zoom' => TRUE, + ], + ], + ], $element['#map_settings']); + // Define the map render array. // @todo Does this have to return a tree structure? $element['#tree'] = TRUE; $element['map'] = [ '#type' => 'farm_map', '#map_type' => $element['#map_type'], - '#map_settings' => [ - 'behaviors' => [ - 'wkt' => [ - 'edit' => TRUE, - 'zoom' => TRUE, - ], - ], - ], + '#map_settings' => $map_settings, ]; // Add a textarea for the WKT value. From 4574cc5e9bc9e2d4b5bac89f21721bf85a0fc866 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 21:08:32 -0400 Subject: [PATCH 10/21] Allow #behaviors to be added directly to farm_map and farm_map_input elements. --- modules/core/map/src/Element/FarmMap.php | 12 ++++++++++++ modules/core/map/src/Element/FarmMapInput.php | 2 ++ modules/core/map/src/Event/MapRenderEvent.php | 2 ++ 3 files changed, 16 insertions(+) diff --git a/modules/core/map/src/Element/FarmMap.php b/modules/core/map/src/Element/FarmMap.php index d4321a620..8f9ae48e7 100644 --- a/modules/core/map/src/Element/FarmMap.php +++ b/modules/core/map/src/Element/FarmMap.php @@ -25,6 +25,7 @@ class FarmMap extends RenderElement { '#theme' => 'farm_map', '#map_type' => 'default', '#map_settings' => [], + '#behaviors' => [], ]; } @@ -38,6 +39,8 @@ class FarmMap extends RenderElement { * * @return array * A renderable array representing the map. + * + * @see \Drupal\farm_map\Event\MapRenderEvent */ public static function preRenderMap(array $element) { @@ -67,6 +70,15 @@ class FarmMap extends RenderElement { $element['#attached']['library'][] = 'farm_map/farmOS-map'; $element['#attached']['library'][] = 'farm_map/farm_map'; + // If #behaviors are included, attach each one. + foreach ($element['#behaviors'] as $behavior_name) { + /** @var \Drupal\farm_map\Entity\MapBehaviorInterface $behavior */ + $behavior = \Drupal::entityTypeManager()->getStorage('map_behavior')->load($behavior_name); + if (!empty($behavior)) { + $element['#attached']['library'][] = $behavior->getLibrary(); + } + } + // Include the map options. $map_options = $map->getMapOptions(); diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 027119f03..3ea23df64 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -36,6 +36,7 @@ class FarmMapInput extends FormElement { '#description_display' => 'before', '#map_type' => 'geofield_widget', '#map_settings' => [], + '#behaviors' => [], '#default_value' => '', '#display_raw_geometry' => TRUE, ]; @@ -75,6 +76,7 @@ class FarmMapInput extends FormElement { '#type' => 'farm_map', '#map_type' => $element['#map_type'], '#map_settings' => $map_settings, + '#behaviors' => $element['#behaviors'], ]; // Add a textarea for the WKT value. diff --git a/modules/core/map/src/Event/MapRenderEvent.php b/modules/core/map/src/Event/MapRenderEvent.php index 3073cb2b8..5829ad075 100644 --- a/modules/core/map/src/Event/MapRenderEvent.php +++ b/modules/core/map/src/Event/MapRenderEvent.php @@ -72,6 +72,8 @@ class MapRenderEvent extends Event { * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * + * @see \Drupal\farm_map\Element\FarmMap */ public function addBehavior(string $behavior_name, array $settings = []) { From 7923c06740de125c18aa9c636d0425ca28702e14 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 21:15:41 -0400 Subject: [PATCH 11/21] Do not wrap map input element in a fieldset by default. --- modules/core/map/src/Element/FarmMapInput.php | 2 +- .../core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 3ea23df64..abe85dbd0 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -31,7 +31,7 @@ class FarmMapInput extends FormElement { '#element_validate' => [ [$class, 'elementValidate'], ], - '#theme_wrappers' => ['fieldset'], + '#theme_wrappers' => ['form_element'], // Display descriptions above the map by default. '#description_display' => 'before', '#map_type' => 'geofield_widget', diff --git a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php index 1e10717af..d23869483 100644 --- a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php +++ b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php @@ -136,6 +136,9 @@ class GeofieldWidget extends GeofieldBaseWidget { // Use the farm_map_input form element. $element['#type'] = 'farm_map_input'; + // Wrap in a fieldset. + $element['#theme_wrappers'] = ['fieldset']; + // Wrap the map with a unique id for populating from files. $field_name = $this->fieldDefinition->getName(); $field_wrapper_id = Html::getUniqueId($field_name . '_wrapper'); From b6744a3cd36419d1b3071bac26dabe5c363f751c Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 21:18:10 -0400 Subject: [PATCH 12/21] Set the map input text field title to that of the parent element + "WKT". --- modules/core/map/src/Element/FarmMapInput.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index abe85dbd0..6740cad4f 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -81,9 +81,10 @@ class FarmMapInput extends FormElement { // Add a textarea for the WKT value. $display_raw_geometry = $element['#display_raw_geometry']; + $element_title = $element['#title'] ?? t('Geometry'); $element['value'] = [ '#type' => $display_raw_geometry ? 'textarea' : 'hidden', - '#title' => t('Geometry'), + '#title' => $element_title . ' ' . t('WKT'), '#attributes' => [ 'data-map-geometry-field' => TRUE, ], From 388a0f43b1f5a34329b9d97de10a5a5c73e9c122 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 21:18:53 -0400 Subject: [PATCH 13/21] Make the map input text field title invisible. --- modules/core/map/src/Element/FarmMapInput.php | 1 + modules/core/map/tests/src/Functional/MapFormTest.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 6740cad4f..6850b144d 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -85,6 +85,7 @@ class FarmMapInput extends FormElement { $element['value'] = [ '#type' => $display_raw_geometry ? 'textarea' : 'hidden', '#title' => $element_title . ' ' . t('WKT'), + '#title_display' => 'invisible', '#attributes' => [ 'data-map-geometry-field' => TRUE, ], diff --git a/modules/core/map/tests/src/Functional/MapFormTest.php b/modules/core/map/tests/src/Functional/MapFormTest.php index c78d9ca71..34693b5d5 100644 --- a/modules/core/map/tests/src/Functional/MapFormTest.php +++ b/modules/core/map/tests/src/Functional/MapFormTest.php @@ -31,11 +31,13 @@ class MapFormTest extends FarmBrowserTestBase { $this->drupalLogin($user); // Go to the test quick form and confirm that both of the geometry fields - // are visible. + // are visible, and only the second field's WKT text field is visible. $this->drupalGet('quick/test'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContains($this->t('Geometry 1')); $this->assertSession()->pageTextContains($this->t('Geometry 2')); + $this->assertSession()->pageTextNotContains($this->t('Geometry 1 WKT')); + $this->assertSession()->pageTextContains($this->t('Geometry 2 WKT')); // Submit the form with a value for the second geometry. $edit = ['geometry2[value]' => 'POINT(-45.967095060886315 32.77503850904169)']; From e5f859409866b6bf6a5c7313cb20652fe365d19c Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 08:43:00 -0400 Subject: [PATCH 14/21] Add wkt and input behaviors to all farm_map_input elements by default. --- modules/core/map/src/Element/FarmMapInput.php | 8 +++++++- .../map/src/EventSubscriber/MapRenderEventSubscriber.php | 6 ------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 6850b144d..19b826f14 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -59,6 +59,12 @@ class FarmMapInput extends FormElement { */ public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { + // Merge provided map behaviors into defaults. + $behaviors = array_merge([ + 'wkt', + 'input', + ], $element['#behaviors']); + // Recursively merge provided map settings into defaults. $map_settings = array_merge_recursive([ 'behaviors' => [ @@ -76,7 +82,7 @@ class FarmMapInput extends FormElement { '#type' => 'farm_map', '#map_type' => $element['#map_type'], '#map_settings' => $map_settings, - '#behaviors' => $element['#behaviors'], + '#behaviors' => $behaviors, ]; // Add a textarea for the WKT value. diff --git a/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php b/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php index 053cbab6c..783aed7c5 100644 --- a/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php +++ b/modules/core/map/src/EventSubscriber/MapRenderEventSubscriber.php @@ -61,12 +61,6 @@ class MapRenderEventSubscriber implements EventSubscriberInterface { $event->addBehavior('wkt'); } - // Add the wkt and input behavior to the geofield_widget map. - if (in_array($event->getMapType()->id(), ['geofield_widget'])) { - $event->addBehavior('wkt'); - $event->addBehavior('input'); - } - // Get whether the side panel should be enabled. $enable_side_panel = $this->configFactory->get('farm_map.settings')->get('enable_side_panel'); From a516fd4218d5935a6f366707af6f617181483bff Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Thu, 16 Jun 2022 21:16:04 -0400 Subject: [PATCH 15/21] Default #map_type to "default" instead of "geofield_widget". --- modules/core/map/src/Element/FarmMapInput.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index 19b826f14..cc7da8ea5 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -34,7 +34,7 @@ class FarmMapInput extends FormElement { '#theme_wrappers' => ['form_element'], // Display descriptions above the map by default. '#description_display' => 'before', - '#map_type' => 'geofield_widget', + '#map_type' => 'default', '#map_settings' => [], '#behaviors' => [], '#default_value' => '', From a1a015cc580b3d64e9e1c6087d9c389cc118a5af Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 08:29:28 -0400 Subject: [PATCH 16/21] Remove geofield_widget map type. --- .../install/farm_map.map_type.geofield_widget.yml | 11 ----------- modules/core/map/farm_map.post_update.php | 9 +++++++-- .../src/Plugin/Field/FieldWidget/GeofieldWidget.php | 3 +++ .../src/EventSubscriber/MapRenderEventSubscriber.php | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 modules/core/map/config/install/farm_map.map_type.geofield_widget.yml diff --git a/modules/core/map/config/install/farm_map.map_type.geofield_widget.yml b/modules/core/map/config/install/farm_map.map_type.geofield_widget.yml deleted file mode 100644 index e8fd56035..000000000 --- a/modules/core/map/config/install/farm_map.map_type.geofield_widget.yml +++ /dev/null @@ -1,11 +0,0 @@ -langcode: en -status: true -dependencies: - enforced: - module: - - farm_map -id: geofield_widget -label: Geofield widget -description: 'Renders a geofield widget using farmOS-map.' -behaviors: { } -options: { } diff --git a/modules/core/map/farm_map.post_update.php b/modules/core/map/farm_map.post_update.php index 4d66458a7..c64245074 100644 --- a/modules/core/map/farm_map.post_update.php +++ b/modules/core/map/farm_map.post_update.php @@ -6,11 +6,12 @@ */ use Drupal\farm_map\Entity\MapBehavior; +use Drupal\farm_map\Entity\MapType; /** - * Rename geofield map behavior to input. + * Generalize geofield map types and behavior. */ -function farm_map_post_update_map_input_behavior(&$sandbox) { +function farm_map_post_update_generalize_geofield_map_types_behavior(&$sandbox) { // Create the new input behavior. $input_behavior = MapBehavior::create([ @@ -32,4 +33,8 @@ function farm_map_post_update_map_input_behavior(&$sandbox) { // Delete the geofield behavior. $geofield_behavior = MapBehavior::load('geofield'); $geofield_behavior->delete(); + + // Delete the geofield_widget map type. + $geofield_widget_map_type = MapType::load('geofield_widget'); + $geofield_widget_map_type->delete(); } diff --git a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php index d23869483..dd158b075 100644 --- a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php +++ b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php @@ -136,6 +136,9 @@ class GeofieldWidget extends GeofieldBaseWidget { // Use the farm_map_input form element. $element['#type'] = 'farm_map_input'; + // Use the geofield map type. + $element['#map_type'] = 'geofield'; + // Wrap in a fieldset. $element['#theme_wrappers'] = ['fieldset']; diff --git a/modules/core/ui/map/src/EventSubscriber/MapRenderEventSubscriber.php b/modules/core/ui/map/src/EventSubscriber/MapRenderEventSubscriber.php index 1683bdeff..1d75f4720 100644 --- a/modules/core/ui/map/src/EventSubscriber/MapRenderEventSubscriber.php +++ b/modules/core/ui/map/src/EventSubscriber/MapRenderEventSubscriber.php @@ -65,7 +65,7 @@ class MapRenderEventSubscriber implements EventSubscriberInterface { $map_id = $event->getmapType()->id(); // Add behaviors/settings to default and geofield maps. - if (in_array($map_id, ['default', 'geofield', 'geofield_widget'])) { + if (in_array($map_id, ['default', 'geofield'])) { // Add "All locations" layers. $event->addBehavior('asset_type_layers'); From 83115768485c1d342cdb6477740e838af8dd9406 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 08:52:31 -0400 Subject: [PATCH 17/21] Do not show raw geometry field by default. --- modules/core/map/src/Element/FarmMapInput.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index cc7da8ea5..c367d34b6 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -38,7 +38,7 @@ class FarmMapInput extends FormElement { '#map_settings' => [], '#behaviors' => [], '#default_value' => '', - '#display_raw_geometry' => TRUE, + '#display_raw_geometry' => FALSE, ]; } From de94168269193efca8be6f55e1518723de84e2b8 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Fri, 17 Jun 2022 08:53:08 -0400 Subject: [PATCH 18/21] Respect the #disabled form element by disabling edit control and textfield/hidden input. --- modules/core/map/src/Element/FarmMapInput.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index c367d34b6..dfa88b405 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -39,6 +39,7 @@ class FarmMapInput extends FormElement { '#behaviors' => [], '#default_value' => '', '#display_raw_geometry' => FALSE, + '#disabled' => FALSE, ]; } @@ -59,17 +60,16 @@ class FarmMapInput extends FormElement { */ public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { - // Merge provided map behaviors into defaults. - $behaviors = array_merge([ - 'wkt', - 'input', - ], $element['#behaviors']); + // Merge provided map behaviors into defaults. Enable wkt and input + // behaviors if #disabled is not TRUE. + $default_behaviors = !$element['#disabled'] ? ['wkt', 'input'] : []; + $behaviors = array_merge($default_behaviors, $element['#behaviors']); // Recursively merge provided map settings into defaults. $map_settings = array_merge_recursive([ 'behaviors' => [ 'wkt' => [ - 'edit' => TRUE, + 'edit' => !$element['#disabled'], 'zoom' => TRUE, ], ], @@ -95,6 +95,7 @@ class FarmMapInput extends FormElement { '#attributes' => [ 'data-map-geometry-field' => TRUE, ], + '#disabled' => $element['#disabled'], ]; // Add default value if provided. From e70b092c0590dccb1914fbe092b653a571469d05 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Sat, 18 Jun 2022 07:54:59 -0400 Subject: [PATCH 19/21] Return a string value from farm_map_input elements instead of an array. --- modules/core/map/src/Element/FarmMapInput.php | 15 +++++---------- .../Plugin/Field/FieldWidget/GeofieldWidget.php | 14 ++++++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/modules/core/map/src/Element/FarmMapInput.php b/modules/core/map/src/Element/FarmMapInput.php index dfa88b405..a8faf5d61 100644 --- a/modules/core/map/src/Element/FarmMapInput.php +++ b/modules/core/map/src/Element/FarmMapInput.php @@ -20,8 +20,6 @@ class FarmMapInput extends FormElement { $class = static::class; return [ '#input' => TRUE, - // @todo Does this have to return a tree structure? - '#tree' => TRUE, '#process' => [ [$class, 'processElement'], ], @@ -59,6 +57,7 @@ class FarmMapInput extends FormElement { * The processed element. */ public static function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { + $element['#tree'] = TRUE; // Merge provided map behaviors into defaults. Enable wkt and input // behaviors if #disabled is not TRUE. @@ -76,8 +75,6 @@ class FarmMapInput extends FormElement { ], $element['#map_settings']); // Define the map render array. - // @todo Does this have to return a tree structure? - $element['#tree'] = TRUE; $element['map'] = [ '#type' => 'farm_map', '#map_type' => $element['#map_type'], @@ -134,6 +131,9 @@ class FarmMapInput extends FormElement { $form_state->setError($element, t('"@value" is not a valid geospatial content.', ['@value' => $value])); } } + + // Save the WKT string value to the overall element. + $form_state->setValueForElement($element, $value); } /** @@ -143,12 +143,7 @@ class FarmMapInput extends FormElement { if ($input === FALSE) { return $element['#default_value'] ?: ''; } - - if ($input['value']) { - return $input['value']; - } - - return NULL; + return $input; } } diff --git a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php index dd158b075..dfab211c8 100644 --- a/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php +++ b/modules/core/map/src/Plugin/Field/FieldWidget/GeofieldWidget.php @@ -149,9 +149,9 @@ class GeofieldWidget extends GeofieldBaseWidget { $element['#suffix'] = ''; // Get the current form state value. Prioritize form state over field value. - $form_value = $form_state->getValue([$field_name, $delta, 'value']); + $form_value = $form_state->getValue([$field_name, $delta]); $field_value = $items[$delta]->value; - $current_value = $form_value ?? $field_value; + $current_value = $form_value['value'] ?? $field_value; $element['#default_value'] = $current_value; // Configure to display raw geometry. @@ -181,6 +181,12 @@ class GeofieldWidget extends GeofieldBaseWidget { ]; } + // Override the element validation to prevent transformation of the value + // from array to string, and because Geofields already perform the same + // geometry validation. + // @see \Drupal\geofield\Plugin\Validation\GeoConstraintValidator. + $element['#element_validate'] = []; + return $element; } @@ -267,11 +273,11 @@ class GeofieldWidget extends GeofieldBaseWidget { $field_name = $this->fieldDefinition->getName(); $delta = $element['#delta']; $user_input = $form_state->getUserInput(); - unset($user_input[$field_name][$delta]['value']); + unset($user_input[$field_name][$delta]); $form_state->setUserInput($user_input); // Set the new form value. - $form_state->setValue([$field_name, $delta, 'value'], $wkt); + $form_state->setValue([$field_name, $delta], ['value' => $wkt]); // Rebuild the form so the map widget is rebuilt with the new value. $form_state->setRebuild(TRUE); From 465bdf59ba8dc6e5faff3cf2983f88fcd8a09902 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Sun, 19 Jun 2022 10:27:22 -0400 Subject: [PATCH 20/21] Document how to use farmOS-map elements and behaviors in farmOS modules. --- docs/development/module/maps.md | 358 ++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 359 insertions(+) create mode 100644 docs/development/module/maps.md diff --git a/docs/development/module/maps.md b/docs/development/module/maps.md new file mode 100644 index 000000000..3089772d4 --- /dev/null +++ b/docs/development/module/maps.md @@ -0,0 +1,358 @@ +# Maps + +farmOS includes features for rendering and manipulating geometry data in +map-based UIs. + +It uses [farmOS-map](https://github.com/farmOS/farmOS-map), which is based on +the open-source [OpenLayers](https://openlayers.org/) project. This includes +tools for drawing and editing geometries, adding imagery and vector layers, and +a framework for writing custom behaviors. + +farmOS-map is maintained by the farmOS community as a standalone library for +common agricultural mapping needs. It is designed to be reusable in any +application with similar needs. It is not specific to or dependent on farmOS +itself. Rather, farmOS includes it as a dependency, and provides some helpful +wrappers for using it inside modules. This page describes how to use farmOS-map +in farmOS modules. + +For more information about the farmOS-map library itself and what it provides, +refer to the farmOS-map documentation on GitHub: + +[github.com/farmOS/farmOS-map](https://github.com/farmOS/farmOS-map) + +## Render element + +Maps can be embedded in pages as a `farm_map` type render element. + +```php +$build['mymap'] = [ + '#type' => 'farm_map', + '#map_type' => 'default', + '#map_settings' => [ + 'mysetting' => 'myvalue', + ], + '#behaviors' => [ + 'mybehavior', + ], +]; +``` + +**Properties:** + +- `#map_type` (optional) - See [Map types](#map-types). Defaults to `default`. +- `#map_settings` (optional) - An array of map settings, which will be passed + into the map instance's client-side JavaScript object, so they are available + in behavior JavaScript. +- `#behaviors` (optional) - See [Behaviors](#behaviors). Defaults to `[]` (but + behaviors may also be added by map types and render events). + +## Form element + +Editable maps can be embedded in forms with a `farm_map_input` type element. +These maps will have the drawing/editing controls enabled, allowing geometries +to be added/edited/deleted directly in the map. A default value can be used to +pre-populate the map with a geometry. A text field can be optionally displayed +beneath the map to show the raw geometry data (auto-updates during editing). + +**Example:** + +```php +$form['mymap'] = [ + '#type' => 'farm_map_input', + '#title' => t('My Geometry'), + '#map_type' => 'default', + '#map_settings' => [ + 'mysetting' => 'myvalue', + ], + '#behaviors' => [ + 'mybehavior', + ], + '#display_raw_geometry' => TRUE, + '#default_value' => 'POINT(-45.967095060886315 32.77503850904169)', +]; +``` + +**Properties:** + +- `#map_type` (same as render element, above) +- `#map_settings` (same as render element, above) +- `#behaviors` (same as render element, above) +- `#display_raw_geometry` (optional) - Whether to show a text field below the + map with the raw geometry value in Well-Known Text (WKT) format. Defaults to + `FALSE`. +- `#default_value` (optional) - The default geometry value to display in the + map initially, in Well-Known Text (WKT) format. This geometry will be + editable in the map unless `#disabled` is `TRUE`. + +## Map types + +farmOS modules can optionally define "map types", which are then referenced in +the `#map_type` property of the render and form elements. + +**This is optional and in most cases the `default` map type is sufficient.** + +Map types are used to define reusable map configurations with common +[behaviors](#behaviors). They can be targeted by [render event](#render-events) +subscribers to add/modify behavior in certain contexts. + +Map types are represented as Drupal config entities, installed via modules, +just like asset types, log types, flags, etc. + +A very simple example of a custom map type definition looks like this: + +`my_module/config/install/farm_map.map_type.mymaptype.yml` + +```yaml +langcode: en +status: true +dependencies: + enforced: + module: + - my_module +id: mymaptype +label: My Map Type +description: "My module's custom map type." +behaviors: { } +options: { } +``` + +**Properties** + +- `id` - A unique ID for the map type. This will be referenced in `#map_type`. +- `label` - A human-readable label for the map type. +- `description` - A human-readable description for the map type. +- `behaviors` - A list of [behaviors](#behaviors) to attach to maps of this + type by default. +- `options` - Default options that will be merged with `#map_settings` and + passed into `farmOS.map.create()`. See: + [github.com/farmOS/farmOS-map#creating-a-map](https://github.com/farmOS/farmOS-map#creating-a-map) + +## Behaviors + +The farmOS-map library uses the concept of "behaviors" to encapsulate common +and reusable sets of map behavior logic into JavaScript objects that can be +"attached" to map instances. + +Behaviors can be used to add layers to a map, add new buttons/controls, enable +OpenLayers interactions, connect maps with other elements of a page like forms, +etc. + +For general information about farmOS-map behaviors, see: +[github.com/farmOS/farmOS-map#adding-behaviors](https://github.com/farmOS/farmOS-map#adding-behaviors) + +Some behaviors that farmOS provides include: + +- `wkt` - Adds a vector layer to the map based on a Well-Known Text (WKT) + string. Edit controls can be optionally enabled to allow drawing, modifying, + moving, and deleting geometries within the map. This behavior is enabled + automatically in the `farm_map_input` form element, and when `wkt` is + included in `#map_settings.` +- `input` - Listens for changes to geometries in the map and copies them to a + form input (`textfield` or `hidden`) to be saved/manipulated server-side. + This behavior is enabled automatically in the `farm_map_input` form element. +- `popup` - Adds a popup interaction to the map, which appears when a geometry + feature is clicked. +- `asset_type_layers` - Adds asset geometry vector and cluster layers to a + map. This behavior is responsible for adding the "Locations" layers on the + farmOS dashboard map, the "Assets" and "Asset counts" layers to asset maps, + automatically zooming to visible geometries, and adding asset details to + popups when a geometry is clicked (depends on the `popup` behavior). + +### Providing behaviors + +Modules can provide their own behaviors with a couple of additional files. + +The behavior itself is represented as a Drupal config entity, which gets +installed as a YML config file during module installation. + +For example (replace `my_module` with the module name, and `mybehavior` with +the behavior name): + +`my_module/config/install/farm_map.behavior.mybehavior.yml` + +```yaml +langcode: en +status: true +dependencies: + enforced: + module: + - my_module +id: mybehavior +label: My Behavior +description: 'Adds my custom behavior logic.' +library: 'my_module/behavior_mybehavior' +settings: { } +``` + +The module must declare the behavior JavaScript file as a "library" so that +it can be included in the page(s) that need it. + +For example (replace `my_module` with the module name, and `mybehavior` with +the behavior name): + +`my_module/my_module.libraries.yml` + +```yaml +behavior_mybehavior: + js: + js/farmOS.map.behaviors.mybehavior.js: { } + dependencies: + - farm_map/farm_map +``` + +Finally, the behavior JavaScript file should have a path and filename that +matches the library definition. + +For example (replace `my_module` with the module name, and `mybehavior` with +the behavior name): + +`my_module/js/farmOS.map.behaviors.mybehavior.js` + +```js +(function () { + farmOS.map.behaviors.mybehavior = { + attach: function (instance) { + + // My custom behavior logic. + } + }; +}()); +``` + +The `instance` object represents the farmOS-map instance, and includes helper +methods for common needs (eg: `instance.addLayer()`), as well as direct access +to the OpenLayers map object at `instance.map`. + +For more information see: +[github.com/farmOS/farmOS-map](https://github.com/farmOS/farmOS-map) + +### Attaching behaviors + +Behaviors can be "attached" (enabled) in a map in a few different ways: + +- [Map types](#map-types) can include a list of default `behaviors`. +- The `#behaviors` property of the `farm_map` [render element](#render-element) + and `farm_map_input` [form element](#form-element) can add specific behaviors + to individual elements. +- A [render event](#render-events) subscriber can use the + `$event->addBehavior()` method. + +In all cases the behavior's `id` (as defined it its config entity YML) is used. + +### Behavior settings + +Some behaviors may require additional settings based on their context. Best +practice is to include these in the map settings so that they are available in +the behavior JavaScript in the following way: + +`const settings = instance.farmMapSettings.behaviors.mybehavior;` + +This can be accomplished in different ways, depending on how the behavior is +being attached to the map. + +[Map types](#map-types) can add behavior settings to their `options` property. +For example: + +```yaml +langcode: en +status: true +dependencies: + enforced: + module: + - my_module +id: mymaptype +label: My Map Type +description: "My module's custom map type." +behaviors: + - mybehavior +options: + behaviors: + mybehavior: + mysetting: True +``` + +Maps added as [render](#render-element) or [form](#form-element) elements can +add behavior settings in their `#map_settings` property. For example: + +```php +$build['mymap'] = [ + '#type' => 'farm_map', + '#map_settings' => [ + 'behaviors' => [ + 'mybehavior' => [ + 'mysetting' => TRUE, + ], + ], + ], + '#behaviors' => [ + 'mybehavior', + ], +]; +``` + +Behaviors that are added via [render event](#render-events) subscribers can add +settings at the same time: + +```php +$event->addBehavior('mybehavior', ['mysetting' => TRUE]); +``` + +All of the above approaches will make the settings available in the behavior +JavaScript in the same place. + +## Render events + +farmOS will trigger an event when a map is rendered. Modules can set up an +event subscriber to perform additional logic at that time, such as adding +behaviors. + +For example, to add a behavior to all maps in farmOS, add the following two +files (replace `my_module` with the module name, and `mybehavior` with the +behavior name): + +`my_module/my_module.services.yml` + +```yaml +services: + my_module_map_render_event_subscriber: + class: Drupal\my_module\EventSubscriber\MapRenderEventSubscriber + tags: + - { name: 'event_subscriber' } +``` + +`my_module/src/EventSubscriber/MapRenderEventSubscriber` + +```php + 'onMapRender', + ]; + } + + /** + * React to the MapRenderEvent. + * + * @param \Drupal\farm_map\Event\MapRenderEvent $event + * The MapRenderEvent. + */ + public function onMapRender(MapRenderEvent $event) { + $event->addBehavior('mybehavior'); + } + +} +``` diff --git a/mkdocs.yml b/mkdocs.yml index ec71d2a4b..b6acd8efa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Quick forms: development/module/quick.md - Entities: development/module/entities.md - Fields: development/module/fields.md + - Maps: development/module/maps.md - OAuth: development/module/oauth.md - Roles: development/module/roles.md - Services: development/module/services.md From c60b7d159757e8659965fd865b0853ef9e6ec606 Mon Sep 17 00:00:00 2001 From: Michael Stenta Date: Sun, 19 Jun 2022 10:28:02 -0400 Subject: [PATCH 21/21] Add a CHANGELOG.md entry for issue #3290929. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18316a526..c640940f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- [Issue #3290929: Provide a farmOS map form element](https://www.drupal.org/project/farm/issues/3290929) + ### Security - Update Drupal core to 9.3.16 for [SA-CORE-2022-011](https://www.drupal.org/sa-core-2022-011).