Get started with 33% off your first certification using code: 33OFFNEW

Roll your own basic MVC framework in JavaScript

5 min read
Published on 13th June 2024

Building your own MVC (Model-View-Controller) framework in JavaScript can be a rewarding exercise. It helps you understand the core principles of web frameworks and gives you control over your application's architecture. In this article, we'll create a basic MVC framework from scratch using JavaScript. This will involve setting up models, views, and controllers, and understanding how they interact with each other.

Introduction to MVC Architecture

The MVC pattern is a design pattern used to separate concerns in web applications. It divides the application into three interconnected components:

  • Model: Manages the data and business logic.
  • View: Handles the presentation of data.
  • Controller: Manages user input and updates the model and view accordingly.

This separation allows for more modular and maintainable code.

Setting Up the Project

First, let's set up our project structure. Create a directory for your project and initialize it with the following structure:

mvc-framework/
|-- index.html
|-- css/
|   |-- styles.css
|-- js/
|   |-- model.js
|   |-- view.js
|   |-- controller.js
|   |-- app.js
  • index.html: The main HTML file.
  • css/styles.css: CSS for styling the application.
  • js/model.js: Contains the model logic.
  • js/view.js: Contains the view logic.
  • js/controller.js: Contains the controller logic.
  • js/app.js: Initializes the application.

Creating the Model

The model will handle our application's data. For simplicity, let's create a model that manages a list of items.

  1. Define the Model: Open js/model.js and add the following code:

    class Model {
        constructor() {
            this.items = JSON.parse(localStorage.getItem('items')) || [];
        }
    
        addItem(item) {
            this.items.push(item);
            this._commit(this.items);
        }
    
        deleteItem(index) {
            this.items.splice(index, 1);
            this._commit(this.items);
        }
    
        _commit(items) {
            localStorage.setItem('items', JSON.stringify(items));
            this.onChange(items);
        }
    
        bindOnChange(callback) {
            this.onChange = callback;
        }
    }
    

Creating the View

The view will manage the presentation of our data. We'll create methods to display the list of items and handle user input.

  1. Define the View: Open js/view.js and add the following code:

    class View {
        constructor() {
            this.app = this.getElement('#root');
    
            this.form = this.createElement('form');
            this.input = this.createElement('input');
            this.input.type = 'text';
            this.input.placeholder = 'Add an item';
            this.input.name = 'item';
            this.submitButton = this.createElement('button');
            this.submitButton.textContent = 'Submit';
    
            this.form.append(this.input, this.submitButton);
    
            this.itemList = this.createElement('ul', 'item-list');
    
            this.app.append(this.form, this.itemList);
    
            this._temporaryItemText = '';
            this._initLocalListeners();
        }
    
        get _itemText() {
            return this.input.value;
        }
    
        _resetInput() {
            this.input.value = '';
        }
    
        createElement(tag, className) {
            const element = document.createElement(tag);
            if (className) element.classList.add(className);
            return element;
        }
    
        getElement(selector) {
            return document.querySelector(selector);
        }
    
        displayItems(items) {
            while (this.itemList.firstChild) {
                this.itemList.removeChild(this.itemList.firstChild);
            }
    
            items.forEach((item, index) => {
                const li = this.createElement('li');
                li.id = index;
    
                const span = this.createElement('span');
                span.textContent = item;
    
                const deleteButton = this.createElement('button', 'delete');
                deleteButton.textContent = 'Delete';
                deleteButton.addEventListener('click', () => {
                    this.onDeleteItem(index);
                });
    
                li.append(span, deleteButton);
    
                this.itemList.append(li);
            });
        }
    
        _initLocalListeners() {
            this.itemList.addEventListener('input', event => {
                if (event.target.className === 'editable') {
                    this._temporaryItemText = event.target.innerText;
                }
            });
        }
    
        bindAddItem(handler) {
            this.form.addEventListener('submit', event => {
                event.preventDefault();
    
                if (this._itemText) {
                    handler(this._itemText);
                    this._resetInput();
                }
            });
        }
    
        bindDeleteItem(handler) {
            this.onDeleteItem = handler;
        }
    }
    

Creating the Controller

The controller will manage the interaction between the model and view.

  1. Define the Controller: Open js/controller.js and add the following code:

    class Controller {
        constructor(model, view) {
            this.model = model;
            this.view = view;
    
            this.model.bindOnChange(this.onModelChange);
            this.view.bindAddItem(this.handleAddItem);
            this.view.bindDeleteItem(this.handleDeleteItem);
    
            this.onModelChange(this.model.items);
        }
    
        onModelChange = items => {
            this.view.displayItems(items);
        };
    
        handleAddItem = item => {
            this.model.addItem(item);
        };
    
        handleDeleteItem = index => {
            this.model.deleteItem(index);
        };
    }
    

Connecting the MVC Components

Now we need to initialize our MVC components and connect them together.

  1. Initialize Components: Open js/app.js and add the following code:

    document.addEventListener('DOMContentLoaded', () => {
        const app = new Controller(new Model(), new View());
    });
    

Adding Event Handling

We’ve already added event handling for adding and deleting items in the view and controller. Let's review and ensure everything is correctly wired up.

  1. View Event Handlers:

    • bindAddItem: Handles the form submission and adds a new item.
    • bindDeleteItem: Handles the deletion of an item when the delete button is clicked.
    bindAddItem(handler) {
        this.form.addEventListener('submit', event => {
            event.preventDefault();
    
            if (this._itemText) {
                handler(this._itemText);
                this._resetInput();
            }
        });
    }
    
    bindDeleteItem(handler) {
        this.onDeleteItem = handler;
    }
    
  2. Controller Event Handlers:

    • handleAddItem: Adds a new item to the model.
    • handleDeleteItem: Deletes an item from the model.
    handleAddItem = item => {
        this.model.addItem(item);
    };
    
    handleDeleteItem = index => {
        this.model.deleteItem(index);
    };
    

Finalizing and Testing

With everything in place, let's finalize and test our application.

  1. HTML Structure: Open index.html and add the following code:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>MVC Framework</title>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <div id="root"></div>
        <script src="js/model.js"></script>
        <script src="js/view.js"></script>
        <script src="js/controller.js"></script>
        <script src="js/app.js"></script>
    </body>
    </html>
    
  2. Styling: Open css/styles.css and add some basic styles:

    body {
        font-family: Arial, sans-serif;
    }
    
    .item-list {
        list-style-type: none;
        padding: 0;
    }
    
    .item-list li {
        display: flex;
        justify-content: space-between;
        padding: 5px;
        border-bottom: 1px solid #ccc;
    }
    
    .item-list li span {
        flex-grow: 1;
    }
    
    .delete {
        background-color: red;
        color: white;
        border: none;
        padding: 5px;
        cursor: pointer;
    }
    
  3. Testing: Open your index.html file in a web browser and test the functionality of adding and deleting items. Make sure everything works as expected.

Conclusion

Building your own MVC framework in JavaScript is a great way to understand the fundamental principles of this popular architectural pattern. By separating concerns into models, views, and controllers, you can create a more modular and maintainable application. This tutorial provided a basic implementation, but you can expand upon it by adding features such as validation, more complex data manipulation, and routing.

This exercise not only helps you understand how MVC frameworks work but also gives you a solid foundation for using more complex frameworks and libraries in the future.