Rails 7/Stimulus:如何在控制器动作中引用动态生成ID的元素?
问题:Stimulus中引用动态生成ID的按钮元素
我有一个Entry模型,用局部视图渲染每个条目,每个条目下方设有按钮,点击该按钮会在Turbo Frame中显示完整条目。当Turbo Frame加载时,Stimulus控制器中的#toggle动作会触发。按钮的ID是Rails通过dom_id(entry, :show)动态生成的(例如show_entry_1000529641),我希望在#toggle动作中引用该按钮元素。目前的做法是在Turbo Frame上设置data-button属性存储按钮ID,再通过ID查找元素,但想知道是否有更优的实现方式。
当前实现代码
局部视图 entries/_entry.html.erb
#... <%= button_to "Show #{entry}", entry_path(entry), id: dom_id(entry, :show), method: :get, data: { turbo_frame: dom_id(entry) } %> #... <%= turbo_frame_tag entry, data: { controller: "buttons", action: "turbo:frame-load->buttons#toggle", button: dom_id(entry, :show) } %>
Stimulus控制器 buttons_controller.js
import { Controller } from "@hotwired/stimulus" export default class extends Controller { toggle() { const button_id = this.element.getAttribute('data-button') const button = document.getElementById(button_id) console.log(button) } }
优化方案
方案1:绑定控制器到按钮(最推荐)
换个思路,把Stimulus控制器直接绑定到按钮上,利用事件冒泡特性监听turbo:frame-load事件,这样控制器里的this.element就是目标按钮,完全不需要额外查找:
修改视图:
#... <%= button_to "Show #{entry}", entry_path(entry), id: dom_id(entry, :show), method: :get, data: { turbo_frame: dom_id(entry), controller: "buttons", action: "turbo:frame-load->buttons#toggle" } %> #... <%= turbo_frame_tag entry %>
修改控制器:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { toggle() { // this.element就是触发关联的按钮本身 console.log(this.element) } }
方案2:利用Turbo的现有关联关系
按钮和Turbo Frame本来就通过data-turbo-frame属性关联,直接通过Frame的ID反向查找对应按钮,不需要额外添加属性:
修改视图(去掉Frame上的data-button):
<%= turbo_frame_tag entry, data: { controller: "buttons", action: "turbo:frame-load->buttons#toggle" } %>
修改控制器:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { toggle() { const frameId = this.element.id // 通过Turbo的关联属性查找按钮 const button = document.querySelector(`[data-turbo-frame="${frameId}"]`) console.log(button) } }
方案3:动态关联Stimulus目标
给按钮添加Stimulus目标属性和共同的条目标识,通过目标集合匹配对应按钮:
修改视图:
#... <%= button_to "Show #{entry}", entry_path(entry), id: dom_id(entry, :show), method: :get, data: { turbo_frame: dom_id(entry), buttons_target: "toggleButton", entry_id: entry.id } %> #... <%= turbo_frame_tag entry, data: { controller: "buttons", action: "turbo:frame-load->buttons#toggle", entry_id: entry.id } %>
修改控制器:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["toggleButton"] toggle() { const entryId = this.element.dataset.entryId // 在目标集合中匹配对应条目ID的按钮 const button = this.toggleButtonTargets.find(target => target.dataset.entryId === entryId) console.log(button) } }
总结
优先推荐方案1,它完全贴合Stimulus的设计理念,代码最简洁,不需要额外的DOM操作或属性配置。如果因业务限制无法将控制器绑定到按钮,方案2是次优选择,利用Turbo自带的关联关系实现无侵入式查找。
内容的提问来源于stack exchange,提问作者user3574603
相关产品推荐
相关产品推荐

