-
Notifications
You must be signed in to change notification settings - Fork 429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow a single stream response to update multiple elements #113
Conversation
I am pretty sure that |
So I looked into it and while |
Dom IDs are supposed to be unique. I don't think we want to encourage straying from that. I'd have each button have their own ID, then just replace each with their own replace action. |
Hey David, I do agree that the semantics around dom IDs are problematic here. I only bring this up since I do see UIs where there are identical/similar elements on the page that need updating in response to user action. Having to make the stream response aware of those elements add a bit of friction since all the other code to render the elements is identical. If it's an issue to have multiple of the same IDs on the page for this case, then I'd like to propose a few alternative solutions.
Both of these proposals would mean an API break, but both would allow a single stream response to update multiple elements without breaking the semantics of ID. |
I think we could introduce a CSS selector style without API breaking by simply reserving the unadorned target to mean #target. Then if you do target=".message", we know that it's a class target. I like that idea and would support an extension for it 👍 |
Hey, maybe this one could be off-topic, but it is a similar situation I'm trying to solve. I'm testing hotwire rails and I want to update by single action a couple of DOM elements in the page. Those elements could be placed far away from each other around the page and outside of the turbo frame. The way I did - I just placed two actions on update_move.turbo_stream.erb and it worked perfectly. Is this how it should work, right ? Thank you.
|
8bb56d7
to
caf3651
Compare
OK! I've added the ability to use CSS selectors as a stream target. The implementation tries to not know anything about CSS selectors by just trying to find the target as if it were an ID, then only using the target as a CSS selector if no elements with that ID are found. This has a nice side effect of not breaking any ID targets that might already look like a CSS selector. The implementation also continues not to allow multiple IDs unless specified as a CSS selector. I've added a test as well. Please let me know if more coverage is desirable. |
src/elements/stream_element.ts
Outdated
if (cssTarget.length !== 0) { | ||
return Array.prototype.slice.call(cssTarget) | ||
} | ||
this.raise(`target "${this.target}" not found`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added this exception. I'm assuming not finding the target is a programming error, but maybe that's too strict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too strict, yeah. And a breaking change. You may well stream an update to something that's no longer there.
I'm happy to fix this up if it's still something we want. |
Still like this. Let's shape it up. |
And drop the idea that its an error if elements are missing.
So there's never any uncertainty
Thanks for all the work on this @blopker. I ended up changing the API to avoid any potential conflicts. The existing |
I see, that's a good compromise. Glad this worked out. |
hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` I'm not 100% happy with the "API" to invoke it, but it's the best I could come up with. Feedback appreciated. The gist of it is the turbo stream tag builder helpers now accept a `target_multiple` keyword arg (default `false`). When set to `true` the end result of the tag will have a `targets` attribute instead of a `target` attribute. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` I'm not 100% happy with the "API" to invoke it, but it's the best I could come up with. Feedback appreciated. The gist of it is the turbo stream tag builder helpers now accept a `target_multiple` keyword arg (default `false`). When set to `true` the end result of the tag will have a `targets` attribute instead of a `target` attribute. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` I'm not 100% happy with the "API" to invoke it, but it's the best I could come up with. Feedback appreciated. The gist of it is the turbo stream tag builder helpers now accept a `target_multiple` keyword arg (default `false`). When set to `true` the end result of the tag will have a `targets` attribute instead of a `target` attribute. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
Background: hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` This adds the functionality to the tag builder. All "action methods" now have an `_all` varient which is piped into a new `action_all` method. The only real difference in these methods is the end result is setting `targets` instead of `target` in `turbo_stream_action_tag` which has also been updated to handle this feature. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
Background: hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` This adds the functionality to the tag builder. All "action methods" now have an `_all` varient which is piped into a new `action_all` method. The only real difference in these methods is the end result is setting `targets` instead of `target` in `turbo_stream_action_tag` which has also been updated to handle this feature. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
Background: hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` This adds the functionality to the tag builder. All "action methods" now have an `_all` varient which is piped into a new `action_all` method. The only real difference in these methods is the end result is setting `targets` instead of `target` in `turbo_stream_action_tag` which has also been updated to handle this feature. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
Background: - hotwired/turbo#113 allowed for multiple targets in turbo frame streams. - hotwired#194 implemented it for `Turbo::Streams::TagBuilder` This attempts to expose the feature on `Turbo::StreamsChannel` methods.
Background: - hotwired/turbo#113 allowed for multiple targets in turbo frame streams. - hotwired#194 implemented it for `Turbo::Streams::TagBuilder` This attempts to expose the feature on `Turbo::StreamsChannel` methods.
* Turbo::StreamsChannel can set targets attribute Background: - hotwired/turbo#113 allowed for multiple targets in turbo frame streams. - #194 implemented it for `Turbo::Streams::TagBuilder` This attempts to expose the feature on `Turbo::StreamsChannel` methods. * Add assertions for `targets` argument
Background: hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` This adds the functionality to the tag builder. All "action methods" now have an `_all` varient which is piped into a new `action_all` method. The only real difference in these methods is the end result is setting `targets` instead of `target` in `turbo_stream_action_tag` which has also been updated to handle this feature. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
* Turbo::StreamsChannel can set targets attribute Background: - hotwired/turbo#113 allowed for multiple targets in turbo frame streams. - hotwired/turbo-rails#194 implemented it for `Turbo::Streams::TagBuilder` This attempts to expose the feature on `Turbo::StreamsChannel` methods. * Add assertions for `targets` argument
Background: hotwired/turbo#113 allowed for multiple targets in turbo frame streams. This attempts to expose the feature to the Rails turbo_stream tag helper. Instead of a `target` which results in a call to `document.getElementById` on the JS level, a `targets` attribute will be passed into `document.querySelectorAll` This adds the functionality to the tag builder. All "action methods" now have an `_all` varient which is piped into a new `action_all` method. The only real difference in these methods is the end result is setting `targets` instead of `target` in `turbo_stream_action_tag` which has also been updated to handle this feature. I purposfully did not touch the ActionCable-related part of the gem as I think its usecase is mostly geared to always updating one element at a time. Regardless, if it were to get this feature, I think it would be better in a separate pull request. For now, I think giving the developer the abiltiy to target multiple elements at once in the turbo stream response from a controller is ok for now.
* Turbo::StreamsChannel can set targets attribute Background: - hotwired/turbo#113 allowed for multiple targets in turbo frame streams. - hotwired/turbo-rails#194 implemented it for `Turbo::Streams::TagBuilder` This attempts to expose the feature on `Turbo::StreamsChannel` methods. * Add assertions for `targets` argument
Hey all, I'm working with the folks integrating Turbo into Django at https://github.com/hotwire-django. Right now we're making a demo site based off of the RealWorld app spec (https://github.com/gothinkster/realworld) to show how the integration works in practice. Over all it's been a smooth experience.
However, there's one feature we're having a little trouble with. Specifically the 'Favorite button' feature. You can see it in action here. Notice the two
Favorite Article
buttons. They both contain the same content and both need to be updated in response to the user's action.Ideally we would respond to the button press with a single Turbo Stream
replace
response to update both buttons at the same time with the new favorite count and button active state.In this proposal PR I've made the buttons work by using
querySelectorAll
instead ofgetElementById
to select all elements with thetarget
ID on the page. Then I've updated each Stream action method to iterate over a list.What do you think?