ruby-on-rails Rails + Turbo_stream自定义操作:我可以在没有Stimulus的情况下根据DOM状态做出有条件的响应吗?

nzk0hqpo  于 11个月前  发布在  Ruby
关注(0)|答案(1)|浏览(219)

我有一个页面,里面有几个用于填写学术数据的模态表单,对于每一个模态,相关的Rails控制器动作都会在一个单独的模态中为表单呈现turbo响应。
注意事项:这并不遵循“填充”页面中预先打印的模态的模式,因为在该应用程序中,用户可能需要同时进行多个模态。
所以我这样做是因为我希望用户能够在提交任何表单之前同时处理多个表单。例如,他们可以关闭一个模态以查看下面的页面,或者查看正在进行的不同模态表单。然后他们可以在页面上使用相同的切换再次打开第一个模态,并从他们离开的地方开始。
每个模态只在第一次出现时从头开始呈现,然后可以关闭或打开它,而无需将其从DOM中删除,直到提交。
我目前正在用一个Stimulus控制器来处理这个问题,它检查模态是否已经在DOM中,要么show是现有的模态,要么fetch是来自Rails控制器的响应,使用requestjs-rails来呈现空白表单模态并将其放入DOM中,然后show
刚刚发现Turbo.StreamActions,我想知道这是否可以完全在Turbo响应中完成。
但是我无法想象如何使用Turbo自定义操作来检查DOM状态(具体来说,返回一个布尔值,是否有一个具有相同ID的模态已经打印在页面中):Turbo操作似乎是Ruby --> JS的单向响应。它们用于发送“HTML over the wire”。Turbo的“payload”。StreamAction是html,而不是数据。
或者.我可以写一个自定义的Turbo.StreamActionreturn的值吗?我无法想象它是如何工作的.
这就是我被卡住的地方:

# app/views/somethings/new.erb

# can a turbo_stream.action take two arguments?
<%
args = { partial: "shared/modal_form",
         locals: { title: title, body: body } }

<%= turbo_stream.show_or_render_modal_form(id, args) %>
// app/javascript/application.js

Turbo.StreamActions.open_modal = function () {
  $("#" + this.templateContent.textContent).modal('show')
};

Turbo.StreamActions.modal_exists = function () {
  return document.getElementById(this.templateContent.textContent)
};

Turbo.StreamActions.show_or_render_modal_form = function () {
  // ??? need to pass an id for the form, the partial path,
  //     and local_assigns for the partial. Can that all be 
  //     packed into this.templateContent ?
};
# config/initializers/turbo_stream_actions.rb
  def show_or_render_modal_form(id, args)
    if action(:modal_exists, "#", id)
      action(:open_modal, "#", id)
    else
      action(:render, "#", **args)
    end
  end
cgfeq70w

cgfeq70w1#

这里不需要Stimulus,但是“呈现或显示模态”部分应该由JavaScript处理。

<!-- view -->

<!-- `content` is not necessary, you could render whatever you want from controller actions -->
<!--                                                                  vvvvvvvvvvvvvv        -->
<%= button_to "edit something 1", "/", params: {modal: {id: :modal_1, content: "one"}} %>
<%= button_to "edit something 2", "/", params: {modal: {id: :modal_2, content: "two"}} %>
<!-- adjust urls as needed        ^^^ -->

<div id="modals"></div>

<style type="text/css">
  .hidden { display: none; }
</style>

字符串
当你点击这些按钮时,它应该首先呈现一个文本字段,第二次点击只会显示呈现的模态并隐藏所有其他模态,文本字段中的任何更改都应该保持不变。

# controller

respond_to do |format|
  format.turbo_stream do
    render turbo_stream: turbo_stream.action(
      :modal,   # action
      "modals", # target  (not strictly necessary, you could append modal to <body>)
      #           content (modal form or anything you want to have here)
      helpers.tag.div(id: params[:modal][:id]) do
        helpers.text_field_tag :content, params[:modal][:content]
      end
    )
  end
end
// app/javascript/application.js

// append action for reference: https://github.com/hotwired/turbo/blob/v7.3.0/src/core/streams/stream_actions.ts#L11
Turbo.StreamActions.modal = function () {
  // targetElements are automatically found by Turbo which is just #modals target
  // it is an array because you could have multiple targets with a class selector
  // https://github.com/hotwired/turbo/blob/v7.3.0/src/elements/stream_element.ts#L99
  this.targetElements.forEach((target) => {
    Array.from(target.children).forEach((child) => {
      child.classList.add("hidden")
    })
    if (this.duplicateChildren.length > 0) {
      // duplicateChildren is a TurboStream function
      // https://github.com/hotwired/turbo/blob/v7.3.0/src/elements/stream_element.ts#L75
      this.duplicateChildren.forEach((modal) => {
        // if there is a modal already on the page, just unhide it
        modal.classList.remove("hidden")
      })
    } else {
      target.append(this.templateContent)
    }
  });
};

的数据
显然,我跳过了实际的模态css外观,这将取决于你的前端css框架。

如果你需要更复杂的逻辑,你可以渲染一个<turbo-stream>标签,并带有其他可以在JavaScript中使用的属性:

# config/initializers/turbo_stream_actions.rb

module CustomTurboStreamActions
  # https://github.com/hotwired/turbo-rails/blob/v1.5.0/app/models/turbo/streams/tag_builder.rb#L214
  # add attributes to <turbo-stream> 
  #                                vvvvvvvvvvvvvv
  def modal(target, content = nil, attributes: {}, allow_inferred_rendering: true, **rendering, &block)
    template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
    #                                                   vvvvvvvvvvvv
    turbo_stream_action_tag :modal, target:, template:, **attributes
  end

  ::Turbo::Streams::TagBuilder.include(self)
end

  • 网址:http://github.com/hotwired/turbo-rails/blob/v1.5.0/app/helpers/turbo/streams/action_helper.rb#L26*
# controller

respond_to do |format|
  format.turbo_stream do
    render turbo_stream: turbo_stream.modal(
      "modals",
      helpers.tag.div(id: params[:modal][:id]) do
        helpers.text_field_tag :content, params[:modal][:content]
      end,
      # add some attributes 
      attributes: {if: "something"}
    )
    #=>
    # <turbo-stream if="something" action="modal" target="modals">
    #   <template>
    #     <div id="modal_1">
    #       <input type="text" name="content" id="content" value="one">
    #     </div>
    #   </template>
    # </turbo-stream>
  end
end
// app/javascript/application.js

Turbo.StreamActions.modal = function () {
  console.log(this.getAttribute("if"));
  // ... hopefully, you get the idea.
};

的字符串
但是,也许在这一点上,它会更简单,有一个刺激控制器的前端逻辑。

相关问题