Young delta heart

The fine art of degradable interface elements

What is the problem?

When working on a web application, you will ultimately have to deal with preferences, settings or controls to allow your users to modify the application behaviour. The simple route is to create a separate page to house these options, with a table or list of form elements representing the user's settings.

However as applications get more complex, and the user interaction becomes more context heavy you may find that separating your preferences outside of the user flow becomes a hinderance to the user experience.

Your users are frustrated, and your client is too. Your answer is to place the controls in an accessible place within the user interaction flow.

Perhaps you already have the form working on a separate page, so pulling that html into an overlay isn't too hard. You also have a link to the settings on the page you need to incorporate with, so with a bit of javascript you can modify that link to activate your overlay providing a nice enhancement to the user.

(If the above statement sounds like it really is too hard, send me an email or message me on twitter about it and maybe I'll write something up for that in a future article)

Overlays are fine, but when you have an interface that has multiple settings, or preference modifiers it starts to behave like a jack-in-a-box orgy.

Early interface sketch

Getting inline

Complex inline interface elements, provide you with an excellent opportunity to allow the user to modify complex settings via a simple interface, and one that shouldn't get in the way of the user flow. An example of this that I'm going to cover is taken from an application that I designed and built for a startup called ideapi.

The project is still in development, so I can't give too much away, but we needed a way to allow users to manage collaborators on a brief proposal.

The following video provides a quick overview of solution I'm currently implementing:

As you can see I have encapsulated a number of fairly complex relationships within a simple interface, this allows the user to quickly update their preferences and get back to what they were doing without breaking flow.

Whilst the interface uses javascript and ajax to enhance the experience, if you take these away you will find an widely accessible form element behind it that will allow the user to make the same modifications no matter what their browsing experience is, which is just the way I like it.

form without js enabled

Now you have a bit of background on what we are trying to achieve lets dive in and get started. Because this article is inevitably going to get a bit code heavy, I'm going to assume that you know all about html, CSS and basic jQuery. Even if you don't, maybe bare with me as I'm sure you will get something out of reading on.

The working example.

For our example application, we need a way of managing our employees. They are rambunctious lot so we shift around the roles quite a lot to keep things interesting. To do this we have created an interface element not unlike the one featured in ideapi.

You can see the finished example here.

Hopefully you want to know how to recreate this, and as luck has it I'm going to try and show you.

Building the form.

First comes the markup, the best place to start really, as it will inform what you can and can't do. Here is a selection of the most important bits, you can always view source on the example, which you can also access by clicking on download source in the code block.

Creating the widget form (download source)

  1. <body>
  2. <form action="" method="get">
  3. <h2>Employee settings</h2>
  4. <ul id="employee_update_settings">
  5. <li class="employee">
  6. <img src="i/avatar.png" alt="profile image" class="avatar" />
  7. <h3>Dave</h3>
  8. <ul class="employee_options">
  9. <li class="manager">
  10. <label for="manager_dave">Manager</label>
  11. <input type="radio" id="manager_dave" name="employees[manager][]" value="dave" checked />
  12. </li>
  13. <li class="assistant">
  14. <label for="assistant_dave">Assistant</label>
  15. <input type="checkbox" id="assistant_dave" name="employees[assistant][]" value="dave" />
  16. </li>
  17. <li class="remove">
  18. <label for="remove_dave">Sack?</label>
  19. <input type="checkbox" id="remove_dave" name="employees[remove][]" value="dave" />
  20. </li>
  21. </ul>
  22. </li>
  23. <li class="employee">
  24. <img src="i/avatar.png" alt="profile image" class="avatar" />
  25. <h3>Sam</h3>

I've also added the basic CSS, which lays out the form as it would look to any poor soul without javascript enabled or available.

Here is a snapshot of the action, with a link for you to peruse the full thing.

Adding some style (download source)

  1. #employee_update_settings {
  2. padding: 0;
  3. margin: 0;
  4. list-style: none;
  5. }
  6. .employee {
  7. border-bottom: 1px solid #aaa;
  8. background: url(i/panel.png) no-repeat;
  9. }
  10. .employee img {
  11. width: 40px;
  12. height: 40px;
  13. border: 1px solid #ccc;
  14. margin: 5px 5px 5px 7px;
  15. }
  16. .employee h3 {
  17. padding: 0;
  18. color: #444;
  19. margin: -35px 0 20px 60px;
  20. text-shadow: 1px 1px 1px #fff;
  21. }

Yawnsville is just around the corner, so lets divert our attention to the heart of the matter. The javascript that turns this rather timid little form into a man with it's own little bit of Mick Jagger swagger.

If you are a sadist, you can have a look at the full source here but baby steps is the way we like to work, so lets break it down a little and look at a few techniques used in the script, which should make things a little clearer.

Wrapping up your code to keep it warm.

Your code is special, so don't just leave it lying round where it could get into trouble. Where possible you should try to encapsulate and there are javascript idioms that allow you to do just that.

(function ($) { ... })(jQuery); // useful for encapsulating a reference to jQuery

This is the technique that I use for creating a sealed block of code which needs access to jQuery. So I pass a reference to jQuery as a parameter when the function is initialised for use throughout the library.

For a simple demonstration of what this does, consider following snippet:

(function (name) { alert("Hello from " + name); })("Jase")

See for yourself

If you have a browser with a javascript console enabled (for instance firebug or safari with the developer bar enabled) you can copy in the following snippet into the console and it will define a function, and then execute it with whatever you pass into it in the second pair of parenthesis. Alternately you can just click on the snippet above as I've bound it to execute the same script, and hopefully you should see an alert.

Back to jQuery

This idiom is used a lot with jQuery as it allows you to pass in a reference to jQuery into your own closures, which also allows you to use the dollar notation throughout your code library, without worrying about conflicts should another library be introduced to the application that also happens to use the dollar notation (prototype.js for example).

(function ($) { $('.div').show(); })(jQuery);

Writing utility functions

Once you start writing an real length of script, you are going to start repeating yourself. Which is fine whilst you are playing around, but if you have any plans to keep that script around for longer than an afternoon you had better start refactoring.

Even in this little example, I've created two utility functions that encapsulate a little of bit of code that I need to execute over and over in the body of the main script.

Create link helper (download source)

  1. // NOTE: I'm saving a reference to createLink so I can return
  2. // it outside of the wrapper, and use it elsewhere in the
  3. // application, whilst encapsulating its magic.
  4. var createLink = function (el, linkText, classNames, append) {
  5. if (!linkText) linkText = '';
  6. if (!classNames) classNames = '';
  7. var link = '<a href="#" class="'+ classNames +'">'+ linkText +'</a>';
  8. if (append) {
  9. el.append(link);
  10. return el.find('a:last-child');
  11. } else {
  12. el.wrap(link);
  13. return el.parent();
  14. }
  15. }

As you can hopefully see createLink() is a utility to wrap or append an element with a link .. usually to add behaviour a document element.

It will return a jQuery reference to the newly created element so it can be chained or have events bound to it, like so:

createLink($('span')).click(function () { $(this).hide() });

Which will find any span elements, wrap them in a link, which when clicked will hide the link and its descendants (the original span element).

Optional extras

Passing in strings to linkText and classNames as parameters will place their values into the link html and a boolean to append will somewhat expectedly append the link to the element passed into the function instead of wrapping it.

Sister input (download source)

  1. function getSisterInput(element) {
  2. return $(element).siblings('input:radio, input:checkbox');
  3. }

The getSisterInput() function is a little contrived, but these two approaches allow you to create helper methods within your application without polluting the overall object space, and by creating a reference to createLink we return a reference to be used in other parts of the application too.

You can see where I do this a little further down the script, which I've included below .. But you may have to look at that line in context if you want to get the full gist.

Returning created methods to the outside world (download source)

  1. // return our helper method so it can be used outside of this closure.
  2. return {
  3. createLink: createLink
  4. }

As we move onto the widget details, you will see these functions get used, and hopefully you will see their use in context.

An earnest jQuery plugin

Our widget is a standalone bit of code that we want to attach to our list of employees. If you have used jQuery for any length of time, chances are you have used a jQuery plugin or at least the functional level at which they operate. It goes a little bit like this:

$('#employee_update_settings').employeeWidget();

Hopefully the above looks like something you have seen before, basically we want to encapsulate our behaviour into a nice one-liner to use inside the usual $(document).ready() setup when writing jQuery. It also means we can hide the real meat away into a separate file should we wish, and not scare off any of those 'real designers' with our terribly dreary code. Not to mention refactoring it enough so we could use it across multiple projects!

Defining a plugin

A plugin is just a function that you apply to the jQuery api, which is basically a fancy way of saying this:

$.fn.pluginName = function () { ... } ;

A practical example below shows how you could create a plugin that when applied to an element, clicking said element would fade it out.

Simple jQuery plugin (download source)

  1. $.fn.hideMe = function () {
  2. $(this).click(function () {
  3. alert("I'm off, see you later!");
  4. $(this).fadeOut();
  5. });
  6. }
  7. $(document).ready(function () {
  8. $('a.hide_me').hideMe();
  9. });

Our example

Taking what we have just learned, look at the following code. It is the main part of the script which transforms the employee widget into the final version, with a little help from our custom helper methods (some of which we are yet to cover), it should make sense how we bind the different events and animations onto the elements within the employee widget.

You will notice that I'm using $.fn.extend( { pluginName: function .. }) instead of
$.fn.pluginName = function () {} when defining the plugin. It is essentially the same thing, but for more information on the extend method you should consult the jQuery documentation.

The employee widget plugin (download source)

  1. // Here I'm using $.fn.extend to add my functions to the jQuery
  2. // api, this is just a different way to defining a plugin action
  3. // but has the same effect of doing
  4. //
  5. // $.fn.employWidget = function () {}
  6. //
  7. $.fn.extend({
  8. employeeWidget: function () {
  9. // add the js class to the element, so we can
  10. // do specific css to javascript users
  11. $(this).addClass('js');
  12. // you can see we are using app.createLink() a reference to
  13. // our utility class outside of its own closure.
  14. app.createLink($(this).find('.employee'), "options", "toggle_options", true).click(function () {
  15. // grab a reference to the toggle link
  16. var link = $(this);
  17. //toggle the options table up and down
  18. $(this).siblings('.employee_options').slideToggle('slow', function () {
  19. // once the slideToggle has finished, work out whether to add
  20. // the selected class to the options link .. if the options block
  21. // has the css display property of 'block' we know that it is visible
  22. link.toggleClass('selected', ($(this).css('display') == "block"));
  23. });
  24. // always make the link active when it is clicked
  25. // to provide instant feedback to the user (it will update
  26. // once the slideToggle animation has finished)
  27. link.toggleClass('selected', true);
  28. // finally we hide the employee options,
  29. // so they are hidden when the page loads
  30. }).siblings('.employee_options').hide();
  31. // apply our enableLabelToggle to each label in the options list, passing in
  32. // the relevant callback to fire once it is clicked
  33. $(this).find('.employee_options li.manager label').enableLabelToggle(employeeManagerCallback);
  34. $(this).find('.employee_options li.assistant label').enableLabelToggle(employeeAssistantCallback);
  35. $(this).find('.employee_options li.remove label').enableLabelToggle(employeeRemoveCallback);
  36. }
  37. });
  38. })(jQuery);
  39. $(document).ready(function () {
  40. $('#employee_update_settings').employeeWidget();
  41. });

If you are struggling try to read the code line by line, breaking it down as they come, you should see they are just simple jQuery statements in isolation.

However the following functions you won't have come across yet enableLabelToggle() , employeeManagerCallback() , employeeAssistantCallback() and employeeRemoveCallback() which can be found in the final three lines of the plugin above.

The employee widget plugin (download source)

  1. $(this).find('.employee_options li.manager label').enableLabelToggle(employeeManagerCallback);
  2. $(this).find('.employee_options li.assistant label').enableLabelToggle(employeeAssistantCallback);
  3. $(this).find('.employee_options li.remove label').enableLabelToggle(employeeRemoveCallback);

Taking a lowly label & making it shine.

We want our widget to control the form that is submitted to our application, so whatever fancy ui elements we can think up will ultimately have to be translated onto the form inputs underneath.

The first function we need to implement to do this is the enableLabelToggle() a somewhat dubious function name, nevertheless it's principal role is taking a form label and turning it into the toggle links we see in the final example.

Here is the code (I can see you are getting into this now):

Making labels rock (download source)

  1. $.fn.extend({
  2. enableLabelToggle: function(action_callback) {
  3. createLink($(this), "", "action").click(function () {
  4. // grab related input element .. using our lovely
  5. // little utility method.
  6. var input = getSisterInput($(this));
  7. // is the input checked? - (try it in the console)
  8. var selected = !input.attr('checked');
  9. // now we add some behavioural rules ...
  10. var canChange = true;
  11. // make sure we don't uncheck any radio buttons that are selected
  12. if (input.attr('type') == "radio" && input.attr('checked')) canChange = false;
  13. // we add more later, lets stay agile and all that ..
  14. // if all our rules passed, we are good to make the change.
  15. if (canChange) {
  16. // toggle the link's class name depending on input state
  17. $(this).toggleClass('selected', selected);
  18. // toggle the input box
  19. input.attr('checked', selected);
  20. // if the input element is a radio button
  21. // we need to tell the other radio elements
  22. // within the group that they have changed
  23. // so they can update their associated links
  24. if (input.attr('type') == "radio") {
  25. // find any inputs that have the same name,
  26. // as they will be part of the same group
  27. // and call change() as defined in the "each"
  28. // function below
  29. $('input[name="'+input.attr('name')+'"]').change();
  30. };
  31. };
  32. // call the function passed into enableLabelToggle
  33. // using apply to make any references to 'this'
  34. // inside the callback function point to this link.
  35. action_callback.apply($(this));
  36. // return false, so it doesn't do anything else.
  37. return false;
  38. //next we loop through each matching label, and apply the following:
  39. }).each(function () {
  40. // we also want to catch any change
  41. // events on the input elements this
  42. // link controls, so we can update
  43. // the link state if the input changes
  44. // by another event that this link's
  45. // click event
  46. // grab a reference to link for closure
  47. var link = $(this);
  48. // find inputs to bind change event on, then hide them
  49. $(this).siblings('input:radio, input:checkbox').hide().change(function (){
  50. // toggle the link's class name depending on input state
  51. link.toggleClass('selected', $(this).attr('checked'));
  52. }).change();
  53. // I have appended a change() on the end of the change() definition
  54. // to catch any initial states set on the form ..
  55. });
  56. // return $(this) so we can chain the method should we wish.
  57. return $(this);
  58. }
  59. });

A quick summary of what is happening in our enableLabelToggle() function:

  1. Wrap the label in a link and define the onclick function*.

  2. Create a reference to the input we want to toggle.

  3. Find out if the input is currently selected / checked.

  4. Apply rules to figure out if we can toggle the input.

  5. If so, we toggle the input, and update the link.

  6. If the input is a radio element, we update all radio links.

  7. Fire the link callback that is passed into the function

  8. Listen for any input changes (catching any initial states) and to update the links as they happen.

* Yes we could bind the function straight onto the label, but I'm sure that isn't as accessible?

Defining the callbacks

The final piece of the puzzle, is what makes the widget actually useful and I've left it out for you to define in your own application. This is where you fire some ajax call off to update the backend, and then probably update the widget with a result. It is where your widget becomes alive.

Here are the callbacks as defined in our example:

Separating out functionality with callbacks (download source)

  1. var employeeManagerCallback = function () {
  2. console.log("FIRE AJAX FOR MANAGER!");
  3. }
  4. var employeeAssistantCallback = function () {
  5. console.log("FIRE AJAX FOR ASSISTANT!");
  6. }
  7. var employeeRemoveCallback = function () {
  8. $(this).parents().filter('.employee').fadeOut();
  9. }

You should be able to match up what you see in the code to what happens when you click on any of the actions - check your console to see the messages being output (I previously had alerts but I think they detract from the overall experience of the widget example).

Why use callbacks?

The reason why I've separated each function out into a callback, is the manager, assistant and remove actions are undoubtably doing different things.

We could very easily define an ajax request (or whatever the action does) inside of enableLabelToggle() but it would start getting unwieldy and lose focus if we had lots of different actions all with individually defined behaviour. We would also have to check what type of action has occurred (i.e. remove) whereas when we bind the employeeRemoveCallback() to all labels that are associated with the remove input elements ( .employee_options li.remove label ) we are are removing the need to do this. Using callbacks promotes cleaner code and provides us with greater flexibility and perhaps most important of all it increases readability.

Adding style to our behavioural additions.

You may remember in our widget plugin, we added a class to our employee list called 'js' which we can use to hook additional CSS declarations onto once the javascript has been attached. You can see these at the bottom of the stylesheet linked to the example page, but here is a quick snapshot of some of the additions to the CSS.

Uprating our widget (download source)

  1. /* style the action buttons */
  2. #employee_update_settings.js .employee ul.employee_options {
  3. height: 50px;
  4. padding: 0;
  5. margin: 0;
  6. }
  7. #employee_update_settings.js .employee ul.employee_options li {
  8. padding: 0;
  9. display: inline;
  10. position: relative;
  11. }
  12. #employee_update_settings.js .employee ul.employee_options li a.action {
  13. width: 38px;
  14. height: 0px;
  15. display: block;
  16. background: url(i/actions.png) 0 -39px;
  17. position: absolute;
  18. top: 5px;
  19. left: 0;
  20. padding-top: 30px;
  21. text-align: center;
  22. text-decoration: none;
  23. text-shadow: 0.1em 0.1em 0.2em #999;
  24. }
  25. #employee_update_settings.js .employee ul.employee_options li a {
  26. color: #666;
  27. }

Wrapping up

So there you have it, all the elements needed to create a degradable interface elements that not only enhance the user experience but also degrade gracefully for less able devices.

Just to recap you can see the finished example here.

Hang on a minute.

You may think that we have written a hell of a lot of code to achieve what could be argued as something that does very little? Should you say:

Surely Jase, this is why the jQuery UI project exists?! - A frustrated designer

Well yes, you can get away with the crafty one-liners we all love but sometimes they just don't exist, or you end up writing boat loads of bridge code just to get it working the way you need. I'd probably say:

You need to make something bespoke, baby. - A wily Jase

In reality once you get to grips with jQuery internals, you will be writing these kind of enhancements a dime a dozen. You will also find that you can write a really complex yet elegant interface elements in 100 lines that would normally take you five or ten times that with vanilla javascript. Best of all, its 100% degradable and unobtrusive so it makes everybody happy.

Thanks for reading

This has been my first article (of 52 this year), and I hope it wasn't too overwhelming and you could get through it fairly easily (extra well done if you got this far without skipping!).

Any comments feel free to send me an email, or message me on twitter and I'll try to respond.

Want something similar on your project?

I'm for hire and also available as part of the best team in town.