views:

1493

answers:

2

I'm creating a data entry app for some in-house stuff.

My team needs to enter info about "items" which can have many "categories" and vice versa.

I need a quick way to let them enter an arbitrary amount of categories.

Here's my idea:

On the item entry page, I'll have it so that initially there's one text input for "categories" and if it's tabbed out of while it's empty, the input field is deleted (unless it's the only one) and focus skips to the next field. If it's not empty when it's tabbed out of and if it's the last input field in the array, then an additional "category" text input will be added and focused.

This way people can enter an arbitrary amount of categories really quickly, without taking their hands off the keyboard, just by typing and hitting tab. Then hitting tab twice to denote the end of the list.

First of all, what do you think of this interface? Is there a better way to do it?

Second of all, is there a jQuery (or something) plugin to do this? I've searched but can't find one. I searched scriptaculous/prototype and mootools too, with no luck.

I would obviously rather use something tried and tested than roll my own.

Any and all advice appreciated

+1  A: 

it's actually not too difficult to implement that, even with vanilla JS (ie: no jQuery, prototype, etc), but everything is easier with jQuery, so I'll have a go at it using that:

Assuming a structure like this:

<form id="myForm">
    <div class="inputRow">
        <input type="text" name="myInput[]" />
    </div>
    <div class="inputRow">
        <input type="text" name="myInput[]" />
    </div>
    ...
</form>

Here's the JS

$('#myForm :text').blur(onBlurHandler);

function onBlurHandler() {
    $row = $(this).parent();
    if ($row
        .nextAll(":has(:text)")     // all following divs with a text element
        .length == 0                // but there aren't any, we're on the last one
    ) {
        if ($.trim($row.find(":text").val())) { // the text box isn't empty
            $copy = $row.clone(true);
            $copy
                .find(":text")  // get the new text box,
                .val('')        // remove any text in it
                .blur(onBlurHandler) // and add the event handler (is this necessary?)
            ;
            $copy.insertAfter($row);
        } else if ($row.prev(':has(:text)').length) {   // the text box is empty, and this one isn't the first row
            $row.remove();  // get rid of the row.
        }
    }
}


Response to comments:

thanks for the answer! i've tried it but it doesn't seem to work as intended. i'm on mac firefox. if i tab off the last field, it adds the new one but focuses the address bar. i tried adding: $copy.find(":text").focus(); after the insertAfter line, but it doesn't change anything. any ideas?

also if i shift-tab the blurhandler doesn't know i'm going in the opposite direction. is there any way around that?

Hmm, I hadn't thought about that. What you could try doing is to put an element after all your text fields which can take focus (like a textbox which is rendered off-screen, eg: margin-left: -10000px). Add an onfocus handler onto that to see if the last row is empty, and if it is, then it would have been added just then by the onBlurHandler function, so pass the focus back to the last row. If the last row isn't empty, then pass the focus onto the next element (your submit button, probably). If there are issues with the last row not existing in the DOM yet, then put the above into a timeout.

(If this actually works) this should let your users tab backwards and forwards without hassle.

nickf
thanks for the answer! i've tried it but it doesn't seem to work as intended. i'm on mac firefox. if i tab off the last field, it adds the new one but focuses the address bar. i tried adding: $copy.find(":text").focus(); after the insertAfter line, but it doesn't change anything. any ideas?
also if i shift-tab the blurhandler doesn't know i'm going in the opposite direction. is there any way around that?
+1  A: 

First I'll try to address the problems commented on nickf solution.

To set the focus on the newly created input $copy.find(":text").focus(); will not work. The jQuery focus method only triggers the event, but does not call the underlying focus method.

You can set the focus with setTimeout(function(){$copy.find(":text").get(0).focus()}, 10); but:

  • setTimeout is needed in firefox or strange things will happen with the blinking cursor.

  • IE7 needs another input to focus when tabbing. I haven't found the way to set the focus on an input if the focus goes to the address bar. I suppose this will not be a problem because you will need at least a submit button.

To control shift-tab I've been trying to track the focused element, in order to skip the blurHandler when the focused element is a previous input, but the resulting code is really ugly so I'll post this and look for a better solution.

And last, you're asking what we think of this UI, and I think that a comma separated list of categories is easier to code an to fill in. :-)

Serhii
This data is prone to typos and so I'm using autocomplete on each text input. Will that still work with a comma-separated list? (I'd rather use a line-feed separated textarea if possible). The second issue is, the order of the categories matters. I am using jQuery sortables to allow easy...
I have been playing with the shift-tab thing too. If I can rely on there being a focusable form element after the dynamic list of inputs it's ok. e.g. a button or another field. Just create the new list item in focus handler of that element, instead of blur handler of previous "last" list element.
note, i distinguish between click focus and implicit focus by watching mousedown. so if someone clicks that button or other field, it won't expand the list of text inputs as it would if they hit tab.
Another thing with using the separate input tags is I'm coloring them differently if they contain an unrecognized value. To further mitigate typos.
sorry i just noticed i repeated something you said. i didn't read that "IE7" line all the way through (i'm only concerned with mac firefox)
The list of ignored/interesting tags here in stackoverflow is an example of space separated list with autocomplete.
Serhii
For doing the same with jQuery we got http://jquery.bassistance.de/autocomplete/demo/
Serhii
If you want to put colours to mark unrecognised values your UI design makes perfect sense. I see that you've managed to solve the problems so keep on and forget about the comma-separated list.
Serhii