Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save zanetagebka/f1961c402975dfb6559a57d9935c5c64 to your computer and use it in GitHub Desktop.
Save zanetagebka/f1961c402975dfb6559a57d9935c5c64 to your computer and use it in GitHub Desktop.
Pagy Documentation - Using Stimulus JS

Using Stimulus JS to POST

Whenever you use pagy links that require interception, you will need to reinitialise any javascript that you need to run. If you submit a form, and use a turbo frame to render those search results, and to also render the pagination links to those results, how are you going to reinitialize your javascript code to intercept those pagy page links?

Stimulus JS is very handy for reinitialising javascript code, and is useful if you are using a library like hotwire (by Basecamp) where changes are made to the DOM.

// pagy_controller.js - a stimulus JS controller
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form", "pageInput" ]

  connect() {    
  } 

  submitForm(event){
    event.preventDefault()
    let pageNumber = event.target.getAttribute("href")
    if (pageNumber === "#") {} 
    else {
          // let's get the form's page input and replace it with the link's page number
          let pageInput = this.pageInputTarget
          pageInput.value = pageNumber
          
          this.formTarget.requestSubmit()
    }
  }
}
<%= turbo_frame_tag "search_results"  do %>
  <div data-controller="pagy">    
      <%= render 'form' %>          
    <div class="text-center">
      <%== pagy_bootstrap_nav(@pagy, link_extra: 'data-action="pagy#submitForm"') %>
    </div>
  </div>
<% end %>
<-- search from -->
<%= search_form_for @q, url: search_path,  html: { method: :post,  "data-pagy-target":"form"  } do |f| %>
    <%= hidden_field_tag 'page', '1', {"data-pagy-target": 'pageInput'} %>
    <%= f.submit %> 
<% end %>

And finally, you will want all your pagination links to have a stimulus js target on them. This can be done directly from the pagy controller:

def index
    @pagy, @search_results = pagy(@q.result.order("id DESC"), overriding: true) # see the following post on what "overriding" parameter means: https://benkoshy.github.io/2020/02/01/overriding-pagy-methods.html
    
end

Using Stimulus JS and Turbo Streams

You may want to employ Ajax-like functionality to return and update your search results. Here's the work flow:

  • Users are presented with a form.
  • The second they start typing, or changing the form: the form is submitted (via stimulus). We are going to throttle the search results so that our server is not hammered every time a user makes a change.
  • The results are "streamed" back to the page. Basically, we are updating the search results via AJAX - but the search form is the same HTML. The only HTML that changes is the search results - the "quotes" that are returned.
  • When a pagination link is clicked, we don't need to worry about submitting a cached form: we are essentially simply changing the form's page input value, and submitting. Simple:

Here is our form:

<--- _search_form.html.erb --->
<%= search_form_for @q, url: search_quotes_path,  html: { method: :post, "data-pagy-target":"form", "data-controller":"submit", "data-action": "turbo:submit-end->submit#reset" } do |f| %>  
  <%= f.search_field :project_name_cont,  class: "form-control", placeholder: "Project name contains...", type: "text", "data-action":"pagy#submitAndRevertPageNumber" %>
  <%= hidden_field_tag 'page', '1', {"data-pagy-target": 'pageInput'} %>
  <%= f.submit "Submit", "data-pagy-target":"submitButton", "data-submit-target":"submitButton" %>
<% end %>

and here is the wrapper:

<div data-controller="pagy">
  <%= render 'searchWrapper' %>
  <div id="quotes">
    <%= render 'quotes/quotes_table', locals: {quotes: @quotes} %>
    <%== pagy_bootstrap_nav(@pagy, link_extra: 'data-action="pagy#submitForm"') %>
  </div>
</div>

And when the form is submitted, all we need to do is return a turbo-stream which updates the quotes:

<%= turbo_stream.replace "quotes" do %>
  <div id="quotes">
      <%= render "quotes/quotes_table", locals: {quotes: @quotes} %>
       <%== pagy_bootstrap_nav(@pagy, link_extra: 'data-action="pagy#submitForm"') %>
   </div>
<% end %>

This is code I've directly cut and paste / extracted (with only stylistic modifications: i.e. eliminating the CSS and style based changes).

There's a tiny issue when submitting forms using Rails and turbo (i.e. hotwire). The submit button is disabled. We need to re-enable it, so that users are not confused. I have created a "reset" method. Ideally, this functionality should be automatically with an attribute like in the good old days when we used rails ujs. Not to worry, we will manually handle it. Perhaps I may - or someone else may - add this functionality directly in the core library:

// app/javascript/controllers/submit_controller.js

import { Controller } from "stimulus";

let debounce = require('lodash/debounce');
let throttle = require('lodash/throttle');

export default class extends Controller {
  static targets = ["submitButton"]

  connect(){
  	this.submit = debounce(this.submit, 250).bind(this)
    this.submit = throttle(this.submit, 250).bind(this)
  }
  
  submit(event) {        
    this.element.requestSubmit();
  };

  reset(event){    
    this.submitButtonTarget.disabled = false;
  }
}
// app/javascript/controllers/pagy_controller.js
import {
  Controller
} from "stimulus"


let debounce = require('lodash/debounce');
let throttle = require('lodash/throttle');


export default class extends Controller {
  static targets = ["form", "pageInput"]

  connect() {
    this.submitAndRevertPageNumber = debounce(this.submitAndRevertPageNumber, 350).bind(this)
    this.submitAndRevertPageNumber = throttle(this.submitAndRevertPageNumber, 350).bind(this)
  }

  submitAndRevertPageNumber(event) {
    let pageInput = this.pageInputTarget
    pageInput.value = 1
    this.formTarget.requestSubmit()
  }

  submitForm(event) {
    event.preventDefault()

    let pageNumber = event.target.getAttribute("href")

    if (pageNumber === "#") {
      // then do nothing
    } else {
      // let's get the form's page input and replace it with the link's page number
      let pageInput = this.pageInputTarget
      pageInput.value = pageNumber

      // and now let's just click the submit button     
      // warning: for safari users who are not using hotwire: you will need to use a polyfill
      // when using requestSubmit. Not to worry, if you are using hotwire - the 
      // polyfill is automatically handled for you.
      this.formTarget.requestSubmit()
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment