Fix M2A display templates from the native picker (#60)#64
Merged
Conversation
An M2A column display built with the native display-template picker rendered
an empty cell. The picker emits per-collection tokens prefixed with the parent
field name (e.g. `{{treatment:partners_catalog.name}}`), but only the
hand-written `{{item:collection.field}}` form was recognised, so the picker
tokens were dropped on both the query and render sides.
- Add a shared isM2APrefix helper in displayHeuristics that accepts the parent
field name, the junction item field, and the literal `item` as valid M2A
token prefixes — matching Directus core's render-template behaviour.
- Use it in expandTokensThroughRelation (query) and renderM2ATemplate (render);
renderM2ATemplate now takes the field name to resolve the prefix.
- Both token formats now produce identical field paths and output.
The native display-template picker is rooted at the parent collection, so it
emits field-key-prefixed tokens (treatment.collection,
treatment.item:service.name) that the renderer did not recognise, leaving M2A
cells blank. A shared stripM2AFieldPrefix helper normalises the picker and
hand-written forms on both the query and render sides.
Also fixes templates without an item token (e.g. {{collection}}), which blanked
the whole cell: the segment guard now distinguishes a not-fetched item
(undefined) from a permission-denied/dangling one (null). Parent-row ({{code}})
and junction-level ({{treatment.sort}}) fields are now resolvable, the deepest
scope wins on a name clash, and every token is validated before the request to
avoid a 403. M2A segment building is extracted into a unit-tested
buildM2ASegments helper.
Document that the native field picker now works directly, the resolvable token scopes (item, discriminator, junction-prefixed, bare parent field) and that the deepest match wins. Removes the now-false "bare token is dropped" and "do not prefix the field name" notes.
Quality Check ResultsTypeScript Type Check✅ Passed - No type errors found ESLint✅ Passed - No linting errors Prettier Format Check✅ Passed - Code is properly formatted Build✅ Passed - Extension builds successfully Updated: 2026-06-01T22:54:45.316Z |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes Many-to-Any (M2A) column-display support so templates built with Directus's native display-template picker actually render. Follow-up to the reopened #60 — v0.5.0 shipped M2A but the picker output never resolved.
Root cause
The picker is rooted at the parent collection, so it emits field-key-prefixed tokens —
{{treatment.collection}},{{treatment.item:service.name}}— not the hand-written{{item:...}}form the renderer expected. Confirmed live against Directus 11.11.0 by reading the picker's emitteddata-fieldattributes.Changes
stripM2AFieldPrefixhelper normalises the picker (treatment.item:…) and hand-written (item:…) forms on both the query and render sides, so the two cannot drift.item:token made the API omit the junctionitem; the segment guard then treated the resultingundefinedlike a permission denial and skipped every row. The guard now distinguishes not-fetched (undefined) from genuinely absent (null).{{code}}resolves against the parent row and{{treatment.sort}}against the junction row, alongside the discriminator and per-collectionitem:values. On a name clash the most specific (deepest) token wins. Every token is validated against its target before the request, so an unknown token is dropped instead of 403'ing.buildM2ASegments;resolveM2ARelationnow reports the junction collection; thecollectiondiscriminator token is shared by the query and render sides.Verification
Refs #60.