Use the office hours field widget as Element (in a settings form)

Created on 9 April 2024, 9 months ago
Updated 13 June 2024, 7 months ago

Problem/Motivation

The office hours widget is great when used for a content entity field.

Would it be possible, to use the same form widget in a settings form? That would allow us to set the office hours of the company that owns the website, i.e. this is only 1 data set and it doesn't justify to create a content entity type just for that.

Feature request
Status

Fixed

Version

1.17

Component

Code - widget

Created by

🇩🇪Germany jurgenhaas Gottmadingen

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Comments & Activities

  • Issue created by @jurgenhaas
  • 🇳🇱Netherlands johnv

    I am not sure.
    I think you can program a settings form, and add the field. Not sure where the settings are then stored.
    Would it be something like this request? Exceptions that apply to entire bundles Active

    I am also maintainer fo Workflow module,
    and there I have learned how to add a widget to a custom form. Perhaps you can take advantage of it:

     public static function createInstance(WorkflowTransitionInterface $transition) : array {
        $element = [];
    
        $entity_type_manager = \Drupal::service('entity_type.manager');
        $entity = $transition->getTargetEntity();
        $entity_type_id = $entity->getEntityTypeId();
        $entity_bundle = $entity->bundle();
        $view_mode = 'default';
        $field_name = $transition->getFieldName();
    
        /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
        $entity_form_display = $entity_type_manager->getStorage('entity_form_display');
        $dummy_form['#parents'] = [];
        $form_state = new FormState();
        $form_display = $entity_form_display->load("$entity_type_id.$entity_bundle.$view_mode");
        // $form_state_clone = clone $form_state;
        // $form_state_clone->set('entity', $entity);
        // $form_state_clone->set('form_display', $form_display);
        // $widget_fields = [$field_name];
        // foreach ($form_display->getComponents() as $name => $component) {
        //   if (in_array($name, $widget_fields)) {
        if ($widget = $form_display->getRenderer($field_name)) {
          $items = $entity->get($field_name);
          $items->filterEmptyItems();
          $element[$field_name] = $widget->form($items, $dummy_form, $form_state);
        }
        //   }
        // }
        return $element;
      }
    
    
  • 🇳🇱Netherlands johnv

    In the past, I encountered a way to create an Article or Basic page, having PHP in the body text. That might be an alternative.

  • 🇳🇱Netherlands johnv

    Also, with the Paragraphs module, you can add fields on a per page basis.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    That approach from the workflow module doesn't work, since I don't have any entity type with an office_hours field. I've carefully reviewed the site's entity types and none could be used as a "dummy" to hold a reference to a field.

    In a settings form, something like this would be nice:

        $form['officehours'] = [
          '#type' => 'office_hours_default',
          '#title' => $this->t('Office hours'),
          '#default_value' => [],
        ];
    

    The, I should receive an array of values when the form gets submitted in the submit handler and should be able to store them in the config entity.

    When I try the above code, just nothing appears in the form.

  • 🇳🇱Netherlands johnv

    Hmm,
    in orde to help you, I need your complete, code. But on this level, office_hours acts as any other field.
    Ofcourse, you can always add the opening hours as plain text on some page.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    Well, just a regular settings form, as almost all modules bring with them, e.g.

    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\example\Form;
    
    use Drupal\Core\Form\FormBase;
    use Drupal\Core\Form\FormStateInterface;
    
    /**
     * Provides a APBS Entities form.
     */
    final class ExampleForm extends FormBase {
    
      /**
       * {@inheritdoc}
       */
      public function getFormId(): string {
        return 'example_example';
      }
    
      /**
       * {@inheritdoc}
       */
      public function buildForm(array $form, FormStateInterface $form_state): array {
        $config = \Drupal::configFactory()->get('example.settings');
    
        $form['office_hours'] = [
          '#type' => 'office_hours_default',
          '#title' => $this->t('Office hours'),
          '#default_value' => $config->get('office_hours'),
        ];
    
        $form['actions'] = [
          '#type' => 'actions',
          'submit' => [
            '#type' => 'submit',
            '#value' => $this->t('Send'),
          ],
        ];
    
        return $form;
      }
    
      /**
       * {@inheritdoc}
       */
      public function submitForm(array &$form, FormStateInterface $form_state): void {
        $config = \Drupal::configFactory()->getEditable('example.settings');
        $config->set('office_hours', $form_state->getValue('office_hours'));
        $config->save();
      }
    
    }
    
  • 🇳🇱Netherlands johnv

    Problem is that your approach uses 'FormElements', not 'FieldWidgets'.
    The FormElements are the single time slots, united in the Weekly FieldWidget.

    Perhaps it is possible to wrap the time slots in a complex Element, like 'checkboxes' is a wrapper around x 'checkbox' elements.

    You may borrow code from OfficeHoursWeekWidget::formElement(), which does $elements[] = ['#type' => 'office_hours_slot', ...]
    or from OfficeHoursCOmplextWeekWidget::formElement(), which calles simpler widgets.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    Ah, that might work. Thanks a lot @johnv for pointing that out, I'll give that a try. FYI, a couple of years ago I had a similar requirement with the address field module, where a solution become available at some point as well. So, it should be possible here too, let's see. I'll report back.

  • 🇳🇱Netherlands johnv

    I have refactored the widget into an element. Now cleaning up code. Please bear with me.

    • johnv committed 54614043 on 8.x-1.x
      Issue #3439685 by johnv, jurgenhaas: Prepare office_hours_week...
  • 🇳🇱Netherlands johnv

    As a preliminary version, please drop the attached file in the src/Element directory.

    Then :

      public function buildForm(array $form, FormStateInterface $form_state) {
        $form = [];
    
        $config = \Drupal::configFactory()->get('example. Settings');
        $field_settings = OfficeHoursItem::defaultStorageSettings();
        $widget_settings = OfficeHoursWeekWidget::defaultSettings();
        $office_hours = $config->get('office_hours') ?? [];
    
        $form['office_hours'] = [
          '#type' => 'office_hours_table',
          '#title' => $this->t('Office hours'),
          '#default_value' => $office_hours,
    
          '#field_settings' => $field_settings,
          '#widget_settings' => $widget_settings,
          '#field_type' => $week_season_id = 0,
        ];
      
      $form['actions'] = [
          '#type' => 'actions',
          'submit' => [
            '#type' => 'submit',
            '#value' => $this->t('Send'),
          ],
        ];
    
        return $form;
    }
    

    and:

      public function submitForm(array &$form, FormStateInterface $form_state) {
        $office_hours = $form_state->getValue(['office_hours']);
        $config = \Drupal::configFactory()->getEditable('example. Settings');
        $config->set('office_hours', $office_hours);
        $config->save();
      }
    
  • 🇩🇪Germany jurgenhaas Gottmadingen

    This looks very promising. But I had to make a couple of corrections:

    • In the attached file I had to change the class name to OfficeHoursTableTest
    • In the form build code the type should be office_hours_table_test

    Everything else seems to be correct. When I do that, and try to open the form, I'm getting this error:

    Error: Attempt to assign property "day_delta" on array in Drupal\office_hours\Element\OfficeHoursBaseSlot::processOfficeHoursSlot() (line 156 of modules/contrib/office_hours/src/Element/OfficeHoursBaseSlot.php).
    

    Note: I'm using the 8.x-1.x dev release for this test.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    It looks like OfficeHoursTableTest provides an array as $element['#value'] instead of an OfficeHoursItem

    • johnv committed 83389d9e on 8.x-1.x
      Issue #3439685 by johnv, jurgenhaas: Prepare elements for...
    • johnv committed 8d43c15f on 8.x-1.x
      Issue #3439685: introduce OfficeHoursDateHelper::getSeasonId()
      
    • johnv committed 154e3ef8 on 8.x-1.x
      Issue #3439685: introduce OfficeHoursDateHelper::getSeasonId()
      
    • johnv committed 3afb76d1 on 8.x-1.x
      Issue #3439685 by johnv, jurgenhaas: Use the office hours field widget...
  • 🇳🇱Netherlands johnv

    Please try the newest dev-version.

  • Status changed to Needs review 9 months ago
  • Status changed to RTBC 9 months ago
  • 🇩🇪Germany jurgenhaas Gottmadingen

    This is amazing!!! Thanks @johnv I've tested the latest dev release and that is working exactly as expected. This is outstanding, thank you so much for providing this feature on request.

  • 🇳🇱Netherlands johnv

    You're welcome :-)

    In order to make sure that this functionality won't be lost in the future, a test case is needed.
    However, I am not sure how to do that.
    Do you know how to create such a test case: create form, save data, check data?

  • 🇩🇪Germany jurgenhaas Gottmadingen

    I'm not an expert in that area, but for our ECA module, a colleague of mine wrote some form submission tests and you can find an example at https://git.drupalcode.org/project/eca/-/blob/2.0.x/modules/form/tests/s...

    Maybe that's helpful?

    • johnv committed 5ed964cb on 8.x-1.x
      Issue #3439685: Prepare elements for office_hours_table FormElement
      
  • 🇳🇱Netherlands johnv

    Thanks, I will take a look.
    Following patch to correct the widget code, which is broken after isolating the Element :-(

    • johnv committed de7bbedc on 8.x-1.x
      Issue #3439685: Use the office hours field widget as Element (in a...
    • johnv committed e8a061a9 on 8.x-1.x
      Issue #3439685: Use the office hours field widget as Element (in a...
    • johnv committed e07e144c on 8.x-1.x
      Issue #3439685: fix regression error on widget Collapsed setting
      
  • Status changed to Fixed 7 months ago
  • 🇳🇱Netherlands johnv

    I created 📌 Add test for WeekTableElement in a form (not a widget) Active , so this issue can be closed.

  • 🇩🇪Germany jurgenhaas Gottmadingen

    Thank you so much for taking care of this. Really much appreciated.

  • 🇳🇱Netherlands johnv

    you are welcome :-)

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024