[HN Gopher] Live previews with Rails and Stimulus 2
       ___________________________________________________________________
        
       Live previews with Rails and Stimulus 2
        
       Author : nilsandrey
       Score  : 112 points
       Date   : 2021-03-23 13:02 UTC (9 hours ago)
        
 (HTM) web link (nts.strzibny.name)
 (TXT) w3m dump (nts.strzibny.name)
        
       | strzibny wrote:
       | Based on the feedback, I updated the post with one more approach
       | based on the idea from
       | https://news.ycombinator.com/item?id=26554476#26556540. The
       | second approach avoids Rails UJS in favour of Turbo Frame.
       | 
       | Of course there are even more ways how to do this kind of thing.
       | Keep sharing!
        
       | jack_riminton wrote:
       | Would be great if you could add a small video/gif of this
        
         | strzibny wrote:
         | Yes, I will add it!
        
       | kaleidawave wrote:
       | The post is well written but I don't see the point in this when
       | you can achieve the same in 145 bytes of JS (vs 80kb of stimulus)
       | and avoid a trip to the server:
       | document.querySelector("input").addEventListener("input", e => {
       | document.querySelector("p").innerHTML =
       | `<strong>${e.target.value}</strong>` });
       | 
       | Reusing the logic could be done if the server was node or through
       | compiling the formatter to wasm. I struggle to see how Hotwire
       | makes things simpler and has comparable speed to a SPA?
        
         | strzibny wrote:
         | You are right, but the point is that you can do a lot of
         | transformations in Ruby. The demo doesn't do it justice, you
         | can be converting the content to Markdown for example.
         | 
         | As for the Stimulus part, the idea is to use Stimulus for
         | everything interactive, so those extra 80kb is for the whole
         | application.
        
         | wavesplash wrote:
         | The author just used Stimulus, not Hotwire - if you add Turbo
         | (the main part of Hotwire) you can remove all the ajax calls.
         | See thread below: https://news.ycombinator.com/item?id=26555842
         | )
        
           | strzibny wrote:
           | Yes, it doesn't use Turbo, but Stimulus is part of Hotwire as
           | well. And actually I do just send a complete HTML over the
           | wire (even if just with AJAX).
        
             | wavesplash wrote:
             | Hey, thanks for the article. The goal of Hotwire is to
             | remove Ajax calls and minimize Javascript client coding as
             | much as possible. That starts with Turbo as the foundation
             | and then Stimulus is the next layer up. There's nothing
             | wrong with doing what you did w/ Stimulus, but calling it
             | Hotwire creates unnecessary confusion for those new to
             | Rails+Hotwire. That confusion is what I'm addressing in the
             | grandparent comment.
        
               | strzibny wrote:
               | I included a second example with Turbo Frame, but I am
               | keeping Stimulus since it's what the article is about
               | (and it's in the title). Maybe you can do without it but
               | you have to use some hidden forms or something. I believe
               | Stimulus makes it "clean."
               | 
               | Btw Turbo Stream is kind of cool, but in this particular
               | case, do you think its' really better?
        
       | yuppiepuppie wrote:
       | As someone who is only vaguely familiar with Rails and doesnt
       | really know anything about stimulus, it would be nice to have a
       | "What this looks like" section with a gif of what the user sees.
       | Otherwise, I dont know what this article is talking about. Im
       | interested as there is probably some application to my daily
       | work, but a wall of code and words doesnt tell me much very
       | quickly.
        
         | tpetry wrote:
         | It's just some ajax logic: Put something in a form, an ajax
         | request will be made an the response is inserted into the dom.
         | 
         | The title is pure clickbait, it's nothing more than a simple
         | ajax call. The type of logic you've done 10 years ago with a
         | little bit of jquery.
        
           | strzibny wrote:
           | Please enlighten me what would be your title for the post?
           | The idea is making a reusable Stimulus 2 controller.
        
         | strzibny wrote:
         | I just added it. The preview text will become bold, but the
         | idea is that you can send any kind of HTML to inject.
        
           | yuppiepuppie wrote:
           | Great job! Now I get it!
        
           | swanson wrote:
           | I think the article would be better if you were to include
           | even a basic template instead of just adding the <strong>
           | tag. Right now, my first impression would be "why not just
           | wrap the content in <strong> in JS instead of making a
           | network request on every keystroke?"
           | 
           | The benefit of being able to reuse the server template logic
           | isn't being demonstrate because of the simplicity of the
           | example.
        
             | strzibny wrote:
             | I was thinking of that, but I think the point is that you
             | do _any_ transformation in Ruby. I focused on how to do
             | that. Thanks for the feedback.
        
       | Igor_Wiwi wrote:
       | good to see Rails dev related posts on HN again:)
        
       | rolae wrote:
       | Nice. I did something similar recently with an iFrame where you
       | have a form for configuring customer sites and it displays the
       | preview in an iFrame.
       | 
       | I had to throw in some debouncing and special handling for hidden
       | inputs, as changes to hidden inputs do not trigger a change event
       | on the form.
       | 
       | In the end the markup was very simple and is very reusable
       | (StimulusJS v1):                       <div data-
       | controller="iframe-preview" data-iframe-preview-url="<%=
       | preview_path(@letter) %>">               <form data-
       | target="iframe-preview.form">                  <textarea
       | name="body">My letter</textarea>              </form>
       | <iframe data-target="iframe-preview.iframe">             </div>
       | 
       | This is when StimulusJS becomes really nice, when you can compose
       | behavior in your markup with some simple data attributes. I did
       | not think at first that I would need this controller in other
       | places, but a couple of weeks later, I actually needed it, and
       | was able to reuse the controller without modification for another
       | use case.
        
         | strzibny wrote:
         | Nice approach. And yes, Stimulus is great for things like this.
        
       | 023984398 wrote:
       | This is cool but its not using hotwire (turbo frames / turbo
       | streams)
        
         | strzibny wrote:
         | It does! Stimulus 2 is Hotwire too[0].
         | 
         | Turbo seemed more connected with existing models, and so I
         | chose a Stimulus controller instead. But maybe I just don't
         | know Turbo as much yet.
         | 
         | [0] https://stimulus.hotwire.dev/
        
           | oh_boy wrote:
           | At least in the non-rails environment, Turbo and Stimulus are
           | two separate libraries and have to be included individually.
        
           | edwinvlieg wrote:
           | The whole idea of Hotwire is to prevent `Rails.ajax` calls.
           | You can easily accomplish this with less code:
           | 
           | 1. Replace the `output` target with a Turbo frame. 2. Add a
           | value to the `data-controller` div with a `preview-url`:
           | https://stimulus.hotwire.dev/reference/values 3. Change the
           | `Rails.ajax` call with `let url =
           | URL.new(this.previewUrlValue);
           | url.searchParams.append('body', this.tweetTarget.value;
           | this.outputTarget.src = url.toString();` 4. Change the
           | `preview` action to render HTML with the same frame.
           | 
           | Now you have an automatic refreshing frame with less code.
        
             | strzibny wrote:
             | Perfect. I will try it, and perhaps update the post with
             | this approach as well. Thank you. I haven't done anything
             | with Turbo yet, although it's my next step. I first just
             | tried to refresh my Stimulus knowledge with the 2.x
             | changes.
             | 
             | But as for the AJAX call, I still think it's quite simple
             | solution that most will instantly understand (and that's a
             | good thing).
        
               | clairity wrote:
               | > "I will try it, and perhaps update the post with this
               | approach as well."
               | 
               | this would be very useful, as there are not many
               | comparative articles that really help you choose one
               | approach over another. a number of years ago, i went
               | through a couple rounds of turbo vs. ujs vs. websockets
               | vs. custom js vs. something other library i don't
               | remember atm, to try to figure out the best option for
               | the app i was working on. lots of articles talk about the
               | strengths of a library compared to others, but almost
               | none walk through an example (or two) with enough depth
               | to show those differences explicitly.
        
               | strzibny wrote:
               | I included a second example with Turbo Frame that
               | hopefully shows how Turbo Frames work.
        
             | JeremyNT wrote:
             | There are a lot of ways to skin this cat!
             | 
             | You can also have a hidden form submission that does this
             | from the existing controller so you don't need to specify
             | the URL.
             | 
             | For example, you can add a hidden submit button like
             | form.submit 'preview', data: { composer_target: 'submit' },
             | hidden: true
             | 
             | Instead of a stimulus target, you wrap your preview area in
             | a turbo frame                 <turbo-frame id="output">
             | ...       </turbo-frame>
             | 
             | In your (ruby) controller you can re-use the existing
             | controller action:                 def create         @post
             | = Post.new(post_attributes)         preview && return if
             | params[:commit] == 'preview'         ...       end
             | 
             | Do the turbo junk in a private method for the preview:
             | private            def preview         render turbo_stream:
             | turbo_stream.replace(           'output', partial:
             | "posts/preview", locals: { post: @post }         end
             | end
             | 
             | Your stimulus controller now just does this:
             | preview() {         this.submitTarget.click();       }
             | 
             | I'm not sure which I prefer!
        
               | strzibny wrote:
               | Yep, that's what actually cross my mind first. In the
               | end, I wanted to avoid depending on the model, but it's
               | certainly interesting.
        
               | JeremyNT wrote:
               | Once the complexity ramps up and the side effects
               | compound, that's when I really appreciate server side
               | rendering. And incidentally, that's exactly when I like
               | to have the whole model!
               | 
               | I just refactored some old jquery stuff to use turbo and
               | stimulus on a really complex form and it turned out well,
               | I think. But below a certain level of complexity, it
               | would have made sense to just do everything directly in
               | stimulus with no AJAX at all.
               | 
               | I know your preview example is contrived (it's tough
               | getting a real-world representative demo into a blog
               | post, so please don't read this as criticism, I don't
               | mean to say it's a bad example and I'm not trying to pick
               | on it!) but really simple use cases are exactly the
               | "sweet spot" for doing everything directly in the browser
               | using Stimulus without having to call back to the server.
               | Only once you start piling in business logic or
               | reshuffling major parts of the display around does it
               | fully pay off, and that's also about when you'll start
               | appreciating having the whole model and the ability to
               | re-use partials (rather than having to pick off a few
               | pieces manually).
               | 
               | EDIT:
               | 
               | And just to be clear, in the sample code I put in above,
               | you don't actually _have_ to use the model. You can
               | always just shove a single parameter into a special
               | partial and go.
        
           | 023984398 wrote:
           | I mean technically they are installed but the example is
           | still using UJS and not the functionality provided by the
           | hotwire libs.
        
             | strzibny wrote:
             | Yes, I was only focusing on Stimulus 2, but someone else
             | posted here how to change the example to use a frame.
        
       | shay_ker wrote:
       | Does anyone use Live Preview / Live Views in Phoenix, but in
       | production with a B2C product?
       | 
       | I'm a bit skeptical of Rails performance with the method the
       | author details here, given that Ruby is much, much slower than
       | Elixir.
       | 
       | I'm curious if even Elixir folks deploy Live Views in consumer-
       | facing apps. But I can totally see it being used for admin
       | interfaces or internal applications, for sure.
        
         | nickjj wrote:
         | > I'm a bit skeptical of Rails performance with the method the
         | author details here
         | 
         | The method used in the blog post is making an ajax request to
         | the server and then displaying the results in a div. For
         | comparison, GitHub is running Rails and when you type into its
         | search box it's making an ajax request to the server and
         | displaying the results in a div.
         | 
         | Just wanted to bring that up here because this is different
         | than what Live View does or what using Turbo Streams would do
         | to broadcast changes over a websocket connection if you were
         | using Rails.
         | 
         | I don't know of anyone running a large scale app using Live
         | View as a primary focus but I did chat with someone on my
         | podcast about how they used Phoenix and Live View to build
         | https://textdb.dev. That episode is at
         | https://runninginproduction.com/podcast/68-textdb-is-a-
         | simpl.... TextDB trended here on HN a few months ago at
         | https://news.ycombinator.com/item?id=23948234. It handled the
         | HN front page load without issues, but it's also not doing a
         | ton.
        
         | vmsp wrote:
         | I wouldn't say "much slower"
         | 
         | https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
        
         | strzibny wrote:
         | I believe both are used for customer facing applications.
         | However, I am not sure about b2c at scale. You could argue that
         | Hey.com is b2c.
         | 
         | In this case, the main advantage is that scaling is stateless,
         | so your application servers just have to be able to deal with
         | more traffic. There is nothing more to it.
         | 
         | As for Phoenix LiveView, it has both advantage and disadvantage
         | of being stateful (something as using Action Cable). You now
         | have to be scaling web sockets, but you save on authenticating
         | the request (the request is lighter).
         | 
         | Even if LiveView is stateful, it's done on the platform which
         | is optimized exactly for this purpose.
         | 
         | In both approaches, you also have to take into account that
         | server latency might be a deal breaker for you.
        
           | Sodman wrote:
           | Some anecdata as a hey.com user myself: It feels like they've
           | optimized a lot for initial load of the main homepage (The
           | "Imbox"). Obviously this is super important for an e-mail
           | app... but once you start trying to load anything older than
           | the most recent ~20 e-mails (Eg scrolling "The Feed"), then
           | the load times can be horrifically slow (>10s, followed by a
           | janky pop-in of HTML suddenly loaded). I'm not sure if this
           | is caused by this technology in particular or if there's some
           | kind of cache miss happening here, but coming from and
           | comparing to gmail - which hey's makers like to dump on
           | regularly - the performance is miserable. Sure, the number of
           | transferred bytes is small and there's not a lot of JS to
           | load - both of which I'm a big fan of, but the actual load
           | time that I feel as a user is pretty bad for these AJAX
           | calls.
           | 
           | Overall I'm a fan of what they're trying to do with Hey.com,
           | but this particular use case feels like a big UX miss to me.
        
       | stevebmark wrote:
       | I'm happy I don't have to deal with these paradigms day to day
       | anymore:
       | 
       | - Making everything go through a "controller" instead of well
       | designed clients and APIs. Mixing rendering, redirection, ajax
       | handling as well as page serving as well as business logic all
       | into a "controller" is painful. ("Models" and the spaghetti
       | design patterns they come with are even worse)
       | 
       | - PHP style templates that trigger database queries inline,
       | making them difficult and eventually impossible to optimize
       | 
       | - Scattering view code across random places instead of writing
       | view code with its corresponding view logic in the same
       | file/component
       | 
       | - Wiring up functionality to templates by targeting selectors
       | 
       | - Manually setting innerHTML for dynamic functionality
       | 
       | - Dealing with rails "turbo" anything, which is not useful in a
       | real world application, and dangerous (we've had production
       | outages because of trying to use turbolinks)
       | 
       | - Not being able to customize behavior because you're locked into
       | Rails design limitations
        
         | albertgoeswoof wrote:
         | You're probably going to end up doing all those things anyway-
         | just inventing it yourself instead of following a convention
        
         | aantix wrote:
         | What happened with the outage and Turbolinks?
        
           | strzibny wrote:
           | Interested as well.
        
         | jypepin wrote:
         | I agree. I love Rails and think that it was beautifully
         | designed for its purpose, but it's absolute hell once you start
         | trying to integrate React and more complex frontend stuff into
         | it through its view system (vs using it as an API and have a
         | separate frontend).
        
           | FanaHOVA wrote:
           | react_on_rails is actually quite nice. What have you tried to
           | build?
        
           | jraedisch wrote:
           | Webpacker was the first time I ever felt comfortable writing
           | React. It just worked for me. So I have to disagree here.
        
       | lucidone wrote:
       | Would be great to see a paragraph or two about how to write tests
       | for this functionality. Testing has been my pain point regarding
       | front end development with Rails. Compelling tech all the same
       | and a great article.
        
         | strzibny wrote:
         | I am heavy on testing as well. I believe that Basecamp guys
         | would just use the Rails system tests. Therefore there is
         | nothing unique about using Stimulus.
        
         | nicoburns wrote:
         | > Testing has been my pain point regarding front end
         | development with Rails
         | 
         | Are there any technologies where UI testing isn't a massive
         | pain?
        
           | ch4s3 wrote:
           | I've recently found tests for LiveView with Phoenix to be
           | pretty nice. They're a little bit slower than regular tests,
           | but quite a bit faster than Rails with like Capybara or
           | something like Cypress. Though they are less full featured
           | than something like cypress. YMMV
        
         | sam0x17 wrote:
         | Is capybara + headless chrome / selenium no longer a panacea
         | for all integration testing? I've even used it in a React app.
        
       ___________________________________________________________________
       (page generated 2021-03-23 23:01 UTC)