LSPS documentation logo
LSPS Documentation
Creating Custom To-Do List

Important: To complete this tutorial, you need the enterprise edition of PDS with SDK installed.

Typically, the default To-Do list will not cut it in the real world of business and you will require custom business-related data for a to-do while retaining the related mechanisms, such as, priority of the todo, its allocation, locking, annotations, delegation, substitution, etc.

Since the Todo is represented by the Todo record which is a system record, you cannot simply add a field to it. However, you can create a new Record related to the Todo Record and add the business data to this Record: in this tutorial, we create the TodoItem record with an additional field and a relationship to the Todo record:

  • To create instances of TodoItem on runtime, we will create the record in the issueAction parameter of the User tasks.
  • To query the to-do information of a TodoItem record, we will use joins from the TodoItem to the Todo.

Note: The pattern of records related to system records can be applied analogously to other system records, for example, to extend the data held by Person.

Creating the Data Model

Before you start, create a project:

  1. Open the Modeling perspective in your PDS.
  2. Go to File -> New -> GO-BPMN Project.
  3. In the pop-up enter the project name custom_todo_list_model and click Finish.

Since Todo is a system record and system records cannot be extended directly, you need to create a record that will represent our todo item with the business data and is related to the Todo system record:

  1. Create a module that will hold the data hierarchy:
    1. Go to File -> New -> GO-BPMN Module.
    2. In the popup, do the following:
      • Select the custom_todo_list_model project.
      • In the Module name field, enter custom_todo_list_data.
      • Unselect the executable module option since this module is intended as a module import and will never be instantiated by itself.
    3. Click Finish.
  2. Create a data type definition: right-click the module and go to New -> Data Type Definition.
  3. Create the shared record with the business data, TodoItem:
    1. Right-click the canvas in the graphical editor and go to New > Shared Record.
    2. Enter the record name TodoItem.
    3. Insert the field priority of type Integer into the TodoItem record.
  4. Establish a relationship to the Todo record:
    1. Right-click the canvas in the graphical editor and go to New > Record Import.
    2. In the human module, select Todo (alternatively start typing todo) and click OK.
      extendingTodo_recordImport.png
    3. To create a relationship between TodoItem to Todo, drag the quicklinker from TodoItem to Todo.
    4. Select the relationship and set the properties of the Todo end in the Properties view:
      • Name: todo
      • Multiplicity: Single (one TodoItem relates to one Todo)
        extendingTodo_recordHierarchy.png

        Note: To display the fields and methods of the imported Todo record, right-click the record and under Compartments select the required items.

Creating the Todo Items

Todos are created when a User Task of a process instance is executed: to create the todo item related to the todo, create it in the issueAction closure of the User Task: issueAction is executed right after a todo is created and has the todo created by the User Task as its input parameter.

Let's create a process that will create a Todo and its TodoItem:

  1. Create the module that will hold the process:
    1. Right-click your project and go to New -> GO-BPMN module.
    2. In the module name field, enter custom_todo_list_process and click Finish.
    3. Import the custom_todo_list_data module (double-click the module Imports node in the custom_todo_list_process module).
  2. Create the process definition and design the process:
    1. Right-click the custom_todo_list_process module and go to New -> Process Definition.
    2. Enter the name CreateTodoItem and select the BPMN-based process option.
    3. In the process, create a local variable newTodoItem of the TodoItem type (in the Outline view of the process definition, right-click the root node and select New > Variable). It will hold the new todo item, so we can pass it to the todo form where we will edit its priority field.
    4. Design a process flow with a User task.
    5. In the Properties of the User task, define the parameters of the task: in the issueAction parameter, create the TodoItem record over the to-do:
      title /* String */ -> "Dummy Submit for Guest",
      performers /* Set<Performer> */ -> {getPerson("guest")},
      uiDefinition /* UIDefinition */ -> submitForm(newTodoItem) ,
      issueAction /* {Todo:void} */ -> { t:Todo -> newTodoItem := new TodoItem(todo -> t)}
      
      Note that the submitForm does not exist yet; we will create it in the next step.

Note: If you attempted to change the value of the related to-do directly, for example, on a flow assignment with newTodoItem.todo.title := "";, you will get a validation error since Todo is a system record and fields of system records cannot be accessed directly.

Creating the Form for the To-Do

Create the form that will be used to gather the priority data for the TodoItem and submit the todo:

  1. Right-click the custom_todo_list_process module and go to New -> Form Definition.
  2. Enter submitForm as the name of your form and make sure the Use FormComponent-based UI is not selected.

    Note: The Use FormComponent-based UI setting defines which module of the Standard Library is used to create the form: when not selected the ui module is used. Forms of the ui module are event driven. When the option is selected, the forms module is used: the forms are created more like in Vaadin. Generally the latter option is more powerful but requires more programming skills and is considered experimental in this version.

  3. Create the form parameter newTodoItemParam of type TodoItem (in the Outline view of the form definition, right-click the root node and select New > Parameter).
  4. In the editor with the form, insert the following components and define their properties in their Properties view.
    • Form Layout
    • Text Box with properties:
      • Label: "Priority:"
      • Binding: &newTodoItemParam.priority
    • Button:
      • Text: "Submit"
      • ActionListener on the button with Submit selected on the Action tab
extendingTodo_submitForm.png

Creating a List of Todo Items

Now we will create a page that will display the list of the todo items that have not been submitted yet (their todo is alive) and are assigned to the current user.

First, create a query that will retrieve todo items:

  1. Right-click the custom_todo_list_data module and go to New -> Query Definition.
  2. In the query editor, click Add.
  3. On the right define the query name as getTodoItems, set TodoItem as the record type, and set an iterator name, for example ti.
  4. At this stage, the query returns all TodoItems. Restrict it so it returns only those todo items that are related to a LIVE todo:
    1. Join the system todo table: select the Join Todo List.
    2. Define the iterator for the returned todos in the Query Todo Iterator, for example t.
    3. In the Todo List Criteria, define an expression that filters the todos from the joined todo list:
      //returns todos of the current person:
      new TodoListCriteria(person -> getCurrentPerson(),
       //exclude interrupted, accomplished, suspended todos:
       includeAllStates -> false,
       //exclude rejected todos:
       includeRejected -> false,
       //exclude to-dos allocated by other persons:
       includeAllocatedByOthers -> false,
       //exclude to-dos of substitutes:
       includeSubstituted -> false)
      
  5. Now the query returns all todo items with a to-do of the currently logged-in person. However, only todo with a matching id should be returned. Define the condition in the Condition property:
    ti.todo.id == t.id
    

The query is ready and you can create a document with a form that will display the todo items:

  1. Create the custom_todo_list_ui non-executable module that will hold the document.
  2. Import the custom_todo_list_data module.
  3. Create the document that represents the page with the todo items: Right-click the custom_todo_list_ui module and go to New -> Document Definition. Create a document with the properties:
    • Name: todoItemsList
    • Title: "My Todo Items"
    • UI definition: listOfTodoItems()
  4. The UI definition does not exist yet, let us create it:
    1. Right-click the custom_todo_list_ui module and go to New -> Form Definition.
    2. Set the form name to listOfTodoItems and click Finish.
  5. In the editor with the form, insert a Vertical Layout.
  6. Into the layout, insert the Grid component and define its properties:
    1. Set Data Kind to Query
    2. Set Data to getTodoItems()
    3. Create Columns with the content set to Property path with the respective custom todo item properties, for example, TodoItem.todo.id.
  7. Create a Column that will contain a link:
    1. Set Content to Closure and define the closure that returns the link content below (You need to use the Closure type since a property path of type Integer cannot use the renderer Link):
      { i:TodoItem -> i.todo.id.toString() }
      
    2. Set the Renderer to Link.
    3. Create a RendererClickListener on the Link:
      1. Right-click the link in the form and open the Event Handling tab of the Properties view.
      2. Under the Private Listeners section, click Add.
      3. On the Actions tab, select the Navigation option and add the navigation expression:
        def TodoItem ti := ((_event as RendererClickEvent).rowObject) as TodoItem;
        new TodoNavigation(todo -> ti.todo, openAsReadOnly -> null)
        
        extendingTodo_rendererlistener.png
        If you haven't done so yet, now is the time to test the modules:
  8. Run the PDS Embedded Server by clicking the Start Embedded Server button .
  9. Generate todos: right-click the custom_todo_list_process module and go to Run As > Model.
  10. Upload the document with the todo items list: right-click the custom_todo_list_ui module and go to Upload As > Model.
  11. Open your browser and go to http://localhost:8080/lsps-application and check the Documents of the guest user.
  12. Check the list of to-dos: Open Documents and click My Todo Items.
  13. Click a link to navigate to the todo.
  14. Stop the PDS Embedded Server by clicking the Stop Embedded Server button .

Adding the Custom To-Do List to the Navigation Menu

Now this is all nice and neat but the user can still access the default To-do List page: generally you want to substitute the To-Do List in the Application User Interface with our to-do item list.

To be able to do this, you need to modify the Application User Interface and deploy it to your server:

  1. Go to File > New > Other
  2. In the popup dialog, select LSPS Application and click Next.
  3. In the updated popup, enter the maven artifact details and click Finish.

This will generate the sources of the Application User Interface. For more details, refer to documentation on development of Custom Application User Interface.

Let us remove the default To-Do List item and add our list item to the navigation menu:

  1. Since this is Java code, switch to the Java perspective.
  2. Remove the To-Do List item:
    1. Open the <YOUR_APP>.vaadin.core.AppLayout.java file.
    2. Comment out the respective line in the buildMenu() method.
      @SuppressWarnings("unused")
        protected void buildMenu() {
          /*if (ui.getUser().hasRight(HumanRights.READ_ALL_TODO) || ui.getUser().hasRight(HumanRights.READ_OWN_TODO)) {
            todoMenuItem = navigationMenu.addViewMenuItem(TodoListView.TITLE, TodoListView.ID, FontAwesome.LIST, todoBadge, null);
          }*/
          if (ui.getUser().hasRight(HumanRights.ACCESS_DOCUMENTS)) {
            documentMenuItem = navigationMenu.addViewMenuItem(DocumentsView.TITLE, DocumentsView.ID, FontAwesome.FILE_TEXT_O, documentBadge, null);
          }
      
    3. Build the application and run the SDK Embedded Server: open the run configuration drop-down menu and click the build launcher and then embedded server launcher for you application.
      extendingTodo_runningSDKEmbedded.png

      Important: Note that this is a different server from the PDS Embedded Server and has your application hot-deployed.

    4. Open the browser and check that the To-Do List item is no longer available in the navigation menu.
  3. Now add the navigation item to the custom to-do list:
    1. First copy the fully qualified name of the module with the document: right-click the document definition and select Copy Qualified Name.
      extendingTodo_copyQualifiedName.png
    2. Add the document to buildMenu() of <YOUR_APP>.vaadin.core.AppLayout.java: paste the qualified name of the document to prevent typos. Do not forget to remove the if statement around or set its condition to true and the other example items as applicable.
      if (hasRightToOpenDocument("custom_todo_list_ui::todoItemsList")) {
         navigationMenu.addDocumentItem(
            "My Todo Items",
            "custom_todo_list_ui::todoItemsList",
            null,
            FontAwesome.ADN,
            null,
            null
         );
      }
      
  4. Rebuild the application and restart the server preferably in debug mode.
    extendingTodo_rebuilding.png
    Rebuilding the application
  5. Upload the ui module and run the process module.

Localizing Name of the Menu Item

You can now access the document directly from the navigation menu, but it contains the ??? characters: these signalize that the system failed to find the localization string. Let's create the string:

  1. Open properties file in the com.whitestein.lsps.vaadin.webapp package (<YOUR_APP>-vaadin project), for example, the localization.properties file with the default localizations.
  2. Add the localization key.
    # navigation menu items
    nav.todoitems = To-do List
    nav.documents = Documents
    nav.runProcess = Run Model
    # This is the new key:
    nav.itemsList = My Todo Items
    
  3. Adapt the addDocumentItem() call: navigationMenu.addDocumentItem("nav.todoitems", "custom_todo_list_ui::listOfTodoItems", null, FontAwesome.ADN, null, null);
  4. Rebuild the application and restart the server.

Excluding the Todo Items Document from Documents

Right now the document is accessible from the dedicated navigation menu plus it is available as a document in the Documents menu. To remove it, open the DocumentsView class and adapt the load() method; for example:

private void load() {
    try {
      List<DocumentInfo> documents = genericDocumentService.getNonParametricDocuments();
 
      for (DocumentInfo document : documents) {
        //Added this to exclude the document from the table:
        if (!"custom_todo_list_ui::todoItemsList".equals(document.getId())) {
          container.addBean(document);
        }
      }

Note that you will need to adapt the calculation of documentBadge on documentMenuItem with the number of available documents.

Simple example of badge calculation

//constant with the document name:
private static final String DOCUMENT_IN_MENU = "custom_todo_list_ui::todoItemsList";
 
  private void calculateBadges() {
    try {
      todoBadge = (int) todoService.getTodoCount(new TodoListCriteria());
      try {
        List<DocumentInfo> nonParametricDocuments = documentService.getNonParametricDocuments();
        //exclude of the document from the count:
        documentBadge = 0;
        for (DocumentInfo documentInfo : nonParametricDocuments) {
          if (!DOCUMENT_IN_MENU.equals(documentInfo.getId())) {
            documentBadge++;
          }
        }
 
      } catch (ErrorException e) {
        throw new RuntimeException(e);
      }
      ModuleCriteria criteria = new ModuleCriteria();
      criteria.setIncludeImports(false);
      criteria.setIncludeExecutableOnly(true);
      criteria.setIncludeLatestOnly(true);
      ModuleList findModules = modelManagementService.findModules(criteria);
      runModelBadge = (int) findModules.getTotal();
    } catch (RuntimeException ex) {
      LspsUI.getCurrent().error(ex);
    }
  }