LSPS documentation logo
LSPS Documentation
Creating Custom To-Do List

Important: To complete this tutorial, you need the Enterprise Edition of Designer and the LSPS Maven Repository 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 Designer.
  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 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 */ -> new 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 selected.

    Note: The Use FormComponent-based UI setting defines the module of the Standard Library that is used to create the form: When the option is selected, the forms module is used. Such forms are created more like in Vaadin and are a more powerful solution. When not selected the ui module is used. forms based on the ui module are used. Such forms are event driven and oriented on users without programming skills.

  3. Create the form variable newTodoItemVar of type TodoItem (in the Outline view of the form definition, right-click the root node and select New > Variable).
  4. Create a parametric constructor for the form that initialize the variable to the parameter value (open the form and display the Methods tab):
    public SubmitForm(TodoItem newTodoItem){
       newTodoItemVar := newTodoItem
    }
    
  5. Back on the Form tab, insert the following components and define their properties in their Properties view:
    • Form Layout
    • Decimal Field with properties:
      • Caption: "Priority:"
      • Binding: set to Reference with the value &newTodoItemVar.priority
    • Button:
      • Text: "Submit"
      • Click listener: { e -> Forms.submit()}
extendingTodo_submitForm.png
If you run the model now and the process executes the User task, and creates a todo along with its todo item. You can set the priority in the Application User Interface in the to-do.

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 related to a to-do of the current person. However, only the todo with the 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: new 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 Source to Query and getTodoItems() as its value.
    2. Create Grid Columns with the value provider set to Property path with the respective custom todo item properties, for example, TodoItem.todo.id, TodoItem.priority.
  7. Create a Grid Column that will contain a link which opens the task item:
    1. Set Value Provider 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. Below define the navigation of the link:
      { ti:TodoItem -> Forms.navigateTo(
        new TodoNavigation(
          todo -> ti.todo,
          openAsReadOnly -> false
          )
        )
      }
      extendingTodo_rendererlistener.png
      If you haven't done so yet, now is the time to test the modules:
  8. Run the Designer 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. Create the guest user with all security roles.
  12. Open your browser and go to http://localhost:8080/lsps-application, log in as the guest user
  13. Go to Documents and click My Todo Items.
  14. Click a link to navigate to the todo.
  15. Stop the Designer Embedded Server by clicking the Stop Embedded Server button .

Removing the To-Do Navigation from the Menu

Now this is all nice and neat but the user can still access the default To-do List page: so we need to substitute the To-Do List in the Application User Interface with our to-do item list. To do this, we need to modify the Application User Interface itself.

First, generate LSPS Application:

  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.

For more details about the sources, refer to the custom-application documentation.

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. Create a custom main menu:
      1. Create and open the <YOUR_APP>.vaadin.core.AppAppLayout.java class.
      2. Modify the createMainMenu() method so it returns you custom MainMenu implementation.
        @Override
          protected Component createMainMenu() {
            return new AppMainMenu(CONTENT_AREA_ID);
          }
        
    2. Create the custom main menu:
      1. Create the AppMainMenu class that extends DefaultMainMenu.
      2. Add the constructor from the DefaultMainMenu (there is a validation warning that the constructor is missing).
      3. Copy and override the DefaultMainMenu's createMenu() method.
      4. Remove the if statement with navigation item menu of the todo list:
        @Override
        @SuppressWarnings("unused")
          protected void createMenu() {
            /*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);
            }*/
            ...
    3. Build the application: open the run configuration drop-down menu and click the build launcher.
      extendingTodo_runningSDKEmbedded.png
    4. Run SDK Embedded Server: open the run configuration drop-down menu and click the embedded server launcher for you application.

      Important: Note that this is a different server from Designer Embedded Server we used previously. The Designer Embedded Server is generated on its first launch in the given workspace with the default LSPS Application deployed. The SDK Embedded Server is generated when you generate your application in the and has your application hot-deployed.

    5. Open the browser and check that the To-Do List item is no longer available in the navigation menu.

Adding the To-Do Items Navigation to the Menu

Now add the navigation item to the custom to-do list:

  1. Copy the fully qualified name of the module with the document to the clipboard: right-click the document definition and select Copy Qualified Name.
    extendingTodo_copyQualifiedName.png
  2. Add a addDocumentItem call with the document to the createMenu() of AppMainMenu.java: paste the qualified name of the document to prevent typos.
     //navigation item to the todo item document:
        if (hasRightToOpenDocument("custom_todo_list_ui::todoItemsList")) {
          navigationMenu.addDocumentItem("Todo items", "custom_todo_list_ui::todoItemsList", null, VaadinIcons.ABACUS, null, null);
        }
  3. Restart the server in debug mode.
  4. From the Modeling perspective, upload the custom_todo_list_ui module and run the custom_todo_list_model module to create some todo items.
  5. Check the menu in the browser.

Localizing the Name of a Menu Item

The document menu item is now in the navigation menu but its label contains the ??? characters: these signalize that the system failed to find the localization string. Let's create the string:

  1. Use a localization key as the titleKey parameter in the addDocumentItem() call: navigationMenu.addDocumentItem("nav.todoitems", "custom_todo_list_ui::listOfTodoItems", null, FontAwesome.ADN, null, null);
  2. Open localization.properties with the default localizations in the com.whitestein.lsps.vaadin.webapp package (<YOUR_APP>-vaadin project).
  3. 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 = Todo Items
  4. Restart the server.

Excluding the Todo Items Document from Documents

Right now the document with the todo items is accessible not only from the dedicated navigation menu but it is also available as a document on the Documents page. To remove it, do the following:

  1. Create and use your custom AppAppNavigator:
    1. In the AppLspsUI.createNavigator() method, call your navigator:
        @Override
        protected void createNavigator(ViewDisplay display) {
          Navigator navigator = new AppAppNavigator(getUI(), display);
        }
    2. Create the AppAppNavigatorClass that extends the DefaultAppNavigator class:
      1. Create the inherited constructor:
         public AppAppNavigator(UI ui, ViewDisplay display) {
            super(ui, display);
          }
      2. Override documentsViewClass() to return your document view class:
        @Override
          protected Class<? extends AppView> documentsViewClass() {
            return AppDocumentsView.class;
          }
  2. Create your implementation of the DocumentsView class (copy the content of the class to AppDocumentsView and adjust the load() method so it excludes the todo list document:
      //added property for excluded documents:
      private static final String EXCLUDED_DOCUMENT = "custom_todo_list_ui::todoItemsList";
      ...
     
       private void load() {
        try {
          //Add documents; renamed original documents to allDocuments:
          List<DocumentInfo> documents = new ArrayList<>();
          List<DocumentInfo> allDocuments = genericDocumentService.getNonParametricDocuments();
     
          //checking in the list of all documents for the excluded documents:
          for (DocumentInfo document : allDocuments) {
            //Added this if to excludes the document from the table:
            if (!EXCLUDED_DOCUMENT.equals(document.getId())) {
              documents.add(document);
            }
          }
          this.documents.clear();
          this.documents.addAll(documents);
        } catch (ErrorException e) {
          getUI().getAppErrorHandler().showErrorMessage("app.unknownErrorOccurred", e);
        }
      }
  3. Adapt the calculation of documentBadge on documentMenuItem (the blue icon with the number of available documents).
  4. Override the getDocumentBadge() method so it returns your local documentBadge variable.

Simple example of badge calculation

//constant with the document name:
private static final String DOCUMENT_IN_MENU = "custom_todo_list_ui::todoItemsList";
 
  @Override
  protected void calculateBadges() {
    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);
    }
    runModelBadge = (int) modelManagementService.getExecutableModulesCount();
  }
  @Override
  protected int getDocumentBadge() {
    return documentBadge;
  }