In the previous post we created the UI for adding a new Todo where in we used a Tab, TabPane and added the required controls- TextField and a Button to the Tab.
Going forward, in this post we will create the UI for listing the Open Todos i.e the Todos which have not been marked as Closed. We intend to implement the following features in the UI:
- Ability to load the Todos when the Tab is selected.
- Obtain a list view for the Todos with a scroll bar if the list takes more vertical space than required.
- For each Todo, provide a button to mark the todo as completed.
Before going further, lets have a look at the possible UI we wish to generate at the end:
Loading the Todo on Tab selection:
We add an EventHandler (class TodoLoader) which is added to the Tab#onSelectionChanged event, which loads the Todos from the repository and creates a GridPane with the task name, date when it was added and the button to mark the Todo as completed. This button, which marks the Todo as closed, uses the CloseTodoHandler.
TodoLoader and CloseTodoHandler implement EventHandler and the handle method is overridden to add the required functionality.
Constructing the Handler for loading the Open Todos:
In the TodoLoader#handle(Event event) method we:
Get all the open todos from the database:
openTodos = TodoDAO.getOpenTodos();
For each Todo we have fetched, create a Label for task text and date added and a button for marking the todo as completed. Also for the button we set its actionEvent handler which is an instance of CloseTodoHandler.
for(Todo aTodo : openTodos) { GridPane todoPane = new GridPane(); Label taskLabel = new Label(aTodo.getTask()); Label dateAdded = new Label(aTodo.getAdded().toString()); CloseTodoHandler closeHandler = new CloseTodoHandler(todoBox, todoPane, aTodo); Button closeButton = ButtonBuilder.create() .graphic(new ImageView( new Image(getClass().getResourceAsStream("check.png")))) .onAction(closeHandler) .build(); todoPane.add(taskLabel,1,1,5,1); todoPane.add(dateAdded,1,2,4,1); todoPane.add(closeButton, 5, 2, 1, 1); todoBox.getChildren().add(todoPane); }
The code for the complete class which loads the Todos and constructs the required UI is:
class TodoLoader implements EventHandler<Event>{ List<Todo> openTodos; VBox todoBox; TodoLoader(VBox todoBox){ this.todoBox = todoBox; } @Override public void handle(Event event) { //Clear the existing Todos, for adding new Todos todoBox.getChildren().clear(); try { openTodos = TodoDAO.getOpenTodos(); for(Todo aTodo : openTodos) { //A component that displays the information for each todo GridPane todoPane = new GridPane(); Label taskLabel = new Label(aTodo.getTask()); Label dateAdded = new Label(aTodo.getAdded().toString()); //Event handler for handling close operations CloseTodoHandler closeHandler = new CloseTodoHandler(todoBox, todoPane, aTodo); Button closeButton = ButtonBuilder.create() .graphic(new ImageView( new Image(getClass(). getResourceAsStream("check.png")))) .onAction(closeHandler) .build(); /* * Add the task label, * date added, and the * close button to the * gridpane component */ todoPane.add(taskLabel,1,1,5,1); todoPane.add(dateAdded,1,2,4,1); todoPane.add(closeButton, 5, 2, 1, 1); todoBox.getChildren().add(todoPane); } } catch (UnknownHostException e) { e.printStackTrace(); } } }
Now, lets look at some aspects of the CloseTodoHandler.
Constructing the handler for closing the Todos:
Each button which handles closing of todo should be aware of:
- which Todo to close i.e have a reference to the Todo instance
- which GridPane to remove from the VBox, should have a reference to GridPane instance
- which VBox to remove from i.e reference to the VBox instance.
When we are creating the instance of the CloseTodoHandler, we pass in these information as shown
CloseTodoHandler closeHandler = new CloseTodoHandler(todoBox, todoPane, aTodo);
In the handling of this event: we just have to invoke the corresponding DAO method to close the Todo and also remove it from the VBox as shown below:
@Override public void handle(ActionEvent actionEvent) { try { TodoDAO.setTodoAsCompleted(todo); todoBox.getChildren().remove(todoPane); } catch (UnknownHostException e) { e.printStackTrace(); } }
The complete code for the CloseTodoHandler is:
class CloseTodoHandler implements EventHandler<ActionEvent>{ VBox todoBox; GridPane todoPane; Todo todo; CloseTodoHandler(VBox todoBox, GridPane todoPane, Todo todo){ this.todo = todo; this.todoBox = todoBox; this.todoPane = todoPane; } @Override public void handle(ActionEvent actionEvent) { try { TodoDAO.setTodoAsCompleted(todo); todoBox.getChildren().remove(todoPane); } catch (UnknownHostException e) { e.printStackTrace(); } } }
Now we have to build the Tab which uses all the above classes, event handlers to generate the UI as shown in the screen shot above. An instance of VBox is created to add all the Todos on the UI and this instance is passed to the TodoLoader class so that it can use it to populate the UI as shown above in the TodoLoader class definition.
final VBox todoBox = new VBox(); todoBox.setTranslateX(10); todoBox.setSpacing(10);
As the items can occupy more than the given height of the application, we use ScrollPane to add the Veritcal scroll bars:
final ScrollPane todoListScroll = ScrollPaneBuilder.create() .hbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED) .prefHeight(APP_HEIGHT - 90) .maxWidth(APP_WIDTH) .build(); todoListScroll.setContent(todoBox);
And finally build the Tab as:
Tab allTodosTab = TabBuilder.create() .text("All todos") .content(todoListScroll) .closable(false) .onSelectionChanged(new TodoLoader(todoBox)) .build();
We refactor this code into a method and this method is invoked from the TodoAppRunner defined in the previous post.
private Tab buildShowAllTodoUi(){ final ScrollPane todoListScroll = ScrollPaneBuilder.create() .hbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED) .prefHeight(APP_HEIGHT - 90) .maxWidth(APP_WIDTH) .build(); final VBox todoBox = new VBox(); todoBox.setTranslateX(10); todoBox.setSpacing(10); todoListScroll.setContent(todoBox); Tab allTodosTab = TabBuilder.create() .text("All todos") .content(todoListScroll) .closable(false) .onSelectionChanged(new TodoLoader(todoBox)) .build(); return allTodosTab; }
The complete source code can be downloaded from here. I know its been a long series of posts and if you are stuck at any point just let us know via the comments here and we would try to clear out the confusion.