As part of my work with Science Olympiad, I worked on making CodeBusters become a national event for the 2018/2019 season. While one part of the process was to define the rules, the real challenge was making it easy for all of the individual states event supervisors to be able to run the event easily.
In the previous two years of being the event supervisor for North Carolina, I wrote almost two dozen tests by hand using Microsoft Word. In the process we analyzed the testing results and formats as well as elicited feedback from event supervisors who administered the tests. Based on the feedback, I tweaked the test format to make it easier to grade by putting boxes around answer spots using tables as well as standardized the format for presenting the questions so that students could clearly recognize how to approach the problems. However the process was still manual and generating the answer key was a tedious effort to ensure that everything was in sync, especially when the test question order was randomized, and yes I did make a couple of mistakes that we caught at the last minute. Additionally, while there was somewhat of a template to follow, the process was tedious such that it could take several hours to prepare a test with only eight questions, ensuring that everything was correct.
From this experience, it became obvious that automation was required. In order to make it accessible, I needed to build a web application.
Laying out the basic requirements:
- Truly WYSIWYG web pages so that as the user updates the data, the test question is dynamically updated.
- Responsive so that the test could be accessible from a desktop, tablet or mobile phone that students use.
- Ability to generate questions using various cipher types (Aristocrat, Vigenère, Hill, Caesar, etc.).
- Customizing question text and score values for each cipher type.
- Organizing questions into tests.
- Ability to customize test, reorder questions.
- Ability to print the test, answer keys and solutions.
- Secure so that there was no chance of test being leaked or accessed by a student.
- Ability to run the entire site offline for people who had limited internet access (or wanted to create tests while traveling).
In looking at the technology available, I had to make choices in four areas
- Frontend Framework (Bootstrap, Foundation, Semantic UI. etc)
- Controller Framework (e.g. React, Angular, Vue, etc.)
- Backend Framework (Django, Ruby on Rails, Flask, etc.)
- Storage Framework (Server side database, client storage)
I probably spent the most time here going back and forth on the various options. While there are literally dozens of CSS frameworks to choose from, the obvious front runners are Bootstrap, Foundation, and Semantic UI. You can find plenty of articles comparing them (such as the codeburst.io article and stackshare.io summary chart) and I would encourage you to do your own research before making a decision. In my case it came down to mobile support and I found that while Bootstrap certainly has the market share by far, Foundation felt like it had started from a more modern base while Bootstrap was catching up to the modern browsers. Additionally, I found that sites built using Bootstrap all tended to look the same while Foundation seemed to have a more flexible user interface. Given that what I was building didn't look like any of the other sites I have seen, ultimately Foundation seemed to offer the flexibility I needed. When I started, Foundation was working on the version 6 release and I had to deal with a few of the bleeding edge bugs which they ultimately fixed.
This is where the project really diverged. As part of my work at Escape Velocity, we build our application using Angular and I was initially tempted to continue along those lines. I'm a big fan of Typescript and liked how the Angular framework worked. Of course you can't ignore the fact that React is the big powerhouse in terms of usage (there's a good article at TechMagic showing trends), but both of them seemed to not quite match where I was going. I looked at several implementations of Undo/Redo (for example the React Tic Tac Toe Tutorial) and didn't feel that it gave me the flexibility that was going to be needed for the encoders. Ultimately I ended up rolling my own controller framework and I'm happy with how it worked out. Each encoder has its own web page with infrastructure to do dynamic page updates and the Undo/Redo code ended up also working with the Save/Load and even URI parameter parsing.
It is very important to be able to download the entire tool as a bundle. For this reason the web pages had to be plain HTML which could be launched by any browser locally. Additionally, because many of the users who download the tool were not technically savvy, there should be no local install of other software required. I.e. they should be able to put a directory on their machine and double click on the index.html file to be able to do the work with the tool. This immediately eliminated many of the Backend frameworks (Django and Flask need Python, Ruby on Rails needs Ruby and so on). For this reason, any backend framework needed to be compiled down to simple HTML pages.
Here is where there can be a lot of fun. The test has a LOT of diverse but small data associated with it. A typical test may have twenty questions. The most obvious way to store the data is to use a REST interface with a server which stores it into a database. Along with this comes a host (pardon the pun) of challenges - security, user ids, passwords, SQL injection problems. In this case, the size of the data is actually quite small — a typical test is only 14K — which presented a very nice opportunity: Simply store all of the data locally on the user's machine. In this way, nothing is ever sent to a server and all of the other security concerns generally go away. For this, a solution of using LocalStorage when available and Cookies when not available turns out to meet all of the application needs.
Architecture of the Application
The entire application is at https://github.com/toebes/ciphers for anyone to see. The application is built on top of several well known open source components.
- TypeScript (http://www.typescriptlang.org/) – All of the code for processing the page
- JQuery (https://jquery.com/) – For general HTML traversal.
- ZURB Foundation 6 for Sites (https://foundation.zurb.com/sites/docs/) – Provides the CSS and reactive layout engine
- what-input – (https://github.com/ten1seven/what-input) Used by Foundation for tracking the current input method
- WebPack – (https://webpack.js.org/) - JS packager/builder
- shopify/draggable – (https://github.com/Shopify/draggable) Enhancements for drag/drop sorting of lists
- KaTeX – (https://khan.github.io/KaTeX/) - Math Rendering (from LaTeX syntax)
- CKEditor 5 – (https://ckeditor.com/ckeditor-5/) - Rich text editor - Custom inline build at https://github.com/toebes/ckeditor5-build-inline
- Es5-shim – (https://github.com/es-shims/es5-shim) Shim to bring all ECMAScript 5 methods to the browser
- Es6-shim – (https://github.com/es-shims/es6-shim) Shim for making sure all ECMAScript 6 methods are supported
- js-cookie – (https://github.com/js-cookie/js-cookie) Provides access to cookies on browsers which don't support localStorage
- Identify the tools used
- Specify the input starting point for the application (app/codebusters/ciphers.ts)
- Specify the output location (dist) and attributes of the generated file
- Resolve location of imported projects.
- List which types of files are to be combined.
- Describe rules for how specific file types are to be transformed.
- Copy files unchanged using copy-webpack-plugin.
- Inject version numbers into the generated files using webpack-auto-inject-version.
- Generate HTML files from templates (lots of them!) using html-webpack-plugin.
- Provide Aliases for JQuery to be accessible from the debugger.
- Generate the final downloadable Zip using webpack-shell-plugin.