Why does IE see null in this for...in loop?

Arg!

After getting a cryptic “Object required” error from all IE’s (6,7,8), I tracked it down. It seemed to be coming from my library, line 134 in bold


    Basis.hasClass = function(target, classValue) {
        var pattern = new RegExp('(^| )' + classValue + '( |$)');
      [b]  if (pattern.test(target.className)) {[/b]
            return true;
        }
        return false;
    };

So IE8 has some sort of script debugger so I stepped through it… what it really didn’t like was when this function was called by something it thought had a null value (so, target above ended up being null):

in my regular script:


[b]//"input" here is always the form control with the id "TypeKiezen"[/b]
    chooseType: function(input) {
        var val = input.value.toLowerCase(),
            value;
 
        for (result in Begrafenis.kosten[input.id][val].show) {
            [b]value = document.getElementById(Begrafenis.kosten[input.id][val].show[result]);[/b]       
            Basis.removeClass(value, 'none');
        }



        for (result in Begrafenis.kosten[input.id][val].hide) {
            [b]value = document.getElementById(Begrafenis.kosten[input.id][val].hide[result]);
[/b]            Basis.addClass(value, 'none');
        }
        Begrafenis.setSelectedValue(value);
    },

value isn’t getting assigned in IE, so it’s null. It is getting assigned in all other browsers, so that leaves out an obvious error.

I haven’t found where IE lists all the relevant things (I’m only seeing “value” here), so I’m not sure where in my map/hash object IE is failing or why.

Here’s the object called “kosten” that everyone but IE can read:

(inside the main object “Begrafenis”)


    kosten: {
        'BasisKosten': {
             'value': {
                 '2000': 2000
             }    
        },
      [b]  'TypeKiezen': {
            'value': {
                'begraven': 'zie onder',
                'cremeren': 1500
            }, 
            'begraven': {
                'show': ['Postal', 'Box']
            },
            'cremeren': {
                'hide': ['Postal', 'Box']
            }
        },[/b]
        'Kist': {
            'value': {
                'geen': 0,
                'eenvoudig': 400,
                'standaard': 750,
                'luxe': 1000
            }
        },
        'Herdenkingsdienst': {
            'value': {
                'geen': 0,
                'rouwcentrum': 250,
                'kerk': 500   
            }
        },
        'Volgautos' : {
            'value': {
                'geen': 0,
                'een': 200,
                'twee': 400,
                'drie': 600
            }
        },
        'Koffietafel' : {
            'value': {
                'geen': 0,
                '50': 250,
                '100': 500,
                '150': 750 
            }
        },
        'Rouwbrieven' : {
            'value': {
                'geen': 0,
                '50': 150,
                '100': 200,
                '150': 450
            }
        },
        'DankBid' : {
            'value': {
                'geen': 0,
                '100': 175,
                '200': 250,
                '300': 325
            }
        },
        'Advertenties' : {
            'value': {
                'geen': 0,
                'regionaal': 650,
                'landelijk': 1000,
                'beide': 1650
            }
        },
        'Bloemen' : {
            'value': {
                'geen': 0,
                'eenvoudig': 100,
                'standaard': 150,
                'luxe': 200
            }
        },
        'Grafsteen' : {
            'value': {
                'geen': 0,
                'staande': 1500,
                'liggende': 2000,
                'grafmonument': 2500
            }
        }
    },

The Typekiezen is a weird one; if there’s a nicer/better way of writing that, that would be nice, but it happens this form input does several things when it’s clicked… the others only bring up an associated value for the option chosen so they’re simpler.

Does anyone know why IE can’t read this " document.getElementById(Begrafenis.kosten[input.id][val].hide[result])"

…or another way I could write it so that IE does get it. Basically, the code needs to grab the ID’s “Postal” and “Box” so the show/hide scripts can DoStuff to them.

Even if I do a bit of
if (value !== null) {
Basis.add/removeClass(value, ‘none’);
}
this still isn’t going get IE to do the right thing, just remove the error.

Further looking reveals information but still not sure what to do with it… and I can’t seem to make IE stop at my breakpoints… instead, seems to only stop at the error, which is after where I need to look : (


    chooseType: function(input) {

        var val = input.value.toLowerCase(),
            value; [b]//value null or undefined
        

        for (result in Begrafenis.kosten[input.id][val].show) {
            alert(Begrafenis.kosten[input.id][val].show[result]); [b]//alerts the correct name, Postal or Box, all browsers[/b]
            var temp = Begrafenis.kosten[input.id][val].show[result];
            alert(temp);[b]//alerts the correct name, Postal or Box, all browsers[/b]
            value = document.getElementById(temp);[b]//assigns value to Postal or Box everywhere but IE[/b]
            Basis.removeClass(value, 'none');

        }
...

I don’t see any records anywhere of an IE bug using variables in getElementById(somevar); value remains null.

Also had it where value was not declared outside the for…in loops, and each for…in loop had its own var value… JSLint thought that was bad practice, so made it the way it is now. No difference in IE.

And… after adding more and more and more alerts, suddenly some other code started showing up in dialogue boxes (“from web page”)… a Indexof Prototype addon from Mozilla so browsers who don’t know array.indexOf would work. I’m not using indexOf at all, but removing that from my library made all the mysterious errors vanish… and nowhere near where any of the errors pointed. Arg.

I hate IE.

Do you mind if we run our debuggers across the page?

I think that it may be due to a fundamental flaw, where for…in is used for arrays.

Arrays should only be processed using a standard for loop.

Here’s an update that will protect you from similar problems in the future:


    chooseType: function(input) {
        var val = input.value.toLowerCase(),
            i,
            show = Begrafenis.kosten[input.id][val].show,
            value;

        for (i = 0; i < show.length; i += 1) {
            var temp = show[i];
            value = document.getElementById(temp);
            Basis.removeClass(value, 'none');
        }

I know it’s a simple update, but standard for loops are the best way to process arrays. Using for…in for arrays is just too fragile. It introduces too many opportunities for failure.

I know it’s a simple update, but standard for loops are the best way to process arrays. Using for…in for arrays is just too fragile. It introduces too many opportunities for failure.

When I say “array”, I mean “associative array”, which in JS means “an object who really isn’t an array even though everyone calls them arrays”. I have huge amounts of code who have to grab values based on keys.

Is this


    kosten: {
        'BasisKosten': {
             'value': {
                 '2000': 2000
             }    
        },
        'TypeKiezen': {
            'value': {
                'begraven': 'zie onder',
                'cremeren': 1500
            },
            'begraven': {
                'show': ['Postal', 'Box']
            },
            'cremeren': {
                'hide': ['Postal', 'Box']
            }
        },
        'Kist': {
            'value': {
                'geen': 0,
                'eenvoudig': 400,
                'standaard': 750,
                'luxe': 1000
            }
        },
        'Herdenkingsdienst': {
            'value': {
                'geen': 0,
                'rouwcentrum': 250,
                'kerk': 500   
            }
        },
        'Volgautos' : {
            'value': {
                'geen': 0,
                '1': 200,
                '2': 400,
                '3': 600
            }
        },
        'Koffietafel' : {
            'value': {
                'geen': 0,
                '50': 250,
                '100': 500,
                '150': 750 
            }
        },
        'Rouwbrieven' : {
            'value': {
                'geen': 0,
                '50': 150,
                '100': 200,
                '150': 450
            }
        },
        'DankBid' : {
            'value': {
                'geen': 0,
                '100': 175,
                '200': 250,
                '300': 325
            }
        },
        'Advertenties' : {
            'value': {
                'geen': 0,
                'regionaal': 650,
                'landelijk': 1000,
                'beide': 1650
            }
        },
        'Bloemen' : {
            'value': {
                'geen': 0,
                'eenvoudig': 100,
                'standaard': 150,
                'luxe': 200
            }
        },
        'Grafsteen' : {
            'value': {
                'geen': 0,
                'staande': 1500,
                'liggende': 2000,
                'grafmonument': 2500
            }
        }
    },

a bad idea?

I honestly don’t know any other way to use values to base other values and actions on. On most of those, just value.somevalue is read and gives out the result, but on TypeKiezen the value also had to be used to determine an action, and then who that action happened to.

(I initially had the hide and show stuff just under the “value” property and there was no “value” property, just values… nothing seemed to work in a way I could write though)

Let’s just look back at the original code again.


for (result in Begrafenis.kosten[input.id][val].show) {

What does show contain?


 'show': ['Postal', 'Box']

The show property consists of array. It’s not an associative array, it’s a full-on standard array with index 0 for ‘Postal’ and index 1 for ‘Box’.

Am I getting confused here? If it’s a standard array, a standard for loop is the best way of handling it.

Am I getting confused here? If it’s a standard array, a standard for loop is the best way of handling it.

Oh, that one! Now your code makes sense.

Though then I have to wrap everything in if statements or it goes looking for hide and show when they don’t exist. : (

I’m not quite sure what you mean.

Please show an example? That’s the best way to clear up any confusion.


    chooseType: function(input) {
        var val = input.value.toLowerCase(),
            show = Begrafenis.kosten[input.id][val].show,
            hide = Begrafenis.kosten[input.id][val].hide,
            value;
 
        if (show) {
            for (var i=0; i<show.length; i++) {
                var temp = show[i];
                value = document.getElementById(temp);
                Basis.removeClass(value, 'none');
            }
        }
        else if (hide) {
            for (var j=0; j<hide.length; j++) {
                var temp = hide[j];
                value = document.getElementById(temp);
                Basis.addClass(value, 'none');
            }
        }
        Begrafenis.setSelectedValue(value);
    },

After getting an error about “show” I also wanted to be extra sure the “i” getting shared didn’t cause any issue so I separated it.

If I don’t have if (show), if(hide) then it tried to run both and only one can run. I got an error about show not being defined even though it was defined.

var show = Begrafenis.kosten[input.id][val].show; doesn’t mean anything if [val] doesn’t have a show. If I understood what was going on correctly.

How about using for…in to retrieve the action, and then deciding whether to show or hide afterwards?


chooseType: function(input) {
        var val = input.value.toLowerCase(),
            action,
            i,
            id,
            value = '';
 
        for (action in Begrafenis.kosten[input.id][val]) {
            for (i = 0; i < Begrafenis.kosten[input.id][val][action].length; i++) {
                id = Begrafenis.kosten[input.id][val][action][i];
                value = document.getElementById(id);
                if (action === 'show') {
                    Basis.removeClass(value, 'none');
                } else if (action === 'hide') {
                    Basis.addClass(value, 'none');
                }
            }
        }
        Begrafenis.setSelectedValue(value);
    },

1 Like