diff --git a/.gitignore b/.gitignore
index 0494531c456..a4c6fb9badf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,4 +36,4 @@ package-lock.json
**/ttyd.sh
**/spring-shell.log
**/version.txt
-/.claude/scheduled_tasks.lock
+.claude/
diff --git a/components/data/data-csvim/src/main/java/org/eclipse/dirigible/components/data/csvim/processor/CsvimProcessor.java b/components/data/data-csvim/src/main/java/org/eclipse/dirigible/components/data/csvim/processor/CsvimProcessor.java
index ad9d1c0a22f..9b7f32273c2 100644
--- a/components/data/data-csvim/src/main/java/org/eclipse/dirigible/components/data/csvim/processor/CsvimProcessor.java
+++ b/components/data/data-csvim/src/main/java/org/eclipse/dirigible/components/data/csvim/processor/CsvimProcessor.java
@@ -218,7 +218,8 @@ public void process(CsvFile csvFile, InputStream content, String dataSourceName)
if (countBatch >= batchSize) {
countBatch = 0;
if (recordsToInsert.size() > 0) {
- insertCsvRecords(connection, targetSchema, tableMetadata, recordsToInsert, csvParser.getHeaderNames(), csvFile);
+ insertCsvRecords(dataSource, connection, targetSchema, tableMetadata, recordsToInsert, csvParser.getHeaderNames(),
+ csvFile);
}
if (Boolean.TRUE.equals(csvFile.getUpsert()) && recordsToUpdate.size() > 0) {
updateCsvRecords(connection, targetSchema, tableMetadata, recordsToUpdate, csvParser.getHeaderNames(), pkName,
@@ -230,12 +231,19 @@ public void process(CsvFile csvFile, InputStream content, String dataSourceName)
}
if (recordsToInsert.size() > 0) {
- insertCsvRecords(connection, targetSchema, tableMetadata, recordsToInsert, csvParser.getHeaderNames(), csvFile);
+ insertCsvRecords(dataSource, connection, targetSchema, tableMetadata, recordsToInsert, csvParser.getHeaderNames(), csvFile);
}
if (Boolean.TRUE.equals(csvFile.getUpsert()) && recordsToUpdate.size() > 0) {
updateCsvRecords(connection, targetSchema, tableMetadata, recordsToUpdate, csvParser.getHeaderNames(), pkName, csvFile);
}
updateSequence(csvFile, connection, countAll);
+ if (countAll > 0) {
+ // CSV inserts always carry an explicit PK value; the DB's IDENTITY counter is therefore
+ // not advanced by the load. Bump it to MAX(col)+1 so a subsequent INSERT … DEFAULT
+ // (e.g. from a Hibernate @GeneratedValue(IDENTITY) entity) doesn't collide on the seeded
+ // rows.
+ restartIdentityColumns(dataSource, connection, targetSchema, tableName);
+ }
}
}
@@ -281,6 +289,192 @@ public void updateSequence(CsvFile csvFile, Connection connection, int countAll)
}
}
+ /**
+ * Walks JDBC metadata for the freshly-loaded table, finds every {@code IS_AUTOINCREMENT == YES}
+ * column, and advances its internal counter to {@code MAX(col) + 1}. Most engines (H2, PostgreSQL,
+ * MSSQL, MySQL) do not auto-advance an identity counter when the INSERT supplies an explicit value,
+ * which leaves the next "generate-me-one" INSERT colliding with the seeded rows on PK = 1.
+ *
+ *
+ * Failures here are logged and swallowed — the CSV data is already persisted, and not every dialect
+ * supports identity restart in a portable way. Production traffic will surface the same collision
+ * the user already saw if the restart fails, but at least the seeded rows survive.
+ */
+ private void restartIdentityColumns(DirigibleDataSource dataSource, Connection connection, String schema, String tableName) {
+ String safeSchema;
+ String safeTable;
+ try {
+ safeSchema = requireSafeIdentifier(schema);
+ safeTable = requireSafeIdentifier(tableName);
+ } catch (IllegalArgumentException badIdent) {
+ logger.warn("Skipping IDENTITY restart — schema/table name not a safe SQL identifier: {}", badIdent.getMessage());
+ return;
+ }
+ try (ResultSet cols = connection.getMetaData()
+ .getColumns(connection.getCatalog(), safeSchema, safeTable, "%")) {
+ while (cols.next()) {
+ if (!"YES".equalsIgnoreCase(cols.getString("IS_AUTOINCREMENT"))) {
+ continue;
+ }
+ String safeColumn;
+ try {
+ safeColumn = requireSafeIdentifier(cols.getString("COLUMN_NAME"));
+ } catch (IllegalArgumentException badColumn) {
+ logger.warn("Skipping IDENTITY restart on [{}.{}] — column name not a safe SQL identifier: {}", safeSchema, safeTable,
+ badColumn.getMessage());
+ continue;
+ }
+ try {
+ long next = computeNextValue(connection, safeSchema, safeTable, safeColumn);
+ executeIdentityRestart(dataSource, connection, safeSchema, safeTable, safeColumn, next);
+ logger.info("Advanced IDENTITY counter on [{}.{}.{}] to [{}]", safeSchema, safeTable, safeColumn, next);
+ } catch (SQLException restartError) {
+ logger.warn("Failed to advance IDENTITY counter on [{}.{}.{}]: {}", safeSchema, safeTable, safeColumn,
+ restartError.getMessage());
+ }
+ }
+ } catch (SQLException metadataError) {
+ logger.warn("Failed to look up IDENTITY columns on [{}.{}]: {}", safeSchema, safeTable, metadataError.getMessage());
+ }
+ }
+
+ /**
+ * Computes the next value to seed an identity counter with: {@code MAX(col) + 1}. Inputs are
+ * pre-validated by {@link #requireSafeIdentifier} so concatenating them into the SQL string is
+ * safe.
+ */
+ private long computeNextValue(Connection connection, String safeSchema, String safeTable, String safeColumn) throws SQLException {
+ String sql = "SELECT MAX(" + quote(connection, safeColumn) + ") FROM " + qualifiedTable(connection, safeSchema, safeTable);
+ try (PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ long current = rs.getLong(1);
+ return rs.wasNull() ? 1L : current + 1L;
+ }
+ }
+ return 1L;
+ }
+
+ /**
+ * Emits dialect-specific DDL to advance an identity counter past {@code next}. Unknown dialects are
+ * skipped with an info-level log — better than throwing and aborting the whole CSVIM cycle. Inputs
+ * are pre-validated by {@link #requireSafeIdentifier} so direct string-concatenation is safe.
+ */
+ private void executeIdentityRestart(DirigibleDataSource dataSource, Connection connection, String safeSchema, String safeTable,
+ String safeColumn, long next) throws SQLException {
+ String sql;
+ if (dataSource.isOfType(DatabaseSystem.H2)) {
+ sql = "ALTER TABLE " + qualifiedTable(connection, safeSchema, safeTable) + " ALTER COLUMN " + quote(connection, safeColumn)
+ + " RESTART WITH " + next;
+ } else if (dataSource.isOfType(DatabaseSystem.POSTGRESQL)) {
+ // pg_get_serial_sequence case-folds its first argument unless inner double-quotes preserve
+ // the original casing. We must pass ""."" (with the quotes inside the SQL
+ // string literal) or PG will look up a lower-cased table that doesn't exist.
+ // setval(seq, n, false) -> next nextval() returns exactly n.
+ sql = "SELECT setval(pg_get_serial_sequence('\"" + safeSchema + "\".\"" + safeTable + "\"', '" + safeColumn + "'), " + next
+ + ", false)";
+ } else if (dataSource.isOfType(DatabaseSystem.MSSQL)) {
+ // RESEED stores the *current* identity, so the next assigned id is current + increment.
+ sql = "DBCC CHECKIDENT('" + safeSchema + "." + safeTable + "', RESEED, " + (next - 1) + ")";
+ } else if (dataSource.isOfType(DatabaseSystem.MYSQL) || dataSource.isOfType(DatabaseSystem.MARIADB)) {
+ sql = "ALTER TABLE `" + safeSchema + "`.`" + safeTable + "` AUTO_INCREMENT = " + next;
+ } else {
+ logger.info("IDENTITY restart not implemented for dialect [{}] — skipping [{}.{}.{}]", dataSource.getDatabaseSystem(),
+ safeSchema, safeTable, safeColumn);
+ return;
+ }
+ try (PreparedStatement ps = connection.prepareStatement(sql)) {
+ ps.execute();
+ }
+ }
+
+ /**
+ * MSSQL refuses to insert an explicit value into an IDENTITY column unless
+ * {@code SET IDENTITY_INSERT
+ *
+
+ * ON} is in effect. We only need to toggle the flag when the target table has an IDENTITY column
+ * AND the CSV headers actually supply a value for it.
+ */
+ private boolean csvSuppliesIdentityColumn(Connection connection, String schema, String tableName, List headerNames) {
+ try {
+ String safeSchema = requireSafeIdentifier(schema);
+ String safeTable = requireSafeIdentifier(tableName);
+ try (ResultSet cols = connection.getMetaData()
+ .getColumns(connection.getCatalog(), safeSchema, safeTable, "%")) {
+ while (cols.next()) {
+ if (!"YES".equalsIgnoreCase(cols.getString("IS_AUTOINCREMENT"))) {
+ continue;
+ }
+ String column = cols.getString("COLUMN_NAME");
+ for (String header : headerNames) {
+ if (header != null && header.equalsIgnoreCase(column)) {
+ return true;
+ }
+ }
+ }
+ }
+ } catch (SQLException | IllegalArgumentException probeError) {
+ logger.warn("Could not probe IDENTITY columns on [{}.{}] for IDENTITY_INSERT toggle: {}", schema, tableName,
+ probeError.getMessage());
+ }
+ return false;
+ }
+
+ /**
+ * Toggle MSSQL {@code SET IDENTITY_INSERT
+ *
+
+ * ON|OFF} around an explicit-id batch insert.
+ */
+ private void setMssqlIdentityInsert(Connection connection, String schema, String tableName, boolean on) {
+ String safeSchema;
+ String safeTable;
+ try {
+ safeSchema = requireSafeIdentifier(schema);
+ safeTable = requireSafeIdentifier(tableName);
+ } catch (IllegalArgumentException badIdent) {
+ logger.warn("Skipping SET IDENTITY_INSERT — schema/table name not a safe SQL identifier: {}", badIdent.getMessage());
+ return;
+ }
+ String sql = "SET IDENTITY_INSERT [" + safeSchema + "].[" + safeTable + "] " + (on ? "ON" : "OFF");
+ try (PreparedStatement ps = connection.prepareStatement(sql)) {
+ ps.execute();
+ } catch (SQLException e) {
+ logger.warn("Failed to {} IDENTITY_INSERT on [{}.{}]: {}", on ? "enable" : "disable", safeSchema, safeTable, e.getMessage());
+ }
+ }
+
+ /** Returns the schema-qualified, dialect-quoted table identifier. */
+ private static String qualifiedTable(Connection connection, String schema, String tableName) throws SQLException {
+ String q = connection.getMetaData()
+ .getIdentifierQuoteString();
+ return q + schema + q + "." + q + tableName + q;
+ }
+
+ /** Returns a dialect-quoted identifier. */
+ private static String quote(Connection connection, String identifier) throws SQLException {
+ String q = connection.getMetaData()
+ .getIdentifierQuoteString();
+ return q + identifier + q;
+ }
+
+ private static final java.util.regex.Pattern SAFE_IDENTIFIER = java.util.regex.Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
+
+ /**
+ * Whitelist-validates a SQL identifier (schema/table/column name) coming from JDBC catalog metadata
+ * or {@link CsvFile} config. Lets callers concatenate the value into SQL string literals or DDL
+ * without parameter binding (which is not available for DDL identifiers anyway). Names that don't
+ * fit {@code [A-Za-z_][A-Za-z0-9_]*} are rejected — this is intentionally narrower than the SQL
+ * standard so the same value is also safe to log.
+ */
+ private static String requireSafeIdentifier(String value) {
+ if (value == null || !SAFE_IDENTIFIER.matcher(value)
+ .matches()) {
+ throw new IllegalArgumentException("not a safe SQL identifier: [" + value + "]");
+ }
+ return value;
+ }
+
private boolean isDefaultDataSource(String dataSourceName) {
return null == dataSourceName || dataSourceName.equalsIgnoreCase(defaultDataSourceName);
}
@@ -423,14 +617,29 @@ private int getCsvDataBatchSize() {
* @param headerNames the header names
* @param csvFile the csv file
*/
- private void insertCsvRecords(Connection connection, String schema, TableMetadata tableModel, List recordsToProcess,
- List headerNames, CsvFile csvFile) {
+ private void insertCsvRecords(DirigibleDataSource dataSource, Connection connection, String schema, TableMetadata tableModel,
+ List recordsToProcess, List headerNames, CsvFile csvFile) {
try {
List csvRecords = recordsToProcess.stream()
.map(e -> new CsvRecord(e, tableModel, headerNames,
csvFile.getDistinguishEmptyFromNull(), csvFile.getParsedLocale()))
.collect(Collectors.toList());
- csvProcessor.insert(connection, schema, tableModel, csvRecords, headerNames, csvFile);
+
+ // MSSQL refuses INSERTs that supply a value for an IDENTITY column unless
+ // SET IDENTITY_INSERT ON is in effect. Every other CSVIM-supported engine
+ // happily accepts the explicit value and just leaves its own counter behind.
+ boolean toggleMssqlIdentityInsert = dataSource.isOfType(DatabaseSystem.MSSQL)
+ && csvSuppliesIdentityColumn(connection, schema, tableModel.getName(), headerNames);
+ if (toggleMssqlIdentityInsert) {
+ setMssqlIdentityInsert(connection, schema, tableModel.getName(), true);
+ }
+ try {
+ csvProcessor.insert(connection, schema, tableModel, csvRecords, headerNames, csvFile);
+ } finally {
+ if (toggleMssqlIdentityInsert) {
+ setMssqlIdentityInsert(connection, schema, tableModel.getName(), false);
+ }
+ }
} catch (Exception e) {
String csvRecordValue = e.getMessage();
CsvimUtils.logProcessorErrors(String.format(PROBLEM_MESSAGE_INSERT_RECORD, tableModel.getName(), csvRecordValue),
diff --git a/components/data/data-store-java/src/main/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapper.java b/components/data/data-store-java/src/main/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapper.java
index 28a4b235691..c4418a6b018 100644
--- a/components/data/data-store-java/src/main/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapper.java
+++ b/components/data/data-store-java/src/main/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapper.java
@@ -214,14 +214,20 @@ private static String hibernateTypeFor(Class> javaType) {
if (javaType == java.sql.Time.class) {
return "time";
}
- if (javaType == java.sql.Timestamp.class || javaType == Date.class || javaType == LocalDateTime.class) {
+ if (javaType == java.sql.Timestamp.class || javaType == Date.class) {
return "timestamp";
}
+ // java.time.* types need their FQN in HBM XML — Hibernate's basic-type registry resolves them
+ // by class name, but the short names ("Instant" / "LocalDate" / "LocalDateTime") aren't
+ // registered and Hibernate falls back to Class.forName("Instant"), which throws.
+ if (javaType == LocalDateTime.class) {
+ return "java.time.LocalDateTime";
+ }
if (javaType == Instant.class) {
- return "Instant";
+ return "java.time.Instant";
}
if (javaType == LocalDate.class) {
- return "LocalDate";
+ return "java.time.LocalDate";
}
if (javaType == UUID.class) {
return "uuid";
diff --git a/components/data/data-store-java/src/test/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapperTest.java b/components/data/data-store-java/src/test/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapperTest.java
index bf6eed6662d..2d362d4b744 100644
--- a/components/data/data-store-java/src/test/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapperTest.java
+++ b/components/data/data-store-java/src/test/java/org/eclipse/dirigible/components/data/store/java/hbm/JavaEntityToHbmMapperTest.java
@@ -71,6 +71,21 @@ void records_audit_flag_for_createdAt() {
.any());
}
+ /**
+ * java.time.* properties must be emitted with their FQN as the Hibernate type. The short names
+ * (e.g. "Instant") aren't registered in Hibernate's basic-type registry and would trip
+ * {@code Class.forName("Instant")} → {@code ClassLoadingException} when the SessionFactory is
+ * built.
+ */
+ @Test
+ void emits_fqn_for_java_time_types() {
+ String xml = JavaEntityToHbmMapper.map("p::Audited", Audited.class)
+ .descriptor()
+ .serialize();
+ assertTrue(xml.contains("type=\"java.time.Instant\""), xml);
+ assertTrue(xml.indexOf("type=\"Instant\"") < 0, "must not emit bare short name 'Instant': " + xml);
+ }
+
@Test
void skips_transient_fields() {
JavaEntityToHbmMapper.Result r = JavaEntityToHbmMapper.map("p::WithTransient", WithTransient.class);
diff --git a/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/domain/TableColumn.java b/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/domain/TableColumn.java
index 67cf470e1fe..3c2da8188ad 100644
--- a/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/domain/TableColumn.java
+++ b/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/domain/TableColumn.java
@@ -11,6 +11,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
import jakarta.persistence.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@@ -75,6 +76,7 @@ public class TableColumn {
private boolean unique;
@Column(name = "COLUMN_AUTOINCREMENT", columnDefinition = "BOOLEAN", nullable = true)
@Expose
+ @SerializedName(value = "autoincrement", alternate = {"identity"})
private boolean autoincrement;
/** The table. */
@ManyToOne(fetch = FetchType.EAGER, optional = false)
diff --git a/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/synchronizer/SchemasSynchronizer.java b/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/synchronizer/SchemasSynchronizer.java
index 58debcba973..273fdad904b 100644
--- a/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/synchronizer/SchemasSynchronizer.java
+++ b/components/data/data-structures/src/main/java/org/eclipse/dirigible/components/data/structures/synchronizer/SchemasSynchronizer.java
@@ -339,6 +339,15 @@ private static void setColumnAttributes(JsonObject column, TableColumn columnMod
&& column.get("nullable")
.getAsBoolean());
+ // The .table parser deserialises this field via Gson's @SerializedName(value="autoincrement",
+ // alternate={"identity"}); the .schema parser is hand-rolled and needs to honour both
+ // spellings explicitly. "identity" is the canonical name in every existing .schema fixture.
+ JsonElement identityElement = column.get("identity");
+ if (identityElement == null || identityElement.isJsonNull()) {
+ identityElement = column.get("autoincrement");
+ }
+ columnModel.setAutoincrement(identityElement != null && !identityElement.isJsonNull() && identityElement.getAsBoolean());
+
String defaultValue = getJsonElementValue(column, "defaultValue", null);
columnModel.setDefaultValue(defaultValue);
diff --git a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerClassConsumer.java b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerClassConsumer.java
index d6616720f88..db8bd2a0caa 100644
--- a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerClassConsumer.java
+++ b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerClassConsumer.java
@@ -109,7 +109,10 @@ public void onClassLoaded(LoadedClass info) {
if (openApiPublisher != null) {
openApiPublisher.publish(entry);
}
- } catch (RuntimeException e) {
+ } catch (Exception | LinkageError e) {
+ // Includes LinkageError so a controller whose @Inject field references a class that failed
+ // to compile (NoClassDefFoundError on getDeclaredFields) doesn't abort the rebuild and take
+ // every other previously-registered controller with it.
LOGGER.error("Failed to register controller [{}]: {}", info.fqn(), e.getMessage(), e);
}
}
diff --git a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerInvoker.java b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerInvoker.java
index b4d3c199bc1..ca62b5cf3f8 100644
--- a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerInvoker.java
+++ b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/controller/ControllerInvoker.java
@@ -50,7 +50,15 @@ public ControllerInvoker(ObjectProvider objectMapperProvider) {
// Prefer the Spring-managed primary ObjectMapper (so users get the platform's Jackson
// configuration); fall back to a fresh one if no bean is registered — e.g. in minimal test
// contexts where JacksonAutoConfiguration didn't fire.
- this.objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
+ //
+ // Work on a copy + ServiceLoader-discover modules so we get JavaTimeModule (jsr310) etc.
+ // without mutating the shared Spring bean. Spring Boot 4 / Jackson 3 no longer registers
+ // java.time.* handlers by default, so a raw Spring mapper rejects LocalDate / Instant fields
+ // with REQUIRE_HANDLERS_FOR_JAVA8_TIMES; this ensures generated entities with audit / date
+ // fields bind cleanly from @Body JSON.
+ this.objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new)
+ .copy()
+ .findAndRegisterModules();
}
/** Test-friendly constructor — bypasses Spring's ObjectProvider. */
@@ -67,6 +75,14 @@ public void invoke(RouteMatch match, HttpServletRequest request, HttpServletResp
try {
args = bindParameters(route, match.pathParameters(), request, response);
} catch (BindingException e) {
+ // Spring Boot 4 strips ResponseStatusException.getReason() from the JSON response body, so
+ // without this log line the caller sees a bare 400 with no clue what failed (e.g. a
+ // mistyped JSON field that Jackson couldn't coerce into the target Java type).
+ LOGGER.warn("Bad request binding for [{}#{}]: {}", match.entry()
+ .fqn(),
+ route.method()
+ .getName(),
+ e.getMessage());
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage(), e);
}
diff --git a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/runtime/JavaLoader.java b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/runtime/JavaLoader.java
index 399dd716ca2..4ecfd669ab3 100644
--- a/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/runtime/JavaLoader.java
+++ b/components/engine/engine-java/src/main/java/org/eclipse/dirigible/engine/java/runtime/JavaLoader.java
@@ -161,7 +161,11 @@ private static void notifyAll(List consumers, List c.fqn()
+ .equals("client.Bystander")));
+ }
+
@Test
void compile_error_is_isolated_to_the_offending_unit() {
JavaLoader.RebuildResult result =
@@ -155,6 +175,35 @@ public void handle(HttpServletRequest req, HttpServletResponse resp) throws Exce
""".formatted(pkg, simple, body));
}
+ /**
+ * Test consumer that throws {@link NoClassDefFoundError} on {@code onClassLoaded} for one named
+ * FQN, accepts every class. Mirrors the production failure mode where
+ * {@code ControllerClassConsumer.injectDependencies}'s {@code getDeclaredFields()} fails because a
+ * field type wasn't compiled.
+ */
+ private static final class ThrowingConsumer implements JavaClassConsumer {
+ private final String poisonFqn;
+
+ ThrowingConsumer(String poisonFqn) {
+ this.poisonFqn = poisonFqn;
+ }
+
+ @Override
+ public boolean accepts(Class> clazz) {
+ return true;
+ }
+
+ @Override
+ public void onClassLoaded(LoadedClass info) {
+ if (poisonFqn.equals(info.fqn())) {
+ throw new NoClassDefFoundError("simulated missing dep on " + info.fqn());
+ }
+ }
+
+ @Override
+ public void onClassUnloaded(LoadedClass info) {}
+ }
+
/** Test consumer that records every load/unload — accepts every class. */
private static final class RecordingConsumer implements JavaClassConsumer {
final List loaded = new ArrayList<>();
diff --git a/components/group/group-templates/pom.xml b/components/group/group-templates/pom.xml
index 7baca06c604..161c8560469 100644
--- a/components/group/group-templates/pom.xml
+++ b/components/group/group-templates/pom.xml
@@ -19,6 +19,10 @@
org.eclipse.dirigible
dirigible-components-template-application-angular
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-angular-java
+
org.eclipse.dirigible
dirigible-components-template-application-angular-v2
@@ -27,6 +31,10 @@
org.eclipse.dirigible
dirigible-components-template-application-dao
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-dao-java
+
org.eclipse.dirigible
dirigible-components-template-application-dao-v2
@@ -55,6 +63,10 @@
org.eclipse.dirigible
dirigible-components-template-application-rest
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-rest-java
+
org.eclipse.dirigible
dirigible-components-template-application-rest-v2
@@ -67,6 +79,10 @@
org.eclipse.dirigible
dirigible-components-template-application-ui-angular
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-ui-angular-java
+
org.eclipse.dirigible
dirigible-components-template-application-ui-angular-v2
diff --git a/components/pom.xml b/components/pom.xml
index 1469682af16..a9b02156b1c 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -237,8 +237,10 @@
template/template-application-angular
+ template/template-application-angular-java
template/template-application-angular-v2
template/template-application-dao
+ template/template-application-dao-java
template/template-application-dao-v2
template/template-application-data
template/template-application-data-v2
@@ -246,9 +248,11 @@
template/template-application-feed-v2
template/template-application-odata
template/template-application-rest
+ template/template-application-rest-java
template/template-application-rest-v2
template/template-application-schema
template/template-application-ui-angular
+ template/template-application-ui-angular-java
template/template-application-ui-angular-v2
template/template-bpm
template/template-camel
@@ -1360,6 +1364,11 @@
dirigible-components-template-application-angular
${project.version}
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-angular-java
+ ${project.version}
+
org.eclipse.dirigible
dirigible-components-template-application-angular-v2
@@ -1370,6 +1379,11 @@
dirigible-components-template-application-dao
${project.version}
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-dao-java
+ ${project.version}
+
org.eclipse.dirigible
dirigible-components-template-application-dao-v2
@@ -1405,6 +1419,11 @@
dirigible-components-template-application-rest
${project.version}
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-rest-java
+ ${project.version}
+
org.eclipse.dirigible
dirigible-components-template-application-rest-v2
@@ -1420,6 +1439,11 @@
dirigible-components-template-application-ui-angular
${project.version}
+
+ org.eclipse.dirigible
+ dirigible-components-template-application-ui-angular-java
+ ${project.version}
+
org.eclipse.dirigible
dirigible-components-template-application-ui-angular-v2
diff --git a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/styles/fonts.css b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/styles/fonts.css
index 3c4c348820e..15ec7c1a831 100644
--- a/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/styles/fonts.css
+++ b/components/resources/application-core/src/main/resources/META-INF/dirigible/application-core/styles/fonts.css
@@ -1,3 +1,14 @@
+/**
+ * Copyright (c) 2010-2026 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors
+ * SPDX-License-Identifier: EPL-2.0
+ */
/* open-sans-latin-300-normal */
@font-face {
font-family: "Open Sans";
diff --git a/components/template/template-application-angular-java/pom.xml b/components/template/template-application-angular-java/pom.xml
new file mode 100644
index 00000000000..dcf1c2ece15
--- /dev/null
+++ b/components/template/template-application-angular-java/pom.xml
@@ -0,0 +1,20 @@
+
+ 4.0.0
+
+
+ org.eclipse.dirigible
+ dirigible-components-parent
+ 13.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Components - Template - Application (Angular) - Java
+ dirigible-components-template-application-angular-java
+ jar
+
+
+ ../generation-header.txt
+
+
+
diff --git a/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/project.json b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/project.json
new file mode 100644
index 00000000000..91ccb1d11ac
--- /dev/null
+++ b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/project.json
@@ -0,0 +1,18 @@
+{
+ "guid": "template-application-angular-java",
+ "dependencies": [
+ {
+ "guid": "template-application-schema"
+ },
+ {
+ "guid": "template-application-dao-java"
+ },
+ {
+ "guid": "template-application-rest-java"
+ },
+ {
+ "guid": "template-application-ui-angular-java"
+ }
+ ],
+ "actions": []
+}
diff --git a/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.extension b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.extension
new file mode 100644
index 00000000000..ead843d6815
--- /dev/null
+++ b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.extension
@@ -0,0 +1,5 @@
+{
+ "extensionPoint": "platform-templates",
+ "module": "template-application-angular-java/template/template.js",
+ "description": "Application Template (with Angular UI) - Java"
+}
diff --git a/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.js b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.js
new file mode 100644
index 00000000000..1e03b5ad84d
--- /dev/null
+++ b/components/template/template-application-angular-java/src/main/resources/META-INF/dirigible/template-application-angular-java/template/template.js
@@ -0,0 +1,34 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+import * as schemaTemplateManager from "template-application-schema/template/template";
+import * as uiAngularjsTemplateManager from "template-application-ui-angular-java/template/template";
+import * as generateUtils from "service-generate/template/generateUtils";
+import * as parameterUtils from "service-generate/template/parameterUtils";
+
+export function generate(model, parameters) {
+ model = JSON.parse(model).model;
+ let templateSources = getTemplate(parameters).sources;
+ parameters.javaRuntime = true;
+ parameterUtils.process(model, parameters);
+ return generateUtils.generateFiles(model, parameters, templateSources);
+};
+
+export function getTemplate(parameters) {
+ let schemaTemplate = schemaTemplateManager.getTemplate(parameters);
+ let uiAngularjsTemplate = uiAngularjsTemplateManager.getTemplate(parameters);
+
+ let templateSources = [];
+ templateSources = templateSources.concat(schemaTemplate.sources);
+ templateSources = templateSources.concat(uiAngularjsTemplate.sources);
+
+ return {
+ name: "Application - Full Stack - Java",
+ description: "Full stack application with a Database Schema, Java REST Services and an AngularJS UI",
+ extension: "model",
+ sources: templateSources,
+ parameters: parameterUtils.getUniqueParameters(schemaTemplate.parameters, uiAngularjsTemplate.parameters)
+ };
+};
diff --git a/components/template/template-application-dao-java/pom.xml b/components/template/template-application-dao-java/pom.xml
new file mode 100644
index 00000000000..03acb105c21
--- /dev/null
+++ b/components/template/template-application-dao-java/pom.xml
@@ -0,0 +1,24 @@
+
+ 4.0.0
+
+
+ org.eclipse.dirigible
+ dirigible-components-parent
+ 13.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Components - Template - Application - DAO - Java
+ dirigible-components-template-application-dao-java
+ jar
+
+
+ generate-sources
+ template-application-dao-java
+ template-application-dao-java
+
+ ../generation-header.txt
+
+
+
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Entity.java.template b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Entity.java.template
new file mode 100644
index 00000000000..b9ccd163d46
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Entity.java.template
@@ -0,0 +1,45 @@
+package gen.${javaGenFolderName}.data.${javaPerspectiveName};
+
+import org.eclipse.dirigible.engine.java.annotations.Column;
+import org.eclipse.dirigible.engine.java.annotations.CreatedAt;
+import org.eclipse.dirigible.engine.java.annotations.CreatedBy;
+import org.eclipse.dirigible.engine.java.annotations.Documentation;
+import org.eclipse.dirigible.engine.java.annotations.Entity;
+import org.eclipse.dirigible.engine.java.annotations.GeneratedValue;
+import org.eclipse.dirigible.engine.java.annotations.GenerationType;
+import org.eclipse.dirigible.engine.java.annotations.Id;
+import org.eclipse.dirigible.engine.java.annotations.Table;
+import org.eclipse.dirigible.engine.java.annotations.UpdatedAt;
+import org.eclipse.dirigible.engine.java.annotations.UpdatedBy;
+
+@Entity
+@Table(name = "${tablePrefix}${dataName}")
+@Documentation("${name} entity mapping")
+public class ${name}Entity {
+
+#foreach($property in $properties)
+#if($property.dataPrimaryKey)
+ @Id
+#end
+#if($property.dataAutoIncrement)
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+#end
+#if($property.auditType == "CREATED_AT")
+ @CreatedAt
+#elseif($property.auditType == "CREATED_BY")
+ @CreatedBy
+#elseif($property.auditType == "UPDATED_AT")
+ @UpdatedAt
+#elseif($property.auditType == "UPDATED_BY")
+ @UpdatedBy
+#end
+ @Column(name = "${property.dataName}"#if($property.dataLength && $property.dataType != "DECIMAL"), length = ${property.dataLength}#end#if($property.dataPrecision), precision = ${property.dataPrecision}#end#if($property.dataScale), scale = ${property.dataScale}#end#if(!$property.dataPrimaryKey && $property.dataNotNull), nullable = false#elseif(!$property.dataPrimaryKey && !$property.dataNotNull), nullable = true#end#if($property.dataUnique), unique = true#end)
+#if($property.description)
+ @Documentation("${property.description}")
+#else
+ @Documentation("${property.name}")
+#end
+ public ${property.dataTypeJavaClass} ${property.name};
+
+#end
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template
new file mode 100644
index 00000000000..3804ed07aea
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/Repository.java.template
@@ -0,0 +1,44 @@
+package gen.${javaGenFolderName}.data.${javaPerspectiveName};
+
+import org.eclipse.dirigible.components.data.store.java.repository.JavaRepository;
+import org.eclipse.dirigible.engine.java.annotations.Repository;
+
+#foreach ($property in $properties)
+ #if($property.isCalculatedProperty && $property.calculatedPropertyExpressionCreate)
+ #set($haveCalculatedPropertyOnCreate = "true")
+ #end
+ #if($property.isCalculatedProperty && $property.calculatedPropertyExpressionUpdate)
+ #set($haveCalculatedPropertyOnUpdate = "true")
+ #end
+#end
+@Repository
+public class ${name}Repository extends JavaRepository<${name}Entity> {
+
+ public ${name}Repository() {
+ super(${name}Entity.class);
+ }
+#if($haveCalculatedPropertyOnCreate)
+
+ @Override
+ public ${name}Entity save(${name}Entity entity) {
+#foreach ($property in $properties)
+#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionCreate)
+ entity.${property.name} = ${property.calculatedPropertyExpressionCreate};
+#end
+#end
+ return super.save(entity);
+ }
+#end
+#if($haveCalculatedPropertyOnUpdate)
+
+ @Override
+ public ${name}Entity update(${name}Entity entity) {
+#foreach ($property in $properties)
+#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionUpdate)
+ entity.${property.name} = ${property.calculatedPropertyExpressionUpdate};
+#end
+#end
+ return super.update(entity);
+ }
+#end
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/entity.extensionpoint.template b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/entity.extensionpoint.template
new file mode 100644
index 00000000000..c6aec60938d
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/data/entity.extensionpoint.template
@@ -0,0 +1,4 @@
+{
+ "name": "${projectName}-${perspectiveName}-${name}",
+ "description": "Extension Point for the ${projectName}-${perspectiveName}-${name} entity"
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json
new file mode 100644
index 00000000000..f786d00d734
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json
@@ -0,0 +1,5 @@
+{
+ "guid": "template-application-dao-java",
+ "dependencies": [],
+ "actions": []
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json.mjs b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json.mjs
new file mode 100644
index 00000000000..ec680a2377d
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/project.json.mjs
@@ -0,0 +1,22 @@
+import { workspace } from '@aerokit/sdk/platform';
+
+export function generate(json) {
+ const parameters = JSON.parse(json);
+
+ const newProjectFile = JSON.stringify({
+ 'guid': parameters.projectName
+ });
+
+ const currenctWorkspace = workspace.getWorkspace(parameters.workspaceName);
+ const currentProject = currenctWorkspace.getProject(parameters.projectName);
+ const maybeProjectFile = currentProject.getFile('project.json');
+
+ if (maybeProjectFile.exists()) {
+ const projectFileContent = maybeProjectFile.getText();
+ if (projectFileContent.trim() !== '') {
+ return projectFileContent;
+ }
+ }
+
+ return newProjectFile;
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.extension b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.extension
new file mode 100644
index 00000000000..23d636a32c5
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.extension
@@ -0,0 +1,5 @@
+{
+ "module": "template-application-dao-java/template/template.js",
+ "extensionPoint": "platform-templates",
+ "description": "Application Template - DAO - Java"
+}
diff --git a/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.js b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.js
new file mode 100644
index 00000000000..d80bf882f2a
--- /dev/null
+++ b/components/template/template-application-dao-java/src/main/resources/META-INF/dirigible/template-application-dao-java/template/template.js
@@ -0,0 +1,65 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+import * as generateUtils from "service-generate/template/generateUtils";
+import * as parameterUtils from "service-generate/template/parameterUtils";
+
+export function generate(model, parameters) {
+ model = JSON.parse(model).model;
+ let templateSources = getTemplate(parameters).sources;
+ parameterUtils.process(model, parameters)
+ return generateUtils.generateFiles(model, parameters, templateSources);
+};
+
+export function getTemplate(parameters) {
+ return {
+ name: "Application - DAO - Java",
+ description: "Application with DAO (Java)",
+ extension: "model",
+ sources: [
+ {
+ location: "/template-application-dao-java/data/Repository.java.template",
+ action: "generate",
+ rename: "gen/{{javaGenFolderName}}/data/{{javaPerspectiveName}}/{{name}}Repository.java",
+ engine: "velocity",
+ collection: "daoModels"
+ },
+ {
+ location: "/template-application-dao-java/data/Entity.java.template",
+ action: "generate",
+ rename: "gen/{{javaGenFolderName}}/data/{{javaPerspectiveName}}/{{name}}Entity.java",
+ engine: "velocity",
+ collection: "daoModels"
+ },
+ {
+ location: "/template-application-dao-java/data/entity.extensionpoint.template",
+ action: "generate",
+ rename: "gen/{{javaGenFolderName}}/data/{{javaPerspectiveName}}/{{name}}.extensionpoint",
+ engine: "velocity",
+ collection: "daoModels"
+ },
+ {
+ location: "/template-application-dao-java/project.json.mjs",
+ action: "generate",
+ rename: "project.json",
+ engine: "javascript",
+ }
+ ],
+ parameters: [
+ {
+ name: "tablePrefix",
+ label: "Table Prefix",
+ placeholder: "Table prefix",
+ required: false
+ },
+ {
+ name: "dataSource",
+ label: "Data Source",
+ placeholder: "Data Source (DefaultDB)",
+ required: false
+ }
+ ]
+ };
+};
diff --git a/components/template/template-application-rest-java/pom.xml b/components/template/template-application-rest-java/pom.xml
new file mode 100644
index 00000000000..4d76a26079d
--- /dev/null
+++ b/components/template/template-application-rest-java/pom.xml
@@ -0,0 +1,24 @@
+
+ 4.0.0
+
+
+ org.eclipse.dirigible
+ dirigible-components-parent
+ 13.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Components - Template - Application - REST - Java
+ dirigible-components-template-application-rest-java
+ jar
+
+
+ generate-sources
+ template-application-rest-java
+ template-application-rest-java
+
+ ../generation-header.txt
+
+
+
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/api/EntityController.java.template b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/api/EntityController.java.template
new file mode 100644
index 00000000000..9531ba0b7e8
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/api/EntityController.java.template
@@ -0,0 +1,285 @@
+package gen.${javaGenFolderName}.api.${javaPerspectiveName};
+
+#foreach ($property in $properties)
+#if($property.roleRead || $property.roleWrite)
+#set($isEntityPropertySecurityEnabled = "true")
+#end
+#end
+#set($needsRoles = false)
+#if($perspectiveRole || $roleRead || $roleWrite || $isEntityPropertySecurityEnabled)
+#set($needsRoles = true)
+#end
+import gen.${javaGenFolderName}.data.${javaPerspectiveName}.${name}Entity;
+import gen.${javaGenFolderName}.data.${javaPerspectiveName}.${name}Repository;
+
+import org.eclipse.dirigible.components.api.security.UserFacade;
+import org.eclipse.dirigible.engine.java.annotations.Documentation;
+import org.eclipse.dirigible.engine.java.annotations.Inject;
+import org.eclipse.dirigible.engine.java.annotations.http.Body;
+import org.eclipse.dirigible.engine.java.annotations.http.Controller;
+import org.eclipse.dirigible.engine.java.annotations.http.Delete;
+import org.eclipse.dirigible.engine.java.annotations.http.Get;
+import org.eclipse.dirigible.engine.java.annotations.http.PathParam;
+import org.eclipse.dirigible.engine.java.annotations.http.Post;
+import org.eclipse.dirigible.engine.java.annotations.http.Put;
+import org.eclipse.dirigible.engine.java.annotations.http.QueryParam;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+@Controller
+@Documentation("${projectName} - ${name} Controller")
+public class ${name}Controller {
+
+ private static final Set FILTER_FIELDS = Set.of(#foreach($property in $properties)"${property.name}"#if($foreach.hasNext), #end#end);
+
+ @Inject
+ private ${name}Repository repository;
+
+ @Get
+ @Documentation("List ${name}")
+ public List<${name}Entity> getAll(@QueryParam("$limit") Integer limit,
+ @QueryParam("$offset") Integer offset#if($layoutType == "MANAGE_DETAILS" || $layoutType == "LIST_DETAILS"),
+ @QueryParam("${masterEntityId}") String ${masterEntityId}#end) {
+#if($needsRoles)
+ checkPermissions("read");
+#end
+ int actualLimit = limit != null ? limit.intValue() : 20;
+ int actualOffset = offset != null ? offset.intValue() : 0;
+#if($layoutType == "MANAGE_DETAILS" || $layoutType == "LIST_DETAILS")
+ List<${name}Entity> result;
+ if (${masterEntityId} != null) {
+ Map params = new LinkedHashMap<>();
+ params.put("${masterEntityId}", ${masterEntityId});
+ result = repository.query("from ${name}Entity e where e.${masterEntityId} = :${masterEntityId}", params);
+ } else {
+ result = repository.findAll(actualLimit, actualOffset);
+ }
+#else
+ List<${name}Entity> result = repository.findAll(actualLimit, actualOffset);
+#end
+#if($isEntityPropertySecurityEnabled)
+ result.forEach(this::redactRead);
+#end
+ return result;
+ }
+
+ @Get("/count")
+ @Documentation("Count ${name}")
+ public Map count() {
+#if($needsRoles)
+ checkPermissions("read");
+#end
+ return Map.of("count", repository.count());
+ }
+
+ @Post("/count")
+ @Documentation("Count ${name} with filter")
+ public Map countWithFilter(@Body Map filter) {
+#if($needsRoles)
+ checkPermissions("read");
+#end
+ return Map.of("count", (long) runFilter(filter).size());
+ }
+
+ @Post("/search")
+ @Documentation("Search ${name}")
+ public List<${name}Entity> search(@Body Map filter) {
+#if($needsRoles)
+ checkPermissions("read");
+#end
+ List<${name}Entity> result = runFilter(filter);
+#if($isEntityPropertySecurityEnabled)
+ result.forEach(this::redactRead);
+#end
+ return result;
+ }
+
+ @Get("/{id}")
+ @Documentation("Get ${name} by id")
+ public ${name}Entity getById(@PathParam("id") #foreach($property in $properties)#if($property.dataPrimaryKey)${property.dataTypeJavaClass}#end#end id) {
+#if($needsRoles)
+ checkPermissions("read");
+#end
+ ${name}Entity entity = repository.findOne(id)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "${name} not found"));
+#if($isEntityPropertySecurityEnabled)
+ redactRead(entity);
+#end
+ return entity;
+ }
+
+ @Post
+ @Documentation("Create ${name}")
+ public ${name}Entity create(@Body ${name}Entity entity) {
+#if($needsRoles)
+ checkPermissions("write");
+#end
+#if($isEntityPropertySecurityEnabled)
+ applyOnCreate(entity);
+#end
+ validate(entity);
+ return repository.save(entity);
+ }
+
+ @Put("/{id}")
+ @Documentation("Update ${name} by id")
+ public ${name}Entity update(@PathParam("id") #foreach($property in $properties)#if($property.dataPrimaryKey)${property.dataTypeJavaClass}#end#end id, @Body ${name}Entity entity) {
+#if($needsRoles)
+ checkPermissions("write");
+#end
+#if($isEntityPropertySecurityEnabled)
+ ${name}Entity existing = repository.findOne(id)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "${name} not found"));
+ mergeWritable(existing, entity);
+ validate(existing);
+ return repository.update(existing);
+#else
+ entity.#foreach($property in $properties)#if($property.dataPrimaryKey)${property.name}#end#end = id;
+ validate(entity);
+ return repository.update(entity);
+#end
+ }
+
+ @Delete("/{id}")
+ @Documentation("Delete ${name} by id")
+ public void deleteById(@PathParam("id") #foreach($property in $properties)#if($property.dataPrimaryKey)${property.dataTypeJavaClass}#end#end id) {
+#if($needsRoles)
+ checkPermissions("write");
+#end
+ if (repository.findOne(id).isEmpty()) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "${name} not found");
+ }
+ repository.deleteById(id);
+ }
+
+ private List<${name}Entity> runFilter(Map filter) {
+ StringBuilder hql = new StringBuilder("from ${name}Entity e");
+ Map params = new LinkedHashMap<>();
+ boolean first = true;
+ if (filter != null && filter.get("equals") instanceof Map, ?> equals) {
+ for (Map.Entry, ?> entry : equals.entrySet()) {
+ String field = requireKnownField(String.valueOf(entry.getKey()));
+ String paramName = "p" + params.size();
+ hql.append(first ? " where e." : " and e.").append(field).append(" = :").append(paramName);
+ params.put(paramName, entry.getValue());
+ first = false;
+ }
+ }
+ if (filter != null && filter.get("conditions") instanceof List> conditions) {
+ for (Object raw : conditions) {
+ if (!(raw instanceof Map, ?> condition)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid filter condition");
+ }
+ String field = requireKnownField(String.valueOf(condition.get("propertyName")));
+ String operator = String.valueOf(condition.get("operator")).toUpperCase(Locale.ROOT);
+ Object value = condition.get("value");
+ String paramName = "p" + params.size();
+ String clause = switch (operator) {
+ case "EQ" -> "e." + field + " = :" + paramName;
+ case "IN" -> {
+ if (!(value instanceof Collection>)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "IN value must be a list for field: " + field);
+ }
+ yield "e." + field + " in (:" + paramName + ")";
+ }
+ case "LIKE" -> "e." + field + " like :" + paramName;
+ default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported operator: " + operator);
+ };
+ hql.append(first ? " where " : " and ").append(clause);
+ params.put(paramName, value);
+ first = false;
+ }
+ }
+ return repository.query(hql.toString(), params);
+ }
+
+ private static String requireKnownField(String field) {
+ if (!FILTER_FIELDS.contains(field)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown filter field: " + field);
+ }
+ return field;
+ }
+#if($needsRoles)
+
+ private void checkPermissions(String op) {
+#if($perspectiveRole)
+ if (!UserFacade.isInRole("${perspectiveRole}")) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+ }
+#end
+#if($roleRead)
+ if ("read".equals(op) && !(UserFacade.isInRole("${roleRead}")#if($roleWrite) || UserFacade.isInRole("${roleWrite}")#end)) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+ }
+#end
+#if($roleWrite)
+ if ("write".equals(op) && !UserFacade.isInRole("${roleWrite}")) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+ }
+#end
+ }
+#end
+#if($isEntityPropertySecurityEnabled)
+
+ private void redactRead(${name}Entity entity) {
+#foreach ($property in $properties)
+#if($property.roleRead)
+ if (!UserFacade.isInRole("${property.roleRead}")) {
+ entity.${property.name} = null;
+ }
+#end
+#end
+ }
+
+ private void mergeWritable(${name}Entity target, ${name}Entity input) {
+#foreach ($property in $properties)
+#if(!$property.dataPrimaryKey)
+#if($property.roleWrite)
+ if (UserFacade.isInRole("${property.roleWrite}")) {
+ target.${property.name} = input.${property.name};
+ }
+#else
+ target.${property.name} = input.${property.name};
+#end
+#end
+#end
+ }
+
+ private void applyOnCreate(${name}Entity entity) {
+#foreach ($property in $properties)
+#if($property.roleWrite)
+ if (!UserFacade.isInRole("${property.roleWrite}")) {
+ entity.${property.name} = null;
+ }
+#end
+#end
+ }
+#end
+
+ private static void validate(${name}Entity entity) {
+#foreach($property in $properties)
+#if($property.isRequiredProperty)
+ if (entity.${property.name} == null) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The '${property.name}' property is required");
+ }
+#end
+#if($property.dataTypeJavaClass == "String" && $property.dataLength && $property.dataTypeJava != "time")
+ if (entity.${property.name} != null && entity.${property.name}.length() > ${property.dataLength}) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The '${property.name}' exceeds the maximum length of ${property.dataLength}");
+ }
+#end
+#if($property.widgetPattern && $property.widgetPattern != "")
+ if (entity.${property.name} != null && !entity.${property.name}.toString().matches("${property.widgetPattern}")) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The value of '${property.name}' does not match the required pattern '${property.widgetPattern}'");
+ }
+#end
+#end
+ }
+}
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json
new file mode 100644
index 00000000000..1c29d0502fe
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json
@@ -0,0 +1,9 @@
+{
+ "guid": "template-application-rest-java",
+ "dependencies": [
+ {
+ "guid": "template-application-dao-java"
+ }
+ ],
+ "actions": []
+}
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json.mjs b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json.mjs
new file mode 100644
index 00000000000..ec680a2377d
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/project.json.mjs
@@ -0,0 +1,22 @@
+import { workspace } from '@aerokit/sdk/platform';
+
+export function generate(json) {
+ const parameters = JSON.parse(json);
+
+ const newProjectFile = JSON.stringify({
+ 'guid': parameters.projectName
+ });
+
+ const currenctWorkspace = workspace.getWorkspace(parameters.workspaceName);
+ const currentProject = currenctWorkspace.getProject(parameters.projectName);
+ const maybeProjectFile = currentProject.getFile('project.json');
+
+ if (maybeProjectFile.exists()) {
+ const projectFileContent = maybeProjectFile.getText();
+ if (projectFileContent.trim() !== '') {
+ return projectFileContent;
+ }
+ }
+
+ return newProjectFile;
+}
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/roles/default-roles.roles.template b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/roles/default-roles.roles.template
new file mode 100644
index 00000000000..86b292c9fe8
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/roles/default-roles.roles.template
@@ -0,0 +1,15 @@
+[
+#foreach($role in $roles)
+ #if($role.roleRead)
+ {
+ "name": "${role.roleRead}",
+ "description": "A role that grants read only permission for ${role.entityName}."
+ }#end#if($role.roleRead && $role.roleWrite || $foreach.hasNext),#end
+ #if($role.roleWrite)
+ {
+ "name": "${role.roleWrite}",
+ "description": "A role that grants full access for ${role.entityName}."
+ }#if($foreach.hasNext),#end
+ #end
+#end
+]
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.extension b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.extension
new file mode 100644
index 00000000000..b86c1a495ec
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.extension
@@ -0,0 +1,5 @@
+{
+ "module": "template-application-rest-java/template/template.js",
+ "extensionPoint": "platform-templates",
+ "description": "Application Template - REST - Java"
+}
diff --git a/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.js b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.js
new file mode 100644
index 00000000000..0a45006dde1
--- /dev/null
+++ b/components/template/template-application-rest-java/src/main/resources/META-INF/dirigible/template-application-rest-java/template/template.js
@@ -0,0 +1,52 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+import * as daoTemplateManager from "template-application-dao-java/template/template";
+import * as generateUtils from "service-generate/template/generateUtils";
+import * as parameterUtils from "service-generate/template/parameterUtils";
+
+export function generate(model, parameters) {
+ model = JSON.parse(model).model;
+ const templateSources = getTemplate(parameters).sources;
+ parameterUtils.process(model, parameters)
+ return generateUtils.generateFiles(model, parameters, templateSources);
+};
+
+export function getTemplate(parameters) {
+ const daoTemplate = daoTemplateManager.getTemplate(parameters);
+
+ let templateSources = [{
+ location: "/template-application-rest-java/api/EntityController.java.template",
+ action: "generate",
+ rename: "gen/{{javaGenFolderName}}/api/{{javaPerspectiveName}}/{{name}}Controller.java",
+ engine: "velocity",
+ collection: "apiModels"
+ }, {
+ location: "/template-application-rest-java/project.json.mjs",
+ action: "generate",
+ rename: "project.json",
+ engine: "javascript"
+ }];
+
+ templateSources.push({
+ location: "/template-application-rest-java/roles/default-roles.roles.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{javaGenFolderName}}/roles/default-roles.roles"
+ });
+
+ templateSources = templateSources.concat(daoTemplate.sources);
+
+ let templateParameters = [];
+ templateParameters = templateParameters.concat(daoTemplate.parameters);
+
+ return {
+ name: "Application - REST - Java",
+ description: "Application with REST APIs (Java)",
+ extension: "model",
+ sources: templateSources,
+ parameters: templateParameters
+ };
+};
diff --git a/components/template/template-application-ui-angular-java/.gitignore b/components/template/template-application-ui-angular-java/.gitignore
new file mode 100644
index 00000000000..1dd33310812
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/.gitignore
@@ -0,0 +1,2 @@
+/target/
+/target/
diff --git a/components/template/template-application-ui-angular-java/pom.xml b/components/template/template-application-ui-angular-java/pom.xml
new file mode 100644
index 00000000000..16654f40855
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/pom.xml
@@ -0,0 +1,24 @@
+
+ 4.0.0
+
+
+ org.eclipse.dirigible
+ dirigible-components-parent
+ 13.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+ Components - Template - Application - UI - AngularJS - Java
+ dirigible-components-template-application-ui-angular-java
+ jar
+
+
+ generate-sources
+ template-application-ui-angular-java
+ template-application-ui-angular-java
+
+ ../generation-header.txt
+
+
+
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/project.json b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/project.json
new file mode 100644
index 00000000000..f72c4fa44cf
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/project.json
@@ -0,0 +1,5 @@
+{
+ "guid": "template-application-ui-angular-java",
+ "dependencies": [],
+ "actions": []
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.extension b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.extension
new file mode 100644
index 00000000000..ba9878a3a04
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.extension
@@ -0,0 +1,5 @@
+{
+ "module": "template-application-ui-angular-java/template/template.js",
+ "extensionPoint": "platform-templates",
+ "description": "Application Template - UI (Angular) - Java"
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.js
new file mode 100644
index 00000000000..b50d94650fd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/template.js
@@ -0,0 +1,62 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+import * as restTemplateManager from "template-application-rest-java/template/template";
+import * as uiTemplate from "template-application-ui-angular-java/template/ui/template";
+import * as generateUtils from "service-generate/template/generateUtils";
+import * as parameterUtils from "service-generate/template/parameterUtils";
+
+export function generate(model, parameters) {
+ model = JSON.parse(model).model;
+ let templateSources = getTemplate(parameters).sources;
+ parameters.javaRuntime = true;
+ parameterUtils.process(model, parameters)
+ return generateUtils.generateFiles(model, parameters, templateSources);
+};
+
+export function getTemplate(parameters) {
+ let restTemplate = restTemplateManager.getTemplate(parameters);
+
+ let templateSources = [
+ ...restTemplate.sources,
+ ...uiTemplate.getSources(parameters)
+ ];
+
+ let templateParameters = getTemplateParameters();
+ templateParameters = templateParameters.concat(restTemplate.parameters);
+
+ return {
+ name: "Application - UI (AngularJS) - Java",
+ description: "Application With UI, Java REST APIs and DAOs",
+ extension: "model",
+ sources: templateSources,
+ parameters: templateParameters
+ };
+};
+
+function getTemplateParameters() {
+ return [
+ {
+ name: "brand",
+ label: "Brand",
+ placeholder: "Enter Brand"
+ },
+ {
+ name: "brandUrl",
+ label: "Brand URL",
+ placeholder: "Enter Brand URL"
+ },
+ {
+ name: "title",
+ label: "Title",
+ placeholder: "Enter Title"
+ },
+ {
+ name: "description",
+ label: "Description",
+ placeholder: "Enter Description"
+ }
+ ];
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/list.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/list.js
new file mode 100644
index 00000000000..2298b812a24
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/list.js
@@ -0,0 +1,116 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/index.html",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.extension",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.js",
+ collection: "uiListModels"
+ },
+ // Location: "gen/{{genFolderName}}/ui/perspective/list"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/controller.js",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/index.html",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.extension",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.js",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/index.html",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.js",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/controller.js",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/index.html",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.extension",
+ collection: "uiListModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/list/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.js",
+ collection: "uiListModels"
+ }
+ ];
+};
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/manage.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/manage.js
new file mode 100644
index 00000000000..46266967886
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/manage.js
@@ -0,0 +1,116 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/index.html",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.extension",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.js",
+ collection: "uiManageModels"
+ },
+ // Location: "gen/{{genFolderName}}/ui/perspective/manage"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/controller.js",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/index.html",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.extension",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.js",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/index.html",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.js",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/controller.js",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/index.html",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.extension",
+ collection: "uiManageModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.js",
+ collection: "uiManageModels"
+ }
+ ];
+};
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsList.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsList.js
new file mode 100644
index 00000000000..2233200496a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsList.js
@@ -0,0 +1,239 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ ...getMaster(parameters),
+ ...getDetails(parameters)
+ ];
+};
+
+function getMaster(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/index.html",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.extension",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/controller.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/index.html",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.extension",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/controller.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/index.html",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/view.extension",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/view.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/index.html",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/controller.js",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/index.html",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.extension",
+ collection: "uiListMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.js",
+ collection: "uiListMasterModels"
+ },
+ ];
+}
+
+function getDetails(parameters) {
+ return [
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/controller.js",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/index.html",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/view.extension",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/view.js",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/controller.js",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/index.html",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/view.extension",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/view.js",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/index.html",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiListDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/view.js",
+ collection: "uiListDetailsModels"
+ }
+ ];
+}
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsManage.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsManage.js
new file mode 100644
index 00000000000..b4151de6d06
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/masterDetailsManage.js
@@ -0,0 +1,239 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ ...getMaster(parameters),
+ ...getDetails(parameters)
+ ];
+};
+
+function getMaster(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/index.html",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.extension",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/perspective.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/perspective.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/controller.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/index.html",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.extension",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/controller.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/index.html",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/view.extension",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/main-details/view.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/index.html",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/controller.js",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/index.html",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.extension",
+ collection: "uiManageMasterModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.js",
+ collection: "uiManageMasterModels"
+ }
+ ];
+}
+
+function getDetails(parameters) {
+ return [
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/controller.js",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/index.html",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/view.extension",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/view.js",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/controller.js",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/index.html",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/view.extension",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-window/view.js",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/index.html",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiManageDetailsModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{masterEntity}}/{{name}}/dialog-filter/view.js",
+ collection: "uiManageDetailsModels"
+ }
+ ];
+}
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/navigation.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/navigation.js
new file mode 100644
index 00000000000..89e7b06dbce
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/navigation.js
@@ -0,0 +1,18 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export const getSources = () => ([{
+ location: '/template-application-ui-angular-java/ui/navigation/perspective-group.extension.template',
+ action: 'generate',
+ rename: 'gen/{{genFolderName}}/navigation/{{navId}}/perspective-group.extension',
+ engine: 'velocity',
+ collection: 'uiNavigations'
+}, {
+ location: '/template-application-ui-angular-java/ui/navigation/perspective-group.js.template',
+ action: 'generate',
+ rename: 'gen/{{genFolderName}}/navigation/{{navId}}/perspective-group.js',
+ engine: 'velocity',
+ collection: 'uiNavigations'
+}]);
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/perspective.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/perspective.js
new file mode 100644
index 00000000000..b7dc6d35d77
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/perspective.js
@@ -0,0 +1,32 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [{
+ location: "/template-application-ui-angular-java/ui/perspective/index.html.template",
+ action: "generate",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/index.html",
+ engine: "velocity",
+ collection: "uiPerspectives"
+ }, {
+ location: "/template-application-ui-angular-java/ui/perspective/extensions/perspective/perspective.extension.template",
+ action: "generate",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/extensions/perspective/perspective.extension",
+ engine: "velocity",
+ collection: "uiPerspectives"
+ }, {
+ location: "/template-application-ui-angular-java/ui/perspective/extensions/perspective/perspective.js.template",
+ action: "generate",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/extensions/perspective/perspective.js",
+ engine: "velocity",
+ collection: "uiPerspectives"
+ }, {
+ location: "/template-application-ui-angular-java/ui/perspective/extensions/view.extensionpoint.template",
+ action: "generate",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/extensions/view.extensionpoint",
+ engine: "velocity",
+ collection: "uiPerspectives"
+ }];
+};
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/report.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/report.js
new file mode 100644
index 00000000000..805277f1568
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/report.js
@@ -0,0 +1,120 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective/list"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/controller.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/index.html",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/view.extension",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/view.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-filter/controller.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-filter/index.html",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-filter/view.extension",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-filter/view.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/controller.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/index.html",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.extension",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-print/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/controller.js",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-print/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/index.html",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/print.extension",
+ collection: "generateReportModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.js.template",
+ action: "generate",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/print.js",
+ collection: "generateReportModels"
+ }];
+};
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportChart.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportChart.js
new file mode 100644
index 00000000000..bfb6c65f5b1
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportChart.js
@@ -0,0 +1,65 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective/"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/controller.js",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/index.html",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/view.extension",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/view.js",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/controller.js",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/index.html",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.extension",
+ collection: "uiReportChartModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-chart/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.js",
+ collection: "uiReportChartModels"
+ }];
+};
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportTable.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportTable.js
new file mode 100644
index 00000000000..bb1bc336b97
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/reportTable.js
@@ -0,0 +1,93 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ // Location: "gen/{{genFolderName}}/ui/perspective/"
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/controller.js",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/index.html",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/view.extension",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window/view.js",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/controller.js",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/index.html",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/view.extension",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-window-filter/view.js",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/controller.js",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/index.html",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.extension",
+ collection: "uiReportTableModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/report-table/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/Reports/{{name}}/view.js",
+ collection: "uiReportTableModels"
+ }];
+};
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/setting.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/setting.js
new file mode 100644
index 00000000000..e1d07e8a39e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/setting.js
@@ -0,0 +1,93 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+export function getSources(parameters) {
+ return [
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/controller.js",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/index.html",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.extension",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-window/view.js",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/controller.js",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/index.html",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.extension",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/dialog-filter/view.js",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/controller.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/controller.js",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/index.html.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/index.html",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/view.extension.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.extension",
+ collection: "uiSettingModels"
+ },
+ {
+ location: "/template-application-ui-angular-java/ui/perspective/manage/view.js.template",
+ action: "generate",
+ engine: "velocity",
+ rename: "gen/{{genFolderName}}/ui/{{perspectiveName}}/{{name}}/view.js",
+ collection: "uiSettingModels"
+ }
+ ];
+};
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/template.js b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/template.js
new file mode 100644
index 00000000000..4a8b5f483a5
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/template/ui/template.js
@@ -0,0 +1,34 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+import * as listTemplate from "template-application-ui-angular-java/template/ui/list";
+import * as manageTemplate from "template-application-ui-angular-java/template/ui/manage";
+import * as masterDetailsListTemplate from "template-application-ui-angular-java/template/ui/masterDetailsList";
+import * as masterDetailsManageTemplate from "template-application-ui-angular-java/template/ui/masterDetailsManage";
+import * as settingTemplate from "template-application-ui-angular-java/template/ui/setting";
+import * as reportTemplate from "template-application-ui-angular-java/template/ui/report";
+import * as reportChartTemplate from "template-application-ui-angular-java/template/ui/reportChart";
+import * as reportTableTemplate from "template-application-ui-angular-java/template/ui/reportTable";
+import * as navigation from "template-application-ui-angular-java/template/ui/navigation";
+// import * as perspective from "template-application-ui-angular-java/template/ui/perspective";
+
+export function getSources(parameters) {
+ return [
+ {
+ location: "/template-application-ui-angular-java/ui/translations.json.template",
+ action: "translate",
+ },
+ ...listTemplate.getSources(parameters),
+ ...manageTemplate.getSources(parameters),
+ ...masterDetailsListTemplate.getSources(parameters),
+ ...masterDetailsManageTemplate.getSources(parameters),
+ ...settingTemplate.getSources(parameters),
+ ...reportTemplate.getSources(parameters),
+ ...reportChartTemplate.getSources(parameters),
+ ...reportTableTemplate.getSources(parameters),
+ ...navigation.getSources(parameters),
+ //...perspective.getSources(parameters),
+ ];
+};
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.extension.template
new file mode 100644
index 00000000000..0147f5c27e6
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/navigation/${navId}/perspective-group.js",
+ "extensionPoint": "application-perspectives",
+ "description": "${projectName} - Perspective Group - ${navLabel}"#if($navRole || $roleRead || $roleWrite),
+ "role": "#if($navRole)${navRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.js.template
new file mode 100644
index 00000000000..18b13e33c2e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/navigation/perspective-group.js.template
@@ -0,0 +1,22 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+exports.getPerspectiveGroup = () => ({
+ id: '${navId}',
+ label: '${navLabel}',
+ translation: {
+ key: '$projectName:${tprefix}.t.${navId}',
+ },
+#if($navHeader)
+ header: '${navHeader}',
+ headerTranslation: {
+ key: '$projectName:${tprefix}.t.${navId}nheader',
+ },
+#end
+ expanded: ${navExpanded},
+ order: ${navOrder},
+ icon: '${navIcon}',
+ items: []
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/index.html.template
new file mode 100644
index 00000000000..bdef8c38339
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/index.html.template
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.state.busy' | t}}
+
+
+ {{'$projectName:${tprefix}.messages.error.loading' | t}}
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template
new file mode 100644
index 00000000000..81901e268f4
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/controller.js.template
@@ -0,0 +1,251 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService) => {
+ const Dialogs = new DialogHub();
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString}
+ },
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ $scope.dataPage = pageNumber;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ let offset = (pageNumber - 1) * $scope.dataLimit;
+ let limit = $scope.dataLimit;
+ let request;
+ if (filter) {
+ if (!filter.$filter) {
+ filter.$filter = {};
+ }
+ filter.$filter.offset = offset;
+ filter.$filter.limit = limit;
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list(offset, limit);
+ }
+ request.then((response) => {
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'select',
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..b0b5605d13b
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/controller.js.template
@@ -0,0 +1,217 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters, LocaleService) => {
+ const Dialogs = new DialogHub();
+ let description = 'Description';
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ });
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear();
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/index.html.template
new file mode 100644
index 00000000000..c8776cc84a3
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..8b596562b61
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.js.template
new file mode 100644
index 00000000000..3a25b9a3a55
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/controller.js.template
new file mode 100644
index 00000000000..924391a6ad9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/controller.js.template
@@ -0,0 +1,30 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = 'select';
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/index.html.template
new file mode 100644
index 00000000000..dd2f95a212e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/index.html.template
@@ -0,0 +1,224 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.extension.template
new file mode 100644
index 00000000000..e0467a1d892
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.js.template
new file mode 100644
index 00000000000..9f6c7c33bdd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/dialog-window/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template
new file mode 100644
index 00000000000..ca42f89ab0f
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/index.html.template
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.extension.template
new file mode 100644
index 00000000000..f9ae92d4291
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.js.template
new file mode 100644
index 00000000000..3e9644ca619
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/list/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+ lazyLoad: false,
+ autoFocusTab: true,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template
new file mode 100644
index 00000000000..b8ecaa431d9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/controller.js.template
@@ -0,0 +1,342 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates) => {
+ const Dialogs = new DialogHub();
+ let translated = {
+ yes: 'Yes',
+ no: 'No',
+ deleteConfirm: 'Are you sure you want to delete ${name}? This action cannot be undone.',
+ deleteTitle: 'Delete ${name}?'
+ };
+
+ LocaleService.onInit(() => {
+ translated.yes = LocaleService.t('$projectName:${tprefix}.defaults.yes');
+ translated.no = LocaleService.t('$projectName:${tprefix}.defaults.no');
+ translated.deleteTitle = LocaleService.t('$projectName:${tprefix}.defaults.deleteTitle', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ translated.deleteConfirm = LocaleService.t('$projectName:${tprefix}.messages.deleteConfirm', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString}
+ },
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', handler: () => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', handler: () => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ $scope.dataPage = pageNumber;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ let offset = (pageNumber - 1) * $scope.dataLimit;
+ let limit = $scope.dataLimit;
+ let request;
+ if (filter) {
+ if (!filter.$filter) {
+ filter.$filter = {};
+ }
+ filter.$filter.offset = offset;
+ filter.$filter.limit = limit;
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list(offset, limit);
+ }
+ request.then((response) => {
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'select',
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ closeButton: true,
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ closeButton: true,
+ });
+ };
+
+ $scope.createEntity = () => {
+ $scope.selectedEntity = null;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'create',
+ entity: {},
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ closeButton: false,
+ });
+ };
+
+ $scope.updateEntity = (entity) => {
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'update',
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ closeButton: false,
+ });
+ };
+
+ $scope.deleteEntity = (entity) => {
+ let id = entity.${primaryKeysString};
+ Dialogs.showDialog({
+ title: translated.deleteTitle,
+ message: translated.deleteConfirm,
+ buttons: [{
+ id: 'delete-btn-yes',
+ state: ButtonStates.Emphasized,
+ label: translated.yes,
+ }, {
+ id: 'delete-btn-no',
+ label: translated.no,
+ }]
+ }).then((buttonId) => {
+ if (buttonId === 'delete-btn-yes') {
+ EntityService.delete(id).then((response) => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToDelete', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..2fe3e8a30ae
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/controller.js.template
@@ -0,0 +1,217 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters, LocaleService) => {
+ const Dialogs = new DialogHub();
+ let description = 'Description';
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ });
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear()
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/index.html.template
new file mode 100644
index 00000000000..80369b41315
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..8b596562b61
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.js.template
new file mode 100644
index 00000000000..3a25b9a3a55
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/controller.js.template
new file mode 100644
index 00000000000..6b37bddddbd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/controller.js.template
@@ -0,0 +1,289 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, ${dollar}http, ViewParameters, LocaleService, EntityService) => {
+ const Dialogs = new DialogHub();
+ const Notifications = new NotificationHub();
+ let description = 'Description';
+ let propertySuccessfullyCreated = '${name} successfully created';
+ let propertySuccessfullyUpdated = '${name} successfully updated';
+
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+ $scope.formHeaders = {
+ select: '${name} Details',
+ create: 'Create ${name}',
+ update: 'Update ${name}'
+ };
+ $scope.action = 'select';
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ $scope.formHeaders.select = LocaleService.t('$projectName:${tprefix}.defaults.formHeadSelect', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.create = LocaleService.t('$projectName:${tprefix}.defaults.formHeadCreate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.update = LocaleService.t('$projectName:${tprefix}.defaults.formHeadUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyCreated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyCreated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyUpdated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyUpdated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = params.action;
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.create = () => {
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.create(entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyCreated,
+ type: 'positive'
+ });
+ $scope.cancel();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ $scope.$evalAsync(() => {
+ $scope.errorMessage = LocaleService.t('$projectName:${tprefix}.messages.error.unableToCreate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message });
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.update = () => {
+ let id = ${dollar}scope.entity.${primaryKeysString};
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.update(id, entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyUpdated,
+ type: 'positive'
+ });
+ $scope.cancel();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ $scope.$evalAsync(() => {
+ $scope.errorMessage = LocaleService.t('$projectName:${tprefix}.messages.error.unableToUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message });
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ ${dollar}scope.service${property.name} = '${property.widgetDropdownControllerUrl}';
+
+ $scope.options${property.name} = [];
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetDependsOnProperty)
+ ${dollar}scope.${dollar}watch('entity.${property.widgetDependsOnProperty}', (newValue, oldValue) => {
+ if (newValue !== undefined && newValue !== null) {
+ ${dollar}http.get(${dollar}scope.service${property.widgetDependsOnProperty} + '/' + newValue).then((response) => {
+ let valueFrom = response.data.${property.widgetDependsOnValueFrom};
+#if($property.widgetType != "DROPDOWN")
+ $scope.entity.${property.name} = valueFrom;
+#end
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDependsOnFilterBy}', operator: 'EQ', value: valueFrom }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ if ($scope.action !== 'select' && newValue !== oldValue) {
+ if (${dollar}scope.options${property.name}.length == 1) {
+ $scope.entity.${property.name} = ${dollar}scope.options${property.name}[0].value;
+ } else {
+ $scope.entity.${property.name} = undefined;
+ }
+ }
+ }, (error) => {
+ console.error(error);
+ });
+#end
+ }, (error) => {
+ console.error(error);
+ });
+ }
+ });
+
+#end
+#end
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+ $scope.cancel = () => {
+ $scope.entity = {};
+ $scope.action = 'select';
+ Dialogs.closeWindow({ id: '${name}-details' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/index.html.template
new file mode 100644
index 00000000000..96e8e9b3b6e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/index.html.template
@@ -0,0 +1,576 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.extension.template
new file mode 100644
index 00000000000..e0467a1d892
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.js.template
new file mode 100644
index 00000000000..9f6c7c33bdd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/dialog-window/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template
new file mode 100644
index 00000000000..41d6304554a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/index.html.template
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.extension.template
new file mode 100644
index 00000000000..831aabab13c
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.extension.template
@@ -0,0 +1,10 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/view.js",
+#if($type == 'SETTING')
+ "extensionPoint": "application-settings",
+#else
+ "extensionPoint": "application-views",
+#end
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.js.template
new file mode 100644
index 00000000000..67b46fcd142
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/manage/view.js.template
@@ -0,0 +1,25 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+#if($type == 'SETTING')
+ lazyLoad: true,
+ autoFocusTab: false,
+#else
+ lazyLoad: false,
+ autoFocusTab: true,
+#end
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/controller.js.template
new file mode 100644
index 00000000000..cb87a8c07d9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/controller.js.template
@@ -0,0 +1,250 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService) => {
+ const Dialogs = new DialogHub();
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataOffset = 0;
+ $scope.dataLimit = 10;
+ $scope.action = 'select';
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function refreshData() {
+ $scope.dataReset = true;
+ $scope.dataPage--;
+ }
+
+ function resetPagination() {
+ $scope.dataReset = true;
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 10;
+ }
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.selectedEntity = null;
+ $scope.action = 'select';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ if (!filter) {
+ filter = {
+ $filter: {}
+ };
+ }
+ $scope.selectedEntity = null;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ $scope.dataPages = Math.ceil($scope.dataCount / $scope.dataLimit);
+ filter.$filter.offset = ($scope.dataPage - 1) * $scope.dataLimit;
+ filter.$filter.limit = $scope.dataLimit;
+ if ($scope.dataReset) {
+ filter.$filter.offset = 0;
+ filter.$filter.limit = $scope.dataPage * $scope.dataLimit;
+ }
+ EntityService.search(filter).then((response) => {
+ if ($scope.data == null || $scope.dataReset) {
+ $scope.data = [];
+ $scope.dataReset = false;
+ }
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ ${dollar}scope.data = ${dollar}scope.data.concat(response.data);
+ $scope.dataPage++;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ entity.${property.name} = entity.${property.name}.split(',').map(e => parseInt(e));
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySelected', data: {
+ entity: entity,
+ selectedMainEntityId: entity.${primaryKeysString},
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ }});
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ }
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template
new file mode 100644
index 00000000000..6514041f993
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/controller.js.template
@@ -0,0 +1,263 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService) => {
+ const Dialogs = new DialogHub();
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ selectedMainEntityId: $scope.selectedMainEntityId,
+ },
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString},
+ selectedMainEntityId: $scope.selectedMainEntityId,
+ },
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 10;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '#if($hasReferencedProjection)${referencedProjectionProjectName}.${referencedProjectionPerspectiveName}#else${projectName}.${perspectiveName}#end.${masterEntity}.entitySelected', handler: (data) => {
+ resetPagination();
+ $scope.selectedMainEntityId = data.selectedMainEntityId;
+ $scope.loadPage($scope.dataPage);
+ }});
+ Dialogs.addMessageListener({ topic: '#if($hasReferencedProjection)${referencedProjectionProjectName}.${referencedProjectionPerspectiveName}#else${projectName}.${perspectiveName}#end.${masterEntity}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ resetPagination();
+ $scope.selectedMainEntityId = null;
+ $scope.data = null;
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.entity = {};
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ let ${masterEntityId} = ${dollar}scope.selectedMainEntityId;
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ if (!filter) {
+ filter = {
+ $filter: {
+ conditions: []
+ }
+ };
+ }
+
+ filter.${dollar}filter.conditions.push({ propertyName: '${masterEntityId}', operator: 'EQ', value: ${masterEntityId} });
+ $scope.dataPage = pageNumber;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ filter.$filter.offset = (pageNumber - 1) * $scope.dataLimit;
+ filter.$filter.limit = $scope.dataLimit;
+ EntityService.search(filter).then((response) => {
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..a42a66b9860
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/controller.js.template
@@ -0,0 +1,203 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear();
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/index.html.template
new file mode 100644
index 00000000000..7a9332b0a48
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.name != $masterEntityId)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..d9523eb789b
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.js.template
new file mode 100644
index 00000000000..315217d3cff
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/controller.js.template
new file mode 100644
index 00000000000..fa1112e0532
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/controller.js.template
@@ -0,0 +1,36 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#if($hasDates)
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+#end
+
+ $scope.entity = params.entity;
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+#end
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/index.html.template
new file mode 100644
index 00000000000..f63d4fb9d64
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/index.html.template
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.relationshipType != "COMPOSITION")
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.extension.template
new file mode 100644
index 00000000000..ce739c305e7
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.js.template
new file mode 100644
index 00000000000..83ee2a40aad
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/dialog-window/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-window/index.html',
+ perspectiveName: '#if($hasReferencedProjection)${referencedProjectionPerspectiveName}#else${perspectiveName}#end'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template
new file mode 100644
index 00000000000..b371e016a1e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/index.html.template
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.messages.detailSelectRecord' | t}}
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.extension.template
new file mode 100644
index 00000000000..4f6c51e52dd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View - Details"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.js.template
new file mode 100644
index 00000000000..30d6da8dc27
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/detail/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'bottom',
+ lazyLoad: false,
+ autoFocusTab: true,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/index.html',
+ perspectiveName: '#if($hasReferencedProjection)${referencedProjectionPerspectiveName}#else${perspectiveName}#end'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..ba2093a6411
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/controller.js.template
@@ -0,0 +1,204 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear();
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/index.html.template
new file mode 100644
index 00000000000..fce038b8a79
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..8b596562b61
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.js.template
new file mode 100644
index 00000000000..3a25b9a3a55
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/controller.js.template
new file mode 100644
index 00000000000..924391a6ad9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/controller.js.template
@@ -0,0 +1,30 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = 'select';
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/index.html.template
new file mode 100644
index 00000000000..fe33641fc95
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/index.html.template
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.extension.template
new file mode 100644
index 00000000000..e0467a1d892
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.js.template
new file mode 100644
index 00000000000..eecaced6c61
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/dialog-window/view.js.template
@@ -0,0 +1,27 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/index.html',
+ perspectiveName: '${perspectiveName}',
+#if($perspectiveRole || $roleRead)
+ roles: [
+#if($perspectiveRole)
+ '${perspectiveRole}',
+#end
+#if($roleRead)
+ '${roleRead}',
+#end
+ ]
+#end
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/index.html.template
new file mode 100644
index 00000000000..9889a75d0a9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/index.html.template
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#elseif($masterProperties.title.widgetType == "DATE")
+ item-title="{{next.${masterProperties.title.name} | date: 'dd/MMM/yyyy'}}">
+#elseif($masterProperties.title.widgetType == "DATETIME-LOCAL")
+ item-title="{{next.${masterProperties.title.name} | date: 'dd/MMM/yyyy HH:MM'}}">
+#elseif($masterProperties.title.widgetType == "TIME")
+ item-title="{{next.${masterProperties.title.name} | date: 'HH:MM:ss'}}">
+#elseif($masterProperties.title.widgetType == "WEEK")
+ item-title="Week {{next.${masterProperties.title.name} | date: 'ww'}}">
+#else
+ item-title="{{next.${masterProperties.title.name}}}">
+#end
+
+#foreach ($property in $masterProperties.properties)
+#if($property.widgetType == "DROPDOWN")
+ {{options${property.name}Value(next.${property.name})}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "DATE")
+ {{next.${property.name} | date: "dd/MMM/yyyy"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "DATETIME-LOCAL")
+ {{next.${property.name} | date: "dd/MMM/yyyy HH:MM"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "TIME")
+ {{next.${property.name} | date: "HH:MM:ss"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "WEEK")
+ Week {{next.${property.name} | date: "ww"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "EMAIL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "URL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "TEL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#else
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#end
+#end
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.loadMore' | t }}
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template
new file mode 100644
index 00000000000..9edb2232f65
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/controller.js.template
@@ -0,0 +1,57 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, Extensions, LocaleService) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString}
+ },
+ closeButton: true,
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.entity = {};
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySelected', handler: (data) => {
+ $scope.$evalAsync(() => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (data.entity.${property.name}) {
+ data.entity.${property.name} = new Date(data.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = data.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = data.options${property.name};
+#end
+#end
+ });
+ }});
+ //-----------------Events-------------------//
+
+ $scope.cancel = () => {
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ };
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template
new file mode 100644
index 00000000000..1c0f344529d
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/index.html.template
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.extension.template
new file mode 100644
index 00000000000..2eaa6ee3f86
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/main-details/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View - Main Details"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.js.template
new file mode 100644
index 00000000000..87de655838a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/main-details/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+ lazyLoad: false,
+ autoFocusTab: true,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/main-details/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.extension.template
new file mode 100644
index 00000000000..f9ae92d4291
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.js.template
new file mode 100644
index 00000000000..a3927b88432
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-list/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'left',
+ lazyLoad: false,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/controller.js.template
new file mode 100644
index 00000000000..ba8c842cebe
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/controller.js.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates) => {
+ const Dialogs = new DialogHub();
+ let translated = {
+ yes: 'Yes',
+ no: 'No',
+ deleteConfirm: 'Are you sure you want to delete ${name}? This action cannot be undone.',
+ deleteTitle: 'Delete ${name}?'
+ };
+
+ LocaleService.onInit(() => {
+ translated.yes = LocaleService.t('$projectName:${tprefix}.defaults.yes');
+ translated.no = LocaleService.t('$projectName:${tprefix}.defaults.no');
+ translated.deleteTitle = LocaleService.t('$projectName:${tprefix}.defaults.deleteTitle', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ translated.deleteConfirm = LocaleService.t('$projectName:${tprefix}.messages.deleteConfirm', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataOffset = 0;
+ $scope.dataLimit = 10;
+ $scope.action = 'select';
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function refreshData() {
+ $scope.dataReset = true;
+ $scope.dataPage--;
+ }
+
+ function resetPagination() {
+ $scope.dataReset = true;
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 10;
+ }
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.selectedEntity = null;
+ $scope.action = 'select';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', handler: () => {
+ refreshData();
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', handler: () => {
+ refreshData();
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ if (!filter) {
+ filter = {
+ $filter: {}
+ };
+ }
+ $scope.selectedEntity = null;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ $scope.dataPages = Math.ceil($scope.dataCount / $scope.dataLimit);
+ filter.$filter.offset = ($scope.dataPage - 1) * $scope.dataLimit;
+ filter.$filter.limit = $scope.dataLimit;
+ if ($scope.dataReset) {
+ filter.$filter.offset = 0;
+ filter.$filter.limit = $scope.dataPage * $scope.dataLimit;
+ }
+
+ EntityService.search(filter).then((response) => {
+ if ($scope.data == null || $scope.dataReset) {
+ $scope.data = [];
+ $scope.dataReset = false;
+ }
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ ${dollar}scope.data = ${dollar}scope.data.concat(response.data);
+ $scope.dataPage++;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ entity.${property.name} = entity.${property.name}.split(',').map(e => parseInt(e));
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySelected', data: {
+ entity: entity,
+ selectedMainEntityId: entity.${primaryKeysString},
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ }});
+ };
+
+ $scope.createEntity = () => {
+ $scope.selectedEntity = null;
+ $scope.action = 'create';
+
+#if($hasDropdowns)
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.createEntity', data: {
+ entity: {},
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ }});
+#else
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.createEntity');
+#end
+ };
+
+ $scope.updateEntity = () => {
+ $scope.action = 'update';
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.updateEntity', data: {
+ entity: $scope.selectedEntity,
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ }});
+ };
+
+ $scope.deleteEntity = () => {
+ let id = ${dollar}scope.selectedEntity.${primaryKeysString};
+ Dialogs.showDialog({
+ title: translated.deleteTitle,
+ message: translated.deleteConfirm,
+ buttons: [{
+ id: 'delete-btn-yes',
+ state: ButtonStates.Emphasized,
+ label: translated.yes,
+ }, {
+ id: 'delete-btn-no',
+ label: translated.no,
+ }],
+ closeButton: false
+ }).then((buttonId) => {
+ if (buttonId === 'delete-btn-yes') {
+ EntityService.delete(id).then(() => {
+ refreshData();
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToDelete', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template
new file mode 100644
index 00000000000..00f1d1fd1d9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/controller.js.template
@@ -0,0 +1,361 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope,#if($hasDropdowns) ${dollar}http,#end EntityService, Extensions, LocaleService, ButtonStates) => {
+ const Dialogs = new DialogHub();
+ let translated = {
+ yes: 'Yes',
+ no: 'No',
+ deleteConfirm: 'Are you sure you want to delete ${name}? This action cannot be undone.',
+ deleteTitle: 'Delete ${name}?'
+ };
+
+ LocaleService.onInit(() => {
+ translated.yes = LocaleService.t('$projectName:${tprefix}.defaults.yes');
+ translated.no = LocaleService.t('$projectName:${tprefix}.defaults.no');
+ translated.deleteTitle = LocaleService.t('$projectName:${tprefix}.defaults.deleteTitle', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ translated.deleteConfirm = LocaleService.t('$projectName:${tprefix}.messages.deleteConfirm', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ selectedMainEntityKey: '${masterEntityId}',
+ selectedMainEntityId: $scope.selectedMainEntityId,
+ },
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString},
+ selectedMainEntityKey: '${masterEntityId}',
+ selectedMainEntityId: $scope.selectedMainEntityId,
+ },
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 10;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '#if($hasReferencedProjection)${referencedProjectionProjectName}.${referencedProjectionPerspectiveName}#else${projectName}.${perspectiveName}#end.${masterEntity}.entitySelected', handler: (data) => {
+ resetPagination();
+ $scope.selectedMainEntityId = data.selectedMainEntityId;
+ $scope.loadPage($scope.dataPage);
+ }});
+ Dialogs.addMessageListener({ topic: '#if($hasReferencedProjection)${referencedProjectionProjectName}.${referencedProjectionPerspectiveName}#else${projectName}.${perspectiveName}#end.${masterEntity}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ resetPagination();
+ $scope.selectedMainEntityId = null;
+ $scope.data = null;
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.entity = {};
+ $scope.action = 'select';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', handler: () => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', handler: () => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ let ${masterEntityId} = ${dollar}scope.selectedMainEntityId;
+ $scope.dataPage = pageNumber;
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ if (!filter) {
+ filter = {
+ $filter: {
+ conditions: []
+ }
+ };
+ }
+ filter.${dollar}filter.conditions.push({ propertyName: '${masterEntityId}', operator: 'EQ', value: ${masterEntityId} });
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ filter.$filter.offset = (pageNumber - 1) * $scope.dataLimit;
+ filter.$filter.limit = $scope.dataLimit;
+ EntityService.search(filter).then((response) => {
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ if (options${property.name}HasMore) {
+ const options${property.name}SearchValues = Array.from(new Set(response.data.map(e => e.${property.name})));
+ if (options${property.name}SearchValues.length > 0) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownKey}', operator: 'IN', value: options${property.name}SearchValues }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name}.push(...response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ })));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }
+ }
+#end
+#end
+#end
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'select',
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+
+ $scope.createEntity = () => {
+ $scope.selectedEntity = null;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'create',
+ entity: {
+ '${masterEntityId}': $scope.selectedMainEntityId
+ },
+ selectedMainEntityKey: '${masterEntityId}',
+ selectedMainEntityId: $scope.selectedMainEntityId,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ closeButton: false
+ });
+ };
+
+ $scope.updateEntity = (entity) => {
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'update',
+ entity: entity,
+ selectedMainEntityKey: '${masterEntityId}',
+ selectedMainEntityId: $scope.selectedMainEntityId,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end },
+ closeButton: false
+ });
+ };
+
+ $scope.deleteEntity = (entity) => {
+ let id = entity.${primaryKeysString};
+ Dialogs.showDialog({
+ title: translated.deleteTitle,
+ message: translated.deleteConfirm,
+ buttons: [{
+ id: 'delete-btn-yes',
+ state: ButtonStates.Emphasized,
+ label: translated.yes,
+ }, {
+ id: 'delete-btn-no',
+ label: translated.no,
+ }],
+ closeButton: false
+ }).then((buttonId) => {
+ if (buttonId === 'delete-btn-yes') {
+ EntityService.delete(id).then(() => {
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToDelete', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error,
+ });
+ console.error('EntityService:', error);
+ });
+ }
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ let options${property.name}HasMore = true;
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}/count').then((response) => {
+ const options${property.name}Count = response.data.count;
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ options${property.name}HasMore = options${property.name}Count > ${dollar}scope.options${property.name}.length;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = function (optionKey) {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..a42a66b9860
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/controller.js.template
@@ -0,0 +1,203 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear();
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/index.html.template
new file mode 100644
index 00000000000..8fc43c68236
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.name != $masterEntityId)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..d9523eb789b
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.js.template
new file mode 100644
index 00000000000..315217d3cff
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/controller.js.template
new file mode 100644
index 00000000000..a3c8c2a9df9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/controller.js.template
@@ -0,0 +1,278 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, ${dollar}http, ViewParameters, LocaleService, EntityService) => {
+ const Dialogs = new DialogHub();
+ const Notifications = new NotificationHub();
+ let description = 'Description';
+ let propertySuccessfullyCreated = '${name} successfully created';
+ let propertySuccessfullyUpdated = '${name} successfully updated';
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+ $scope.formHeaders = {
+ select: '${name} Details',
+ create: 'Create ${name}',
+ update: 'Update ${name}'
+ };
+ $scope.action = 'select';
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ $scope.formHeaders.select = LocaleService.t('$projectName:${tprefix}.defaults.formHeadSelect', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.create = LocaleService.t('$projectName:${tprefix}.defaults.formHeadCreate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.update = LocaleService.t('$projectName:${tprefix}.defaults.formHeadUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyCreated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyCreated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyUpdated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyUpdated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = params.action;
+#if($hasDates)
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+#end
+ $scope.entity = params.entity;
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+#end
+ }
+
+ $scope.create = () => {
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.create(entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyCreated,
+ type: 'positive'
+ });
+ $scope.cancel();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCreate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.update = () => {
+ let id = ${dollar}scope.entity.${primaryKeysString};
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.update(id, entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyUpdated,
+ type: 'positive'
+ });
+ $scope.cancel();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ ${dollar}scope.service${property.name} = '${property.widgetDropdownControllerUrl}';
+
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetDependsOnProperty)
+ ${dollar}scope.${dollar}watch('entity.${property.widgetDependsOnProperty}', function (newValue, oldValue) {
+ if (newValue !== undefined && newValue !== null) {
+ ${dollar}http.get(${dollar}scope.service${property.widgetDependsOnProperty} + '/' + newValue).then((response) => {
+ let valueFrom = response.data.${property.widgetDependsOnValueFrom};
+#if($property.widgetType != "DROPDOWN")
+ $scope.entity.${property.name} = valueFrom;
+#end
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDependsOnFilterBy}', operator: 'EQ', value: valueFrom }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ if ($scope.action !== 'select' && newValue !== oldValue) {
+ if (${dollar}scope.options${property.name}.length == 1) {
+ $scope.entity.${property.name} = ${dollar}scope.options${property.name}[0].value;
+ } else {
+ $scope.entity.${property.name} = undefined;
+ }
+ }
+ }, (error) => {
+ console.error(error);
+ });
+#end
+ }, (error) => {
+ console.error(error);
+ });
+ }
+ });
+
+#end
+#end
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+ $scope.cancel = () => {
+ $scope.entity = {};
+ $scope.action = 'select';
+ Dialogs.closeWindow({ id: '${name}-details' });
+ };
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/index.html.template
new file mode 100644
index 00000000000..222e1978230
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/index.html.template
@@ -0,0 +1,578 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.relationshipType != "COMPOSITION")
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.extension.template
new file mode 100644
index 00000000000..ce739c305e7
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.js.template
new file mode 100644
index 00000000000..83ee2a40aad
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/dialog-window/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/dialog-window/index.html',
+ perspectiveName: '#if($hasReferencedProjection)${referencedProjectionPerspectiveName}#else${perspectiveName}#end'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template
new file mode 100644
index 00000000000..82ead712f55
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/index.html.template
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.messages.detailSelectRecord' | t}}
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.extension.template
new file mode 100644
index 00000000000..4f6c51e52dd
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View - Details"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.js.template
new file mode 100644
index 00000000000..30d6da8dc27
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/detail/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'bottom',
+ lazyLoad: false,
+ autoFocusTab: true,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${masterEntity}/${name}/index.html',
+ perspectiveName: '#if($hasReferencedProjection)${referencedProjectionPerspectiveName}#else${perspectiveName}#end'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..ba2093a6411
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/controller.js.template
@@ -0,0 +1,204 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ }});
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ lastSearchValues${property.name}.clear();
+ allValues${property.name}.length = 0;
+#end
+#end
+#end
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/index.html.template
new file mode 100644
index 00000000000..3c5162eb9c6
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/index.html.template
@@ -0,0 +1,336 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..8b596562b61
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.js.template
new file mode 100644
index 00000000000..3a25b9a3a55
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-filter',
+ label: '${name} Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.filter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-filter/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/controller.js.template
new file mode 100644
index 00000000000..90dfec0f29a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/controller.js.template
@@ -0,0 +1,289 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, ${dollar}http, ViewParameters, LocaleService, EntityService) => {
+ const Dialogs = new DialogHub();
+ const Notifications = new NotificationHub();
+ let description = 'Description';
+ let propertySuccessfullyCreated = '${name} successfully created';
+ let propertySuccessfullyUpdated = '${name} successfully updated';
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+ $scope.formHeaders = {
+ select: '${name} Details',
+ create: 'Create ${name}',
+ update: 'Update ${name}'
+ };
+ $scope.action = 'select';
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ $scope.formHeaders.select = LocaleService.t('$projectName:${tprefix}.defaults.formHeadSelect', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.create = LocaleService.t('$projectName:${tprefix}.defaults.formHeadCreate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.update = LocaleService.t('$projectName:${tprefix}.defaults.formHeadUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyCreated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyCreated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyUpdated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyUpdated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = params.action;
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const options${property.name}Map = new Map();
+ params.options${property.name}?.forEach(e => options${property.name}Map.set(e.value, e));
+ $scope.options${property.name} = Array.from(options${property.name}Map.values());
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.create = () => {
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.create(entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyCreated,
+ type: 'positive'
+ });
+ $scope.cancel();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ $scope.$evalAsync(() => {
+ $scope.errorMessage = LocaleService.t('$projectName:${tprefix}.messages.error.unableToCreate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message });
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.update = () => {
+ let id = ${dollar}scope.entity.${primaryKeysString};
+ let entity = $scope.entity;
+ entity[$scope.selectedMainEntityKey] = $scope.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ if (entity.${property.name}) {
+ entity.${property.name} = entity.${property.name}.join();
+ }
+#end
+#end
+ EntityService.update(id, entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', data: response.data });
+ $scope.cancel();
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyUpdated,
+ type: 'positive'
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ $scope.$evalAsync(() => {
+ $scope.errorMessage = LocaleService.t('$projectName:${tprefix}.messages.error.unableToUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message });
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ ${dollar}scope.service${property.name} = '${property.widgetDropdownControllerUrl}';
+
+ $scope.options${property.name} = [];
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+#end
+#end
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetDependsOnProperty)
+ ${dollar}scope.${dollar}watch('entity.${property.widgetDependsOnProperty}', (newValue, oldValue) => {
+ if (newValue !== undefined && newValue !== null) {
+ ${dollar}http.get(${dollar}scope.service${property.widgetDependsOnProperty} + '/' + newValue).then((response) => {
+ let valueFrom = response.data.${property.widgetDependsOnValueFrom};
+#if($property.widgetType != "DROPDOWN")
+ $scope.entity.${property.name} = valueFrom;
+#end
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDependsOnFilterBy}', operator: 'EQ', value: valueFrom }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ if ($scope.action !== 'select' && newValue !== oldValue) {
+ if (${dollar}scope.options${property.name}.length == 1) {
+ $scope.entity.${property.name} = ${dollar}scope.options${property.name}[0].value;
+ } else {
+ $scope.entity.${property.name} = undefined;
+ }
+ }
+ }, (error) => {
+ console.error(error);
+ });
+#end
+ }, (error) => {
+ console.error(error);
+ });
+ }
+ });
+
+#end
+#end
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+ $scope.cancel = () => {
+ $scope.entity = {};
+ $scope.action = 'select';
+ Dialogs.closeWindow({ id: '${name}-details' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/index.html.template
new file mode 100644
index 00000000000..9508241f605
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/index.html.template
@@ -0,0 +1,580 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.extension.template
new file mode 100644
index 00000000000..e0467a1d892
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.js.template
new file mode 100644
index 00000000000..7d7d5180fb3
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/dialog-window/view.js.template
@@ -0,0 +1,27 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: "${name}-details",
+ label: "${name}",
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: "/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/dialog-window/index.html",
+ perspectiveName: "${perspectiveName}",
+#if($perspectiveRole || $roleRead)
+ roles: [
+#if($perspectiveRole)
+ "${perspectiveRole}",
+#end
+#if($roleRead)
+ "${roleRead}",
+#end
+ ]
+#end
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/index.html.template
new file mode 100644
index 00000000000..4424b86fdd4
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/index.html.template
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#elseif($masterProperties.title.widgetType == "DATE")
+ item-title="{{next.${masterProperties.title.name} | date: 'dd/MMM/yyyy'}}">
+#elseif($masterProperties.title.widgetType == "DATETIME-LOCAL")
+ item-title="{{next.${masterProperties.title.name} | date: 'dd/MMM/yyyy HH:MM'}}">
+#elseif($masterProperties.title.widgetType == "TIME")
+ item-title="{{next.${masterProperties.title.name} | date: 'HH:MM:ss'}}">
+#elseif($masterProperties.title.widgetType == "WEEK")
+ item-title="Week {{next.${masterProperties.title.name} | date: 'ww'}}">
+#else
+ item-title="{{next.${masterProperties.title.name}}}">
+#end
+
+#foreach ($property in $masterProperties.properties)
+#if($property.widgetType == "DROPDOWN")
+ {{options${property.name}Value(next.${property.name})}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "DATE")
+ {{next.${property.name} | date: "dd/MMM/yyyy"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "DATETIME-LOCAL")
+ {{next.${property.name} | date: "dd/MMM/yyyy HH:MM"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "TIME")
+ {{next.${property.name} | date: "HH:MM:ss"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "WEEK")
+ Week {{next.${property.name} | date: "ww"}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "EMAIL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "URL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#elseif($property.widgetType == "TEL")
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#else
+ {{next.${property.name}}}#if(!$foreach.isLast()) |#end
+#end
+#end
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.loadMore' | t }}
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template
new file mode 100644
index 00000000000..929a0778448
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/controller.js.template
@@ -0,0 +1,363 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(["EntityServiceProvider", (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, ${dollar}http, Extensions, LocaleService, EntityService) => {
+ const Dialogs = new DialogHub();
+ const Notifications = new NotificationHub();
+ let description = 'Description';
+ let propertySuccessfullyCreated = '${name} successfully created';
+ let propertySuccessfullyUpdated = '${name} successfully updated';
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+ $scope.formHeaders = {
+ select: '${name} Details',
+ create: 'Create ${name}',
+ update: 'Update ${name}'
+ };
+ $scope.action = 'select';
+
+ LocaleService.onInit(() => {
+ description = LocaleService.t('$projectName:${tprefix}.defaults.description');
+ $scope.formHeaders.select = LocaleService.t('$projectName:${tprefix}.defaults.formHeadSelect', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.create = LocaleService.t('$projectName:${tprefix}.defaults.formHeadCreate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ $scope.formHeaders.update = LocaleService.t('$projectName:${tprefix}.defaults.formHeadUpdate', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyCreated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyCreated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ propertySuccessfullyUpdated = LocaleService.t('$projectName:${tprefix}.messages.propertySuccessfullyUpdated', { name: '$t($projectName:${tprefix}.t.${dataName})' });
+ });
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString}
+ },
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', handler: () => {
+ $scope.$evalAsync(() => {
+ $scope.entity = {};
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+ $scope.action = 'select';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySelected', handler: (data) => {
+ $scope.$evalAsync(() => {
+ #foreach ($property in $properties)
+#if($property.isDateType)
+ if (data.entity.${property.name}) {
+ data.entity.${property.name} = new Date(data.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = data.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = data.options${property.name};
+#end
+#end
+ $scope.action = 'select';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.createEntity', handler: (data) => {
+ $scope.$evalAsync(() => {
+ $scope.entity = {};
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = data.options${property.name};
+#end
+#end
+ $scope.action = 'create';
+ });
+ }});
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.updateEntity', handler: (data) => {
+ $scope.$evalAsync(() => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (data.entity.${property.name}) {
+ data.entity.${property.name} = new Date(data.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = data.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = data.options${property.name};
+#end
+#end
+ $scope.action = 'update';
+ });
+ }});
+
+#foreach ($property in $properties)
+#if ($property.widgetType == "DROPDOWN")
+ ${dollar}scope.service${property.name} = '${property.widgetDropdownControllerUrl}';
+#end
+#end
+
+#foreach ($property in $properties)
+#if($property.widgetDependsOnProperty)
+
+ ${dollar}scope.${dollar}watch('entity.${property.widgetDependsOnProperty}', (newValue, oldValue) => {
+ if (newValue !== undefined && newValue !== null) {
+ ${dollar}http.get(${dollar}scope.service${property.widgetDependsOnProperty} + '/' + newValue).then((response) => {
+ let valueFrom = response.data.${property.widgetDependsOnValueFrom};
+#if($property.widgetType != "DROPDOWN")
+ $scope.entity.${property.name} = valueFrom;
+#end
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDependsOnFilterBy}', operator: 'EQ', value: valueFrom }
+ ]
+ }).then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ if ($scope.action !== 'select' && newValue !== oldValue) {
+ if (${dollar}scope.options${property.name}.length == 1) {
+ $scope.entity.${property.name} = ${dollar}scope.options${property.name}[0].value;
+ } else {
+ $scope.entity.${property.name} = undefined;
+ }
+ }
+ }, (error) => {
+ console.error(error);
+ });
+#end
+ }, (error) => {
+ console.error(error);
+ });
+ }
+ });
+#end
+#end
+ //-----------------Events-------------------//
+
+ $scope.create = () => {
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.join();
+#end
+#end
+ EntityService.create($scope.entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityCreated', data: response.data });
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails' , data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyCreated,
+ type: 'positive'
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCreate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.update = () => {
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN" && $property.widgetDropDownMultiSelect)
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.join();
+#end
+#end
+ EntityService.update($scope.entity.${primaryKeysString}, $scope.entity).then((response) => {
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entityUpdated', data: response.data });
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.clearDetails', data: response.data });
+ Notifications.show({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ description: propertySuccessfullyUpdated,
+ type: 'positive'
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCreate', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+
+ $scope.cancel = () => {
+ Dialogs.triggerEvent('${projectName}.${perspectiveName}.${name}.clearDetails');
+ };
+
+ //-----------------Dialogs-------------------//
+ $scope.alert = (message) => {
+ if (message) Dialogs.showAlert({
+ title: description,
+ message: message,
+ type: AlertTypes.Information,
+ preformatted: true,
+ });
+ };
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.create${property.name} = () => {
+ Dialogs.showWindow({
+ id: '${property.relationshipEntityName}-details',
+ params: {
+ action: 'create',
+ entity: {},
+ },
+ closeButton: false
+ });
+ };
+#end
+#end
+#end
+
+ //-----------------Dialogs-------------------//
+
+
+
+ //----------------Dropdowns-----------------//
+
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ const lastSearchValues${property.name} = new Set();
+ const allValues${property.name} = [];
+ let loadMoreOptions${property.name}Counter = 0;
+ $scope.options${property.name}Loading = false;
+ $scope.options${property.name}HasMore = true;
+
+ $scope.loadMoreOptions${property.name} = () => {
+ const limit = 20;
+ $scope.options${property.name}Loading = true;
+ ${dollar}http.get(`${property.widgetDropdownControllerUrl}?$limit=${dollar}{limit}&$offset=${dollar}{++loadMoreOptions${property.name}Counter * limit}`)
+ .then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const resultValues = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ const newValues = [];
+ resultValues.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ newValues.push(e);
+ }
+ });
+ newValues.forEach(e => {
+ if (!${dollar}scope.options${property.name}.find(o => o.value === e.value)) {
+ ${dollar}scope.options${property.name}.push(e);
+ }
+ })
+ $scope.options${property.name}HasMore = resultValues.length > 0;
+ $scope.options${property.name}Loading = false;
+ }, (error) => {
+ $scope.options${property.name}Loading = false;
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+
+ $scope.onOptions${property.name}Change = (event) => {
+ if (allValues${property.name}.length === 0) {
+ allValues${property.name}.push(...${dollar}scope.options${property.name});
+ }
+ if (event.originalEvent.target.value === '') {
+ allValues${property.name}.sort((a, b) => a.text.localeCompare(b.text));
+ ${dollar}scope.options${property.name} = allValues${property.name};
+ $scope.options${property.name}HasMore = true;
+ } else if (isText(event.which)) {
+ $scope.options${property.name}HasMore = false;
+ let cacheHit = false;
+ Array.from(lastSearchValues${property.name}).forEach(e => {
+ if (event.originalEvent.target.value.startsWith(e)) {
+ cacheHit = true;
+ }
+ })
+ if (!cacheHit) {
+ ${dollar}http.post('${property.widgetDropdownControllerUrl}/search', {
+ conditions: [
+ { propertyName: '${property.widgetDropDownValue}', operator: 'LIKE', value: `${dollar}{event.originalEvent.target.value}%` }
+ ]
+ }).then((response) => {
+ const optionValues = allValues${property.name}.map(e => e.value);
+ const searchResult = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ searchResult.forEach(e => {
+ if (!optionValues.includes(e.value)) {
+ allValues${property.name}.push(e);
+ }
+ });
+ ${dollar}scope.options${property.name} = allValues${property.name}.filter(e => e.text.toLowerCase().startsWith(event.originalEvent.target.value.toLowerCase()));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ lastSearchValues${property.name}.add(event.originalEvent.target.value);
+ }
+ }
+ };
+
+ $scope.refresh${property.name} = () => {
+ $scope.options${property.name} = [];
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ allValues${property.name}.length === 0;
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+ };
+#end
+#end
+
+ function isText(keycode) {
+ if ((keycode >= 48 && keycode <= 90) || (keycode >= 96 && keycode <= 111) || (keycode >= 186 && keycode <= 222) || [8, 46, 173].includes(keycode)) return true;
+ return false;
+ }
+#end
+
+ //----------------Dropdowns-----------------//
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template
new file mode 100644
index 00000000000..42882bd555d
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/index.html.template
@@ -0,0 +1,589 @@
+#set($dollar = '$')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+ More Options
+
+
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+ #if($property.description)
+
+
+
+ #end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+ #end
+
+
+ #if(!$property.isCalculatedProperty)
+
+ #end
+ #if($property.description)
+
+ #end
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.extension.template
new file mode 100644
index 00000000000..2eaa6ee3f86
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/main-details/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View - Main Details"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.js.template
new file mode 100644
index 00000000000..87de655838a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/main-details/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+ lazyLoad: false,
+ autoFocusTab: true,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/main-details/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.extension.template
new file mode 100644
index 00000000000..f9ae92d4291
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/view.js",
+ "extensionPoint": "application-views",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.js.template
new file mode 100644
index 00000000000..a3927b88432
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/master-manage/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'left',
+ lazyLoad: false,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/${name}/index.html',
+ perspectiveName: '${perspectiveName}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.extension.template
new file mode 100644
index 00000000000..bb5708b3b2e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/${perspectiveName}/perspective.js",
+ "extensionPoint": "application-perspectives",
+ "description": "${projectName} - Perspective - ${perspectiveName}"#if($perspectiveRole || $roleRead || $roleWrite),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead},#end#if($roleWrite)${roleWrite}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.js.template
new file mode 100644
index 00000000000..9c57d2021f1
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/perspective.js.template
@@ -0,0 +1,29 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const perspectiveData = {
+ id: '${perspectiveName}',
+ label: '${perspectiveLabel}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+#if($perspectiveHeader)
+ header: '${perspectiveHeader}',
+ headerTranslation: {
+ key: '$projectName:${tprefix}.t.${perspectiveName}pheader',
+ },
+#end
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/${perspectiveName}/index.html',
+#if($perspectiveNavId)
+ groupId: '${perspectiveNavId}',
+#end
+#if($perspectiveOrder)
+ order: ${perspectiveOrder},
+#end
+ icon: '${perspectiveIcon}'
+};
+if (typeof exports !== 'undefined') {
+ exports.getPerspective = () => perspectiveData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/controller.js.template
new file mode 100644
index 00000000000..0c7647c1517
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/controller.js.template
@@ -0,0 +1,218 @@
+#set($dollar = '$')
+#set($hasDropdowns = false)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+#set($hasDropdowns = true)
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+#set($hasDropdowns = true)
+#end
+#end
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, LocaleService) => {
+ const Dialogs = new DialogHub();
+ $scope.filter = {};
+
+ const ctx = document.getElementById('myChart');
+ const myChart = new Chart(ctx, {
+#if($layoutType == "REPORT_BAR")
+ type: 'bar',
+#elseif($layoutType == "REPORT_LINE")
+ type: 'line',
+#elseif($layoutType == "REPORT_PIE")
+ type: 'pie',
+#elseif($layoutType == "REPORT_DOUGHNUT")
+ type: 'doughnut',
+#elseif($layoutType == "REPORT_POLARAREA")
+ type: 'polarArea',
+#elseif($layoutType == "REPORT_RADAR")
+ type: 'radar',
+#end
+ data: {
+ labels: [],
+ datasets: []
+ }
+ });
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.filter', handler: (data) => {
+ $scope.filter = data;
+ ${dollar}scope.loadPage();
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = () => {
+ EntityService.count($scope.filter).then((resp) => {
+ $scope.dataCount = resp.data.count;
+ EntityService.list($scope.filter).then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ myChart.data.labels = ${dollar}scope.data.map(e => #foreach($property in $properties)#if($property.dataPrimaryKey)e.${property.name}#end#end);
+ myChart.data.datasets = [
+#foreach ($property in $properties)
+#if(!$property.dataPrimaryKey)
+ {
+#if($property.widgetLabel)
+ label: LocaleService.t('$projectName:${tprefix}.t.$property.dataName', '${property.widgetLabel}'),
+#else
+ label: '${property.name}',
+#end
+ data: ${dollar}scope.data.map(e => e.${property.name}),
+#if($layoutType == "REPORT_LINE")
+ fill: true,
+#end
+ borderWidth: 1
+ },
+#end
+#end
+ ];
+ myChart.canvas.parentNode.style.height = '90%';
+ myChart.update();
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage();
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-details-filter',
+ params: {
+ action: 'filter',
+ filter: $scope.filter,
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ },
+ });
+ };
+
+#if($hasDropdowns)
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/controller.js.template
new file mode 100644
index 00000000000..f87c039d48e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/controller.js.template
@@ -0,0 +1,51 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $filter.properties)
+#if($property.isDateType)
+ if (params?.filter?.${property.name}) {
+ params.filter.${property.name} = new Date(params.filter.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.filter ?? {};
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ const filter = {
+ ...$scope.entity
+ };
+#foreach ($property in $filter.properties)
+#if($property.isDateType)
+ filter.${property.name} = filter.${property.name}?.getTime();
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.filter', data: filter });
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-details-filter' });
+ };
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/index.html.template
new file mode 100644
index 00000000000..f993cbea70c
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/index.html.template
@@ -0,0 +1,462 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $filter.properties)
+#if(!$property.dataAutoIncrement && !$property.dataPrimaryKey)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.extension.template
new file mode 100644
index 00000000000..4df3b883da0
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window Filter"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.js.template
new file mode 100644
index 00000000000..11cf1467699
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/dialog-window-filter/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details-filter',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window-filter/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/index.html.template
new file mode 100644
index 00000000000..e0ec4e8ae48
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/index.html.template
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.extension.template
new file mode 100644
index 00000000000..131c0d28481
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/view.js",
+ "extensionPoint": "application-reports",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.js.template
new file mode 100644
index 00000000000..35a6d21a2e2
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-chart/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+ lazyLoad: true,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/controller.js.template
new file mode 100644
index 00000000000..dc97e5f71f6
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/controller.js.template
@@ -0,0 +1,137 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope, EntityService, LocaleService, Extensions) => {
+ const Dialogs = new DialogHub();
+ const exportsHub = new ExportsHub();
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+
+ $scope.triggerExportAction = () => {
+ let request = EntityService.exportCsv();
+ request.then(() => {
+ exportsHub.refresh();
+ });
+ }
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ filterEntity: $scope.filterEntity,
+ },
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ $scope.dataPage = pageNumber;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ let offset = (pageNumber - 1) * $scope.dataLimit;
+ let limit = $scope.dataLimit;
+ let request;
+ if (filter) {
+ if (!filter.$filter) {
+ filter.$filter = {};
+ }
+ filter.$filter.offset = offset;
+ filter.$filter.limit = limit;
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list(offset, limit);
+ }
+ request.then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${tId}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${tId})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${tId}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${tId})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-Report-details',
+ params: {
+ action: 'select',
+ entity: entity,
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-Report-filter',
+ params: {
+ entity: $scope.filterEntity,
+ },
+ });
+ };
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..94413cef269
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/controller.js.template
@@ -0,0 +1,56 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($parameter in $parameters)
+#if($parameter.typeTypescript == 'Date')
+ if (params?.entity?.${parameter.name}) {
+ params.entity.${parameter.name} = new Date(params.entity.${parameter.name});
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+
+ };
+#foreach($parameter in $parameters)
+ if (entity.${parameter.name}) {
+#if($parameter.typeTypescript == 'Date')
+ filter.${parameter.name} = entity.${parameter.name}?.getTime();
+#else
+ filter.${parameter.name} = entity.${parameter.name};
+#end
+ }
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ } });
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-Report-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/index.html.template
new file mode 100644
index 00000000000..3e64acd2057
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/index.html.template
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($parameter in $parameters)
+#if($parameter.typeTypescript == 'boolean')
+
+
+
+
+ ${parameter.name}
+
+
+#elseif($parameter.typeTypescript == 'number')
+
+
+ ${parameter.name}
+
+
+
+
+
+
+#elseif($parameter.typeJava == 'time')
+
+
+ ${parameter.name}
+
+
+
+
+
+
+
+
+#elseif($parameter.typeJava == 'timestamp')
+
+
+ ${parameter.name}
+
+
+
+
+
+
+
+
+#elseif($parameter.typeJava == 'date')
+
+
+ ${parameter.name}
+
+
+
+
+
+
+
+
+#else
+
+
+ ${parameter.name}
+
+
+
+
+
+
+
+
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..a680c80d3f2
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $security.roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($security.roleRead)${security.roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.js.template
new file mode 100644
index 00000000000..b62f9628c70
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: "${name}-Report-filter",
+ label: "${label} Report Filter",
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.${tId}) $t($projectName:${tprefix}.defaults.reportFilter)',
+ }
+ },
+ path: "/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-filter/index.html",
+ perspectiveName: "Reports"
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/controller.js.template
new file mode 100644
index 00000000000..9068fb51e80
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/controller.js.template
@@ -0,0 +1,66 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, EntityService, LocaleService, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ const filterEntity = params.filterEntity ?? {};
+
+ $scope.filter = {};
+#foreach ($parameter in $parameters)
+ if (filterEntity.${parameter.name}) {
+#if($parameter.typeTypescript == 'Date')
+ $scope.filter.${parameter.name} = new Date(filterEntity.${parameter.name});
+#else
+ $scope.filter.${parameter.name} = filterEntity.${parameter.name};
+#end
+ }
+#end
+ }
+
+ $scope.loadPage = (filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ let request;
+ if (filter) {
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list();
+ }
+ request.then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($column in $columns)
+#if($column.typeTypescript == 'Date')
+ if (e['${column.alias}']) {
+ e['${column.alias}'] = new Date(e['${column.alias}']);
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ setTimeout(() => {
+ window.print();
+ }, 250);
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${tId}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${tId})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.filter);
+
+ window.onafterprint = () => {
+ Dialogs.closeWindow({ path: viewData.path });
+ }
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/index.html.template
new file mode 100644
index 00000000000..ae8c5f71c2e
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/index.html.template
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.reportTitle' | t:{'name':'$t($projectName:${tprefix}.t.${tId})'}:'${label}' }}
+
+
+
+
+
+ #foreach ($column in $columns)
+ | {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }} |
+ #end
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+ #foreach ($column in $columns)
+ #if($column.typeJava == 'time')
+ |
+
+ |
+ #elseif($column.typeJava == 'timestamp')
+
+
+ |
+ #elseif($column.typeJava == 'date')
+
+
+ |
+ #elseif($column.typeTypescript == 'boolean')
+
+
+
+ |
+ #else
+ {{next['${column.alias}']}} |
+ #end
+ #end
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.extension.template
new file mode 100644
index 00000000000..4d88b62f526
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.extension.template
@@ -0,0 +1,6 @@
+{
+ "extensionPoint": "${projectName}-custom-action",
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-print/print.js",
+ "description": "Print ${name} Report"#if($perspectiveRole || $security.roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($security.roleRead)${security.roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.js.template
new file mode 100644
index 00000000000..e72ea300abb
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-print/print.js.template
@@ -0,0 +1,15 @@
+const viewData = {
+ id: '{{projectName}}-Reports-{{name}}-print',
+ label: 'Print',
+ translation: {
+ key: '{{projectName}}:{{tprefix}}.defaults.print',
+ },
+ path: '/services/web/{{projectName}}/gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/index.html',
+ perspective: 'Reports',
+ view: '{{name}}',
+ type: 'page',
+ order: 10
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/controller.js.template
new file mode 100644
index 00000000000..361d77acde4
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/controller.js.template
@@ -0,0 +1,18 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = 'select';
+
+#foreach ($column in $columns)
+#if($column.typeTypescript == 'Date')
+ if (params.entity['${column.alias}']) {
+ params.entity['${column.alias}'] = new Date(params.entity['${column.alias}']);
+ }
+#end
+#end
+ $scope.entity = params.entity;
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/index.html.template
new file mode 100644
index 00000000000..5c32bc693c0
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/index.html.template
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($column in $columns)
+#if($column.typeTypescript == 'boolean')
+
+
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+#elseif($column.typeTypescript == 'number')
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+
+
+
+
+#elseif($column.typeJava == 'time')
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+
+
+
+
+#elseif($column.typeJava == 'timestamp')
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+
+
+
+
+#elseif($column.typeJava == 'date')
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }}
+
+
+
+
+
+
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.extension.template
new file mode 100644
index 00000000000..a21603202c6
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $security.roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($security.roleRead)${security.roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.js.template
new file mode 100644
index 00000000000..9745e969ea9
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/dialog-window/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-Report-details',
+ label: '${label} Report',
+ translation: {
+ key: '$projectName:${tprefix}.defaults.reportTitle',
+ options: {
+ name: '$t($projectName:${tprefix}.t.${tId})',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/index.html.template
new file mode 100644
index 00000000000..c7fa8570c34
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/index.html.template
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+#if($parameters && $parameters.size() > 0)
+
+#end
+
+
+
+
+
+
+
+
+
+#foreach ($column in $columns)
+ | {{ '$projectName:${tprefix}.t.${column.tId}' | t:'${column.label}' }} |
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($column in $columns)
+ | {{next['${column.alias}']}} |
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.extension.template
new file mode 100644
index 00000000000..822b23ace4f
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/view.js",
+ "extensionPoint": "application-reports",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $security.roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($security.roleRead)${security.roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.js.template
new file mode 100644
index 00000000000..d35b195e57f
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-file/view.js.template
@@ -0,0 +1,23 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}Report',
+ label: '${label} Report',
+ translation: {
+ key: '$projectName:${tprefix}.defaults.reportTitle',
+ options: {
+ name: '$t($projectName:${tprefix}.t.${tId})',
+ }
+ },
+ region: 'center',
+ lazyLoad: true,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/controller.js.template
new file mode 100644
index 00000000000..d2bd85da9b3
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/controller.js.template
@@ -0,0 +1,224 @@
+#set($dollar = '$')
+#set($hasDropdowns = false)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+#set($hasDropdowns = true)
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+#set($hasDropdowns = true)
+#end
+#end
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end, EntityService, LocaleService) => {
+ const Dialogs = new DialogHub();
+
+ $scope.filter = {};
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.filter', handler: (data) => {
+ $scope.filter = data;
+ ${dollar}scope.loadPage(1);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber) => {
+ const listFilter = {
+ $filter: {
+ offset: (pageNumber - 1) * $scope.dataLimit,
+ limit: $scope.dataLimit,
+ ...$scope.filter
+ }
+ };
+ $scope.dataPage = pageNumber;
+ EntityService.count($scope.filter).then((resp) => {
+ $scope.dataCount = resp.data.count;
+ EntityService.list(listFilter).then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'), message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }), type: AlertTypes.Error });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlertError({ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'), message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }), type: AlertTypes.Error });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-details',
+ params: {
+ action: 'select',
+ entity: entity,
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-details-filter',
+ params: {
+ action: 'filter',
+ filter: $scope.filter,
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+ }
+ });
+ };
+
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/controller.js.template
new file mode 100644
index 00000000000..6727ddc7c70
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/controller.js.template
@@ -0,0 +1,62 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $filter.properties)
+#if($property.isDateType)
+ if (params?.filter?.${property.name}) {
+ params.filter.${property.name} = new Date(params.filter.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.filter ?? {};
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+#foreach ($property in $filter.properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ const filter = {
+ ...$scope.entity
+ };
+#foreach ($property in $filter.properties)
+#if($property.isDateType)
+ filter.${property.name} = filter.${property.name}?.getTime();
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.filter', data: filter });
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-details-filter' });
+ };
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/index.html.template
new file mode 100644
index 00000000000..999957e6fd7
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/index.html.template
@@ -0,0 +1,463 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $filter.properties)
+#if(!$property.dataAutoIncrement && !$property.dataPrimaryKey)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+#if(!$property.isCalculatedProperty)
+
+ #elseif($property.maxLength && $property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMinMax' | t:{'min':'${property.minLength}','max':'${property.maxLength}'} }}">
+ #elseif($property.maxLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMax' | t:{'max':'${property.maxLength}'} }}">
+ #elseif($property.minLength)
+ text="{{ '$projectName:${tprefix}.messages.error.lengthMin' | t:{'min':'${property.minLength}'} }}">
+ #else
+ text="{{ '$projectName:${tprefix}.messages.error.incorrectInput' | t }}">
+ #end
+#end
+
+
+#if(!$property.isCalculatedProperty)
+
+#end
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.extension.template
new file mode 100644
index 00000000000..4df3b883da0
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window Filter"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.js.template
new file mode 100644
index 00000000000..11cf1467699
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window-filter/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details-filter',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window-filter/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/controller.js.template
new file mode 100644
index 00000000000..3f33cda3422
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/controller.js.template
@@ -0,0 +1,28 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = 'select';
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/index.html.template
new file mode 100644
index 00000000000..05b17a7951a
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/index.html.template
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.extension.template
new file mode 100644
index 00000000000..ec6b82a3c18
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.js.template
new file mode 100644
index 00000000000..f10442fcfcc
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/dialog-window/view.js.template
@@ -0,0 +1,17 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-details',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/index.html.template
new file mode 100644
index 00000000000..b8844bd17ca
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/index.html.template
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if($property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if($property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.extension.template
new file mode 100644
index 00000000000..131c0d28481
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/view.js",
+ "extensionPoint": "application-reports",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.js.template
new file mode 100644
index 00000000000..35a6d21a2e2
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report-table/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}',
+ label: '${name}',
+ translation: {
+ key: '$projectName:${tprefix}.t.$dataName',
+ },
+ region: 'center',
+ lazyLoad: true,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/controller.js.template
new file mode 100644
index 00000000000..409513a3607
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/controller.js.template
@@ -0,0 +1,217 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', (${dollar}scope,#if($hasDropdowns) ${dollar}http,#end EntityService, LocaleService, Extensions) => {
+ const Dialogs = new DialogHub();
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+
+ //-----------------Custom Actions-------------------//
+ Extensions.getWindows(['${projectName}-custom-action']).then((response) => {
+ $scope.pageActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && (e.type === 'page' || e.type === undefined));
+ $scope.entityActions = response.data.filter(e => e.perspective === '${perspectiveName}' && e.view === '${name}' && e.type === 'entity');
+ });
+
+ $scope.triggerPageAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ filterEntity: $scope.filterEntity,
+ #if($hasDropdowns)
+ #foreach ($property in $properties)
+ #if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+ #end
+ #end
+ #end
+ },
+ maxWidth: action.maxWidth,
+ maxHeight: action.maxHeight,
+ closeButton: true,
+ });
+ };
+
+ $scope.triggerEntityAction = (action) => {
+ Dialogs.showWindow({
+ hasHeader: true,
+ title: LocaleService.t(action.translation.key, action.translation.options, action.label),
+ path: action.path,
+ params: {
+ id: $scope.entity.${primaryKeysString}
+ },
+ closeButton: true,
+ });
+ };
+ //-----------------Custom Actions-------------------//
+
+ function resetPagination() {
+ $scope.dataPage = 1;
+ $scope.dataCount = 0;
+ $scope.dataLimit = 20;
+ }
+ resetPagination();
+
+ //-----------------Events-------------------//
+ Dialogs.addMessageListener({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', handler: (data) => {
+ resetPagination();
+ $scope.filter = data.filter;
+ $scope.filterEntity = data.entity;
+ $scope.loadPage($scope.dataPage, $scope.filter);
+ }});
+ //-----------------Events-------------------//
+
+ $scope.loadPage = (pageNumber, filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ $scope.dataPage = pageNumber;
+ EntityService.count(filter).then((resp) => {
+ if (resp.data) {
+ $scope.dataCount = resp.data.count;
+ }
+ let offset = (pageNumber - 1) * $scope.dataLimit;
+ let limit = $scope.dataLimit;
+ let request;
+ if (filter) {
+ if (!filter.$filter) {
+ filter.$filter = {};
+ }
+ filter.$filter.offset = offset;
+ filter.$filter.limit = limit;
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list(offset, limit);
+ }
+ request.then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToCount', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.dataPage, $scope.filter);
+
+ $scope.selectEntity = (entity) => {
+ $scope.selectedEntity = entity;
+ };
+
+ $scope.openDetails = (entity) => {
+ $scope.selectedEntity = entity;
+ Dialogs.showWindow({
+ id: '${name}-Report-details',
+ params: {
+ action: "select",
+ entity: entity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+
+ $scope.openFilter = () => {
+ Dialogs.showWindow({
+ id: '${name}-Report-filter',
+ params: {
+ entity: $scope.filterEntity,
+#if($hasDropdowns)
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ options${property.name}: $scope.options${property.name},
+#end
+#end
+#end
+ },
+ });
+ };
+#if($hasDropdowns)
+
+ //----------------Dropdowns-----------------//
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = [];
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+
+ ${dollar}http.get('${property.widgetDropdownControllerUrl}').then((response) => {
+ ${dollar}scope.options${property.name} = response.data.map(e => ({
+ value: e.${property.widgetDropDownKey},
+ text: e.${property.widgetDropDownValue}
+ }));
+ }, (error) => {
+ console.error(error);
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: '${property.name}',
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLoad', { message: message }),
+ type: AlertTypes.Error
+ });
+ });
+#end
+#end
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+#if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+#else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+#end
+ };
+#end
+#end
+ //----------------Dropdowns-----------------//
+#end
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/controller.js.template
new file mode 100644
index 00000000000..ce7b88fca07
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/controller.js.template
@@ -0,0 +1,92 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ $scope.entity = {};
+ $scope.forms = {
+ details: {},
+ };
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params?.entity?.${property.name}From) {
+ params.entity.${property.name}From = new Date(params.entity.${property.name}From);
+ }
+ if (params?.entity?.${property.name}To) {
+ params.entity.${property.name}To = new Date(params.entity.${property.name}To);
+ }
+#end
+#end
+ $scope.entity = params.entity ?? {};
+ $scope.selectedMainEntityKey = params.selectedMainEntityKey;
+ $scope.selectedMainEntityId = params.selectedMainEntityId;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+
+ $scope.filter = () => {
+ let entity = $scope.entity;
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+ Dialogs.postMessage({ topic: '${projectName}.${perspectiveName}.${name}.entitySearch', data: {
+ entity: entity,
+ filter: filter
+ } });
+ $scope.cancel();
+ };
+
+ $scope.resetFilter = () => {
+ $scope.entity = {};
+ $scope.filter();
+ };
+
+ $scope.cancel = () => {
+ Dialogs.closeWindow({ id: '${name}-Report-filter' });
+ };
+
+ $scope.clearErrorMessage = () => {
+ $scope.errorMessage = null;
+ };
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/index.html.template
new file mode 100644
index 00000000000..50d0faa8cff
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/index.html.template
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorMessage }}
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.defaults.from' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.to' | t:{'text':'$t($projectName:${tprefix}.t.$property.dataName)'} }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.extension.template
new file mode 100644
index 00000000000..ad430eb623b
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-filter/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.js.template
new file mode 100644
index 00000000000..e4eedc0a0bb
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-filter/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-Report-filter',
+ label: '${name} Report Filter',
+ translation: {
+ key: '$projectName:${tprefix}.extName',
+ options: {
+ content: '$t($projectName:${tprefix}.t.$dataName) $t($projectName:${tprefix}.defaults.reportFilter)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-filter/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/controller.js.template
new file mode 100644
index 00000000000..cf66ad8d145
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/controller.js.template
@@ -0,0 +1,130 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale', 'EntityService'])
+ .config(['EntityServiceProvider', (EntityServiceProvider) => {
+ EntityServiceProvider.baseUrl = '/services/java/${projectName}/gen/${javaGenFolderName}/api/${javaPerspectiveName}/${name}Controller';
+ }])
+ .controller('PageController', ($scope, EntityService, LocaleService, ViewParameters) => {
+ const Dialogs = new DialogHub();
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ const filterEntity = params.filterEntity ?? {};
+
+ const filter = {
+ $filter: {
+ conditions: [],
+ sorts: [],
+ limit: 20,
+ offset: 0
+ }
+ };
+#foreach ($property in $properties)
+#if($property.dataTypeTypescript == 'number')
+ if (entity.${property.name} !== undefined) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'boolean')
+ if (entity.${property.name} !== undefined && entity.is${property.name}Indeterminate === false) {
+ const condition = { propertyName: '${property.name}', operator: 'EQ', value: entity.${property.name} };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'string')
+ if (entity.${property.name}) {
+ const condition = { propertyName: '${property.name}', operator: 'LIKE', value: `%$dollar{entity.${property.name}}%` };
+ filter.$filter.conditions.push(condition);
+ }
+#elseif($property.dataTypeTypescript == 'Date')
+ if (entity.${property.name}From) {
+ const condition = { propertyName: '${property.name}', operator: 'GE', value: entity.${property.name}From };
+ filter.$filter.conditions.push(condition);
+ }
+ if (entity.${property.name}To) {
+ const condition = { propertyName: '${property.name}', operator: 'LE', value: entity.${property.name}To };
+ filter.$filter.conditions.push(condition);
+ }
+#end
+#end
+
+ $scope.filter = filter;
+
+ #foreach ($property in $properties)
+ #if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+ #if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+ #end
+ #end
+ #end
+ }
+
+ $scope.loadPage = (filter) => {
+ if (!filter && $scope.filter) {
+ filter = $scope.filter;
+ }
+ let request;
+ if (filter) {
+ request = EntityService.search(filter);
+ } else {
+ request = EntityService.list();
+ }
+ request.then((response) => {
+#if($hasDates)
+ response.data.forEach(e => {
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (e.${property.name}) {
+ e.${property.name} = new Date(e.${property.name});
+ }
+#end
+#end
+ });
+
+#end
+ $scope.data = response.data;
+ setTimeout(() => {
+ window.print();
+ }, 250);
+ }, (error) => {
+ const message = error.data ? error.data.message : '';
+ Dialogs.showAlert({
+ title: LocaleService.t('$projectName:${tprefix}.t.${dataName}'),
+ message: LocaleService.t('$projectName:${tprefix}.messages.error.unableToLF', { name: '$t($projectName:${tprefix}.t.${dataName})', message: message }),
+ type: AlertTypes.Error
+ });
+ console.error('EntityService:', error);
+ });
+ };
+ $scope.loadPage($scope.filter);
+
+#foreach ($property in $properties)
+ #if($property.widgetType == "DROPDOWN")
+ ${dollar}scope.options${property.name}Value = (optionKey) => {
+ #if($property.widgetDropDownMultiSelect)
+ const values = [];
+ if (Array.isArray(optionKey)) {
+ optionKey = optionKey.join();
+ }
+ optionKey.split(',').map(e => parseInt(e)).forEach(key => {
+ const found = ${dollar}scope.options${property.name}.find(e => e.value === key);
+ if (found) {
+ values.push(found.text);
+ }
+ });
+ return values.join(', ');
+ #else
+ for (let i = 0; i < ${dollar}scope.options${property.name}.length; i++) {
+ if (${dollar}scope.options${property.name}[i].value === optionKey) {
+ return ${dollar}scope.options${property.name}[i].text;
+ }
+ }
+ return null;
+ #end
+ };
+ #end
+#end
+ window.onafterprint = () => {
+ Dialogs.closeWindow({ path: viewData.path });
+ }
+ });
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/index.html.template
new file mode 100644
index 00000000000..8df0de7f828
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/index.html.template
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ '$projectName:${tprefix}.defaults.reportTitle' | t:{'name':'$name'} }}
+
+
+
+
+
+ #foreach ($property in $properties)
+ #if(!$property.dataAutoIncrement)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+ #end
+ #end
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+ #foreach ($property in $properties)
+ #if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ #if($property.widgetType == "DROPDOWN")
+ | {{options${property.name}Value(next.${property.name})}} |
+ #elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+ #elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+ #elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+ #elseif($property.widgetType == "COLOR")
+
+
+ |
+ #elseif($property.widgetType == "WEEK")
+
+
+ |
+ #elseif($property.widgetType == "MONTH")
+
+
+ |
+ #elseif($property.widgetType == "TIME")
+
+
+ |
+ #elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+ #elseif($property.widgetType == "DATE")
+
+
+ |
+ #elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+ #else
+ {{next.${property.name}}} |
+ #end
+ #end
+ #end
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.extension.template
new file mode 100644
index 00000000000..58d876e1670
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.extension.template
@@ -0,0 +1,6 @@
+{
+ "extensionPoint": "${projectName}-custom-action",
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-print/print.js",
+ "description": "Print ${name} Report"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.js.template
new file mode 100644
index 00000000000..f7e2db83111
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-print/print.js.template
@@ -0,0 +1,15 @@
+const viewData = {
+ id: '{{projectName}}-Reports-{{name}}-print',
+ label: 'Print',
+ translation: {
+ key: '$projectName:${tprefix}.defaults.print',
+ },
+ path: '/services/web/{{projectName}}/gen/{{genFolderName}}/ui/Reports/{{name}}/dialog-print/index.html',
+ perspective: 'Reports',
+ view: '{{name}}',
+ type: 'page',
+ order: 10
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/controller.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/controller.js.template
new file mode 100644
index 00000000000..3f33cda3422
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/controller.js.template
@@ -0,0 +1,28 @@
+#set($dollar = '$')
+angular.module('page', ['blimpKit', 'platformView', 'platformLocale']).controller('PageController', ($scope, ViewParameters) => {
+ $scope.entity = {};
+
+ let params = ViewParameters.get();
+ if (Object.keys(params).length) {
+ $scope.action = 'select';
+
+#foreach ($property in $properties)
+#if($property.isDateType)
+ if (params.entity.${property.name}) {
+ params.entity.${property.name} = new Date(params.entity.${property.name});
+ }
+#end
+#end
+ $scope.entity = params.entity;
+#foreach ($property in $properties)
+#if($property.widgetType == "DROPDOWN")
+ $scope.options${property.name} = params.options${property.name};
+#if($property.widgetDropDownMultiSelect)
+ if (${dollar}scope.entity.${property.name}) {
+ ${dollar}scope.entity.${property.name} = ${dollar}scope.entity.${property.name}.split(',').map(e => parseInt(e));
+ }
+#end
+#end
+#end
+ }
+});
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/index.html.template
new file mode 100644
index 00000000000..016cf6a9384
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/index.html.template
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement)
+#if($property.widgetType == "DROPDOWN")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+#elseif($property.widgetType == "COLOR")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "NUMBER")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "MONTH")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "WEEK")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TIME")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "DATE")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEXTAREA")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#elseif($property.widgetType == "TEL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "URL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#elseif($property.widgetType == "EMAIL")
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+ {{entity.${property.name}}}
+
+
+#else
+
+
+ {{ '$projectName:${tprefix}.t.$property.dataName' | t }}
+
+
+
+
+
+
+#end
+#end
+#end
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.extension.template
new file mode 100644
index 00000000000..ec6b82a3c18
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/view.js",
+ "extensionPoint": "application-windows",
+ "description": "${projectName} - Application Dialog Window"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.js.template
new file mode 100644
index 00000000000..a42c6ac8b21
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/dialog-window/view.js.template
@@ -0,0 +1,20 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}-Report-details',
+ label: '${name} Report',
+ translation: {
+ key: '$projectName:${tprefix}.defaults.reportTitle',
+ options: {
+ name: '$t($projectName:${tprefix}.t.$dataName)',
+ }
+ },
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/dialog-window/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/index.html.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/index.html.template
new file mode 100644
index 00000000000..ca42f89ab0f
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/index.html.template
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'$projectName:${tprefix}.defaults.items' | t}}
+
+
+
+
+
+
+
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+ | {{ '$projectName:${tprefix}.t.$property.dataName' | t:'$property.name' }} |
+#end
+#end
+ |
+
+
+
+
+ | {{'$projectName:${tprefix}.messages.noData' | t}} |
+
+
+#foreach ($property in $properties)
+#if(!$property.dataAutoIncrement && $property.widgetIsMajor)
+#if($property.widgetType == "DROPDOWN")
+ |
+ {{options${property.name}Value(next.${property.name})}}
+ |
+#elseif($property.widgetType == "EMAIL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "URL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "TEL")
+
+ {{next.${property.name}}}
+ |
+#elseif($property.widgetType == "COLOR")
+
+
+ |
+#elseif($property.widgetType == "WEEK")
+
+
+ |
+#elseif($property.widgetType == "MONTH")
+
+
+ |
+#elseif($property.widgetType == "TIME")
+
+
+ |
+#elseif($property.widgetType == "DATETIME-LOCAL")
+
+
+ |
+#elseif($property.widgetType == "DATE")
+
+
+ |
+#elseif($property.widgetType == "CHECKBOX")
+
+
+
+ |
+#else
+ {{next.${property.name}}} |
+#end
+#end
+#end
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.extension.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.extension.template
new file mode 100644
index 00000000000..131c0d28481
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.extension.template
@@ -0,0 +1,6 @@
+{
+ "module": "${projectName}/gen/${genFolderName}/ui/Reports/${name}/view.js",
+ "extensionPoint": "application-reports",
+ "description": "${projectName} - Application View"#if($perspectiveRole || $roleRead),
+ "role": "#if($perspectiveRole)${perspectiveRole},#end#if($roleRead)${roleRead}#end"#end
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.js.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.js.template
new file mode 100644
index 00000000000..043208bbb51
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/perspective/report/view.js.template
@@ -0,0 +1,23 @@
+/*
+ * Generated by Eclipse Dirigible based on model and template.
+ *
+ * Do not modify the content as it may be re-generated again.
+ */
+const viewData = {
+ id: '${name}Report',
+ label: '${name} Report',
+ translation: {
+ key: '$projectName:${tprefix}.defaults.reportTitle',
+ options: {
+ name: '$t($projectName:${tprefix}.t.$dataName)',
+ }
+ },
+ region: 'center',
+ lazyLoad: true,
+ autoFocusTab: false,
+ path: '/services/web/${projectName}/gen/${genFolderName}/ui/Reports/${name}/index.html',
+ perspectiveName: 'Reports'
+};
+if (typeof exports !== 'undefined') {
+ exports.getView = () => viewData;
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations-report.json.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations-report.json.template
new file mode 100644
index 00000000000..9c057188f27
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations-report.json.template
@@ -0,0 +1,30 @@
+{
+ "aria": {
+ "tableRowMenu": "Table Row Menu",
+ "tableRowMenuBtn": "Table Row Menu Button"
+ },
+ "messages": {
+ "error" : {
+ "incorrectInput": "Incorrect Input",
+ "unableToLF": "Unable to list/filter {{name}}: '{{message}}'",
+ "unableToCount": "Unable to count {{name}}: '{{message}}'"
+ },
+ "inputEnter": "Enter {{name}}...",
+ "noData": "No data available."
+ },
+ "defaults": {
+ "filter": "Filter",
+ "print": "Print",
+ "reset": "Reset",
+ "cancel": "Cancel",
+ "items": "Items",
+ "formGrpFilter": "{{name}} Filter",
+ "formHeadSelect": "{{name}} Details",
+ "reportTitle": "{{name}} Report",
+ "reportFilter": "Report Filter",
+ "viewDetails": "View Details",
+ "export": "Export"
+ },
+ "extName": "{{content}}",
+ "t": {}
+}
\ No newline at end of file
diff --git a/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations.json.template b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations.json.template
new file mode 100644
index 00000000000..68ad078f4d3
--- /dev/null
+++ b/components/template/template-application-ui-angular-java/src/main/resources/META-INF/dirigible/template-application-ui-angular-java/ui/translations.json.template
@@ -0,0 +1,68 @@
+{
+ "aria": {
+ "showHide": "show/hide {{name}} options",
+ "options": "{{name}} options",
+ "tableRowMenu": "Table Row Menu",
+ "tableRowMenuBtn": "Table Row Menu Button",
+ "headerMenu": "Header Menu",
+ "headerMenuBtn": "Header Menu Button",
+ "addEntity": "add entity",
+ "editEntity": "edit entity",
+ "deleteEntity": "delete entity"
+ },
+ "state": {
+ "busy": "Loading..."
+ },
+ "messages": {
+ "error" : {
+ "incorrectInput": "Incorrect Input",
+ "loading": "Encounterd an error while loading",
+ "unableToCreate": "Unable to create {{name}}: '{{message}}'",
+ "unableToUpdate": "Unable to update {{name}}: '{{message}}'",
+ "unableToDelete": "Unable to delete {{name}}: '{{message}}'",
+ "unableToLoad": "Unable to load data: '{{message}}'",
+ "unableToLF": "Unable to list/filter {{name}}: '{{message}}'",
+ "unableToCount": "Unable to count {{name}}: '{{message}}'",
+ "pattern": "The value doesn't match the required pattern: {{rule}}",
+ "lengthMinMax": "Value must be between {{min}} and {{max}} characters long",
+ "lengthMin": "Value must not be less than {{min}} characters",
+ "lengthMax": "Value must not be more than {{max}} characters"
+ },
+ "propertySuccessfullyCreated": "{{name}} successfully created",
+ "propertySuccessfullyUpdated": "{{name}} successfully updated",
+ "inputSearch": "Search {{name}}...",
+ "inputEnter": "Enter {{name}}...",
+ "noData": "No data available.",
+ "deleteConfirm": "Are you sure you want to delete {{name}}? This action cannot be undone.",
+ "detailSelectRecord": "Select a record to get a list of it's details."
+ },
+ "defaults": {
+ "yes": "Yes",
+ "no": "No",
+ "add": "Add",
+ "refresh": "Refresh",
+ "to": "To {{text}}",
+ "from": "From {{text}}",
+ "filter": "Filter",
+ "print": "Print",
+ "reset": "Reset",
+ "create": "Create",
+ "edit": "Edit",
+ "update": "Update",
+ "delete": "Delete",
+ "cancel": "Cancel",
+ "items": "Items",
+ "description": "Description",
+ "formGrpFilter": "{{name}} Filter",
+ "formHeadSelect": "{{name}} Details",
+ "formHeadCreate": "Create {{name}}",
+ "formHeadUpdate": "Update {{name}}",
+ "reportTitle": "{{name}} Report",
+ "reportFilter": "Report Filter",
+ "viewDetails": "View Details",
+ "deleteTitle": "Delete {{name}}?",
+ "loadMore": "Load More..."
+ },
+ "extName": "{{content}}",
+ "t": {}
+}
\ No newline at end of file
diff --git a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js
index 0f8089765da..a561b90e2ab 100644
--- a/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js
+++ b/components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js
@@ -14,6 +14,8 @@ import { Base64 } from "@aerokit/sdk/utils";
import { Bytes } from "@aerokit/sdk/io";
export function process(model, parameters) {
+ parameters.javaGenFolderName = sanitizeJavaIdentifier(parameters.genFolderName);
+
model.entities.forEach(e => {
if (parameters.dataSource && !e.dataSource) {
e.dataSource = parameters.dataSource;
@@ -22,6 +24,7 @@ export function process(model, parameters) {
e.dataSource = defaultDataSourceName;
parameters.dataSource = defaultDataSourceName;
}
+ e.javaPerspectiveName = sanitizeJavaIdentifier(e.perspectiveName);
let tablePrefix = parameters.tablePrefix ? parameters.tablePrefix : '';
if (tablePrefix !== '' && !tablePrefix.endsWith("_")) {
tablePrefix = `${tablePrefix}_`;
@@ -74,6 +77,7 @@ export function process(model, parameters) {
const parsedDataType = parseDataTypes(p.dataType);
p.dataTypeJava = parsedDataType.java;
p.dataTypeTypescript = parsedDataType.ts;
+ p.dataTypeJavaClass = resolveJavaClass(parsedDataType.javaClass, p.auditType);
if (p.dataPrimaryKey) {
if (e.primaryKeys === undefined) {
@@ -132,27 +136,27 @@ export function process(model, parameters) {
})
if (p.widgetType == "DROPDOWN") {
- let projectNameString = `/services/ts/${parameters.projectName}/gen/${parameters.genFolderName}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Service.ts`;
- let projectNameControllerString = `/services/ts/${parameters.projectName}/gen/${parameters.genFolderName}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Controller.ts`;
-
e.hasDropdowns = true;
+ let targetProject = parameters.projectName;
+ let targetGenFolder = parameters.genFolderName;
if (e.referencedProjections.length !== 0) {
- let foundReferenceProjection = false;
- e.referencedProjections.forEach(referencedProjection => {
- if (referencedProjection.name === p.relationshipEntityName && !foundReferenceProjection) {
- p.widgetDropdownUrl = `/services/ts/${referencedProjection.project}/gen/${referencedProjection.genFolderName}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Service.ts`;
- p.widgetDropdownControllerUrl = `/services/ts/${referencedProjection.project}/gen/${referencedProjection.genFolderName}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Controller.ts`;
- foundReferenceProjection = true;
- }
- });
- if (!foundReferenceProjection) {
- p.widgetDropdownUrl = projectNameString;
- p.widgetDropdownControllerUrl = projectNameControllerString;
+ const referencedProjection = e.referencedProjections.find(rp => rp.name === p.relationshipEntityName);
+ if (referencedProjection) {
+ targetProject = referencedProjection.project;
+ targetGenFolder = referencedProjection.genFolderName;
}
+ }
+
+ if (parameters.javaRuntime) {
+ const javaGen = sanitizeJavaIdentifier(targetGenFolder);
+ const javaPerspective = sanitizeJavaIdentifier(p.relationshipEntityPerspectiveName);
+ const javaUrl = `/services/java/${targetProject}/gen/${javaGen}/api/${javaPerspective}/${p.relationshipEntityName}Controller`;
+ p.widgetDropdownUrl = javaUrl;
+ p.widgetDropdownControllerUrl = javaUrl;
} else {
- p.widgetDropdownUrl = projectNameString;
- p.widgetDropdownControllerUrl = projectNameControllerString;
+ p.widgetDropdownUrl = `/services/ts/${targetProject}/gen/${targetGenFolder}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Service.ts`;
+ p.widgetDropdownControllerUrl = `/services/ts/${targetProject}/gen/${targetGenFolder}/api/${p.relationshipEntityPerspectiveName}/${p.relationshipEntityName}Controller.ts`;
}
}
});
@@ -222,7 +226,8 @@ export function getUniqueParameters(...parameters) {
export function parseDataTypes(dataType) {
const parsedDataType = {
java: '',
- ts: ''
+ ts: '',
+ javaClass: 'Object'
};
switch (dataType.toUpperCase()) {
case "TINYINT":
@@ -232,6 +237,7 @@ export function parseDataTypes(dataType) {
case "SMALLSERIAL":
parsedDataType.java = "short";
parsedDataType.ts = "number";
+ parsedDataType.javaClass = "Short";
break;
case "MEDIUMINT":
case "INT3":
@@ -241,27 +247,35 @@ export function parseDataTypes(dataType) {
case "SERIAL":
parsedDataType.java = "int";
parsedDataType.ts = "number";
+ parsedDataType.javaClass = "Integer";
break;
case "BIGINT":
case "INT8":
case "BIGSERIAL":
parsedDataType.java = "long";
parsedDataType.ts = "number";
+ parsedDataType.javaClass = "Long";
break;
case "DECIMAL":
case "DEC":
case "NUMERIC":
case "FIXED":
+ parsedDataType.java = "double";
+ parsedDataType.ts = "number";
+ parsedDataType.javaClass = "java.math.BigDecimal";
+ break;
case "DOUBLE":
case "DOUBLE PRECISION":
case "REAL":
parsedDataType.java = "double";
parsedDataType.ts = "number";
+ parsedDataType.javaClass = "Double";
break;
case "FLOAT":
case "MONEY":
parsedDataType.java = "float";
parsedDataType.ts = "number";
+ parsedDataType.javaClass = "Float";
break;
case "CHAR":
case "ENUM":
@@ -276,36 +290,78 @@ export function parseDataTypes(dataType) {
case "CHARACTER VARYING":
case "CHARACTER":
case "BPCHAR":
+ case "CLOB":
parsedDataType.java = "string";
parsedDataType.ts = "string";
+ parsedDataType.javaClass = "String";
break;
case "DATE":
parsedDataType.java = "date";
parsedDataType.ts = "Date";
+ parsedDataType.javaClass = "java.time.LocalDate";
break;
case "TIME":
case "TIME WITH TIME ZONE":
parsedDataType.java = "time";
parsedDataType.ts = "string";
+ parsedDataType.javaClass = "java.time.LocalTime";
break;
case "DATETIME":
case "TIMESTAMP":
case "TIMESTAMP WITH TIME ZONE":
parsedDataType.java = "timestamp";
parsedDataType.ts = "Date";
+ parsedDataType.javaClass = "java.time.Instant";
break;
case "BOOLEAN":
case "BIT":
parsedDataType.java = "boolean";
parsedDataType.ts = "boolean";
+ parsedDataType.javaClass = "Boolean";
+ break;
+ case "BLOB":
+ parsedDataType.java = "blob";
+ parsedDataType.ts = "string";
+ parsedDataType.javaClass = "byte[]";
break;
case "NULL":
parsedDataType.java = "null";
parsedDataType.ts = "null";
+ parsedDataType.javaClass = "Object";
break;
default:
parsedDataType.ts = "unknown";
+ parsedDataType.javaClass = "Object";
}
return parsedDataType;
}
+
+/**
+ * Resolves the Java class name to emit for a property, applying the audit-field overrides demanded
+ * by org.eclipse.dirigible.engine.java.annotations.{CreatedAt,UpdatedAt,CreatedBy,UpdatedBy}.
+ */
+export function resolveJavaClass(baseJavaClass, auditType) {
+ if (auditType === "CREATED_AT" || auditType === "UPDATED_AT") {
+ return "java.time.Instant";
+ }
+ if (auditType === "CREATED_BY" || auditType === "UPDATED_BY") {
+ return "String";
+ }
+ return baseJavaClass || "Object";
+}
+
+/**
+ * Sanitises an arbitrary name (perspective, model folder) into a lower-case Java identifier safe
+ * for use both as a path segment and a package fragment.
+ */
+export function sanitizeJavaIdentifier(name) {
+ if (name === undefined || name === null || name === "") {
+ return "_";
+ }
+ let s = String(name).toLowerCase().replace(/[^a-z0-9_]/g, '_');
+ if (/^[0-9]/.test(s)) {
+ s = '_' + s;
+ }
+ return s === "" ? "_" : s;
+}
diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/CsvimIdentityRestartIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/CsvimIdentityRestartIT.java
new file mode 100644
index 00000000000..6775ad1e8e9
--- /dev/null
+++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/CsvimIdentityRestartIT.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2010-2026 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.integration.tests.api;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.eclipse.dirigible.components.data.sources.manager.DataSourcesManager;
+import org.eclipse.dirigible.components.initializers.synchronizer.SynchronizationProcessor;
+import org.eclipse.dirigible.repository.api.IRepository;
+import org.eclipse.dirigible.repository.api.IRepositoryStructure;
+import org.eclipse.dirigible.tests.base.IntegrationTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Verifies that {@code CsvimProcessor} advances a table's IDENTITY counter past the seeded explicit
+ * IDs. The seed CSV inserts rows with PKs 1..5; without the post-load restart, the IDENTITY counter
+ * would still point at 1 and the next "generate-me-one" INSERT would collide on PK = 1.
+ *
+ *
+ * Pure HTTP / JDBC — no Selenide, no IDE.
+ */
+class CsvimIdentityRestartIT extends IntegrationTest {
+
+ private static final String PROJECT = "csvim-identity-restart-it";
+
+ private static final String TABLE_PATH = IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/tables/book.table";
+ private static final String CSV_PATH = IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/data/books.csv";
+ private static final String CSVIM_PATH = IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/data/books.csvim";
+
+ private static final String TABLE_NAME = "CSVIM_IDENT_BOOK";
+
+ private static final String TABLE_SOURCE = """
+ {
+ "name": "CSVIM_IDENT_BOOK",
+ "type": "TABLE",
+ "columns": [
+ {
+ "type": "INTEGER",
+ "primaryKey": true,
+ "identity": true,
+ "nullable": false,
+ "name": "BOOK_ID"
+ },
+ {
+ "type": "VARCHAR",
+ "length": 40,
+ "nullable": false,
+ "name": "BOOK_TITLE"
+ }
+ ]
+ }
+ """;
+
+ private static final String CSV_SOURCE = """
+ BOOK_ID,BOOK_TITLE
+ 1,Dune
+ 2,Foundation
+ 3,Neuromancer
+ 4,Hyperion
+ 5,Snow Crash
+ """;
+
+ private static final String CSVIM_SOURCE = """
+ {
+ "files": [
+ {
+ "table": "CSVIM_IDENT_BOOK",
+ "schema": "PUBLIC",
+ "file": "/%s/data/books.csv",
+ "header": true,
+ "useHeaderNames": true,
+ "delimField": ",",
+ "distinguishEmptyFromNull": true,
+ "version": "1.0"
+ }
+ ]
+ }
+ """.formatted(PROJECT);
+
+ @Autowired
+ private IRepository repository;
+
+ @Autowired
+ private SynchronizationProcessor synchronizationProcessor;
+
+ @Autowired
+ private DataSourcesManager dataSourcesManager;
+
+ @Test
+ void identity_counter_is_advanced_past_explicitly_seeded_ids() throws Exception {
+ write(TABLE_PATH, TABLE_SOURCE);
+ write(CSV_PATH, CSV_SOURCE);
+ write(CSVIM_PATH, CSVIM_SOURCE);
+ synchronizationProcessor.forceProcessSynchronizers();
+
+ DataSource dataSource = dataSourcesManager.getDefaultDataSource();
+ try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
+
+ // Sanity: the 5 seeded rows are in place — MAX(BOOK_ID) == 5.
+ try (ResultSet rs = statement.executeQuery("SELECT MAX(\"BOOK_ID\") FROM \"" + TABLE_NAME + "\"")) {
+ assertThat(rs.next()).isTrue();
+ assertThat(rs.getInt(1)).isEqualTo(5);
+ }
+
+ // The actual contract: an INSERT that omits BOOK_ID must get id = 6 (not 1).
+ // If the IDENTITY counter wasn't advanced, this throws a PK-violation.
+ statement.execute("INSERT INTO \"" + TABLE_NAME + "\" (\"BOOK_TITLE\") VALUES ('Cryptonomicon')",
+ Statement.RETURN_GENERATED_KEYS);
+ try (ResultSet keys = statement.getGeneratedKeys()) {
+ assertThat(keys.next()).isTrue();
+ assertThat(keys.getInt(1)).isEqualTo(6);
+ }
+
+ // And MAX(BOOK_ID) now equals 6, confirming the row landed.
+ try (ResultSet rs = statement.executeQuery("SELECT MAX(\"BOOK_ID\") FROM \"" + TABLE_NAME + "\"")) {
+ assertThat(rs.next()).isTrue();
+ assertThat(rs.getInt(1)).isEqualTo(6);
+ }
+ }
+ }
+
+ private void write(String path, String source) {
+ repository.createResource(path, source.getBytes(StandardCharsets.UTF_8), false, "text/plain", true);
+ }
+
+ @AfterEach
+ void cleanup() throws Exception {
+ for (String path : List.of(CSVIM_PATH, CSV_PATH, TABLE_PATH)) {
+ if (repository.hasResource(path)) {
+ repository.removeResource(path);
+ }
+ }
+ synchronizationProcessor.forceProcessSynchronizers();
+ // Drop the table so reruns start from a clean state — the synchronizer's "keep data" policy
+ // means the table itself survives a .table file removal otherwise.
+ try (Connection connection = dataSourcesManager.getDefaultDataSource()
+ .getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute("DROP TABLE IF EXISTS \"" + TABLE_NAME + "\"");
+ }
+ }
+}
diff --git a/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/JavaTemplateIT.java b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/JavaTemplateIT.java
new file mode 100644
index 00000000000..b080a52ceb3
--- /dev/null
+++ b/tests/tests-integrations/src/main/java/org/eclipse/dirigible/integration/tests/api/JavaTemplateIT.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2010-2026 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.integration.tests.api;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.containsString;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.dirigible.components.initializers.synchronizer.SynchronizationProcessor;
+import org.eclipse.dirigible.repository.api.IRepository;
+import org.eclipse.dirigible.repository.api.IRepositoryStructure;
+import org.eclipse.dirigible.tests.base.IntegrationTest;
+import org.eclipse.dirigible.tests.framework.restassured.RestAssuredExecutor;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import io.restassured.http.ContentType;
+
+/**
+ * Validates the runtime contract produced by {@code template-application-dao-java} +
+ * {@code template-application-rest-java}: drops {@code .java} sources matching the templates'
+ * output (entity + repository + controller) into the registry, triggers synchronization, and
+ * exercises the generated CRUD endpoints through HTTP.
+ *
+ *
+ * The generator step itself runs in JavaScript inside the IDE's template engine and is not invoked
+ * from this test — that would require the IDE / Selenide. Instead we assert that the *shape of
+ * code* the templates emit compiles cleanly under {@code engine-java} and reaches the
+ * {@code /services/java/...} dispatch path the same way {@code JavaEngineIT} does.
+ */
+class JavaTemplateIT extends IntegrationTest {
+
+ private static final String PROJECT = "java-template-it";
+
+ private static final String TABLE_PATH =
+ IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/gen/sample/data/books/Book.table";
+ private static final String ENTITY_PATH =
+ IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/gen/sample/data/books/BookEntity.java";
+ private static final String REPOSITORY_PATH =
+ IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/gen/sample/data/books/BookRepository.java";
+ private static final String CONTROLLER_PATH =
+ IRepositoryStructure.PATH_REGISTRY_PUBLIC + "/" + PROJECT + "/gen/sample/api/books/BookController.java";
+
+ private static final String CONTROLLER_BASE = "/services/java/" + PROJECT + "/gen/sample/api/books/BookController";
+
+ private static final long ASSERTION_TIMEOUT_SECONDS = 30;
+
+ private static final String TABLE_SOURCE = """
+ {
+ "name": "JAVATEMPLATEIT_BOOK",
+ "type": "TABLE",
+ "columns": [
+ {
+ "type": "INTEGER",
+ "primaryKey": true,
+ "identity": true,
+ "nullable": false,
+ "name": "BOOK_ID"
+ },
+ {
+ "type": "VARCHAR",
+ "length": 40,
+ "nullable": false,
+ "name": "BOOK_TITLE"
+ }
+ ]
+ }
+ """;
+
+ private static final String ENTITY_SOURCE = """
+ package gen.sample.data.books;
+
+ import org.eclipse.dirigible.engine.java.annotations.Column;
+ import org.eclipse.dirigible.engine.java.annotations.Documentation;
+ import org.eclipse.dirigible.engine.java.annotations.Entity;
+ import org.eclipse.dirigible.engine.java.annotations.GeneratedValue;
+ import org.eclipse.dirigible.engine.java.annotations.GenerationType;
+ import org.eclipse.dirigible.engine.java.annotations.Id;
+ import org.eclipse.dirigible.engine.java.annotations.Table;
+
+ @Entity
+ @Table(name = "JAVATEMPLATEIT_BOOK")
+ @Documentation("Book entity mapping")
+ public class BookEntity {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "BOOK_ID")
+ @Documentation("Id")
+ public Integer id;
+
+ @Column(name = "BOOK_TITLE", length = 40, nullable = false)
+ @Documentation("Title")
+ public String title;
+ }
+ """;
+
+ private static final String REPOSITORY_SOURCE = """
+ package gen.sample.data.books;
+
+ import org.eclipse.dirigible.components.data.store.java.repository.JavaRepository;
+ import org.eclipse.dirigible.engine.java.annotations.Repository;
+
+ @Repository
+ public class BookRepository extends JavaRepository {
+
+ public BookRepository() {
+ super(BookEntity.class);
+ }
+ }
+ """;
+
+ private static final String CONTROLLER_SOURCE = """
+ package gen.sample.api.books;
+
+ import gen.sample.data.books.BookEntity;
+ import gen.sample.data.books.BookRepository;
+
+ import org.eclipse.dirigible.engine.java.annotations.Documentation;
+ import org.eclipse.dirigible.engine.java.annotations.Inject;
+ import org.eclipse.dirigible.engine.java.annotations.http.Body;
+ import org.eclipse.dirigible.engine.java.annotations.http.Controller;
+ import org.eclipse.dirigible.engine.java.annotations.http.Delete;
+ import org.eclipse.dirigible.engine.java.annotations.http.Get;
+ import org.eclipse.dirigible.engine.java.annotations.http.PathParam;
+ import org.eclipse.dirigible.engine.java.annotations.http.Post;
+ import org.eclipse.dirigible.engine.java.annotations.http.Put;
+ import org.eclipse.dirigible.engine.java.annotations.http.QueryParam;
+ import org.springframework.http.HttpStatus;
+ import org.springframework.web.server.ResponseStatusException;
+
+ import java.util.Collection;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Locale;
+ import java.util.Map;
+ import java.util.Set;
+
+ @Controller
+ @Documentation("java-template-it - Book Controller")
+ public class BookController {
+
+ private static final Set FILTER_FIELDS = Set.of("id", "title");
+
+ @Inject
+ private BookRepository repository;
+
+ @Get
+ @Documentation("List Book")
+ public List getAll(@QueryParam("$limit") Integer limit,
+ @QueryParam("$offset") Integer offset) {
+ int actualLimit = limit != null ? limit.intValue() : 20;
+ int actualOffset = offset != null ? offset.intValue() : 0;
+ return repository.findAll(actualLimit, actualOffset);
+ }
+
+ @Get("/count")
+ @Documentation("Count Book")
+ public Map count() {
+ return Map.of("count", repository.count());
+ }
+
+ @Post("/count")
+ @Documentation("Count Book with filter")
+ public Map countWithFilter(@Body Map filter) {
+ return Map.of("count", (long) runFilter(filter).size());
+ }
+
+ @Post("/search")
+ @Documentation("Search Book")
+ public List search(@Body Map filter) {
+ return runFilter(filter);
+ }
+
+ @Get("/{id}")
+ @Documentation("Get Book by id")
+ public BookEntity getById(@PathParam("id") Integer id) {
+ return repository.findOne(id)
+ .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found"));
+ }
+
+ @Post
+ @Documentation("Create Book")
+ public BookEntity create(@Body BookEntity entity) {
+ return repository.save(entity);
+ }
+
+ @Put("/{id}")
+ @Documentation("Update Book by id")
+ public BookEntity update(@PathParam("id") Integer id, @Body BookEntity entity) {
+ entity.id = id;
+ return repository.update(entity);
+ }
+
+ @Delete("/{id}")
+ @Documentation("Delete Book by id")
+ public void deleteById(@PathParam("id") Integer id) {
+ if (repository.findOne(id).isEmpty()) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found");
+ }
+ repository.deleteById(id);
+ }
+
+ private List runFilter(Map filter) {
+ StringBuilder hql = new StringBuilder("from BookEntity e");
+ Map params = new LinkedHashMap<>();
+ boolean first = true;
+ if (filter != null && filter.get("equals") instanceof Map, ?> equals) {
+ for (Map.Entry, ?> entry : equals.entrySet()) {
+ String field = requireKnownField(String.valueOf(entry.getKey()));
+ String paramName = "p" + params.size();
+ hql.append(first ? " where e." : " and e.").append(field).append(" = :").append(paramName);
+ params.put(paramName, entry.getValue());
+ first = false;
+ }
+ }
+ if (filter != null && filter.get("conditions") instanceof List> conditions) {
+ for (Object raw : conditions) {
+ if (!(raw instanceof Map, ?> condition)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid filter condition");
+ }
+ String field = requireKnownField(String.valueOf(condition.get("propertyName")));
+ String operator = String.valueOf(condition.get("operator")).toUpperCase(Locale.ROOT);
+ Object value = condition.get("value");
+ String paramName = "p" + params.size();
+ String clause = switch (operator) {
+ case "EQ" -> "e." + field + " = :" + paramName;
+ case "IN" -> {
+ if (!(value instanceof Collection>)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
+ "IN value must be a list for field: " + field);
+ }
+ yield "e." + field + " in (:" + paramName + ")";
+ }
+ case "LIKE" -> "e." + field + " like :" + paramName;
+ default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported operator: " + operator);
+ };
+ hql.append(first ? " where " : " and ").append(clause);
+ params.put(paramName, value);
+ first = false;
+ }
+ }
+ return repository.query(hql.toString(), params);
+ }
+
+ private static String requireKnownField(String field) {
+ if (!FILTER_FIELDS.contains(field)) {
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unknown filter field: " + field);
+ }
+ return field;
+ }
+ }
+ """;
+
+ @Autowired
+ private IRepository repository;
+
+ @Autowired
+ private SynchronizationProcessor synchronizationProcessor;
+
+ @Autowired
+ private RestAssuredExecutor restAssuredExecutor;
+
+ @Test
+ void generated_dao_and_controller_serve_crud_over_http() {
+ writeAll();
+
+ // List on a fresh table returns 200 + a JSON array (may be empty or contain prior runs' data
+ // — assert only on the status code here).
+ restAssuredExecutor.execute(() -> given().when()
+ .get(CONTROLLER_BASE)
+ .then()
+ .statusCode(200),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST persists a row through the @Inject-resolved repository. The retry-on-AssertionError
+ // executor doesn't return a value, so capture the IDENTITY-assigned id via AtomicReference.
+ AtomicReference created = new AtomicReference<>();
+ restAssuredExecutor.execute(() -> created.set(given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"title\":\"Dune\"}")
+ .post(CONTROLLER_BASE)
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune"))
+ .extract()
+ .path("id")),
+ ASSERTION_TIMEOUT_SECONDS);
+ Integer createdId = created.get();
+
+ // GET /{id} resolves @PathParam binding.
+ restAssuredExecutor.execute(() -> given().when()
+ .get(CONTROLLER_BASE + "/" + createdId)
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // PUT updates an existing row.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"title\":\"Dune Messiah\"}")
+ .put(CONTROLLER_BASE + "/" + createdId)
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune Messiah")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // GET /count is non-negative and includes the row we just created.
+ restAssuredExecutor.execute(() -> given().when()
+ .get(CONTROLLER_BASE + "/count")
+ .then()
+ .statusCode(200)
+ .body(containsString("count")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /search with a known field returns the matching row.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"equals\":{\"title\":\"Dune Messiah\"}}")
+ .post(CONTROLLER_BASE + "/search")
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune Messiah")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /count with the same filter returns at least 1.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"equals\":{\"title\":\"Dune Messiah\"}}")
+ .post(CONTROLLER_BASE + "/count")
+ .then()
+ .statusCode(200)
+ .body(containsString("count")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /search with an unknown field is rejected by the SQL-injection allow-list.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"equals\":{\"nope\":\"x\"}}")
+ .post(CONTROLLER_BASE + "/search")
+ .then()
+ .statusCode(400),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /search with the conditions shape the dropdown widget sends (IN with a list).
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"conditions\":[{\"propertyName\":\"title\",\"operator\":\"IN\",\"value\":[\"Dune Messiah\",\"Foundation\"]}]}")
+ .post(CONTROLLER_BASE + "/search")
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune Messiah")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /search with a LIKE condition matches the row by pattern.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"conditions\":[{\"propertyName\":\"title\",\"operator\":\"LIKE\",\"value\":\"Dune%\"}]}")
+ .post(CONTROLLER_BASE + "/search")
+ .then()
+ .statusCode(200)
+ .body(containsString("Dune Messiah")),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // POST /search with an unsupported operator is rejected.
+ restAssuredExecutor.execute(() -> given().when()
+ .contentType(ContentType.JSON)
+ .body("{\"conditions\":[{\"propertyName\":\"title\",\"operator\":\"REGEX\",\"value\":\".*\"}]}")
+ .post(CONTROLLER_BASE + "/search")
+ .then()
+ .statusCode(400),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // DELETE removes the row; subsequent GET returns 404.
+ restAssuredExecutor.execute(() -> given().when()
+ .delete(CONTROLLER_BASE + "/" + createdId)
+ .then()
+ .statusCode(200),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ restAssuredExecutor.execute(() -> given().when()
+ .get(CONTROLLER_BASE + "/" + createdId)
+ .then()
+ .statusCode(404),
+ ASSERTION_TIMEOUT_SECONDS);
+
+ // The OpenAPI aggregator publishes one fragment per registered controller.
+ restAssuredExecutor.execute(() -> given().when()
+ .get("/services/openapi")
+ .then()
+ .statusCode(200)
+ .body(containsString(CONTROLLER_BASE)),
+ ASSERTION_TIMEOUT_SECONDS);
+ }
+
+ private void writeAll() {
+ // .table artefact first so TableSynchronizer materialises JAVATEMPLATEIT_BOOK
+ // (with IDENTITY on BOOK_ID) before the Java entity is registered against it.
+ write(TABLE_PATH, TABLE_SOURCE, "application/json");
+ write(ENTITY_PATH, ENTITY_SOURCE, "text/x-java");
+ write(REPOSITORY_PATH, REPOSITORY_SOURCE, "text/x-java");
+ write(CONTROLLER_PATH, CONTROLLER_SOURCE, "text/x-java");
+ synchronizationProcessor.forceProcessSynchronizers();
+ }
+
+ private void write(String path, String source, String contentType) {
+ repository.createResource(path, source.getBytes(StandardCharsets.UTF_8), false, contentType, true);
+ }
+
+ @AfterEach
+ void cleanup() {
+ for (String path : List.of(TABLE_PATH, ENTITY_PATH, REPOSITORY_PATH, CONTROLLER_PATH)) {
+ if (repository.hasResource(path)) {
+ repository.removeResource(path);
+ }
+ }
+ synchronizationProcessor.forceProcessSynchronizers();
+ }
+}