Wednesday, January 16, 2013

Javascript: Developing with State Machines

Usually when I develop a website, I do a lot server side. Servers are fast, PHP5 has some great Object Oriented qualities, and I can send only exactly as much data to the web browser as I need. That said, it isn't always the optimal way of doing things. Web apps rely on often massive amounts of JavaScript executing in the web browser to provide a smooth and seamless "app-like" experience.

For a recent project, my challenge was to display a list of data fetched from a server. Simple enough, but the first version was very procedural, and thus, difficult to maintain. If I wanted to "edit" an item in the list, it meant retrieving from some HTML element the information about the item, and then getting more information from the server, and kicking off a whole new procedure to create a UI of some sort to allow changes to the data. Unfortunately, ECMA Script, at least, out of the box, doesn't have strong Object Oriented support, so while it might be possible to treat each item as an object that seemed messy and relatively complicated.

Instead, I decided to use a State Machine, a decision that I will take a look at here.

JavaScript and State Machines

First of all, JavaScript does have objects, they're just not the kind of objects you may be familiar with. A JS object is very simply a collection of keyed strings. Even a function in a JS object isn't much more than a string labeled as a function. Tell JS to print out myObject.myFunction, and you'll get the code, tell JS to print out myObject.myFunction() with the parenthesis at the end, and instead you'll get the result of executing the function. This simple object representation is very flexible, but very much unlike the rigid OO structure found in OO langages, by virtue of ECMA Script being a prototype language, not an object oriented one.

Luckily, this simple prototype based model is also great for organizing anything, and it makes it ideal to create a state machine.

State Machines

At the most basic level, a state machine is a programming paradigm where a process is repeated, changing what it does based on the previous result. One way to do this, is to use a switch and a loop. Let's look at a stupid and far too simple function for validating an email address by checking that a string has (text)@(text).(text) .

function stateCheckEmail(emailAddress){ var state = 1; for(var i=0; i<emailAddress.length; i++){ switch(state){ case 1: if(emailAddress[i] != '@'){ state++; }else{ state = 0; } break; case 2: if(emailAddress[i] == '@'){ state++; } break; case 3: if(emailAddress[i] != '@'){ state++; }else{ state = 0; } break; case 4: if(emailAddress[i] == '.'){ state++; } break; case 5: if(emailAddress[i] != '.'){ state++; } break; } } if(state == 6) return true; return false; }
    The state machine, however, isn't enough of a paradigm for an entire project without needing to be expanded in some way.
  1. A state machine at its core is simply a way of saying, "I have an easily accessible variable or object that indicates the current state of my application."
  2. A state machine still requires some programming in the terms of functions.
  3. A state machine is a great concept, but it doesn't inherently provide a way to produce code like, say, an MVC paradigm.
In order to expand the paradigm appropriately, therefore, I started by creating my state machine with three objects. I'll use an example of a To Do list.

First, I need to store the machine state. toDoState will be a simple object with key-value pairs, each key representing a part of the machine state. For example, want to know what items are loaded into your state machine? toDoState.items can hold that information. When you click on one to select it, you need to set the state machine so that it knows, and you can do that by setting toDoState.selected. Determining what goes in to the state object is a bit tricky, but I tried to adhere to certain rules.
  • If the information can be used multiple times without needing to change or be reloaded, you can load it once into the machine state.
  • Any item that can be "viewed" or "selected" should also be here.
  • If something needs to be loaded from the server, do it once, and then only update it when needed.
  • If possible, only update the specific part of the state object that has updated.
Next, I want to separate from the state machine reusable code, functions. To do this, I created a second object to work with the first, which I'll call toDoFunctions. I'll also hold to the same pattern convention as the state object. For example, since this is an AJAX powered list of To Do notes, I may need a function to retrieve the list of notes from the server. To reduce server calls, I want to get this into the state machine. Now, things start to come together. 

toDoState.items = toDoFunctions.getItems();

Now, the items are part of the state of the machine. That might sound odd, but it makes more sense when we consider the third part of the extended paradigm.

When creating web pages, eventually, you need to generate HTML code. One of the biggest problems with creating HTML code on the fly, say, using jQuery, is that modifying that generated code is not nearly as easy as editing it if it's just HTML. This brings in to play the last part of the extended paradigm. This third object, I'll call it the "generator" will take care of producing pieces of HTML code. Each object will read the state of the machine and return the appropriate snippet of code based on the machine state. Now, having the list of items as part of the state makes sense, because if we call a function, say, toDoGenerator.itemsHTML(), it should return the HTML for the items that are in the state machine. This means if you delete an item, you just remove it from the toDoState.items object, call the generator function again, and this time you get back the HTML without the removed item, as it always reflects the current state.

Results

Creating a state machine for web apps with this extended paradigm has proved very useful to me. It makes it easy to manipulate the machine at any point, by simply changing the appropriate value in the states object, and allowing the code to execute as normal. It also makes it clear and concise to generate the HTML required for displaying the web app.

No comments:

Post a Comment