Skip to content

Convert "API Maker UI Maker" (JSON-config-driven grid + form) to hand-written Angular + PrimeNG

1. Title & Objective

You are an AI coding agent. Your task is to convert an API Maker "UI Maker" page — a single JSON configuration object (IDBMasterConfig, conventionally the variable dbMasterConfig) that drives a PrimeNG data grid plus add/edit/view reactive forms inside an iframe — into a normal, explicit, hand-written Angular + PrimeNG application: a p-table-based grid component and reactive FormGroup-based form components, where every feature currently expressed in JSON is implemented directly in Angular/PrimeNG TypeScript and templates.

Non-negotiable rule: ZERO FEATURE LOSS. Every form control, every property, every validation rule, every file-upload behavior, every dependent/cascading dropdown, every conditional show/hide, every dynamic-required rule, every nested field, every nested grid, every add-new-inline form, every API-call override, every CSV import/export behavior, every theme, every iframe/postMessage event, every row-level action-hiding flag, every custom action button, every grid grouping/sorting/pagination/selection/filter/search/column-selector behavior, and every operations-ordering rule must be reproduced exactly. Do not invent features that are not in the source config. Where the source config is ambiguous, inspect the original dbMasterConfig for that page and reproduce the observed runtime behavior.

Work page-by-page: for each UI Maker page you are given (identified by db-master-name), produce a dedicated Angular feature module/standalone component set. Read that page's dbMasterConfig in full first — it is the source of truth that overrides any general guidance below.


2. Background: What UI Maker is

UI Maker is an extension layered on top of API Maker. It takes one IDBMasterConfig object and renders, at runtime, a complete CRUD screen for a database table/collection:

  • A PrimeNG data grid (grid config) with columns, sorting, filtering, global search, pagination, grouping, row selection, row actions (edit/view/delete + custom), and a toolbar (add/import/export/refresh/column-selector/theme-selector/delete-all + custom actions).
  • Reactive add/edit/view forms (form.fields, a 2-D array: outer = rows, inner = columns) built from 24 control types (EDBMasterFormControl).
  • Each field is bound to a dot-notation path, supports static or JS-expression-string visible/disabled/requiredFun, can nest other fields (fields[][]) or a whole child grid (gridSettings).
  • Pages run inside an iframe and talk to the host via window.postMessage. They call API Maker auto-generated POST endpoints for all data operations, each overridable via apiCallOverrides.
  • Custom CSS (cssCode[{runOn:'header', code}]) and JS lifecycle hooks (jsCode[{appendTo, code}]) can be injected globally (page) or per control. Code can be inline strings, functions, or references to external named functions (userUtils.Main.* per-page, userUtils.GeneralUtils.* shared).
  • Inbuilt libraries available to custom code: Papa Parse (Papa), date-fns (dateFns), date-fns-tz (dateFnsTz); commonly added: lodash (_), moment. Inbuilt CSS: Bootstrap 5.3.3 grid + utilities.

UI Maker cannot run standalone — it is coupled to the API Maker backend that supplies data. Your conversion must therefore also implement an explicit Angular HttpClient service layer that calls the same backend endpoints.


3. Target architecture

Implement this structure (standalone Angular components, PrimeNG, ReactiveFormsModule):

src/app/
  ui-maker-shared/                  # shared, imported by EVERY converted page
    services/
      api.service.ts                # HttpClient layer; replaces all API Maker calls + apiCallOverrides
      query.service.ts              # builds API Maker IQueryFormat (find/select/limit/deep/sort) bodies
      parent-integration.service.ts # postMessage send/receive (iframe contract) OR @Input/@Output bridge
      expression.service.ts         # safe evaluator for visible/disabled/requiredFun JS-expression strings
      hook-runner.service.ts        # executes jsCode (string | function | userUtils.* ref) with $scope
      message.service.ts            # toast wrapper -> utils.messageService.*
      file-upload.service.ts        # upload/download/remove contract
      theme.service.ts              # theme switching
      date-format.service.ts        # date-fns <-> PrimeNG calendar format + tz conversion
    utils/
      general-utils.ts              # <= userUtils.GeneralUtils.* (shared common JS)
      form-path.util.ts             # dot-notation path <-> nested FormGroup helpers
    components/
      um-grid/                      # reusable generic p-table grid component (config-light, typed)
      um-form/                      # reusable form renderer OR base class for per-page forms
      controls/                     # one component per EDBMasterFormControl (24)
  pages/
    <db-master-name>/               # ONE per UI Maker page
      <name>.component.ts/.html/.scss   # page component: grid + form wiring, page jsCode hooks, page cssCode
      <name>.main.ts                # <= userUtils.Main.* (page-specific JS functions)
      <name>.api.ts                 # page-specific endpoints / overrides
      <name>.model.ts               # typed row + form model interfaces
  styles.scss                       # global: Bootstrap grid+utilities + shared cssCode (common CSS)

Recommended approach — per-page hand-written components backed by reusable building blocks. Build a small library of reusable, typed primitives (a generic <um-grid>, the 24 control components, the shared services), then hand-write one explicit component per UI Maker page that composes them with the page's exact behavior. Rationale: a fully generic runtime engine would just re-implement UI Maker; explicit per-page components are debuggable, type-safe, tree-shakeable, and let you delete dead config. Reuse the primitives so you do not re-implement PrimeNG wiring 24 times.

Key mappings:

  • Reactive forms: each form is a FormGroup. Dot-notation path (a.b.c) maps to nested FormGroups (form.get('a.b.c')). Provide form-path.util.ts to build the nested group from all field paths and to get/set by path.
  • 2-D fields[][] layout: outer array = a <div class="row">; inner array = the columns inside that row. Each field wrapper gets cssClassDiv classes (default col-lg mt-4 col-md-{calculated based on columns.length} — compute the col-md-* span from the number of columns in that inner array). Bootstrap 5.3.3 grid is the layout system.
  • API layer replaces API Maker auto-generated calls and apiCallOverrides (see §9).
  • Parent integration (parent-integration.service.ts) reproduces the iframe postMessage contract (see §12). If the converted app is no longer embedded as an iframe, replace inbound parent events with component @Input()s / route/query params and outbound events with @Output() EventEmitters — but preserve the same semantics and payloads.

4. Global vs Page-specific CSS & JS (READ CAREFULLY — explicit requirement)

UI Maker has two tiers of custom CSS and JS: common/shared (used across many or all pages) and page-specific. You must split them correctly.

4.1 CSS

Source (UI Maker) What it is Target (Angular)
Common/global CSS used across many pages shared style overrides Put into the global styles.scss (or a shared _um-shared.scss imported there). Loaded once, applies everywhere.
Page-specific CSS: config.cssCode: [{ runOn: 'header', code: '<raw CSS>' }] raw CSS injected into <head> for that page Put into that page component's stylesheet (<name>.scss). Note runOn is always 'header'. If a class must override already-applied global/theme classes (UI Maker allows overriding existing classes), keep the same selectors and use :host ::ng-deep or move it to global scope as needed to win specificity.
Inbuilt Bootstrap 5.3.3 grid + utilities col-*, mt-*, row, etc. used by cssClassDiv Ensure Bootstrap grid + utilities CSS is loaded globally so cssClassDiv classes keep working.

Mapping example:

// BEFORE (UI Maker page config)
cssCode: [{ runOn: 'header', code: `.font-orange { color: orange; }` }]
/* AFTER — <name>.scss (page-specific) */
.font-orange { color: orange; }

4.2 JS

UI Maker JS comes in three forms for the code value: an inline JS string, a function ($scope) => any, or a reference to a named external function (userUtils.Main.fnName or userUtils.GeneralUtils.fnName). Code may mutate the provided data in place, return a Promise (await it), or return a function (invoke it).

Source (UI Maker) What it is Target (Angular)
Common/global JS: userUtils.GeneralUtils.<fn> shared utilities callable from any page via code: 'userUtils.GeneralUtils.fn' Implement as methods/exports in ui-maker-shared/utils/general-utils.ts, imported by all pages.
Page-specific JS (named): userUtils.Main.<fn> per-page functions (e.g. userUtils.Main.first_name_blur) referenced from config Implement as methods on that page's <name>.main.ts (or the page component).
Page-level lifecycle hooks: top-level config.jsCode: [{ appendTo: EDBMasterConfigAppendTo, code }] run at named page lifecycle points Implement as page component methods/lifecycle hooks (see table below).
Per-control hooks: <controlSettings>.jsCode: [{ appendTo, code }] run at named control events Implement as the control component's event handlers (see §7).

Top-level page lifecycle hooks — EDBMasterConfigAppendTo — and their available variables. Translate each to the natural Angular equivalent:

appendTo Available variables in scope Angular equivalent
oncePageLoad (NONE — no config, no globalData) run once early in ngOnInit before context is ready
oncePageLoadWithContext config, globalData, utils, queryParams run once in ngOnInit after config/parent context resolved
gridRender gridEvent, gridData, globalData, utils, queryParams hook that runs every time grid data loads; may mutate gridData rows; may return a Promise (await before render)
modifyGridRequest gridEvent, reqBody (IQueryFormat), globalData, utils, queryParams; also moment, _ available mutate the find/sort/select request body before the grid data fetch
onceGridDataLoaded (grid data context) run after grid data first loads
preSaveAndEdit reqBody, globalData, utils, queryParams, mode ('save' \| 'edit' \| 'import') run before save/update/import; branch on mode; may delete fields from reqBody
beforeSaveModalOpen formData, utils (form-introspection) mutate formData before the Add dialog opens (e.g. formData.remarkDate = new Date())
beforeEditModalOpen formData, utils mutate formData before the Edit dialog opens
beforeViewModalOpen formData, utils mutate formData before the View dialog opens
columnSelectionChanged selected columns react to column-selector change
deleteConfirmationDialogGenerator / deleteAllConfirmationDialogGenerator / editConfirmationDialogGenerator / saveConfirmationDialogGenerator the relevant item(s) produce the confirmation message/dialog content
processDataBeforeExport export rows transform rows before CSV export
processDataBeforePDFExport export rows (mutable copy) + jsPdfDoc (pre-created jsPDF instance, replaceable) + globalData, utils, queryParams transform rows / build the PDF before PDF export (see §10.2)

utils helpers exposed to hooks/scripts — re-implement on a utils object passed into every hook:

  • utils.findFormElement(pathOrHiddenId) — returns the field config by its path or its hiddenId.
  • utils.getFlatArrayOfAllFormFields() — flat array of all form fields excluding grid fields.
  • utils.getFlatArrayOfAllFormFieldsIncludingGridFields() — flat array including nested grid fields.
  • utils.messageService.showInfoToast(msg, opts) / showSuccessToast(msg, {life}) / showErrorToast(...) — toast notifications.
  • utils.errorsMap[path] = 'message' — set a validation error on another field by path (reflect onto that reactive control's errors).
  • window.top.postMessage({ eventType, eventData }, '*') — send a custom event to the parent (see §12).

globalData is the object pushed by the parent via SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT; pass it into every hook scope. queryParams are the URL query params.


5. Top-level grid configuration → PrimeNG p-table

Build a reusable <um-grid> around p-table. Map every key below. Defaults are stated; apply them when the key is absent.

5.1 Grid-level keys

UI Maker key Type / default PrimeNG / Angular equivalent Notes
grid.header string grid title/caption header
grid.minWidth default '50rem' [style]="{'min-width': minWidth}" on <p-table>
grid.breakpoint default '40rem' [breakpoint] (responsive)
grid.currentPageReportTemplate e.g. 'Showing {first} to {last} of {totalRecords} entries' [currentPageReportTemplate] + paginator report
grid.showColumnSelector default false p-multiSelect of columns toggling visible columns
grid.dataFieldPath string nested grid only: pick the data array from this field path not for top-level grid
grid.selectFieldsForQuery projection {field:1\|-1} (default all DB fields) API Maker query select; pass into the data-fetch request not for nested grids
grid.sortMode 'single'\|'multiple', default 'single' [sortMode] 'multiple' requires the meta key when adding columns
grid.multiSortMeta [{field, order:1\|-1}] [multiSortMeta] each field must have enableSorting

5.2 Columns — grid.fields[] (IDBMasterConfigGridField)

If grid.fields is empty/omitted: auto-generate columns from the schema in schema order, EXCLUDING the Mongo _id column.

Per-column key Equivalent Notes
header <th> text
path cell value path; supports dotted (country.name) resolve nested
enableSorting pSortableColumn="path" + <p-sortIcon>
visibilityStatus EDBMasterConfigGridColumnVisibilityStatus.INVISIBLE \| VISIBLE initial column visibility (used with column selector)
autoFormatDate format the cell as a date default format dd-MM-yyyy hh:mm:ss a (date-fns); see §13
dateTimeFormat per-column date format override date-fns
cssClass CSS class on the cell/column
dbData (FK lookup) { collection, pkField, displayField } resolve the stored key (e.g. _id) to a display value via a lookup; in nested-grid columns use dataSource:'db_data' + dbData:{pkField, displayField}
dataSource 'db_data' in nested-grid columns resolve FK display

5.3 Pagination — grid.pagination

Key Default Equivalent
showPagination false [paginator]
serverSidePagination false [lazy]="true" + (onLazyLoad) server fetch; NOT supported for nested grids
rowsPerPage 10 [rows]
rowsPerPageOptions [10,20,50,100,500,1000] [rowsPerPageOptions]

5.4 Selection — grid.selection

Key Default Equivalent
mode 'multiple' single → selectionMode="single"; multiple → "multiple"
dataKey auto from schema (PK) [dataKey]
metaKeySelection true [metaKeySelection] (true = needs Ctrl/Cmd)
checkboxSelection false (multiple only) <p-tableHeaderCheckbox> + <p-tableCheckbox> column

5.5 Filter / search — grid.filter

Key Default Equivalent
showGlobalSearch false global filter input
globalSearchFields string[] (dotted paths ok) string → contains, number → exact match; pass to [globalFilterFields] and/or server query
rowFilter false per-column row filter — see §5.5.1

Global search (the toolbar single-box search) is detailed in §5.10.1. Per-column row filtering is detailed below in §5.5.1.

5.5.1 Row filter — a filter for EVERY column (grid.filter.rowFilter)

When grid.filter.rowFilter is true, render a filter control under (or in) every data column's header — i.e. a filter row across the whole grid — so the user can filter each column independently. This is in addition to, and independent of, the global search box.

Implement with PrimeNG <p-columnFilter> per column (or a dedicated filter row via <ng-template pTemplate="filter">), one for every grid column in grid.fields[]: - Choose the filter input/match modes by the column's data type (the same calculated filterType UI Maker derives per column — EDBMasterConfigFilterType): - String/text → text input; match modes contains (default), startsWith, endsWith, equals, notEquals. - Number → numeric input; match modes equals (default), notEquals, lt, lte, gt, gte. - Date → date picker; date-comparison match modes (dateIs, before, after, range) honoring the column's dateTimeFormat/dateTimeZone (§13). - Boolean → tri-state checkbox / true-false dropdown. - FK / dbData lookup columns → filter on the resolved display value (or a dropdown of the related options) — match the original behavior. - Support dotted paths (e.g. country.name) as filter fields, same as the grid columns. - Combine multiple active column filters with AND (a row must satisfy all active per-column filters), and combine with the global search where both are active. - Client-side grid → use <p-columnFilter>'s built-in filtering against [value]. - Server-side grid (serverSidePagination/[lazy]) → translate the active column filters into the API Maker query find (per-field conditions by match mode) on each (onLazyLoad) and re-fetch; run the modifyGridRequest hook so custom code can adjust the filter query. - Provide clear/reset affordances per column (and respect any column that should be excluded from filtering, e.g. the action column and checkbox-selection column).

Do not skip any column: if rowFilter is enabled, every displayable data column must have its own filter. The action and selection columns are not filterable.

5.6 Grouping — grid.grouping (rowGroupMode 'subheader' only)

groupRowsBy[rowGroupMode]="'subheader'" + [groupRowsBy]; headerStyle/headerCssClass (group header <tr>); footerStyle/footerCssClass; footerColumnPath (field used to render the group footer). rowspan grouping is not supported.

5.7 Row operations — grid.operations

Key Default Equivalent / behavior
delete { enable, cssClass, apiCallOverrides, apiCallOverridesDeleteMany } row delete button (icon styled by cssClass, e.g. text-danger)
edit { enable, cssClass, apiCallOverrides, apiCallOverridesGetById } row edit button (e.g. text-info); edit fetches single record via apiCallOverridesGetById
hideDeleteConfirmation / hideEditConfirmation suppress per-row confirm dialog
hideDeleteManySuccessMessage false suppress bulk-delete success toast (then you must show it manually)
disableDoubleClickForEdit (default: double-click row opens edit) when set, disable double-click-to-edit
disableDeleteButtonClickForDelete (default: delete button deletes) when set, delete button does not delete
gridOperationsOrder EDBMasterGridOperationsOrder[] = delete \| edit \| custom (only these 3) order of the row action buttons
gridColumnsOrder EDBMasterGridColumnsOrder[] = checkbox \| dbFields \| actions (only these 3) order of column groups
actionColumnWidth e.g. '120px' width of the actions column
customActionButtons[] { actionName, cssClass (icon e.g. 'pi pi-check'), style (e.g. {color:'yellow'}), confirmationMessageScript } per-row custom button. confirmationMessageScript is a JS template-literal string evaluated with selectedGridItems in scope, e.g. "`Do you want to approve ${selectedGridItems.name}?`"

Per-row action hiding (magic flags on the ROW DATA object) — when rendering each row's buttons, honor: ____hide_delete_button, ____hide_edit_button, ____hide_view_button, and ____hide_<actionName> (custom) — if true on the row object, hide that button for that row only.

5.8 Toolbar operations — top-level operations

Key Default Equivalent
operations.add IDBMasterConfigOperation { enable, label, apiCallOverrides } Add button → opens add form dialog; title 'Add ${screenName}'
operations.hideAddConfirmation suppress add confirm
operations.hideDeleteAllButton false hide Delete-All button
operations.hideDeleteAllConfirmation suppress delete-all confirm
operations.showRefreshButton Refresh button (reload grid)
operations.exportData see §10 Export button
operations.importData see §10 Import button
operations.customActionButtons[] IDBMasterTopLevelCustomActionButton[] toolbar custom buttons (distinct from row ones)
operations.operationsOrder EDBMasterOperationsOrder[] order toolbar items; default order: theme_selector, column_selector, global_search, import, export, refresh, add, custom_actions, delete_all

5.9 Grid data fetch override

grid.apiCallOverrides (IDBMasterAPICallOverrides) overrides the get-grid-data API (see §9). Note: in the grid (non-form) context, formData and allDropdownDataMap are not available to the override hooks.

IDBMasterConfigGridField, IDBMasterConfigOperation, IDBMasterCustomActionButton, and IDBMasterTopLevelCustomActionButton are not given standalone interface blocks in the docs — infer their exact shapes from the page's actual config and the worked examples here.

5.10 Global search & column selector (toolbar features — implement both fully)

These are two toolbar features and must each be reproduced as a first-class control in the grid header, positioned according to operations.operationsOrder (defaults: column_selector then global_search, near the front). Both can also be hidden/reordered like any other toolbar op.

Config Type / default Behavior to reproduce
grid.filter.showGlobalSearch boolean, default false When true, render a single search input in the toolbar that filters the grid across multiple fields at once. When false, render no global search box.
grid.filter.globalSearchFields string[] The exact set of fields the search applies to. Supports dotted paths (e.g. 'country.name'). Only string and number fields are searchable.

Search semantics — match the original exactly: - String fieldscontains match (case-insensitive substring). - Number fieldsexact match (equals the parsed number). - The single search term is applied across all globalSearchFields with OR semantics (a row matches if any listed field matches). - Client-side grid (serverSidePagination false): bind [globalFilterFields]="globalSearchFields" on <p-table> and call dt.filterGlobal(term, 'contains'); because PrimeNG's single global mode is contains-only, additionally enforce the number-exact rule yourself (custom filter/predicate) so numeric columns are not matched as substrings. - Server-side grid (serverSidePagination true / [lazy]): on each keystroke (debounced), build the API Maker query find with an $or of per-field conditions — string fields as contains/regex, number fields as exact equality — over globalSearchFields, and re-fetch via the data service. Run any modifyGridRequest hook so custom code can adjust the search query. - Debounce input (e.g. 300 ms); show a clear (×) affordance that resets the search and reloads the grid.

5.10.2 Column selector

Config Type / default Behavior to reproduce
grid.showColumnSelector boolean, default false When true, render a column-selector control in the toolbar (a p-multiSelect of the grid columns, or equivalent overlay of checkboxes) that lets the user toggle which columns are visible.
per-column visibilityStatus EDBMasterConfigGridColumnVisibilityStatus.VISIBLE \| INVISIBLE Sets each column's initial checked/visible state in the selector. Columns marked INVISIBLE start hidden but remain available to toggle on. Absent → visible by default.

Implementation: - Build the selector's option list from grid.fields[] (label = column header, value = the column). Seed the selected/visible set from each column's visibilityStatus (everything except INVISIBLE). - Bind the table body/header column rendering to the currently-selected columns so toggling immediately shows/hides columns. Keep the action column and the checkbox-selection column out of (or fixed in) the selector as the original does — they are governed by gridColumnsOrder, not the column selector. - Position the selector per operations.operationsOrder (default places column_selector first). Hiding it entirely = showColumnSelector: false (or omit it from operationsOrder). - When the selection changes, fire the page-level columnSelectionChanged lifecycle hook (EDBMasterConfigAppendTo.columnSelectionChanged, see §4.2) with the selected columns so custom code can react. - Optional: persist the chosen columns for the page session if the original does (otherwise reset to visibilityStatus defaults on reload).


6. Form framework → Angular Reactive Forms

6.1 Layout

form.fields is a 2-D array (outer = rows, inner = columns). A field that itself has a nested fields (also 2-D) and no control is a pure layout container — render its children but bind no value for it. form.width sets the dialog (p-dialog) width. The form runs in three modes: add, edit, view (view = read-only).

6.2 Shared base field props (IDBMasterConfigFormField) — present on EVERY control

Prop Type / default Handling
hiddenId string element id for programmatic access (utils.findFormElement)
label string field label (may be ' ' to reserve space for alignment)
helpText HTML string render below the control via [innerHTML] (sanitized)
path dotted string the nested FormControl key where the value is stored
control EDBMasterFormControl which control component to render
cssClassDiv default col-lg mt-4 col-md-{calc by columns.length} wrapper div classes
autofocus default false focus on form open
disabled boolean \| JS-expression string static OR evaluated expression (see §6.3)
visible boolean \| JS-expression string, default true static OR evaluated expression
fields IDBMasterConfigFormField[][] nested 2-D layout
validations Pick<IPropertyValidation,'required'> & { requiredFun?: string } required (boolean) + requiredFun (dynamic expression)
validationErrors.required string custom required error message

Build the FormGroup from all field paths (nested groups for dotted paths). Add/remove Validators.required per validations.required. Set [autofocus]. Render helpText HTML below each control. Container fields and value-less controls (button, image, customHTML, divider, accordion, tab_view) must NOT be registered as value-bearing FormControls unless they actually carry a path.

6.3 JS-expression-string evaluation (visible / disabled / requiredFun)

visible, disabled, and requiredFun may each be a boolean OR a JavaScript expression string evaluated against form data, e.g. "formData.type === 'readonly'", "formData.userRole === 'admin'", "formData.type === 'individual' ? true : false".

  • Implement a single expression.service.ts evaluator. Build the expression scope: { formData, queryParams, globalData, utils, column, config }.
  • Recommended safe approach: a constrained new Function('formData','queryParams','globalData','utils','column','config', 'return (' + expr + ');') invoked with those args, wrapped in try/catch. Do not use eval. (If your security posture forbids new Function, swap in a sandbox like a small expression interpreter, but the string contract must be honored.)
  • Re-evaluate reactively on form.valueChanges (and on globalData changes). Bind visible*ngIf / [hidden]; disabledcontrol.disable()/enable() (and [disabled] for non-control elements).
  • requiredFun takes precedence over static required when both are present: when requiredFun exists, ignore static required and dynamically add/remove Validators.required based on the evaluated result on every value change.
  • The full IPropertyValidation set exists at schema level (required, min, max, minLength, maxLength, unique [deprecated], email, validatorFun, enum). Most controls only expose required/requiredFun via validations, but control-specific validators appear in settings (e.g. inputTextSettings.minLength, inputNumberSettings.min/max/minLength, editorSettings.maxLength). Map each to the matching Angular Validators.*; map a custom validatorFun to a custom validator; map enum to a membership validator.

7. Per-control conversion reference (THE BIG SECTION — all 24 controls)

For every control: render the listed PrimeNG component bound to a FormControl at path (support dotted paths); honor the shared base props (§6.2); apply style as [ngStyle], cssClass as styleClass, tooltip/tooltipPosition/tooltipStyleClass via pTooltip; wire the control's jsCode hooks to the matching PrimeNG outputs. The jsCode $scope (IDBMasterUIPageUtilsScope) for input-like controls is { formData, column, allDropdownDataMap, globalData, utils, queryParams, config, event } (data-source controls additionally get reqBody, dropdownData, reloadDropdownsOfPath). Code may be a string, a function, or a userUtils.* reference, may mutate data, return a Promise (await), or return a function (invoke).

7.1 input — p-inputText (pInputText on <input>)

  • Settings (inputTextSettings): style, placeholder, autocomplete (default off), spellcheck (default false), maxLength (→ maxlength attr + Validators.maxLength), minLength (→ Validators.minLength), tooltip/tooltipPosition/tooltipStyleClass, jsCode, validationErrors.minLength (custom minLength message).
  • Validation: required/requiredFun; minLength (custom msg via inputTextSettings.validationErrors.minLength); maxLength.
  • Events (EDBMasterInputTextAppendTo): visible, disabled, ngModelChange, focus, blur, keyUp, keyDown.

7.2 textarea — pInputTextarea on <textarea>

  • Settings (textAreaSettings): style, placeholder, rows, autoResize (default false), maxLength, tooltip*, jsCode.
  • Validation: required/requiredFun; maxLength. (No minLength/email/pattern exposed.)
  • Events (EDBMasterTextAreaAppendTo): visible, disabled, ngModelChange, focus, blur, keyUp, keyDown.

7.3 password — p-password

  • Settings (inputPasswordSettings): style, cssClass, placeholder, maxLength, tooltip*, jsCode. (toggleMask, feedback, prompt/strength labels referenced in examples — map to p-password [toggleMask]/[feedback]/labels.)
  • Validation: required/requiredFun; maxLength.
  • Events (EDBMasterInputPasswordAppendTo): visible, disabled, ngModelChange, focus, blur, keyUp, keyDown.

7.4 inputNumber — p-inputNumber

  • Settings (inputNumberSettings): style, cssClass, placeholder, min, max, minFractionDigits, mode ('decimal'\|'currency'\|''), currency (ISO 4217 — required when mode='currency', no default), locale (default 'en-US'), prefix, suffix, showButtons, buttonLayout (default 'stacked'; also horizontal/vertical), step, useGrouping, maxLength, minLength, tooltip*, jsCode, validationErrors.minLength.
  • Validation: required/requiredFun; min/max (PrimeNG constraint); minLength/maxLength; minFractionDigits.
  • Events (EDBMasterInputNumberAppendTo): visible, disabled, ngModelChange, focus, blur, keyUp, keyDown.

7.5 inputMask — p-inputMask

  • Settings (inputMaskSettings): style, cssClass, mask (a=alpha, 9=numeric, *=alphanumeric, ?=optional remainder, literals ( ) - / . , :; e.g. '(999) 999-9999', '99/99/9999', '99:99 aa'), placeholder, slotChar (default _), autoClear (default true — clears incomplete value on blur), maxLength, tooltip*, jsCode.
  • Validation: required/requiredFun; maxLength; mask enforces format/length; autoClear enforces completeness.
  • Events (EDBMasterInputMaskAppendTo): visible, disabled, ngModelChange, focus, blur, complete, keyUp, keyDown.
  • GOTCHA: modifying the model value inside the blur hook does NOT work (PrimeNG bug) — route model-mutating logic to the complete hook.

7.6 inputOtp — p-inputOtp

  • Settings (inputOtpSettings): mask (boolean — obscure values, password-style), integerOnly (numeric only), uiControlWidth (default 300px), length (number of boxes / OTP length), tooltip*.
  • Validation: required/requiredFun; integerOnly; length (fixed OTP length).
  • Events: none control-specific; only evaluation-based visible/disabled/requiredFun. Auto-focus between boxes is built-in.

7.7 date_picker — p-calendar (Calendar / DatePicker)

  • Value is always a native JS Date stored at path (write back a Date, not ISO string).
  • Settings (datePickerSettings): style, cssClass, placeholder, showSeconds, showTime, hourFormat ('12'\|'24', default '24'), dateTimeFormat, timeOnly, minDate (Date), maxDate (Date), view ('date'\|'month'\|'year', default 'date'), tooltip*, jsCode.
  • Mapping: showTime[showTime], showSeconds[showSeconds], hourFormat[hourFormat], timeOnly[timeOnly], minDate/maxDate[minDate]/[maxDate], view[view], dateTimeFormat[dateFormat] (convert date-fns tokens to PrimeNG calendar tokens, e.g. dd-MM-yyyydd-mm-yy; see §13).
  • GOTCHA: for view='month' set dateTimeFormat='MM-yyyy'; for view='year' set 'yyyy'. minDate/maxDate are selection constraints only — add equivalent validators if true validation is needed.
  • Validation: required/requiredFun.
  • Events (EDBMasterDatePickerAppendTo): visible, disabled, ngModelChange (on onSelect/value change), focus, blur.

7.8 checkbox — p-checkbox (binary)

  • Bind a boolean FormControl; [binary]="true".
  • Settings (checkboxSettings): tooltip*, jsCode (onChange).
  • Validation: required/requiredFun.
  • Events (EDBMasterCheckboxAppendTo): ngModelChange (a.k.a. onChange → (onChange)), visible, disabled.

7.9 radio — p-radioButton (one per option, single FormControl)

  • Settings (radioSettings): displayType ('inline' = horizontal one line, 'new_line' = vertical), displayInCenter (center the group), options[] (REQUIRED): { label, value } (value any type: string/boolean/number — selected option's value is stored), tooltip*, jsCode.
  • Validation: required/requiredFun.
  • Events (EDBMasterRadioAppendTo): ngModelChange, visible, disabled.

7.10 dropdown — p-dropdown (data-source control — see §8)

  • Settings (dropdownSettings): style, cssClass, placeholder, showClear (default false), dataSource (REQUIRED: 'static_data'\|'db_data'\|'api_call'), staticData[], dbData (Partial<{instance,database,collection,table}> & {find,select,limit,deep,sort}select may be CSV string OR object map {field:1}; identity may be inherited from schema), optionLabel (default 'label', HTML supported → use item template with [innerHTML]), optionValue (default 'value', stored value), filter (default false), filterBy (comma-separated, no spaces), filterMatchMode (default 'contains'; also startsWith\|endsWith\|equals\|notEquals\|in\|lt\|lte\|gt\|gte), alwaysGetLatestDataOnFormOpen (default false), virtualScroll (default false), reloadDropdownsOfPath[], isDependentOnPath[], tooltip*, jsCode, addNewFormConfig (IDBMasterConfig — inline add modal), apiCallOverrides.
  • Validation: required/requiredFun.
  • Events (EDBMasterDropdownAppendTo): visible, disabled, modifyDropdownRequest (mutate reqBody before fetch), onceDropdownDataLoaded (post-process dropdownData), onChange.

7.11 auto_complete — p-autoComplete (data-source control — see §8) (v1.11+)

  • Stores the selected option's optionLabel value (there is NO optionValue — it is commented out; the whole label-field value is stored).
  • Settings (autocompleteSettings): style, cssClass, placeholder, showClear, minLengthForSearch (→ minLength), delay (debounce ms), forceSelection (clears manual input not matching a suggestion), dataSource, staticData[], dbData, optionLabel (default 'label', HTML supported), filterBy, filterMatchMode (contains\|startsWith\|endsWith only), alwaysGetLatestDataOnFormOpen, virtualScroll, reloadDropdownsOfPath[], isDependentOnPath[], dropdown (show dropdown button), maxLength, tooltip*, addNewFormConfig, apiCallOverrides, jsCode.
  • completeMethod: fire only after minLengthForSearch chars, debounced by delay; for db_data/api_call honor isDependentOnPath (skip until all parent paths have values).
  • Validation: required/requiredFun; forceSelection; maxLength.
  • Events (EDBMasterAutoCompleteAppendTo): onSelect, visible, disabled, modifyAutoCompleteRequest, onceAutoCompleteDataLoaded, focus, blur, keyUp, keyDown, onShow, onHide, onClear, onDropdownClick.

7.12 multi_select — p-multiSelect (data-source control — see §8)

  • Settings (multiselectSettings): style, cssClass, placeholder, showClear, minLength, delay, dataSource, staticData[], dbData, optionLabel (default 'label', HTML), optionValue (default 'value'), showHeader, filter, filterBy, filterMatchMode (default 'contains'; full set), alwaysGetLatestDataOnFormOpen, virtualScroll, maxSelectedLabels, selectionLimit, selectedItemsLabel (e.g. '{0} items selected'), showToggleAll, optionDisabled (field marking an option disabled), optionGroupLabel + optionGroupChildren (grouping), display ('comma'\|'chip'), reloadDropdownsOfPath[], isDependentOnPath[], addNewFormConfig, apiCallOverrides, tooltip*, jsCode.
  • Validation: required/requiredFun; selectionLimit.
  • Events (EDBMasterMultiSelectAppendTo): visible, disabled, modifyMultiSelectRequest, onceMultiSelectDataLoaded, onChange (+ panel show/hide/clear as applicable).

7.13 color_picker — p-colorPicker

  • Settings (colorPickerSettings): style, cssClass, format ('hex'\|'rgb'\|'hsb'), tooltip*, jsCode.
  • Validation: required/requiredFun.
  • Events (EDBMasterColorPickerAppendTo): visible, disabled, ngModelChange.

7.14 editor — p-editor (Quill WYSIWYG) (v1.14.0+)

  • Value is HTML rich text; round-trip and sanitize on render.
  • Settings (editorSettings): style (e.g. {height:'350px'}), placeholder, maxLength (no native maxlength — enforce via Validators.maxLength and/or guard in onTextChange), tooltip*, formats[] (whitelist of allowed Quill formats from: background, bold, color, font, code, italic, link, size, strike, script, underline, blockquote, header, indent, list, align, direction, code-block, image, video, clean; configure the Quill toolbar/formats whitelist — when omitted use full default toolbar), jsCode.
  • Validation: required/requiredFun; maxLength.
  • Events (EDBMasterEditorAppendTo): visible, disabled, ngModelChange, onTextChange, onInit, onSelectionChange, focus, blur, keyUp, keyDown.

7.15 rating — p-rating

  • Settings (ratingSettings): cancel (default true — show clear icon), stars (number of stars = max value), iconOnClass/iconOnStyle, iconOffClass/iconOffStyle, iconCancelClass/iconCancelStyle, onIconHTML/offIconHTML/cancelIconHTML (raw custom HTML icons, e.g. <img> or <i> — override class/style), tooltip*, jsCode.
  • Validation: required/requiredFun.
  • Events (EDBMasterRatingAppendTo): ngModelChange, onRate, onCancel, onFocus, onBlur, visible, disabled.

7.16 knob — p-knob

  • Settings (knobSettings): min, max, step, size (px diameter, e.g. 150, 200), strokeWidth, valueColor, rangeColor, textColor, valueTemplate (e.g. '{value}%', '{value}°C'{value} substituted), cssClass, style, tooltip*, jsCode.
  • Validation: required/requiredFun; min/max/step enforced by knob.
  • Events (EDBMasterKnobAppendTo): ngModelChange, visible, disabled.

7.17 file_upload — p-fileUpload (full spec)

  • Stored value at path = array of { originalName: string, uploadPath: string } objects (extra props allowed). NOT raw File objects.
  • Settings (fileUploadSettings): style, cssClass, auto (default false; true auto-uploads after selection, else show upload button), uploadApiUrl (REQUIRED), downloadApiUrl, removeApiUrl, multiple, accept (e.g. 'image/*', '.pdf,.doc,.docx'), maxFileSize (default 10000000 = 10MB), fileLimit, tooltip*. (_showUploadButton, _fileSelectEvent are internal — do not expose; you may need equivalent internal state.)
  • Contracts (resolve :beHostPort / :userPath placeholders first — see §9):
  • UPLOAD: POST files in multipart field named exactly files to uploadApiUrl; response is [{originalName, uploadPath}, ...] → becomes the FormControl value. Use p-fileUpload customUpload/uploadHandler.
  • DOWNLOAD/PREVIEW: call downloadApiUrl → returns base64; use for inline preview (data URI for images) and download.
  • REMOVE: call removeApiUrl with a single { originalName, uploadPath } payload, then update the FormControl array.
  • Validation: required/requiredFun; maxFileSize; fileLimit; accept.
  • No data-source / cascading behavior.

7.18 image — p-image (display-only; <img> inside <span>)

  • Settings (imageSettings): src (REQUIRED; may start empty and be set dynamically via jsCode from formData, e.g. column.imageSettings.src = formData.profilePicture), style, cssClass, imageParentSpanClass (class on the wrapping <span>), width, height, alt, preview (fullscreen preview on click), previewImageSrc (separate high-res preview source, distinct from thumbnail src), tooltip*, jsCode.
  • Display-only — do NOT register as a value FormControl unless a path is set. Often paired with file_upload to preview uploaded images. Re-render when src changes.
  • Events (EDBMasterImageAppendTo): visible, disabled, click.

7.19 button — p-button (action only, no value)

  • Render as a standalone <p-button>; do NOT add it as a value-bearing FormControl.
  • Settings (buttonSettings): label (required, can be empty for icon-only), style, cssClass, link, icon, iconPos (default 'left'; right/top/bottom), loading, loadingIcon, severity (primary/success/info/warning/danger), raised, rounded, text, outlined, badge, badgeClass (newer PrimeNG: badgeSeverity), size ('small'\|'large'), tooltip*, jsCode (click).
  • Events (EDBMasterButtonAppendTo): click (→ (onClick)), visible, disabled. Click code commonly uses utils.messageService.showSuccessToast(...) and utils.errorsMap[path]='message'.

7.20 divider — p-divider (presentational, no value)

  • Settings (dividerSettings): style, cssClass, align (center/left/right for horizontal; top/center/bottom for vertical → [align]), type (dashed/dotted/solid[type]), dividerText (projected content; or use field label).
  • No control-specific events; only visible/disabled (evaluated).

7.21 customHTML — <div [innerHTML]> + DomSanitizer (no PrimeNG component)

  • Settings (customHTMLSettings): htmlCode (REQUIRED) — raw HTML rendered via [innerHTML]. Sanitized (XSS protection — some tags/attributes/event-handlers/scripts stripped). Use Angular DomSanitizer.sanitize (NOT bypassSecurityTrust) to preserve the same protective behavior. Keep inline style attributes intact where possible.
  • No value, no FormControl. Primary dynamic feature is visible.

7.22 accordion — p-accordion (field container, no value)

  • Settings (accordionSettings): style, cssClass, activeIndex (number | number[]), defaultIndex (default 0; seed activeIndex if absent), multiple (→ [multiple]; in multiple mode activeIndex is number[]), jsCode, tabs[]: each { style, header (HTML → render with[innerHTML]), headerStyle, headerCssClass, disabled (**boolean OR JS-expression string**), fields (2-D nested fields) }.
  • Recursively render each tab's fields[][] as their own reactive controls; the accordion contributes no value of its own.
  • Events (EDBMasterAccordionAppendTo): visible, disabled, activeIndexChange.

7.23 tab_view — p-tabView + p-tabPanel (field container, no value)

  • Settings (tabViewSettings): activeIndex, defaultIndex (default 0), style, cssClass, jsCode, tabs[]: { style, header (HTML), headerStyle, headerCssClass, disabled (**boolean only** here), fields (2-D nested fields) }.
  • Events (EDBMasterTabViewAppendTo): visible, disabled, activeIndexChange (→ (onChange)/(activeIndexChange)).

7.24 grid (nested) — p-table + add/edit p-dialog backed by a FormArray of FormGroups

  • Value is an array stored at path → model as a FormArray<FormGroup>; bind to <p-table> [value]; edit/add via a p-dialog hosting the nested form.
  • Settings (gridSettings) = Omit<IDBMasterConfig,'schema'> — a full child config (its own operations, grid, form, jsCode, externalLibs, cssCode, apiCallOverrides...). Recurse: a nested grid inside gridSettings.form is another FormArray + p-table + dialog. Unlimited nesting. For nested grids, locate the child array via dataFieldPath; serverSidePagination and selectFieldsForQuery are NOT supported for nested grids.
  • Reproduce all grid features (§5), row magic-hide flags, the Add modal flow (operations.add.enable/label; on save push the FormGroup value into the FormArray; respect hideAddConfirmation), edit (open dialog with row), delete (remove from array; respect confirmations/disableDoubleClickForEdit/disableDeleteButtonClickForDelete), db_data column FK resolution (collection/pkField/displayField → display value), and nested-form dropdown loading (re-fetch on dialog open when alwaysGetLatestDataOnFormOpen).
  • Lifecycle hooks (gridSettings.jsCode): oncePageLoad, oncePageLoadWithContext, gridRender, modifyGridRequest, preSaveAndEdit (mode), beforeSaveModalOpen, beforeEditModalOpen (see §4.2).
  • Nested form fields carry the full IPropertyValidation (required, min, max, minLength, maxLength, email, validatorFun, enum) — convert each to Angular validators.

8. Dependent / cascading dropdowns

Cascading is NOT automatic. It requires, on the relevant dropdownSettings/autocompleteSettings/multiselectSettings, all three of:

  1. reloadDropdownsOfPath: string[] — on the parent control. When the parent's value changes, the child controls at these paths are cleared and reloaded.
  2. isDependentOnPath: string[] — on the child control. The child loads data only when all listed parent paths have values in formData.
  3. jsCode modify{Dropdown|AutoComplete|MultiSelect}Request — on the child. Mutates reqBody to inject the parent value into the query, e.g. reqBody.find.continentId = formData.continentId;.

N-level example: planet → continent → country → state

// continent depends on planet, reloads country on change
{ path:'continentId', control:'dropdown', dropdownSettings:{
  dataSource:'db_data', dbData:{ collection:'ui_maker_continent' }, optionValue:'_id', optionLabel:'name',
  isDependentOnPath:['planetId'], reloadDropdownsOfPath:['countryId'],
  jsCode:[{ appendTo:'modifyDropdownRequest', code:`reqBody.find.planetId = formData.planetId;` }]
}}

Angular re-implementation:

  • For each control with reloadDropdownsOfPath, subscribe to its FormControl valueChanges. On change: for each child path, reset the child's value and trigger its reload (clear cached options and re-fetch).
  • For each control with isDependentOnPath, gate loadOptions() behind a check that every listed parent path has a non-empty value in formData; otherwise leave options empty/disabled.
  • Before each child fetch, run the modify*Request hook against a mutable reqBody object so it can inject parent values (reqBody.find.<parent> = formData.<parent>), then send that body to the data service.
  • Run once*DataLoaded after the fetch to post-process dropdownData.
  • Cascading mixes freely across dropdown / auto_complete / multi_select. The chain can be arbitrarily deep — reset and reload transitively down the chain. reloadDropdownsOfPath is also available as a mutable variable inside hooks: pushing a path into it triggers that path's reload.

9. API layer & overrides

Replace API Maker's auto-generated calls and all apiCallOverrides with an explicit api.service.ts (HttpClient).

IDBMasterAPICallOverrides:

interface IDBMasterAPICallOverrides {
  url: string;                 // REQUIRED; supports :beHostPort and :userPath tokens
  headers?: any;               // custom request headers
  method?: 'POST';             // ONLY 'POST' is supported
  body?: any;                  // custom request body
  pkField?: string;            // PK field name; '_id' for MongoDB (delete/getById/deleteMany)
  codeBeforeAPICall?: string;  // JS hook: mutate request before call
  codeAfterAPICall?: string;   // JS hook: mutate response after call
}
  • All override calls are POST. Implement only POST.
  • Placeholder substitution (do before every call): :beHostPort → backend protocol+host+port (e.g. https://example.com); :userPath → the admin user path segment.
  • codeBeforeAPICall / codeAfterAPICall scope: config, formData, allDropdownDataMap, apiResponse (after only), globalData, headers, reqBody, utils, queryParams, mode. Note: in the grid context, formData and allDropdownDataMap are NOT available.

Per-operation overrides to implement (each points to a custom endpoint):

Override path Operation Extra
operations.add.apiCallOverrides create/save
operations.importData.apiCallOverrides CSV import persist default uses save API
grid.apiCallOverrides get grid data
grid.operations.delete.apiCallOverrides delete by id uses pkField
grid.operations.delete.apiCallOverridesDeleteMany bulk delete uses pkField
grid.operations.edit.apiCallOverrides edit/update by id
grid.operations.edit.apiCallOverridesGetById fetch single record before edit uses pkField
dropdownSettings/autocompleteSettings/multiselectSettings.apiCallOverrides dropdown options (dataSource:'api_call') map response via optionLabel/optionValue

When no override exists, call the standard API Maker endpoint for that operation; build query bodies (find, select, limit, deep, sort) via query.service.ts using the API Maker IQueryFormat shape (selectFieldsForQuery projection {field:1|-1}).


10. CSV import & export

10.1 Import — operations.importData (IDBMasterImportData)

interface IDBMasterImportData {
  enable: boolean;
  icon?: string;                 // default 'pi pi-file-import'
  gridWidth?: string;            // import preview grid width, e.g. '2000px'
  fieldMappings: IDBMasterImportGridColumn[];
  apiCallOverrides?: IDBMasterAPICallOverrides; // default: the save API
}
interface IDBMasterImportGridColumn {
  header: string;                          // UI preview grid header
  sourceField: string;                     // CSV column name (e.g. 'NAME', 'DOB')
  targetField: string;                     // DB field path (e.g. 'name', 'dateOfBirth')
  convertTargetFieldToType: EType;         // convert CSV value to this type before save (e.g. string, date)
  dateParseFormat?: string;                // date-fns parse format (default ISO)
  dateTimeZone?: string;                   // tz to convert parsed date into before DB query
  dateFormatUIDisplay?: string;            // date format for UI display in preview
  dateTimeZoneUIDisplay?: string;          // tz for UI display in preview
  width?: string;                          // per-column preview width (e.g. '300px')
  filterType?: EDBMasterConfigFilterType;  // CALCULATED/internal — do not set
}

Implement: a file picker → parse CSV with Papa Parse (available globally as Papa) → render an editable preview p-table (columns from fieldMappings, widths from width/gridWidth) → for each row map sourceFieldtargetField, convert to convertTargetFieldToType (use date-fns parse with dateParseFormat; convert tz via date-fns-tz with dateTimeZone; display in preview using dateFormatUIDisplay/dateTimeZoneUIDisplay) → POST to the save API (or apiCallOverrides).

10.2 Export — operations.exportData (CSV and PDF)

The Export toolbar button offers CSV and PDF export, each supporting export all and export selected rows. Implement both formats.

operations: {
  exportData: {
    showExportButton: true,
    icon: 'pi pi-download',                                  // default 'pi pi-download'
    csv: { enable: true, labelCSV: 'CSV', labelCSVSelected: 'CSV Selected' },
    pdf: { enable: true, labelPDF: 'PDF', labelPDFSelected: 'PDF Selected' }
  }
}
Key Default Behavior
exportData.showExportButton show the Export button/menu in the toolbar
exportData.icon 'pi pi-download' export button icon
exportData.csv.enable enable CSV export
exportData.csv.labelCSV 'CSV' "export all rows → CSV" label
exportData.csv.labelCSVSelected 'CSV Selected' "export selected rows → CSV" label
exportData.pdf.enable enable PDF export
exportData.pdf.labelPDF 'PDF' "export all rows → PDF" label
exportData.pdf.labelPDFSelected 'PDF Selected' "export selected rows → PDF" label

The source interface explicitly defines csv; pdf is the parallel option (it appears commented in the older grid interface, and PDF export is fully wired through the processDataBeforePDFExport hook below). Confirm the page's actual exportData config and reproduce whichever of csv/pdf it enables. Keep the same key/label names so existing configs keep working.

Both formats must expose two actions each: export all (the full/queried grid data) and export selected (only the rows currently selected via grid selection). When nothing is selected, the "selected" action exports nothing (or is disabled) — match the original. Honor the currently visible columns (from the column selector, §5.10.2) and the configured grid field order/headers in the exported output.

CSV export: - Run the processDataBeforeExport hook first (EDBMasterConfigAppendTo.processDataBeforeExport). Scope: gridData (a mutable copy — mutating it does NOT affect the displayed grid), globalData, utils, queryParams. Use it to reshape/format rows before serialization. - Serialize the (possibly transformed) rows to CSV using Papa Parse (Papa.unparse, available globally as Papa) with the grid headers as the column headers, then trigger a browser download.

PDF export: - Run the processDataBeforePDFExport hook first (EDBMasterConfigAppendTo.processDataBeforePDFExport). Scope: gridData (a mutable copy, does not affect the displayed grid), jsPdfDoc, globalData, utils, queryParams. - jsPdfDoc is a pre-created jsPDF instance the hook may customize or replace. The hook can build a fully custom document, e.g.:

const { jsPDF } = window.jspdf;                 // jsPDF available globally as window.jspdf
const doc = new jsPDF({ orientation: 'landscape', format: 'a4' });
$scope.jsPdfDoc = doc;                          // replace the instance
$scope.jsPdfDoc.text('Custom Report Title', 14, 15);
- After the hook, render the grid rows into jsPdfDoc (a tabular layout — typically via the jsPDF autoTable plugin or manual text/table calls) using visible columns + headers, then save/download the PDF. If the hook fully built the document, respect its content. - Load jsPDF (and autoTable, if used) the same way other libs are loaded (§4.2 / externalLibs); expose it as window.jspdf so hook code referencing window.jspdf works unchanged.

On export completion emit the GRID_EXPORTED parent event (§12) for both formats.


11. Theming

  • theme = an EDBMasterTheme value mapping directly to a PrimeNG prebuilt theme stylesheet name (e.g. md-dark-deeppurple → PrimeNG md-dark-deeppurple). Families include Bootstrap4, MD (Material), MDC, Fluent, Lara, Soho, Viva, Mira, Nano, Saga, Vela, Arya, Nova, Luna, Rhea — with light/dark variants and color accents (blue/indigo/purple/teal/green/orange/amber/pink). Load the corresponding PrimeNG theme CSS at runtime.
  • showThemeSelector (default false) → render a theme switcher in the grid header that swaps the loaded theme stylesheet (theme.service.ts).
  • Global defaults to carry: dateTimeFormat (default 'dd-MM-yyyy hh:mm:ss a'), dateTimeZone (default browser tz).

12. Iframe / parent-app integration

UI Maker pages run in an iframe and communicate via window.postMessage with { eventType, eventData }. Implement parent-integration.service.ts to send/receive these. Handshake: iframe emits PAGE_READY; parent sets config (append-query/global data) then sends PARENT_READY. With wait-for-parent-ready=true, the page blocks until PARENT_READY.

Query params (currently from URL; if not iframe-embedded, take as @Input/route/query params): admin-path, db-master-name, x-am-authorization, x-am-user-authorization, x-am-internationalization, wait-for-parent-ready, show-logs.

Iframe → Parent events (IDBMasterEventFromIframeToParent): PAGE_READY, RECORD_SAVED, RECORDS_IMPORTED, RECORD_UPDATED, RECORD_DELETED, MANY_RECORD_DELETED, GRID_EXPORTED, GRID_REFRESHED, ADD_NEW_BUTTON_CLICKED, CUSTOM_ACTION_BUTTON_CLICKED, DROPDOWN_ADD_NEW_RECORD_SAVED. Plus custom events via window.top.postMessage({ eventType, eventData }, '*').

Parent → Iframe events (IDBMasterEventFromParentToIframe): TRIGGER_ADD_NEW_BUTTON_CLICK, TRIGGER_REFRESH_BUTTON_CLICK, DATA_TO_APPEND_IN_GRID_LOAD_FIND_QUERY (merge into grid find query), DATA_TO_APPEND_IN_RECORD_SAVE_API_PAYLOAD (merge into create payload), DATA_TO_APPEND_IN_RECORD_UPDATE_API_PAYLOAD (merge into update payload), SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT (populate globalData), PARENT_READY.

The data layer must support merging an externally-supplied object into (a) the grid load/find query, (b) the save payload, and (c) the update payload. If replacing the iframe with normal Angular: expose @Input() appendToFindQuery, @Input() appendToSavePayload, @Input() appendToUpdatePayload, @Input() globalData, @Output() emitters for each iframe→parent event, and methods triggerAddNew() / triggerRefresh(). Preserve wait-for-parent-ready semantics (don't load until inputs are set) and show-logs. (For real iframe binding in Angular, sanitize the URL with DomSanitizer.bypassSecurityTrustResourceUrl.)


13. Date & timezone handling

  • All dates use date-fns formats. Global dateTimeFormat default 'dd-MM-yyyy hh:mm:ss a'; global dateTimeZone default browser tz. Dates are converted to dateTimeZone before grid display AND before being sent into the DB query (use date-fns-tz).
  • Grid columns: autoFormatDate:true formats the cell (default 'dd-MM-yyyy hh:mm:ss a'); per-column dateTimeFormat/dateTimeZone override.
  • date_picker value is a JS Date. PrimeNG calendar format caveat: PrimeNG's dateFormat token syntax differs from date-fns — convert (e.g. dd-MM-yyyydd-mm-yy). PrimeNG calendar has no time format token, so time is auto-appended to the displayed value. Unsupported PrimeNG calendar tokens will break — validate during conversion.
  • For view='month'/'year', keep dateTimeFormat consistent ('MM-yyyy'/'yyyy').
  • Centralize all of this in date-format.service.ts (date-fns format string, date-fns → PrimeNG token conversion, tz conversion).

14. Step-by-step conversion procedure (per UI page)

For each UI Maker page:

  1. Read the page's dbMasterConfig in full — it is authoritative. List every key actually used (don't port unused keys).
  2. Scaffold the page component set under pages/<db-master-name>/ (.ts/.html/.scss, .main.ts, .api.ts, .model.ts). Import the shared library.
  3. Build the FormGroup from form.fields[][]: create nested FormGroups for dotted paths; lay out rows/columns with cssClassDiv (compute col-md-* from columns count).
  4. Wire validations: static requiredValidators.required; control-specific (minLength, maxLength, min, max, email, enum, custom validatorFun) → matching validators; requiredFun → dynamic validator re-evaluated on valueChanges (takes precedence over static required). Surface validationErrors.* messages.
  5. Wire dropdowns/cascades: implement dataSource (static/db/api), optionLabel/optionValue (HTML labels via templates), filter/filterBy/filterMatchMode, virtualScroll, alwaysGetLatestDataOnFormOpen, addNewFormConfig, and reloadDropdownsOfPath + isDependentOnPath + modify*Request cascades (§8).
  6. Wire file uploads (§7.17): upload/download/remove contracts, placeholder substitution, response shape.
  7. Port jsCode hooks: page-level (§4.2) → component methods/lifecycle; per-control → control event handlers. Resolve userUtils.Main.* (page) and userUtils.GeneralUtils.* (shared). Support string/function/reference, Promise, returned-function.
  8. Port cssCode → page <name>.scss; common CSS → global styles.scss.
  9. Wire the API service: standard endpoints + every apiCallOverrides (POST only, placeholder substitution, before/after hooks).
  10. Wire the grid (§5): columns, sorting, per-column row filter on every column (§5.5.1), global search (§5.10.1), column selector (§5.10.2), pagination, grouping, selection, row actions + ordering + magic-hide flags, toolbar ops + ordering, custom action buttons + confirmationMessageScript.
  11. Wire CSV import and export (both CSV and PDF, all + selected) (§10) and theme (§11).
  12. Wire parent integration (§12): postMessage events / @Input/@Output bridge, append-query/save/update payload merging, globalData.
  13. Verify against the original running page (§15) — behavior, not just structure.

15. Definition of Done / verification checklist

Tick every item for the page. This is the safety net for "do not miss any feature."

Form controls — every control present in the config is rendered correctly: - [ ] input, textarea, password, inputNumber, inputMask, inputOtp, date_picker, checkbox, radio - [ ] dropdown, auto_complete, multi_select (all three data sources: static_data / db_data / api_call) - [ ] color_picker, editor (Quill formats whitelist), rating (custom icons), knob - [ ] file_upload, image, button, divider, customHTML (sanitized), accordion, tab_view, nested grid

Per-control fidelity: - [ ] Every settings property mapped (no property dropped) - [ ] optionLabel/optionValue correct (auto_complete stores label-value, no optionValue) - [ ] filterMatchMode set per control (autoComplete only contains/startsWith/endsWith) - [ ] HTML in optionLabel/helpText/headers rendered via [innerHTML] - [ ] inputMask model mutation done in complete, not blur - [ ] inputNumber currency requires currency; locale default en-US; buttonLayout default stacked - [ ] date_picker value is Date; PrimeNG format conversion; month/year view formats; time auto-appended

Validation: - [ ] static required, dynamic requiredFun (precedence), validationErrors messages - [ ] control-specific minLength/maxLength/min/max/minFractionDigits/email/enum/validatorFun - [ ] selectionLimit, forceSelection, maxFileSize, fileLimit, accept

Conditional logic: - [ ] visible / disabled as boolean OR JS-expression string, re-evaluated on value changes - [ ] requiredFun re-evaluated dynamically - [ ] tab/accordion disabled (accordion: bool|expr; tabView: bool only)

Dependent / cascading dropdowns: - [ ] reloadDropdownsOfPath, isDependentOnPath, modify*Request working N levels, mixed control types - [ ] children cleared + reloaded; parent value injected into child query; once*DataLoaded runs

File upload: - [ ] upload (files field, returns [{originalName, uploadPath}]), download/preview (base64), remove ({originalName, uploadPath}) - [ ] auto vs manual, multiple, accept, maxFileSize (10MB default), fileLimit, :beHostPort/:userPath

Grid: - [ ] columns (auto-from-schema excludes _id), header/path/dotted paths, sorting, visibilityStatus, autoFormatDate/dateTimeFormat, dbData FK lookup, cssClass - [ ] sortMode/multiSortMeta, pagination (showPagination/serverSide/rowsPerPage/options/report template), grouping (subheader), selection (mode/dataKey/metaKeySelection/checkboxSelection) - [ ] global search (showGlobalSearch toolbar input; globalSearchFields incl. dotted paths; string→contains / number→exact; OR across fields; client filterGlobal + server $or query; debounced + clear) — see §5.10.1 - [ ] column selector (showColumnSelector toolbar p-multiSelect; per-column visibilityStatus seeds initial visible set; toggling shows/hides columns live; fires columnSelectionChanged) — see §5.10.2 - [ ] row filter — a filter on EVERY data column (rowFilterp-columnFilter per column; type-aware inputs+match modes for string/number/date/boolean/FK; dotted paths; AND across columns; client built-in + server find-query translation via modifyGridRequest; action/selection columns excluded) — see §5.5.1 - [ ] selectFieldsForQuery, minWidth/breakpoint - [ ] row ops (edit/view/delete) + cssClass + confirmations + double-click-edit + delete-button-click + gridOperationsOrder/gridColumnsOrder/actionColumnWidth - [ ] per-row magic-hide flags (____hide_delete_button/_edit_/_view_/____hide_<actionName>) - [ ] row custom action buttons + confirmationMessageScript (template-literal eval with selectedGridItems) - [ ] toolbar ops + operationsOrder (default order), add/refresh/delete-all/export/import/column-selector/theme-selector + hide flags + hideDeleteManySuccessMessage; top-level custom action buttons

Nested structures: - [ ] nested fields[][] layout containers; nested grid (gridSettings) as FormArray, unlimited depth; dataFieldPath; addNewFormConfig inline add flow

API & overrides: - [ ] standard endpoints + all apiCallOverrides (add/save, edit, getById, delete, deleteMany, import, grid get-data, dropdown) — POST only - [ ] :beHostPort/:userPath substitution; codeBeforeAPICall/codeAfterAPICall with correct scope vars; pkField

CSV import / export (CSV + PDF) / theming / dates: - [ ] CSV import (Papa Parse, fieldMappings, type conversion, date parse/tz, UI display formats, preview grid widths) - [ ] CSV export (export all + export selected; csv.enable/labelCSV/labelCSVSelected; icon; processDataBeforeExport mutable-copy hook; Papa unparse; visible columns + headers) — §10.2 - [ ] PDF export (export all + export selected; pdf.enable/labelPDF/labelPDFSelected; processDataBeforePDFExport hook with replaceable jsPdfDoc; window.jspdf loaded; tabular render; visible columns + headers) — §10.2 - [ ] GRID_EXPORTED parent event emitted on export (both formats) - [ ] theme applied + showThemeSelector; global dateTimeFormat/dateTimeZone defaults; date-fns ↔ PrimeNG conversion

JS / CSS hooks: - [ ] page-level jsCode hooks (all EDBMasterConfigAppendTo used) with correct scope (config, globalData, utils, queryParams, gridEvent, gridData, reqBody, formData, mode) - [ ] per-control jsCode events wired to PrimeNG outputs; string/function/userUtils.* refs; Promise/returned-function support - [ ] utils helpers (findFormElement, getFlatArrayOfAllFormFields[IncludingGridFields], messageService.*, errorsMap) - [ ] common CSS → global styles; page CSS → page styles; common JS → GeneralUtils; page JS → Main - [ ] external libs (externalLibs.css/js CDNs loaded before user code); inbuilt libs available: Papa, dateFns, dateFnsTz, _, moment; Bootstrap 5.3.3 grid+utilities

Parent integration: - [ ] all iframe→parent and parent→iframe events; handshake (PAGE_READY/PARENT_READY); wait-for-parent-ready; show-logs - [ ] append-data merging into find query / save payload / update payload; SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPTglobalData; query params

When any item cannot be satisfied from the config alone, inspect the original UI Maker page at runtime and reproduce the observed behavior exactly. Do not skip, do not approximate.