Ok, well, thanks to people here, I figured out the portion of my js code that's rendering my links useless is what's pasted below... Still trying to figure out why, but if someone has any insight, that'd be great...
function initJumpMenus() {
// Turns all <select> elements with the 'jumpmenu' class into jump menus
var selectElements = document.getElementsByTagName("select");
for( i = 0; i < selectElements.length; i++ ) {
// Check for the class and make sure the element has an ID
if( selectElements[i].className == "jumpmenu" &&
document.getElementById(selectElements[i].id) != ""
) {
jumpmenu = document.getElementById(selectElements[i].id);
jumpmenu.onchange = function() {
if( this.options[this.selectedIndex].value != '' ) {
// Redirect
location.href=this.options[this.selectedIndex].value;
}
}
}
}
}
window.onload = function() {
initJumpMenus();
}
This:
document.getElementById(selectElements[i].id) != ""
is wrong. You want to check if the element has an id, so simply do:
selectElements[i].id != ""
I'd like to point out that you can improve your code here and there:
1 Don't write for loops that get the length property for each iteration,
instead do:
for( i = 0, num = selectElements.length; i < num; i++ ) {
...
}
Only if you expect selectElements to grow or shrink it would make sense to requery the property value, but in that case, you probably should not write a for loop anyway.
2: Don't write nodelist[index]
For nodelists, such as returned by getElementsByTagName() you should not write nodelist[index] (even though most browser support that). The standard DOM method is item, so write nodelist.item(index) instead.
3 fetch items from the list only once
If you need an item from the list more than once, store it in a local variable. your loop would become:
for( i = 0, selectElement; i < selectElements.length; i++ ) {
selectElement = selectElements.item(i);
...more code using selectElement...
}
Note the declaration of the selectElement variable in the for loop. since you don't use it outside the loop, declaring it there avoids clutter and ensures that if you move the loop, you move the declaration.
4. cheapest comparisons first
You wrote:
selectElement.className == "jumpmenu" &&
selectElement.id != ""
this could be slightly improved if you reverse the legs:
selectElement.id != "" &&
selectElement.className == "jumpmenu"
This will be faster, since it is less work to check if a string is empty, and for those cases where the string is empty, we won't even check the className
5 Don't use document.getElementById() if you already have the element
Inside the loop you have this:
jumpmenu = document.getElementById(selectElements[i].id);
You are basically getting the id from the selectElement and use that to query the document to find ....the element having and id equal to that of the current selectElement. Becuase id's are unique within the document (or should be), you are basically writing a completely unecessary sentence. jumpmenu and selectElement refer to one and the same object.
6. onchange handler improvements
inside the loop you assign an onchange handler. you do so by creating a new function for each loop iteration. This is the handler code:
function() {
if( this.options[this.selectedIndex].value != '' ) {
// Redirect
location.href=this.options[this.selectedIndex].value;
}
}
Three things are of note here: First, the onchange handler code contains this location.href = ... that should probably be document.location.href = ....
Second, you refer twice to this.options[this.selectedIndex].value. Again, put this in a local variable.
Third, the this refers to the element that experienced the onchange event by the time this function is executing. Other than this and properties of this, there are no variables in this handler that originate from the loop or the outer initJumpMenus function. You should simply create it once, outside the loop, and assign it every iteration:
var onchange_handler = function() {
if( this.options[this.selectedIndex].value != "" ) {
// Redirect
location.href=this.options[this.selectedIndex].value;
}
}
for (...) {
if (...) {
selectElement.onchange = onchange_handler;
}
}
7 Summary
Putting it all toghether, this is how I would write it:
function initJumpMenus() {
var handler = function() {
var value = this.options.item(this.selectedIndex).value;
if( value != "" ) {
// Redirect
document.location.href = value;
}
}
var selectElements = document.getElementsByTagName("select");
for(var i = 0, num = selectElements.length, selectElement; i < num; i++ ) {
selectElement = selectElements.item(i);
// Check for the class and make sure the element has an ID
if( selectElement.id != "" &&
selectElement.className == "jumpmenu"
) {
selectElement.onchange = handler;
}
}
}
Check the error console; any exception in the JS will stop the links form reacting to a click.
Related
context
I created an array docket to keep track of coordinates as the user clicks on a canvas space. During the main program loop the array is to be scanned by a draw function so that selected pixels can be seen. Originally, inside of my event listener, I was using the push( ) method but then I realized I wanted a way to sort of toggle the pixels.
code description
So I added a method poke( ) to Array.prototype, as seen below, which allows me to push the whole docket array into a local array param.array and assign the trigger coordinate to a local variable param.entry. entry is then pushed into array and array is processed by the main poke( ) loop to ensure there are no duplicate values. If a match is found, both elements are annihilated and param.array is returned to the top, ultimately shrinking docket by 1; If no matches are found then no elements are annihilated and param.array is returned to the top, ultimately expanding docket by 1.
main issue: example 1
Anyway, as the method is currently written, it must be called thusly:
docket.poke( docket, e.key ); Note: for simplicity I have used keyboard key values.
Array.prototype.poke = function( a, b ) {
var bool = { }, i = { }, param = { };
param.array = a; param.entry = b;
//
param.array.push( param.entry );
i.len = param.array.length;
i.end = i.len - 1;
//
for ( i.cur = 0; i.cur < i.len; i.cur++ ) {
bool.match = param.array[ i.cur ] == param.array[ i.end ];
bool.nSelf = !( i.cur == i.end );
//
if ( bool.match && bool.nSelf ) {
param.array.splice( i.end, 1 );
param.array.splice( i.cur, 1 );
//
i.end -= 2;
i.len -= 2;
}
}
//
return param.array;
}
This seems a little redundant, but it offers two critical advantages. First to readability and aesthetic. Being able to visibly pass off the contents of docket to a local array for processing and then visibly return the results to the top I think is very helpful to comprehension. Second, both this example and the next use a sort of confusing truth test to filter out false positives on duplicate value detection. This example doesn't have too though. It could easily be rewritten to compare each element in param.array to param.entry using a tight, no nonsense for loop.
main issue: example 2
docket.poke( e.key ); is the less redundant and more desired approach. This is my code.
Array.prototype.poke = function( a ) {
var bool = { }, entry = a, i = { };
//
this.push( entry );
i.len = this.length;
i.end = i.len - 1;
//
for ( i.cur = 0; i.cur < i.len; i.cur++ ) {
bool.match = this[ i.cur ] == this[ i.end ];
bool.nSelf = !( i.cur == i.end );
//
if ( bool.match && bool.nSelf ) {
this.splice( i.end, 1 );
this.splice( i.cur, 1 );
//
i.end -= 2;
i.len -= 2;
}
}
}
As you can see, this eliminates the the redundancy in the call, but it sacrifices some readability of the method and more importantly the opportunity to really slim up the code using the simple comparison I mentioned above.
So now I'm wondering if there is some less than obvious way that I've missed which will allow me to pass the full contents of my array to a local variable without having to first pass them in as a parameter of its own method.
Any ideas?
There is no reason to define the method on the prototype if you are going to pass the array as an argument. A plain function would be just fine for that.
The second version of your code has indeed the advantage that you can apply the method to a given array instead of passing the array to a function.
The code could however be simplified if:
You would only add the element after you have determined it does not yet occur in the array
You would use indexOf:
Array.prototype.toggle = function(value) {
var index = this.indexOf(value);
if (index > -1) {
this.splice(index, 1);
} else {
this.push(value);
}
}
var a = [4,2,5,8];
a.toggle(2);
console.log(a.join());
a.toggle(2);
console.log(a.join());
NB: I personally find the name toggle more telling than poke.
Consider also the power of a Set: it will find an existing member in constant time (while an array implementation needs linear time), and will also be able to remove it in constant time. So if you are open to using something else than an array for this, go for a Set.
Set.prototype.toggle = function(value) {
if (!this.delete(value)) this.add(value);
}
var a = new Set([4,2,5,8]);
a.toggle(2);
console.log([...a].join());
a.toggle(2);
console.log([...a].join());
Using the common 'if ID exist' method found here, is it still possible check for the existence of the ID when concating the ID with an array variable like below?
for (var i=0; i < lineData.length; i++)
{
optionData = lineData[i].split(",");
if ($("#" + optionData[0]).length)
{
$("#" + optionData[0]).text(optionData[1]);
}
}
When running this in debugging, if the concated $("#" + optionData[0]) ID doesn't exist it yeilds a result of 'undefined: undefined' and jumps to:
Sizzle.error = function( msg ) {
throw "Syntax error, unrecognized expression: " + msg;
in the JQuery code.
Is it proper code etiquette to use check for, and set, HTML ID's in this manner? Why does this not work in the popular 'exist' method? What can I do to fix it and make it skip ID's that don't exist using this type of ID concatenation with an array string?
http://jsfiddle.net/P824r/ works fine, so the problem is not where you think it is. Simplify your code and add in some checks. You're also not doing anything that requires jQuery, so I don't see how this is a jQuery question, but fine:
function handler(data, i) {
var optionData = data.split(","),
$element;
if (optionData[0] && optionData[1]) {
$element = $("#" + optionData[0]);
if ($element.length > 0) {
// omitting >0 as 'trick' causes JS coercion from number to boolean.
// there's literally no reason to ever do so: it's both slower and
// hides your intention to others reading your code
$element.text(optionData[1]);
}
} else { console.error("unexpected optionData:", optionData);
}
lineData.forEach(handler);
but we can do this without jQuery, since we're not really using for anything that we can't already do with plain JS, in the same number of calls:
function handler(data) {
var optionData = data.split(",");
if (optionData.length === 2) {
var id = optionData[0],
content = optionData[1],
element = document.getElementById(id);
// because remember: ids are unique, we either get 0
// or 1 result. always. jQuery makes no sense here.
if (element) {
element.textContent = content;
}
} else { console.error("unexpected input:", optionData);
}
lineData.forEach(handler);
(the non-jquery version unpacks the optionsData into separate variables for improved legibility, but the ultimate legibility would be to make sure lineData doesn't contain strings, but just contains correctly keyed objects to begin with, so we can do a forEach(function(data) { ... use data.id and data.content straight up ... }))
If you want to keep this jQuery-related, there's more "syntax sugar" you're not making use of:
// check for ID in array
jQuery.each(someArray,
function(index, value) {
var the_id = $(value).attr('id');
if ( the_id !== undefined && the_id !== false ) {
// This item has an id
}
});
I've got two problems with the following javascript and jquery code.
The jquery each loop only iterates once, it gets the first element with the right ID does what it needs to do and stops.
The second problems is that when I use the else in the code the one inside the each function, it doesn't even tries the next if, it just exits there.
I'm probably doing something fundamental wrong, but from the jquery each function and what I'd expect from an else, I don't see it.
Javascript code:
var $checkVal;
var $checkFailed;
$("#compliance").live("keypress", function (e) {
if (e.which == 10 || e.which == 13) {
var checkID = $(this).parents('td').next().attr('id');
var checkVal = $(this).val();
$('#' + checkID).each(function () {
var cellVal = $(this).text();
if (checkVal == cellVal) {
$(this).removeClass("compFail").addClass("compOk");
} else {
$(this).removeClass("compOk").addClass("compFail");
var checkFailed = True;
}
});
if (checkFailed == 'True') {
(this).addClass("compFail");
} else {
(this).addClass("compOk");
}
}
});
How could I get the each loop to iterate through all instances of each element with the id assigned to the variable checkID, and get the code to continue after the else, so it can do the last if?
An id should appear on a page only once. If you want to have multiple elements with same id, then use a class, not an id.
Your each loop iter only once because you are selecting by id thus you are selecting only one element in the page. If you change you elements to a class it should work like you expect.
This is to illustrate what I'm talking about in my comment, so that you do not remove the wrong var:
var checkVal;
var checkFailed;
$("#compliance").live("keypress", function (e) {
if (e.which == 10 || e.which == 13) {
var checkID = $(this).parents('td').next().attr('id');
//HERE is the first edit
checkVal = $(this).val();
$('#' + checkID).each(function () {
var cellVal = $(this).text();
if (checkVal == cellVal) {
$(this).removeClass("compFail").addClass("compOk");
} else {
$(this).removeClass("compOk").addClass("compFail");
//HERE is the second
checkFailed = True;
}
});
if (checkFailed == 'True') {
(this).addClass("compFail");
} else {
(this).addClass("compOk");
}
}
});
Normally, the way you have it would cause a compile-time error (in a typed language like C#) for redeclaring a variable. Here, it's not clear to me if it will be used as a local variable (ignoring your global variable) or if javascript will combine them and consider them the same. Either way, you should use it as I have shown so that your intent is more clear.
EDIT: I have removed the $ from your variables (var $checkVal) as on jsFiddle it was causing issues. SO if you do not need those $'s, then remove them. Also, note that testing on jsFiddle indicates that you do not need to change your code (other than possibly removing the $ from your declaration) as javascript appears to consider them the same variable, despite the redeclaration, which I find a bit suprising tbh.
The jquery each loop only iterates once, it gets the first element
with the right ID does what it needs to do and stops.
Yes, this is absolutely right for the code you're using:
$('#' + checkID).each(function(){};)
ID attributes are unique. There must be only one element with a given ID in the DOM. Your selector can match only one element. You are iterating over a collection containing just 1 item.
I am creating a 'simple' javaScript function which basically displays new information on the page when a user clicks next or previous.
The information is taken from an array and I want to use either i++ or i-- to call an element of the array.
Heres my JavaScript:
var titles = ["Dundalk", "Navan", "Drogheda", "Dublin"];
var i = 0;
function next()
{
i++;
if (i == titles.length)
{
i = 0;
}
var object = document.getElementById('tname');
object.innerHTML = titles[i];
}
function prev()
{
if (i == 0)
{
i = titles.length;
}
i--;
var object = document.getElementById('tname');
object.innerHTML = titles[i];
}
The problem is, when I run this code in my HTML page, I get an 'UNDEFINED' result. The JavaScript is not recognizing that i has been initialized as 0 in the beginning.
If i change titles[i] to titles[2], for example, the correct text is displayed in HTML.
What am I forgetting or how can I overcome this?
Thanks
The fact that you're seeing undefined indicates that you're accessing an array index which hasn't been set. Your code looks fine at a glance, so I would guess that there's some more code you're not showing which also uses i as a loop variable and leaves it set to a value > titles.length after the code above has run.
Currently I have a loop that updates the DOM in each iteration; I have learned this is a bad practice & you should update the DOM as little as possible for better speed.
So I was wondering how I go about editing the below so I can store all the elements in one element or something & then do a single DOM addition once the loop ends.
Here is the loop..
for (var i = spot; i < spot + batchSize && i < cats.options.length; i++) {
// Check if the cat is selected
if (cats.options[i].selected == true) {
// Set this category's values to some variables
var cat_id = cats.options[i].getAttribute('value');
var cat_name = cats.options[i].text;
if (checkCatSICAdd(cat_id) === false) {
// Now we create the new element
var new_option = document.createElement('option');
// Add attribute
new_option.setAttribute('value',cat_id);
// Create text node
var new_text_node = document.createTextNode(cat_name);
// Append new text node to new option element we created
new_option.appendChild(new_text_node);
// Append new option tag to select list
sel_cats.appendChild(new_option);
} else {
failed++;
}
}
}
Working with DOM element in the loop is slow - no matter if you attach them to the document or not. Attaching them at the end is a bit faster since only only redraw is required but it's still slow.
The proper way is generating a plain old string containing HTML and attaching this string to the DOM using the innerHTML property of a DOM element.
Your code should be ok. The DOM won't actually redraw until the Javascript has finished executing. However, if you've encountered a problem browser that does perform badly, you could try creating a new select before your loop that is not yet attached to the DOM, populating it as you are now, then replacing sel_cats with that new select at the end. That way, the DOM is only updated once.
Your way is good enough unless you have great many items added to sel_cats - you add to the DOM only once.
The only way to improve efficiency might be to store the options as raw HTML then assign that after the loop:
var arrHTML = [];
for (var i = spot; i < spot + batchSize && i < cats.options.length; i++) {
// Check if the cat is selected
if (cats.options[i].selected == true) {
// Set this category's values to some variables
var cat_id = cats.options[i].value;
var cat_name = cats.options[i].text;
if (checkCatSICAdd(cat_id) === false) {
arrHTML.push("<option value=\"" + cat_id + "\">" + cat_name + "</option>";
}
else {
failed++;
}
}
}
sel_cats.innerHTML = arrHTML.join("");
Once you have the select list assigned to a variable, remove it from the dom using removeChild on its parent tag. You can then use appendChild in the loop before adding the select list back into the dom.
Your code is way bloated, DOM 0 methods will be much faster.
If speed really matters, store spot + batchSize && i < cats.options.length in variables so they aren't re-calcuated on each loop (modern browsers probably don't, but older ones did):
for (var i=spot, j=spot+batchSize, k=cats.options.length; i < j && i < k; i++) {
// Store reference to element
var opt = cats.options[i];
// The selected property is boolean, no need to compare
if (opt.selected) {
// if checkCatSICAdd() returns boolean, just use it
// but maybe you need the boolean comparison
if (checkCatSICAdd(opt.name) === false) {
// Wrapped for posting
sel_cats.options[sel_cats.options.length] =
new Option(opt.value, opt.name);
} else {
failed++;
}
}
}