Another autoTab question

“Another” because I notice there are a lot of them (here and elsewhere).

Since I rewrote the other JS stuff on our forms, I thought I should rewrite this one too (even though autoTab is totally not DoWhatIMean and should be banned from teh innernets… yet another reason to fill out forms with Javascript off!).

Current one looks pretty old (works fine of course):


This script and many more are available free online at
The JavaScript Source!! http://javascript.internet.com
Created by: Cyanide_7 |  

var isNN = (navigator.appName.indexOf("Netscape")!=-1);

function autoTab(input,len, e) {
  var keyCode = (isNN) ? e.which : e.keyCode; 
  var filter = (isNN) ? [0,8,9] : [0,8,9,16,17,18,37,38,39,40,46];
  if(input.value.length >= len && !containsElement(filter,keyCode)) {
    input.value = input.value.slice(0, len);
    input.form[(getIndex(input)+1) % input.form.length].focus();
  }
etc...

You can see it’s checking for Netscape, and checking for control keys being hit. I figured something simple like autoTab would be a good learning exercise for me.

On just one set of questions, they want autoTab to run, but not on any others.

relevant HTML (HTML4 transitional, inside a form):


<div>
  <label for="Postcodenum">Postcode:</label>
    <input type="text" name="Postcode" id="Postcodenum" title="postcode cijfers"  value="" size="4" maxlength="4">
  <label class="access" for="Postcodelet">Postcode letters:</label>
    <input type="text" name="Postcode2" id="Postcodelet" value="" size="2" maxlength="2">
</div>
<div>
  <label for="Plaats">Plaats:</label>
    <input type="text" id="Plaats" name="Plaats" value="">
</div>

The desired behaviour is that after the first postcode input is filled to maxlength (4), the focus moves to the next one, until the maxlength is filled (2), then focus is moved to the next input (Plaats) and further is there no more autoTab.

So I wanted to not only rewrite this, but I wanted it to be very general.

I’m having trouble with the “next” part, as stuff like nextSibling-if-an-input stumbles on the wrapping divs.

While everything works up to the “next” stuff, I’m not sure I did this the right way either:


var inputs = [document.getElementById('Postcodenum'),
              document.getElementById('Postcodelet')],
    j;

function autoTab() {
    var next = this;
   /* do {
        next = next.nextSibling;
    }
    while (next.nodeName !=='INPUT');

    if (this.value.length === this.maxLength) {
        next.focus();    
    }*/
}
for (j = 0; j < inputs.length; j++) {
    inputs[j].onkeyup = autoTab;
}

I think I did the array clumsily… I would have liked to make an array of whatever elements have an id that matches a regex pattern but since right now it’s always only ever 2 inputs, I just directly asked for their id’s. I put them in an array because I wanted to be able to use one var to Do Stuff to (theinput.onkeyup, theinput+thenextinput.focus() etc). There will possibly maybe when-hell-freezes be some input validation added here as well.

I got the do-while loop from an older thread in this forum, and I see what it does and works for what it’s meant for (a string of inputs altogether like you see for creditcard and phonenumbers and birthdates), but obviously can’t deal with my HTML setup with wrapping divs.

My question is, must I do the whole document.forms[“formID”].elements and then loop through all of them (it’s a long form) looking for “this” (whichever postcode input I’m on) and then adding 1 to the index and calling this “next” for next.focus()?? Many of the solutions and examples I see online use tabindex (I’d rather not add that to my HTML), or explicitly state the next input using name or id, and most examples (including what we are using now) have javascript in the HTML.

Thanks,
poes

A simple solution, where “dutchform” is just whatever element is able to contain all the inputs:

var inputs = document.getElementById('dutchform').getElementsByTagName('input');
for (var i = 0, j = inputs.length; i < j; i++) {
  inputs[i].autoTabIndex = i;
  inputs[i].onkeyup = function() {
    if (this.value.length === this.maxLength && this.autoTabIndex < j) {
      inputs[this.autoTabIndex + 1].focus();
    }
  }
}

It might be too simple, but from what I can see in your HTML it’ll work as long as all the elements have a maxlength. Otherwise the collection will have to be filtered to contain only elements with a maxlength attribute.

Otherwise the collection will have to be filtered to contain only elements with a maxlength attribute.

I have maxlength on some other elements who should not have autoTab… so I like the super-generality of your code…

Lemme play with this and see if I can put some extra filter between these two lines:
inputs[i].autoTabIndex = i;
inputs[i].onkeyup = autoTab; (since I’m keeping functions out of for loops)

something like
var somePattern = /Postcode.*/;

inputs[i].autoTabIndex = i;
if (somePattern.test(inputs[i].id)) {
inputs[i].onkeyup = autoTab;
}
?

This seems like it would let me add or remove inputs as I want, or if it gets too weird, make an array of names/id’s of inputs calling the autoTab and looking to see if they match the array in the if statement… does this sound reasonable or weird?

so…


    var inputs = document.getElementById('formAfsluiten').getElementsByTagName('input');

    function autoTab() {
        if (this.value.length === this.maxLength && this.autoTabIndex < j) {
            inputs[this.autoTabIndex + 1].focus();   
        }
    }

    for (var i = 0, j = inputs.length; i < j; i++) {
        inputs[i].autoTabIndex = i;
        var somePattern = /Postcode.*/;

        if (somePattern.test(inputs[i].id)) {
            inputs[i].onkeyup = autoTab;
        }
    }

I tried in Mozilla just to see if I didn’t screw anything up too badly… it looks short and sweet. Thanks a lot! I was really overwriting it.

This forum rules.

Cool beans. :slight_smile:

One minor nitpick - since you abstracted the event handler into the autoTab function, now the j variable will be undefined. I had to include && this.autoTabIndex < j because an error gets thrown when you get to the last element in the list. I’ll leave you to sort this one out. :wink:

I haven’t gotten that error: plus since the autoTab is being referenced from within the for loop, I thought it then also could reach “j”

Everything seems to work in all browsers, when I realised I have more than one form with these autoTabs (though not more than one per page).

Likely because I still don’t have syntax down correctly, I could not use my getElementsbyClass(“someclass”).getElementsByTagName(“input”) nor any combination I could think of for forms and elements, except
document.forms[0].elements which means the form in question must be the first (it may not be).

Still, I want to get more familiar with forms anyway and so will see how to get general listings of forms… all examples in my books and on google were always targetting a particular form using name or id and I suspect my forms[0].elements, while working, is actually not legal.

since the autoTab is being referenced from within the for loop, I thought it then also could reach “j”
Whoops, yes, “j” is outside the scope of the function so it’s accessible in it.

I don’t really like the document.forms way of getting at forms. I prefer to use the standard getElementsBy… methods. In any case, this is where document.querySelector would come in handy - or XPath. But that’s opening another cross-browser-compatibility can of worms. What does the HTML look like? Maybe I can help figure out the best way of getting the collection of inputs you need.

Oh, good, I was right about the j? Cause while biking home from work it occurred to me that if it was an end-of-array error I’d never see it as the autoTabbed inputs aren’t at the end of the form : )

I can post some HTML tomorrow, but basically on a bunch of sites I’m hitting two forms:

<form action=“someaction” method=“post” id=“formAfsluiten”>

and
<form action=“someaction” method=“post” id=“formBereken”>

The j thing is just so that inputs[this.autoTabIndex + 1].focus(); doesn’t break when there is no input after the last one being typed into. If j was undefined you’d just get a “j is not defined” error. But it isn’t. :slight_smile:

Merging HTML Collections like that is not trivial. In Firefox you can do this sort of thing:

var frms = document.getElementsByTagName('form');
var inputs1 = frms[0].getElementsByTagName('input');
var inputs2 = frms[1].getElementsByTagName('input');
var inputs = Array.concat(Array.slice.call(inputs1), Array.slice.call(inputs2));

It’s not the same as a normal HTML Collection though. It’s an array of HTML DOM elements, but at least it means they retain all their inherited methods and properties (from the Element object). And it won’t work in IE. :frowning:

What about just doing document.getElementsByTagName(‘input’)? Is that too general?

What about just doing document.getElementsByTagName(‘input’)? Is that too general?

Lawlz, duh. That worked just fine : )

I wonder at what point it would be too general… how many inputs are so many that it’s a waste for the script to go looking for ALL of them just to pick out a few of them? If it were that bad, though, I suppose I’d do the id filter earlier, before making the array…

On of the pages in question is here. Some pages have a little, separate form asking for license plate number on the same page, and (not in the air yet) there’s a mutation page with a couple of separate forms on the same page (though I’ve been waiting 3 years for that one to go live, so I’m not holding my breath lawlz). Anyway, that doesn’t seem like too many inputs, does it? I’m thinking it’s ok… and seems to work everywhere (haven’t tested IE6).

Thanks again!

Well, you seem to only care about the postcode one there, so I’dd just target that one manually. Cycling through the rest seems pointless when you’re only targeting one or two inputs.

I also noticed there are some SELECTs there. If you have a text box right before a SELECT, you might want to include the SELECTs as part of the things that can receive focus automatically.

I just ran into that, on a different form (where a select came after the postcode). So I ended up needing to do
var inputs = document.forms[0].elements;
Which sucks as sometimes it’s the first form and sometimes it’s the second.

So I considered doing it manually but then I can’t deal with multiple element types, OR I have to manually list each name or ID and set a separate focus() command for each (some pages the postcode is followed by different inputs with different id’s though so this is really brittle).

You can just give it an ID and use elements anyway:

document.getElementById('some_form').elements

This is where jQuery’s power really manifests itself - $(‘select, input’) is so easy! Mind, so is XPath, and so is document.querySelector, but as always, somebody makes these unviable.

Still, I think using elements is in this case the best option.