1

Consider the following simplified html code:

<html>
    <head>
        <style>
            .opts {display: none}
        </style>
    </head>

    <body onload = "list = document.getElementsByClassName('opts')">

        <form>
            <ul>
                <li>
                    <label>Show Options?</label>
                    <input type="radio" onclick="list[0].style.display = 'block'; list[1].style.display = 'block'; list[2].style.display = 'block';">yes<br>
                    <input type="radio" onclick="list[0].style.display = 'none'; list[1].style.display = 'none'; list[2].style.display = 'none';">no<br>
                </li>

                <li class="opts">
                    <label>Option 1</label>
                    <input type="text">
                </li>

                <li class="opts">
                    <label>Option 2</label>
                    <input type="text">
                </li>

                <li class="opts">
                    <label>Option 3</label>
                    <input type="text">
                </li>

                ... etc ...

            </ul>
        </form>

    </body>

</html>

Basically, it uses javascript to show or hide optional elements according to user onclick event.

Issue
Currently, for each element in getElementsByClassName there is a line setting 'manually' the display property:

list[0].style.display = 'block';
list[1].style.display = 'block';
list[2].style.display = 'block';

Is there a simpler way to do that? For example, something like:

list[All].style.display = 'block';

ps: of course this could be done via some for loop and an additional function declaration, but I'm looking for an easy inline js code (ie: no external js files)

Followup

Based on comments, there are two proposed ways to easily code this inline without external files:

1) spread syntax

[...list].forEach(el => el.style.display = 'block')

2) for loop new syntax

for (const x of list) x.style.display = "block";

Particularly, I've decided to use the for loop new syntax due to be easier to read then the spread syntax. However, since both ways are somewhat recent features of JavaScript, caution is advised for older browsers compatibility.

Mark Messa
  • 440
  • 4
  • 22
  • 1
    No, you cannot do it inline unless you use a loop of some sort. – Andrew Li Dec 18 '17 at 00:28
  • 2
    Pretty short: `[...list].forEach(el => el.style.display = 'block')` though I'd opt for something more readable. – Andrew Li Dec 18 '17 at 00:29
  • `block` should be `'block'` (I missed the quotes in my comment) but yes. `getElementsByClassName` returns an HTMLCollection, so using `[...list]` uses spread syntax to transform it into an array so you can use `Array#forEach`. – Andrew Li Dec 18 '17 at 00:34
  • Why do you think that a `for` loop would require another function declaration? –  Dec 18 '17 at 00:38
  • It's spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator – Andrew Li Dec 18 '17 at 00:41
  • 1
    You can use a `for-of` loop instead, and not have to use any kind of function or global variable. `for (const x of list) x.style.display = "block";` –  Dec 18 '17 at 00:43
  • @MarkMessa It's an integral syntax for ES6. It allows you to spread iterables into others, for example `[1, 2, ...[4, 5]]` gives `[1, 2, 4, 5]`. The `[4, 5]` is spread into the surrounding iterable. The MDN link is a great place to start. – Andrew Li Dec 18 '17 at 01:08
  • @MarkMessa You can see the browser support in the linked MDN page. – Andrew Li Dec 18 '17 at 01:12
  • Instead of directly modifying the style, consider adding and removing classes instead. Also, toggle between "none" and "" (empty string) so that the element then adopts its default or inherited display value. The [default for input elements](https://stackoverflow.com/questions/6867254/browsers-default-css-for-html-elements) is "inline-block" (but toggling with "" means you don't need to know that). ;-) – RobG Dec 18 '17 at 01:35
  • You can modify the class rules which will affect all elements with the class in one go, no matter how many there are. But it's more code than a simple loop. – RobG Dec 18 '17 at 02:18

2 Answers2

1

// setup for the environment
const All = Symbol("All");
const setter = {set: function(fn) {
  for (var i = 0; i < this.length; i++) {
    fn(this[i], i, this);
  }
}};
Object.defineProperty(HTMLCollection.prototype, All, setter);
Object.defineProperty(NodeList.prototype, All, setter);




// Your code
const list = document.getElementsByClassName('opts')

list[All] = x => x.style.fontWeight = "bold";
<form>
  <ul>
    <li>
      <label>Show Options?</label>
      <input type="radio" onclick="list[0].style.display = 'block'; list[1].style.display = 'block'; list[2].style.display = 'block';">yes<br>
      <input type="radio" onclick="list[0].style.display = 'none'; list[1].style.display = 'none'; list[2].style.display = 'none';">no<br>
    </li>

    <li class="opts">
      <label>Option 1</label>
      <input type="text">
    </li>

    <li class="opts">
      <label>Option 2</label>
      <input type="text">
    </li>

    <li class="opts">
      <label>Option 3</label>
      <input type="text">
    </li>

    ... etc ...

  </ul>
</form>
  • Wrapped in quotes, but yes, if you desire to put that much repeating JS in the HTML anyway. –  Dec 18 '17 at 01:06
  • You probably shouldn't assume that browsers implement prototype inheritance for host objects. There is no requirement for them to do so, or at least for them to be modifiable, see [*What's wrong with extending the DOM*](http://perfectionkills.com/whats-wrong-with-extending-the-dom/). The above code throws errors in Chrome. – RobG Dec 18 '17 at 01:39
  • @RobG: That article was mostly relevant 6 years ago. I get no errors in Chrome. Any browser that doesn't allow host objects to be modified is a browser that nobody will use because most JS code will break. –  Dec 18 '17 at 12:27
  • Throws errors in Chrome Version 58.0.3029.110 and IE 11. I'm on a site whose SOE is Windows 7 so don't blame me for the browser I'm running (I expect there are many people who can't run the latest browser for similar reasons). The vast majority of script doesn't modify host objects, it's a bad design decision. Most sites seem to avoid it. In IE, `typeof HTMLCollection` returns "object", not "function". It is likely that in future browsers will seal some host objects so you can't modify them. – RobG Dec 19 '17 at 06:16
  • @RobG: Chrome has been able to modify host objects from the start. The error is probably from the ES6 syntax. [jQuery mutates host objects](https://jsfiddle.net/benudj8s/) (though not prototypes). It is extremely common to polyfill missing/broken stuff on host prototypes. So yes, the web would break. It is extremely unlikely that browsers will seal these objects. The `typeof` result is immaterial and entirely unrelated to whether or not you can extend its `.prototype`. –  Dec 19 '17 at 15:23
  • When attaching a listener to a DOM element, jQuery adds its own properties as a component of its event system. That (adding non–standard properties) has been a criticism of jQuery for a long time. But DOM elements are supposed to be modifiable, within limits, so it's a philosophical argument. However, relying on modification of host constructors and prototypes has been recognised as a bad idea since the very first version of Prototype.js (which abandoned the practice). The point of the *typeof* test was just to show that host objects don't always behave consistently. – RobG Dec 20 '17 at 00:49
  • @RobG: The most salient arguments in your linked article were based on the problems of IE7 and lower. Since host prototypes are mutable, and will continue to be so since preventing it will break all polyfills, please tell me what problem will be created by the prototype modification in my answer? –  Dec 20 '17 at 12:01
0

Consider toggling classes rather than directly setting display values. Also, for a straight binary selection, I'd just use a checkbox.

Iterating over an array using forEach isn't a lot of code, and accommodating older browsers is simple too:

function toggleOpts() {
  var opts = Array.from(document.querySelectorAll('.opts'));
  opts.forEach(function(opt) {
    opt.style.display = this.checked ? '' : 'none';
  }, this);
}
window.onload = function() {
  document.getElementById('optToggle').addEventListener('click', toggleOpts, false);
}
<title>Sample</title>
<form>
  <ul>
    <li>
      <label for="optToggle">Show Options?
        <input type="checkbox" id="optToggle" checked>yes
      </label>
    </li>

    <li class="opts">
      <label>Option 1</label>
      <input type="text">
    </li>

    <li class="opts">
      <label>Option 2</label>
      <input type="text">
    </li>

    <li class="opts">
      <label>Option 3</label>
      <input type="text">
    </li>
  </ul>
</form>
RobG
  • 142,382
  • 31
  • 172
  • 209