Home > Web Front-end > JS Tutorial > The MVC Design Pattern in Vanilla JavaScript

The MVC Design Pattern in Vanilla JavaScript

Lisa Kudrow
Release: 2025-02-16 12:22:10
Original
580 people have browsed it

The MVC Design Pattern in Vanilla JavaScript

Core points

  • MVC (Model-View-Controller) Design Pattern is a powerful method for organizing JavaScript code to enhance maintainability and readability by clearly separating concerns.
  • Unlike frameworks that may impose specific implementations, the MVC pattern provides a flexible structure that allows developers to adapt and scale their applications more easily.
  • Using Penguin's demonstration shows how to apply MVC in native JavaScript to systematically handle user interaction, data management, and UI updates.
  • The persistence and adaptability of the MVC pattern make it a valuable asset for developers who want to hone their programming skills in a rigorous and scalable way.
  • Key components of the MVC pattern include: models for managing data, views for processing displays, and controllers for coordinating user input and application output, each with different responsibilities to ensure the code is modular. and well organized.
  • This article emphasizes the importance of clean code and avoiding dependency locking with the framework, and advocates the use of MVC as a strategy to maintain the manageability and scalability of the code as applications grow and evolve as they develop .

The MVC Design Pattern in Vanilla JavaScript Design patterns are often integrated into popular frameworks. For example, the Model-View-Controller (MVC) design pattern is everywhere. In JavaScript, it is difficult to separate frameworks from design patterns. Often, a specific framework will come with its own interpretation of this design pattern. Frames come with perspectives, and each frame forces you to think in some way.

Modern frameworks determine the specific implementation method of the MVC model. When all explanations are different, this can be confusing, thereby adding noise and confusion. Frustrating confusion occurs when any code base adopts multiple frameworks. The question in my mind is, is there a better way?

MVC pattern is suitable for client frameworks, but modern frameworks will change. Today’s modernization will die over time. In this case, I want to explore alternatives and see where a little discipline can take us.

Read modern JavaScript and keep abreast of the ever-changing world of JavaScript! The MVC Design Pattern in Vanilla JavaScript Read this book The MVC model itself can be traced back decades. This makes it a design pattern worth your investment in programming skills. MVC mode is a mode that can exist independently. The question is, how far can this take us?

Wait, is this another framework?

First of all, I want to eliminate this common misunderstanding: design patterns are not frameworks. Design patterns are a rigorous way to solve code problems. This requires a certain skill level and puts the responsibility on the programmer. Design patterns separate concerns and facilitate writing clean code.

The frame is different because it does not have to follow any design pattern. One way to distinguish between frameworks and patterns is to look for Hollywood principles. The Hollywood principle is: "Don't call us, we'll call you." There is a dependency at any time to decide when you use it, and it's a framework. The framework is much like Hollywood, and you can't decide what to do or how to do it. In fact, developers are like actors, following scripts when asked to act.

There are many good reasons to avoid client frameworks:

  • Framework adds complexity and risk to the solution
  • You will encounter dependency locks, which will make the code difficult to maintain.
  • With the advent of new popular frameworks, it is difficult to rewrite existing legacy code

MVC mode

MVC design pattern originated from Xerox Smalltalk research project in the 1970s and 1980s. This is a time-tested mode for the front-end graphical user interface. This pattern comes from desktop applications, but has been proven to work well for web applications as well.

Its core is that the MVC design pattern is about a clear separation of concerns. The purpose is to make the solution clear and understandable. Any programmer who wants to make a specific change can easily find the right place.

Penguin Demo

Penguin! Cute and furry, one of the cutest creatures on earth. In fact, they are very cute, with 17 different penguins, not all of them live in Antarctica's environment.

It's time to make a Penguin demo! I'll show a deck showing several species on a page. For this, I want to use MVC design patterns and a little discipline. I will use extreme programming methods to use unit testing and simple methods to solve the problem at hand. Finally, you should be able to browse through several penguins, each with its own data and profile picture.

By the end of this example, you should have learned enough to use the MVC design pattern in pure JavaScript. The pattern itself is very easy to test, so good unit testing is expected.

I will stick with ES5 in this demo for cross-browser compatibility reasons. It makes sense to use proven language features combined with this permanent design pattern.

Are you ready? Let's wait and see.

Skeleton

The demo will consist of three main parts: the controller, the view, and the model. Each section has its own concerns and issues that need to be solved.

The following is a visualization of its appearance:

The MVC Design Pattern in Vanilla JavaScript

PenguinController handles events and is the intermediary between the view and the model. It calculates what happens when the user performs an action (for example, clicking a button or pressing a key). Client-specific logic can be placed in the controller. In a larger system with a lot of things to do, you can break it down into modules. The controller is the entry point for events and the only mediator between the view and the data.

PenguinView cares about DOM. DOM is the browser API you use to perform HTML operations. In MVC, no part of the change DOM except the view. The view can attach user events, but leave the event handling issue to the controller. The main command of the view is to change the status the user sees on the screen. For this demonstration, the view will use pure JavaScript for DOM operations.

PenguinModel cares about data. In client JavaScript, this means Ajax. One advantage of MVC pattern is that you now have a single location for server-side Ajax calls. This makes it easier for other programmers who are not familiar with the solution to get started. The model in this design pattern only cares about JSON or objects from the server.

One anti-pattern is against this inherent separation of concerns. For example, the model must not care about HTML. Views must not care about Ajax. The controller must act as a mediator without worrying about implementation details.

I find that when using this pattern, developers initially have good intentions, but they will leak their concerns. It's tempting to turn everything into a web component and end up in a mess. Focus on features and user-oriented concerns. However, functional concerns are different from functional concerns.

What I like in programming is to make a clear separation of functional concerns. Each individual programming problem will be solved consistently. This makes it easier to understand when you read the code. The purpose is to write easy-to-understand code so that others can also make positive contributions.

It's not a good demonstration without a real example that you can see and touch. So, needless to say, here is a CodePen showing the Penguin's demonstration:

View SitePoint (@SitePoint) Pen A Demo of Penguins on CodePen.

I have said enough, it’s time to write code.

Controller

View and model are two components used by the controller. The controller contains all the components needed to do the job in its constructor:

<code>var PenguinController = function PenguinController(penguinView, penguinModel) {
  this.penguinView = penguinView;
  this.penguinModel = penguinModel;
};
</code>
Copy after login
Copy after login

The constructor uses control inversion and injects the module in this way. This mode allows you to inject any component that satisfies the advanced contract. Think of it as a great way to abstract code and implement details. This pattern allows you to write clean code in pure JavaScript.

The user events are then connected and handled in this way:

<code>PenguinController.prototype.initialize = function initialize() {
  this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this);
};

PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) {
  var target = e.currentTarget;
  var index = parseInt(target.dataset.penguinIndex, 10);

  this.penguinModel.getPenguin(index, this.showPenguin.bind(this));
};
</code>
Copy after login
Copy after login

Note that this event uses the current target to get the state stored in the DOM. In this case, the DOM will tell you all about its current status. The current status of the DOM is what the user sees on the browser. You can store the status data in the DOM itself, as long as the controller does not change the status.

After the event is triggered, the controller will get the data and explain what will happen next. This.showPenguin() callback is interesting:

<code>PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) {
  var penguinViewModel = {
    name: penguinModelData.name,
    imageUrl: penguinModelData.imageUrl,
    size: penguinModelData.size,
    favoriteFood: penguinModelData.favoriteFood
  };

  penguinViewModel.previousIndex = penguinModelData.index - 1;
  penguinViewModel.nextIndex = penguinModelData.index + 1;

  if (penguinModelData.index === 0) {
    penguinViewModel.previousIndex = penguinModelData.count - 1;
  }

  if (penguinModelData.index === penguinModelData.count - 1) {
    penguinViewModel.nextIndex = 0;
  }

  this.penguinView.render(penguinViewModel);
};
</code>
Copy after login
Copy after login

The controller calculates the index of each penguin and tells the view to render it. It takes data from the model and converts it into objects that the view understands and cares about.

The following is a unit test showing the happy path when penguin:

<code>var PenguinController = function PenguinController(penguinView, penguinModel) {
  this.penguinView = penguinView;
  this.penguinModel = penguinModel;
};
</code>
Copy after login
Copy after login

PenguinViewMock has the same contract as real implementation. This makes it possible to write unit tests and make assertions. Assertions come from Node assertions and also exist in Chai assertions. This allows you to write tests that can run on Node and on your browser.

Please note that the controller does not care about implementation details. It uses the contract provided by the view, such as this.render(). That's the discipline required to write clean code. The controller can trust that every component can do what it says. This increases transparency and makes the code easier to read.

View

View only cares about DOM elements and connection events, for example:

<code>PenguinController.prototype.initialize = function initialize() {
  this.penguinView.onClickGetPenguin = this.onClickGetPenguin.bind(this);
};

PenguinController.prototype.onClickGetPenguin = function onClickGetPenguin(e) {
  var target = e.currentTarget;
  var index = parseInt(target.dataset.penguinIndex, 10);

  this.penguinModel.getPenguin(index, this.showPenguin.bind(this));
};
</code>
Copy after login
Copy after login

When it changes the status seen by the user, the implementation is as follows:

<code>PenguinController.prototype.showPenguin = function showPenguin(penguinModelData) {
  var penguinViewModel = {
    name: penguinModelData.name,
    imageUrl: penguinModelData.imageUrl,
    size: penguinModelData.size,
    favoriteFood: penguinModelData.favoriteFood
  };

  penguinViewModel.previousIndex = penguinModelData.index - 1;
  penguinViewModel.nextIndex = penguinModelData.index + 1;

  if (penguinModelData.index === 0) {
    penguinViewModel.previousIndex = penguinModelData.count - 1;
  }

  if (penguinModelData.index === penguinModelData.count - 1) {
    penguinViewModel.nextIndex = 0;
  }

  this.penguinView.render(penguinViewModel);
};
</code>
Copy after login
Copy after login

Note that its main concern is converting view model data to HTML and changing the state. The second is to connect the click event and let the controller act as an entry point. After the status changes, the event handler is attached to the DOM. This technology handles event management at one time.

To test this, we can verify that the element has been updated and changed the status:

<code>var PenguinViewMock = function PenguinViewMock() {
  this.calledRenderWith = null;
};

PenguinViewMock.prototype.render = function render(penguinViewModel) {
  this.calledRenderWith = penguinViewModel;
};

// Arrange
var penguinViewMock = new PenguinViewMock();

var controller = new PenguinController(penguinViewMock, null);

var penguinModelData = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrapl.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  index: 2,
  count: 5
};

// Act
controller.showPenguin(penguinModelData);

// Assert
assert.strictEqual(penguinViewMock.calledRenderWith.name, 'Chinstrap');
assert.strictEqual(penguinViewMock.calledRenderWith.imageUrl, 'http://chinstrapl.jpg');
assert.strictEqual(penguinViewMock.calledRenderWith.size, '5.0kg (m), 4.8kg (f)');
assert.strictEqual(penguinViewMock.calledRenderWith.favoriteFood, 'krill');
assert.strictEqual(penguinViewMock.calledRenderWith.previousIndex, 1);
assert.strictEqual(penguinViewMock.calledRenderWith.nextIndex, 3);
</code>
Copy after login

This solves all major issues, state changes and connection events. But where does the data come from?

Model

In MVC, all models care about Ajax. For example:

<code>var PenguinView = function PenguinView(element) {
  this.element = element;

  this.onClickGetPenguin = null;
};
</code>
Copy after login

Note that the module XMLHttpRequest is injected into the constructor. This is a way to let other programmers know what components this model needs. If the model needs more than simple Ajax, you can use more modules to represent this. Also, using unit testing, I can inject mocks that have exactly the same contract as the original module.

It's time to get the penguin based on the index:

<code>PenguinView.prototype.render = function render(viewModel) {
  this.element.innerHTML = '<h3>' + viewModel.name + '</h3>' +
    '<img alt="' + viewModel.name + '" src="'%20+%20viewModel.imageUrl%20+%0A%20%20%20%20%20%20'">' +
    '<p><b>Size:</b> ' + viewModel.size + '</p>' +
    '<p><b>Favorite food:</b> ' + viewModel.favoriteFood + '</p>' +
    '<a href="https://www.php.cn/link/f0b875eb6cff6fd5f491e6b6521c7510">      ' data-penguin-index="' + viewModel.previousIndex + '">Previous</a> ' +
    '<a href="https://www.php.cn/link/f0b875eb6cff6fd5f491e6b6521c7510">      ' data-penguin-index="' + viewModel.nextIndex + '">Next</a>';

  this.previousIndex = viewModel.previousIndex;
  this.nextIndex = viewModel.nextIndex;

  // Wire up click events, and let the controller handle events
  var previousPenguin = this.element.querySelector('#previousPenguin');
  previousPenguin.addEventListener('click', this.onClickGetPenguin);

  var nextPenguin = this.element.querySelector('#nextPenguin');
  nextPenguin.addEventListener('click', this.onClickGetPenguin);
  nextPenguin.focus();
};
</code>
Copy after login

This points to an endpoint and gets data from the server. We can test this by mocking data with unit tests:

<code>var ElementMock = function ElementMock() {
  this.innerHTML = null;
};

// Stub functions, so we can pass the test
ElementMock.prototype.querySelector = function querySelector() { };
ElementMock.prototype.addEventListener = function addEventListener() { };
ElementMock.prototype.focus = function focus() { };

// Arrange
var elementMock = new ElementMock();

var view = new PenguinView(elementMock);

var viewModel = {
  name: 'Chinstrap',
  imageUrl: 'http://chinstrap1.jpg',
  size: '5.0kg (m), 4.8kg (f)',
  favoriteFood: 'krill',
  previousIndex: 1,
  nextIndex: 2
};

// Act
view.render(viewModel);

// Assert
assert(elementMock.innerHTML.indexOf(viewModel.name) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.imageUrl) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.size) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.favoriteFood) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.previousIndex) > 0);
assert(elementMock.innerHTML.indexOf(viewModel.nextIndex) > 0);
</code>
Copy after login

As you can see, the model cares only about the original data. This means using Ajax and JavaScript objects. If you are not aware of Ajax in pure JavaScript, there is an article with more information.

Unit Test

For any discipline, it is important to obtain guaranteed work. The MVC design pattern does not stipulate how to solve the problem. Design patterns provide you with a broad set of boundaries that enable you to write clean code. This saves you from dependence. For me, this means providing a complete set of unit tests for each use case. Testing provides guidance on how the code can be useful. This makes it open and tempting to any programmer who wants to make a specific change.

Feel free to view the entire set of unit tests. I think this will help you understand this design pattern. Each test is targeted at a specific use case; consider it as a fine-grained focus. Unit testing helps you independently consider each coding problem and solve it. This separation of functional concerns in MVC is reflected in every unit test.

Looking forward

Penguin's demonstration only includes basic feasible concepts that show how useful MVC is. However, you can iterate over many improvements:

  • Add a screen that displays all lists of penguins
  • Add keyboard events so you can flip through the penguin, and add a swipe function
  • An SVG chart to visualize data, select any data point, such as the size of a penguin

Of course, my readers, you can further improve this demo. These are just some ideas that you can showcase the power of this design pattern.

Conclusion

I hope you can see where MVC design patterns and a little discipline can take you. A good design pattern will facilitate writing clean code without getting in the way. It will keep you focused on the task when solving the problem at hand. It will make you a better and more efficient programmer.

In programming, the purpose is to focus closely on the issues at hand while eliminating redundancy. The art of programming is to solve one problem at a time. In MVC, this means solving a functional problem one at a time.

As a developer, it's easy to believe that you are logical and don't deal with emotions. The truth is, you get frustrated when you have too many problems at once. This is the normal human reaction we all have to deal with. In fact, frustration can negatively affect code quality. When this feeling captures you and leads your work, it’s no longer about logic. This can be frustrating as solutions take more risks and complex dependencies.

What I like is focusing on a single focus. Solve one problem at a time and get positive feedback. This way, you can stay focused, efficient and avoid meaningless things.

This article was peer-reviewed by Vildan Softic. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!

Frequently Asked Questions about JavaScript MVC Design Patterns

What is the significance of JavaScript MVC design pattern?

Model-View-Controller (MVC) design pattern in JavaScript is crucial because it helps organize code in a concise and systematic way. It divides the application's focus into three interrelated components. The model processes data and business logic, the view manages the display of data, and the controller processes user input. This separation allows for efficient code management, easier debugging and improved scalability.

How does MVC pattern improve the readability and maintainability of the code?

MVC mode enhances the readability and maintainability of the code by isolating responsibilities. Each component of the MVC pattern has a different function. This separation means that developers can handle individual components without affecting others. It also makes it easier to find and fix errors, update features, or refactor code, because changes in one component do not affect others.

Can you explain the role of the model in the MVC pattern?

The model in the MVC pattern is responsible for managing data and business logic. It retrieves data from the database, operates on data and updates data. The model is independent of the user interface and does not interact directly with the view or controller. Instead, it sends notifications to them when its state changes.

What is the function of the view in MVC mode?

Views in MVC mode are responsible for displaying data to users. It receives data from the model and presents the data in a user-friendly format. The view does not interact directly with the model. Instead, it receives updates from the controller.

How does the controller promote MVC mode?

The controller in MVC mode acts as a mediator between the model and the view. It processes user input and updates the model and view accordingly. When the user interacts with the view, the controller interprets the input and makes necessary changes to the model. It also updates the view to reflect these changes.

How does MVC mode enhance scalability?

MVC mode enhances scalability by separating focus points. This separation allows developers to modify or extend one component without affecting others. For example, if you need to change how your data is displayed, you can modify the view without touching the model or the controller. This modularity makes it easier to scale and grow your application over time.

Can MVC pattern be used with other JavaScript frameworks?

Yes, the MVC pattern can be used with various JavaScript frameworks such as AngularJS, Ember.js, and Backbone.js. These frameworks provide a structured approach to implementing the MVC pattern, making it easier to build complex applications.

What are the challenges of implementing MVC pattern in JavaScript?

Implementing the MVC pattern in JavaScript can be challenging due to the dynamic nature of JavaScript. It requires a good understanding of the language and requires careful planning to ensure that the model, view and controller are properly separated and interact correctly. Additionally, managing updates between these components can be complicated.

How does the MVC model support team development?

MVC mode supports team development by allowing different developers to handle different components simultaneously. For example, one developer can handle the model, while another developer can handle the view. This separation of concerns not only increases productivity, but also reduces the possibility of conflicts or errors due to code overlap.

Can MVC mode be used to develop mobile applications?

Yes, MVC mode can be used to develop mobile applications. It provides a structured approach to application development that makes managing complex mobile applications easier. Many popular mobile development frameworks, such as React Native and Ionic, support the MVC mode.

The above is the detailed content of The MVC Design Pattern in Vanilla JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template