Monday 26 November 2018

Misfiring On User Experience

It's nearing the end of the month, and I'd like to share a little story that happened back when I was working in this tiny office as a wet-behind-the-ears web developer, and the valuable lesson I learned from it. My memory of the exact details are a little fuzzy, so bear with me if some of it doesn't make sense.

It all started with a little assignment...

Back then, my boss also doubled as a tech trainer, and he had a series of courses he marketed. At some point, he wanted me to write a course scheduler that would allow the user to pick a course, select the available dates and input his or her details. Discounted rates were available for prospective students who selected certain options (this little detail will be relevant later).

So I dug in and wrote the code. It was HTML and JavaScript, with a PHP back-end to send an email upon completion, with the details that the applicant had entered, to my boss. Within a day, I had the site up, and I was testing my JavaScript, and putting it through all the test cases I could think of. There were plenty of moving parts, so I had to be careful to try all options to ensure that the site displayed only the options that were available under certain conditions.

The site was up and running for a week. It seemed to work, and I received no complaints.

Change... and catastrophe

After a week, the boss requested that I make a slight change to the site. The discounted price would no longer be available no matter what options the applicant selected. I made the necessary adjustments, which amounted to some lines of code being commented out. It was a quick and dirty fix, but tests indicated that it worked.

Then one morning, all hell erupted. One applicant had somehow managed to get the discounted price on his application and raised a fuss when informed by my boss that the discounted price was no longer available. After the dust had settled, he was livid. He chewed me out for allowing this to happen. I didn't take it personally; I was too preoccupied trying to figure out where my tests had gone wrong.

Clicking repeatedly and randomly.

Speaking to the aggrieved applicant, it turned out that when the discounted price had not appeared, he simply assumed that it was a browser error and refreshed the page over and over again till he got the result he wanted. This somehow jammed up my asynchronous procedures, another reminder that where async is concerned, just getting correct output is never enough.

Rumination

When the dust settled, I took a long hard look at the entire thing I had set up, and the events that had led up to the catastrophe.

A user had succeeded in hacking the system; not by design, but by accident, which was worse. How could this have been prevented? Well, prevented is a strong word and presumes that the problem was technical. And yes, to an extent, it was. But there was a bigger picture behind this incident. Had the user known that the discounted price was no longer available, he would not have assumed that the system had calculated his final bill incorrectly and taken those actions.

What I learned from this, is that the importance of User Experience (UX) cannot be overestimated. The system had not even warned the user that the discount had been discontinued. There was no feedback that would take the user from one point to the next; no visibility. Sure, this would not have stopped a deliberate and determined hacker, but it would have made the system more user-friendly and forestalled the relatively innocent user from what I call "panic-clicking" and causing the system to malfunction. Not looking pretty is one thing; but clarity is important. When it gets to the point where the user is just randomly clicking around to see what happens, the system is failing. Not at a technical level, but at an interface level.

Lost in the interface.

Also, how many deliberate and determined hackers are there compared to the number of users with innocent intentions? I'd wager that the latter outnumbers the former by a factor of hundreds. Therefore, it's only good business sense to put in the extra bit of effort for those people.

Think about it: all it would have taken was a simple system message to inform the user that the discounted rate was no longer available! Such a simple thing, and so obvious in hindsight.

No matter how solid you think your code is, no matter how much you've tested the system, it will never be enough. Something's going to get through. Something you never foresaw. And if this can be mitigated with just a little more user-friendliness, it's worth it.

Till next time, stay UXcellent!
T___T

Thursday 22 November 2018

Web Tutorial: The Smart Drop-down List (Part 3/3)

And now for the next step!

The drop-down list looks presentable and behaves like an actual drop-down list. But we want more, don't we? What if you could actually filter this long-ass list of countries?

To do that, we add an oninput event handler to the textbox (which should have its readonly attribute removed now). It should run the showList() function, which we've already written, passing in "Countries" and false as an argument. The second argument is to tell the function that it was not called from clicking the "button", but rather, from entering text in the txtCountries text box.
            <div>
                <input placeholder="(select one)" id="txtCountries" oninput="showList('Countries', false)">
                <div onclick="showList('Countries', true)">&#9660;</div>
            </div>


Now, on to the showList() function. Here, we add an else block to the main conditional. This tells the function what to do if we're supposed to filter, as opposed to just displaying the entire list. The first action is to reveal the list. At the same time, declare the variable txt and grab the element txtCountries.
            function showList(objName, showAll)
            {
                var txt = document.getElementById("txt" + objName);
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                ul.innerHTML = "";

                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";
                    }
                }
                else
                {
                    ul.style.display = "block";               
                }
            }


And then we iterate through the options of the ddlCountries drop-down list.
                else
                {
                    ul.style.display = "block";

                    for (var i = 0; i < ddl.options.length; i++)
                    {

                    }                   
                }


Then we declare a variable, opt, and set it to the current option. Next, we use the indexOf() method on the text property of opt to see if it contains the text currently entered in the txtCountries text box. We should also ensure that the value is not an empty string. If it satisfies those conditions, add the item to the list using the addListItem() function we created earlier.
                else
                {
                    ul.style.display = "block";

                    for (var i = 0; i < ddl.options.length; i++)
                    {
                        var opt = ddl.options[i];

                        if (opt.text.indexOf(txt.value) != -1 && opt.text != "")
                        {
                            addListItem(objName, opt.value, opt.text);
                        }
                    }                   
                }


Try it! Type in "h" into the text box. The list should pop up, showing all countries with a "h" in the name.


But that list is still too long. Try adding "a" after the "h". The list is dramatically shortened! Select "Ghana". Does Ghana get selected?


Hold your horses, we're not done!

What happens when you type in some random text with no matches and then try to close the list by clicking on the "button"? Looks like the value is left there. That's not what we want, so let's fix that.


Go to the showList() function and get into the conditional that handles the toggling of the "button". After it hides the list, check if the list is empty. If it is, that means there were no matches for the string in the text box.
                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";

                        if (ul.innerHTML == "")
                        {

                        }
                    }
                }


In that case, we want to reset the value of the text box. Check if the value of ddlCountries is empty.
                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";

                        if (ul.innerHTML == "")
                        {
                            if (ddl.value == "")
                            {

                            }
                            else
                            {

                            }
                        }
                    }
                }


If so, set the value of the txtCountries text box to an empty string too. If not, set it to the current displayed text in the ddlCountries drop-down list. This way, the text box doesn't get stuck displaying some nonsense value.
                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";

                        if (ul.innerHTML == "")
                        {
                            if (ddl.value == "")
                            {
                                txt.value = "";
                            }
                            else
                            {
                                txt.value = ddl.options[ddl.selectedIndex].innerHTML;
                            }
                        }
                    }
                }


And now, after hiding the ddlCountries drop-down list like I showed you in Part 2, we're done. Check it out below.


How do we use this?

Well, although the ddlCountries drop-down list is invisible, it's still there. The drop-down list we made is simply a prettier and smarter display version of it. When you send the values over via a form, though, you should still grab the value of ddlCountries.

Why all the abstraction?

Yes, you notice a lot of this code is indirect referencing. That's so you can reuse the code for multiple drop-down lists! Remember DRY (Don't Repeat Yourself)!

I'm Ghana go now, see you next time!
T___T

Tuesday 20 November 2018

Web Tutorial: The Smart Drop-down List (Part 2/3)

Welcome back, and in this part of the web tutorial, we'll make what we've done so far, presentable.

First off, let's give the div some rounded corners and a bit of padding.
            .ddl_wrapper div
            {
                border-radius: 5px;
                border: 1px solid #DDDDDD;
                width: 100%;
                padding: 2px;
            }




Next, since the "button" is supposed to be clickable, let's make it even more obvious. Specify that the color is black, then add a hover style to it, changing the color to light grey.
            .ddl_wrapper div
            {
                border-radius: 5px;
                border: 1px solid #DDDDDD;
                width: 100%;
                padding: 2px;
            }

            .ddl_wrapper div div
            {
                width: 10%;
                float: right;
                text-align: center;
                cursor: pointer;
                padding: 0;
                border: none;
                color: #000000;
            }

            .ddl_wrapper div div:hover
            {
                color: #DDDDDD;
            }


Now whenever you mouse over, it changes color!


Time to go for the main source of ugliness - the list. Let's create a style right there.

Again, we make the corners rounded and give it a light grey border, along with some padding. We limit the width to 100% of its parent. display: block ensures that it renders correctly, and setting the margin-top property to 0px ensures that it aligns nicely to the bottom of the div. Lastly, we set list-style-type to none so that the ugly markers are gone.
            .ddl_wrapper div input
            {
                width: 80%;
                height: 90%;
                border: none;
                outline: none;
            }

            .ddl_wrapper ul
            {
                border-radius: 5px;
                border: 1px solid #DDDDDD;
                width: 100%;
                padding: 2px;
                display: block;
                margin-top: 0px;
                list-style-type: none;
            }




Then we style individual list items by setting colors, padding and a light grey underline for readability. The display property needs to be set to block for the underline to work nicely. While we're at it, set the cursor property to pointer to further hammer home the point that the user is supposed to click.
            .ddl_wrapper ul
            {
                border-radius: 5px;
                border: 1px solid #DDDDDD;
                width: 100%;
                padding: 2px;
                display: block;
                margin-top: 0px;
                list-style-type: none;
            }

            .ddl_wrapper ul li
            {
                display: block;
                color: #999999;
                border-bottom: 1px solid #EEEEEE;
                padding: 2px;
                cursor: pointer;
            }




Now we set a hover selector. On mouse over, the background color changes to a nice orange, and the foreground color changes to white.
            .ddl_wrapper ul li
            {
                display: block;
                color: #999999;
                border-bottom: 1px solid #EEEEEE;
                padding: 2px;
                cursor: pointer;
            }

            .ddl_wrapper ul li:hover
            {
                background-color: #FF8800;
                color: #FFFFFF;
            }


Yep, this is good!


Are we done?

Well, if all you really want is for the new drop-down list to behave more or less like a standard drop-down list, here a couple things to do.

            <div>
                <input placeholder="Countries" readonly="readonly" id="txtCountries">
                <div onclick="showList('Countries', true)">&#9660;</div>
            </div>


Then hide the old drop-down list.
            .ddl_wrapper ul li:hover
            {
                background-color: #FF8800;
                color: #FFFFFF;
            }

            .ddl_wrapper select
            {
                display: none;
            }


And there you have it! A perfectly serviceable, pretty drop-down list. But if you want more out of your drop-down list, undo those changes and head on to the next part!


Oh yeah, before I forget...

It's kind of silly to have the textbox use "Countries" as a placeholder now that we actually have a list with values, so just replace it with "(select one)" for consistency.
<input placeholder="(select one)" id="txtCountries">


There you go.


Next

More functionality. The drop-down list should accept your input and offer you suggestions on what to input. Watch this space!

Saturday 17 November 2018

Web Tutorial: The Smart Drop-down List (Part 1/3)

Howdy, and welcome to today's web tutorial!

We're going to make what I call a Smart Drop-down List. Not only can you select from this list, you can type in text and the list will show you only the options corresponding to that text. It's one of the most ubiquitous features in many UI packages today and you could probably just download one from the plethora of packages out there... or we could have some fun instead and make our own.

For that, let's first implement a classic drop-down list. It will be a very standard select element, id ddlCountries. The first option will be blank, and after that, I simply copied an entire list of countries from this site.

<!DOCTYPE html>
<html>
    <head>
        <title>Smart Drop-down List</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>
        <select id="ddlCountries">
            <option value="">(select one)</option>
            <option value="AF">Afghanistan</option>
            <option value="AX">Ă…land Islands</option>
            <option value="AL">Albania</option>
            <option value="DZ">Algeria</option>
            <option value="AS">American Samoa</option>
            <option value="AD">Andorra</option>
            ... (get the rest of the code here)
        </select>
    </body>
</html>


That's a very long list...


And what comes next, is that we wrap this within a div with a class of ddl_wrapper.
        <div class="ddl_wrapper">
            <select id="ddlCountries">
                <option value="">(select one)</option>
                <option value="AF">Afghanistan</option>
                <option value="AX">Ă…land Islands</option>
                <option value="AL">Albania</option>
                <option value="DZ">Algeria</option>
                <option value="AS">American Samoa</option>
                <option value="AD">Andorra</option>
                ... (get the rest of the code here)
            </select>
        </div>



Let's write the style for ddl_wrapper. Give it a black outline so we can see what's going on, set overflow to visible so that nothing gets cut off, and maybe a width and height, though height isn't really necessary.
        <style>
            .ddl_wrapper
            {
                outline: 1px solid black;
                overflow: visible;
                width: 200px;
                height: 30px;
            }
        </style>


Now you see that your drop-down list is encased within the div!


OK, here's the deal. We'll create something that simulates what this drop-down list does, and later hide this drop-down list.

So first, let's put in a div.
        <div class="ddl_wrapper">
            <div>

            </div>

            <select id="ddlCountries">
                <option value="">(select one)</option>
                <option value="AF">Afghanistan</option>
                <option value="AX">Ă…land Islands</option>
                <option value="AL">Albania</option>
                <option value="DZ">Algeria</option>
                <option value="AS">American Samoa</option>
                <option value="AD">Andorra</option>
                ... (get the rest of the code here)
            </select>
        </div>


Then add in two things - a text box with an id of txtCountries and another div containing the &#9660; symbol.
        <div class="ddl_wrapper">
            <div>
                <input placeholder="Countries" id="txtCountries">
                <div>&#9660;</div>
            </div>

            <select id="ddlCountries">
                <option value="">(select one)</option>
                <option value="AF">Afghanistan</option>
                <option value="AX">Ă…land Islands</option>
                <option value="AL">Albania</option>
                <option value="DZ">Algeria</option>
                <option value="AS">American Samoa</option>
                <option value="AD">Andorra</option>
                ... (get the rest of the code here)
            </select>
        </div>


Things looking a bit messy at the moment. Not to worry, we'll clean it up with some styling.


First, remove the black border in the ddl_wrapper CSS class.
        <style>
            .ddl_wrapper
            {
                outline: 0px solid black;
                overflow: visible;
                width: 200px;
                height: 30px;
            }
        </style>


Now, for the main div within the ddl_wrapper CSS class, give it a light grey border, and set the width to 100%. That means it'll occupy the entirety of the parent div.
        <style>
            .ddl_wrapper
            {
                outline: 0px solid black;
                overflow: visible;
                width: 200px;
                height: 30px;
            }

            .ddl_wrapper div
            {
                border: 1px solid #DDDDDD;
                width: 100%;
            }
        </style>


For the div with the &#9660; symbol, it's supposed to be clickable. So set the cursor property to pointer, give it a width of 10% and make it float right. Text alignment, border and padding are up to you, though I think the values I've used below work best.
        <style>
            .ddl_wrapper
            {
                outline: 0px solid black;
                overflow: visible;
                width: 200px;
                height: 30px;
            }

            .ddl_wrapper div
            {
                border: 1px solid #DDDDDD;
                width: 100%;
            }

            .ddl_wrapper div div
            {
                width: 10%;
                float: right;
                text-align: center;
                cursor: pointer;
                padding: 0;
                border: none;
            }
        </style>


As for the text box, give it 80% width, 90% height and set both the border and outline properties to none. This will make it totally seamless.
        <style>
            .ddl_wrapper
            {
                outline: 0px solid black;
                overflow: visible;
                width: 200px;
                height: 30px;
            }

            .ddl_wrapper div
            {
                border: 1px solid #DDDDDD;
                width: 100%;
            }

            .ddl_wrapper div div
            {
                width: 10%;
                float: right;
                text-align: center;
                cursor: pointer;
                padding: 0;
                border: none;
            }

            .ddl_wrapper div input
            {
                width: 80%;
                height: 90%;
                border: none;
                outline: none;
            }
        </style>


Looking much better now.


Now let's make it perform like a drop-down list! Insert an unordered list after the first div inside the div styled with ddl_wrapper. The id will be ulCountries. Make it disappear using an inline style attribute and display: none. Then put an onclick event in the "button". It should run the showList() function, passing in "Countries" and true as arguments.
            <div>
                <input placeholder="Countries" id="txtCountries">
                <div onclick="showList('Countries', true)">&#9660;</div>
            </div>

            <ul id="ulCountries" style="display:none;"></ul>


Let's write some JavaScript! The showList() function accepts two parameters - objName and showAll. objName is the name of the object you'll be accessing. In this case, it will be "Countries". showAll is a Boolean variable that tells you if we need to show the entire list or filter it by search. If we're clicking on the "button", that value is true. Because clicking on that button will show all results.

Declare two variables - ddl and ul. We'll use the objName parameter and the getElementById() method to derive the drop-down list and the unordered list. Initially, the unordered list will be empty.
        <script>
            function showList(objName, showAll)
            {
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                ul.innerHTML = "";
            }
        </script>


Now let's toggle the unordered list. Each time you click on the "button", if the ulCountries list is hidden, it'll be displayed. If it's displayed, it'll be hidden. Bear in mind that this is only if showAll is true, which means the "button" has been clicked.
        <script>
            function showList(objName, showAll)
            {
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                ul.innerHTML = "";

                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";
                    }
                    else
                    {
                        ul.style.display = "none";
                    }
                }
            }
        </script>


After that, iterate through ddlCountries's options and add them, one by one, into the ulCountries! We do this by calling the addListItem() function and passing in objName, the current option's value and text as arguments.
        <script>
            function showList(objName, showAll)
            {
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                ul.innerHTML = "";

                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";
                    }
                }
            }
        </script>


Now let's write the addListItem() function. It will accept the name of the object, and two other strings as parameters. val is the value of each option, and text is the displayed value.
        <script>
            function showList(objName, showAll)
            {
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                ul.innerHTML = "";

                if (showAll)
                {
                    if (ul.style.display == "none")
                    {
                        ul.style.display = "block";

                        for (var i = 0; i < ddl.options.length; i++)
                        {
                            var opt = ddl.options[i];

                            addListItem(objName, opt.value, opt.text);
                        }
                    }
                    else
                    {
                        ul.style.display = "none";
                    }
                }
            }

            function addListItem(objName, val, text)
            {

            }
        </script>


Here, we declare the variable ul and repeat what we did in the showList() function. Then we use the createElement() method to create a list item.
            function addListItem(objName, val, text)
            {
                var ul = document.getElementById("ul" + objName);

                var li = document.createElement("li");
            }


Next, set the list item's displayed text to text, and append it to ddlCountries. For the time being, we won't use the val parameter.
            function addListItem(objName, val, text)
            {
                var ul = document.getElementById("ul" + objName);

                var li = document.createElement("li");
                li.innerHTML = text;
                ul.appendChild(li);
            }


Let's test this! Clicking on the "button" should toggle the list on and off! I know it looks shitty right now, but our objective is first to get it to work.


Now, what else should we implement? The list toggles on and off, but we can't select anything. That's what we'll do next. After setting the text, add an onclick handler that will run the selectOption() function with objName, val and text as arguments. Then create said function.
            function addListItem(objName, val, text)
            {
                var ul = document.getElementById("ul" + objName);

                var li = document.createElement("li");
                li.innerHTML = text;
                li.onclick = function()
                {
                    selectOption(objName, val, text);
                }
                ul.appendChild(li);
            }

            function selectOption(objName, val, text)
            {

            }


Here, we use objName to first do pretty much what we did for the other two functions, except that now we'll grab the textbox, txtCountries as well.
            function selectOption(objName, val, text)
            {
                var txt = document.getElementById("txt" + objName);
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);
            }


Then we set the text in the txtCountries text box to text and ensure that ddlCountries is also set to val. After that, hide ulCountries.
            function selectOption(objName, val, text)
            {
                var txt = document.getElementById("txt" + objName);
                var ddl = document.getElementById("ddl" + objName);
                var ul = document.getElementById("ul" + objName);

                txt.value = text;
                ddl.value = val;
                ul.style.display = "none";
            }


Now try this. Click on the "button" and select, say, Ghana. Does "Ghana" appear in the text box? Does ddlCountries select Ghana too?


Next

It looks messy right now, so in the next part, we're going to pretty it up a bit. Stay with me!