State pattern with UI Code

By : Akshar Raaj

This blog tells how I used state pattern in my UI code, and explains state pattern with JavaScript and jQuery.

Disclaimer:

  • This blog will not talk much about benefits of State pattern as there is already a lot written about it. It only gives a practical example of using state pattern with UI code.
  • I am not much confident about difference between State pattern and Strategy pattern. Let's discuss if I misunderstood State pattern.

Use case:

  • I have a page with two input fields.
  • Page initially has one title field and one body field.
  • There is a '+Add' button under title and body: State pattern with javascript
  • The page can be in two states which are the two radio buttons at top i.e 'Add one' and 'Add both'.
  • Functionality of '+Add' differs based on what state(radio button) is selected.

Different scenarios:

Our assumption at start of each scenarios is that page has only one title and one body.

'Add one' is selected and user clicks on '+Add' of title

The page starts looking like: State pattern with javascript

If he clicks on '+Add' of title again, it starts looking like: State pattern with javascript

'Add one' is selected and user clicks on '+Add' of body

The page starts looking like: State pattern with javascript

'Add both' is selected and user clicks on '+Add' of title

Page starts looking like: State pattern with javascript

In 'Add both' both title and body field should be added irrespective of which '+Add' button is clicked.

If the user clicks any of the '+Add' button again, one more combination of title and body will be added. So, it will start looking like: State pattern with javascript

So, you got the idea of how '+Add' buttons will behave depending on which radio button is selected at that instant. User can switch between the different radio buttons and '+Add' will behave differently.

Let's deal with code now.

Code for initial page.

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    </head>
    <body>
        <div style="width:100%;">
            <div style="float:left; width:50%">
                <input type="radio" name="state" checked="checked" value="one">Add one</input>
            </div>
            <div style="float:left; width:50%">
                <input type="radio" name="state" value="both">Add both</input>
            </div>
        </div>

        <div class="input_div">
            <input type="text" class="title" placeholder="Title" />
            <br/>
            <a href="" class="add">+Add</a>
        </div>

        <br/>
        <br/>
        <br/>

        <div class="input_div">
            <input type="text" class="body" placeholder="Body" />
            <br/>
            <a href="" class="add">+Add</a>
        </div>
    </body>
</html>

Let's add the javascript for '+Add' buttons.

<script type="text/javascript">
    $(document).ready(function () {
        $("a.add").click(function (e) {
            e.preventDefault();
            var state = $("input[name='state']:checked").val();
            if (state=="one") {
                var lastInput = $(this).prev().prev();
                var newInput = lastInput.clone();
                $(this).before(newInput);
                $(this).before("<br/>");
            }
            else if(state=="both"){
                var inputDivs = $(".input_div");
                inputDivs.each(function (index, inputDiv) {
                    var $inputDiv = $(inputDiv);
                    var addButtton = $inputDiv.find("a.add");
                    var lastInput = $inputDiv.find("input:last");
                    var newInput = lastInput.clone();
                    addButtton.before(newInput);
                    addButtton.before("<br/>");
                });
            }
        });
    });
</script>

Add this script in <head> of the page.

Switch between two radio buttons at top and notice the difference in behaviour of '+Add' buttons.

Two states required two conditions in the '+Add' button handler. The number of conditions in handler will increase as number of state increases.

Also, we only concerned ourselves with '+Add' field functionality. There can be a use case with '-Delete' field functionality. So handler for '-Delete' will have as many conditions as number of states too. So the delete handler would look like:

$(".some_class_on_delete_buttons").click(function () {
    if (state=="one") {
        //do something
    }
    else if (state="both") {
        //do something else
    }
    //And in case there are more state
    //there will be more conditionals
});

With more states and more functionalities this approach becomes unmaintainable.

I want two things:

  • I want to get rid of conditionals.
  • I want to keep all the functionality for a particular state at a single place. eg: For state 'Add one', I want both add and delete in a single class. For state 'Add both', I want add and delete in a separate class.

It needs to be accomplished in following way:

  • I will keep separate classes for different states.
  • All the state specific classes need to extend from a super class.
  • Super class will define the abstract methods and subclasses should provide the actual implementation for those methods.

In case you are in hurry, see the final code at github.

Superclass State looks like:

var State = function () {};

State.prototype.add = function (addButton) {
    throw new Error("It needs to be implemented by subclasses");
};

Subclasses AddOneState and AddBothState correspond to states 'Add One' and 'Add Both':

var AddOneState = function () {};
AddOneState.prototype = Object.create(State.prototype);
AddOneState.prototype.add = function (addButton) {
    var lastInput = addButton.prev().prev();
    var newInput = lastInput.clone();
    addButton.before(newInput);
    addButton.before("<br/>");
};

var AddBothState = function () {};
AddBothState.prototype = Object.create(State.prototype);
AddBothState.prototype.add = function (addButton) {
    var inputDivs = $(".input_div");
    inputDivs.each(function (index, inputDiv) {
        var $inputDiv = $(inputDiv);
        var addButtton = $inputDiv.find("a.add");
        var lastInput = $inputDiv.find("input:last");
        var newInput = lastInput.clone();
        addButtton.before(newInput);
        addButtton.before("<br/>");
    });
};

Notice that I just moved the conditional statements to add() of proper class.

I need another class. Let's call it Page. This class will represent the web page we are looking at. This class will have an instance variable called state that keeps track of current state of Page i.e whether 'Add One' or 'Add Both' is selected.

var Page = function (state) {
    this.state = state;
};
Page.prototype.add = function (addButton) {
    this.state.add(addButton);
};

The above three lines are the core of state pattern. Page has an add which delegates to add of current state.

Initially the page has 'Add One' selected, so we need to keep the initial state as an instance of AddOneState.

var page = new Page(new AddOneState());

And finally, let's add the handlers:

$("a.add").click(function (e) {
    e.preventDefault();
    page.add($(this));
});

$("input[name='state']").change(function () {
    var stateVal = $("input[name='state']:checked").val();
    if (stateVal == "one") {
        page.state = new AddOneState();
    }
    else {
        page.state = new AddBothState();
    }
});

So, final script and html looks like:

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function () {
                var State = function () {};

                State.prototype.add = function (addButton) {
                    throw new Error("It needs to be implemented by subclasses");
                };

                var AddOneState = function () {};
                AddOneState.prototype = Object.create(State.prototype);
                AddOneState.prototype.add = function (addButton) {
                    var lastInput = addButton.prev().prev();
                    var newInput = lastInput.clone();
                    addButton.before(newInput);
                    addButton.before("<br/>");
                };

                var AddBothState = function () {};
                AddBothState.prototype = Object.create(State.prototype);
                AddBothState.prototype.add = function (addButton) {
                    var inputDivs = $(".input_div");
                    inputDivs.each(function (index, inputDiv) {
                        var $inputDiv = $(inputDiv);
                        var addButtton = $inputDiv.find("a.add");
                        var lastInput = $inputDiv.find("input:last");
                        var newInput = lastInput.clone();
                        addButtton.before(newInput);
                        addButtton.before("<br/>");
                    });
                };

                var Page = function (state) {
                    this.state = state;
                };
                Page.prototype.add = function (addButton) {
                    this.state.add(addButton);
                };

                var page = new Page(new AddOneState());

                $("a.add").click(function (e) {
                    e.preventDefault();
                    page.add($(this));
                });

                $("input[name='state']").change(function () {
                    var stateVal = $("input[name='state']:checked").val();
                    if (stateVal == "one") {
                        page.state = new AddOneState();
                    }
                    else {
                        page.state = new AddBothState();
                    }
                });
            });
        </script>
    </head>
    <body>
        <div style="width:100%;">
            <div style="float:left; width:50%">
                <input type="radio" name="state" checked="checked" value="one">Add one</input>
            </div>
            <div style="float:left; width:50%">
                <input type="radio" name="state" value="both">Add locked combination</input>
            </div>
        </div>

        <div class="input_div">
            <input type="text" class="title" placeholder="Title" />
            <br/>
            <a href="" class="add">+Add</a>
        </div>

        <br/>
        <br/>
        <br/>

        <div class="input_div">
            <input type="text" class="body" placeholder="Body" />
            <br/>
            <a href="" class="add">+Add</a>
        </div>
    </body>
</html>

Though the script becomes huge compared to the initial script which contained conditionals, it is much easier to maintian and scale.

Say we need to handle one more state, we will create another class corresponding to that state and write an appropriate add() method on that class. But we wouldn't need any change in Page to accomodate a new state and our script will keep working fine.

Say we want to add delete functionality, we can add a delete() method on all the states and define a delete() on Page. delete of Page will delegate to delete of current state.

You should read more about State Pattern to make sense of this example.

References:

  • I came across State pattern while reading "Refactoring" by Martin Fowler.


Related Posts


Can we help you build amazing apps? Contact us today.

Comments

http://macmakeupbulk.com/

Elizabeth Arden Gorgeous Shade Greatest Volume MascaraHi Beauties,The most favourite part of my makeup program is Mascara. I might not have an iota of makeup with me, but you'll by no means locate me devoid of a mascara in my bag. Mascara is actually a product, regardless of whichever brand, generally comes in the size which can very easily fit into any bag. It instantaneously opens my eyes and gives me a wide awake look. However I do have some favourite mascaras, I do like to dabble and experiment with it. Also, considering that a mascara shouldn't be used to get a very long time, it offers me a legitimate purpose to throw out an outdated one and purchase a new one. Currently, I'm gonna review a mascara to suit your needs which I have started applying a short while ago..It comes in two colors, Black and Brown Black. I am working with; and reviewing the color 01-Black to suit your needs. About the item: This attractiveness award winner substantially thickens lashes as it separates and defines. Smudge-proof formula lasts all day. Ingredients: Water, Glyceryl stearate, Ammonium Acrylates Copolymer, DisteardimoniumHectorite, Propylene Glycol, Stearic acid, CoperniciaCerifera(Carnauba) Wax, Triethanolamine, Synthetic Wax, Acrylates Copolymer, Polyvinyl Alcohol, Lecithin, Propylene Carbonate, Oleic Acid, Glycerin, Sodium Laureth-12 Sulfate, Panthenol, Alcohol Denat., Xanthun Gum, Trisodium EDTA, Benzyl Alcohol, Simethicone, Ethylparaben, Methylparaben, Phenoxyethanol, PropylparabenApplication and Employs: For thicker lashes, start on the base of the lashes and hold the mascara wand in horizontal position, operating from side to side when you function your way up to the end with the lashes.Price:19 USD for ten.25 ml of productShelf life: Upto six months from opening the packagingMy Knowledge Elizabeth Arden Stunning Colour Highest Volume Mascara:It comes within a golden colour box packaging. The mascara comes in the black metallic tube, quite plain looking, similar to every other mascara. When selecting a mascara, I ordinarily seem in the wand very first. I am definitely liking the Japanese Mascara, which have small wands, pretty fine, numerous and compact bristles, which are seriously uncomplicated to use and define the eyes beautifully.The Wand of this mascara has incredibly, quite a few smaller size bristles, which may seriously visit the inner corner with the eyes, grab each of the eye lashes in between them and apply the mascara nicely. It applies really smoothly and softly, with out poking into your eyes. It definitely is actually a superb brush (one particular from the greatest I have applied), and it is also very light excess weight.The formula is smudge proof, although not water evidence. Though all through one particular of my watering of eyes session, I remember…it did not smudge or give me racoon eyes appear. Nevertheless, it really is quickly washable, and gets eliminated conveniently applying an eye makeup remover (How I detest individuals mascaras which I've to rub and rub, and even now it will not budge from eyelashes.). The formula isn't going to get clumped in any way.One coat of your mascara usually do not makes the lashes incredibly thickened, as a substitute they look normal, just more enhanced, lengthened and well separated. It's very good for day time office look, because it will not seem abnormal, but will definitely define the eyelashes. 1 coat will make your eye lashes extra defined, even though adding a different coat on top (though the primary one hasn’t dried absolutely) will help in thickening the lashes. But even soon after two coats, it really is not a mega volumiser.&nbsp;Pros of?Elizabeth Arden Gorgeous Color Greatest Volume Mascara? Far more amount of solution compared to other brands ? Decent dimension of packaging ? Wand quality is incredibly good ? Defines and Separates Eyelashes incredibly well ? Formula very light weight ? Fantastic Lengthening ? No Clumps ? Smudge proof ? Doesn't irritate my eyes ? Doesn't give me Racoon eyes (though it is actually not water proof) ? Gets eliminated simply with a makeup remover ? Provides a really natural, and far more defined seem towards the eyes. Cons of?Elizabeth Arden Stunning Colour Optimum Volume Mascara:? Limited Availability in India ? Expensive ? Not water evidence (but may perhaps call it water resistant because it isn't going to get washed with my tears) ? Doesn't give mega-thickness towards the lashes, in spite of how lots of coats are applied (contrary to claims) IMBB rating: 4.8/5 Will I recommend Elizabeth Arden Attractive Shade Highest Volume Mascara ?Yes, to everyone.This is often a excellent all round mascara in terms of quality, and quantity. The formula is light excess weight, non smudging and non clumping, while the brush is one from the very best I have applied till now.Elizabeth Arden Stunning Color Lash Enhancing Mascara – 01 Black Elizabeth Arden Double Density Maximum Volume Mascara Review Elizabeth Arden 8 Hour Cream Skin Protectant Elizabeth Arden’s Red Door Eau De Toilette Spray Elizabeth Arden Green Tea Intense Eau De Parfum Elizabeth Arden Eight Hour Cream Sun Defense for Face SPF 50 Bourjois Applicator Double Tip Estee Lauder Double Wear Stay in Area Gel Eyeliner Oynx Estee Lauder Double Wear Highest Cover Camouflage Makeup Elizabeth Arden Flawless Finish Highest Coverage Concealer Lancome L’ Absolu Nu Repleneshing &amp; Enhancing Lip Shade – Rose Candy Smashbox Lip Enhancing Gloss – Illume Review Custom Search

commmenttor
bottes hermes femme

We absolutely love your blog and find many of your post's to be just what I'm looking for. Does one offer guest writers to write content in your case? I wouldn't mind publishing a post or elaborating on most of the subjects you write about here. Again, awesome web log!

commmenttor
Post a comment Name :

Email :

Your site url:

Comment :

© Agiliq, 2009-2012