LSPS documentation logo
LSPS Documentation
CRUD Grid

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.

Creating Database Data

First, prepare the persisted data that will be displayed in the document:

  1. Create a data type hierarchy of shared Records Book, Author, and Publisher. Create relationships between Book and Author, and Book and Publisher as depicted below.
    forms_datahierarchy_book.png
  2. Initialize database data, for example:
    1. Create a process definition.
    2. In the process, create a workflow that will be executed: for example, a None Start Event with an outgoing Flow to a Simple End Event.
    3. On the Assignment tab of the Flow, define an expression that will initialize the database data, for example:
      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");
      &nbsp
      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);
  3. 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.

Creating the Form

You will create a Grid over the shared Records that will display values of the Record fields:

  1. Create a form definition.
  2. In the form, insert the Vertical Layout and Grid component.
  3. Define the Grid component properties:
    • Define its name as EntityGrid
    • Set its data source to Type and the value to Author
    • Select the Editor enabled flag in the Editing property.
    • On the Grid's Init tab, enable filtering by calling c.setFiltrable(true)
  4. For each Author property, insert a Grid Columns into the Grid and set the Value Provider to Property path and insert the property path: you will insert columns for the Author.id, Author.firstName, and Author.surname property paths. Also select the Editable flag on each column.
  5. 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.

    forms_static_author_grid.png
    Note if you click a row, you can edit the entries: edits are reflected on the database when you save the edits or press Enter.

    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.

  6. First, let us externalize the setting of the displayed entity type:
    1. Create a form variable currentEntity of type Type<Record> that will hold the entity the user selects.
    2. On the Grid, set value of the data source to currentEntity.
    3. 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:

    4. Open the methods file of the form: the file is created automatically along with the form file and bears the same name.
    5. Define a new constructor with the variable initialization:
      public EntityOverview(){
        currentEntity := type(Author);
      }
  7. Next adapt the columns and their value providers according to the currentEntity record. Do this dynamically; do not insert individual columns into the form (each entity has requires its own columns):
    1. Delete the Grid Columns in the Grid.
    2. On the Init tab of the Grid, define how to add the columns:
      //get a list of properties of the entity in the currentEntity variable:
      def List<Property> properties := currentEntity.getProperties();
      foreach Property p in properties do
      c.addColumn(new PropertyPathValueProvider(p));
      end
    3. Run preview of the form: right-click the form and go to Run As > Form Preview.
      forms_table_with_all_props.png
      The next problem is how to deal with display columns for properties on related Records. Let us filter properties of these complex types: adjust the Init expression on the Grid as follows:
        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
  8. Allow the user to change the value of the currentEntity:
    1. Add a local variable 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"];
          }
      }
    2. Add a Single Select List component above the Grid with the following properties:
      • Binding to Reference and its value to &currentEntity
      • Options to Map and its value to options
    3. On the Init tab of the Single Select List component, define the action when the user selects an entity:
      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);
  9. 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).

  10. Add the column with the Delete button: in the setEntityColumns extension method, add the call 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:

  1. Set the labels on Field of the Author, Book, and Publisher Records.
  2. Set the column header to the label value: if you defined the setEntityColumns() extension method then you need to add a 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()});
    }
  3. Run preview of the form.

Adjusting Presentation

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:

  • on the Single Select List, set the number of rows to the number of displayed options:
    c.setRowCount(options.size());
  • on the Grid, set the number of rows to the number of data-source entries: You will need to get the number of entries for the selected Record and set this as the height of the Grid on initialization and whenever the user changes the displayed entity, that is in the 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.

Creating the Document

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:

  1. Create a document definition file:
    1. Right-click your module.
    2. In the context menu, go to New > Document Definition
    3. In the New Document Definition dialog, define the definition file properties: check its location and modify its name.
  2. Open the document definition file.
  3. In the Documents area of the Document Editor, click Add.
  4. In the right part, define the properties of the document:
    • Name: entityOverviewDoc
    • Title: Entity Overview
    • UI definition: new EntityOverview()
  5. Upload your Module and check the Document on the Documents tab of the Application User Interface.