Overview

Timeline / Gantt

Business archetype for metadata-driven operational planning on a time axis (gantt): tasks as bars, milestones, dependencies, drag/resize with persistence. Rendering based on frappe-gantt (SVG, lazy-loaded).

Use cases

  • Job/activity planning with start/end dates.
  • Project roadmaps with milestones and task dependencies.
  • Quick rescheduling via drag (move) and resize (extend/shorten) of bars.

md_props_bag: archetypes.timeline

The archetype is activated with timeline (accepted aliases: gantt, gantt-list, timeline-list).

Snippet 1JSON
{
  "archetypes": {
    "timeline": {
      "advancedFilter": false,
      "startDateField": "StartDate",
      "endDateField": "EndDate",
      "titleField": "Title",
  • advancedFilter: when true shows the wuic-filter-bar for the timeline archetype (at data-repeater level).
  • startDateField (required): task start date field. If missing, the component shows a configuration warning and does not crash.
  • endDateField: end date field. If the value on the record is empty, the task is rendered as a milestone (1-day bar).
  • titleField: text field used as the bar label (fallback: first text column with name/title/descri in its name).
  • progressField: numeric 0-100 field for progress (clamped; optional).
  • dependencyField: field holding the predecessors. Two forms are accepted (see "Predecessors: csv or multiselect" below): a text column with a csv of ids ("3, 7"), or a multiple_check column (the framework multiselect). v1: dependencies are rendered as standard finish→start arrows (FS); if a task starts before its predecessor ends a (non-blocking) warning is shown.
  • milestoneField: boolean field that marks the record as a milestone (diamond, not resizable).
  • groupByField: optional grouping. v1: sorts tasks by group and prefixes the title with [group] (frappe-gantt has no group lanes). It can point to a text column or to a lookupByID — see "Grouping: text or lookup".
  • persistMode (enum):

- immediate (default): every drag/resize persists immediately server-side (visual rollback on error);

- batch: accumulates changes and saves/discards them in bulk from the toolbar.

  • timeScale (enum): day, week (default), month — initial zoom, changeable from the toolbar.
  • showTodayLine: highlights the current day (default true).
  • clickAction (enum): none (default), detail, edit — navigation on bar click.

Predecessors: csv or multiselect

dependencyField accepts two configurations, and the component detects which one is in use:

1. Text column with a csv of ids ("3, 7"): minimal setup, no extra table. Users type ids by hand, so there is no validation or autocompletion.

2. `multiple_check` column — the framework multiselect (see Many To Many Widget): the recommended form, because in edit users pick predecessors from a list of tasks instead of typing ids.

The second one needs the standard many-to-many widget setup, with one twist: the relation is self-referencing, since the predecessors of a task are tasks themselves.

  • a bridge table with two FKs to the tasks table (e.g. wuic_timeline_task_deps with FK_Task and FK_Predecessor);
  • on the main route, a virtual column (empty mc_real_column_name, mc_is_computed = 1, voa_class = 4) of type multiple_check, whose mc_ui_grid_route points back to the route itself and whose mc_ui_grid_manytomany_* fields map the two bridge FKs;
  • on the bridge table, both FKs configured as lookupByID towards the tasks route (voa_class = 2).

That last point is the easiest to get wrong: scaffolding creates the integer FKs as number, but when the backend composes many-to-many values it casts the related column to a lookup and reads the entity it points to. If the FKs stay number, every read of the parent route fails with a NullReferenceException in ParseGridColumns.

On read, the framework exposes the column as a collection of related records: the component extracts each predecessor id from the field declared in mc_ui_grid_related_id_field, falling back to the route primary key.

The app script scripts/timeline-sample/scaffold-timeline-sample.ps1 applies exactly this schema on mssql, mysql, postgres and oracle and is a practical, reusable reference.

Grouping: text or lookup

Like predecessors, groupByField accepts two configurations and the component detects which one is in use:

1. Text column: no extra table, but the group is typed by hand — a typo creates a brand new group, which shows up as a separate row and, if its name sorts before the others, ends up at the top of the chart.

2. `lookupByID` column pointing at a dedicated lookup table (e.g. wuic_timeline_project): the recommended form, because groups become a closed set picked from a list.

With a lookup the raw column value is the id, not the name: rendering it would give [3] Title. The component resolves the label through MetadatiColonna.formatGridViewValue, the same resolver the grid uses, which knows the denormalized alias emitted by the server (<route>___<textField>__<column>) and falls back to __lookup_obj when the record hasn't round-tripped yet — so the label is correct even right after an insert.

Ordering. Rows are sorted by group and, within a group, by start date: the order is not topological, so a task can appear above its own predecessor when it belongs to a group that sorts before it.

Data rendering rules

  • Record without a valid startDateField → excluded from the gantt, counted in the "excluded rows" warning.
  • Empty endDateField → implicit milestone (end = start).
  • end < start (in the data or after a drag) → clamped to start + warning.
  • Persistence uses the datasource current-record flow (syncData with pristine) → the __changes payload contains only the modified dates and is compatible with optimistic concurrency and audit.

Bar popup and record actions

Clicking a bar opens the popup with title, date range and progress. The actions below the text follow the permissions declared in the table metadata, so a read-only route shows a purely informational popup:

ActionConditionBehaviour
Editmd_editableOpens the record in the parametric-dialog, the same form as the list-grid edit.
Newmd_insertableOpens the insert dialog with the clicked task already preselected among the new task's predecessors.
Deletemd_deletable and leaf taskAsks for confirmation and deletes the record.

On save-and-close (or after a deletion) the datasource reloads and the gantt redraws.

Why only leaves can be deleted. A task is a leaf when no other task lists it among its predecessors. Deleting a task others depend on would leave orphan dependencies — arrows pointing at an id that no longer exists — so on those tasks the button doesn't appear at all. In the Requirements analysis → Architecture design example, the button shows on Architecture design but not on Requirements analysis.

Predecessor preselection. With dependencyField on a text column the id is written straight into the csv. With the multiselect both the id list and the related-records collection are filled in, marking the entry as added: that's the same contract the lookup-editor produces when the user picks by hand, and it's what the backend uses to write into the bridge table.

The dialog is loaded with a dynamic import on first click, so the archetype chunk doesn't carry the field editors.

Events and subscriptions (host)

Events available on wuic-timeline-list:

  • onTimelineDataBound: emitted when the gantt is rebuilt from the datasource ({ metaInfo, tasks, skipped }).
  • onTimelineTaskChange: emitted on drag/resize ({ task, start, end, mode }).
  • onTimelineTaskClick: emitted on bar click ({ task }).
  • onTimelineBatchSave: emitted after batch save ({ savedCount }).
  • onTimelineBatchCancel: emitted after batch discard ({ revertedCount }).

<!-- import-and-mount -->

Import and mount (Angular component)

Import LazyTimelineListComponent from 'wuic-framework-lib' and mount it with the framework DataSource:

Snippet 2ts
import { DataSourceComponent, LazyTimelineListComponent } from 'wuic-framework-lib';

@Component({
  selector: 'app-esempio',
  imports: [DataSourceComponent, LazyTimelineListComponent],
  template: `
    <wuic-data-source #ds [hardcodedRoute]="'<route>'" [autoload]="true"></wuic-data-source>

EXACT names (wuic-framework-lib barrel): class LazyTimelineListComponent, selector <wuic-timeline-list-lazy>. The non-lazy TimelineListComponent is NOT exported from the barrel (it statically imports frappe-gantt): always use the lazy wrapper.

Metadata-driven config: the tag accepts ONLY the standard inputs [hardcodedRoute], [hardcodedDatasource], [hideToolbar]. The specific configuration (TimelineOptions: date fields, milestone, dependencies, etc.) is NOT an HTML input → it goes in the metadata (md_props_bag.archetypes.timeline).