Turbo Stream 和个性化的内容
Hotwire 和Turbo对于实时更新网页而不需要加载整页非常的好。
但是如果你想使用stream传个性化的信息到客户端,你可能会遇到点问题,比如发帖人“James” ,如果你是“James”,就将James 变成 You。
看看官方的相关例子
但是如果你想使用stream传个性化的信息到客户端,你可能会遇到点问题,比如发帖人“James” ,如果你是“James”,就将James 变成 You。
看看官方的相关例子
<!-- app/views/messages/_message.html.erb --> <div id="<%= dom_id message %>"> <%= message.content %> </div> <!-- app/views/messages/index.html.erb --> <h1>All the messages</h1> <%= render partial: "messages/message", collection: @messages %>
# app/controllers/messages_controller.rb class MessagesController < ApplicationController def index @messages = Message.all end def create message = Message.create!(params.require(:message).permit(:content)) respond_to do |format| format.turbo_stream do render turbo_stream: turbo_stream.append(:messages, partial: "messages/message", locals: { message: message }) end format.html { redirect_to messages_url } end end end
这个示例是当我们这index页面提交一个请求创建一个new message,通过turbo stream就不会重新打开index页面,而是直接append 这个message的html。 使用这个partial: _message.html.erb. 不需要加载整页。非常简洁。
我们扩展这例子,增加个消息的发送者 :message.user.name。 Message模型关联一个User模型。这是这个新的partial
我们扩展这例子,增加个消息的发送者 :message.user.name。 Message模型关联一个User模型。这是这个新的partial
<!-- app/views/messages/_message.html.erb --> <div id="<%= dom_id message %>"> <span class="from"><%= message.user.name %></span>: <%= message.content %> </div>
这个没问题。但是如果想通过You代替实际的名字。当看到是我发的信息的情况下。我们假设有个current_user的方法。现在新的partial变成了:
<!-- app/views/messages/_message.html.erb --> <div id="<%= dom_id message %>"> <span class="from"> <% if message.user == current_user %> You <% else %> <%= message.user.name %> </span>: <%= message.content %> </div>
按道理,我应该将这些逻辑判断的代码做成一个helper,但是现在我们先不考虑这些优化的问题。
现在当你加载这个index page of messages。你看见的可能就是你预期想看到的。一些是别人的名字,而你留下的message写的是You。
但是当你post一个新message,当这个消息出现增加在bottom底部时候,他不是You,而是James。 发生了什么呢?关于显示You的逻辑有什么问题? 当你reload这个页面的时候,又变成正常的了。You又正常的显示了。
难道这是什么Maybe it was some kind of non-deterministic bug? So you post another message, and again, it's from "James", not "You".
某种不确定的错误码?你又发布了一条消息,依然显示“James”,而不是“You”。而且当你再次reload的时候,又开始显示“You”。
为什么?
Turbo stream从来都不知道这个current_user, 这是个好事!
用于Turbo Steam的Partials不能有全局引用, 他们是通过ApplicationRenderer呈现的。不包含在特定的上下文请求中。
这是什么意思呢?就是说当这个方法调用turbo_stream.append的时候,实际上就是渲染一个partial,他做这个没有引用任何的请求,他不知道 “current user”应该是啥,就好像没有人登录一样。
这实际上是好事,因为让我们你想一秒钟,我们的朋友Patrick也在index page上。当我post我的message。我的请求会触发controller,它将这个消息渲染,然后广播到所有的连接上来的clients。如果渲染的时候将我作为了current_user,那么这个partial里面的name就变成了“You”,不仅仅是我看到You,Patrick也看到的是You,和发送者看到的是一样了。
这是因为同样的rendererd string会通过trubo stream发送给所有的连上来的客户,而不是每个client得到不同的renderer,并且知道他们是谁。
那么现在我想要的得到“You”,我要怎么做?
我已经不得不在很多个应用里面解决这个问题了。当我想要highlight高亮显示一些内容,当这个内容和用户strongly related 密切相关的时候。
I've had to solve this in a couple of applications now, where I wanted to highlight when a message or other piece of data was strongly related to the user viewing it.
为什么?
Turbo stream从来都不知道这个current_user, 这是个好事!
用于Turbo Steam的Partials不能有全局引用, 他们是通过ApplicationRenderer呈现的。不包含在特定的上下文请求中。
这是什么意思呢?就是说当这个方法调用turbo_stream.append的时候,实际上就是渲染一个partial,他做这个没有引用任何的请求,他不知道 “current user”应该是啥,就好像没有人登录一样。
这实际上是好事,因为让我们你想一秒钟,我们的朋友Patrick也在index page上。当我post我的message。我的请求会触发controller,它将这个消息渲染,然后广播到所有的连接上来的clients。如果渲染的时候将我作为了current_user,那么这个partial里面的name就变成了“You”,不仅仅是我看到You,Patrick也看到的是You,和发送者看到的是一样了。
这是因为同样的rendererd string会通过trubo stream发送给所有的连上来的客户,而不是每个client得到不同的renderer,并且知道他们是谁。
那么现在我想要的得到“You”,我要怎么做?
我已经不得不在很多个应用里面解决这个问题了。当我想要highlight高亮显示一些内容,当这个内容和用户strongly related 密切相关的时候。
I've had to solve this in a couple of applications now, where I wanted to highlight when a message or other piece of data was strongly related to the user viewing it.
这里也没有practical 切实可行的方式去解决这服务端,至少在没有用双份的turbostream链接,通过一个后端去处理current user. 然后其它的后端渲染别的客户端。(然后在客户端,构建逻辑让turbo知道忽略来自group stream的message,而是用personal stream来取代它 supercede it.)
相反,我们在客户端去fix这个问题。Here's what I've come up with. Firstly, a Stimulus controller: 这是我的想法。
相反,我们在客户端去fix这个问题。Here's what I've come up with. Firstly, a Stimulus controller: 这是我的想法。
// app/javascript/controllers/personalization_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static targets = ["name"] static values = { userId: Number } nameTargetConnected(target) { if (target.dataset.userId == this.userIdValue) { target.innerHTML = "You" } } }
然后在main application layout里面,我们启用这个controller,告诉contrller这个current_user的id
<!-- app/views/layouts/application.html.erb --> <!DOCTYPE html> <html lang="en"> <!-- head element stuff --> <body data-controller="personalization" data-personalization-user-id-value="<%= current_user.id %>"> <%= yield %> <!-- etc... --> </body> </html>
一个hepler,存放显示username的逻辑。
# app/helpers/application_helper.rb def user_name(user) display_name = (user == current_user) ? 'You' : user.name content_tag(:span, display_name, data: { personalization_target: 'name', user_id: user.id }) end
最后修改我们的partial来使用那个helper:
<!-- app/views/messages/_message.html.erb --> <div id="<%= dom_id message %>"> <span class="from"><%= user_name message.user %></span>: <%= message.content %> </div>
现在显示都正常了。
当我们的partial通过turbo插入,stimulus控制器running across整个页面,通知新name target。然后invokes调用 nameTargetConnected() function在那个element。对比目标上的userId和页面上的属于current_user的id。如果相同就替换目标内容为"You"。
阅读量: 953
发布于:
修改于:
发布于:
修改于: