Rewrite stream rendering to avoid client-side expanding

* adding streams in client breaks things (e.g. autofocus)
* some tasks need to be performed in one stream action to avoid
  flickering (e.g. table row substitution)
This commit is contained in:
cryptogopher 2024-02-11 18:31:06 +01:00
parent 759a0b31b7
commit 1198add901
7 changed files with 107 additions and 38 deletions

View File

@ -103,8 +103,6 @@ module ApplicationHelper
end
def render_turbo_stream(partial, locals)
# TODO: extend with smth like "if outside of rendering, render; otherwise
# appendChild() template within current render"
"Turbo.renderStreamMessage('#{j(render partial: partial, locals: locals)}'); return false;"
end

View File

@ -14,6 +14,7 @@ function beforeStreamRender(event) {
document.addEventListener('turbo:before-stream-render', beforeStreamRender)
*/
/*
Turbo.session.streamMessageRenderer.appendFragment = async function (fragment) {
for (let child of [...fragment.children]) {
document.documentElement.appendChild(child)
@ -32,6 +33,38 @@ Turbo.session.streamMessageRenderer.appendFragment = async function (fragment) {
}
}
window.renderTurboStream = function (message) {
// Render if not already rendering, otherwise just append fragment to DOM
if (document.documentElement.getElementsByTagName("turbo-stream").length == 0) {
Turbo.renderStreamMessage(message)
} else {
Turbo.session.streamMessageRenderer
.appendFragment(Turbo.StreamMessage.wrap(message).fragment)
}
}
*/
Turbo.StreamElement.prototype.enableElement = function(element) {
element.removeAttribute("disabled")
element.removeAttribute("aria-disabled")
// 'tabindex' is not used explicitly, so removing it is safe
element.removeAttribute("tabindex")
}
Turbo.StreamElement.prototype.removePreviousForm = function(form) {
const id = form.id
const row = document.getElementById(id + "_cached")
form.remove()
if (row) {
row.id = id
row.style.display = "revert"
}
if (form.hasAttribute("data-link-id")) {
const link = document.getElementById(form.getAttribute("data-link-id"))
this.enableElement(link)
}
}
Turbo.StreamActions.disable = function() {
this.targetElements.forEach((e) => {
@ -42,12 +75,7 @@ Turbo.StreamActions.disable = function() {
}
Turbo.StreamActions.enable = function() {
this.targetElements.forEach((e) => {
e.removeAttribute("disabled")
e.removeAttribute("aria-disabled")
// 'tabindex' is not used explicitly, so removing it is safe
e.removeAttribute("tabindex")
})
this.targetElements.forEach((e) => { this.enableElement(e) })
}
Turbo.StreamActions.blur = function() {
@ -59,6 +87,43 @@ Turbo.StreamActions.focus = function() {
this.targetElements[0].focus({focusVisible: true})
}
Turbo.StreamActions.prepend_form = function() {
this.targetElements.forEach((e) => {
[...e.getElementsByClassName("form")].forEach((f) => {
this.removePreviousForm(f)
})
e.prepend(this.templateContent)
})
}
Turbo.StreamActions.after_form = function() {
this.targetElements.forEach((e) => {
[...e.parentElement?.getElementsByClassName("form")].forEach((f) => {
this.removePreviousForm(f)
})
e.parentElement?.insertBefore(this.templateContent, e.nextSibling)
})
}
Turbo.StreamActions.replace_form = function() {
this.targetElements.forEach((e) => {
[...e.parentElement?.getElementsByClassName("form")].forEach((f) => {
this.removePreviousForm(f)
})
e.style.display = "none"
e.id = e.id + "_cached"
e.parentElement?.insertBefore(this.templateContent, e.nextSibling)
})
}
Turbo.StreamActions.close_form = function() {
this.targetElements.forEach((e) => {
this.removePreviousForm(e.closest(".form"))
})
}
/*
Turbo.StreamActions.click = function() {
this.targetElements.forEach((e) => { e.click() })
}
*/

View File

@ -1,5 +1,7 @@
<%= fields_for @unit do |form| %>
<tr id="<%= dom_id(@unit) %>" onkeydown="processKey(event)">
<%= tag.tr id: dom_id(@unit), class: "form", onkeydown: "processKey(event)",
data: {link_id: link_id} do %>
<td class="<%= class_names({subunit: @unit.base}) %>">
<%= form.text_field :symbol, form: :unit_form, required: true, autofocus: true, size: 12,
maxlength: @unit.class.columns_hash['symbol'].limit, autocomplete: "off" %>
@ -19,8 +21,8 @@
<td class="actions">
<%= form.submit form: :unit_form %>
<%= image_link_to t(:cancel), "close-circle-outline", units_path, class: 'dangerous',
name: :cancel, onclick: render_turbo_stream('form_close', {id: id}) %>
name: :cancel, onclick: render_turbo_stream('form_close', {link_id: link_id}) %>
</td>
</tr>
<% end %>
<!-- TODO: display error_messages_for unit -->
<% end %>

View File

@ -1,3 +1,2 @@
<%= @unit.persisted? ? turbo_stream.replace(@unit) : turbo_stream.remove(@unit) %>
<%= turbo_stream.enable id %>
<%= turbo_stream.focus id %>
<%= turbo_stream.close_form @unit %>
<%#= turbo_stream.focus link_id %>

View File

@ -1,7 +1,7 @@
<%# TODO: make sure turbo_stream layout is used in new/edit %>
<%= turbo_stream.replace @unit, partial: 'form', locals: {id: dom_id(@unit, :edit)} %>
<%= turbo_stream.update :unit_form_frame do %>
<%= form_with model: @unit, html: {id: :unit_form} do %>
<% end %>
<% end unless @unit.errors.present? %>
<% end %>
<%= turbo_stream.replace_form @unit, partial: 'form', locals: {link_id: dom_id(@unit, :edit)} %>

View File

@ -1,20 +1,9 @@
<% options = {partial: 'form', locals: {id: dom_id(@unit.base || @unit, :add)}} %>
<% link_id = dom_id(@unit.base || @unit, :add) %>
<%= turbo_stream.disable link_id -%>
<% if @unit.errors.present? %>
<%= turbo_stream.replace @unit, **options -%>
<% else %>
<%= turbo_stream.disable options[:locals][:id] -%>
<%= turbo_stream.click_all 'tbody a[name=cancel]' -%>
<%#= turbo_stream.blur_all %>
<% if @unit.base.nil? %>
<%= turbo_stream.prepend :units, **options -%>
<% else %>
<%= turbo_stream.after @unit.base, **options -%>
<% end %>
<%= turbo_stream.update :unit_form_frame do %>
<%= form_with model: @unit, html: {id: :unit_form} do %>
<% end %>
<%= turbo_stream.update :unit_form_frame do %>
<%= form_with model: @unit, html: {id: :unit_form} do %>
<% end %>
<% end %>
<%= turbo_stream.insert_form (@unit.base || :units), partial: 'form', locals: {link_id: link_id} %>

View File

@ -23,11 +23,27 @@ ActiveSupport.on_load :turbo_streams_tag_builder do
action :focus, target, allow_inferred_rendering: false
end
def click(target)
action :click, target, allow_inferred_rendering: false
#def click(target)
# action :click, target, allow_inferred_rendering: false
#end
#def click_all(targets)
# action_all :click, targets, allow_inferred_rendering: false
#end
def insert_form(target, content = nil, **rendering, &block)
if target.is_a? Symbol
action :prepend_form, target, content, **rendering, &block
else
action :after_form, target, content, **rendering, &block
end
end
def click_all(targets)
action_all :click, targets, allow_inferred_rendering: false
def replace_form(target, content = nil, **rendering, &block)
action :replace_form, target, content, **rendering, &block
end
def close_form(target)
action :close_form, target, allow_inferred_rendering: false
end
end