admin管理员组

文章数量:1316974

I have a simple checklist adding functionality, with two different checklists on the page.

Turbo adds the form to the frame, and then i'm trying to set the focus using stimulus.

The trouble is, when submitting the form by hitting return, even though logging the target shows it has found the correct input, it simply sets the focus to the first instance of the controller on the page, instead of the one that just got connected. when submitting the form using the submit button, this works fine.

How can I make it work?


import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["name"]
  connect() {      
    console.log('Checklist controller connected'); 
    this.focusInput();     
  }

  focusInput() {    
    console.log(this.nameTarget)
    console.log('set focus')
    this.nameTarget.focus()
  }
}

The form looks like this:

<div  data-controller="checklist" >
  <%= simple_form_for trip.checklist_items.new , data: { checklist_target: "form" }   do |f| %>
    <%= f.input :checklist_type, as: 'hidden', input_html: { value: checklist_type }  %>          
    <%= f.input :trip_id, as: 'hidden', input_html: { value: trip.id }  %>    
    <div class="row">
      <div class="col-md-9">
        <%= f.input :name, label: false,  placeholder: 'E.G Ski Goggles', required: true,  input_html: { data: {  checklist_target: "name" } } %>   
      </div>
      <div class="col-md-3">    
        <%= button_tag(type: 'submit', class: 'btn btn-primary btn-full') do %>
          Add Item to Checklist <i class='bi bi-arrow-90deg-left icon-rotate-90'></i>
        <% end %>
      </div>
    </div>                                  
  <% end  %>
</div>

and the turbo stream

<%= turbo_stream.append "checklist_items_frame_#{@checklist_item.checklist_type}" do %>
  <%= render partial: "checklist_items/checklist_item" , locals: { checklist_item: @checklist_item, highlight: true }  %>
<% end %>

<%= turbo_stream.update "new_checklist_item_#{@checklist_item.checklist_type}" do %>
  <%= render "form", trip: @checklist_item.trip, checklist_type: @checklist_item.checklist_type %>
<% end %>

I have a simple checklist adding functionality, with two different checklists on the page.

Turbo adds the form to the frame, and then i'm trying to set the focus using stimulus.

The trouble is, when submitting the form by hitting return, even though logging the target shows it has found the correct input, it simply sets the focus to the first instance of the controller on the page, instead of the one that just got connected. when submitting the form using the submit button, this works fine.

How can I make it work?


import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["name"]
  connect() {      
    console.log('Checklist controller connected'); 
    this.focusInput();     
  }

  focusInput() {    
    console.log(this.nameTarget)
    console.log('set focus')
    this.nameTarget.focus()
  }
}

The form looks like this:

<div  data-controller="checklist" >
  <%= simple_form_for trip.checklist_items.new , data: { checklist_target: "form" }   do |f| %>
    <%= f.input :checklist_type, as: 'hidden', input_html: { value: checklist_type }  %>          
    <%= f.input :trip_id, as: 'hidden', input_html: { value: trip.id }  %>    
    <div class="row">
      <div class="col-md-9">
        <%= f.input :name, label: false,  placeholder: 'E.G Ski Goggles', required: true,  input_html: { data: {  checklist_target: "name" } } %>   
      </div>
      <div class="col-md-3">    
        <%= button_tag(type: 'submit', class: 'btn btn-primary btn-full') do %>
          Add Item to Checklist <i class='bi bi-arrow-90deg-left icon-rotate-90'></i>
        <% end %>
      </div>
    </div>                                  
  <% end  %>
</div>

and the turbo stream

<%= turbo_stream.append "checklist_items_frame_#{@checklist_item.checklist_type}" do %>
  <%= render partial: "checklist_items/checklist_item" , locals: { checklist_item: @checklist_item, highlight: true }  %>
<% end %>

<%= turbo_stream.update "new_checklist_item_#{@checklist_item.checklist_type}" do %>
  <%= render "form", trip: @checklist_item.trip, checklist_type: @checklist_item.checklist_type %>
<% end %>
Share Improve this question edited Jan 30 at 19:53 Alex 30.1k10 gold badges66 silver badges81 bronze badges asked Jan 29 at 19:39 WillWill 4,7443 gold badges40 silver badges70 bronze badges 2
  • can you share your form. – Alex Commented Jan 30 at 8:15
  • @Alex - ok added more of the code. thanks for looking – Will Commented Jan 30 at 9:23
Add a comment  | 

2 Answers 2

Reset to default 1

Can confirm that clicking a button sets focus correctly, but hitting Ender from the input focuses the first input. I don't know why it behaves differently, but there is a solution.

Rendering the same form multiple times produces duplicate id attributes on inputs. Removing these ids or making them unique fixes the issue.

This is a simplified setup:

# app/views/home/_form.html.erb

<div data-controller="checklist">
  <%= form_with url: "/" do |f| %>
    <%= f.hidden_field :type, value: type %>
    <%= f.text_field :name, data: {checklist_target: "name"} %>
    <%= f.submit %>
  <% end %>
</div>
# app/views/home/create.turbo_stream.erb

<%= turbo_stream.update "new_checklist_item_#{params[:type]}" do %>
  <%= render "form", type: params[:type] %>
<% end %>

<%= turbo_stream.after "new_checklist_item_#{params[:type]}" do %>
  <div><%= params[:name] %></div>
<% end %>
# app/views/home/index.html.erb

<div id="new_checklist_item_one">
  <%= render "form", type: "one" %>
</div>
<div id="new_checklist_item_two">
  <%= render "form", type: "two" %>
</div>
<div id="new_checklist_item_three">
  <%= render "form", type: "three" %>
</div>
// app/javascript/controllers/checklist_controller.js

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="checklist"
export default class extends Controller {
  static targets = ["name"]

  nameTargetConnected(target) {
    target.focus()
  }
}

The fix:

:namespace - A namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.

https://api.rubyonrails./classes/ActionView/Helpers/FormHelper.html#method-i-form_with

<%= form_with url: "/", namespace: type do |f| %>

same for simple_form:

<%= simple_form_for trip.checklist_items.new, namespace: checklist_type do |f| %>

Alternatively, you can remove the id from the input:

<%= f.text_field :name, id: nil, data: {checklist_target: "name"} %>

for simple_form:

<%= f.input :name, 
  input_html: { 
    id: nil,
    data: {checklist_target: "name"}
  } 
%>   

Alex's answer is better, but as a workaround this worked too. Set the focus immediately.

 connect() {          
    this.nameTarget.focus()     
    this.checkFocusTime(1)  
   
   ...
   
  }

  checkFocusTime(time){
     setTimeout(() => {      
       this.focusInputUnlessActive()
     }, time);
   }

本文标签: ruby on railsStimulus Not Setting focus for newly connected ControllersStack Overflow