We will create a document with an editable overview of persisted entities: the user will be able to switch the entity type displayed in the grid, edit and delete any entry.
First, prepare the persisted data that will be displayed in the document:
def Author heller := new Author(firstName -> "Joseph", surname -> "Heller");
def Author vonnegut := new Author(firstName -> "Kurt", surname -> "Vonnegut");
def Author kerouac := new Author(firstName -> "Jack", surname -> "Kerouac");
def Publisher sas := new Publisher(name -> "Simon & Schuster");
def Publisher p := new Publisher(name -> "Putnam");
def Publisher dp := new Publisher(name -> "Delacorte Press");
def Publisher vp := new Publisher(name -> "Viking Press");
 
new Book(year -> 1961, title -> "Catch 22", authors -> {heller}, publisher -> sas);
new Book(year -> 1988, title -> "Picture This", authors -> {heller}, publisher -> p);
new Book(year -> 1973, title -> "Breakfast of Champions", authors -> {vonnegut}, publisher -> dp);
new Book(year -> 1969, title -> "Slaughterhouse-Five", authors -> {vonnegut}, publisher -> dp);
new Book(year -> 1957, title -> "On the Road", authors -> {kerouac}, publisher -> vp);
Run the module.
The quickest way to test your models is to do the development testing on the PDS Embedded Server: click to start it and connect PDS to the server, and then right-click the module and go to Run As > Model to upload the module and create its model instance.
You will create a Grid over the shared Records that will display values of the Record fields:
EntityGrid
Author
c.setFiltrable(true)
Author.id
, Author.firstName
, and Author.surname
property paths. Also select the Editable flag on each column.If you have not done so yet, run PDS Embedded Server by clicking , right-click the form and go to Run As > Form Preview: this will open a preview of the form in your browser.
The Grid is static and it will always display only Authors. However, the user should be allowed to change the type of entity displayed in the grid. When they select an entity type, that is, author, book, or publisher, the content of the Grid need to be updated. And not only the content: the grid columns have to be update so that the columns with the correct data are displayed. To achieve this, you will use the dynamic features of the forms.
currentEntity
of type Type<Record>
that will hold the entity the user selects.currentEntity
.Run preview of the form: right-click the form and go to Run As > Form Preview. The preview will fail with a runtime exception since the currentEntity
variable is null
.
The first solution that comes to mind is to initialize the variable from a component higher in the hierarchy, in our case, the vertical layout component, during form initialization: However, this will result in the same exception because these expressions are executed after the form tree is initialized. You can check this in the form expression (right-click into the form and select Display Widget Expression): You need to initialize the variable sooner. You can do so in the form constructor:
public EntityOverview(){
currentEntity := type(Author);
}
currentEntity
record. Do this dynamically; do not insert individual columns into the form (each entity has requires its own columns): def List<Property> properties := currentEntity.getProperties();
foreach Property p in properties do
//exclude properties with Records or Collections:
if !(
p.getPropertyType().isSubtypeOf(type(Record)) ||
p.getPropertyType().isSubtypeOf(type(Collection<Object>))
)
then
c.addColumn(new PropertyPathValueProvider(p), null,
//Boolean sortable:
null,
// Boolean editable:
true,
// Editor editor:
null);
end
end
options
of type Map<Object, String>
and initialize it from the constructor: EntityOverview {
public entityOverview(){
currentEntity := type(Author);
//added initialization of options:
options := [Author -> "Author", Book -> "Book", Publisher -> "Publisher"];
}
}
¤tEntity
options
c.setOnChangeListener({ e ->
//remove all columns:
foreach forms::GridColumn c in EntityGrid.getColumns() do
c.remove()
end;
//update the type data source of the grid:
EntityGrid.setDataSource(new forms::TypeDataSource(currentEntity));
//get list of properties of the entity record:
def List<Property> properties := currentEntity.getProperties();
//create colums for properties in the grid:
foreach Property p in properties do
if !(p.getPropertyType().isSubtypeOf(type(Record)) or
p.getPropertyType().isSubtypeOf(type(Collection<Object>)))
then
EntityGrid.addColumn(new PropertyPathValueProvider(p));
end
end;
} );
c.setNullSelectionAllowed(false);
Since a part of the code runs when the user selects an option in the Single Select List and a part of the code that loads the Grid when initialized are identical, extract the code to a method. The concept is quite generic so you can define it as an extension method of the grid:
@ExtensionMethod
public void setEntityColumns(Grid g, Type<Record> currentEntity) {
g.setDataSource(new forms::TypeDataSource(currentEntity));
def List<Property> properties := currentEntity.getProperties();
properties.compact().collect(
{ p ->
if !(p.getPropertyType().isSubtypeOf(type(Record)) or
p.getPropertyType().isSubtypeOf(type(Collection<Object>)))
then
g.addColumn(new PropertyPathValueProvider(p), null,
//Boolean sortable:
false,
// Boolean editable:
true,
// Editor editor:
null
)
end
}
);
}
Adapt the assembly of columns on the grid and on the single-select list to EntityGrid.setEntityColumns(currentEntity)
.
g.addButtonColumn("Delete", { e:Record -> deleteRecords({e})});
to the end. If you have not defined the method, add the call to the code that creates the columns in the Single-Select component and to the Init code of the Grid.There is one more issue to take care of and that is the headers of columns. Normally, to display a name of a Record or its property, you set the caption expression on each component with the setHeader()
call. As this can be pretty tedious, you can use labels to pass the caption expression. Labels are defined on records and fields and intended to hold their user-friendly name:
setHeader(core::getLabel(p))
call to the generated columns. If you have not, you will need to add it to the code that creates the columns in the Single-Select component and to the Init code of the Grid. @ExtensionMethod
public void setEntityColumns(Grid g, Type<Record> currentEntity) {
g.setDataSource(new forms::TypeDataSource(currentEntity));
def List<Property> properties := currentEntity.getProperties();
properties.compact().collect(
{ p ->
if !(p.getPropertyType().isSubtypeOf(type(Record)) or
p.getPropertyType().isSubtypeOf(type(Collection<Object>)))
then
g.addColumn(new PropertyPathValueProvider(p), null,
//Boolean sortable:
false,
// Boolean editable:
true,
// Editor editor:
null
)
//adding header to each column:
.setHeader(core::getLabel(p));
end
}
);
g.addButtonColumn("Delete", { e:Record -> deleteRecords({e}); g.refresh()});
}
In the preview, you can spot that the Single-Select List and the Grid have empty space below: their size does not get adapted to their content.
To fix this, set the number of rows to the number of displayed items:
setEntityColumns()
method: ...
);
g.addButtonColumn("Delete", { e:Record ->
deleteRecords({e});
g.setHeightByRows(countAll(currentEntity));
g.refresh()}
);
g.setHeightByRows(countAll(currentEntity));
}
...
If you need to adjust the presentation further, such as, adding margin, consider using CSS or JavaScript.
The final step is to create a page with the form: a page is represented by a document definition. When you upload a document definition, it is included in the list of documents, which are accessible from the Application User Interface. For more information on documents, refer to Documents-related documentation.
Note: You can create fully customized page in Java directly as well. Refer to instructions on how to create a custom view.
To define a new document, do the following:
entityOverviewDoc
Entity Overview
new EntityOverview()