1

In JavaScript I often use the || operator to get the value of some property - or a default value. For instance:

var dens = mapping.defaultElementNamespaceURI||mapping.dens||'';

If mapping has defaultElementNamespaceURI, it will be used, otherwise look for dens or use an empty string by default.

However if my property is boolean, it does not work:

var dom = mapping.allowDom || mapping.dom || true;

Returns true even if mapping.dom is false.

I wonder, what would be the best (laconic but still readable) syntax for defaulting boolean properties? Defaulting in a sense that if the property is defined, use its value otherwise some provided default value. I can write a function for this, sure, but maybe there's someting as elegant as a || b || c?

Code sample:

var a = {};
$('#result').text(a.boo||a.b||true);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre id="result"></pre>

Update

Seems like there's no "syntactically sweet" way to do this, so I've resorted to writing a function:

var defaultValue = function()
{
    var args = arguments;
    if (args.length === 0)
    {
        return undefined;
    }
    else
    {
        var defaultValue = args[args.length - 1];
        var typeOfDefaultValue = typeof defaultValue;
        for (var index = 0; index < args.length - 1; index++)
        {
            var candidateValue = args[index];
            if (typeof candidateValue === typeOfDefaultValue)
            {
                return candidateValue;
            }
        }
        return defaultValue;

    }
}

Considers the last argument to be the default value. Returns the first argument which has the same type as the default value.

lexicore
  • 42,748
  • 17
  • 132
  • 221
  • You'd have to explicitly test the value. If you are working with config *objects*, you can also write a simple merge function that only copies the property if it doesn't exist. – Felix Kling Apr 02 '15 at 21:59
  • 1
    You basically are stuck with doing `typeof` tests or `===` tests (in a way that works, as opposed to my deleted answer :) – Pointy Apr 02 '15 at 22:00
  • yep! misunderstood, and deleted my comment :] – Patrick Apr 02 '15 at 22:01
  • @Patrick I was also thinking: huh, your code is doing exactly what it is intended to do, but with Pointy's comment I see now :) – Michelangelo Apr 02 '15 at 22:02
  • Does this get you what you want? `var dom = mapping.allowDom ? mapping.allowDom : mapping.dom ? mapping.dom : true;` ? – Tony Apr 02 '15 at 22:04
  • `var dom = 'allowDom' in mapping ? mapping.allowDom : true;` will check for the property, and use whatever value is in the property, and only default to `true` if the property isn't defined. – adeneo Apr 02 '15 at 22:04
  • Unfortunately both `undefined` and `false` are "not truthy" (falsey?) So without checking if `mapping.dom` is a boolean you're out of luck. – ryanyuyu Apr 02 '15 at 22:04
  • @Tony Probably the closest. – lexicore Apr 02 '15 at 22:07
  • `'boo' in a ? a.boo : ('b' in a ? a.b : true)` – CD.. Apr 02 '15 at 22:13
  • @Tony Why don't you post it as an answer? – lexicore Apr 02 '15 at 22:18
  • @lexicore Well I wasn't sure if that was giving you the answer you wanted. My test cases work, but I didn't use your data. =) – Tony Apr 02 '15 at 22:20

6 Answers6

3

The closest thing to elegant you'll find is:

var dom = mapping.allowDom ? mapping.allowDom : mapping.dom ? mapping.dom : true;

As pointed out, this is merely a presence check. You could do either of these to accept false values:

var dom = mapping.allowDom != null ? mapping.allowDom : mapping.dom != null ? mapping.dom : true;

var dom = 'allowDom' in mapping ? mapping.allowDom : 'dom' in mapping ? mapping.dom : true;

Update for completeness: The function in your update is ideal if you have a variable list of values to compare against. But, if the only things we're comparing are the 2 mentioned (and we were in control of setting/verifying mapping.thing) my code would look like the following. (I also don't know how you're using dom later on.)

var dom = mapping.allowDom || mapping.dom;
dom = dom === undefined ? true : dom;

Anything more than 2 elements and I would probably opt for your method.

Tony
  • 2,473
  • 1
  • 21
  • 34
  • 1
    Is the OP sure this is what he wants. I tried it here, http://jsfiddle.net/myra7x2m/ and I got true when the values had false in them. – John Apr 02 '15 at 22:26
  • 1
    This will fail on `false` values as well, as the ternary will continue if the value is `false`, not just when it's undefined etc. – adeneo Apr 02 '15 at 22:27
  • Valid points. My original comment asked if this was the desired outcome. A sample modification could be this: http://jsfiddle.net/myra7x2m/1/ – Tony Apr 02 '15 at 22:29
  • I like the != null it's better than my answer as it should cover undefined and null. – John Apr 02 '15 at 22:43
  • @John Yes. I tested the compare to both. As long as you don't do the strict `!==` check this should solve both. Just tell jshint to be quiet about it. =) – Tony Apr 02 '15 at 22:44
  • Yes very true jsHint will bother you about that one, but sometimes you want that effect from your code. It's just one thing that makes JS that sweet. – John Apr 02 '15 at 22:49
  • This solution relies on quirky and unobvious behavior by `!=`, what Douglas Crockford in his book _JavaScript: The Good Parts_ called "a bad part". – Michael Lorton Apr 03 '15 at 00:05
2
a.x || b

will return the value of b if a.x is "falsey" -- undefined, empty string, 0, a few other values.

If you need to display something unless the field is in an object, even if the field's value is falsey, you have to say so explicitly.

'x' in a ? a.x : b

If you explicitly set a.x to undefined, that expression will return 'undefined', not b.

Michael Lorton
  • 43,060
  • 26
  • 103
  • 144
1

It's a lot longer than the accepted answer, but it should give you exactly what you want from what I understand.

var mapping = {dom: false};
var dom = typeof mapping.allowDom !== "undefined" ? mapping.allowDom : typeof mapping.dom !== "undefined" ? mapping.dom : true;
$('#result').append("" + dom);

More tests on this fiddle.

http://jsfiddle.net/myra7x2m/

John
  • 7,114
  • 2
  • 37
  • 57
  • Is there a benefit to using `typeof` over a `!= null` or `in` comparison? – Tony Apr 02 '15 at 22:43
  • 1
    @Tony != null is better if he wants it to be true when it's null or undefined which I assume is what he wants, but he may only want it when it is undefined I am unsure. I did post a comment on your answer as soon as I saw it updated. – John Apr 02 '15 at 22:48
  • 1
    @Tony Oh and as for the in comparison if he wanted to set a certain thing to be undefined he could whereas with in you would have to delete the element instead to have the same effect so not much difference there just different preference. So the only problem with in is if the element is literally undefined it will return undefined. – John Apr 02 '15 at 23:00
  • Looks like `typeof` would be the preferred route based on [this answer](http://stackoverflow.com/a/894877/413397). – Tony Apr 03 '15 at 22:30
1

Another nice form of implementation is via function and switch without control expression what makes it to a nicer if/else block:

dom = (function() {
    switch (false) {
      case mapping.allowDom == null:
        return mapping.allowDom;
      case mapping.dom == null:
        return mapping.dom;
      default:
        return true;
    }
})();

You can test this here

http://jsfiddle.net/w3jtsk5u/

If you are wondering, why i am comparing for == null and not != null and it still is working, is that i used switch(false) so the value compared to is false which negates the expression.

for compact friends, of course you could write:

dom = (function(){switch(false){
  case mapping.allowDom == null: return mapping.allowDom;
  case mapping.dom == null:      return mapping.dom;
  default:                       return true;
}})();

still readable :)

hexerei software
  • 3,100
  • 2
  • 15
  • 19
  • I like the idea of a function not everything can or I guess I should say should be put on one line. Yeah it's elegant, but sometimes you give up readability and simplicity with that approach. – John Apr 02 '15 at 23:06
0

When it comes to options that are booleans testing for null is the way to go:

var dom;
if (mapping.allowDom != null) {
  dom = mapping.allowDom;
} else if (mapping.dom != null) {
  dom = mapping.dom;
} else {
  dom = true;
}

I would wrap the above in a function or a Maybe monad. Note this is the only use case I use the == or != operators any other comparison is always the === or !== operators. Because a == null will test for null or undefined.

Sukima
  • 9,965
  • 3
  • 46
  • 60
  • Not quite sure why you compare boolean or potentially missing properties with `null` via the `!=` operator. – lexicore Apr 02 '15 at 22:16
  • You could also compare with `mapping.allowDom !== false` but I found the `!= null` more expressive to me. And it is also the same output you get from CoffeeScript's default arguments syntax. – Sukima Apr 03 '15 at 12:11
0

The OR operator will return the 1st 'truthy' value, if that value is evaluated to false it will check the next until the last.

Therefore it's only possible to have false or 'falsey' value as a default as the last in the sequence.

Dave Jones
  • 344
  • 1
  • 7