-
-
Notifications
You must be signed in to change notification settings - Fork 176
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
Returns #458
Returns #458
Conversation
I love it! One concern though: Isn't the name |
I don't see a problem with naming TBH, because still nothing is morphed |
Okay if you look at it from the morph perspective. But shouldn't it then be possible to add a Maybe it would anyway make sense that wrap it in a named param: def lookup(search)
morph :nothing, returns: [{text: "chris", value: 1}, {text: "chip", value: 2}]
end In the def card
morph dom_id(user), render(partial: "users/card", user: user), returns: user.id
end |
I'm long-since on the record for disliking the way we use "nothing". I understand why we do this and I was not able to come up with a better alternative. I wouldn't generally want to change it, but if we're moving to v4 and a great alternative name comes up, I'd likely support the transition, even if we'd likely have to have a deprecation period for the "nothing" term. I spent time thinking about the other broadcasters and whether they should
The reason is that nothing morphs are similar to errors, in that we piggyback on the mechanism we use to handle/report on errors and halted/aborted Reflexes. We do this because page and selector morphs have their own CableReady operation and broadcast loops. There is no server message event for a morph; we pull the Reflex envelope data out of the morph operations themselves. In order to make this work for all three morph types, we'd have to look at adding a server-message to the other morph types. It's not necessarily a bad idea. I just don't have a lot of passion for it right now, especially when I'm waiting on so many other things to land. #444 in particular is a major blocker. |
I like this, but want to propose a slightly different API. Something like this would satisfy @marcoroth's request for supporting return values on all morph modes, but without changing the args passed to the broadcaster. // stimulus controller
ajax: (search, callback) => {
this.stimulate('Example#lookup', search).then(payload => callback(payload.reflexReturnValue))
} # reflex
def lookup(search)
self.return_value = [{text: "chris", value: 1}, {text: "chip", value: 2}]
morph :nothing
end It's a bit more explicit; albeit, a little less elegant, but it also supports more use cases. I think this is justified given that reflex return values are something of an edge case, but one I'd like to see supported across all morph modes. Thoughts? |
I actually love your suggestions, Nate. My primary hestitation for doing just Nothing was not wanting to refactor Page/Selector morphs to handle an extra event operation that would need to be dispatched, just on the off-chance that someone would want to use a return value. Now that I've had some time/distance, I think we could expand what we do here and here to make it work for all three. I'm a little fuzzy on how to implement your suggested Thanks for the review! |
I was thinking of something along these lines. class StimulusReflex::Reflex
attr_accessor :return_value
...
end class Broadcaster
...
def broadcast_message(subject:, body: nil, data: {}, error: nil)
...
cable_ready.dispatch_event(
name: "stimulus-reflex:server-message",
detail: {
reflexId: data["reflexId"],
reflexReturnValue: reflex.return_value,
...
end
end We'd then use a similar technique on the page and selector broadcasters. class PageBroadcaster < Broadcaster
def broadcast(selectors, data)
...
cable_ready.morph(
selector: selector,
html: html,
reflexReturnValue: reflex.return_value,
...
end
end |
I love the idea of having return values everywhere. My current use case: I tack a UUID on temporary this.stimulate(...).then(({ uuid }) => this.element.querySelector(`[data-uuid=${uuid}]`).remove() }) |
Alright @julianrubisch @marcoroth, I think this should make you happy! @hopsoft I noticed that the selector broadcaster tests were all commented out, but I updated them anyhow. This implementation is so much simpler. I updated the description with the current syntax and details (eg. no longer just Nothing Morphs). |
This looks great. @marcoroth did have some good feedback in Discord on this PR. Namely that perhaps What you're doing in this PR is somewhat similar to output parameters in stored procedures. What if we changed it to use an object that feels similar to Rails flash but named it output[:value] = 123
output[:another_key] = "why not, it's cool" The entire The actual implementation wouldn't need to change much to support this. Thoughts? |
Okay, since we're having this discussion, I think
|
Yes I think too, that's why I went with def lookup(search)
payload[:value] = [{text: "chris", value: 1}, {text: "chip", value: 2}]
payload[:and_another_one] = "because why not"
morph :nothing
end |
I'm not stuck on |
I've noticed that you do need to preface the accessor (whatever it's called) with I actually don't love Does making the accessor a hash automatically make the solution feel more Railsy? Surely this is a little reductive... I am not planning to waste mana blocking such a niche thing, but it really does seem like if someone wants to send multiple values, they could just send an array or a hash instead of forcing everyone to assign a key even for simple values. |
Okay, I have updated the code to return a Hash called |
Type of PR (feature, enhancement, bug fix, etc.)
Feature
Description
This PR proposes to introduce RPC-style return values to all Reflex types.
The Reflex class now has an accessor called
payload
that is initialized to an empty Hash. This Hash is added to thedetail
object of themorph
/inner_html
operation, or the event dispatched bybroadcast_message
. It works a bit like theflash
orsession
objects, in that it accepts a key:payload[:sign] = 666
If you want to return a single value, you can do so using this syntax:
self.payload = 666
The
payload
object is passed as a parameter to the client-side promise resolver, making it possible for someone to capture one or several return values from their Reflex in addition to whatever morphing or other functionality.Why should this be added
I'm currently working on adding Reflex support to my SlimSelect Stimulus controller. It requires that you provide a callback to support "Ajax" lookups:
Then in my contrived Reflex, I broadcast an event that contains the required data, which is picked up by the client.
It's fine... it works. There's nothing wrong with the above... but it does bother me that it requires an extra step on the server and client. Cognitively, the program flow is split between the initial call and the event handler. I tell the Ajax handler that there are no results, and then update the options a few moments later, when the event arrives.
Instead, allowing a Reflex to return a result makes promise resolution much more powerful on the client, and without having to send an extra event:
No extra event dispatch makes for compact Reflex action methods:
I think it's pretty cool.
Note that if you wanted to redo this using a single-value payload, it would look like:
No extra event dispatch makes for compact Reflex action methods:
Checklist