phpreactjssymfony5sulu

Sulu CMF - Pass parameter to autocomplete field in FormOverlayList add form


This question is a followup.

I have two entities DataSet and DataGroup.

class DataSet {
  string $name;
  Collection $groups; // Collection<int, DataGroup>
}
class DataGroup {
  string $name;
  DataSet $dataSet;
  ?DataGroup $nextGroup; // Condition: $nextGroup !== $this && $nextGroup->dataSet === $this->dataSet
}

The property DataGroup::nextGroup may refer to any other DataGroup entity associated with the same DataSet.

I want to create a CRUD form where I can add, edit and remove DataSet entities. In this DataSet form, I also want to include a tab where I can CRUD DataGroup entities associated with the current DataSet.

I have created list metadata data_sets.xml and data_groups.xml, as well as form metadata data_set.xml and data_group.xml.

<!-- lists/data_sets.xml -->
<list xmlns="http://schemas.sulu.io/list-builder/list">
  <key>data_sets</key>
  <properties>
    <property name="name" visibility="always" searchability="yes">
      <field-name>name</field-name>
      <entity-name>App\Entity\DataSet</entity-name>
    </property>
  </properties>
</list>

<!-- lists/data_groups.xml -->
<list xmlns="http://schemas.sulu.io/list-builder/list">
  <key>data_groups</key>
  <properties>
    <property name="name" visibility="always" searchability="yes">
      <field-name>name</field-name>
      <entity-name>App\Entity\DataSet</entity-name>
    </property>
    <property name="dataSet" visibility="always">
      <field-name>name</field-name>
      <entity-name>App\Entity\DataSet</entity-name>
      <joins>
        <join>
          <entity-name>App\Entity\DataSet</entity-name>
          <field-name>App\Entity\DataGroup.dataSet</field-name>
        </join>
      </joins>
    </property>
  </properties>
</list>
<!-- forms/data_set.xml -->
<form xmlns="http://schemas.sulu.io/template/template"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://schemas.sulu.io/template/template 
      http://schemas.sulu.io/template/form-1.0.xsd"
>
  <key>data_set</key>
  <properties>
    <property name="name" type="text_line" mandatory="true">
      <params>
        <param name="headline" value="true"/>
      </params>
    </property>
  </properties>
</form>

<!-- forms/data_group.xml -->
<form xmlns="http://schemas.sulu.io/template/template"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://schemas.sulu.io/template/template 
      http://schemas.sulu.io/template/form-1.0.xsd"
>
  <key>data_group</key>
  <properties>
    <property name="name" type="text_line" mandatory="true">
      <params>
        <param name="headline" value="true"/>
      </params>
    </property>
    <property name="nextGroup" type="single_data_group_selection">
      <params>
        <param name="type" value="auto_complete"/>
        <param name="resource_store_properties_to_request" type="collection">
          <param name="dataSetId" value="id"/>
        </param>
      </params>
    </property>
  </properties>
</form>

I have also configured sulu_admin.yaml for REST routes and the custom single selection:

sulu_admin:
  resources:
    data_sets:
      routes:
        list: app.get_data_sets
        detail: app.get_data_set
    data_groups:
      routes:
        list: app.get_data_groups
        details: app.get_data_group
  field_type_options:
    single_selection:
      single_data_group_selection:
        default_type: 'auto_complete'
        resource_key: 'data_groups'
        types:
          auto_complete:
            display_property: 'name'
            search_properties:
              - 'name'

I implemented two REST controllers for both entities. DataSetController is built as of the Docs, while DataGroupController has a small extension for the list route:

class DataGroupController implements ClassResourceInterface
{
  public function cgetAction(int $dataSetId, Request $request): Response
  {
    // ... Init field descriptors and execute listBuilder
    
    $list = new ListRepresentation(
      $listResponse,
      'data_groups',
      \array_merge(['dataSetId' => $dataSetId], $request->query->all()), // add DataSet ID
      $listBuilder->getCurrentPage(),
      $listBuilder->getLimit(),
      $listBuilder->count()
    );

    // ... handle view
  }

  // ...
}

Finally, I implemented a custom Admin class like explained in the Docs.

class DataAdmin extends Admin
{
  public const DATA_SET_DETAILS = 'data_set_details';
  public const DATA_SET_GROUPS = 'data_set_groups';
  public const DATA_SET_LIST = 'app.data_sets_list';
  public const DATA_SET_ADD_FORM = 'app.data_set_add_form';
  public const DATA_SET_ADD_FORM_DETAILS = 'app.data_set_add_form.details';
  public const DATA_SET_EDIT_FORM = 'app.data_set_edit_form';
  public const DATA_SET_EDIT_FORM_DETAILS = 'app.data_set_edit_form.details';
  public const DATA_SET_EDIT_FORM_GROUPS = 'app.data_set_edit_form.groups';

  public function configureViews(ViewCollection $viewCollection): void
  {
    // Add DataSet list view
    // Add DataSet add form view
    // Add DataSet edit form view (details)
    
    /*
     * Custom second DataSet edit form tab for DataGroup CRUDding
     */
    $groupsFormOverlayList = $this->viewBuilderFactory
      ->createFormOverlayListViewBuilder(self::DATA_SET_EDIT_FORM_GROUPS, '/groups')
      ->setResourceKey(self::DATA_SET_GROUPS)
      ->setListKey(self::DATA_SET_GROUPS)
      ->addListAdapters(['table'])
      ->addRouterAttributesToListRequest(['id' => 'dataSetId'])
      ->setFormKey(self::DATA_SET_GROUPS)
      ->addRouterAttributesToFormRequest(['id' => 'dataSetId'])
      ->setTabTitle('app.data_groups')
      ->addToolbarActions([
        new ToolbarAction('sulu_admin.add'),
        new ToolbarAction('sulu_admin.delete')
      ])
    ;
    $viewCollection->add($groupsFormOverlayList->setParent(self::DATA_SET_EDIT_FORM));
  }
}

As of now

When trying to add a new DataGroup, I can't perform autocomplete search for nextGroup because the dataSetId parameter is not passed to the field, forbidding the ResourceRequester to perform the REST request.

How do I pass dataSetId to fields of the "New DataGroup" form in order to search for matching entities only?


Solution

  • Thanks a lot for the detailed description! Unfortunately, I am afraid that it is not possible to implement what you are trying to do while using the built-in autocomplete component at the moment.

    The resource_store_properties_to_request param reads the values from the data of the form that renders the autocomplete component. If you are creating a new DataGroup entity, the data of the form is empty (because there is now existing data for a new entity) and therefore the resource_store_properties_to_request param is not able to read a dataSetId value.

    I am sorry to say, but I think you would need to implement a custom autocomplete field-type that reads the dataSetId value from the current url to achieve the desired functionality. If you are interested in doing this, I would recommend to have a look at the basic field-type example in the sulu-demo repository.