-
-
Notifications
You must be signed in to change notification settings - Fork 175
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
Morph Modes: page, selector and nothing #211
Conversation
Add support for room param to facilitate per-session broadcasts
@leastbad Nice! Great work on this PR! The only concern I have is, that we "clutter" the framework by introducing a lot of new and sometimes specific data attributes (not particular to the features you just proposed, but in general). Is it just me to be overcautious? |
I think @marcoroth is right that we have to watch very carefully how the API evolves. @leastbad‘s addition looks pretty straightforward in this respect. The only thing that worries me a bit - and please take this with a grain of salt - I truly think we should start integration testing |
I have a set of integration tests stood up against the expo site but it's not complete and we need to make such testing more accessible to all contributors. |
@marcoroth I agree with you 100% about being extremely careful about adding data attributes. ... says the guy intending to add mode - the first one since he added/championed permanent and root 🤠 |
Just throwing it on the table: there is a reasonable and possibly desirable alternative to introducing a new data attribute, and that is to revisit the syntax and conventions of We borrow the Stimulus -> for refresh. What if >> indicated a launch? What if ~> indicated an update? While we’re brainstorming, @hopsoft is there a dimension close to ours where you’d consider making the string fragment Reflex optional, as it is completely redundant? All declared calls point to a reflex class, and all reflex classes end in Reflex. We could lop off six characters from every declaration. |
On my first read-through I didn't get that you put the I just had a quick thought. I know, those are technically 3 new data-attributes but would eliminate the need for <!-- data-reflex defaults to "refresh" -->
<button type="button" data-reflex="click->EvolveReflex#refresh">Refresh</button>
<button type="button" data-reflex-refresh="click->EvolveReflex#refresh">Refresh</button>
<button type="button" data-reflex-launch="click->EvolveReflex#launch">Launch</button>
<button type="button" data-reflex-update="click->EvolveReflex#update" data-reflex-root="#poop">Update</button> This one has the disadvantage that you have 4 attributes which technically almost do the same thing. I don't know 🤷♂️ I personally don't know which style I'd prefer, but I just wanted to share it with you anyway 😉 |
I also wanted to say, for posterity, that I’m not emotionally attached to refresh, update or launch as terms. I entertained many options, and these happened to float to the top. It’s entirely likely that there’s a better word than launch, for example. |
Sorry @marcoroth. We’re big kids here, you know I love you. Hard pass on introducing three new attributes. |
Personally I think I prefer the |
@leastbad I'm fine with this since I’m also not a big fan of introducing 3 new attributes (after I wrote that we should be careful about adding new ones 🙈) I just wanted to share the idea anyway 😄 |
@seb1441 good points! Arguments for modifying data-reflex
Arguments for adding data-reflex-mode
|
I really like the ideas here and have been considering the public API for a while now. While I like how terse modifying data-reflex attribute values would be, it poses a higher risk of being confusing and prone to user error/misunderstanding (especially for developers new to the library). It also breaks with our convention of repurposing Stimulus semantics, so my vote is for data-reflex-mode. Having said that, I'd like to propose a new name for data-reflex-mode because I think the current semantics on the PR are a bit too presumptive about the types of things people might use these features for. Here's my proposed re-naming scheme.
I realize that using I'd love to hear what others think. |
Very thoughtful, 100% agree |
I like the idea of renaming it to |
@marcoroth Makes a great point and his concerns make me wonder if we should consider deprecating Thoughts? |
I‘d also opt for deprecating it for the moment |
@hopsoft In my opinion we shouldn‘t deprecate If I would need to provide the return value for every root in the Reflex it would be much more work then just letting StimulusReflex rerender the page through the controller and just let it morph the given DOM element(s). |
After I wrote that I realized that we could also name it the way where the "response" is rendered. Either in the controller or in the reflex. This would lead to:
But apart from the naming, we would still need the |
Of course I went to sleep at the exact moment everyone started saying interesting things... Okay, thanks for thinking about this so much! I was reading the comments and either in complete agreement or totally willing to go along with the group when I got to the part about potentially deprecating If you get rid of If we're worried about a confusing combination of names, I have two alternative suggestions. This first, respectfully, is that this concern is almost certainly overblown as we have documentation, examples and group of users who have clearly expressed a desire for this feature to exist... and they can be taught a new thing. The keywords aren't particularly confusing IMHO. The second suggestion is that we could introduce The only other idea I have at the moment is that in terms of keywords, I like "component" roughly as much as "partial" because it speaks to what form the result will take. I feel like partial is a means to an end, while many folks think of atomic chunks as components. This isn't a deal-breaker either way - I'd be perfectly happy with partial, too. |
I agree that removing the functionality is not what's desired; rather, more clarity in the API. I think what's not quite feeling right is supporting both:
It feels like option 2 should be the preferred mechanic for partial page updates, so I'm questioning the value of keeping option 1 once this PR ships. Note that it's entirely possible that there are valid use cases, but I'd also prefer to minimize competing features in such a small library. |
I'll also go on record in defense of keeping partial for |
To address @marcoroth's proposal of using controller, reflex, none instead of page, partial, none. I still prefer using the format of what vs where for |
Thank you for explaining this so clearly. I completely agree; the existance of partial+target makes page+root feel awkward and superfluous. I feel good about have a page update be a page update. Simplifies everything. People can use Question, is there still room for accepting a list of selectors, or should we go 1:1 for an update? It seems like if they want to do more than 1:1, they should start planning to call CableReady directly, no? |
I'm going to update the PR with what I think are the latest decisions. Should I be changing data-reflex-root to data-reflex-target? How do we want to roll this out? v3? |
Love this PR and conversation. I think letting it simmer for a month has proven incredibly helpful for the thinking and decision making. Key decisions that I very much agree with.
APII think that repurposing UsageHere's how I see the API usage shaping up. class ExampleReflex < ApplicationReflex
def page_morph
# page will automatically re-render, this is the default
end
def targeted_morph
morph "#selector", ApplicationController.render(partial: "/path/to/partial")
end
def multiple_targeted_morphs
morph "#selector", ApplicationController.render(partial: "/path/to/partial")
morph "#selector", ApplicationController.render(partial: "/path/to/partial")
morph "#selector", ApplicationController.render(partial: "/path/to/partial")
end
def no_morph
morph :nothing
end
end If I'm not mistaken, this will not introduce any breaking changes which means we can get away with a minor version bump. |
Also, this PR resolves #186 |
@julianrubisch I'd love to sync and collaborate on your ideas around #183 once things settle down a bit for me. |
Also happy to sync on the equivalent javascript adapter once things have
settled down a bit :)
…Sent from my mobile
On Sat, 27 Jun 2020, 16:24 Nate Hopkins, ***@***.***> wrote:
@julianrubisch <https://github.com/julianrubisch> I'd love to sync and
collaborate on your ideas around #183
<#183> once things settle
down a bit for me.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#211 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQGYEXE6MX6YHTFKS6UJZ3RYX6ILANCNFSM4NIK6D2Q>
.
|
I love where this landed; no need to specify a morph type unless it’s :nothing. I’m going to get to work on making this exist. Once it’s done, let’s each of us try to kick the tires really hard for a week. I’m going to try and script a video to go out for the release. |
lib/stimulus_reflex/channel.rb
Outdated
@@ -41,7 +42,14 @@ def receive(data) | |||
broadcast_message subject: "halted", data: data | |||
else | |||
begin | |||
render_page_and_broadcast_morph reflex, selectors, data | |||
case reflex.morph_mode |
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.
Okay I‘m being a know it all here, but I really have a strong feeling „morph_mode“ should be its own class, or rather superclass. That way you could resort to polymorphism here (and possibly at other locations too) and do „tell don‘t ask“
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.
Respect. So, here's what's happening: I'm merging from master, then pushing, then updating the readme, then going to sleep. I'd be thrilled to wake up to a PR.
Truth is, that sounds great but I have no idea how to do what you're suggesting. If you want it, I'd love to learn.
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'm liking where this last refactor is headed. Had some questions.
Starts with a bang, ends with a whimper. 💯 |
Thank you to @hopsoft @julianrubisch @marcoroth @seb1441 @joshleblanc @jonathan-s @ideasasylum @RolandStuder @mtomov and @davidalejandroaguilar for your opinions, reactions, advice and patience. Go team! |
Description
This PR adds the concept of Morph Modes to StimulusReflex; Morph Selector, Morph Nothing and the current behavior, Morph Page.
Thanks to the thoughtful design of the existing library on both the client and server, it was possible to bolt on two entirely new modes of operation without any dramatic changes to existing functionality. Both benefit from the same logging, callbacks, events and promises as the classic refresh mode.
I suspect that a strict interpretation of SemVer will force a minor version bump.Selector Morphs are similar to classic Page Morph operations, except that they do not call ActionDispatch - the controller is not run and the view template is not re-rendered. Instead, the developer can call the
morph
method one or more times to push an arbitrary string to an arbitrary DOM element. Selector morphs are syncronous - allowing you to morph a selector to display a spinner, call a slow API, and then remove the spinner... all in the same morph. Selector morphs also accept a hash syntax, allowing multiple morphs to be executed asyncronously in the same CableReady broadcast.Yes, not hitting the routing engine OR ActionDispatch means that you're going to be seeing a lot of 0-1ms updates. Yes, this means that you can return a rendered partial collection, a ViewComponent or a string.
Morph Nothing is for calling Reflex actions that initiate long-running processes like ActiveJobs or brewing your morning coffee. They are the perfect place to make CableReady broadcasts that surgically update tiny bits of your DOM, like message notifications. When a Morph Nothing Reflex completes, no morphing occurs but all of the usual suspects (callbacks etc) fire.
I have added an optional 2nd parameter to
stimulate
. It has to be an object that contains at least one of the following keys: attrs, reflexId, selectors. If this options parameter is passed, it isshift()-ed
out of the arguments splat.Yes, you can now supply your own reflexId, and even override the attrs if you really know what you're doing. selectors is supposed to be an array of strings; if you pass
#poop
it will quietly coerce it into['#poop']
for you.In the next version of the library,data-reflex-morph-target
will replacedata-reflex-root
(now deprecated).Page reflexes that currently specify a data-reflex-root will get notified of both the switch to data-reflex-morph-target, and that morph_target will be ignored for Page reflexes starting with the next version.Before I forget: I made the left/right arrows on @marcoroth's logging mechanism more defined. I'm getting old and my eyes were squinty trying to figure out whether I was looking at requests or responses.
Okay, demonstration time!
Fixes #39
Why should this be added
Many newcomers request the ability to update sections of the page and/or updating from specified URLs that might differ from the current page. While we understand why they are asking for these tools, the design of SR was intentional and it doesn't actually make sense to update the current page with the rendered content of a different page. There are simpler, clearer ways that won't confuse TF out of users. Morph Selector Reflexes are the perfect solution to this legitimate request.
Also, as the Ruby community lights up with the possibilities of CableReady, a Morph Nothing reflex is the rug that really ties the room together - it will make it easy for people to kick off long-running processes and call CableReady broadcasts. It is the perfect corollary to CableReady, in that it is a command sent from the client to the server.
Checklist