Learn how to implement the MVC pattern in JavaScript applications with practical examples and detailed explanations.
In this section, we will explore how to implement the Model-View-Controller (MVC) architectural pattern using plain JavaScript. The MVC pattern is a powerful way to structure applications, promoting separation of concerns and making your code more maintainable and scalable. Let’s dive into each component of MVC and see how they interact within a JavaScript application.
The MVC pattern divides an application into three interconnected components:
Let’s build a simple JavaScript application using the MVC pattern. We’ll create a basic to-do list application that allows users to add, remove, and view tasks.
The model is responsible for managing the data of the application. In our to-do list example, the model will handle the list of tasks.
// Model
class TodoModel {
constructor() {
this.todos = [];
}
addTodo(todo) {
this.todos.push(todo);
}
removeTodo(index) {
this.todos.splice(index, 1);
}
getTodos() {
return this.todos;
}
}
Explanation:
TodoModel
class manages the list of tasks.The view is responsible for displaying the data to the user and capturing user input. In our example, the view will render the list of tasks and provide input fields for adding new tasks.
// View
class TodoView {
constructor() {
this.app = this.getElement('#root');
this.form = this.createElement('form');
this.input = this.createElement('input');
this.input.type = 'text';
this.input.placeholder = 'Add a new task';
this.input.name = 'todo';
this.submitButton = this.createElement('button');
this.submitButton.textContent = 'Submit';
this.todoList = this.createElement('ul', 'todo-list');
this.form.append(this.input, this.submitButton);
this.app.append(this.form, this.todoList);
}
createElement(tag, className) {
const element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
return document.querySelector(selector);
}
displayTodos(todos) {
// Remove all child nodes
while (this.todoList.firstChild) {
this.todoList.removeChild(this.todoList.firstChild);
}
// Show default message
if (todos.length === 0) {
const p = this.createElement('p');
p.textContent = 'Nothing to do! Add a task?';
this.todoList.append(p);
} else {
// Create todo item nodes for each todo
todos.forEach((todo, index) => {
const li = this.createElement('li');
li.id = index;
const span = this.createElement('span');
span.textContent = todo;
const deleteButton = this.createElement('button', 'delete');
deleteButton.textContent = 'Delete';
li.append(span, deleteButton);
// Append nodes
this.todoList.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener('submit', event => {
event.preventDefault();
if (this.input.value) {
handler(this.input.value);
this.input.value = '';
}
});
}
bindDeleteTodo(handler) {
this.todoList.addEventListener('click', event => {
if (event.target.className === 'delete') {
const id = parseInt(event.target.parentElement.id);
handler(id);
}
});
}
}
Explanation:
TodoView
class handles the DOM elements for displaying the to-do list.The controller acts as an intermediary between the model and the view. It handles user input, updates the model, and refreshes the view.
// Controller
class TodoController {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindDeleteTodo(this.handleDeleteTodo);
// Display initial todos
this.onTodoListChanged(this.model.getTodos());
}
onTodoListChanged = todos => {
this.view.displayTodos(todos);
}
handleAddTodo = todoText => {
this.model.addTodo(todoText);
this.onTodoListChanged(this.model.getTodos());
}
handleDeleteTodo = index => {
this.model.removeTodo(index);
this.onTodoListChanged(this.model.getTodos());
}
}
// Initialize the app
const app = new TodoController(new TodoModel(), new TodoView());
Explanation:
TodoController
class connects the model and the view.In the MVC pattern, DOM manipulation is primarily handled by the view. The view listens for user interactions and updates the DOM accordingly. This separation ensures that the model remains unaware of the presentation logic, adhering to the separation of concerns principle.
Implementing MVC in plain JavaScript can be challenging due to the following reasons:
Despite these challenges, implementing MVC in plain JavaScript is an excellent way to understand the pattern deeply and appreciate the abstractions provided by frameworks.
To get hands-on experience, try modifying the code examples:
Below is a diagram illustrating the interaction between the Model, View, and Controller in our JavaScript application:
sequenceDiagram participant User participant View participant Controller participant Model User->>View: Add Task View->>Controller: Add Task Event Controller->>Model: Add Task Model->>Controller: Task Added Controller->>View: Update View View->>User: Display Updated Task List
Diagram Explanation: This sequence diagram shows how user actions flow through the MVC components. The user interacts with the view, which communicates with the controller. The controller updates the model, and the model’s changes are reflected back in the view.
For more in-depth information on MVC and JavaScript, consider exploring the following resources:
Remember, mastering MVC in JavaScript is just the beginning. As you progress, you’ll be able to build more complex and interactive applications. Keep experimenting, stay curious, and enjoy the journey!