PBS 171 of X: MVC in XKpasswd-js
As explained in the previous instalment the new XKPasswd-js code uses the Model View Controller (MVC) pattern to structure the code. This instalment will explain this in more detail and also some of the choices why this was done.
Matching Podcast Episode
You can also Download the MP3
Read an unedited, auto-generated transcript with chapter marks: PBS_2024_09_28
A Little History
In the very first web version of XKPasswd-js, the UI was 99% static HTML. Life was easy because the only action needed was the push of the Generate button. It took only a little bit of code to add the generated passwords to the text area that was already created in the HTML, with an id
set for easier reference.
Thanks to the use of Bootstrap version 5, there was already a dropdown menu, a responsive hamburger menu, and an accordion to hold the configuration information. All these elements contained placeholders but all were already fully functional. The focus was on getting the actual password generation code to work.
Slowly, over time the placeholders were replaced by working code and the main file, index.mjs
, grew bigger and became more complex. It was time to break the code up into multiple classes. There are many reasons to break up a complex file.
When a file has hundreds of lines and you need to refer to a different part of the file, you are either scrolling up and down through the file, inevitably losing your editing spot or you need an editor that can display different locations of a single file side by side.
Oftentimes, in such a file, you tend to create long functions that perform long and complex tasks. Before you know it, you even need to scroll within a function to reference some code.
Put this code aside for a few weeks and by the time you get back to it, you’ll be scratching your head about where to begin adding new features or fixing bugs.
When you get tired of scrolling and of losing hair from scratching you will start to break up long functions into shorter ones, but then you also need a kind of table of contents of all the functions you’ve created, because there will be many.
Sure, any good IDE, e.g. VSCode, will provide this functionality but scrolling is now replaced by jumping around using the table of contents. By the way, the IDE will usually call such a table of contents an overview.
Another reason for breaking up a long and complex file is unit testing. A unit test is, as the name implies, a test that checks the functionality of a single unit. A unit in code is a function or method. It is very difficult to write unit tests for complex functions that call other functions you also need to test. This means it becomes very difficult to test the various functions in isolation.
It also becomes almost impossible to unit test a function if that function also manipulates the UI. For example, one function handles the button press, generates the passwords, AND updates the text area with the passwords.
Time for the implementation of the Model View Controller or MVC pattern.
Layout of the project
Before we dive into the MVC pattern, let’s step back and review the project structure.
In the diagram, you can see the structure of the project. The source code lives in the src
directory. The scripts, defined in package.json
, generate a web app from the source code and place it in the dist
directory.
The other directories contain documentation and diagrams. These provide additional information to the developers, but they are not the focus of this instalment. So, running the application is done from the dist
directory, and editing the application’s source code is done in the src
directory.
The src
directory contains several directories and files. We will only focus on the files and directories that are relevant to the source code. The files in the src
directory that we’re concerned with for the source code are the index.html
file and the index.mjs
file. The index.html
file contains the code for the UI of the application and the index.mjs
file contains the Javascript code that serves as the entry point to our app.
Also in the src
directory is the lib
directory which contains the classes for the actual password generator. These are the classes that were ported from Perl to Javascript. Each class lives in its own file with the same name. For example, the XKPasswd
class can be found in the xkpasswd.mjs
file. The convention here is to name the file after the class name but in lowercase with an extension of mjs
to indicate it’s an ES6 class. As per Jest conventions, the file that contains the tests for the XKPasswd
class is called xkpasswd.test.mjs
and can be found in the same directory.
The web
directory contains the classes that drive the UI of the application.
When you start the application with the commandline command npm start
the HTML in the index.html
file is loaded which in turn loads the Javascript code in the index.mjs
file.
Note, the command actually starts the code in the dist
directory, but this is merely a bundled version of the code in the src
directory.
The code in the index.mjs
imports all relevant external code such as Bootstrap and the web font we use, but also the local CSS and all the classes in the lib
and web
directories.
There is one global object, the XKP
object. This object serves as a wrapper around all the code to prevent it from being in the global namespace. It contains a single function init
that initializes the web
and lib
classes.
Without going into much detail, that will be explained later, you can see the code in the code for the init
function how all classes are initialized.
init: () => {
// setup variables for key parts of the website
XKP.xkpasswd = new XKPasswd();
XKP.passwordController =
new PasswordController(XKP.xkpasswd, new PasswordView());
XKP.settingsController =
new SettingsController(XKP.xkpasswd, new SettingsView());
XKP.configController =
new ConfigController(XKP.xkpasswd, new ConfigView(),
XKP.settingsController);
// Set the configController in the settingsController
// because it's reciprocal.
XKP.settingsController.setConfigController(XKP.configController);
XKP.presetController =
new PresetController(XKP.xkpasswd, new PresetView(),
XKP.settingsController,
XKP.passwordController);
}
Finally, a Document Ready handler is set up using jQuery which executes the XKP.init
function. This bootstraps the application.
The MVC pattern
Usually, the MVC pattern leads to a trio of classes: the Model class, the View and the Controller class. Together they take care of some aspect of the application or of the entire functionality of the application, depending on the size and complexity of the application.
In XKPasswd-js there is just one Model, but multiple Controller and View pairs.
A Model and a Gateway design pattern
The heart of the application, aka the business logic aka the reason for its existence, is the port from the old Perl library to Javascript. As we learned last time, this is the Model in the MVC pattern. In the diagram above you see the Model depicted as a cylinder shape in the center of the diagram.
Before we go on, we need to take a side step.
The Model is actually not one class, but all the classes in the src/lib
directory together.
There is one class, XKPasswd
, that serves as the entrance to the other classes. This means that the other classes in this directory are only accessed by the XKPasswd
class and all requests for the functionality of the other classes have to go to the XKPasswd
class. In fact, the content of thelib
directory becomes a black box to the rest of the code with only one door, the XKPasswd
class. This is also a design pattern, like the MVC pattern, and this pattern is called the Gateway pattern.
This Gateway setup allows us to take contents of the lib
directory any time in the future and create a command line application out of it by simply adding one or more classes that become the command line ‘view’ the user interacts with.
This Gateway setup also allows us to keep the Model and various Views (both web and command line) in one git repository and still build various versions of the application.
Finally, because the classes in the lib
directory only deal with the business logic, it is very easy to test them with unit tests. Writing tests for UI manipulation is the hardest part of testing. By separating the Model from the View and the Controller it becomes much easier to write tests for the Model.
So back to our diagram, the cylinder represents all of the classes in the lib
directory with the XKPasswd
class as the Gateway.
Due to this setup, there is just one Model that is used by all the Controllers.
Views and Controllers
Rule of thumb for the UI of XKPasswd is if something can be done in static HTML, it will be added to index.html. This reduces the Javascript part of the view to change visibility and CSS classes of existing objects and to fill or empty the input fields. These functions are usually so trivial they don’t need any unit tests, so fewer unit tests to write. All the Views and Controllers are in the src/web
directory.
XKPasswd has two main areas in the UI: the part where the configuration can be changed and the part where the passwords are generated and displayed along with the statistics. The configuration part can be separated into the preset part and the individual settings part.
The Preset part is very straightforward. It displays the available presets along with the description of that preset. The selected preset is displayed in the header of the box.
The Password part is also quite simple. It handles the input of the number of passwords and displays the generated passwords and the statistics. Finally, it handles the copy actions of the passwords and the switch between the list and text area.
The most complicated part is Settings, which has a lot of input fields which need to be shown or hidden based on the value of other fields. Settings also has to show error messages when the input is not correct and hide them again when the error is corrected.
To keep the code manageable the code driving the UI was also divided into three parts with a View and a Controller pair for each part.
These View and Controller pairs are visible in the diagram surrounding the Model.
If you look at the screenshot of the UI you will see the Preset part is marked with a green box. The Settings part with a purple box and the Password part with a blue box. These colors match with the colors of the controller/view pairs in the diagram above.
Let’s have a more detailed look into the views and controller classes.
Views
The constructor of the Views classes initialises class variables with jQuery objects of the HTML elements that are relevant to the part they focus on. For example, the PresetView class has variables for the header of the preset box, the element that holds the buttons, and the element that holds the description.
class PresetView {
/**
* @private presetGroup - reference to the HTML preset group
*/
#presetGroup;
/**
* @private presetHeader - reference to the HTML preset header
*/
#presetHeader;
/**
* @private presetDescription - reference to the HTML div that will contain
* the description of the current preset
*/
#presetDescription;
/**
* @constructor
*/
constructor() {
this.#presetGroup = $('#preset-btn-group');
this.#presetHeader = $('#currentPreset');
this.#presetDescription = $('#presetDescription');
// ......
}
// ......
}
These Views classes also have one or more functions with a name that starts with ‘bind’. This function binds an event handler to the relevant HTML elements. It takes one parameter called handle
. This is a reference to a function of the Controller to handle the action that this event initiated. In this project, these functions are called bind functions, because the word ‘bind’ is in the name and it binds an event handler in the View class to a function in the Controller class.
Finally, these Views classes contain helper functions to make showing/hiding of UI parts or other actions easier to call.
Controllers
In the constructor of the Controller classes, several class variables are initialized with references to the Model class (the XKPasswd
class) and the respective View class. If necessary, the Controller also has variables for the other Controllers it needs to address.
class PresetController {
/**
* @private model - reference to preset model
*/
#model;
/**
* @private view - reference to PresetView
*/
#view;
/**
* @private settingsController - reference to SettingsController
*/
#settingsController;
/**
* @private passwordController - reference to PasswordController
*/
#passwordController;
/**
* @constructor
*
* @param {XKPasswd} model - reference to PresetModel
* @param {PresetView} view - reference to PresetView
* @param {SettingsController} sc - reference to SettingsController
* @param {PasswordController} pc - reference to PasswordController
*/
constructor(model, view, sc, pc) {
this.#model = model;
this.#view = view;
this.#settingsController = sc;
this.#passwordController = pc;
// .....
}
// .....
}
The constructor also calls the bind function of the View class and passes its own function that needs to handle the data that is created by the user through the View.
If necessary the Controller class has one or more helper functions that in turn call a helper function in its View class. These Controller helper functions can be called by other Controllers in case the UI needs to change and that change is not the responsibility of the Controller that wants to change the UI. This is visible in the diagram where colored dashed arrows point to other Controllers to call their helper functions.
Example
To get a better idea of how such a bind function works, let’s look at the bind function of the PasswordView class, because that’s the easiest one.
bindGeneratePassword(handle) {
$('form#generatePasswords').on('submit', (e) => {
e.preventDefault();
e.stopPropagation(); // stop the event bubbling
let num = parseInt(this.#numberOfPasswords.val());
if (isNaN(num) || num < 1) {
num = 1;
this.#numberOfPasswords.val(num);
}
handle(num);
});
};
It creates a submit handler for the form with the number of passwords. This submit handler reads the number of passwords, and checks if it’s a positive number. If not, it sets the number to 1 and passes the number to the handle
function.
The PasswordController calls this bindGeneratePassword
function and passes its own generatePasswords
function as a parameter.
this.#view.bindGeneratePassword(this.generatePasswords);
In turn, the generatePasswords
function calls the XKPasswd
class (the Gateway class) that is stored in the #model
variable, to generate the number of passwords given and passes the result on to the PasswordView class to render the passwords. The PasswordView class is stored in the #view
variable. Rendering just means displaying the information in the predefined way.
generatePasswords = (num) => {
// call generatePasswords from library
try {
const passAndStats = this.#model.generatePassword(num);
if (!passAndStats) {
// something went wrong
throw new Error('ERROR - server returned no passwords');
}
this.#view.renderPassword(passAndStats, num);
} catch (error) {
log.error(`Password generation threw an error ${error}`);
this.#view.renderPasswordError('ERROR password generation failed!');
}
};
This looks like a difficult way to perform an action, but by splitting the action over different classes, it becomes easier to change one of them, without affecting the others. E.g. we could totally change the way the number is entered and how the passwords are displayed, but there is no change in the way the PasswordController passes the number onto the XKPasswd
class nor is there any change in the code that generates the passwords.
On the other hand, when we would change the way the Controllers are set up, there would be no change in code in the View class, nor in the XKPasswd
class.
A fourth Controller and View pair
In June of 2024 Luis Tavares, also known as irsheep on GitHub, created a pull request to add a much-requested feature to XKPasswd: a way to store and retrieve the current settings. His implementation was to create an encoded blob that contains the settings and add that as a query parameter to the URL of the XKPasswd website.
You can copy that URL and bookmark it and Luis’ code will decode the blob and fill the settings according to your preferences. He created the element below the Settings section, above the Generate Passwords section. It consists of an input box and a ‘copy link’ button. The URL is automatically updated whenever you change any part of the Settings.
I was implementing a similar feature to import and export configurations as JSON files, and I was debating where to put the code for the View and the Controller actions.
Luis’ code was basically doing the same thing but starting from a different position. He created a Config class, so, why not combine the different ways to save and reuse a specific configuration?
Luis’ code created a Config class that contained the functions to generate, parse, code, and decode the URL. These functions were called by various functions in the SettingsController class. When trying to add the import/export functionality the code became very difficult to maintain so it became obvious these features had to be taken out of the SettingsController and SettingsView and rearranged to create a ConfigView and a ConfigController class.
This change contained all the code handling Luis’ functionality as well as my code for the import/export of configuration files, in a separate Controller/View pair. The only impact on the code of the SettingsController was adding a single line in the saveSettings
and the updateSettings
functions to call the ConfigController to update the settings link. None of the other classes needed changes.
It doesn’t matter that the import and export of the JSON files are in the menu and the settings link is under the Settings box. It shows that a View can cover any or multiple parts of the UI.
In summary, the ConfigController handles the reading and writing of the JSON files with the configuration and the encoding and decoding of the settings link. The ConfigView handles the actual dialog boxes for uploading and saving the JSON files and the visual update of the settings link. The ConfigController also tells the SettingsController to display the custom settings. Finally, it takes care of updating the settings link. So whenever the settings change, the SettingsController calls the ConfigController to update the settings link. In turn, the ConfigController calls the ConfigView to update the url in the input in the UI.
// Update the URL with the encoded settings
this.#config.updateLink(settings);
If you look at the screenshot of the UI you will see two orange boxes around the menu items and the settings link. In the diagram, there is an orange Controller/View pair.
At the time of this episode, the functionality is available on the website, but it hasn’t been tested yet by many people.
Wrap up
In the previous instalment Bart explained what a Model View Controller pattern is. In this episode we walked through the code of the XKPasswd-js application and saw how the code is separated into a single Model that is used by various Controller and View classes. A typical application has multiple MVC patterns, which is also true for the XKPasswd-js.
The MVC pattern is a design pattern. There are many more design patterns and one of them, the Gateway pattern, is used to create a separation between the classes in the lib
directory and the classes that drive the UI in the web
directory.
The MVC pattern allows adding new functionality, in this case, the settings link and the import and export of the configuration files, without much impact on the existing code. This helps in avoiding to introduce side effects in the rest of the code.