0

It's possible to focus on an input field using D3, without even the need to go through setTimeout (I don't know why, but I'm not complaining).

But this only works the first time. After the second press on Set (i.e.: Set, Reset, Set), the input field does not receive focus. Why?

Setting a callback to focus after the transition is complete gives me an error.

<!DOCTYPE HTML>
<head>
    <style>
        div { width: 300px; }
        div#outerdiv  { height: 500px; background-color: #ccc; }
        div#topdiv    { height: 400px; background-color: #dff; }
        div#bottomdiv { height: 0px;   background-color: #ffd; display: 'none' }
        input:focus { background-color: pink; }
    </style>
</head>
<body>
    <div id="outerdiv">
        <div id="topdiv">
            <button onclick=open_form();>Set</button>
            <button onclick=close_form();>Reset</button>
        </div>

        <div id="bottomdiv">
            <form action="javascript:close_form()">
                Name:
                <input type="text" id="myTextField" name="name">
                <input type="submit" name="submit" value="Submit">
            </form>
        </div>
    </div>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>

    <script>
        function open_form() {
            d3.select('#topdiv').transition().duration(500)
              .style('height', '100px');
            d3.select('#bottomdiv').transition().duration(500)
              .style('height', '300px').style('display', 'inline-block');

            d3.select("#myTextField").node().focus();
        } 
        function close_form() {
            d3.select('#topdiv').transition().duration(500)
              .style('height', '390px');
            d3.select('#bottomdiv').transition().duration(500)
              .style('height', '10px').style('display', 'none');
        }
    </script>
</body>
Sebastian
  • 119
  • 12

2 Answers2

1

It wasn't able to find #myTextField because it was mid-transition. You can fix this by removing the transition for display: inline-block, as seen below.

function open_form() {
    d3.select('#topdiv').transition().duration(500)
      .style('height', '100px');
    d3.select('#bottomdiv').style('display', 'inline-block');
    d3.select('#bottomdiv').transition().duration(500)
      .style('height', '300px');
    d3.select("#myTextField").node().focus();
} 
1

Each time you hit the Reset button, i.e. when close_form() is called, your #bottomdiv is transitioned to display: none:

d3.select('#bottomdiv').transition().duration(500)
    .style('height', '10px').style('display', 'none');

This has some side effects, though. Looking at the HTML5 spec on focus management shows, that an element, which is not being rendered, is also not focusable:

7.4.2 Focus management

An element is focusable if all of the following conditions are met:

Hence, the next time you hit Set whereby invoking open_form() the input is not focusable because the transition to inline-block has not come to an end yet. The easiest way around this is to listen for the transition's end event which is fired once the transition ends.

   d3.select('#bottomdiv').transition().duration(500)
       .style('height', '300px').style('display', 'inline-block')
       .on("end", () => d3.select("#myTextField").node().focus());

Have a look at the following snippet for a working demo:

function open_form() {
    d3.select('#topdiv').transition().duration(500)
      .style('height', '100px');
    d3.select('#bottomdiv').transition().duration(500)
      .style('height', '300px').style('display', 'inline-block')
      .on("end", () => d3.select("#myTextField").node().focus());

} 
function close_form() {
    d3.select('#topdiv').transition().duration(500)
      .style('height', '390px');
    d3.select('#bottomdiv').transition().duration(500)
      .style('height', '10px').style('display', 'none');
}
div { width: 300px; }
div#outerdiv  { height: 500px; background-color: #ccc; }
div#topdiv    { height: 400px; background-color: #dff; }
div#bottomdiv { height: 0px;   background-color: #ffd; display: 'none' }
input:focus { background-color: pink; }
<body>
    <div id="outerdiv">
        <div id="topdiv">
            <button onclick=open_form();>Set</button>
            <button onclick=close_form();>Reset</button>
        </div>

        <div id="bottomdiv">
            <form action="javascript:close_form()">
                Name:
                <input type="text" id="myTextField" name="name">
                <input type="submit" name="submit" value="Submit">
            </form>
        </div>
    </div>

    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>

</body>
Community
  • 1
  • 1
altocumulus
  • 21,179
  • 13
  • 61
  • 84
  • Homework to self: figure out the difference between .on('end', ..) and .each('end', ..) as listed here: https://stackoverflow.com/questions/10692100/invoke-a-callback-at-the-end-of-a-transition/10692220#10692220 – Sebastian Jun 06 '17 at 22:09
  • 1
    @Sebastian Well, [`transition.each("end")`](https://github.com/d3/d3-3.x-api-reference/blob/master/Transitions.md#each) was the way of registering handler functions in v3. As of v4 this was renamed to [`transition.on("end")`](https://github.com/d3/d3-transition/blob/master/README.md#transition_on). – altocumulus Jun 07 '17 at 08:05