Recent conversation with the PHB regarding internal web application project:
PHB: This is all good, but these 4 items here need to have a “not available” option
Me: Ok, no problem. I’ll change it into a yes, no, n/a radio buttons instead of check boxes
PHB: Oh, no – I like the check boxes. It’s just that sometimes I want to be able to say N/A there
Me: So… You want like two check boxes?
PHB: No, just one. But with N/A option.
Me: But… Check boxes just don’t work like that – they are on/off kind of thing
PHB: Sure they do. I saw it on several websites.
Me: Oh, ok… Can you send me a link? I’d like to see how they did it.
PHB: Oh, hmm… It was a while ago. If I remember where I saw it, I’ll shoot you an email.
So… Any of you know how a 3-state checkbox looks like? Cause it beats me but powers that be seem to be dead set on having one. I could try to argue in favor of radio boxes or something more sane, but it seemed like a waste of time. So instead I decided to make it work. Just how?
I figured it would be like a toggle switch that oscillates between 3 states. Regular HTML check boxes can only be checked or un-checked so I gave up on them initially. I figured I could use a button as a toggle. It would have an attached onClick script which would change the button’s appearance and then modify a value of some hidden input field. Then I realized it was stupid. Not only was it too complex, and hackish but also completely failed if Javascript was disabled.
I really needed the form to at least half-work without Javascript – for example just default to standard on/off check-box forgetting about n/a option. And for that I needed to stick with the HTML check boxes. I would have to style them or change them up somehow. So I searched for some code that would let me do it with minimum effort and minimum dependencies.
BrainError’s script seemed like a good fit. He wrote some code that allowed me to replace check boxes with images on the fly which is really what I wanted here. Naturally I had to modify it to fit my needs. You can see the changes below:
var inputs;
var checked = 'checked.png';
var unchecked = 'unchecked.png';
var na = 'n-a.png';
function replaceChecks()
{
//get all the input fields on the funky_set inside of the funky_form
inputs = document.funky_form.getElementsByTagName('input');
//cycle trough the input fields
for(var i=0; i < inputs.length; i++)
{
//check if the input is a funky_box
if(inputs[i].className == 'funky_box')
{
//create a new image
var img = document.createElement('img');
//check if the checkbox is checked
if(inputs[i].value == 0 )
{
img.src = unchecked;
inputs[i].checked = false;
}
else if(inputs[i].value = 1 )
{
img.src = checked;
inputs[i].checked = true;
}
else if(inputs[i].value = 2 )
{
img.src = na;
inputs[i].checked = true;
}
//set image ID and onclick action
img.id = 'checkImage'+i;
//set image
img.onclick = new Function('checkChange('+i+')');
//place image in front of the checkbox
inputs[i].parentNode.insertBefore(img, inputs[i]);
//hide the checkbox
inputs[i].style.display='none';
}
}
}
//change the checkbox status and the replacement image
function checkChange(i)
{
if(inputs[i].value==0)
{
inputs[i].checked = true;
inputs[i].value = 2;
document.getElementById('checkImage'+i).src=na;
}
else if(inputs[i].value==1)
{
inputs[i].checked = false;
inputs[i].value = 0;
document.getElementById('checkImage'+i).src=unchecked;
}
else if(inputs[i].value==2)
{
inputs[i].checked = true;
inputs[i].value = 1;
document.getElementById('checkImage'+i).src=checked;
}
}
window.onload = replaceChecks;
This is the script. To get it working all I need is to create a form with a name "funky_form". The original script looped through all the input fields on the page which seemed like an overkill in my case. I'm also applying this formating to the check-boxes with the "funky_box" style. My form may have 60-80 input fields, many of which are check-boxes, but only 4 or 5 of them in a very specific section need to have this behavior. I was fine with using regular check-boxes for the rest of them.
It's also important to add that the getAttribute method does not work in IE when you try to grab the class of your element. BrainError was using it to select on type, which was working well. I'm much more selective, and it took me a while before I realized that IE borked this method. Fortunately you can use the className property instead - and it works equally well in FF as it does in IE.
The form would look something like this:
If you want to see it in action, check out the working demo.
All my boxes all have distinct names which intended. This way I can individually access via the $_POST array from PHP when the form submits and extract their value as needed. You may also have noticed they all start checked. This is actually a requirement in the spec, but also a fall-back measure.
If you disable Javascript you will see regular check-boxes. They start with value 1, and if they are not checked (and thus not present in $_POST) they will default to 0. This way you can submit the form normally - you just won't be able to set them to n/a.
There is a small downside - when the page loads, you can see the regular check boxes for a brief second or two. But that's just the way this script works. Suggestions and corrections are welcome. I tested this in Firefox 2.x and IE 6.x+. Let me know if it fails miserably in your browser of choice. :)
[tags]javascript, check-box, 3 state check box, web design, html, scripting[/tags]
You did a good job with a silly request. Maybe your PHB was referring to a check box that would grey out as an alternative. For example, if the question was is your dog a chihuahua, a check would indicate yes, an empty box would indicate no, and a greyed-out box would indicate n/a (i.e. you have no dog). I know I’ve seen those around, but I like your solution just as well.
My 2 cents. To see a three-states checkbox, just select anything on your desktop, rightclick, properties, and go look at the permissions parameters – read only on windows, or executable on linux. If you have selected files/folders of both types, you can see the third state.
Thanks for your site, is a resource ^_^
-Jaba
Gosh, I wish I could have been here sooner to help you… I could have finnally got that geek point.
I have seen check boxes, (I think they were in an FTP program, filezilla is the only one i ever use) but you can check a box 3 times one will put a dot then twice will make a square and three times will bring it back to blank.
I will look more into it, but this might be what he is thinking.
Basically the script you has does exactally that (and works on newest version of firefox on Windows XP if you wanted to know)
http://www.geeksparadox.com/filezilla3checks.png
BTW THIS IS YOUR 9000th COMMENT! WOOT!
Damn never mind.. it said in preview i was… oddly enough my Anti-Spam word = Fail
:(
Good job indeed. What did your PHB said?
I have to point out another small downside though: keyboard navigation didn’t work for me. Depending on your application this can be quite a drawback for the user interaction.
I don’t know if it is possible but maybe you could use the property “disabled” of the actual checkboxes to achieve that for both mouse click and keyboard navigation.
Ah! So these things do occur in nature! Btw, what is the difference between the ckeck and the square in the Filezilla? It seems like unnecessary feature in that dialog, but I might be wrong.
The problem, the way I see it is that there is no easy way to do this sort of thing with a regular HTML check box. It is actually the one element that is very difficult to style. So replacing it with image controls seemed like a good idea.
Ricardo – good point about the keboard navigation. I wonder if I can somehow make the images a selectable part of the form… It might be tricky.
Btw, disabling regular check boxes doesn’t work because then their value doesn’t get submitted even if you “check” it with javascript. :P
@Travis – nice failget! LOL
Btw, when I preview my comment, it also says 9000 – and I think I know why. The post number is, incidentally the primary key of the comments table. So you don’t really know what it is going to be without either committing the post or querying the db which seems like an overkill for the preview function.
Works fine in Konqueror.
I kind of like the trick.
For your images, instead of a greyed-out cross, I’d use a short light grey dash. I think that’d eliminate some enduser confusion about the difference between the red and grey X’s, since we all know that users never look at legends.
Plus the different shape would assist people with poor colour discrimination, which is fairly common.
Seems to work fine in Opera as well.
Tomarrow morning I can test IE7
@vacri – good point about the dash. I didn’t think about that, but you are right – a gray x may be to confusing or too similar to the red x for some people.
It’s interesting just how many faults you can pick up in a UI if you consider yourself to be a 50-60 year old user, who generally have poorer eyesight and coarser hand-eye coordination. Things that needlessly require subtle visual discrimination and small mouse targets stand out. I’m currently fighting over icons with my devs who are all 20somethings and have fine vision but the software will be used by 20-50somethings. It’s the old fight about form vs function. :)
My pet example to hate is iTunes-for-Windows which forces you to use their UI. Tiny tiny menus that are both hard to read and are small targets. Ironic for a company that prides itself on usability, to override the chosen theme of users who may have a real need a particular theme.
checked – set the permission (changes permissions)
unchecked – remove the permission (changes permission)
dotted (? :P) – do not modiffy the permissions (dosn’t change the permission)
Ah, so that the dotted thing means. :) Thanks!
Are you insane?
Have you really never seen disabled form elements?
NO REALLY
wtf.
Also, when something is ‘half’ selected – if you select multiple files in windows, and set the read only attribute for instance… it will be clear, you can check it, and then make it clear.
If some are read only and others not, then the initial state will be half shaded. You can then clear or check it, or again half shade it.
But your ‘No, just one. But with N/A option.’ means a disabled checkbox.
your captcha uses shell commands, how awesome
[quote post=”2322″]Have you really never seen disabled form elements?[/quote]
No, I’ve seen them. It’s just that the value of a disabled checkbox is not sent when the form is submitted. So a disabled box is virtually indistinguishable from an un-checked box when you examine the POST arguments.
What I needed to do was to capture 1 out of 3 distinct states. So yeah, it makes perfect sense until you actually sit down and try to implement it this way.
Oh, and if you were wondering why I didn’t just “style” the checkboxes with CSS it’s because it’s virtually impossible to do so. Most browsers gleefully ignore any styles applied to check-boxes and radio buttons.
So this is really not as insane as it looks like. It’s just hard to see the method in our madness from the outside sometimes. ;)
Why are you using “label” if you don’t use ID attribute in “input”. “label” works with ID *not* NAME attribute.
Now, if we fix this error, and if we click on the text label, the checkbox internal state can be unchecked by the user and checkbox value is no more sent by the brower.
It’s very easy to test by adding this php line: print_r($_POST);
Thanks for the tip Mx!
Grate Job!
I found a small bug in function replaceChecks()
lines:
else if(inputs[i].value = 1 ) &&
else if(inputs[i].value = 2 )
should chnage to:
else if(inputs[i].value == 1 ) &&
else if(inputs[i].value == 2 )
A nice-to-have would be to use the labels as they would work with a regular checkbox (as in: they’re a click target, too).
I would have started with a set of three like-named radio buttons as your original HTML. This would allow the fallback (no JS) to support the proper functionality of submitting 3 distinct states. Just replace the set of radio buttons with your current solution onload for the desired behavior. Reducing functionality of devices that don’t support (or have disabled) JavaScript should only be a worst-case scenario. In this case, there is a better fallback solution.