Content Drive: split DotFolderListViewComponent into a presentational table and a browsing container
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Description
PR #37023 (issue #36975) made the Action Center's action preview reuse DotFolderListViewComponent instead of the hand-rolled table it had been maintaining. That was the right call — the preview inherits the grid's per-row lock icon, which was the whole point of the change, and it removes a near-identical second table that drifted every time either was touched.
The cost is that DotFolderListViewComponent now serves two callers that want very different things:
| Portlet grid | Action preview | |
|---|---|---|
| Data | server-paged (lazy) |
in-memory, whole list |
| Row actions | drag, context menu, kebab, open-on-click | none |
| Sorting | yes | no |
| Selection | uncontrolled (owns its set) | controlled (parent owns it) |
| Columns | full set | 3-column subset |
| Row key | identifier |
inode |
Reconciling them took seven inputs — dataKey, lazy, visibleColumns, disabled, lockedByOthers, readOnly, selection — plus guard clauses in onContextMenu, onDoubleClick and onFirstChange, and an effect that re-asserts selection onto PrimeNG's table. It works and it is tested, but three smells came with it:
readOnlyis a flag argument standing in for five behaviours — drag, context menu, open-on-click, kebab and column sorting. The name doesn't say what it does, and every future behaviour has to remember to consult it.- The header is data-driven; the body is hand-coded.
$columnsis computed fromHEADER_COLUMNS, but each body cell is a literal@if ($visibleColumnSet().has('title')). The two are kept aligned by hand. (#37023 typed the field names as a closed union so a typo is now a compile error, but the structural coupling remains — and a bug where the header and body disagreed about extra-column placement was found and fixed during that review.) - Selection has four moving parts — the
selectioninput, an internal signal, a computed, and a read-onlyselectedItemsgetter — plus an effect that reaches into PrimeNG internals (table.selection,updateSelectionKeys(),tableService.onSelectionChange()) because PrimeNG's table assumes[(selection)]two-way binding and swallows the first one-way value after a checkbox toggle.
Proposed shape — split into two layers
Layer 1 — DotContentTableComponent (presentational core), in libs/ui.
Columns, rows, checkbox selection (controlled and uncontrolled), row markers, client-side pagination. Pure inputs and outputs; injects nothing. This is what the action preview consumes.
Layer 2 — DotFolderListViewComponent (browsing container), staying in libs/portlets/dot-content-drive/ui.
Wraps layer 1 and adds the behaviours that assume a browsing grid: drag and drop, context menu, kebab, open-on-click, server-lazy paging, sorting, and the service dependencies (DotLanguagesService for languagesMap, dragOverRowId state).
What that buys:
readOnlydisappears. The preview doesn't get drag, context menu or navigation because those live in layer 2 — not because a flag suppressed them. The three guard clauses go with it.lazy/$offset/onFirstChangemove to layer 2, where "there might be more on the server" is actually true. Layer 1 always knows its full row count.- The preview stops booting the grid's dependency graph. Its spec currently has to provide
DotLanguagesService,DotcmsConfigServiceandDotFormatDateServicejust to render three columns. - Controlled/uncontrolled selection lives in exactly one place, with one owner, including the PrimeNG resync.
Design decision to resolve first
libs/ui is for domain-agnostic components (see core-web/CLAUDE.md → Code Placement Rules), but the grid's cells are not domain-agnostic today: the folder icon and isFolder branch, dot-content-thumbnail, dot-contentlet-status-badge, and the locale tag all know about DotContentDriveItem.
So layer 1 cannot simply be the current body markup relocated. Whoever picks this up should decide, and record, how the cells are supplied — most likely caller-provided cell templates per column (ng-template + TemplateRef), leaving layer 1 responsible for structure, selection and pagination only, with Content Drive's cells staying in layer 2.
This is the main risk in the ticket: done carelessly, libs/ui gains a component that is generic in name and Content-Drive-specific in fact. If genericising proves disproportionate, keeping layer 1 in libs/portlets/dot-content-drive/ui and promoting it later, when a third consumer appears, is a legitimate outcome — say so on the ticket rather than forcing the move.
Acceptance Criteria
- A presentational
DotContentTableComponentexists, owning columns, rows, checkbox selection (controlled and uncontrolled), row markers and client-side pagination - It injects no services and depends on no Content Drive model — cell rendering is supplied by the caller, or the decision to keep it in the Content Drive ui lib is recorded on this ticket with its rationale
-
DotFolderListViewComponentis a container over it, keeping drag and drop, context menu, kebab, open-on-click, server-lazy paging and sorting - The
readOnlyinput is gone, along with thereadOnlyguards inonContextMenuandonDoubleClick -
lazy,$offsetandonFirstChangelive only in the container; the presentational layer has no concept of a server cursor - The PrimeNG selection resync (
table.selection/updateSelectionKeys/tableService.onSelectionChange) lives in the presentational layer alone, or is unnecessary once selection has a single owner - The action preview renders identically — same columns, same lock markers, same checkbox behaviour — and its spec no longer provides
DotLanguagesService,DotcmsConfigServiceorDotFormatDateService - The main Content Drive grid is unchanged in behaviour:
dot-folder-list-view.component.spec.tspasses without assertions being weakened, and the portlet's specs pass untouched - Selection tests carried over from #37023 still hold on the new layer — a caller-provided selection survives an
itemschange, a declined change leaves the row checked, and a normalised set is what renders - No user-visible change
Priority
Low
Additional Context
Pure refactor — no functional or user-visible change. Raised in the architecture review of PR #37023, which implemented issue #36975 and where the flag pile was accepted as a deliberate interim state rather than the destination.
The reviewer's summary of the tension: "Each guard is individually reasonable; collectively they're the component apologising for doing two jobs."
Worth doing before another caller binds to the flag surface — each new consumer makes the split more expensive. Not urgent: the current inputs are documented, tested, and default to the grid's original behaviour.
Related: #36975, PR #37023.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with libs/portlets/dot-content-drive/ui/DotFolderListViewComponent and its dot-folder-list-view.component.spec.ts, then read core-web/CLAUDE.md and the selection tests from #37023. Resolve how caller-provided cell templates keep libs/ui domain-agnostic, or record why the component stays in the Content Drive UI library. Done means both layers preserve the listed selection, preview, and grid behaviors without user-visible changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- angular, typescript
- Domain
- frontend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100