@webtypen/webframez-core 0.3.101 → 0.3.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,9 @@ export declare class Datatable {
54
54
  } | null;
55
55
  getInit(req: Request): Promise<any>;
56
56
  private loadColumnState;
57
+ /** Options may be declared inline or loaded from an authenticated host callback. */
58
+ getColumnDisplayOptions(req: Request, key: string): Promise<any>;
59
+ private resolveDisplayOptions;
57
60
  private getColumnState;
58
61
  saveColumnPreferences(req: Request, columns: unknown): Promise<any>;
59
62
  resetColumnPreferences(req: Request): Promise<any>;
@@ -64,13 +64,13 @@ class Datatable {
64
64
  this.getFilter(req), this.getColumnState(req), this.getOnPressLink(req),
65
65
  ]);
66
66
  out.filter = filter;
67
- out.columns = Object.entries(state.visible).map(([key, column]) => (Object.assign(Object.assign({}, column), { key })));
68
- out.availableColumns = Object.entries(state.all).map(([key, column]) => (Object.assign(Object.assign({}, column), { key })));
67
+ out.columns = Object.entries(state.visible).map(([key, column]) => (Object.assign(Object.assign({}, column), { displayOptions: typeof column.displayOptions === "function" ? true : column.displayOptions, key })));
68
+ out.availableColumns = Object.entries(state.all).map(([key, column]) => (Object.assign(Object.assign({}, column), { displayOptions: typeof column.displayOptions === "function" ? true : column.displayOptions, key })));
69
69
  out.columnPreferences = Boolean(state.scope) && Object.keys(state.all).length > 0;
70
70
  out.columnSelection = out.columns.map((column) => {
71
- var _a, _b;
72
- return (Object.assign({ key: column.key }, (((_b = (_a = state.preferences) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.key === column.key)) === null || _b === void 0 ? void 0 : _b.width)
73
- ? { width: state.preferences.find((entry) => entry.key === column.key).width } : {})));
71
+ var _a;
72
+ const saved = (_a = state.preferences) === null || _a === void 0 ? void 0 : _a.find((entry) => entry.key === column.key);
73
+ return Object.assign(Object.assign(Object.assign({ key: column.key }, ((saved === null || saved === void 0 ? void 0 : saved.width) ? { width: saved.width } : {})), ((saved === null || saved === void 0 ? void 0 : saved.title) ? { title: saved.title } : {})), ((saved === null || saved === void 0 ? void 0 : saved.displayField) && saved.displayField === column.displayField ? { displayField: saved.displayField } : {}));
74
74
  });
75
75
  out.onPressLink = onPressLink;
76
76
  if (this.exports) {
@@ -97,7 +97,35 @@ class Datatable {
97
97
  const store = new DatatablePreferencesStore_1.DatatablePreferencesStore(config || {});
98
98
  const scope = config === false ? null : yield store.scope(req);
99
99
  const preferences = scope ? yield store.load(scope) : null;
100
- return { all, visible: DatatableColumnPreferences_1.DatatableColumnPreferences.apply(all, preferences), preferences, store, scope };
100
+ const savedKeys = Array.isArray(preferences) ? preferences.map(entry => entry === null || entry === void 0 ? void 0 : entry.key).filter(key => Object.prototype.hasOwnProperty.call(all, key)) : [];
101
+ const selected = savedKeys.length ? savedKeys : Object.keys(all).filter(key => !all[key].hidden);
102
+ const resolved = yield this.resolveDisplayOptions(req, all, selected);
103
+ return { all, visible: DatatableColumnPreferences_1.DatatableColumnPreferences.apply(resolved, preferences), preferences, store, scope };
104
+ });
105
+ }
106
+ /** Options may be declared inline or loaded from an authenticated host callback. */
107
+ getColumnDisplayOptions(req, key) {
108
+ var _a;
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const all = (_a = yield this.getColumns(req)) !== null && _a !== void 0 ? _a : {};
111
+ if (typeof key !== "string" || !Object.prototype.hasOwnProperty.call(all, key)) {
112
+ throw Object.assign(new Error("Unbekannte Spalte."), { status: 400 });
113
+ }
114
+ const resolved = yield this.resolveDisplayOptions(req, all, [key]);
115
+ return resolved[key].displayOptions || [];
116
+ });
117
+ }
118
+ resolveDisplayOptions(req, columns, keys) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const resolved = Object.assign({}, columns);
121
+ yield Promise.all(Array.from(new Set(keys)).filter(key => typeof key === "string" && Object.prototype.hasOwnProperty.call(columns, key)).map((key) => __awaiter(this, void 0, void 0, function* () {
122
+ const column = columns[key];
123
+ if (typeof column.displayOptions === "function") {
124
+ const options = yield column.displayOptions(req, key);
125
+ resolved[key] = Object.assign(Object.assign({}, column), { displayOptions: Array.isArray(options) ? options : [] });
126
+ }
127
+ })));
128
+ return resolved;
101
129
  });
102
130
  }
103
131
  getColumnState(req) {
@@ -115,7 +143,8 @@ class Datatable {
115
143
  throw Object.assign(new Error("Spalteneinstellungen sind für diesen Benutzer nicht verfügbar."), { status: 403 });
116
144
  let preferences;
117
145
  try {
118
- preferences = DatatableColumnPreferences_1.DatatableColumnPreferences.validate(columns, state.all);
146
+ const resolved = yield this.resolveDisplayOptions(req, state.all, Array.isArray(columns) ? columns.map(entry => entry === null || entry === void 0 ? void 0 : entry.key) : []);
147
+ preferences = DatatableColumnPreferences_1.DatatableColumnPreferences.validate(columns, resolved);
119
148
  }
120
149
  catch (error) {
121
150
  throw Object.assign(error, { status: 400 });
@@ -10,6 +10,15 @@ export interface DatatableColumnPreference {
10
10
  key: string;
11
11
  /** Omit to retain the current backend default. */
12
12
  width?: DatatableColumnWidth;
13
+ title?: string;
14
+ displayField?: string;
15
+ }
16
+ export interface DatatableDisplayOption {
17
+ value: string;
18
+ label: string;
19
+ type?: string;
20
+ plain?: boolean;
21
+ options?: any[];
13
22
  }
14
23
  export interface DatatableColumnPreferencesConfig {
15
24
  collection?: string;
@@ -26,5 +35,6 @@ export interface DatatableColumnContext {
26
35
  }
27
36
  export declare class DatatableColumnPreferences {
28
37
  static validate(value: unknown, columns: Record<string, any>): DatatableColumnPreference[];
38
+ static display(definition: any, selected?: string): any;
29
39
  static apply(columns: Record<string, any>, preferences: unknown): Record<string, any>;
30
40
  }
@@ -13,6 +13,19 @@ class DatatableColumnPreferences {
13
13
  }
14
14
  seen.add(entry.key);
15
15
  const preference = { key: entry.key };
16
+ if (entry.title !== undefined) {
17
+ if (typeof entry.title !== "string" || entry.title.length > 160)
18
+ throw new Error("Ungültiger Spaltentitel (maximal 160 Zeichen).");
19
+ if (entry.title.trim())
20
+ preference.title = entry.title.trim();
21
+ }
22
+ if (entry.displayField !== undefined && entry.displayField !== "") {
23
+ if (typeof entry.displayField !== "string" || !Array.isArray(columns[entry.key].displayOptions)
24
+ || !columns[entry.key].displayOptions.some((option) => option.value === entry.displayField)) {
25
+ throw new Error("Unzulässiges Anzeigefeld für diese Spalte.");
26
+ }
27
+ preference.displayField = entry.displayField;
28
+ }
16
29
  if (entry.width !== undefined) {
17
30
  const width = entry.width;
18
31
  if ((width === null || width === void 0 ? void 0 : width.mode) === "auto") {
@@ -29,11 +42,28 @@ class DatatableColumnPreferences {
29
42
  return preference;
30
43
  });
31
44
  }
45
+ static display(definition, selected) {
46
+ const column = Object.assign({}, definition);
47
+ const options = Array.isArray(column.displayOptions) ? column.displayOptions : [];
48
+ const option = options.find(entry => entry.value === (selected || column.displayField)) || options[0];
49
+ if (option) {
50
+ column.displayField = option.value;
51
+ for (const key of ["type", "plain", "options"])
52
+ if (option[key] !== undefined)
53
+ column[key] = option[key];
54
+ }
55
+ return column;
56
+ }
32
57
  static apply(columns, preferences) {
33
- const defaults = () => Object.fromEntries(Object.entries(columns).filter(([, column]) => column.hidden !== true));
58
+ const defaults = () => Object.fromEntries(Object.entries(columns).filter(([, column]) => column.hidden !== true).map(([key, column]) => [key, this.display(column)]));
34
59
  if (!Array.isArray(preferences))
35
60
  return defaults();
36
- const available = preferences.filter(entry => entry && typeof entry.key === "string" && Object.prototype.hasOwnProperty.call(columns, entry.key));
61
+ const available = preferences.filter(entry => entry && typeof entry.key === "string" && Object.prototype.hasOwnProperty.call(columns, entry.key)).map(entry => {
62
+ const options = columns[entry.key].displayOptions;
63
+ // Removed target fields fall back independently; retain the rest of the layout.
64
+ return entry.displayField && (!Array.isArray(options) || !options.some((option) => option.value === entry.displayField))
65
+ ? Object.assign(Object.assign({}, entry), { displayField: undefined }) : entry;
66
+ });
37
67
  if (!available.length)
38
68
  return defaults();
39
69
  let validated;
@@ -44,7 +74,11 @@ class DatatableColumnPreferences {
44
74
  return defaults();
45
75
  }
46
76
  return Object.fromEntries(validated.map(preference => {
47
- const column = Object.assign({}, columns[preference.key]);
77
+ const column = this.display(columns[preference.key], preference.displayField);
78
+ if (preference.title) {
79
+ column.title = preference.title;
80
+ column.label = preference.title;
81
+ }
48
82
  if (preference.width) {
49
83
  delete column.width;
50
84
  delete column.minWidth;
@@ -40,11 +40,13 @@ class DatatableController extends Controller_1.Controller {
40
40
  if (req.body.init_request || req.body.mode === "columns-get") {
41
41
  return res.send({ status: "success", data: yield table.getInit(req) });
42
42
  }
43
- if (req.body.mode === "columns-save" || req.body.mode === "columns-reset") {
43
+ if (["columns-save", "columns-reset", "columns-options"].includes(req.body.mode)) {
44
44
  try {
45
- const data = req.body.mode === "columns-save"
46
- ? yield table.saveColumnPreferences(req, req.body.columns)
47
- : yield table.resetColumnPreferences(req);
45
+ const data = req.body.mode === "columns-options"
46
+ ? yield table.getColumnDisplayOptions(req, req.body.column)
47
+ : req.body.mode === "columns-save"
48
+ ? yield table.saveColumnPreferences(req, req.body.columns)
49
+ : yield table.resetColumnPreferences(req);
48
50
  return res.send({ status: "success", data });
49
51
  }
50
52
  catch (error) {
@@ -0,0 +1,8 @@
1
+ /** Converts DataBuilder/DataSchema field definitions into an extensible column catalog. */
2
+ export declare class DatatableSchemaColumns {
3
+ static fromSchema(schema: Record<string, any>, options?: {
4
+ prefix?: string;
5
+ labelPrefix?: string;
6
+ prepare?: (column: Record<string, any>, field: any, path: string) => Record<string, any> | null;
7
+ }): Record<string, any>;
8
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatatableSchemaColumns = void 0;
4
+ /** Converts DataBuilder/DataSchema field definitions into an extensible column catalog. */
5
+ class DatatableSchemaColumns {
6
+ static fromSchema(schema, options = {}) {
7
+ const columns = {};
8
+ for (const [key, field] of Object.entries(schema || {})) {
9
+ if (!field || /[.$]/.test(key) || ["__proto__", "constructor", "prototype"].includes(key)
10
+ || field.secret || field.writeOnly || field.type === "password")
11
+ continue;
12
+ const mapping = options.prefix ? `${options.prefix}.${key}` : key;
13
+ const label = [options.labelPrefix, field.label || key].filter(Boolean).join(" · ");
14
+ if (field.type === "object") {
15
+ Object.assign(columns, this.fromSchema(field.schema, Object.assign(Object.assign({}, options), { prefix: mapping, labelPrefix: label })));
16
+ continue;
17
+ }
18
+ const types = { integer: "number", float: "number", number: "number", boolean: "boolean", date: "date", datetime: "datetime", option: "option", currency_cent: "currency", currency: "currency" };
19
+ const column = Object.assign(Object.assign({ label, mapping, type: types[field.type] || "string", width: 170, hidden: true, sortable: !["array", "file", "filemanager_file", "relation"].includes(field.type) && !field.multi }, (field.type === "currency" ? { plain: true } : {})), (field.options ? { options: field.options } : {}));
20
+ const prepared = options.prepare ? options.prepare(column, field, mapping) : column;
21
+ if (prepared)
22
+ columns[mapping] = prepared;
23
+ }
24
+ return columns;
25
+ }
26
+ }
27
+ exports.DatatableSchemaColumns = DatatableSchemaColumns;
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from "./DataBuilder/DataBuilder";
29
29
  export * from "./DataBuilder/DataBuilderController";
30
30
  export * from "./Datatable/Datatable";
31
31
  export * from "./Datatable/DatatableColumnPreferences";
32
+ export * from "./Datatable/DatatableSchemaColumns";
32
33
  export * from "./Datatable/DatatablePreferencesStore";
33
34
  export * from "./Datatable/DatatableRegistry";
34
35
  export * from "./Datatable/DatatableController";
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ __exportStar(require("./DataBuilder/DataBuilder"), exports);
46
46
  __exportStar(require("./DataBuilder/DataBuilderController"), exports);
47
47
  __exportStar(require("./Datatable/Datatable"), exports);
48
48
  __exportStar(require("./Datatable/DatatableColumnPreferences"), exports);
49
+ __exportStar(require("./Datatable/DatatableSchemaColumns"), exports);
49
50
  __exportStar(require("./Datatable/DatatablePreferencesStore"), exports);
50
51
  __exportStar(require("./Datatable/DatatableRegistry"), exports);
51
52
  __exportStar(require("./Datatable/DatatableController"), exports);
@@ -101,3 +101,28 @@ mappings, active filter/search/sort fields and sums, as well as model-selector
101
101
  preview fields. This avoids breaking a sort/filter on a hidden column. Dependencies
102
102
  inside custom expressions, custom filters, exports or other calculated columns
103
103
  remain the table author's responsibility.
104
+
105
+ ## Relation display fields and custom titles
106
+
107
+ A column can declare `displayOptions: [{ value: "name", label: "Name", type: "string" }]`
108
+ or an async `displayOptions(req, columnKey)` callback. The callback receives the
109
+ normal authenticated request. Initialization advertises lazy options with `true`;
110
+ `mode: "columns-options", column: "key"` resolves them when the editor needs them.
111
+ Only visible columns resolve options during normal loading. `displayField` selects
112
+ the backend default; otherwise the first option is used.
113
+
114
+ Saved preferences accept `title` (up to 160 characters) and `displayField` in
115
+ addition to width. Empty values restore the backend defaults. An unknown display
116
+ field is rejected on save; if a previously saved field disappears, loading falls
117
+ back to the default while preserving the other layout settings.
118
+
119
+ Core validates the selection and exposes it through `context.columns[key].displayField`;
120
+ the host performs the relation lookup and writes the value to the column's stable
121
+ `mapping`. Options can supply `type`, `plain` and `options` for typed rendering.
122
+ Never interpret an unchecked client field as a database projection.
123
+
124
+ `DatatableSchemaColumns.fromSchema(schema, { prefix, labelPrefix, prepare })`
125
+ creates an optional column catalog from nested DataBuilder/DataSchema fields.
126
+ The `prepare(column, field, path)` callback can attach inline or lazy display
127
+ options, change the mapping, or return null to omit a field. Plugin installation,
128
+ tenant checks and relation hydration belong in the host adapter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.101",
3
+ "version": "0.3.102",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",