4
votes

Possible Duplicate:
javascript test for existence of nested object key

I'm attempting to construct an error message for a formset by testing if a certain object is not undefined, and if it's not undefined, then I end up populating it with that error message. The main problem is that I have to validate if each nested object is undefined, which results in some pretty ugly code. Here's the example:

errorsForField: function(fieldName, formsetName, formNumber) {
            if (typeof this.model.errors != 'undefined'){

                var fieldError = document.createElement('span');
                $(fieldError).addClass('field-error');

                // THE FOLLOWING LINE THROWS ERROR.
                if (formsetName && _.isUndefined(this.model.errors[formsetName][fieldName]) != true) {
                    $(fieldError).text(this.model.errors[formsetname][fieldName]);
                } else if (typeof this.model.errors[fieldName] != "undefined"){
                    $(fieldError).text(this.model.errors[fieldName]);
                }

                this.errors[fieldName] = fieldError.outerHTML;
                return fieldError.outerHTML; 
            }
            return false; 
        },

I get an error stating that I cannot determine [fieldName] of an undefined object this.model.errors[formsetName]. In other words, I have to first determine if this.model.errors[formsetName] is empty and then test if [fieldname] is undefined.

This seems like a really cumbersome solution. Any suggestions for changing this?

2
I've never seen a solution to this problem in pure JavaScript. This is called "safe navigation" or "soaking up nulls" by the way. Coffeescript, which boils down to JS, has this feature. You may want to look at their implementation for inspiration; it seems to use a lot of ternary operators. The overview of Coffeescript has an example. Search that page for "soaking up nulls"Paul Phillips

2 Answers

6
votes

You can create a library function that takes property names as parameters and returns the final value if it exists, or null:

function TryGetPropertyValue(o, propertyName1 /*, ... propertyNameN */) {
    var names = [].slice.call(arguments, 1);
    while (o && names.length) {
        o = o[names.shift()];
    }
    return names.length ? null : o;
}

Call it like:

var err = TryGetPropertyValue(this.model.errors, formsetName, fieldName) ||
          TryGetPropertyValue(this.model.errors, fieldName);
if (err != null) {
    $(fieldError).text(err);
}

If you want it to return undefined instead of null if the field is not found, you can change the function slightly:

function TryGetPropertyValue(o, propertyName1 /*, ... propertyNameN */) {
    var names = [].slice.call(arguments, 1);
    while (o && names.length) {
        o = o[names.shift()];
    }
    if (names.length == 0) {
        return o;
    }
}

http://jsfiddle.net/HbggQ/

3
votes

As Paul suggested, this is an inherent limitation of Javascript. Even Coffeescript (which is just a layer of syntactic sugar on top of JS) doesn't really solve the problem; it just hides the workaround under it's syntactic sugar (which admittedly is really handy)

If you want to stick to Javascript, you basically have two options: use ternary operators, or use boolean operators. Here's examples of each that check A.B.C.D (where A, B, C or D might not exist):

// Returns A.B.C.D, if it exists; otherwise returns false (via ternary)
return !A ? false :
            !A.B ? false :
                   !A.B.C ? false :
                            A.B.C.D ? A.B.C.D : false;

// Returns A.B.C.D, if it exists; otherwise returns false (via booleans)
return A && A.B && A.B.C && A.B.C.D;

Obviously the latter is a lot shorter. Both solutions rely on Javascript's "truthiness" (ie. that the values 0, "", null, and undefined count as false). This should be fine for your case, as none of those values will have an errors property. However, if you did need to distinguish between (say) 0 and undefined, you could use the ternary style, and replace !A with typeof(A) == 'undefined'.