diff --git a/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/data/transferRelationDeclaration.eol b/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/data/transferRelationDeclaration.eol index a2a1ac86..ea9da432 100644 --- a/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/data/transferRelationDeclaration.eol +++ b/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/data/transferRelationDeclaration.eol @@ -13,11 +13,6 @@ operation JSL!TransferRelationDeclaration isEager(): Boolean { return jslUtils.isEager(self); } -@cached -operation JSL!TransferRelationDeclaration getDetail(): JSL!DetailModifier { - return self.modifiers.selectOne(m | m.isTypeOf(JSL!DetailModifier)); -} - @cached operation JSL!TransferRelationDeclaration isAggregation(): Boolean { return jslUtils.isAggregation(self); @@ -87,7 +82,7 @@ operation JSL!TransferRelationDeclaration isChoiceDefined(): Boolean { operation JSL!TransferRelationDeclaration isAddReferenceAllowed(): Boolean { var lower = self.isRequired() and not self.isMany ? 1 : 0; var upper = self.isMany ? -1 : 1; - return self.maps() and self.eContainer.isUpdateSupported() + return self.maps() and self.getTransferContainer().isUpdateSupported() and upper != 1 and (lower < upper or upper == -1); } @@ -95,18 +90,18 @@ operation JSL!TransferRelationDeclaration isAddReferenceAllowed(): Boolean { operation JSL!TransferRelationDeclaration isRemoveReferenceAllowed(): Boolean { var lower = self.isRequired() and not self.isMany ? 1 : 0; var upper = self.isMany ? -1 : 1; - return self.maps() and self.eContainer.isUpdateSupported() + return self.maps() and self.getTransferContainer().isUpdateSupported() and upper != 1 and (lower < upper or upper == -1); } @cached operation JSL!TransferRelationDeclaration isSetReferenceAllowed(): Boolean { - return self.maps() and self.eContainer.isUpdateSupported() and not self.isRequired(); + return self.maps() and self.getTransferContainer().isUpdateSupported() and not self.isRequired(); } @cached operation JSL!TransferRelationDeclaration isUnsetReferenceAllowed(): Boolean { - return self.maps() and self.eContainer.isUpdateSupported() and not self.isRequired(); + return self.maps() and self.getTransferContainer().isUpdateSupported() and not self.isRequired(); } @cached @@ -171,3 +166,8 @@ operation JSL!TransferRelationDeclaration getContainerEquivalentClassType(): UI! operation JSL!TransferRelationDeclaration uiContainer() : UI!ui::VisualElement { return self.referenceType.equivalent("TransferDeclarationVisualElement"); } + +@cached +operation JSL!TransferRelationDeclaration getTransferObjectType(): JSL!TransferDeclaration { + return self.referenceType; +} diff --git a/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/namespace/modifiable.eol b/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/namespace/modifiable.eol index e5219abe..0d757ec9 100644 --- a/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/namespace/modifiable.eol +++ b/judo-tatami-jsl-jsl2ui/src/main/epsilon/operations/jsl/namespace/modifiable.eol @@ -23,6 +23,11 @@ operation JSL!Modifiable getStretchModifier(): JSL!Modifier { return self.modifiers.selectOne(m | m.isTypeOf(JSL!StretchModifier)); } +@cached +operation JSL!Modifiable getWidth(): JSL!Modifier { + return (self.modifiers.selectOne(m | m.type == "width")); +} + @cached operation JSL!Modifiable getPrecision(): JSL!Modifier { return (self.modifiers.selectOne(m | m.type == "precision")); @@ -63,6 +68,11 @@ operation JSL!Modifiable getMimeType(): JSL!Modifier { return (self.modifiers.selectOne(m | m.type == "mime-type")); } +@cached +operation JSL!Modifiable getDetail(): JSL!DetailModifier { + return self.modifiers.selectOne(m | m.isTypeOf(JSL!DetailModifier)); +} + @cached operation JSL!Modifiable getOpposite(): JSL!Modifier { return (self.modifiers.selectOne(m | m.type == "opposite" or m.type == "opposite-add")); diff --git a/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewLinkDeclaration.etl b/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewLinkDeclaration.etl index 9fbd8485..980fbf02 100644 --- a/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewLinkDeclaration.etl +++ b/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewLinkDeclaration.etl @@ -14,3 +14,115 @@ rule ViewLinkPageDefinition log.debug("Create ViewLinkPageDefinition: " + t.name); } + +@abstract +rule AbstractViewLinkDeclaration + transform s: JSL!ViewLinkDeclaration + to t: UI!ui::Link { + guard: actorDeclaration.getAllRelations().contains(s) + + t.name = s.name; + t.relationName = s.name; + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + if (s.getIconModifier().isDefined()) { + t.icon = s.equivalent("LinkIcon"); + } + t.row = 1d; + t.col = s.width.isDefined() ? s.width.asReal() : 12d; + t.~pos = s.~pos; + t.isEager = s.isEager(); + if (t.~pos.isUndefined()) { + t.~pos = 0; + } + + if (s.isEager()) { + // TODO finish additional mas attributes + /* + for (dataFeature in s.additionalMaskFeatures) { + var attributeType = dataFeature.getMember().mapAttributeType(s.getRoot().getTransferObjectType().equivalent("ClassType")); + t.additionalMaskAttributes.add(attributeType); + } + */ + } +} + +@lazy +@greedy +rule LinkIcon + transform s: JSL!ViewLinkDeclaration + to t: UI!ui::Icon { + t.iconName = s.getIconModifier().value.value; + t.name = s.name + "Icon"; + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/LinkIcon"); +} + +rule InlineViewLink + transform s: JSL!ViewLinkDeclaration + to t: UI!ui::Link + extends AbstractViewLinkDeclaration { + guard: s.getTransferObjectType().isGenerated() + + var id = actorDeclaration.name + "/(jsl/" + s.getId() + ")/InlineViewLink"; + t.setId(id); + t.dataElement = s.equivalent("RelationType"); + + s.eContainer.uiContainer().children.add(t); + + var col = s.equivalentDiscriminated("ViewLinkDeclarationRepresentationColumn", id); + t.parts.add(col); + + // TODO transform selector row per page + t.selectorRowsPerPage = 10; + t.autoCompleteRows = 10; + + t.actionButtonGroup = s.equivalent("TabularReferenceFieldLinkButtonGroup"); + + /* + t.autocompleteRangeActionDefinition = s.equivalent("TabularReferenceFieldLinkAutocompleteRangeActionDefinition"); + t.autocompleteSetActionDefinition = s.equivalent("TabularReferenceFieldLinkAutocompleteSetActionDefinition"); + + if (relation.isRefreshSupported() and relation.relationKind == ESM!esm::structure::RelationKind#ASSOCIATION) { + t.refreshActionDefinition = s.equivalent("TabularReferenceFieldLinkRefreshActionDefinition"); + } + */ + + log.debug("InlineViewLink: " + t.name); +} + +@lazy +rule InlineViewLinkButtonGroup + transform s: JSL!ViewLinkDeclaration + to t: UI!ui::ButtonGroup { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/InlineViewLinkButtonGroup"); + t.name = s.name + "::Actions"; + t.label = "Actions"; + + // TODO add action buttons + + log.debug("InlineViewLinkButtonGroup: " + t.name); +} + +@lazy +rule ViewLinkDeclarationRepresentationColumn + transform s: JSL!ViewLinkDeclaration + to t: UI!ui::Column { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/ViewLinkDeclarationRepresentationColumn"); + + var firstStringFromTarget = s.referenceType.getAllPrimitiveFields().selectOne(f | f.referenceType.isDefined() and f.referenceType.`primitive` == "string"); + + if (firstStringFromTarget.isUndefined()) { + throw "Could not get string type field from relation target to use for representation: " + s.referenceType.name; + } + + t.attributeType = firstStringFromTarget.getTransferFieldDeclarationEquivalent(); + t.name = t.attributeType.name; + t.label = ""; + t.col = 12d; + t.row = 1d; + t.format = "%s"; + t.sort = UI!ui::Sort#ASC; + + log.debug("ViewLinkDeclarationRepresentationColumn: " + t.name); +} diff --git a/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewTableDeclaration.etl b/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewTableDeclaration.etl index 17c8a8e2..d2236f6b 100644 --- a/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewTableDeclaration.etl +++ b/judo-tatami-jsl-jsl2ui/src/main/epsilon/transformations/ui/modules/view/viewTableDeclaration.etl @@ -14,3 +14,250 @@ rule ViewTablePageDefinition log.debug("Create ViewTablePageDefinition: " + t.name); } + +@abstract +rule AbstractViewTableDeclaration + transform s: JSL!ViewTableDeclaration + to t: UI!ui::Table { + guard: actorDeclaration.getAllRelations().contains(s) + + t.name = s.name; + t.relationName = s.name; + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + if (s.getIconModifier().isDefined()) { + t.icon = s.equivalent("TableIcon"); + } + t.row = 1d; + t.col = s.width.isDefined() ? s.width.asReal() : 12d; + t.~pos = s.~pos; + if (t.~pos.isUndefined()) { + t.~pos = 0; + } +} + +@lazy +@greedy +rule TableIcon + transform s: JSL!ViewTableDeclaration + to t: UI!ui::Icon { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/TableIcon"); + t.name = s.name + "Icon"; + t.iconName = s.getIconModifier().value.value; +} + +rule InlineViewTable + transform s: JSL!ViewTableDeclaration + to t: UI!ui::Table + extends AbstractViewTableDeclaration { + guard: s.getTransferObjectType().isGenerated() + + var id = actorDeclaration.name + "/(jsl/" + s.getId() + ")/InlineViewTable"; + t.setId(id); + t.dataElement = s.equivalent("RelationType"); + + s.eContainer.uiContainer().children.add(t); + + var primitiveFields = s.referenceType.members.select(m | m.isTypeOf(JSL!RowFieldDeclaration) and m.referenceType.`primitive`.isDefined()); + var links = s.referenceType.members.select(m | m.isTypeOf(JSL!RowLinkDeclaration)); + + for (field in primitiveFields) { + var col = field.equivalentDiscriminated("TablePrimitiveColumn", id); + t.columns.add(col); + if (col.attributeType.isFilterable) { + t.filters.add(field.equivalentDiscriminated("TablePrimitiveColumnFilter", id)); + } + } + + for (link in links) { + if (link.getDetail().isUndefined()) { + var col = link.equivalentDiscriminated("RowLinkDerivedColumn", id); + t.columns.add(col); + if (col.attributeType.isFilterable) { + t.filters.add(link.equivalentDiscriminated("RowLinkDerivedColumnFilter", id)); + } + } else { + // TODO generate open action from link with detail modifier + } + + } + + t.tableActionButtonGroup = s.equivalent("InlineViewTableButtonGroup"); + t.rowActionButtonGroup = s.equivalent("InlineViewTableRowButtonGroup"); + + // TODO transform selector row per page + t.selectorRowsPerPage = 10; + + log.debug("InlineViewTable: " + t.name); +} + +@lazy +@greedy +rule TablePrimitiveColumn + transform s: JSL!TransferFieldDeclaration + to t: UI!ui::Column { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/TablePrimitiveColumn"); + t.name = s.name; + t.format = "%s"; + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + if (s.getIconModifier().isDefined()) { + t.icon = s.equivalent("ColumnIcon"); + } + t.width = s.getWidth().isDefined() ? s.getWidth().value : "120"; + + /* + if (s.customImplementation.isDefined()) { + t.customImplementation = s.customImplementation; + } + if (s.sort.isDefined()) { + t.sort = s.getUiSort(); + } + if (s.sortPrecedence.isDefined()) { + t.sortPrecedence = s.sortPrecedence; + } + if (s.formatValue.isDefined()) { + t.formatValue = s.formatValue; + } + */ + t.attributeType = s.getTransferFieldDeclarationEquivalent(); + + log.debug("TablePrimitiveColumn: " + t.name); +} + +@lazy +@greedy +rule ColumnIcon + transform s: JSL!TransferFieldDeclaration + to t: UI!ui::Icon { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/ColumnIcon"); + t.iconName = s.getIconModifier().value.value; + t.name = s.name + "Icon"; + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/ColumnIcon"); + + log.debug("ColumnIcon: " + t.name); +} + +@lazy +@greedy +rule TablePrimitiveColumnFilter + transform s: JSL!TransferFieldDeclaration + to t: UI!ui::Filter { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/TablePrimitiveColumnFilter"); + t.name = s.name + "Filter"; + t.attributeType = s.getTransferFieldDeclarationEquivalent(); + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + log.debug("TablePrimitiveColumnFilter: " + t.name); +} + +@lazy +@greedy +rule RowLinkDerivedColumn + transform s: JSL!RowLinkDeclaration + to t: UI!ui::Column { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/RowLinkDerivedColumn"); + // we keep the name of the link it self because the relation won't be present in the visual elements only in the data model + t.name = s.name; + t.format = "%s"; + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + if (s.getIconModifier().isDefined()) { + t.icon = s.equivalent("RowLinkDerivedColumnIcon"); + } + t.width = s.getWidth().isDefined() ? s.getWidth().value : "120"; + + /* + if (s.customImplementation.isDefined()) { + t.customImplementation = s.customImplementation; + } + if (s.sort.isDefined()) { + t.sort = s.getUiSort(); + } + if (s.sortPrecedence.isDefined()) { + t.sortPrecedence = s.sortPrecedence; + } + if (s.formatValue.isDefined()) { + t.formatValue = s.formatValue; + } + */ + t.attributeType = s.equivalent("RowLinkDerivedAttributeType"); + t.representsRelation = s.equivalent("RelationType"); + + log.debug("RowLinkDerivedColumn: " + t.name); +} + +@lazy +@greedy +rule RowLinkDerivedColumnIcon + transform s: JSL!RowLinkDeclaration + to t: UI!ui::Icon { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/RowLinkDerivedColumnIcon"); + t.iconName = s.getIconModifier().value.value; + t.name = s.name + "Icon"; + + log.debug("RowLinkDerivedColumnIcon: " + t.name); +} + +@lazy +@greedy +rule RowLinkDerivedColumnFilter + transform s: JSL!RowLinkDeclaration + to t: UI!ui::Filter { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/RowLinkDerivedColumnFilter"); + t.name = s.name + "Filter"; + t.attributeType = s.equivalent("RowLinkDerivedAttributeType"); + if (s.getLabelModifier().isDefined()) { + t.label = s.getLabelModifier().value.value; + } + log.debug("RowLinkDerivedColumnFilter: " + t.name); +} + +@lazy +@greedy +rule RowLinkDerivedAttributeType + transform s: JSL!RowLinkDeclaration + to t: UI!ui::data::AttributeType { + t.setId("(jsl/" + s.getId() + ")/RowLinkDerivedAttributeType"); + t.memberType = UI!ui::data::MemberType#DERIVED; + t.isReadOnly = true; + t.name = "_text_" + s.name; + t.isRequired = false; + t.isFilterable = true; + + // TODO investigate if it really does not matter which string type we pick + t.dataType = actorDeclaration.equivalent("Application").dataTypes.selectOne(d | d.isTypeOf(UI!ui::data::StringType)); + + s.getContainerEquivalentClassType().attributes.add(t); + log.debug("Created RowLinkDerivedAttributeType for ViewLinkDeclaration: [" + t.name + "] for [" + s.name + "]"); +} + +@lazy +rule InlineViewTableButtonGroup + transform s: JSL!ViewTableDeclaration + to t: UI!ui::ButtonGroup { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/InlineViewTableButtonGroup"); + t.name = s.name + "InlineViewTableButtonGroup"; + t.label = "Actions"; + + // TODO add buttons + + log.debug("InlineViewTableButtonGroup: " + t.name); +} + +@lazy +rule InlineViewTableRowButtonGroup + transform s: JSL!ViewTableDeclaration + to t: UI!ui::ButtonGroup { + t.setId(actorDeclaration.name + "/(jsl/" + s.getId() + ")/InlineViewTableRowButtonGroup"); + t.name = s.name + "InlineViewTableRowButtonGroup"; + t.label = "Actions"; + + // TODO add buttons + + log.debug("TabularReferenceTableRowButtonGroup: " + t.name); +} diff --git a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiApplicationTest.java b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiApplicationTest.java index 4a290c10..fcf77dd9 100644 --- a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiApplicationTest.java +++ b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiApplicationTest.java @@ -104,7 +104,7 @@ void testMenu() throws Exception { } row UserRow(User user) { - field String userName <= user.userName; + field String userName <= user.userName label:"Username"; } entity Product { @@ -117,8 +117,8 @@ row UserRow(User user) { } row ProductRow(Product product) { - field String name <= product.name; - field String price <= product.price.asString() + " HUF"; + field String name <= product.name label:"Name"; + field String price <= product.price.asString() + " HUF" label:"Price"; } actor MenuActor human { @@ -226,14 +226,14 @@ view ProductDetailView2(Product2 product2) { row ProductRow(Product product) { link ProductDetailView detail <= product eager detail; - field String name <= product.name; - field String price <= product.price.asString() + " HUF"; + field String name <= product.name label:"Name"; + field String price <= product.price.asString() + " HUF" label:"Price"; } row ProductRow2(Product2 product2) { link ProductDetailView2 detail2 <= product2 eager detail; - field String name2 <= product2.name2; - field String price2 <= product2.price2.asString() + " HUF"; + field String name2 <= product2.name2 label:"Name 2"; + field String price2 <= product2.price2.asString() + " HUF" label:"Price 2"; } actor Actor1 human { diff --git a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiDataTest.java b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiDataTest.java index 39074a06..88ed5de2 100644 --- a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiDataTest.java +++ b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiDataTest.java @@ -262,12 +262,12 @@ void testBasicDataCrossTransfers() throws Exception { } row Transfer1Row(Entity1 e1) { - field String string <= e1.string; - field Boolean boolean <= e1.boolean; + field String string <= e1.string label:"String"; + field Boolean boolean <= e1.boolean label:"Boolean"; } row Transfer2Row(Entity2 e2) { - field Integer integer <= e2.integer; + field Integer integer <= e2.integer label:"Integer"; } view Transfer1ListView { @@ -327,8 +327,8 @@ void testNestedFieldsAndRelations() throws Exception { } row Transfer1Row(Entity1 e1) { - field String stringOnRow <= e1.string; - field Boolean booleanOnRow <= e1.boolean; + field String stringOnRow <= e1.string label:"String On Row"; + field Boolean booleanOnRow <= e1.boolean label:"Boolean On Row"; } view Transfer1View(Entity1 e1) { @@ -485,7 +485,7 @@ view UserView(User u) { } row UnmappedRelatedRow { - field String transient; + field String transient label:"Transient"; } view MappedRelated(EntityRelated e) { @@ -496,7 +496,7 @@ view MappedRelated(EntityRelated e) { } row MappedRelatedRow(EntityRelated e) { - field String mappedAttribute <= e.hello; + field String mappedAttribute <= e.hello label:"Mapped Attribute"; event create onCreate; } diff --git a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiWidgetsTest.java b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiWidgetsTest.java index a104f692..42b44df2 100644 --- a/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiWidgetsTest.java +++ b/judo-tatami-jsl-jsl2ui/src/test/java/hu/blackbelt/judo/tatami/jsl/jsl2ui/application/JslModel2UiWidgetsTest.java @@ -2,8 +2,12 @@ import hu.blackbelt.judo.meta.jsl.runtime.JslParser; import hu.blackbelt.judo.meta.ui.*; +import hu.blackbelt.judo.meta.ui.data.AttributeType; +import hu.blackbelt.judo.meta.ui.data.ClassType; +import hu.blackbelt.judo.meta.ui.data.RelationType; import hu.blackbelt.judo.tatami.jsl.jsl2ui.AbstractTest; import lombok.extern.slf4j.Slf4j; +import org.eclipse.emf.common.util.EList; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -294,4 +298,186 @@ view UserView(User u) { assertFalse(mappedEnum.getAttributeType().isIsRequired()); assertFalse(mappedEnum.getAttributeType().isIsReadOnly()); } + + @Test + void testRelationWidgets() throws Exception { + jslModel = JslParser.getModelFromStrings("RelationWidgetsTestModel", List.of(""" + model RelationWidgetsTestModel; + + type numeric Numeric scale: 0 precision: 9; + type string String min-size: 0 max-size: 255; + + entity User { + identifier String email required; + field Numeric numeric; + + field Related related; + relation Related relatedAssociation; + relation Related[] relatedCollection; + } + + entity Related { + field String first; + field Numeric second; + } + + view UserView(User u) { + group level1 label:"Yo" icon:"text" { + group level2 width:12 frame:true icon:"unicorn" label:"Level 2" stretch:true { + link RelatedView related <= u.related eager:true icon:"related" label:"Related" width:6 create:true update:true delete:true; + link RelatedView relatedAssociation <= u.relatedAssociation icon:"related-association" label:"Related Association" width:6 choices:Related.all() selector:RelatedRow create:true update:true delete:true; + } + + tabs tabs0 orientation:horizontal width:6 { + group tab1 label:"Tab1" icon:"numbers" h-align:left { + field String email <= u.email icon:"text" label:"My Email"; + } + + group tab2 label:"Tab2" icon:"numbers" h-align:right { + table RelatedRow[] relatedCollection <= u.relatedCollection icon:"relatedCollection" label:"Related Collection" width:6 rows:25 choices:Related.all() selector:RelatedRow create:true update:true delete:true; + } + } + } + + event create onCreate; + event update onUpdate; + event delete onDelete; + } + + row RelatedRow(Related r) { + field String first <= r.first label:"First"; + field Numeric second <= r.second label:"Second"; + link RelatedView detail <= r detail:true; + link RelatedView nonDetailLink <= r text:r.first label:"Non Detail Link"; + + event create onCreate; + event update onUpdate; + event delete onDelete; + } + + view RelatedView(Related r) { + field String first <= r.first label: "First"; + field Numeric second <= r.second label: "Second"; + + event create onCreate; + event update onUpdate; + event delete onDelete; + } + + actor RelationWidgetsActor human { + link UserView user <= User.any() label:"User" icon:"tools"; + } + """)); + + transform(); + + List apps = uiModelWrapper.getStreamOfUiApplication().toList(); + + assertEquals(1, apps.size()); + + Application application = apps.get(0); + + List classTypes = application.getClassTypes(); + List links = application.getLinks(); + List tables = application.getTables(); + + // Links + + assertEquals(2 , links.size()); + + ClassType relatedViewClassType = classTypes.stream().filter(c -> c.getName().equals("RelationWidgetsTestModel::RelatedView::ClassType")).findFirst().orElseThrow(); + + Link related = links.stream().filter(l -> l.getName().equals("related")).findFirst().orElseThrow(); + RelationType relatedAttribute = (RelationType) related.getDataElement(); + + assertEquals("Related", related.getLabel()); + assertEquals("related", related.getIcon().getIconName()); + assertEquals("related", related.getRelationName()); + assertTrue(related.isIsEager()); + assertEquals("related", relatedAttribute.getName()); + assertEquals(relatedViewClassType, relatedAttribute.getTarget()); + + Link relatedAssociation = links.stream().filter(l -> l.getName().equals("relatedAssociation")).findFirst().orElseThrow(); + RelationType relatedAssociationAttribute = (RelationType) relatedAssociation.getDataElement(); + + assertEquals("Related Association", relatedAssociation.getLabel()); + assertEquals("related-association", relatedAssociation.getIcon().getIconName()); + assertEquals("relatedAssociation", relatedAssociation.getRelationName()); + assertFalse(relatedAssociation.isIsEager()); + assertEquals("relatedAssociation", relatedAssociationAttribute.getName()); + assertEquals(relatedViewClassType, relatedAssociationAttribute.getTarget()); + + // Tables + + assertEquals(1 , tables.size()); + + Table table = tables.stream().filter(t -> t.getName().equals("relatedCollection")).findFirst().orElseThrow(); + RelationType tableRelation = (RelationType) table.getDataElement(); + ClassType relatedRowClassType = classTypes.stream().filter(c -> c.getName().equals("RelationWidgetsTestModel::RelatedRow::ClassType")).findFirst().orElseThrow(); + + assertEquals("Related Collection", table.getLabel()); + assertEquals(12, table.getCol()); + assertEquals("relatedCollection", table.getRelationName()); + assertEquals("relatedCollection", tableRelation.getName()); + assertEquals(relatedRowClassType, tableRelation.getTarget()); + + // Columns + + List columns = table.getColumns(); + + assertEquals(3, columns.size()); + + Column firstColumn = columns.stream().filter(c -> c.getName().equals("first")).findFirst().orElseThrow(); + AttributeType firstAttribute = relatedRowClassType.getAttributes().stream().filter(a -> a.getName().equals("first")).findFirst().orElseThrow(); + Column secondColumn = columns.stream().filter(c -> c.getName().equals("second")).findFirst().orElseThrow(); + AttributeType secondAttribute = relatedRowClassType.getAttributes().stream().filter(a -> a.getName().equals("second")).findFirst().orElseThrow(); + Column nonDetailLinkColumn = columns.stream().filter(c -> c.getName().equals("nonDetailLink")).findFirst().orElseThrow(); + AttributeType nonDetailLinkAttribute = relatedRowClassType.getAttributes().stream().filter(a -> a.getName().equals("_text_nonDetailLink")).findFirst().orElseThrow(); + RelationType nonDetailLinkRepresentsRelation = relatedRowClassType.getRelations().stream().filter(a -> a.getName().equals("nonDetailLink")).findFirst().orElseThrow(); + + assertEquals("First", firstColumn.getLabel()); + assertEquals("%s", firstColumn.getFormat()); + assertEquals("120", firstColumn.getWidth()); + assertEquals(firstAttribute, firstColumn.getAttributeType()); + assertTrue(firstAttribute.getIsMemberTypeDerived()); + assertTrue(firstAttribute.isIsFilterable()); + assertEquals("String", firstAttribute.getDataType().getName()); + + assertEquals("Second", secondColumn.getLabel()); + assertEquals("%s", secondColumn.getFormat()); + assertEquals("120", secondColumn.getWidth()); + assertEquals(secondAttribute, secondColumn.getAttributeType()); + assertTrue(secondAttribute.getIsMemberTypeDerived()); + assertTrue(secondAttribute.isIsFilterable()); + assertEquals("Numeric", secondAttribute.getDataType().getName()); + + assertEquals("Non Detail Link", nonDetailLinkColumn.getLabel()); + assertEquals("%s", nonDetailLinkColumn.getFormat()); + assertEquals("120", nonDetailLinkColumn.getWidth()); + assertEquals(nonDetailLinkAttribute, nonDetailLinkColumn.getAttributeType()); + assertTrue(nonDetailLinkAttribute.getIsMemberTypeDerived()); + assertTrue(nonDetailLinkAttribute.isIsFilterable()); + assertEquals("String", nonDetailLinkAttribute.getDataType().getName()); + assertEquals(nonDetailLinkRepresentsRelation, nonDetailLinkColumn.getRepresentsRelation()); + assertEquals(relatedViewClassType, nonDetailLinkRepresentsRelation.getTarget()); + + // Filters + + List filters = table.getFilters(); + + assertEquals(3, filters.size()); + + Filter firstFilter = filters.stream().filter(c -> c.getName().equals("firstFilter")).findFirst().orElseThrow(); + Filter secondFilter = filters.stream().filter(c -> c.getName().equals("secondFilter")).findFirst().orElseThrow(); + Filter nonDetailLinkFilter = filters.stream().filter(c -> c.getName().equals("nonDetailLinkFilter")).findFirst().orElseThrow(); + + assertEquals("First", firstFilter.getLabel()); + assertEquals(firstAttribute, firstFilter.getAttributeType()); + + assertEquals("Second", secondFilter.getLabel()); + assertEquals(secondAttribute, secondFilter.getAttributeType()); + + assertEquals("Non Detail Link", nonDetailLinkFilter.getLabel()); + assertEquals(nonDetailLinkAttribute, nonDetailLinkFilter.getAttributeType()); + } } diff --git a/pom.xml b/pom.xml index 6221b118..b3ccec46 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 1.0.3.20240713_040616_e4d27267_develop 1.0.2.20240612_080018_05e004e9_develop 1.0.4.20240410_091349_e488ea5d_develop - 1.1.0.20240713_040611_6b4377ba_develop + 1.1.0.20240725_085552_28a1c2a8_feature_JNG_5858_support_new_relations 1.3.0.20240713_040609_6f9ce2de_develop 1.1.4.20240414_042403_a93471de_develop 1.0.5.20240414_043040_426cab39_develop