如何在经常变化的项目上使用刺激和turbo_stream?

问题描述 投票:0回答:1

我有一个简单的物品清单。每个项目都有经常变化的属性(例如每秒)。

我使用

turbo_stream_from "list_items"
以保持数据新鲜。

我想在每个 list_item 旁边添加一个复选框,以便能够例如从列表中删除特定项目。

当用户检查/选择特定项目时,以及之后通过 turbo 流接收到新更改时 -> 选择丢失,因为该项目的 HTML 已被完全替换。

有没有办法通过“Rails 标准”来处理这个问题?考虑 React/Vue(具有虚拟 DOM 的库)是否更好?

ruby-on-rails stream turbo
1个回答
1
投票

这是一个解决方案:morphdom + 自定义涡轮流操作:

# app/models/item.rb

class Item < ApplicationRecord
  after_create_commit -> { broadcast_append_to(:list_items) }
  after_destroy_commit -> { broadcast_remove_to(:list_items) }
  # send a custom turbo stream action, instead of `update`
  after_update_commit -> { broadcast_action_to(:list_items, action: :morph, target: self, **broadcast_rendering_with_defaults({})) }

  # `Item.play` from a console to send some test streams
  def self.play
    Item.destroy_all
    10.times { Item.create(name: "") }
    item_ids = Item.ids
    count = 0
    loop do
      item = Item.find(item_ids.sample)
      item.update(name: item.name + ("a".."z").to_a.sample)

      Item.where("length(name) > 15").update_all(name: "")

      count += 1
      Turbo::StreamsChannel.broadcast_prepend_to(:list_items,
        target: :items,
        html: %(<div id="counter">#{count}</div>)
      )
    end
  end
end
# app/views/items/index.html.erb
<%= turbo_stream_from :list_items %>
<%= tag.div id: :items, class: "grid gap-2" do %>
  <%= render @items %>
<% end %>

# app/views/items/_item.html.erb
<%= tag.div id: dom_id(item), class: "flex gap-4" do %>
  <%= check_box field_name(:item, item.id), :_destroy %>
  <%= link_to item.name, item %>
<% end %>

在前端处理自定义morph动作:

// config/importmap.rb

pin "morphdom", to: "https://cdn.jsdelivr.net/npm/[email protected]/dist/morphdom-esm.js"
// app/javascript/application.js

import morphdom from "morphdom";

// this function is called when `turbo_stream.action(:morph, ...)` renders
// on the front end.
Turbo.StreamActions.morph = function () {
  this.targetElements.forEach((target) => {
    morphdom(target, this.templateContent, {
      // this is the magic bit
      onBeforeElUpdated: function (fromElement, toElement) {
        if (fromElement.isEqualNode(toElement)) return false;
        return true;
      },
    });
  });
};

https://github.com/patrick-steele-idem/morphdom

https://turbo.hotwired.dev/handbook/streams#custom-actions


更新

对于自定义操作有

action
方法,它适用于模板和控制器:
https://github.com/hotwired/turbo-rails/blob/v1.4.0/app/models/turbo/streams/tag_builder.rb#L214

<%= tag.div "one", id: :target_id %>


<%= turbo_stream.action :morph, :target_id, tag.div("two") %>

<%= turbo_stream.action :morph, :target_id, partial: "two", locals: {name: "two"} %>

要使其成为一种方法,请将其添加到 turbo 标签生成器中:

# config/initializers/turbo_stream_actions.rb

module TurboStreamActions
  def morph(...)
    action(:morph, ...)
  end

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

现在你可以这样做:

<%= turbo_stream.morph :target_id, partial: "two", locals: {name: "three"} %>
© www.soinside.com 2019 - 2024. All rights reserved.