Showing posts with label usability. Show all posts
Showing posts with label usability. Show all posts

Tuesday, April 14, 2009

Connecting labels to inputs using dojo

When using dijit, you have to be careful not to include any id elements in your template HTML file. Otherwise, you can end up with elements with the same id in your document which can lead to a lot of head scratching. Having been bitten by the "multiple elements having the same id in my document" bug, I started stripping out all ids from my template files, replacing them with dojoAttachPoint attributes.

All was going fine and I wasn't getting mixed up in id clashing anymore. However, there was one drawback - my labels wouldn't work anymore. Code which previously looked like this:

<li>
<input id="simple" type="radio" dojoattachevent="onclick: simpleClicked"/>
<label for="simple">${statics.i18n.simple}</label>
</li>

no longer worked because it had changed to this:

<li>
<input dojoAttachPoint="simple" type="radio" dojoattachevent="onclick: simpleClicked"/>
<label for="simple">${statics.i18n.simple}
</li>

The label's for attribute expects to reference an id and not a dojoAttachPoint. Previously I was able to toggle the 'simple' checkbox input by clicking on the label. I always tend to click on labels instead of radio/checkboxes (mostly because they're bigger and easier to click), so I saw this move away from id-based inputs as a degradation in the usability of the web application.

So, I hacked together a piece of code which is invokable after every widget is set up and appended to the dom:

connectLabelsToInputs: function(query, widget) {
dojo.query(query, widget.domNode).forEach(function(item, index, array) {
if(widget[item.getAttributeNode("for").value] && (widget[item.getAttributeNode("for").value].type == "checkbox" || widget[item.getAttributeNode("for").value].type == "radio")) {
var attrs = widget[item.getAttributeNode("for").value].attributes;
for(var i = 0; i < attrs.length; i++) {
if(attrs[i].value.indexOf("onclick") != -1) {
dojo.addClass(item, "clickable");
dojo.connect(item, 'onclick', widget, dojo.trim(attrs[i].value.substring(attrs[i].value.indexOf(":") + 1, attrs[i].value.length)));
}
}
if(!dojo.hasClass(item, "clickable")) {
dojo.addClass(item, "clickable");
dojo.connect(item, "onclick", dojo.hitch(widget, function() {
widget[item.getAttributeNode("for").value].checked = widget[item.getAttributeNode("for").value].checked == false ? true : false;
}));
}
}
}, widget);
}

This code makes the assumption that you have a class called 'clickable' which looks something like this:

.clickable {
cursor: pointer;
}

So, if I know that a widget I just created contains labels and checkboxes or radio buttons, all I need to do is invoke:

common.connectLabelsToInputs("fieldset ol li label", this);

and my labels are clickable again.

Friday, March 13, 2009

Listening for pasted input

A lot of the input forms I work on defaults the "Submit" button to disabled. Added to client side validation, this default usually prevents the user from submitting forms without required fields. I usually attach onkeyup events to each of the form fields to see whether I should enable the "Submit" button for a particular form.

However, onkeyup doesn't catch everything. If the user pastes input into a field using their mouse, onkeyup doesn't catch that event. So, the user is left with an input field which appears filled, but the "Submit" button remains disabled.

The solution is pretty simple. You can attach something like this to your input fields:



<input type="text" dojoAttachEvent="onkeyup: checkSubmitButton, oninput: checkSubmitButton, onpaste: checkSubmitButton">



More on oninput and onpaste

Monday, March 2, 2009

Catching dijit's Dialog close event

I tend to use dijit Dialogs whenever I need the user to make a decision before proceeding with any other actions. They're especially useful for server error conditions and asking the user how they'd like to react to something like an API failure.

However, popping up dialogs can be annoying to users. I know I'm guilty of scanning dialog text and just hitting the 'x' button and ignoring the message. There are two ways you can prevent the user from doing this with dojo.

1. The most obvious solution: Hide the 'x' button. This is easily done with some simple CSS:


#myDialog .dijitDialogTitleBar .dijitDialogCloseIcon {
display:none;
}


The one drawback to this is that the user can hit the escape button and that'll close the dialog.

2. Attach the following two callbacks to your dialog:


this._userClosedDialogHandle = dojo.connect(dijit.byId("myDialog"), "hide", this, "_handleCloseDialog");
this._userClosedEscDialogHandle = dojo.connect(dijit.byId("myDialog").containerNode, 'onkeypress', function (evt) {
key = evt.keyCode;
if(key == dojo.keys.ESCAPE) {
this._handleCloseDialog();
}
});


The one caveat with this method is that you need to disconnect both handles in the _handleCloseDialog method. Otherwise, you'll find yourself in an infinite loop pretty quickly.

Sunday, February 8, 2009

dojo and the enter key

Attaching a listener for the enter key on a form can help a lot from a usability perspective. If your users are more familiar with tabbing through a forms using only keys, asking them to use the mouse just to submit the form can be a little irritating. dojo comes to the rescue with a simple dojoattachevent. All you need to do is add the a dojoAttachEvent to your template file:


dojoattachevent="onkeyup: checkForEnter"


Then, in your widget logic, all you need to do is:


checkForEnter: function(keyEvent) {
if(keyEvent.keyCode == 13) {
if(!dojo.isFF) {
if(this._validateForm()) {
this._createAccount();
}
}
}
}


Oddly enough, Firefox is able to recognize the enter key and submits the form all by itself.

dojo campus has quite a few keys defined so you can capture a lot of what your users are trying to achieve with your web application.