Thunder Client

The Thunder client is a single page application written entirely in javascript. The source files are located in the /client folder of the repository.

Editing

The application is packaged with Browserify using gulp. In order to make changes to the client, gulp needs to be running. Gulp will automatically re-bundle the application when changes are made.

npm i

from the root directory to install necessary dependencies, and then

npx gulp

will keep gulp running and watching the files for changes.

Flow

Index

The entry point of the application is /client/js/index.js. It creates and appends Main

Main

Bit of a mess. Main is in charge of:

  • kicking off the initial business logic
  • showing the login dialog
  • handling logout
  • Appending various UI divs (Console, Bar, Notification, Alert),
  • managing opening and closing cards and their layout

Router

A much bigger mess. Router is a singleton who's main function .route() receives a string and passes a card to Main for display.

  • establishes all of the routes, automatically adding /client/js/cards
  • passes the active inmate id to cards when appropriate, or parameters. e.g. edit user zeus passes "zeus" to the editUser.js card
  • provides routes for the directory card

Cards

Almost all functionality in thunder is presented to the user in a card.

Unfortunately there is a lot of boilerplate for cards. An example card looks like this:

var Diver = require('diver');
var ViewHelper = require('viewHelper');
var Events = require('events');
var Remote = require('remote');
var Main = require('main');

module.exports = function(id){

    var _this = this;
    Events.help(this);
    
    Diver
     .a('new-card')
        .b('body')
            .c('header')
                .d('title').html('New Card')
                .d('close')
            .c('content')
                .d('hello').html('Hello World')
            .c('bottom')
                .d('enter').html('Submit');
                
    var d = _this.div = Diver.pop();
    
    d.enter.on('click', function(){
        Remote.post('/not/a/real/api/call', {}, function(){
            Notification.success('Everything worked.');
            Main.closeView(_this);
        })
    })
    
    _this.layout = function(){
    
        var s = ViewHelper.style(_this);
        
        d.hello.style({
            text_align: 'center'
        })
        
        d.enter.style(s.button, {
            margin_left: s.secondLeft,
            margin_top: s.margin
        })
    }
    
    ViewHelper.help(_this);

}
  • It's important that .div is set, otherwise nothing will be added to the screen
  • Remote is used to communicate with the API
  • ViewHelper.help will automatically add functionality to the X close button
  • ViewHelper.style gives everything the default style, and exposes values in the s style variable
  • Notification is a singleton for showing notifications at the top of the screen
  • Diver is for DOM expression.

Automatically required and registered

Any js file in the /client/js/cards folder will be automatically included in the application bundle and can be accessed by require('cards/cardName'). It's important that a card is named in camelCase so that the in-application router registers it correctly. newCard.js will be available at the new card address when using the ~ console.

Forms

Forms exist as json objects served by the backend. They can be accessed and displayed with:

var Form = require('form');
var Remote = require('remote');

function(){

    var _formInstance;

    Remote.getForm('form_title', function (form) {
        _formInstance = new Form.instance(form);
        _this.div.append(formInstance.div);
        _this.layout();
    }
    
    //some time later
    setTimeout(function(){
        var formValues = _formInstance.report();
        // probably send the form data to the server
    }, 10000)
    
    _this.layout = function(){
        _formInstance.layout();
    }
}
  • The server first looks for a form as a .json file in /forms
  • If it doesn't find a file of that name, it looks in /forms/forms.json

A simple form might look like this:

{
    "title": 'example_form',
    "columns": "1"
    "labels": "beside",
    "labelRatio": 0.4, //how wide the label column is
    "components": [
        {
            "type": "input",
            "key": "key_in_database_eventually",
            "label": "Simple input"
        }
    ]
}

Forms get complicated when they become nested (a form can exist as a component of another form).