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");
       
      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

We 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.
  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.

    Now we are facing the following problem: the Grid is static and it will always display only Authors and the user cannot change this. You need to allow the user to change the type of entity that is displayed in the grid and when they select an entity, you need to update the content of the Grid. And not only the content: you need to actually update which Columns are displayed. To achieve this, we 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). Hence we need to initialize the variable sooner: you can do so in a 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. Now you need to adapt the columns and their value providers according to the currentEntity value. You need to do this dynamically as opposed to modeling individual column since each entity has different 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
           def forms::GridColumn col := 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 you are facing is that the Grid adds 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
            def forms::GridColumn col := c.addColumn(new PropertyPathValueProvider(p), null,
                //Boolean sortable:
                null,
                // Boolean editable:
                true,
                // Editor editor:
                null);
         end
      end
      
  8. Provide the user a component that will 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.
    3. In its properties, set:
      • Binding to Reference and its value to &currentEntity
      • Options to Map and its value to options
    4. On the Init tab, 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)) ||
                p.getPropertyType().isSubtypeOf(type(Collection<Object>))
                )
             then
                def forms::GridColumn col := EntityGrid.addColumn(new PropertyPathValueProvider(p));
             end
           end;
        } );
        c.setNullSelectionAllowed(false);
      
      Since the adding of columns on click and on init are identical starting from the properties declaration, consider defining a function. Here we define extension method of the Grid component:
      @ExtensionMethod
       
      public Grid getGridWithColumns(Grid g, Type<Record> currentEntity) {
       
        def List<Property> properties := currentEntity.getProperties();
       
          properties.compact().collect(
            { p ->
               if !(p.getPropertyType().isSubtypeOf(type(Record)) ||
                 p.getPropertyType().isSubtypeOf(type(Collection<Object>)))
               then
                 g.addColumn(new PropertyPathValueProvider(p), null,
                   //Boolean sortable:
                   true,
                   // Boolean editable:
                   true,
                   // Editor editor:
                  null)
               end
              }
          );
        g;
      }
      

    Adapt the assembly of columns on grid to EntityGrid.getGridWithColumns(currentEntity).

  9. Add the column with the Delete button: if you defined the getGridWithColumns extension method then you need to add EntityGrid.addButtonColumn("Delete", { e:Record -> deleteRecords({e}); EntityGrid.refresh()});. 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.
  10. There is one more issue to take care of in and that is the headers of columns. Normally, to display a name of a Record or it field, you would set the caption, which is a String, for each component with the values of the record or field. As this can be pretty annoying, you can define labels: They are defined on records and fields and used exactly in such cases:
  11. Set the labels on Field of the Author, Book, and Publisher Records.
  12. Set the column header to the label value: if you defined the getGridWithColumns() 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.
    public Grid getGridWithColumns(Grid g, Type<Record> currentEntity) {
     
    def List<Property> properties := currentEntity.getProperties();
     
    properties.compact().collect(
      { p -> if !(p.getPropertyType().isSubtypeOf(type(Record)) ||
              p.getPropertyType().isSubtypeOf(type(Collection<Object>)))
           then
             g.addColumn(new PropertyPathValueProvider(p), null,
               //Boolean sortable:
               true,
               // 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()});
    g
    }
    
  13. 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.setRows(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:
    ...
       g.addButtonColumn("Delete", { e:Record -> deleteRecords({e}); g.refresh()});
       g.setHeightByRows(countAll(currentEntity));
       g
    }
    ...
    

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.

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.