Implementing the TodoListView interface

Then, of course, we can implement this interface:

class HTMLTodoListView implements TodoListView { 
    constructor() { 
        // TODO 
    } 

    clearInput(): void { 
        // TODO 
    } 

    getFilter(): string { 
        // TODO 
    } 

    getInput(): TodoItem { 
        // TODO 
    } 

    render(todoList: ReadonlyArray<TodoItem>): void { 
        // TODO 
    } 

    filter(): void { 
        // TODO 
    } 
} 

This is the base structure. Now, we need to start refactoring/integrating our previous code to this new class while implementing the interface methods.

First, add private fields to the class for the different HTML elements that we know for sure should be available. Then, make sure to initialize them in the constructor:

    private readonly todoInput: HTMLInputElement; 
    private readonly todoListDiv: HTMLDivElement; 
    private readonly todoListFilter: HTMLInputElement; 

    constructor() { 
        this.todoInput = document.getElementById('todoInput') as 
HTMLInputElement; this.todoListDiv = document.getElementById
('todoListContainer') as HTMLDivElement; this.todoListFilter = document.getElementById
('todoFilter') as HTMLInputElement; // defensive checks if(!this.todoInput) { throw new Error("Could not find the todoInput HTML input
element. Is the HTML correct?"); } if(!this.todoListDiv) { throw new Error("Could not find the todoListContainer HTML
div. Is the HTML correct?"); } if(!this.todoListFilter) { throw new Error("Could not find the todoFilter HTML input
element. Is the HTML correct?"); } }

With this, we know that when the class is instantiated, it will retrieve the necessary HTML elements from the DOM. If that fails, then errors will be thrown to help us identify what went wrong.

Such validations are usually referred to as defensive programming checks. Oftentimes, defensive checks incur wasted CPU cycles checking elements that should be invariants, but in practice, those checks actually help avoid a lot of troubleshooting to identify where bugs have been introduced.

In our case, if the HTML template gets modified in a way that prevents our code from getting the elements that we expected to be available, then errors will be thrown right away.

Perform defensive checks as soon as possible and make your code fail fast. The sooner you let errors surface, the easier it becomes to identify and fix problems.

Next up, let's add the implementation of the clearInput method:

    clearInput(): void { 
        this.todoInput.value = ''; 
    } 

The getFilter method is also straightforward to implement:

    getFilter(): string { 
        return this.todoListFilter.value.toUpperCase(); 
    } 

Now, implement the getInput method:

    getInput(): TodoItem { 
        const todoInputValue: string = this.todoInput.value.trim(); 
        const retVal: TodoItem = new TodoItem(todoInputValue); 
        return retVal; 
    } 
We have created a superfluous constant called retVal simply to ease debugging later on.

For the render function, we simply need to integrate/refactor the code of the updateTodoList function:

    render(todoList: ReadonlyArray<TodoItem>): void { 
        console.log("Updating the rendered todo list"); 
        this.todoListDiv.innerHTML = ''; 
        this.todoListDiv.textContent = ''; // Edge, ... 

        const ul = document.createElement('ul'); 
        ul.setAttribute('id', 'todoList'); 
        this.todoListDiv.appendChild(ul); 

        todoList.forEach(item => { 
            const li = document.createElement('li'); 
            li.setAttribute('class','todo-list-item'); 
            li.innerHTML = `<a href='#' 
onclick='todoIt.removeTodo("${item.identifier}")
'>${item.description}</a>`; ul.appendChild(li); }); }

Notice that we have modified the link to remove a todo item. As you can see, we invoke the removeTodo function on a todoIt object. We will define this later on.

Finally, we need to implement the filter function, which will simply show/hide elements in the view depending on the filter value:

    filter(): void { 
        console.log("Filtering the rendered todo list"); 
        const todoListHtml: HTMLUListElement = 
document.getElementById('todoList') as HTMLUListElement if (todoListHtml == null) { console.log("Nothing to filter"); return; } const todoListFilterText = this.getFilter(); todoListHtml.childNodes.forEach((item) => { let itemText: string | null = item.textContent; if (itemText !== null) { itemText = itemText.toUpperCase(); if (itemText.startsWith(todoListFilterText)) { (item as HTMLLIElement).style.display = "list-
item"; } else { (item as HTMLLIElement).style.display = "none"; } } }); }

At this point, our view implementation is almost done, but the puzzle remains incomplete. We now need to turn our attention to the controller layer.