Perform a tab with js

Let’s get started then. The steps are:

[list=1][]set a blur event on each of the elements you wish to track
[
]assign a common function called blurHandler to that blur event
[]the blurHandler function stores a reference to the element in a form property called previousFocus
[
]next is to assign a function to the click event to the tab button
[]in the tab click function do the following things:[list]
[
]loop through each element in the form
[]check if the element matches the one from the stored previousFocus. If so, set a flag called focusNext and continue on to the next element
[
]check if that focusNext flag has been set, and if so set the focus to that element, then end the loop
[*]deal with situations that the loop won’t catch, such as a next element not being found, or no previousFocus being set[/list]
[/list]

That should result in something close to a working prototype, but other things might need to be done too in order to account for different situations that crop up.

Paul,
I’m sorry if I am asking of too much, but I am a huge JS noob. Thats why I posted in the forums.
Apologies if I sounded rude, I wans’t meaning to be. But in my previous experiences, fellow SPF members have tought me to always try something myself and do as much as I can do and when I absolutely need help, then ask for it.

So, i tried :blush: and all I could come up with is this:

var previousFocus = null;
$(".formElements").blur();
blurHandler = function(){
	previousFocus = $this
}
document.getElementById('button').onclick = function () {
/*
 I have no clue how loop through form...
or what a flag is. Or how to assign it. Or check it... :-(
*/
};

I know its not much, but its all I knew how to do. Again, I know we’ve both spend quite a bit of time on this topic, especially recently, and I want it to be done :), so I can move on to the more fun things. So, I hope you don’t mind me asking if you could help me fill the rest. :slight_smile:

Thanks in Advance,
Team 1504

It would not be appropriate for me to entirely create the scripting code, for which you would receive payment for from your boss.

However, if you have the time we can go through it piece by piece.

Oh, yes of course. Then it would be only fair for you to get paid too :slight_smile:
And, if so, I would asked with your help in writing it and further developing it.

However, thank you for still being willing to and wanting to help me :slight_smile:

I guess the first piece then is what did I do wrong in the code I posted in post #42? (ignoring the looping and flagging for now)

I see that you’re using jQuery in your sample code, so we’ll use that to help with some of the heavy lifting.

The first thing is that the previousFocus variable should not be a variable, but instead a property on the form itself. That ensures that the information is kept in a location that is easily accessible to other code that works with the form, while also being as local as practical to the form itself. Global variables are considered to be bad form, so the most global that we can achieve while still remaining as local as practical, is the form itself.

If the form has no identifier, it needs one. You should make the identifier as relevant to the form as possible so if it were a form for filling in user details, you could give it an id of “userDetails”. Until that sort of info is known, I’ll go with “sampleForm”.


$('#sampleForm').prop('previousFocus', false);

Then in the blur handler, you can set that previousFocus property to be the element that triggered the blur event. But we’re getting ahead of ourselves here. How do we use jQuery to easily do things with form elements in the first place? You can use the ‘:input’ selector, but we don’t want the tab button (which I’ll currently assume has an id of “tab”) to also be included in that, so we can tell the selector to exclude that.

Because this is jQuery now, we can provide the function directly to the blur event itself:


$('#sampleForm :input:not(#tab)').blur(function () {
    ...
});

It’s inside that event where we’ll set the previousFocus property of the form.
With event functions within jQuery, the this keyword can be used to refer to the element that triggered the event, and each form element contains a natural reference to the form itself, so all we should need to do within that function is:


$(this.form).prop('previousFocus', this);

Which will assign the element (via the this keyword) to the previousFocus property of the form.
That’s done so that later on with the tab button, the click event for it can refer to that previousFocus property to find out the most recent form element that previously had the focus.

Now there’s only (and it’s a big only) the tab click event to deal with.

Apologies, it is just something I am more familiar with and know so I naturally started typing in said syntax?— if that makes sense.

ah okay. Yeah global variables are / were a bad habit of mine. But that makes sense :slight_smile:

I didn’t give it an id, so i guess we can keep it at #sampleForm and ill tell them to give it that id or replace it with an id they choose.

ah, okay. And yes it has an id of #tab.

Okay, so now we have this:

$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
    }
});

unfortunately, yes…

well, isn’t the first, and really simple, piece is writing the jQuery for when the button is clicked:

$("#tab").click(function(){
    ...
});

And, now, the second or next piece:
looping, right?.. I know nothing about looping in js or jQuery :frowning:

But, I really appreciate your explanations— I learned a lot or at least something:p:)

Before we get on with the looping, the loop will need to make use of the information from previousFocus, so the first step for you in there would be to have the function display information about what is in the previousFocus, perhaps the nodeName of the element, or even the field name itself.

Once you have worked out how to gain access to that information, we can work on looping through things from there.

hmm display as in print / show the user or store it for us to use later?

Well, this is what I could come up with after researching some:


$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);

        var $possibleItemWithPropPreviousFocus = $('input[property=previousFocus]')

        /*But what do I do about the <select> and <textarea>'s that could and will have said property at some point. Can I do:
       var itemWithPropPreviousFocus = $('input[property=previousFocus]') || $('select[property=previousFocus]') || $('textarea[previousFocus]');*/

        var $actualItemWithPropPreviousFocus = $('#sampleForm').find($itemWithPropPreviousFocus);
        
        /* But then how to get name of it? alert($($actualItemWithPropPreviousFocus).get());
            That alerts it, but i couldn't find anything that returns the name-attribute of an element. */
       
    }
});

jQuery’s :input selector picks up more than just input elements, it gets all form controls including input, textarea, select and button elements.

Within the tab click function, it’s better to start from what you already have.
If you use #sampleForm you are then restricting that code to working only within that one unique identifier.
Instead, you can use the tab button itself to get to the form. All form controls contain a property called form, which references the form that the control is in, so you can gain a reference to the form from that form property.


$("#tab").click(function(){
    var form = this.form;
    ...
});

It also makes the code more flexible, because if the form identifier ever changes, you won’t have to come back to this piece of code to change it as well.

Once you have the form, you can then retrieve that previousFocus property.
If the initial reference is a jQuery object, you can use the .prop() method such as $(form).prop(‘previousFocus’)
If the reference is not a jQuery object, you can use a normal property refernce instead, with form.previousFocus
Both will result in accessing the same property value from the form.


$("#tab").click(function(){
    var form = this.form,
        previousFocus = form.previousFocus;
    ...
});

The previousFocus variable will now contain a reference to an input, or text area, or other type of form control.
You can get the name of that form control just by getting the name property from that element, with previousFocus.name

sweet!

wow, that is really nice and useful— Glad I know that now :slight_smile:

okay. Thank you, I’m sure my colleagues will appreciate that if they change the identifier. And i will as it makes it easier to use in other instances.

wait, doesn’t that set the property of previousFocus to form? And not get / retrieve it?

okay. that makes sense. var form = this.form set the variable form to the form that the #tab button is a part of.
And then form.previousFocus returns the element with said property within the variable form, which is the form, right?

wow, thats simple and logical! I spend a long time looking into a getName() function :lol:

okay, so now we have:


$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
    }
    $('#tab').click(function(){
        var form = this.form;
        previousFocus = form.previousFocus;
    }
});

or is it inside the blur function?


$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
        $('#tab').click(function(){
            var form = this.form;
            previousFocus = form.previousFocus;
        }
    }
});

Do one of those look right?

Also, so now have we finished with the piece regarding the name / nodeName of the element that currently has the prop previousFocus; and, then would we move on to the next piece— i assume the looping.
Which, btw, I am so scared of. Thinking about what it does / is supposed to do makes me scared :(:lol:

Take a look at jQuery’s .prop() documentation page and you’ll see that it has two forms of syntax.

prop(propertyName) gets the value of a property for the first element in the set of matched elements
and
prop(propertyName, value) set one or more properties for the set of matched elements

Nearly but not quite. form.previousFocus accesses the form property called previousFocus. That property might not exist if it hasn’t previously been set, but if it does exist it will contain a reference to the most recently accessed form control.

I like to keep it simple, because simple is hard to break.

Yep, the first one looks right. Alert the name of previousFocus, and you should find that after you’ve clicked on a form field, the tab button should alert the name of that field. Get that working first, and that confirms that the blur event and the tab button event are working properly.

After making sure that this part of things is working as it should, we’ll take the next step from there.

Alert the name of previousFocus, and you should find that after you’ve clicked on a form field, the tab button should alert the name of that field.

ahhh, thats just alert(previousFocus.name); , right?

I’ll try it out and get back to you.

I made up a quick form to try it in and it doesn’t seem to be working:(…

Do you have any ideas why? (Sorry, but the code is really messy) :blush:


<!Doctype HTML>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>tab button js</title>
		<style type="text/css">
		input, textarea, select{
	margin:5px 0;
	padding:5px;
	background:#f5f5f5;
	border:1px solid #d9d9d9;
	border-top:1px solid #c0c0c0;
	color:#000; /*Makes text black after user has typed it in. 
	In other words, when there is text in the input-field. This 
	makes it easier to read on the grey background after done typing */
	font:1em "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif;
	font-weight:bold;
	border-radius:5px;
	line-height:normal;
}   
input:hover, textarea:hover, select:hover{
	background:#fff;
	border:1px solid #b9b9b9;
	border-top:1px solid #a0a0a0;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
	font-weight:bold;
}
input:focus, textarea:focus{
	background:#fff;
	border:1px solid #4d90fe;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#333;
	font-weight:normal;
	outline:none;
}
select:focus{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
}
#tab{
	height:35px;
	width:100px;
	border:1px solid #3079ed;
	background:#4d90f3;
	border-radius:3px;
	box-shadow:none;
	color:#fff;
	cursor:pointer;
	text-shadow:0 1px rgba(0,0,0,0.1);
}
#tab:hover{
	background:#357ae8;
	border:1px solid #2f5bb7;
	box-shadow:none;
	text-shadow:0 1px rgba(0,0,0,0.3);
}
#tab:active{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
}
label{
	font-size:20px;
	font-weight:500;
}
		</style>
		<script type="text/javascript">
		$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
    }
    $('#tab').click(function(){
        var form = this.form;
        previousFocus = form.previousFocus;
        alert(previousFocus.name);
    }
});
		</script>
	</head>
	<body>
            <legend align="center">Form</legend>
            <form  method="post" action="" id="sampleForm">

			<label for=name accesskey=U class="verticallyCentredInput"><span class="required">&#x2a;</span> Your Name: </label>
            <input name="name" type="text" id="name" size="28" autocomplete="off" />

			<br />
            <label for=email accesskey=E class="verticallyCentredInput"><span class="required">&#x2a;</span> Email: </label>
            <input name="email" type="text" id="email" size="28" autocomplete="off"  />

			<br />
          
            <label for=subject accesskey=S id="subjectLabel"><span class="required">&#x2a;</span> Subject: </label>
            <select name="subject" type="text" id="subject">
            	<option value="commt">Comments</option>
            	<option value="concr">Concerns</option>
            	<option value="bugs">Report a bug (issue)</option>
            	<option value="sales">Sales</option>
            	<option value="suppt">Support</option>
            </select>

			<br />
            <label for=comments accesskey=C><span class="required">&#x2a;</span> Your Comments: </label>
            <textarea name="comments" cols="27" rows="3" id="comments"></textarea>
            <hr />
            
            <div id="tab">Tab</div>

            </form>
        </body>
</html>

First of all, the jQuery library has forgotten to be loaded.

Secondly, some of the event methods are missing some of their syntax. Remove the function from the event and that should help you to find where some of the faults are.

whoops, sorry for forgetting the jQuery library.
I added it:

<!Doctype HTML>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>tab button js</title>
		<style type="text/css">
		input, textarea, select{
	margin:5px 0;
	padding:5px;
	background:#f5f5f5;
	border:1px solid #d9d9d9;
	border-top:1px solid #c0c0c0;
	color:#000; /*Makes text black after user has typed it in. 
	In other words, when there is text in the input-field. This 
	makes it easier to read on the grey background after done typing */
	font:1em "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif;
	font-weight:bold;
	border-radius:5px;
	line-height:normal;
}   
input:hover, textarea:hover, select:hover{
	background:#fff;
	border:1px solid #b9b9b9;
	border-top:1px solid #a0a0a0;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
	font-weight:bold;
}
input:focus, textarea:focus{
	background:#fff;
	border:1px solid #4d90fe;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#333;
	font-weight:normal;
	outline:none;
}
select:focus{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
}
#tab{
	height:35px;
	width:100px;
	border:1px solid #3079ed;
	background:#4d90f3;
	border-radius:3px;
	box-shadow:none;
	color:#fff;
	cursor:pointer;
	text-shadow:0 1px rgba(0,0,0,0.1);
}
#tab:hover{
	background:#357ae8;
	border:1px solid #2f5bb7;
	box-shadow:none;
	text-shadow:0 1px rgba(0,0,0,0.3);
}
#tab:active{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
}
label{
	font-size:20px;
	font-weight:500;
}
		</style>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
		<script type="text/javascript">
		$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
    }
    $('#tab').click(function(){
        var form = this.form;
        previousFocus = form.previousFocus;
        alert(previousFocus.name);
    }
});
		</script>
	</head>
	<body>
            <legend align="center">Form</legend>
            <form  method="post" action="" id="sampleForm">

			<label for=name accesskey=U class="verticallyCentredInput"><span class="required">&#x2a;</span> Your Name: </label>
            <input name="name" type="text" id="name" size="28" autocomplete="off" />

			<br />
            <label for=email accesskey=E class="verticallyCentredInput"><span class="required">&#x2a;</span> Email: </label>
            <input name="email" type="text" id="email" size="28" autocomplete="off"  />

			<br />
          
            <label for=subject accesskey=S id="subjectLabel"><span class="required">&#x2a;</span> Subject: </label>
            <select name="subject" type="text" id="subject">
            	<option value="commt">Comments</option>
            	<option value="concr">Concerns</option>
            	<option value="bugs">Report a bug (issue)</option>
            	<option value="sales">Sales</option>
            	<option value="suppt">Support</option>
            </select>

			<br />
            <label for=comments accesskey=C><span class="required">&#x2a;</span> Your Comments: </label>
            <textarea name="comments" cols="27" rows="3" id="comments"></textarea>
            <hr />
            
            <div id="tab">Tab</div>

            </form>
        </body>
    </html>

Wait, what is missing in the script itself. I copied it straight from post 50. The first one, which you said was good. Thats why I’ve been asking if the script is good at the end of my posts after adding something— to make sure i didn’t forget anything.

The content within the function of that post was good, but there’s a subtle something missing outside of the function. Can you spot it?

Hint: temporarily removing the function will help you to spot what is incomplete.

I didn’t close the .click() function properly!

I added the );, here it is jsut for proof :), but it still doesn’t seem to be doing anything and chrome dev tools is giving me a JS error.

<!Doctype HTML>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>tab button js</title>
		<style type="text/css">
		input, textarea, select{
	margin:5px 0;
	padding:5px;
	background:#f5f5f5;
	border:1px solid #d9d9d9;
	border-top:1px solid #c0c0c0;
	color:#000; /*Makes text black after user has typed it in. 
	In other words, when there is text in the input-field. This 
	makes it easier to read on the grey background after done typing */
	font:1em "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif;
	font-weight:bold;
	border-radius:5px;
	line-height:normal;
}   
input:hover, textarea:hover, select:hover{
	background:#fff;
	border:1px solid #b9b9b9;
	border-top:1px solid #a0a0a0;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
	font-weight:bold;
}
input:focus, textarea:focus{
	background:#fff;
	border:1px solid #4d90fe;
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#333;
	font-weight:normal;
	outline:none;
}
select:focus{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
	color:#000;
}
#tab{
	height:35px;
	width:100px;
	border:1px solid #3079ed;
	background:#4d90f3;
	border-radius:3px;
	box-shadow:none;
	color:#fff;
	cursor:pointer;
	text-shadow:0 1px rgba(0,0,0,0.1);
}
#tab:hover{
	background:#357ae8;
	border:1px solid #2f5bb7;
	box-shadow:none;
	text-shadow:0 1px rgba(0,0,0,0.3);
}
#tab:active{
	box-shadow:inset 0 1px 2px rgba(0,0,0,0.3);
}
label{
	font-size:20px;
	font-weight:500;
}
		</style>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
		<script type="text/javascript">
		$(document).ready(function(){
    $('#sampleForm').prop('previousFocus', false);
    $('#sampleForm :input:not(#tab)').blur(function () {
        $(this.form).prop('previousFocus', this);
    }
    $('#tab').click(function(){
        var form = this.form;
        previousFocus = form.previousFocus;
        alert(previousFocus.name);
    });
});
		</script>
	</head>
	<body>
            <legend align="center">Form</legend>
            <form  method="post" action="" id="sampleForm">

			<label for=name accesskey=U class="verticallyCentredInput"><span class="required">&#x2a;</span> Your Name: </label>
            <input name="name" type="text" id="name" size="28" autocomplete="off" />

			<br />
            <label for=email accesskey=E class="verticallyCentredInput"><span class="required">&#x2a;</span> Email: </label>
            <input name="email" type="text" id="email" size="28" autocomplete="off"  />

			<br />
          
            <label for=subject accesskey=S id="subjectLabel"><span class="required">&#x2a;</span> Subject: </label>
            <select name="subject" type="text" id="subject">
            	<option value="commt">Comments</option>
            	<option value="concr">Concerns</option>
            	<option value="bugs">Report a bug (issue)</option>
            	<option value="sales">Sales</option>
            	<option value="suppt">Support</option>
            </select>

			<br />
            <label for=comments accesskey=C><span class="required">&#x2a;</span> Your Comments: </label>
            <textarea name="comments" cols="27" rows="3" id="comments"></textarea>
            <hr />
            
            <div id="tab">Tab</div>

            </form>
        </body>
    </html>

This is how it is with development. There may-well be a number of issues, but step by step they are found and decisions are made to deal with them.

In this case, the JS error is due to a similar issue that you fixed with .click. The .blur event hasn’t been properly closed.

wow. I really need to get on top of things :lol:

Okay. I closed everything, but now i am getting 7 error of the same type. I guess it was a good-idea to check it before we moved on :slight_smile:

They are: Uncaught TypeError: Cannot read property ‘previousFocus’ of undefined

what is it saying is undefined? 7 times too. It looks like everything is defined properly and closed properly.

Do you have any idea on what the error means?

That’s due to the script expecting a reference to the form with this.form, but it is unable to gain one from a div element.

It was expected that the tab button would be one of these types of buttons:

Either:


<input type="button" id="tab" value="Tab">

Or:


<button id="tab">Tab</button>

Either of those are considered to be form controls, from which a reference to the form is available.

A div element which is what you have used, is not a form control, so a reference to the form is not available from that.

You now have one of two options to pick from:

[list=1][]use an actual form button as in the samples given before, resulting in working scripting code
[
]keep the div for your tab button and add some more scripting to find the form[/list]