Skip to content

Model View Controller – Painless switching from Java Swing to modern ES 6 JavaScript

Link to GitHub project: https://github.com/LarryWho11/javascript-mvc.

The majority of software engineering and computer science courses use Java to teach students about coding since the language is purely object-oriented and newcomers will have to learn about organizing code properly, which can be easily overseen when learning a scripting language like Python or JavaScript. The most common design pattern used is MVC aka. Model View Controller, which is a pretty big deal to keep your code clean and nice if you stick to it strictly. Together with some solid Java Swing, you can easily build almost any information retrieval application out there.

After learning Java for about 3 semesters and properly have done half a dozen GUI projects with Java Swing, I tried to take on frontend web development for a while, not too much, just enough to do some prototypes to show people what my ideas could look like. In my experience, vanilla JavaScript in the browser (EMACS 3) can be a bless and curse at the same time.

On one hand, you can get work done very quick with some HTML and DOM manipulation without thinking too much about code architecture. On another hand, you don’t have to care about code architecture to get things done. As soon as your single main.js get bigger than 1000 lines of code, adding any feature could be a pain in the *ss. And not to forget about unit tests, which is virtually impossible with this kind of spaghetti mess.

Since my colleagues, who only know Java Swing have been suffering from JavaScript. I decided to write this tutorial to show that JavaScript can also be coded without too much pain.

This will be a small tutorial about how to organize your code properly in JavaScript for people who are switching from Java Swing to JavaScript. Since I am not a frontend developer, this post will not be an exhausting tutorial. In order to start, I recommend you to use the following tools of my choice:

  • WebStorm, which is a product of JetBrains (the same company which produced Kotlin, IntelliJ, PhpStorm, Pycharm, etc.)
  • NPM, which is basically the equivalent of Java Maven for JavaScript.
  • Parcel (https://github.com/parcel-bundler/parcel), which is a library for converting ES6 JavaScript, which is way too modern for any browser out there, into ES5. Like Java, JavaScript is also evolving at a very fast pace, too fast for any browser to keep up. It, therefore, is easier just to write a transpiler, which compiles modern JS code into browser-compatible code so developers can benefit from the newest standards.

I assume you already have advanced coding experience, therefore I will skip the parts about setting up your project with WebStorm since this tutorial is more about the abstract architecture rather than a coding tutorial (even though we will write some code to demonstrate about things work).

First thing first, this is how our project’s structure should look like for initial:

For this setting, this is how our package.json would look like. package.json is the equivalent of pom.xml if you use maven as your package manager in Java. We will also need three folders inside the project folder. The first is src, where we will store all the production code. The next folder will be public, where our index.html, as well as images and CSS files, will be stored. And the last one will be the folder tests, where all unit tests files will be stored.

{
  "name": "javascript-mvc",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "parcel public/index.html --no-cache",
    "build": "parcel build public/index.html",
    "test": "mocha tests/**/*.spec.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "mocha": "^7.0.0",
    "parcel-bundler": "^1.12.4"
  },
  "dependencies": {
    "uuid": "^3.3.3"
  }
}

As dependencies, we will only use mocha, parcel, and UUID. Mocha is a testing library, we can see it as the equivalent JUnit from Java. UUID will help us to generate a universally unique string that we can use for id generation.

We start by taking a look at the public folder. This directory will have only plain HTML, CSS files, you can decorate your index.html however you want, I will not give any advice since my application doesn’t look very pretty.

For index.html in the public folder, we will have the following code, which will be the user interface for our application:

 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello world</title>
</head>
<body>
<div class="container">
    <div class="data_input">
        <input id="first_name" placeholder="First name" type="text">
        <input id="last_name" placeholder="Last name" type="text">
        <button id="new_student">New Student</button>
        <button id="new_teacher">New Teacher</button>
    </div>
    <div>
        <h1>Student list</h1>
        <ul id="student_list"></ul>
    </div>app.js
    <div>
        <h1>Teacher list</h1>
        <ul id="teacher_list"></ul>
    </div>
</div>
<script src="../src/app.js"></script>
</body>
</html>

For this site to work properly, we will also need the JavaScript file. The entry JavaScript file will be placed in the directory src, which has the following subfolders:

I like to keep one directory for each group of codes. As first, the app.js will import the ListController and initialize one object of this class. This is all that app.js will do.

import {ListController} from './controller/ListController';

let litController = new ListController();

The controller is the most important component since all logic will happen here. That also means that no DOM manipulation should ever take place in this file. As you can see, since we use ES6 as a coding standard, we can use the newest features of JavaScript like class, require, etc… which normally will not run on the browser. This is also very nice for Java developers since the syntax is almost identical to native Java.

const {BinaryTree} = require("../datastructure/BinaryTree");
const {ListView} = require("../view/ListView");
const {Student} = require("../model/Student");
const {Teacher} = require("../model/Teacher");
const uuidv1 = require('uuid/v1');

class ListController {
    constructor() {
        this.listView = new ListView();
        this.studentBinaryTree = new BinaryTree();
        this.teacherBinaryTree = new BinaryTree();
        this.listView.addNewStudentButtonListener(this.addNewStudentButtonListener, this);
        this.listView.addNewTeacherButtonListener(this.addNewTeacherButtonListener, this);
    }

    addNewStudentButtonListener(){
        let new_student_first_name = this.listView.getFirstNameValue();
        let new_student_last_name = this.listView.getLastNameValue();
        let new_student_id = uuidv1();
        let new_student = new Student(new_student_id, new_student_first_name, new_student_last_name);
        this.studentBinaryTree.insert(new_student);
        this.listView.updateStudentList(this.studentBinaryTree.inOrderTraverse());
    }

    addNewTeacherButtonListener(){
        let new_teacher_first_name = this.listView.getLastNameValue();
        let new_teacher_last_name = this.listView.getLastNameValue();
        let new_teacher_id = uuidv1();
        let new_teacher = new Teacher(new_teacher_id, new_teacher_first_name, new_teacher_last_name);
        this.teacherBinaryTree.insert(new_teacher);
        this.listView.updateTeacherList(this.teacherBinaryTree.inOrderTraverse());
    }
}

module.exports = {
    ListController
};

A small notice, this controller will use a binary tree to store objects of Student and Teacher, I will not explain how the implementation works since this is pretty CS101 which everyone has learned in any algorithm class.

And now for DOM manipulation, we will need the View class to be implemented. As you can see, this class contains no logic at all and will only be called from the Controller to pass event listeners as well as retrieving user’s inputs.

class ListView {
    addNewStudentButtonListener(handle, bindObject){
        document.getElementById("new_student").addEventListener('click', handle.bind(bindObject));
    }

    addNewTeacherButtonListener(handle, bindObject){
        document.getElementById("new_teacher").addEventListener('click', handle.bind(bindObject));
    }

    getFirstNameValue(){
        return document.getElementById("first_name").value;
    }

    getLastNameValue(){
        return document.getElementById("last_name").value;
    }

    updateStudentList(studentList){
        this.deleteStudentList();
        let ul = document.getElementById("student_list");
        for(let student of studentList){
            let li = document.createElement("li");
            li.innerHTML = student.first_name;
            ul.appendChild(li);
        }
    }

    deleteStudentList(){
        let ul = document.getElementById("student_list");
        while (ul.firstChild) {
            ul.removeChild(ul.firstChild);
        }
    }

    updateTeacherList(teacherList){
        this.deleteTeacherList();
        let ul = document.getElementById("teacher_list");
        for(let teacher of teacherList){
            let li = document.createElement("li");
            li.innerHTML = teacher.first_name;
            ul.appendChild(li);
        }
    }

    deleteTeacherList(){
        let ul = document.getElementById("teacher_list");
        while (ul.firstChild) {
            ul.removeChild(ul.firstChild);
        }
    }
}

module.exports = {
    ListView
};

Running the npm run dev will compile your code and host the development version under http://localhost:1234 (Yes, I know, the app looks like a donkey *ss, but as I mentioned, this is a tutorial about project architecture)

So far so good, but one more benefit of writing ES6 code is the advantages of unit tests, which could be written to test your logic in separately JS files, like this one to test the functionality of the binary tree implementation:

'use strict';

const Node = require('../src/datastructure/BinaryTree').Node;
const BinaryTree = require('../src/datastructure/BinaryTree').BinaryTree;
const Teacher = require('../src/model/Teacher').Teacher;
const Student = require('../src/model/Student').Student;
const {UserNotFound} = require("../src/exceptions/UserNotFound");
const { AssertionError } = require('assert');
const assert = require('assert');


describe('Student model', function () {
    it('Creating student should be ok', function () {
        const student = new Student("1", "a", "b");
        assert.deepEqual("1", student.id);
        assert.deepEqual("a", student.first_name);
        assert.deepEqual("b", student.family_name);
    });
});

describe('Teacher model', function () {
    it("Creating a teacher should be ok", function () {
        const teacher = new Teacher("1", "a", "b");
        assert.deepEqual("1", teacher.id);
        assert.deepEqual("a", teacher.first_name);
        assert.deepEqual("b", teacher.family_name);
    });
});

In conclusion, MVC can be done in JavaScript without learning a real Frontend Framework like Angular, React or VueJS and you can still use your experience from Java to develop modern web applications.

Published inSoftware Engineer

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *