Skip to content

Commit 78b5bef

Browse files
author
Sjors Baltus
committed
Wrap futurism with CableReady#updates_for and instruct updates-for element to not morph but instead emit an event
1 parent 03ad0c6 commit 78b5bef

8 files changed

Lines changed: 184 additions & 26 deletions

File tree

javascript/elements/futurism_li.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import {
44
extendElementWithIntersectionObserver,
5-
extendElementWithEagerLoading,
6-
extendElementWithCableReadyUpdatesFor
5+
extendElementWithEagerLoading
76
} from './futurism_utils'
87

98
export default class FuturismLI extends HTMLLIElement {
109
connectedCallback () {
1110
extendElementWithIntersectionObserver(this)
1211
extendElementWithEagerLoading(this)
13-
extendElementWithCableReadyUpdatesFor(this)
1412
}
1513
}

javascript/elements/futurism_table_row.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import {
44
extendElementWithIntersectionObserver,
5-
extendElementWithEagerLoading,
6-
extendElementWithCableReadyUpdatesFor
5+
extendElementWithEagerLoading
76
} from './futurism_utils'
87

98
export default class FuturismTableRow extends HTMLTableRowElement {
109
connectedCallback () {
1110
extendElementWithIntersectionObserver(this)
1211
extendElementWithEagerLoading(this)
13-
extendElementWithCableReadyUpdatesFor(this)
1412
}
1513
}

javascript/elements/futurism_utils.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,20 @@ export const extendElementWithEagerLoading = element => {
6464
}
6565

6666
export const extendElementWithCableReadyUpdatesFor = (element) => {
67-
element.addEventListener('cable-ready:after-update', () => {
68-
dispatchAppearEvent(element);
69-
});
67+
if (element.dataset.updatesFor) {
68+
if (element.hasAttribute('keep')) {
69+
if (element.observer) element.observer.disconnect()
70+
}
71+
72+
element.addEventListener('cable-ready:after-update', (event) => {
73+
const evt = new CustomEvent('futurism:appear', {
74+
bubbles: true,
75+
detail: {
76+
target: element,
77+
observer: null
78+
}
79+
})
80+
document.dispatchEvent(evt)
81+
});
82+
}
7083
}

javascript/futurism_channel.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const debounceEvents = (callback, delay = 20) => {
1616
}
1717
}
1818

19+
const targetResolver = (event) => {
20+
return event.detail.target || event.target
21+
}
22+
1923
export const createSubscription = consumer => {
2024
consumer.subscriptions.create('Futurism::Channel', {
2125
connected () {
@@ -24,13 +28,13 @@ export const createSubscription = consumer => {
2428
'futurism:appear',
2529
debounceEvents(events => {
2630
this.send({
27-
signed_params: events.map(e => e.target.dataset.signedParams),
28-
sgids: events.map(e => e.target.dataset.sgid),
31+
signed_params: events.map(e => targetResolver(e).dataset.signedParams),
32+
sgids: events.map(e => targetResolver(e).dataset.sgid),
2933
signed_controllers: events.map(
30-
e => e.target.dataset.signedController
34+
e => targetResolver(e).dataset.signedController
3135
),
3236
urls: events.map(_ => window.location.href),
33-
broadcast_each: events.map(e => e.target.dataset.broadcastEach)
37+
broadcast_each: events.map(e => targetResolver(e).dataset.broadcastEach)
3438
})
3539
})
3640
)

lib/futurism/helpers.rb

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,18 @@ def initialize(extends:, placeholder:, options:)
6868
@eager = options.delete(:eager)
6969
@broadcast_each = options.delete(:broadcast_each)
7070
@controller = options.delete(:controller)
71+
@updates_for_object = options.delete(:updates_for)
7172
@html_options = options.delete(:html_options) || {}
7273
@data_attributes = html_options.fetch(:data, {}).except(:sgid, :signed_params)
7374
@model = options.delete(:model)
75+
@wrapped_for_updates_for = options.delete(:wrapped_for_updates_for)
76+
if @wrapped_for_updates_for
77+
@html_options[:keep] = 'keep'
78+
@data_attributes['updates-for'] = true
79+
end
7480
@options = data_attributes.any? ? options.merge(data: data_attributes) : options
81+
82+
warn "[Futurism] `updates_for` feature is not available for extends: :li or :tr elements." if [:tr, :li].include?(extends)
7583
end
7684

7785
def dataset
@@ -85,6 +93,53 @@ def dataset
8593
end
8694

8795
def render
96+
return render_updates_for if use_updates_for?
97+
98+
render_tag
99+
end
100+
101+
def transformed_options
102+
dump_options(options)
103+
end
104+
105+
private
106+
107+
############
108+
# TODO: Include CableReadyHelper
109+
include CableReady::Compoundable
110+
include CableReady::StreamIdentifier
111+
include ActionView::Context
112+
113+
def updates_for(*keys, url: nil, debounce: nil, only: nil, html_options: {}, &block)
114+
options = build_options(*keys, html_options)
115+
options[:url] = url if url
116+
options[:debounce] = debounce if debounce
117+
options[:only] = only if only
118+
tag.updates_for(**options) { capture(&block) }
119+
end
120+
121+
private
122+
123+
def build_options(*keys, html_options)
124+
keys.select!(&:itself)
125+
{identifier: signed_stream_identifier(compound(keys))}.merge(html_options)
126+
end
127+
############
128+
129+
def render_updates_for
130+
arguments = Array.wrap(@updates_for_object)
131+
kwargs = arguments.last.is_a?(Hash) ? arguments.pop : {}
132+
kwargs[:html_options] ||= {}
133+
kwargs[:html_options][:data] ||= {}
134+
kwargs[:html_options][:data]['ignore-morph'] = true
135+
kwargs[:html_options][:data]['after-update-event-selector'] = 'futurism-element'
136+
137+
updates_for(*arguments, **kwargs) do
138+
render_tag
139+
end
140+
end
141+
142+
def render_tag
88143
case extends
89144
when :li
90145
content_tag :li, placeholder, html_options.deep_merge({data: dataset, is: "futurism-li"})
@@ -95,14 +150,24 @@ def render
95150
end
96151
end
97152

98-
def transformed_options
99-
dump_options(options)
153+
def use_updates_for?
154+
@updates_for_object.present? && ![:tr, :li].include?(extends)
100155
end
101156

102-
private
103-
104157
def signed_params
105-
message_verifier.generate(transformed_options)
158+
message_verifier.generate(transformed_options.merge(updates_for_params))
159+
end
160+
161+
def updates_for_params
162+
return {} unless use_updates_for? || @wrapped_for_updates_for
163+
164+
{
165+
wrap_for_updates_for: {
166+
extends: extends,
167+
html_options: html_options,
168+
data_attributes: data_attributes,
169+
}
170+
}
106171
end
107172

108173
def signed_controller

lib/futurism/resolver/resources.rb

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,48 @@ def initialize(resource_definitions:, connection:, params:)
1515

1616
def resolve
1717
resolved_models.zip(@resources_with_sgids).each do |model, resource_definition|
18-
html = renderer_for(resource_definition: resource_definition).render(model)
18+
options = options_from_resource(resource_definition)
19+
html = render_html(model, resource_definition: resource_definition, render_exceptions: false)
20+
html = wrapped_html(html, options)
1921

2022
yield(resource_definition.selector, html, resource_definition.broadcast_each)
2123
end
2224

2325
@resources_without_sgids.each do |resource_definition|
2426
options = options_from_resource(resource_definition)
25-
renderer = renderer_for(resource_definition: resource_definition)
26-
html =
27-
begin
28-
renderer.render(options)
29-
rescue => exception
30-
error_renderer.render(exception)
31-
end
27+
html = render_html(options, resource_definition: resource_definition)
28+
html = wrapped_html(html, options)
3229

3330
yield(resource_definition.selector, html, resource_definition.broadcast_each)
3431
end
3532
end
3633

3734
private
3835

36+
def wrapped_html(html, options)
37+
wrap_for_updates_for = options.delete(:wrap_for_updates_for)
38+
return html unless wrap_for_updates_for
39+
40+
# Only wrap the element again if we were told to for the updates_for feature
41+
options = options.dup
42+
options.merge!(wrap_for_updates_for)
43+
options[:wrapped_for_updates_for] = true
44+
45+
extends = options.delete(:extends)
46+
47+
Futurism::Helpers::WrappingFuturismElement.new(extends: extends, placeholder: html, options: options).render
48+
end
49+
50+
def render_html(model, render_exceptions: true, **kwargs)
51+
return renderer_for(**kwargs).render(model) unless render_exceptions
52+
53+
begin
54+
renderer_for(**kwargs).render(model)
55+
rescue => exception
56+
error_renderer.render(exception)
57+
end
58+
end
59+
3960
def error_renderer
4061
ErrorRenderer.new
4162
end

test/cable/channel_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,41 @@ class Futurism::ChannelTest < ActionCable::Channel::TestCase
103103
end
104104
end
105105

106+
test "broadcasts a rendered partial wrapped by a futurism element after receiving signed params" do
107+
with_mocked_renderer do |mock_renderer|
108+
post = Post.create title: "Lorem"
109+
fragment = Nokogiri::HTML.fragment(futurize(partial: "posts/card", locals: {post: post}, extends: :div, wrap_for_updates_for: { extends: :div }, wrapped_for_updates_for: true) {})
110+
signed_params = fragment.children.first["data-signed-params"]
111+
subscribe
112+
113+
mock_renderer
114+
.expect(
115+
:render,
116+
"<tag></tag>",
117+
[
118+
partial: "posts/card",
119+
locals: {post: post},
120+
:wrap_for_updates_for => {
121+
:extends => :div,
122+
:html_options => {
123+
:keep => "keep"
124+
},
125+
:data_attributes => {
126+
"updates-for" => true
127+
}
128+
},
129+
:data => {
130+
"updates-for" => true
131+
}
132+
]
133+
)
134+
135+
perform :receive, {"signed_params" => [signed_params]}
136+
137+
assert_mock mock_renderer
138+
end
139+
end
140+
106141
test "broadcasts a rendered partial after receiving the shorthand syntax" do
107142
with_mocked_renderer do |mock_renderer|
108143
post = Post.create title: "Lorem"

test/helper/helper_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,30 @@ def self.find(id)
174174
assert_includes element.children.first.children.first.text, "Lorem"
175175
end
176176

177+
test "allows to automatically wrap the futurism html element with a CableReady updates_for element" do
178+
post = Post.create title: "Lorem"
179+
180+
element = Nokogiri::HTML.fragment(futurize(post, updates_for: post, extends: :div) {})
181+
assert_equal "updates-for", element.children.first.name
182+
assert_equal "futurism-element", element.children.first.children.first.name
183+
end
184+
185+
test "does not wrap the futurism html element with a CableReady updates_for element when using extends: :tr" do
186+
post = Post.create title: "Lorem"
187+
188+
element = Nokogiri::HTML.fragment(futurize(post, updates_for: post, extends: :tr) {})
189+
refute_equal "updates-for", element.children.first.name
190+
assert_equal "tr", element.children.first.name
191+
end
192+
193+
test "does not wrap the futurism html element with a CableReady updates_for element when using extends: :li" do
194+
post = Post.create title: "Lorem"
195+
196+
element = Nokogiri::HTML.fragment(futurize(post, updates_for: post, extends: :li) {})
197+
refute_equal "updates-for", element.children.first.name
198+
assert_equal "li", element.children.first.name
199+
end
200+
177201
def verifier
178202
Futurism::MessageVerifier.message_verifier
179203
end

0 commit comments

Comments
 (0)