All in the <head>

– Ponderings & code by Drew McLellan –

– Live from The Internets since 2003 –

About

Coping With Internet Explorer's Mishandling of Buttons

30 July 2008

One of the more exasperating quirks of Internet Explorer is the way it mishandles BUTTON elements. If you’re not all that familiar with HTML buttons (and don’t be ashamed, it’s not all that widely used) it’s a very useful element. Unlike regular the INPUT with its type set to submit which displays its value as a textual label on the UI element, a BUTTON can have both a value and contain a mixture of text, images and what have you.

Let’s look at an example:

<button type="submit" name="delete" value="1234"><img src="delete.gif" width="16" height="16" alt="Delete" /> Delete message</button>

When a user activates the button, the form would be submitted containing an item named “delete” with a value of “1234”. On the server side, you can then pick up that a delete button has been activated, and from the value you know which item you should be deleting. All the while, the user interface displays an attractive button with an icon and some call-to-action text.

The problem with IE

I don’t claim to know anything about the internals of a browser, but I sort of understand how CSS bugs occur. The spec is open to interpretation in places, and generally laying stuff out is a hard problem. There are lots of factors to consider. Sometimes even writing CSS for a given layout can be hard, so it’s a bit mind-blowing to think how difficult a job it is to translate that into graphical elements in a window. Bugs happen, but really I’m surprised all works so well at all.

When it comes to something like a form element, things are more clear-cut. Most elements have a value attribute, and it’s the contents of that attribute that get sent to the server. That’s the same for text fields, checkboxes, submit buttons – the only one that stands out as different is textareas. If you read the spec for the BUTTON element, the description for the value attribute even says:

value CDATA #IMPLIED — sent to server when submitted —

Seems pretty clear. However, what IE does is send the innerHTML value of the button. Just like if it were a textarea, really. The content of the value attribute is discarded entirely. The result from the above example, remembering that IE handles HTML all in uppercase, would be something like:

delete=<IMG SRC="delete.gif" WIDTH="16" HEIGHT="16" ALT="Delete" /> Delete message

From my testing with the current beta of IE8, it looks like this bug has been fixed. Thank goodness. But how can we deal with the problem today for IE6 and 7 users?

Working around the issue

I encountered this problem last night on an e-commerce project. Each item in the cart had a “Remove item” button, that although styled as a link, needed to be a button as its activation makes a change to the user’s cart. As my cart display was wrapped in one big form (each item had a quantity field) I was using a BUTTON element with its value set to the product code in order to detect which item to remove.

<button type="submit" name="remove" value="ABC123">Remove item</button>

In Firefox, this would being submitted as remove=ABC123, which was enabling me to both detect that a removal button had been activated (by the presence of the remove item in POST) and the product code of the item to remove form the cart (using the value). In Internet Explorer, the same action resulted in

remove=Remove item

As no product in the cart had a product code of “Remove item” the action was failing. My work-around to the issue is not elegant but did enable me to get the show back on the road. I amended my HTML to include the value inside the button, and wrapped the product code inside a SPAN:

<button type="submit" name="remove" value="ABC123">Remove item <span>ABC123</span></button>

Borrowing from a common accessibility technique, I then threw the SPAN off-screen with CSS, resulting in no visible change to the button. Without CSS or for non-visual users, the text used for the button is still acceptable for my purposes. This meant that when posted, the field came through as:

remove=Remove item <SPAN>ABC123</SPAN>

All I needed to do was to add a quick test in my form handler to look for the presence of the string <SPAN> in the value, and if present, perform a quick string manipulation to extract the product code.

Not pretty, but also not harmful, and it worked.

Addendum

My friend and former colleague David Dorward dropped me a note to explain that things get even more complex with IE6. Apparently that browser will send all buttons in the POST, regardless of whether they’ve been activated. In my case, that could mean all items being removed from the cart, which would be bad.

So if you’re still building for IE6, it may be back to the drawing board. Thanks David!

- Drew McLellan

Comments

  1. § David Dorward:

    Another approach is to forget about the value and just hide all the data in the name, and then approach the form data with regular expressions.

    For example (using Perl with CGI.pm)

    my %params = $q->Vars;
    my ($action) = map { /^remove_(.+)$/ } keys %params;

  2. § Drew:

    Good tip, David. That’s often what I do with complex arrangements of things like checkboxes. In this instance, I wanted to follow the spec where browsers supported it, and just add a workaround for IE.

  3. § Kari Pätilä:

    It gets even weirder if you try to use an input type=“image”.

  4. § Ozbon:

    I may be missing the point here, but I find that usually if I want to delete something from a list/set of options, I use something like

    <a href=’/delete/item/[item id]’ title=‘Delete this item’>
    <img src=’/images/icons/delete.png’ class=‘icon’ alt=‘Delete item’/>
    </a>

    and do the delete thing in a separate script/function (usually using .htaccess to rewrite /delete/item/id to delete.php?type=item&value=$id ) rather than letting the form handling stuff do it.

    That way it works cross-browser, and doesn’t have any issues with IE being utter $#!+

  5. § Drew:

    @Ozbon: In the situation where you have a list of something like articles in a CMS, I’d normally have a link to a separate delete page, and have a form on that page asking ‘Are you sure?’ with a submit button to confirm. For removing items from a cart, that extra level of confirmation is unnecessary and a stumbling point.

    I’d not normally use a link to directly delete an item, as GET requests are supposed to be ‘safe’. There should be no consequence of clicking on a normal link, other than to retrieve another page or file. Users should be able to click on a regular link a million times without anything changing. (There’s more on this in the HTTP 1.1 spec.) So if you’re deleting anything, it should always be a POST.

  6. § Ben Ward:

    @Obzon: This is what I used to do. Apparently, it’s deprecated by the W3C, and as we’re talking about standards here, then I suppose we should follow. In practical terms, if Mr Google Accelerator or his friends come to visit, this could potentially delete anything.

    This is why Ruby on Rails does lots of mucking around to make POST links.

  7. § Ben Ward:

    what he said ^^^^

  8. § Ozbon:

    Fair enough. My bad. :-)

    Admittedly, I usually do this for a list of contacts or whatever, or on a cart. I usually do check first – as part of delete.php , and if they decide not to delete, it just rolls back to the sending page. (There’s also usually checking that the item ‘deleted’ is owned by the logged-in user etc. of course)

    But I take your point regarding GET v. POST . Cheers!

  9. § Jeremy Keith:

    My solution is to avoid the problem. I use the BUTTON element a lot but always just as a button for submitting a from; never for sending information. In the situation you describe, Drew, I would use:

    <input type=“hidden” name=“delete” value=“ABC123” />
    <button type=“submit”><img src=“delete.gif” width=“16” height=“16” alt=“Delete” /> Delete message</button>

    I would go as far as to say that if you’re explicitly putting type=“submit” on a BUTTON element, then it definitely shouldn’t be used for sending data.

    The more problematic issue is the situation with multiple BUTTON elements and IE’s inability to distinguish between them. Again, my solution is to avoid the problem: I never use more than one BUTTON element in a form (I use input type=“submit” or input type=“image” instead).

  10. § Drew:

    In one way I agree with what you’re saying there, Jeremy. How would you deal with the shopping cart scenario, where you have one form and multiple buttons to remove individual items from the cart?

    It’s not so much about sending data, as identifying which button was used to submit the form.

  11. § Matt Wilcox:

    Jeremy, that’s a viable solution if you’ve got one form per item to be deleted – but not if it’s many items inside one form. How would the parsing script know which hidden input to use?

  12. § Ben Darlow:

    Google following deletion links aside, I would assert that having links that perform actions like this isn’t necessarily harmful. There’s no semantic reason why a POST should be interpreted as a delete action and that a link shouldn’t; technically these two examples are only related to sending information to the server and retrieving information from the server, respectively (hence the existence of a DELETE HTTP method). But since DELETE isn’t widely supported and we have to work with what we have, it becomes more about how you present your deletion mechanism, and how you handle the behaviour surrounding them (confirmation at the client side? server side confirmation as a fallback?). It’s perfectly possible to make a link that looks and behaves more like a button, and vice versa, so we have a lot of scope for guiding the user through appropriate styling.

    Anyway, that’s sort of orthogonal to the subject of the article. Curious stuff; I’d always thought BUTTON was a proprietary Internet Explorer element — how wrong was I!

  13. § Eddie Welker:

    Jeremy’s solution could easily be extended to a multi-item case. You’ve got a form, one hidden input (what to delete) and a number of buttons. Give each button a value=“whatever” attribute for the item to be deleted. Even in the Javascript IE will try to return the innerHTML for “button.value”, so you access the value attribute using “button.getAttributeNode(’value’).nodeValue;” (the button has an array of attributes… you simply get the one named “value”). Once you’ve got the value, simply update the hidden input with that value, and you know exactly what to delete.

  14. § Drew:

    @Eddie: a solution that relies on JavaScript isn’t a solution.

  15. § Tyler:

    This bug in IE is rather annoying but thankfully it will be fixed in IE8 (see here)
    http://webbugtrack.blogspot.com/2007/10/bug-341-button-element-submits-wrong.html

    There are some hacks to set/reset the innerHTML just before the submit but they are very clumsy and tend to make the screen flicker oddly.

  16. § Jeremy Keith:

    Drew, for the situation with your shopping cart, I would use different forms:

    <form method=“post” action=”/path/to/update”>
    <input type=“text” name=“quantity” value=“1” />
    <input type=“hidden” name=“item_id” value=“ABC123” />
    <button type=“submit”><img src=“update.gif” width=“16” height=“16” alt=“Update” /> Update message</button>
    </form>

    <form method=“post” action=”/path/to/delete”>
    <input type=“hidden” name=“item_id” value=“ABC123” />
    <button type=“submit”><img src=“delete.gif” width=“16” height=“16” alt=“Delete” /> Delete message</button>
    </form>

    <form method=“post” action=”/path/to/update”>
    <input type=“text” name=“quantity” value=“1” />
    <input type=“hidden” name=“item_id” value=“DEF456” />
    <button type=“submit”><img src=“update.gif” width=“16” height=“16” alt=“Update” /> Update message</button>
    </form>

    <form method=“post” action=”/path/to/delete”>
    <input type=“hidden” name=“item_id” value=“DEF456” />
    <button type=“submit”><img src=“delete.gif” width=“16” height=“16” alt=“Delete” /> Delete message</button>
    </form>

    But if the situation is that you must have one form but multiple elements that submit the form, then I would not use the BUTTON element. I know that sounds like a copout but good programmers are lazy programmers, right? ;-)

    It’s kind of like the old joke about the guy who says to the doctor, “it hurts when I do this” and the doctor’s response is, “don’t do that.”

  17. § John Sutherland:

    @Drew: like Jeremy I use a hidden input to send any data. Then to handle multiple items (like your shopping cart example) I’d use multiple forms with a single hidden input and a single button in each.

  18. § Mike Stenhouse:

    @Matt Wilcox: In that case IE would submit every button element anyway, as if they were regular inputs rather than user controls (I think), triggering all add/delete actions on every item. I seem to remember struggling with this years ago and writing off the poor button tags.

  19. § Ciaran McNulty:

    If you include angle brackets in your @name (sorry I’m a Textile n00b) you can get a fairly useful input:

    <button name=“delete[1234]“ value=“delete”>Delete this item</button>

    In both IE and Firefox, the delete array in your request will have its 1234 key set.

  20. § Jeff:

    @Drew, actually I think JS would be a perfectly valid solution to this. Especially wrapped in IE6/7 conditional tag. Just have it, on submit, change the innerHTML value to the button value.

    JS is legit solution because:

    1) IE 6/7 will have JS enabled.
    2) It’s actually nicer, IMO, because you don’t have to muck up your HTML & CSS for everyone (especially screen readers) simply to fix the one bug in IE.
    3) Someday, down the road, when we no longer have to worry about IE6/7 (ya know, in like, 10 years, sigh) you remove that single workaround JS script and forget about it.

  21. § Drew:

    @Jeff:

    1) IE 6/7 will have JS enabled.

    How do you know that?

  22. § Mike Busch:

    @Jeff I have to agree with Drew. Any “solution” that depends on Javascript is NOT a solution. Granted in 95% of cases it will work. But for the other 5% that have it disabled for various reasons, they won’t be able to delete items from their shopping cart… which will inevitably make them leave your ecommerce site, and then YOU/YOUR CLIENT don’t get the customer’s money.

    When your business model is “selling things”, you want to eliminate (not just lower) any bars to completing a sale, and that means making all the functionality of your site work all the time for 100% of users. Even for people browsing in IE6 with JS turned off.

    I rather like Jeremy’s solutions having mutliple forms if the delete function must be done as a form submit. Otherwise, why not just use a REST style link?

  23. § Jeremy Keith:

    Mike Busch said:

    “Otherwise, why not just use a REST style link?”

    Because delete actions should never, ever, ever be RESTful. Nor should any action that updates anything on the server (adding, editing).

    As Drew said:

    GET requests are supposed to be ‘safe’. There should be no consequence of clicking on a normal link, other than to retrieve another page or file. Users should be able to click on a regular link a million times without anything changing. (There’s more on this in the HTTP 1.1 spec.) So if you’re deleting anything, it should always be a POST.”

  24. § David House:

    To all those who suggested use of multiple forms: that approach won’t work in the situation Drew is describing. From what I gathered, Drew was working on a page that would display all the elements in the user’s cart, with a button to delete each next to each item. However, there would be additional controls at the bottom to perform actions on the entire cart at once: “Proceed to checkout”, “Clear cart” and so on. Or maybe each item has a checkbox, and you include buttons at the bottom for performing mass operations (increase quantity, delete, etc.) on each checked item.

    (This second example is perhaps better as it excludes options like getting the entire cart from $_SESSION or including the buttons at the bottom in a form on their own, with a list of hidden inputs containing the details of the items; in the case of the checkboxes, you’re not necessarily acting on all items in the cart.)

    So you in fact need one large form.

  25. § Howie:

    I tend to agree with Jeremy: Use multiple forms and a hidden field

    The other option not mentioned is to use multiple submit buttons, each with a unique value. Only the value of the selected submit button is actually posted.

    I tensd to use both approaches with pages showing lists of items with multiple events (add/delete etc.). So, for a shopping cart, I would have one form per item, with a hidden field for the item id, plus multiple submit buttons for the add/delete events.

    Of course, this doesn’t solve the issue of using an image for the button. But then you could perhaps use <input type=“image” … /> I’ve not used this in about 8 years; remember imagemaps?

  26. § Chris:

    I know I’m late the party, but did you consider using checkboxes instead of buttons? Presumably, your restricted to one form element because you want to allow people to update the quantities of the items in their cart, yes? Using checkboxes would also allow customers to delete multiple items from their cart at once while enabling you to use only one form element and one submit button.

  27. § Stefan Coetzee:

    You don’t need multiple forms. You can use a single hidden input to store the value or index to the variable which needs to be deleted using javascript onClick attribute. For example:

    <form name=“shopping_cart” method=“POST” action=“http://www.example.com/shopping/”>

    <input type=“hidden” name=“itemtodelete” value=”“>

    </form>

    Then somewhere on the page you have this button (doesn’t even have to be in the shopping_cart form):

    <button type=“submit” name=“delete” value=“Delete” onClick=“javascript:document.forms.shopping_cart.item_to_delete.value=‘ABC123’;document.forms.shopping_cart.submit();”> Delete </button>

    After button is pressed, the Request will contain a key called itemtodelete with the value of the item to be deleted.

  28. § Drew McLellan:

    @Stefan: a solution that relies on JavaScript isn’t a solution.

Photographs

Work With Me

edgeofmyseat.com logo

At edgeofmyseat.com we build custom content management systems, ecommerce solutions and develop web apps.

Follow me

Affiliation

  • Web Standards Project
  • Britpack
  • 24 ways

I made

Perch - a really little cms

About Drew McLellan

Photo of Drew McLellan

Drew McLellan (@drewm) has been hacking on the web since around 1996 following an unfortunate incident with a margarine tub. Since then he’s spread himself between both front- and back-end development projects, and now is Director and Senior Web Developer at edgeofmyseat.com in Maidenhead, UK (GEO: 51.5217, -0.7177). Prior to this, Drew was a Web Developer for Yahoo!, and before that primarily worked as a technical lead within design and branding agencies for clients such as Nissan, Goodyear Dunlop, Siemens/Bosch, Cadburys, ICI Dulux and Virgin.net. Somewhere along the way, Drew managed to get himself embroiled with Dreamweaver and was made an early Macromedia Evangelist for that product. This lead to book deals, public appearances, fame, glory, and his eventual downfall.

Picking himself up again, Drew is now a strong advocate for best practises, and stood as Group Lead for The Web Standards Project 2006-08. He has had articles published by A List Apart, Adobe, and O’Reilly Media’s XML.com, mostly due to mistaken identity. Drew is a proponent of the lower-case semantic web, and is currently expending energies in the direction of the microformats movement, with particular interests in making parsers an off-the-shelf commodity and developing simple UI conventions. He writes here at all in the head and, with a little help from his friends, at 24 ways.