manage views with css or regions in Backbone Marionette

535 views Asked by At

I am working on a page having lot of input-controls and related divs. There are use-cases on this page where I am suppossed to show/hide the divs depending on the order of user clicking on input-controls in various follow-up screens.

Now the divs are all there in first load itself and by showing/hiding, the screen changes for the user. Now to show/hide I can use css and add view* class to .main content div depending on business logic.

ex.:

.main div{
  display: none;
 }
.main.view1 div.a,.main.view1 div.b,.main.view1 div.f{
  display:block;
 }

.main.view2 div.c,.main.view2 div.f {
  display:block;
 }
.main.view3 div.c,.main.view3 div.f {
  display:block;
 }

....etc

But this way the no. of css classes are getting unmanageable.

Please suggest if there is a better method I can use wherein it becomes easy to manage the user-flows. I think there are regions in marionette which can help me manage this. Please suggest the best way and elaborate if the answer is marionette.regions

1

There are 1 answers

2
Pramod On BEST ANSWER

You can model the application as a state machine to model complicated workflows.

To define a state machine:

  1. Define all the states that your application can be in.
  2. Define the set of actions that are allowed in each state. Each action will transition the state of the application from one state to another.
  3. Write the business logic for each action which includes both persisting changes to the server and also changing the state of the views accordingly.

This design is similar to creating a DFA, but you can add extra behaviour according to your needs.

If this sounds too abstract, here's an example of a simple state machine.

Let's say you're building a simple login application.

Design the States and Actions

  1. INITIAL_STATE: The user visits the page for the first time and both fields are empty. Let's say you only want to make the username visible, but not the password in this state. (Similar to the new Gmail workflow)

  2. USERNAME_ENTRY_STATE: When the user types in the username and hits return, in this state, you want to display the username and hide the password. You can have onUsernameEntered as an action in this state.

  3. PASSWORD_ENTRY_STATE: Now, the username view will be hidden and the password view will be shown. When the user hits return, you have to check if the usernames and passwords match. Let's call this action onPasswordEntered

  4. AUTHENTICATED_STATE: When the server validates the username/password combination, let's say you want to show the home page. Let's call this action onAuthenticated

I have omitted handling the Authentication Failed case for now.

Design the Views:

In this case, we have the UsernameView and the PasswordView

Design the Models:

A single Auth model suffices for our example.

Design the Routes:

Check out the best practices for handling routes with Marionette. The state machine should be initialized in the login route.

Sample Pseudo-Code:

I've only shown the code relevant to managing the state machine. Rendering and event handling can be handled as usual;

var UsernameView = Backbone.View.extend({

    initialize: function(options) {
        this.stateMachine = options.stateMachine;
    },

    onUserNameEntered: function() {
         username = //get username from DOM;
         this.stateMachine.handleAction('onUserNameEntered', username)
    },

    show: function() {
       //write logic to show the view
    },

    hide: function() {
       //write logic to hide the view
    } 

});

var PasswordView = Backbone.View.extend({

    initialize: function(options) {
        this.stateMachine = options.stateMachine;
    },

    onPasswordEntered: function() {
         password = //get password from DOM;
         this.stateMachine.handleAction('onPasswordEntered', password)
    },

    show: function() {
       //write logic to show the view
    },

    hide: function() {
       //write logic to hide the view
    } 

});

Each state will have an entry function which will initialize the views and and exit function which will cleanup the views. Each state will also have functions corresponding to the valid actions in that state. For example:

var BaseState = function(options) {
   this.stateMachine = options.stateMachine;
   this.authModel = options.authModel;
};

var InitialState = BaseState.extend({

    entry: function() {
        //show the username view
        // hide the password view
    },

    exit: function() {
       //hide the username view
    },

    onUsernameEntered: function(attrs) {
        this.authModel.set('username', attrs.username');
        this.stateMachine.setState('PASSWORD_ENTRY_STATE'); 
    }


});

Similarly, you can write code for other states.

Finally, the State Machine:

var StateMachine = function() {
    this.authModel = new AuthModel;
    this.usernameView = new UserNameView({stateMachine: this});
    //and all the views

    this.initialState = new InitialState({authModel: this.authModel, usernameView: this.usernameView});
    //and similarly, all the states

    this.currentState = this.initialState;
};


StateMachine.prototype = {

    setState: function(stateCode) {
        this.currentState.exit(); //exit from currentState;
        this.currentState = this.getStateFromStateCode(stateCode);
        this.currentState.entry();  
    },

    handleAction: function(action, attrs) {
        //check if action is valid for current state
        if(actionValid) {
            //call appropriate event handler in currentState
        }
    }


};

StateMachine.prototype.constructor = StateMachine;

For a simple application this seems to be an overkill. For complicated business logic, it is worth the effort. This design pattern automatically prevents cases such as double-clicking on a button, since you would have already moved on to the next state and the new state does not recognise the previous state's action.

Once you have built the state machine, other members of your team can just plug in their states and views and also can see the big picture in a single place.

Libraries such as Redux do some of the heavy-lifting shown here. So you may want to consider React + Redux + Immutable.js as well.