One of the most frustrating things about working with .NET from a front-end developer’s viewpoint is the Single Form Model. Enclosing an entire website or web-application in one single <form> element poses a number of accessibility and usability problems surrounding form input and usage. One of these is ensuring the correct default actions are assigned to sets of input fields when the enter key is used.

Traditionally, the default action for a <form> is to fire the first submit button found within the current <form> element. Every form has one default action.

Striking the enter key within a text input field should submit the current set of—logically grouped—fields; this is the expected behaviour. For pages with multiple forms and actions, this is easily separated by having multiple <form> elements, each with their own submit buttons and actions. Each form operates independently, has its own default action, and doesn’t interfere with other forms.

In the Single Form Model, the presence of just one <form> element, means that different default actions cannot be easily separated. Every input field on the page is automatically tied to just one default action – the first submit button on the page.

Take a blog site as an example – like this very page! It has a search form at the top, with associated submit button, a comment form further down the page and perhaps another form for signing up to a newsletter. Implemented with the Single Form Model, only the search form will produce the correct behaviour as it introduces the first submit button on the page. All subsequent fields will be tied to this same button as their default action – submitting a comment by pressing enter would cause the search form to submit, as would signing up to a newsletter. Not particularly useful.

Can we cure this problem?

The answer is yes… partially. The problem we have concerns UI behaviour. Any solution needs to manipulate and override behaviour, and this is the domain of Javascript. We can’t manipulate the markup in our favour as we’re under the control of the Single Form Model (not without a complete switch to .NET MVC anyway). But there’s a conflict here—javascript is not a fully accessible technology. There are many environments where users do not have Javascript available.

However, in this case, using Javascript is an acceptable answer—this is progressive enhancement of sorts. We’re not adding a dependency on Javascript, merely enhancing the usability of the form inputs for those users with it enabled. Javascript-disabled users will still be able to use the form, except they will experience the ‘broken’ behaviour. Form accessibility in .NET is pretty horrendous anyway, so anything we can do to make improvements is better than nothing at all.

Doesn’t .NET provide a solution already?

Yes, it does in version 2.0 and above. This is the defaultbutton attribute, which can be used in <asp:panel /> and <form>. Fine, so why not use this? There’s no real reason not to, if you’re starting from scratch and don’t mind crufty .aspx pages.

But this wasn’t suitable for me. In an existing codebase, this requires a lot of extra change, and the addition of specific panels to group related sets of form fields is completely unnecessary. An HTML element already exists for that: <fieldset>. Yes, the mighty fieldset-who would have thought?! In any sanely coded page, fieldsets group fields, and so we already have part of the solution without needing to make any changes.

#Code
0001<fieldset>
0002<legend>Search</legend>
0003<input type="text" id="query" name="query" value="" />
0004<button type="submit" name="search" id="search">Search</button>
0005</fieldset>

get this code

The other reason for rolling our own solution is that defaultbutton doesn’t offer the flexibility of applying the default action to any type of button—be it a <input type="submit />, a <button />, an <a href="javascript:..."> anchor and so on. Nor provide any custom event handling when default actions are fired.

A Proposed Solution

1. Connect fieldsets with their default buttons using minimal markup

Although fields are grouped, we still need to provide some relationship between a button and a fieldset. We should provide enough flexibility that our default button could be anywhere on the page, not necessarily within the <fieldset>.

A clean and unobtrusive way to provide the relationship is via a common attribute, the classname. We could use the id attribute, but this should be avoided – you have to accept that when you’re working with .NET, it owns every ID attribute.

Using the classname, we create a unique identifier for the fieldset. If we then add the same identifier to the default button for that fieldset, a useful relationship between the two is created. For this to work best, all identifiers should begin with the same string so that we may write a generic function to pick them out of the markup and identifiers for each button to fieldset relationship must be unique. Here I’ve used “submit-…“:

#Code
0001<fieldset class="submit-search">
0002<legend>Search</legend>
0003<input type="text" id="query" name="query" value="" />
0004<button type="submit" name="search" id="search" class="submit-search">Search</button>
0005</fieldset>
0006 
0007...
0008 
0009<fieldset class="submit-comment">
0010<legend>Add a comment</legend>
0011<input type="text" id="name" name="name" value="" />
0012 <textarea name="comment" id="comment"></textarea>
0013<input type="submit" name="search" id="search" class="submit-comment" value="Submit Comment" />
0014</fieldset>

get this code

Now we have connected buttons and fieldsets, we need to handle events on the page fired when the enter key is hit.

2. Handle enter key events with Javascript/jQuery

There are a couple of ways we can do this:

  1. Be proactive. Scan the entire DOM on page load, find any fieldsets with a “submit-…” class and bind an onkeypress event to every input inside to connect it with the right button.
  2. Be passive. Use event delegation and listen for keypress events on the page and decide whether we need to handle a default action event for it.

Option 1 is quick to implement, but very inefficient. On every page load, we must scan the entire DOM for <fieldset>s, and then do all the work of binding events to the input elements, even to elements that may never be used. This is computationally expensive and will be a real drag on page startup times.

With option 2, we need only find one element in the DOM, bind one event to it and then wait for the enter key to be pressed:

  1. Using a fast ID selector, $('#content'), we find one containing element for the whole page. It could be <body>, but in this code I’ve gone for an imaginary <div id="content"></div> element that might always surround our site’s content. This element will handle the keypresses for all fields on the page.
  2. To this element we bind an onkeypress event listener function to listen out for any keypress that happen within it— $('#content').bind('keypress', function(e) {});.
  3. We start the function by checking for two attributes of the keypress event. These are both contained within the event object that is passed into the function— e:
    1. The key that was pressed
    2. The target HTML element that the keypress has bubbled up from.
  4. For valid keypresses— an enter keypress from an input target— we find the input element’s parent fieldset, and extract the classname begining with “submit-”. We use the CSS attribute selector and find any value that begins with (^=) the submit string— ('class^="submit-"').
  5. We can now find the button in the page that corresponds to the identifier, and trigger a click event on it. We need to handle different types of buttons, submits and anchors slightly differently.

#Code
0001KeyListener = {
0002 
0003init : function() {
0004$('#content').bind('keypress', function(e) {
0005var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
0006var target = e.target.tagName.toLowerCase();
0007if (key === 13 && target === 'input') {
0008e.preventDefault();
0009 
0010var parentFieldset = $(e.target).parents('fieldset');
0011parentFieldset = parentFieldset.filter('class^="submit-"').eq(0);
0012 
0013if (parentFieldset.length > 0) {
0014var classnames = parentFieldset.attr('class').split(' ');
0015 
0016for (var i = 0; i < classnames.length; i++) {
0017if (classnames[i].substring(0, 7) == 'submit-') {
0018var button = $('a.' + classnames[i] + ', button.' + classnames[i], $(this)).eq(0);
0019if (button.length > 0) {
0020if (typeof(button.get(0).onclick) == 'function') {
0021button.trigger('click');
0022} else if (button.attr('href')) {
0023window.location = button.attr('href');
0024} else {
0025button.trigger('click');
0026}
0027}
0028break;
0029}
0030}
0031}
0032}
0033});
0034}
0035};
0036 
0037$(document).ready(function() {
0038KeyListener.init()
0039});
0040 

get this code

If no button is found, we leave the function silently. We could re-trigger the default action, but this is what we’re trying to fix. I think it’s better the enter key stops working completely, rather than producing unexpected or potentially dangerous results if the wrong action is triggered.

Comments for "Fixing the Enter Keypress Event in ASP.NET with jQuery"

Commenting is now closed for this article

About

beardscratchers.com is a music-focused web experiment and creative-arts journal from London, England.

Subscribe/Syndicate

Categories

Previous Entries…

Journal content and design are © of Nick Skelton

built with web standards and a baseline.