@siliconst/metaui 1.0.0
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.
- package/README.md +123 -0
- package/package.json +35 -0
- package/src/nz/co/siliconst/ui5/metaui/.library +7 -0
- package/src/nz/co/siliconst/ui5/metaui/controls/GeneratorHost.ts +162 -0
- package/src/nz/co/siliconst/ui5/metaui/core/ConditionEngine.ts +40 -0
- package/src/nz/co/siliconst/ui5/metaui/core/Engine.ts +56 -0
- package/src/nz/co/siliconst/ui5/metaui/core/EventBus.ts +45 -0
- package/src/nz/co/siliconst/ui5/metaui/core/PluginRegistry.ts +56 -0
- package/src/nz/co/siliconst/ui5/metaui/core/SchemaNormalizer.ts +31 -0
- package/src/nz/co/siliconst/ui5/metaui/core/StateManager.ts +56 -0
- package/src/nz/co/siliconst/ui5/metaui/interfaces/IEventBus.ts +29 -0
- package/src/nz/co/siliconst/ui5/metaui/interfaces/ILayoutManager.ts +26 -0
- package/src/nz/co/siliconst/ui5/metaui/interfaces/IPlugin.ts +33 -0
- package/src/nz/co/siliconst/ui5/metaui/interfaces/ISchema.ts +70 -0
- package/src/nz/co/siliconst/ui5/metaui/layouts/BaseLayout.ts +34 -0
- package/src/nz/co/siliconst/ui5/metaui/layouts/FormLayout.ts +60 -0
- package/src/nz/co/siliconst/ui5/metaui/layouts/TableLayout.ts +102 -0
- package/src/nz/co/siliconst/ui5/metaui/library.ts +43 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/BasePlugin.ts +58 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/BooleanPlugin.ts +42 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/DatePlugin.ts +63 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/DropdownPlugin.ts +65 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/NumberPlugin.ts +55 -0
- package/src/nz/co/siliconst/ui5/metaui/plugins/StringPlugin.ts +55 -0
- package/ui5.yaml +12 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file IPlugin.ts
|
|
3
|
+
* @description Contract for all discrete UI control generation plugins.
|
|
4
|
+
* Enforces a strict lifecycle for rendering, validation, and dynamic state updates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Control from "sap/ui/core/Control";
|
|
8
|
+
import { IFieldMetadata } from "./ISchema";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Defines the contract that every MetaUI plugin must fulfill.
|
|
12
|
+
*/
|
|
13
|
+
export interface IPlugin {
|
|
14
|
+
/**
|
|
15
|
+
* Instantiates and binds the appropriate UI5 control based on the field metadata.
|
|
16
|
+
* @param fieldMetadata The immutable schema configuration for this specific field.
|
|
17
|
+
* @param bindingPath The exact model path where this field's data is stored.
|
|
18
|
+
* @returns The fully constructed and bound UI5 Control ready for layout insertion.
|
|
19
|
+
*/
|
|
20
|
+
render(fieldMetadata: IFieldMetadata, bindingPath: string): Control;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Evaluates the current state of the UI5 control against the required schema rules.
|
|
24
|
+
* @returns True if the input is valid, false otherwise. Must set ValueState natively.
|
|
25
|
+
*/
|
|
26
|
+
validate(): boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook triggered when a cross-field dependency requires this field to re-evaluate its state.
|
|
30
|
+
* @param newMetadata The dynamically updated schema rules for this field.
|
|
31
|
+
*/
|
|
32
|
+
onStateChange(newMetadata: IFieldMetadata): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ISchema.ts
|
|
3
|
+
* @description Defines the core structural contract for the MetaUI engine.
|
|
4
|
+
* The schema maps perfectly to the JSON payload generated by the ABAP RTTI extractor.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supported data primitive types that drive plugin selection.
|
|
9
|
+
*/
|
|
10
|
+
export type FieldType = "string" | "number" | "boolean" | "date" | "dropdown";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Value help structure mapping SAP Key/Text pairs.
|
|
14
|
+
*/
|
|
15
|
+
export interface IValueHelp {
|
|
16
|
+
/** The technical key passed back to the backend. */
|
|
17
|
+
key: string;
|
|
18
|
+
/** The localized descriptive text displayed to the user. */
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Metadata definition for a single atomic input field.
|
|
24
|
+
*/
|
|
25
|
+
export interface IFieldMetadata {
|
|
26
|
+
/** The technical field name mapping to the data model property. */
|
|
27
|
+
fieldName: string;
|
|
28
|
+
/** The primitive type determining the rendering plugin. */
|
|
29
|
+
type: FieldType;
|
|
30
|
+
/** The localized label text for the UI. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Indicates if the field is a primary key (often rendered first or highlighted). */
|
|
33
|
+
isKey: boolean;
|
|
34
|
+
/** If true, the field is rendered as a non-editable display element. */
|
|
35
|
+
isReadOnly: boolean;
|
|
36
|
+
/** If true, triggers UI5 validation to ensure a value exists. */
|
|
37
|
+
isRequired: boolean;
|
|
38
|
+
/** Maximum allowed character length for string-based inputs. */
|
|
39
|
+
maxLength?: number;
|
|
40
|
+
/** The total number of significant digits for numeric inputs. */
|
|
41
|
+
precision?: number;
|
|
42
|
+
/** The number of decimal places for numeric inputs. */
|
|
43
|
+
scale?: number;
|
|
44
|
+
/** Pre-loaded static value help arrays for dropdowns. */
|
|
45
|
+
valueHelp?: IValueHelp[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Metadata definition for a tabular structure.
|
|
50
|
+
*/
|
|
51
|
+
export interface ITableMetadata {
|
|
52
|
+
/** The internal ID/name of the table. */
|
|
53
|
+
tableName: string;
|
|
54
|
+
/** The localized table header label. */
|
|
55
|
+
label: string;
|
|
56
|
+
/** Array of field definitions dictating the table's columns. */
|
|
57
|
+
fields: IFieldMetadata[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The master schema contract ingested by the Engine to build the UI5 layout.
|
|
62
|
+
*/
|
|
63
|
+
export interface ISchema {
|
|
64
|
+
/** Determines the root container layout strategy. */
|
|
65
|
+
mode: "form" | "table" | "mixed";
|
|
66
|
+
/** The fields to be rendered at the root level (typically form inputs). */
|
|
67
|
+
rootFields: IFieldMetadata[];
|
|
68
|
+
/** The table structures to be rendered sequentially. */
|
|
69
|
+
tables: ITableMetadata[];
|
|
70
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BaseLayout.ts
|
|
3
|
+
* @description Central layout factory that delegates to specific form or table layout engines.
|
|
4
|
+
* Implements ILayoutManager.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ILayoutManager } from "../interfaces/ILayoutManager";
|
|
8
|
+
import { ISchema, ITableMetadata } from "../interfaces/ISchema";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
import { FormLayout } from "./FormLayout";
|
|
11
|
+
import { TableLayout } from "./TableLayout";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The core layout manager that delegates structural grid rendering based on schema configurations.
|
|
15
|
+
*/
|
|
16
|
+
export class BaseLayout implements ILayoutManager {
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a responsive form layout container.
|
|
19
|
+
* @param schema The master schema containing rootFields to be mounted.
|
|
20
|
+
* @returns A populated UI5 container (sap.ui.layout.form.SimpleForm).
|
|
21
|
+
*/
|
|
22
|
+
public renderForm(schema: ISchema): Control {
|
|
23
|
+
return FormLayout.build(schema.rootFields);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Constructs a responsive table layout container.
|
|
28
|
+
* @param tableMeta The metadata defining the table columns.
|
|
29
|
+
* @returns A UI5 Table control (sap.m.Table).
|
|
30
|
+
*/
|
|
31
|
+
public renderTable(tableMeta: ITableMetadata): Control {
|
|
32
|
+
return TableLayout.build(tableMeta);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file FormLayout.ts
|
|
3
|
+
* @description Generates a fully responsive SAP Fiori Form layout (SimpleForm).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import SimpleForm from "sap/ui/layout/form/SimpleForm";
|
|
7
|
+
import Label from "sap/m/Label";
|
|
8
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
9
|
+
import { PluginRegistry } from "../core/PluginRegistry";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Utility class strictly for constructing Form layouts without custom CSS.
|
|
13
|
+
*/
|
|
14
|
+
export class FormLayout {
|
|
15
|
+
/**
|
|
16
|
+
* Builds a SimpleForm with ResponsiveGridLayout, injecting the requested plugins.
|
|
17
|
+
* @param fields The array of root fields to render as FormElements.
|
|
18
|
+
* @returns A fully populated sap.ui.layout.form.SimpleForm.
|
|
19
|
+
*/
|
|
20
|
+
public static build(fields: IFieldMetadata[]): SimpleForm {
|
|
21
|
+
const form = new SimpleForm({
|
|
22
|
+
editable: true,
|
|
23
|
+
layout: "ResponsiveGridLayout",
|
|
24
|
+
labelSpanXL: 4,
|
|
25
|
+
labelSpanL: 4,
|
|
26
|
+
labelSpanM: 4,
|
|
27
|
+
labelSpanS: 12,
|
|
28
|
+
adjustLabelSpan: false,
|
|
29
|
+
emptySpanXL: 0,
|
|
30
|
+
emptySpanL: 0,
|
|
31
|
+
emptySpanM: 0,
|
|
32
|
+
emptySpanS: 0,
|
|
33
|
+
columnsXL: 1,
|
|
34
|
+
columnsL: 1,
|
|
35
|
+
columnsM: 1,
|
|
36
|
+
singleContainerFullSize: false
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const registry = PluginRegistry.getInstance();
|
|
40
|
+
|
|
41
|
+
fields.forEach((field) => {
|
|
42
|
+
// Respect Fiori accessibility standards by enforcing the required flag on labels
|
|
43
|
+
const label = new Label({
|
|
44
|
+
text: field.label,
|
|
45
|
+
required: field.isRequired
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Instantiate the correct plugin for the data type
|
|
49
|
+
const plugin = registry.getPlugin(field.type);
|
|
50
|
+
|
|
51
|
+
// In the isolated state manager architecture, the binding path maps exactly to the fieldName.
|
|
52
|
+
const control = plugin.render(field, `/${field.fieldName}`);
|
|
53
|
+
|
|
54
|
+
form.addContent(label);
|
|
55
|
+
form.addContent(control);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return form;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TableLayout.ts
|
|
3
|
+
* @description Generates a responsive SAP Fiori Table layout.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import Table from "sap/m/Table";
|
|
7
|
+
import Column from "sap/m/Column";
|
|
8
|
+
import Text from "sap/m/Text";
|
|
9
|
+
import ColumnListItem from "sap/m/ColumnListItem";
|
|
10
|
+
import Toolbar from "sap/m/Toolbar";
|
|
11
|
+
import Title from "sap/m/Title";
|
|
12
|
+
import ToolbarSpacer from "sap/m/ToolbarSpacer";
|
|
13
|
+
import Button from "sap/m/Button";
|
|
14
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
15
|
+
import { ITableMetadata, IFieldMetadata } from "../interfaces/ISchema";
|
|
16
|
+
import { PluginRegistry } from "../core/PluginRegistry";
|
|
17
|
+
import Control from "sap/ui/core/Control";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Utility class strictly for constructing sap.m.Table structures with dynamic pop-in vectors.
|
|
21
|
+
*/
|
|
22
|
+
export class TableLayout {
|
|
23
|
+
/**
|
|
24
|
+
* Builds a responsive Table, dynamically calculating column priorities.
|
|
25
|
+
* @param tableMeta The metadata defining the table's columns and nested fields.
|
|
26
|
+
* @returns A fully constructed sap.m.Table ready to be bound to a list array.
|
|
27
|
+
*/
|
|
28
|
+
public static build(tableMeta: ITableMetadata): Table {
|
|
29
|
+
const table = new Table({
|
|
30
|
+
headerToolbar: new Toolbar({
|
|
31
|
+
content: [
|
|
32
|
+
new Title({ text: tableMeta.label || tableMeta.tableName }),
|
|
33
|
+
new ToolbarSpacer(),
|
|
34
|
+
new Button({
|
|
35
|
+
text: "Add Row",
|
|
36
|
+
icon: "sap-icon://add",
|
|
37
|
+
press: (oEvent: any) => {
|
|
38
|
+
const btn = oEvent.getSource();
|
|
39
|
+
const tbl = btn.getParent().getParent() as Table;
|
|
40
|
+
const model = tbl.getModel("meta") as JSONModel;
|
|
41
|
+
const path = `/${tableMeta.tableName}`;
|
|
42
|
+
const data = model.getProperty(path) || [];
|
|
43
|
+
data.push({}); // append empty row
|
|
44
|
+
model.setProperty(path, data);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
}),
|
|
49
|
+
fixedLayout: false, // Allows native UI5 column width calculations
|
|
50
|
+
mode: "Delete",
|
|
51
|
+
delete: (oEvent: any) => {
|
|
52
|
+
const item = oEvent.getParameter("listItem");
|
|
53
|
+
const path = item.getBindingContext("meta").getPath(); // e.g. "/Contacts/0"
|
|
54
|
+
const model = item.getModel("meta") as JSONModel;
|
|
55
|
+
|
|
56
|
+
const parts = path.split("/");
|
|
57
|
+
const index = parseInt(parts.pop() || "0", 10);
|
|
58
|
+
const arrayPath = parts.join("/");
|
|
59
|
+
|
|
60
|
+
const data = model.getProperty(arrayPath);
|
|
61
|
+
data.splice(index, 1);
|
|
62
|
+
model.setProperty(arrayPath, data);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const templateCells: Control[] = [];
|
|
67
|
+
const registry = PluginRegistry.getInstance();
|
|
68
|
+
|
|
69
|
+
tableMeta.fields.forEach((field: IFieldMetadata) => {
|
|
70
|
+
// Build the column header
|
|
71
|
+
const column = new Column({
|
|
72
|
+
header: new Text({ text: field.label }),
|
|
73
|
+
// Fiori UX: High priority for primary keys so they don't pop-in too early
|
|
74
|
+
demandPopin: !field.isKey,
|
|
75
|
+
minScreenWidth: field.isKey ? "" : "Tablet",
|
|
76
|
+
popinDisplay: "Inline"
|
|
77
|
+
});
|
|
78
|
+
table.addColumn(column);
|
|
79
|
+
|
|
80
|
+
// Instantiate the cell template (plugin)
|
|
81
|
+
const plugin = registry.getPlugin(field.type);
|
|
82
|
+
|
|
83
|
+
// In table lists, the binding path is relative to the row's context context (e.g. "{fieldName}")
|
|
84
|
+
const control = plugin.render(field, field.fieldName);
|
|
85
|
+
templateCells.push(control);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Create the row template and bind it to the table via the tableName array path
|
|
89
|
+
const rowTemplate = new ColumnListItem({
|
|
90
|
+
cells: templateCells
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// The absolute binding path from the StateManager for this table (using the named model 'meta')
|
|
94
|
+
table.bindItems({
|
|
95
|
+
path: `meta>/${tableMeta.tableName}`,
|
|
96
|
+
template: rowTemplate,
|
|
97
|
+
templateShareable: false
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return table;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file library.ts
|
|
3
|
+
* @description Standard SAP UI5 library registration file. Exposes the MetaUI namespace
|
|
4
|
+
* and automatically bootstraps the internal PluginRegistry.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Core from "sap/ui/core/Core";
|
|
8
|
+
import { PluginRegistry } from "./core/PluginRegistry";
|
|
9
|
+
import { StringPlugin } from "./plugins/StringPlugin";
|
|
10
|
+
import { NumberPlugin } from "./plugins/NumberPlugin";
|
|
11
|
+
import { BooleanPlugin } from "./plugins/BooleanPlugin";
|
|
12
|
+
import { DropdownPlugin } from "./plugins/DropdownPlugin";
|
|
13
|
+
import { DatePlugin } from "./plugins/DatePlugin";
|
|
14
|
+
|
|
15
|
+
// Delegate to standard library registration
|
|
16
|
+
Core.initLibrary({
|
|
17
|
+
name: "nz.co.siliconst.ui5.metaui",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
dependencies: [
|
|
20
|
+
"sap.ui.core",
|
|
21
|
+
"sap.m",
|
|
22
|
+
"sap.ui.layout",
|
|
23
|
+
"sap.ui.table"
|
|
24
|
+
],
|
|
25
|
+
controls: [
|
|
26
|
+
"nz.co.siliconst.ui5.metaui.controls.GeneratorHost"
|
|
27
|
+
],
|
|
28
|
+
elements: [],
|
|
29
|
+
interfaces: []
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Global Bootstrapper: Maps all standard primitive types to their respective plugins.
|
|
34
|
+
*/
|
|
35
|
+
const registry = PluginRegistry.getInstance();
|
|
36
|
+
registry.register("string", StringPlugin);
|
|
37
|
+
registry.register("number", NumberPlugin);
|
|
38
|
+
registry.register("boolean", BooleanPlugin);
|
|
39
|
+
registry.register("dropdown", DropdownPlugin);
|
|
40
|
+
registry.register("date", DatePlugin);
|
|
41
|
+
|
|
42
|
+
// Exporting window object safely for UI5 compatibility if needed
|
|
43
|
+
export default (window as any)["nz"]["co"]["siliconst"]["ui5"]["metaui"];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BasePlugin.ts
|
|
3
|
+
* @description Abstract foundation for all MetaUI plugins, enforcing lifecycle hooks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IPlugin } from "../interfaces/IPlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import { EventBus } from "../core/EventBus";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Abstract class providing baseline properties and event publishing hooks for all UI5 controls.
|
|
13
|
+
*/
|
|
14
|
+
export abstract class BasePlugin implements IPlugin {
|
|
15
|
+
/** The actual generated UI5 control. */
|
|
16
|
+
protected control: Control | null = null;
|
|
17
|
+
/** The schema configuration associated with this specific field. */
|
|
18
|
+
protected metadata: IFieldMetadata | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Must be implemented by concrete classes to generate the specific control.
|
|
22
|
+
* @param fieldMetadata The immutable schema configuration.
|
|
23
|
+
* @param bindingPath The exact model path where this field's data is stored.
|
|
24
|
+
*/
|
|
25
|
+
public abstract render(fieldMetadata: IFieldMetadata, bindingPath: string): Control;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Must be implemented by concrete classes to run data integrity checks.
|
|
29
|
+
*/
|
|
30
|
+
public abstract validate(): boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Triggered by the ConditionEngine when cross-field rules alter this field's metadata.
|
|
34
|
+
* @param newMetadata The dynamically updated rules.
|
|
35
|
+
*/
|
|
36
|
+
public onStateChange(newMetadata: IFieldMetadata): void {
|
|
37
|
+
this.metadata = newMetadata;
|
|
38
|
+
this.applyState();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Abstract hook for concrete plugins to re-bind their visibility or read-only states natively.
|
|
43
|
+
*/
|
|
44
|
+
protected abstract applyState(): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Utility method for concrete plugins to easily broadcast changes to the EventBus.
|
|
48
|
+
* @param newValue The newly entered/selected value.
|
|
49
|
+
*/
|
|
50
|
+
protected publishChange(newValue: any): void {
|
|
51
|
+
if (this.metadata) {
|
|
52
|
+
EventBus.getInstance().publishFieldChange({
|
|
53
|
+
fieldName: this.metadata.fieldName,
|
|
54
|
+
newValue: newValue
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file BooleanPlugin.ts
|
|
3
|
+
* @description Renders a sap.m.Switch for boolean states.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BasePlugin } from "./BasePlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import Switch from "sap/m/Switch";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handles rendering logic for toggleable booleans.
|
|
13
|
+
*/
|
|
14
|
+
export class BooleanPlugin extends BasePlugin {
|
|
15
|
+
public render(fieldMetadata: IFieldMetadata, bindingPath: string): Control {
|
|
16
|
+
this.metadata = fieldMetadata;
|
|
17
|
+
|
|
18
|
+
this.control = new Switch({
|
|
19
|
+
state: `{meta>${bindingPath}}`,
|
|
20
|
+
enabled: !fieldMetadata.isReadOnly,
|
|
21
|
+
change: (oEvent: any) => {
|
|
22
|
+
const val = oEvent.getParameter("state");
|
|
23
|
+
this.publishChange(val);
|
|
24
|
+
this.validate();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return this.control;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public validate(): boolean {
|
|
32
|
+
// Boolean switches inherently have a state (true/false) so they rarely fail validation.
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected applyState(): void {
|
|
37
|
+
if (this.control && this.metadata) {
|
|
38
|
+
const sw = this.control as Switch;
|
|
39
|
+
sw.setEnabled(!this.metadata.isReadOnly);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DatePlugin.ts
|
|
3
|
+
* @description Renders a sap.m.DatePicker natively bound to ISO date strings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BasePlugin } from "./BasePlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import DatePicker from "sap/m/DatePicker";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handles rendering logic for Date strings.
|
|
13
|
+
*/
|
|
14
|
+
export class DatePlugin extends BasePlugin {
|
|
15
|
+
public render(fieldMetadata: IFieldMetadata, bindingPath: string): Control {
|
|
16
|
+
this.metadata = fieldMetadata;
|
|
17
|
+
|
|
18
|
+
this.control = new DatePicker({
|
|
19
|
+
value: `{meta>${bindingPath}}`,
|
|
20
|
+
valueFormat: "yyyy-MM-dd",
|
|
21
|
+
displayFormat: "long",
|
|
22
|
+
editable: !fieldMetadata.isReadOnly,
|
|
23
|
+
required: fieldMetadata.isRequired,
|
|
24
|
+
change: (oEvent: any) => {
|
|
25
|
+
const val = oEvent.getParameter("value");
|
|
26
|
+
this.publishChange(val);
|
|
27
|
+
this.validate();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return this.control;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public validate(): boolean {
|
|
35
|
+
if (!this.control || !this.metadata) return true;
|
|
36
|
+
const dp = this.control as DatePicker;
|
|
37
|
+
const isValid = dp.isValidValue();
|
|
38
|
+
const val = dp.getValue();
|
|
39
|
+
|
|
40
|
+
if (this.metadata.isRequired && (!val || val.trim() === "")) {
|
|
41
|
+
dp.setValueState("Error" as any);
|
|
42
|
+
dp.setValueStateText("A valid date is required.");
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (val && !isValid) {
|
|
47
|
+
dp.setValueState("Error" as any);
|
|
48
|
+
dp.setValueStateText("Invalid date format.");
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
dp.setValueState("None" as any);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected applyState(): void {
|
|
57
|
+
if (this.control && this.metadata) {
|
|
58
|
+
const dp = this.control as DatePicker;
|
|
59
|
+
dp.setEditable(!this.metadata.isReadOnly);
|
|
60
|
+
dp.setRequired(this.metadata.isRequired);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DropdownPlugin.ts
|
|
3
|
+
* @description Renders a sap.m.Select utilizing the schema's valueHelp structure.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BasePlugin } from "./BasePlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import Select from "sap/m/Select";
|
|
9
|
+
import Item from "sap/ui/core/Item";
|
|
10
|
+
import Control from "sap/ui/core/Control";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles rendering logic for dropdown inputs.
|
|
14
|
+
*/
|
|
15
|
+
export class DropdownPlugin extends BasePlugin {
|
|
16
|
+
public render(fieldMetadata: IFieldMetadata, bindingPath: string): Control {
|
|
17
|
+
this.metadata = fieldMetadata;
|
|
18
|
+
|
|
19
|
+
const select = new Select({
|
|
20
|
+
selectedKey: `{meta>${bindingPath}}`,
|
|
21
|
+
enabled: !fieldMetadata.isReadOnly,
|
|
22
|
+
change: (oEvent: any) => {
|
|
23
|
+
const item = oEvent.getParameter("selectedItem");
|
|
24
|
+
const val = item ? item.getKey() : "";
|
|
25
|
+
this.publishChange(val);
|
|
26
|
+
this.validate();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (fieldMetadata.valueHelp && Array.isArray(fieldMetadata.valueHelp)) {
|
|
31
|
+
if (!fieldMetadata.isRequired) {
|
|
32
|
+
select.addItem(new Item({ key: "", text: "--- Select ---" }));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fieldMetadata.valueHelp.forEach(vh => {
|
|
36
|
+
select.addItem(new Item({ key: vh.key, text: vh.text }));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.control = select;
|
|
41
|
+
return this.control;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public validate(): boolean {
|
|
45
|
+
if (!this.control || !this.metadata) return true;
|
|
46
|
+
const select = this.control as Select;
|
|
47
|
+
const val = select.getSelectedKey();
|
|
48
|
+
|
|
49
|
+
if (this.metadata.isRequired && (!val || val.trim() === "")) {
|
|
50
|
+
select.setValueState("Error" as any);
|
|
51
|
+
select.setValueStateText("Selection is required.");
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
select.setValueState("None" as any);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected applyState(): void {
|
|
60
|
+
if (this.control && this.metadata) {
|
|
61
|
+
const select = this.control as Select;
|
|
62
|
+
select.setEnabled(!this.metadata.isReadOnly);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NumberPlugin.ts
|
|
3
|
+
* @description Renders a sap.m.StepInput for numeric data.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BasePlugin } from "./BasePlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import StepInput from "sap/m/StepInput";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handles rendering and logic for numeric inputs with precision and scale.
|
|
13
|
+
*/
|
|
14
|
+
export class NumberPlugin extends BasePlugin {
|
|
15
|
+
public render(fieldMetadata: IFieldMetadata, bindingPath: string): Control {
|
|
16
|
+
this.metadata = fieldMetadata;
|
|
17
|
+
|
|
18
|
+
this.control = new StepInput({
|
|
19
|
+
value: `{meta>${bindingPath}}`,
|
|
20
|
+
displayValuePrecision: fieldMetadata.scale || 0,
|
|
21
|
+
editable: !fieldMetadata.isReadOnly,
|
|
22
|
+
required: fieldMetadata.isRequired,
|
|
23
|
+
change: (oEvent: any) => {
|
|
24
|
+
const val = oEvent.getParameter("value");
|
|
25
|
+
this.publishChange(val);
|
|
26
|
+
this.validate();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return this.control;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public validate(): boolean {
|
|
34
|
+
if (!this.control || !this.metadata) return true;
|
|
35
|
+
const input = this.control as StepInput;
|
|
36
|
+
const val = input.getValue();
|
|
37
|
+
|
|
38
|
+
if (this.metadata.isRequired && (val === null || val === undefined)) {
|
|
39
|
+
input.setValueState("Error" as any);
|
|
40
|
+
input.setValueStateText("This field is required.");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
input.setValueState("None" as any);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected applyState(): void {
|
|
49
|
+
if (this.control && this.metadata) {
|
|
50
|
+
const input = this.control as StepInput;
|
|
51
|
+
input.setEditable(!this.metadata.isReadOnly);
|
|
52
|
+
input.setRequired(this.metadata.isRequired);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file StringPlugin.ts
|
|
3
|
+
* @description Renders a sap.m.Input for string data and validates lengths.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BasePlugin } from "./BasePlugin";
|
|
7
|
+
import { IFieldMetadata } from "../interfaces/ISchema";
|
|
8
|
+
import Input from "sap/m/Input";
|
|
9
|
+
import Control from "sap/ui/core/Control";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handles rendering and logic for basic text inputs.
|
|
13
|
+
*/
|
|
14
|
+
export class StringPlugin extends BasePlugin {
|
|
15
|
+
public render(fieldMetadata: IFieldMetadata, bindingPath: string): Control {
|
|
16
|
+
this.metadata = fieldMetadata;
|
|
17
|
+
|
|
18
|
+
this.control = new Input({
|
|
19
|
+
value: `{meta>${bindingPath}}`,
|
|
20
|
+
maxLength: fieldMetadata.maxLength || 0,
|
|
21
|
+
editable: !fieldMetadata.isReadOnly,
|
|
22
|
+
required: fieldMetadata.isRequired,
|
|
23
|
+
change: (oEvent: any) => {
|
|
24
|
+
const val = oEvent.getParameter("value");
|
|
25
|
+
this.publishChange(val);
|
|
26
|
+
this.validate();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return this.control;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public validate(): boolean {
|
|
34
|
+
if (!this.control || !this.metadata) return true;
|
|
35
|
+
const input = this.control as Input;
|
|
36
|
+
const val = input.getValue();
|
|
37
|
+
|
|
38
|
+
if (this.metadata.isRequired && (!val || val.trim() === "")) {
|
|
39
|
+
input.setValueState("Error" as any);
|
|
40
|
+
input.setValueStateText("This field is required.");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
input.setValueState("None" as any);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected applyState(): void {
|
|
49
|
+
if (this.control && this.metadata) {
|
|
50
|
+
const input = this.control as Input;
|
|
51
|
+
input.setEditable(!this.metadata.isReadOnly);
|
|
52
|
+
input.setRequired(this.metadata.isRequired);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|