Skip to content

[Data Object Editor] Many to one relation - display mode "Inline search" - #3960

Merged
ValeriaMaltseva merged 6 commits into
pimcore:2026.xfrom
alexbaat:feature/889-many-to-one-inline-search
Aug 18, 2026
Merged

[Data Object Editor] Many to one relation - display mode "Inline search"#3960
ValeriaMaltseva merged 6 commits into
pimcore:2026.xfrom
alexbaat:feature/889-many-to-one-inline-search

Conversation

@alexbaat

@alexbaat alexbaat commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Resolves pimcore/platform-version#287

Supersedes #889, which tracked the same gap before tracking issues moved to platform-version.

Problem

The class definition editor offers Display mode → "Inline search" on a manyToOneRelation and stores it correctly (displayMode: 'combo', persisted via AbstractRelations::$displayMode). The object data component never reads it, though: DynamicTypeObjectDataManyToOneRelation.getObjectDataComponent() always renders ManyToOneRelation, the path reference input. Only manyToManyObjectRelation implements a combo branch (labelled "Tag field" there).

So picking "Inline search" changes nothing anywhere — both options render the same widget.

Change

Adds a single-select inline search field for manyToOneRelation, used when displayMode === 'combo' and the relation is restricted to objects of exactly one class.

The combo offers its options through a search on a single data object class, so it can only represent a relation restricted to exactly that. A relation that also accepts assets, documents or object folders keeps the path reference input, which can address every element type — otherwise those targets would become unselectable.

New files, next to the owning dynamic type and mirroring the existing many-to-many combo:

  • components/many-to-one-relation/components/combo-field/many-to-one-relation-combo-field.tsx
  • components/many-to-one-relation/components/combo-field/hooks/use-combo-field-data.ts — paged full-text search plus label resolution
  • components/many-to-one-relation/components/combo-field/hooks/use-relation-source-object-id.ts

Notes on three details that are easy to get wrong

Labels. A manyToOneRelation has no visibleFields, so option labels come from the field's pathFormatterClass (data-objects/format-path), falling back to the object path when none is configured. getAvailableDataObjectColumnConfigurationForRelation cannot be reused here — it throws for anything that is not a (Advanced)ManyToManyObjectRelation — hence a separate hook rather than reusing the many-to-many useComboFieldData.

Source object. format-path resolves the field definition from a concrete object. That object is taken from the row being edited (grid cell edit modal, via getGridCellEditComponent), the first selected row (batch edit), or the element context (object editor). Inside a listing the element context is the folder being listed, and the endpoint answers 404 Object with ID: x not found for it, so it is only used outside a listing.

Searching. Filtering happens server side (filterOption={false}). Filtering the options client side only matches the rendered labels, so typing an object's name finds nothing whenever those labels are object paths.

useClassDefinitionCollectionQuery maps the allowed class name to its id rather than useClassDefinitions(), because that context is folder-scoped inside a listing (ClassDefinitionsProvider filters its items by elementId) and would not know the related class.

Tests

  • dynamic-type-object-data-many-to-one-relation.test.ts — the rendering decision: combo vs. path input per display mode, the object-only guard with a case per mixed configuration (assets, documents, object folders), and that the grid cell editor forwards the edited row id.
  • use-combo-field-data.test.ts — class id resolution, the debounced full-text term, page reset on search, paging on scroll versus a fully loaded list, and label resolution through the path formatter including both fallbacks.

npm run check-types clean, npm run lint clean for the touched files, npm test 265 passed.

Manual verification

Verified against a project with ~8.000 objects in the related class and a pathFormatterClass configured, in all three places the field renders — object editor, grid cell edit modal and batch edit — covering searching, paging, selecting, saving and clearing.

Review feedback addressed

  • Inline search no longer claims mixed asset/document/folder relations; regression cases added per type.
  • The inherited state is forwarded to Select, so combo-mode fields keep the inherited styling.
  • The component moved next to the owning dynamic type and is no longer exported through the SDK barrel.
  • Added hook coverage for search, pagination and label resolution.

alexbaat and others added 2 commits August 4, 2026 14:07
The class definition editor lets you pick "Inline search" (displayMode 'combo')
on a manyToOneRelation and stores it, but the object data component never read
it and always rendered the path reference input — only manyToManyObjectRelation
implemented the combo branch. The setting was effectively dead UI.

Adds a single-select inline search field for manyToOneRelation, rendered
whenever displayMode is 'combo' and the relation allows objects of exactly one
class. Everything else keeps the path reference input.

Option labels are resolved through the field's pathFormatterClass and fall back
to the object path when none is configured. Because the format-path endpoint
resolves the field definition from a concrete object, the source object is taken
from the row being edited in the grid, the first selected row in batch edit, and
the element context in the object editor — a listing's element context is the
folder, which the endpoint rejects.

Searching always goes to the server: filtering the options client side would
only match the rendered labels, so typing a name would find nothing whenever
the labels are object paths.

Resolves pimcore#889

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexbaat
alexbaat marked this pull request as ready for review August 4, 2026 12:15
Copilot AI balanced review requested due to automatic review settings August 4, 2026 12:15
@pimcore-deployments
pimcore-deployments marked this pull request as draft August 4, 2026 12:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Verdict: Needs changes. The PR activates inline search for eligible many-to-one relations.

Changes:

  • Adds the combo field with server-side search and formatted labels.
  • Supports object editor, grid editing, and batch editing.
  • Adds display-mode selection tests.

Review assessment:

  • Root cause addressed at dynamic-type-object-data-many-to-one-relation.tsx:49-60.
  • Mixed asset/document/folder configurations are incorrectly routed to object-only search at :110-112.
  • Inherited styling is lost at many-to-one-relation-combo-field.tsx:84.
  • The feature-specific component is unnecessarily exposed through the SDK at index.ts:20-24.
  • Tests cover component selection but not search, pagination, or label resolution. No breaking API change was found.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
dynamic-type-object-data-many-to-one-relation.tsx Selects combo rendering and forwards grid row IDs.
dynamic-type-object-data-many-to-one-relation.test.ts Tests display-mode routing.
many-to-one-relation/index.ts Exports the combo component.
many-to-one-relation-combo-field.tsx Implements the single-select UI.
use-relation-source-object-id.ts Resolves the source object context.
use-combo-field-data.ts Implements search, pagination, and label formatting.

Comment thread assets/js/src/core/components/many-to-one-relation/index.ts Outdated
- Restrict inline search to object-only relations. The combo searches a single
  data object class, so a relation that also accepts assets, documents or object
  folders would make those targets unselectable. Those configurations keep the
  path reference input, with a regression case per mixed type.
- Forward the inherited state to Select, so combo-mode fields keep the inherited
  styling the path input already shows.
- Move the component next to the owning object-data dynamic type, mirroring the
  many-to-many combo, and drop the barrel export that exposed a feature-specific
  implementation as a central SDK component.
- Cover the data hook: class id resolution, server-side searching, the debounced
  term, paging on scroll and label resolution through the path formatter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alexbaat
alexbaat marked this pull request as ready for review August 4, 2026 12:38
@ValeriaMaltseva
ValeriaMaltseva self-requested a review August 5, 2026 13:29

@ValeriaMaltseva ValeriaMaltseva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alexbaat, and thanks a lot for your contribution! The PR already looks good.

One thing I think we need before merging: the class definition editor is not updated. field-definition-many-to-one-relation-form-fields.tsx:151-158 still offers Display mode → "Inline search" for every configuration, including the ones usesInlineSearch() now rejects - more than one allowed class, assets or documents allowed, folder-only. There the setting saves and then silently does nothing, which is the dead-UI symptom pimcore/platform-version#287 describes, just moved to a different configuration.

Could you mirror the usesInlineSearch() condition there and disable the "Inline search" option when it does not apply, with the reason in the Form.Item tooltip? The form currently watches only displayMode, so it would also need objectsAllowed, assetsAllowed, documentsAllowed and classes. field-definition-many-to-many-object-relation-form-fields.tsx:25,44-56 does the same kind of reconciliation for visibleFields if you want a reference.

Please keep the option in the list and disabled rather than removing it - with a stored displayMode: 'combo' antd would otherwise render the raw value as the label. No need to migrate stored values.

alexbaat and others added 2 commits August 17, 2026 14:58
The class definition editor offered Display mode -> "Inline search" for
every manyToOneRelation, including the configurations the data component
rejects: more than one allowed class, assets or documents allowed, or
folder-only. There the setting saved and then did nothing, which is the
same dead UI the field itself was meant to fix.

The condition moves out of DynamicTypeObjectDataManyToOneRelation into
supportsInlineSearch(), so the editor and the data component cannot drift
apart. The editor now watches objectsAllowed, assetsAllowed,
documentsAllowed and classes alongside displayMode, disables the option
when the relation is not eligible and explains why in the Form.Item
tooltip.

The option stays in the list rather than being removed: with a stored
displayMode 'combo' antd would otherwise render the raw value as its
label. Stored values are left untouched.
@sonarqubecloud

Copy link
Copy Markdown

@alexbaat

Copy link
Copy Markdown
Contributor Author

Hi @ValeriaMaltseva, thanks for the review — good catch, the editor side was
indeed still offering the mode unconditionally. Pushed in 48635b0.

Shared condition. The eligibility check moved out of
DynamicTypeObjectDataManyToOneRelation into supportsInlineSearch() in
helpers/relations/inline-search.ts, without the displayMode part. The data
component is now props.displayMode === 'combo' && supportsInlineSearch(props)
and the editor calls the same function, so the two cannot drift apart. It takes
Partial<IRelationAllowedTypesClassDefinition> and compares explicitly against
true, because the form values are undefined until their switch is touched.

Editor. field-definition-many-to-one-relation-form-fields.tsx now watches
objectsAllowed, assetsAllowed, documentsAllowed and classes next to
displayMode. The "Inline search" option gets disabled when the relation is
not eligible, and the Form.Item gets a tooltip naming the reason:

Inline search is only available when the relation allows objects of exactly
one class. A relation that also allows assets, documents or object folders
uses the path reference instead.

Added to all seven locale files. The tooltip is only set while the option is
unavailable — happy to make it permanent if you would rather have the
constraint documented at all times.

No reconciliation. I deliberately did not mirror the visibleFields reset
from field-definition-many-to-many-object-relation-form-fields.tsx: a stored
displayMode: 'combo' is left as it is, and the option stays in the list so
antd keeps rendering its label. That follows your last paragraph — say the word
if you would prefer the value to fall back to grid once the configuration
stops supporting it.

Tests. inline-search.test.ts covers the predicate itself: a single allowed
class, more than one class, folder-only, assets or documents allowed, objects
disallowed, empty and undefined classes, and the untouched switches.
field-definition-many-to-one-relation-form-fields.test.tsx covers the wiring:
the option is disabled and the tooltip is set for each rejected configuration,
neither for the supported one, and the option is still present with a stored
combo. Full suite 280 passed, check-types and lint clean.

Copilot AI commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install dependencies with Composer

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

assets/js/src/core/modules/element/dynamic-types/definitions/objects/data-related/components/many-to-one-relation/components/combo-field/hooks/use-combo-field-data.ts:196

  • Path formatter results are HTML-capable: the existing relation input renders them through SanitizeHtml, and the grid preview marks formatted paths as HTML. Storing formatedPath as a plain string here means a formatter returning markup (for example, <strong>Name</strong>) is displayed literally in the Select. Preserve the distinction between raw paths and formatted HTML, and render formatter labels through the sanitizer.
        if (Number.isInteger(id) && typeof item.formatedPath === 'string' && item.formatedPath !== '') {
          resolved.set(id, item.formatedPath)

assets/js/src/core/modules/element/dynamic-types/definitions/objects/data-related/components/many-to-one-relation/components/combo-field/many-to-one-relation-combo-field.tsx:48

  • Batch edit never supplies combinedFieldName: its data-object adapter passes only config.fieldDefinition and defaultFieldWidth, unlike the editor and grid adapters that explicitly derive this value. Consequently this is undefined in batch edit, hasPathFormatter stays false, and configured path formatters are silently skipped even though useRelationSourceObjectId resolves a selected row. Pass the batch field path (or a reliable field-name fallback) into the combo data hook.
  } = useComboFieldData({
    allowedClasses: props.allowedClasses,
    combinedFieldName: props.combinedFieldName,
    objectId,
    pathFormatterClass: props.pathFormatterClass,
    value: props.value

@ValeriaMaltseva ValeriaMaltseva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alexbaat! Thanks for the really quick update to the PR, and once again, thanks for your contribution. I'll merge it ;)

@ValeriaMaltseva
ValeriaMaltseva merged commit f0cedaf into pimcore:2026.x Aug 18, 2026
21 of 22 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data Object Editor: many-to-one relation ignores display mode "Inline search"

3 participants