{{announcement.body}}
{{announcement.title}}

A Simple JavaScript State Machine

DZone 's Guide to

A Simple JavaScript State Machine

Create clean and robust UIs with a simple state machine.

· Web Dev Zone ·
Free Resource

The benefits of using state machines for building robust UIs is well documented — see, for instance, Edward J. Pring and David Khourshid. Some popular approaches to state machines in JavaScript are described by Krasimir Tsonev.  Some popular JavaScript libraries are jakesgordon/javascript-state-machine and davidkpiano/xstate.

In a previous article, I presented a simple state machine for Java applications. In this article, I apply the same idea to JavaScript UIs. To keep the content brief, I am using jQuery.

Classic Turnstile

The classic "Hello, World" example for state machines is the Turnstile. Application of the proposed approach to the turnstile problem suggests the following steps:

Step 1: Write the state transitions table like:

defaultState

coinEvent

handleCoin()

coinSuccessEvent

coinSuccessState

defaultState

coinEvent

handleCoin()

coinErrorEvent

coinErrorState

coinErrorState

coinEvent

handleCoin()

coinSuccessEvent

coinSuccessState

coinSuccessState

pushEvent

handlePush()

pushSuccessEvent

pushSuccessState


Step 2: Capture the states in a data structure like:

const turnstileStates = {
  defaultState : function() {
    $("#thankyou").hide();
    $("#cointxt").val("");
    $("#push").prop("disabled", true);
    $("#cointxt").prop("disabled", false);
    $("#turnstile_locked").show();
    $("#turnstile_unlocked").hide();
    $("#coinerrmsg").hide();
  },
  coinSuccessState : function() {
    $("#turnstile_locked").hide();
    $("#cointxt").prop("disabled", true);
    $("#push").prop("disabled", false);
    $("#coin").prop("disabled", true);
    $("#turnstile_unlocked").show();
    $("#coinerrmsg").hide();
  },
  coinErrorState : function() {
    $("#thankyou").hide();
    $("#cointxt").prop("disabled", false);
    $("#push").prop("disabled", true);
    $("#turnstile_locked").show();
    $("#coinerrmsg").show();
    $("#turnstile_unlocked").hide();
  },
  pushSuccessState : function() {
    $("#thankyou").show();
    $("#welcome").hide();
    $("#cointxt").prop("disabled", true);
    $("#turnstile_locked").hide();
    $("#coin").prop("disabled", true);
    $("#push").prop("disabled", true);
    $("#turnstile_unlocked").hide();
    $("#coinerrmsg").hide();
  }
};


Note that the function bodies above can be refactored to call a method like renderComponent() with appropriate data parameters. I am using the verbose mode here so the readers can see the concept behind the turnstileStates configuration quickly in one place. Note that in this "Hello, World" style example I am not using any data (model) coming from the server. When we do have such a model from the server, the functions in the turnstileStates structure can have a model argument.

Step 3: Capture the events and event handling like:

const turnstileEvents = {
  coinEvent : {
    handleCoin : function(e) {
      if (e.data.coinval() > 0) {
        return turnstileEvents.coinSuccessEvent;
      } else {
        return turnstileEvents.coinErrorEvent;
      }
    }
    //nextState not needed for this event
  },
  coinSuccessEvent : {
    nextState : function() {
      return turnstileStates.coinSuccessState();
    }
    //no handlers are needed for this event
  },
  coinErrorEvent : {
    nextState : function() {
      return turnstileStates.coinErrorState();
    }
    //no handlers are needed for this event
  },
  pushEvent : {
    handlePush : function() {
      return turnstileEvents.pushSuccessEvent;
    }
    //nextState not needed for this event
  },
  pushSuccessEvent : {
    nextState : function() {
      return turnstileStates.pushSuccessState();
    }
    //no handlers are needed for this event
  }
};

Note that the nextState property is used in the turnstileEvents configuration instead of in the turnstileStates configuration, since we see that in the state transitions table, the post-event dictates what the next state should be. 

Step 4: Orchestrate the states and events in the controller (jQuery body in our case) like:

//handle the page load event
turnstileStates.defaultState();

//handle the coin event
$("#coin").on("click",{ coinval : function(){return $("#cointxt").val();} },function(event) {
  return turnstileEvents.coinEvent.handleCoin(event).nextState();
});

//handle the push event
$("#push").click(function() {
  return turnstileEvents.pushEvent.handlePush().nextState();
});


A live demo of this example is deployed here, where the four state transitions can be tested. The full source for the demo is available on GitHub

Conclusions

It is interesting to note that the approach used for Java applications is equally applicable for JavaScript applications. The difference I see in this approach from the ones mentioned in the introduction is the clean separation of concerns in the three components — states, events/eventhandlers, and the controller. In conclusion, it can be mentioned that the use of a state machine for frontend applications results in clean and robust UIs.

Topics:
javascript, jquery, jquery 3.0, state machine, tutorial, web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}