Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

A JavaScript Framework for Robust UI Applications

DZone 's Guide to

A JavaScript Framework for Robust UI Applications

Create robust, dynamic UI with this easy-to-use framework.

· Web Dev Zone ·
Free Resource

person-outlined-by-sunset-jumping-up-mountains

Your end-user after seeing your new UI

In a previous article I presented a simple JavaScript state machine. In this article, I extend the approach to UI applications that are integrated with server models. I have used jQuery to demo the framework and Spring Boot to provide the server API.

You may also like: Developers Guide: How to Build a Dashboard With JavaScript Libraries

Framework Overview

This framework has the following core components:

  • uiParams — that define the view state.
  • <app>States — that are various possible view states.
  • <app>Events — that are various possible native and custom events.
  • stateTransitionsController — that orchestrates various state transitions.

where <app> corresponds to the app name like turnstile, order etc.

The framework suggests configuring the UI application as a series of state transitions with each transition like:

[initial state] -> [native event] -> [handler] -> [custom event] -> [final state]

The initial and final states are configured in <app>States, the native and custom events are configured in <app>Events.

When a native event is triggered, the controller dispatches it to a configured handler, and when the handler completes the execution of the business rule it, in turn, triggers a configured event based on the outcome of the execution.

When a custom event is triggered, the controller transitions the view to a configured final state.

A Sample UI Application

Consider a turnstile UI application where a user enters a coin amount to unlock the turnstile, and if there are no errors, the user can push to gain access. For server model integration, consider the coin amount validation handled by the server and the result returned as a JSON. See the UI design for this example here.

The framework suggests that we first create a state transitions table like:

defaultState coinEvent coinHandler() coinSuccessEvent coinSuccessState

defaultState

coinEvent

coinHandler()

coinErrorEvent coinErrorState

coinErrorState

coinEvent

coinHandler()

coinSuccessEvent

coinSuccessState

coinSuccessState

pushEvent pushHandler() pushSuccessEvent pushEventState

Once the table is correctly constructed, the rest of the steps are straighforward.

The uiProps are configured like:

var uiProps = {
state: "defaultState",
welcomeShow: "block",
turnstileLockedShow: "block",
turnstileUnlockedShow: "none",
coinTxtDisabled: false,
coinTxtValue: "",
coinBtnDisabled: false,
pushBtnDisabled: true,
coinErrorMsgShow: "none",
thankyouShow: "none"
}


The states in the table are configured for the example like:

const turnstileStates = {
defaultState : function(e) {
renderApp(uiProps);
},
coinSuccessState : function(e) {
uiProps.state="coinSuccessState";
uiProps.turnstileLockedShow="none";
uiProps.turnstileUnlockedShow="block";
uiProps.coinTxtDisabled=true;
uiProps.coinTxtValue=e.data.coinval;
uiProps.coinBtnDisabled=true;
uiProps.pushBtnDisabled=false;
uiProps.coinErrorMsgShow="none";
renderApp(uiProps);
},
coinErrorState : function(e) {
uiProps.state="coinErrorState";
uiProps.turnstileLockedShow="block";
uiProps.turnstileUnlockedShow="none";
uiProps.coinTxtDisabled=false;
uiProps.coinTxtValue=e.data.coinval;
uiProps.coinBtnDisabled=false;
uiProps.pushBtnDisabled=true;
uiProps.coinErrorMsgShow="block";
uiProps.coinErrorMsgText=e.data.errorMessage;
renderApp(uiProps);
},
pushSuccessState : function(e) {
uiProps.state="pushSuccessState";
uiProps.welcomeShow="none";
uiProps.thankyouShow="block";
uiProps.turnstileLockedShow="none";
uiProps.turnstileUnlockedShow="none";
uiProps.coinTxtDisabled=true;
uiProps.coinBtnDisabled=true;
uiProps.pushBtnDisabled=true;
uiProps.coinErrorMsgShow="none";
renderApp(uiProps);
}
};


where the utility function renderApp is:

function renderApp(uiProps){
$("#state").text(uiProps.state);
$("#thankyou").css("display", uiProps.thankyouShow)
$("#turnstileLocked").css("display", uiProps.turnstileLockedShow);
$("#turnstileUnlocked").css("display", uiProps.turnstileUnlockedShow);
$("#coinTxt").prop("disabled", uiProps.coinTxtDisabled);
$("#coinTxt").val(uiProps.coinTxtValue);
$("#coinBtn").prop("disabled", uiProps.coinBtnDisabled);
$("#pushBtn").prop("disabled", uiProps.pushBtnDisabled);
$("#coinErrorMsg").css("display", uiProps.coinErrorMsgShow);
$("#coinErrorMsg").text(uiProps.coinErrorMsgText);
}


The events in the table are configured like:

const turnstileEvents = {
coinEvent : {
handleCoin : function(e) {
//call the server api to validate coin amount
var result = null;
isCoinAmountValid(e.data.coinval())
.done(function(serverData){
$("body").triggerHandler("turnstileEvents.coinSuccessEvent");
})
.fail( function(xhr, textStatus, errorThrown) {
if(xhr.status==0)xhr.responseText="Unknown server error, please try later.";
$("body").triggerHandler("turnstileEvents.coinErrorEvent", xhr.responseText);
    });
return result;
}
},
coinSuccessEvent : {
nextState : function(e) {
return turnstileStates.coinSuccessState(e);
}
},
coinErrorEvent : {
nextState : function(e) {
//e.data.errorMessage = 'server sent messge';
return turnstileStates.coinErrorState(e);
}
},
pushEvent : {
handlePush : function(e) {
return turnstileEvents.pushSuccessEvent;

}
},
pushSuccessEvent : {
nextState : function(e) {
return turnstileStates.pushSuccessState(e);
}
}
};


Note that the nextState() is configured in turnstileEvents instead of in the turnstileStates since we see in the state transitions table that the post-event dictates what the next state should be.

Finally, the stateTransitionsController activities are configured in the jQuery body like:

    //handle the page load event
    //initiaize uiData if necessary
    //for the current example there is no server data on page load
turnstileStates.defaultState(event);

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

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

    //handle the custom coinSuccessEvent
    $("body").on("turnstileEvents.coinSuccessEvent", {coinval:function(){return $("#coinTxt").val();}}, function(event){
    return turnstileEvents.coinSuccessEvent.nextState(event);
    });

    //handle the custom coinErrorEvent
    $("body").on("turnstileEvents.coinErrorEvent",{errorMessage:"", coinval:function(){return $("#coinTxt").val();}}, function(event, param){
    event.data.errorMessage=param;
    return turnstileEvents.coinErrorEvent.nextState(event);
    });


Ajax call to the server API:

//use ajax post to the server api /turnstile/payment 
function isCoinAmountValid(coinAmt){
if(coinAmt=='')coinAmt=0.0;
var requestBody = "{ \"payment\":" + coinAmt +"}";
$.ajaxSetup({
   headers:{
      'Content-Type': "application/json"
   }
});
return $.post('http://localhost:8080/turnstile/payment', requestBody)
}


Consider a Spring Boot controller like:

@PostMapping("/turnstile/payment")
public TurnstileData validatePayment(@Valid @RequestBody TurnstileData turnstileData) throws Exception {
  if(turnstileData.getPayment()<=0) {
    throw new BadDataException("Payment value must be more than 0.00");
  }
  return turnstileData;
}


The full source code for the application is on my GitHub.

When the GitHub project is imported into an IDE like STS and run, the state transitions listed above in the table can be tested.

Test #1: On accessing http://localhost:8080/jqStateMachine.html we get the defaultState view like:

Default state of turnstile application

Default size of turnstile application

Test #2: While the defaultState is active, on entering 0 in the textbox and clicking "Drop Coins" button we get the following coinErrorState.

Coin error state as a result of entering zero coins

Coin error state as a result of entering zero coins

Test #3: While the defaultState is active, on entering 86 in the textbox and clicking "Drop Coins" button we get the following coinSuccessState:

Coin success state as a resuly of entering a valid number of coins

Coin success state as a result of entering valid number coins

Test #4: While cointErrorState is active, on entering 86 and clocking the "Drop Coins" button, we get the following coinSuccessState. 

The turnstile is unlocked!

The turnstile is unlocked!

Test #5: While the coinSucessState is active, on clicking the "Push" button, we get the following pushSuccessState. 

Push success state

Push success state


Conclusions

In conclusion, it can be said that the framework steps are quite straightforward once the state transitions table is correctly created. Even though a major change like server integration was added to the application, the complexity of the application increased only marginally with the clean separation of concerns remaining unaffected.


Related Articles

Topics:
javascript ,state machine ,jquery ,ui ,javascript framework ,jquery 3.0 ,spring boot ,tutorial

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}