dotCMS / dotCMS/core

Content Drive: split DotFolderListViewComponent into a presentational table and a browsing container

Open
#37,024 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS: Content Drive Team : Scout Type : Task
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:

  1. readOnly is 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.
  2. The header is data-driven; the body is hand-coded. $columns is computed from HEADER_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.)
  3. Selection has four moving parts — the selection input, an internal signal, a computed, and a read-only selectedItems getter — 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:

  • readOnly disappears. 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 / onFirstChange move 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, DotcmsConfigService and DotFormatDateService just 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 DotContentTableComponent exists, 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
  • DotFolderListViewComponent is a container over it, keeping drag and drop, context menu, kebab, open-on-click, server-lazy paging and sorting
  • The readOnly input is gone, along with the readOnly guards in onContextMenu and onDoubleClick
  • lazy, $offset and onFirstChange live 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, DotcmsConfigService or DotFormatDateService
  • The main Content Drive grid is unchanged in behaviour: dot-folder-list-view.component.spec.ts passes 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 items change, 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.