formkit icon indicating copy to clipboard operation
formkit copied to clipboard

Auto-focus First Input on Next Page in Multistep Form

Open totalhack opened this issue 3 years ago • 1 comments

Describe the feature

The current multi-step form example does not auto-focus the first input on next page which is a useful feature I've seen implemented in other form builders such as JotForm. I'm wondering what the recommended way to do this is with FormKit, particularly when building the form from a JSON schema.

I can easily add a handler to the next/previous step functions of the useSteps plugin but I'm not sure if A) that will be the right place to do it timing-wise since the next input may not have rendered yet or B) If there is some built-in way to get the first/next visible input given a root form node object. For the former, I would typically look to something like nextTick before calling focus but I don't think I can access that (or don't know how). For the latter, my first thought was to just pass a reference to group nodes to the next step handler and then find the first non-hidden input on the fly to call focus.

Interested to hear your thoughts! Thanks for your help.

totalhack avatar Jul 18 '22 18:07 totalhack

In my dev environment I have this working in some form with the approach I described above. In the next step click handler I get the new active group node and find the first input. I then focus on that element after a brief setTimeout. Some snippets I added:

Utility in useSteps() that finds the first input of a node:

  const findFirstInput = (n) => {
    for (var i = 0; i < n.children.length; i++) {
      const child = n.children[i]
      if (child.type === 'input' || child.type === 'list') {
        return child
      }
      const res = findFirstInput(child)
      if (res) {
        return res
      }
    }
    return null
  }

Then down at the end of setStep:

    if (autoFocus) {
      const newNode = steps[activeStep.value].node
      setTimeout(function () {
        const firstInput = findFirstInput(newNode)
        if (!firstInput) {
          return
        }
        const elem = document.getElementById(firstInput.context.id)
        elem.focus()
      }, 300);
    }

Note that without setTimeout it did not work. Is there some better way than setTimeout, perhaps a particular event on the new group node that I should wait for with node.on? Just hoping there is something more precise. EDIT: I just quickly tried created, settled, and even prop events and am not seeing anything akin to "inputs are now visible for this node".

Also, is child.type === 'input' || child.type === 'list' a reasonable way to filter to nodes that accept input? Not sure how future proof that is. EDIT: I had to also exclude hidden inputs via child.context.type === 'hidden'

Thanks.

totalhack avatar Jul 20 '22 17:07 totalhack

I've improved on the approach above based on this SO answer. See focusAndOpenKeyboard usage in the useSteps plugin for an example.

I still have the open question about confirming the right way to filter to nodes that accept input but I'm going to close this as I've got it ~working for now. It would probably be a useful thing to build right into formkit (or some officially supported multi-step form plugin).

totalhack avatar Aug 22 '22 13:08 totalhack