Stimulus Discourse

Fetching a partial on click

Hello friends,

I’m seeking some general advice on how to go about this problem. (I’m using rails)

  • I have a list of customers. I am listing only their names in an index action. When the name is clicked i want to retrieve further information from the server concerning each particular customer. viz:

    customers/index.html.erb

    <% @customers.each do |customer| %>

    <%= customer.name %>
    

    <-- when you click on the customer name, I want further details to appear -->
    <-- I want to append the customers/_show.html.erb partial here when the customer name is clicked -->
    <% end %>

    customers/_show.html.erb

    <-- more information re: the customer is shown in this partial -->
    <%= customer.date_of_birth %>
    <%= customer.address %>

I suppose there are two ways which one can do this:

  1. Using plain vanilla ajax with a customers/show.js.erb template which appends the partial to the DOM.
  2. Another method using Stimulus js erb controller and then using a [Renderer](https://api.rubyonrails.org/classes/ActionController/Renderer.html)

Was wondering how some of you folks would go about solving this particular problem?

Hey @BKSpurgeon, welcome to the forum!

Without knowing the full details of your page and thus any factors that might make this a bad idea, my first suggestion would be to render all of the additional details onto the page and then just show and hide them as necessary. This recent post does something similar, with sample code. It’s just toggling a class.

If that doesn’t work (e.g. it’s going to add, I dunno, megabytes of data to the page load), I’d guide you in the direction of your second option. Just call out to a normal controller that returns a normal payload but without a layout (so the customer details response doesn’t have html, body, etc. tags). Then have a Stimulus controller just plop that onto the page.

The great thing about that approach is the JS can easily be written so that it’s dumb - instead of a CustomerDetailController, for instance, it could be a RenderResponseController that does nothing more than call out to a URL and place the response into a specified target div. Then, any time you need to do some AJAXy rendering, you’re writing no additional JS (just re-use this controller) and just returning pre-rendered HTML from the backend.

That said, with that option you do still need to handle possible errors from the backend when fetching the customer info. Just one more reason why simply pre-rendering the customer info, if possible, would be preferred here.

Hope this helps!

Hi there @welearnednothing

Thank you for your pointers.

You are correct in your judgment: loading all the customer information on the original page load and then hiding / revealing them as required will prove an expensive operation. I am leaning towards only loading what’s necessary, and if required via further requests:

I have created a separate route and action: Customers#show_partial

CustomersController.rb
  def show_partial
    show
    render "show", layout: false  # without the layout as suggested
  end
end

And then I plan to hit this controller and return that html upon an event (e.g. click). Would you suggest that this is the correct way to approach this problem? Are there any obvious flaws here?

pointers much appreciated.

rgds

Ben

I’d say this is a great approach. In my experience it’s very common. As I mentioned, it can be done in such a way that the Stimulus controller is generic and reusable, and the backend code is obviously trivial, so it’s win win all around.

You can simplify the Stimulus controller even more if you leverage Rails’ Unobtrusive Javascript to make the actual calls to the controller by leveraging remote: true on your links.

For example:

index.html.erb

<ul>
<% @customers.each do |customer| %>
  <li data-controller="renderer">
    <%= link_to customer.name, customer_detail_path(customer), data: { remote: true, action: 'ajax:success->renderer#render' }  %>
    <span data-target="renderer.display"></span>
  </li>
<% end %>
</ul>

renderer_controller.js

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = ['display']

  render(evt) {
    this.displayTarget.innerHTML = evt.detail[0].body.innerHTML
  }
}

You’ve effectively introduced a single line of JS here that can be reused in many situations by leveraging the JS that’s already packaged in Rails. It’s an all around pretty elegant solution if you ask me!

One thing I’ll point out, I did reference a customer_detail_path here, hinting at using a CustomerDetailsController rather than bundling the details response in with the CustomersController. Purely optional, but it follows this advice from DHH and I’ve found it to be helpful in keeping my controllers dead simple.

4 Likes

@welearnednothing

Thanks for the pointer.
i was thinking there are two ways to do this:

  1. Use Stimulus to fetch the partial and render it in the DOM, OR
  2. Return a js.erb to the user which attaches to the DOM:

e.g.

# CustomerDetailsController.rb 
def show
   @customer = Customer.find(params[:id])
    respond_to do |format|
      format.html do
      end
      format.js do
        render layout: false
      end
    end
  end

// views/customer_details/show.js.erb
$('div.test-display').html("<%= escape_javascript(render partial: 'customer_details/show', locals: { customer: @customer } ) %>");

Was wondering if you had any thoughts/ideas on the matter: should one use stimulus (as you’ve suggsted above) or just return a js.erb to the user?

I mean, you’re on a Stimulus forum so of course I’m going to say go with Stimulus :slight_smile:

But honestly, I can’t recommend you start a new project these days with jQuery. If it’s a legacy project and is already using jQuery, so be it. But jQuery came about at a time (along with similar libraries like YUI and Dojo) where it was really necessary to deal with browser incompatibilities and is largely just not needed anymore. Moreover, Stimulus is focused on allowing you to very easily build isolated and composable components of functionality with defined lifecycles and fits very well into modern ecosystems, and plays very nicely with Turbolinks.

There could be extenuating circumstances that lead to jQuery being the right choice for this project (such as it’s a legacy project with a lot of jQuery and currently no Stimulus). But extenuating circumstances aside, ditch jQuery and give Stimulus a whirl.

1 Like

@welearnednothing

Thx I appreciate your pointers. I’ll go with stimulus as suggested.

There’s one final problem.

Background:

  • I’m using Rails with turbolinks and stimulus js.

  • I’m fetching some html and adding it to the dom successfully via stimulus.

  • But I need to initialise some javascript on the html that is being fetched. Would you know how to do this - in a very general way? I tried running method which initialises the javascript:

    show(){
    	console.log(this.data.get("url"))         	 
    	fetch(this.data.get("url"))
      	.then(response => response.text())
      	.then(html => {this.displayTemplateTarget.innerHTML = html     } )
     	.catch(error => { console.log(error)})
    
       initialiseElmApp()  // this method parses all the html on a page,
                      // then it searches for particular html elements
                      // and then runs a javascript on those elements
                      // to basically run an elm app on a particular html node.
    }  
    

    }

  • the html loads fine, but the elm app does not seem to get initialised when the first fetch call is made, but it DOES work on subsequent fetch calls. would you know how this type of paradigm is typically handled?

EDIT and UPDATE

The problem has been fixed. I simply moved the initialisation code WITHIN the fetch event handler when the response was successful. I think it works now because the fetch works asynchronously and can only initialise elements if they are existing in the DOM.

Still would be interesting to know how initialisation of javascript code is typically handled when obtaining html via the fetch API and when one is using turbolinks?

1 Like

Hey @BKSpurgeon, glad to hear you got it working!

So, going back to the topics of reusability and compatibility of Stimulus, the preferred approach here would be not to combine both of these functionalities into one controller and instead just wrap any Elm apps in a different controller that is specifically for initializing any Elm app. The Elm controller’s HTML would just be in the returned response, and when it’s added to the DOM, Stimulus will automatically instantiate that controller. So to answer your last question directly, any Stimulus controller will be automatically instantiated when obtaining HTML via the Fetch API (and that HTML is added to the DOM… if it’s not added to the DOM, it won’t be instantiated). It all “just works”, it’s great!

This approach would leave you with two completely reusable controllers that aren’t tied to a specific use case or flow.

So for example, the returned HTML might look like:

<div data-controller="elm">
  ... customer details ...
</div> 

with an ElmController along the lines of:

export default class extends Controller {
  connect() {
    this.initializeElmApp()
  }
}

When that HTML is added to the DOM, the ElmController will be automatically instantiated which will then initialize your Elm app.

With this approach, the code loading the remote data isn’t tied to initializing Elm apps and the code tied to initializing Elm apps isn’t tied to loading remote data and they can just be mixed and matched anywhere.

That being said… rather than using Stimulus here, why not just use Elm for everything? I’d certainly recommend not mixing and matching two completely different JS frameworks in your app. But assuming there’s no way around it, the above approach would be considered a best practice.

Cheers!

1 Like

@welearnednothing Hi Jeff thanks for this, very interesting ideas! This approach looks a little better than the approach I was currently utilising to initialise all my elm code.

(Unfortunately, I cannot do everything in elm, and must rely on some vanilla javascript make things work).

Hey there @BKSpurgeon!

I’m sorry for dropping in late to the party here. I see that you may be using Elm, but I just wanted to drop this link about a similar partial-fetching implementation.

I really love that work being done by Matt Swanson over at Boring Rails, and I think his implementation of hover-cards with StimulusJS and plain JavaScript is super elegant and is in the spirit of HTML over-the-wire. Additionally, his solution may be ready out of the box for you to use. You would just have to swap the mouse-over action triggers with click events.

Hope this helped!

2 Likes

@jacobdaddario This hover card functionality looks very promising. I will certainly spend some time studying this implementation - there are also some other interesting stimulus articles in the link you’ve provided too.

I appreciate the pointer + link!

1 Like