Skip to content
This repository has been archived by the owner on Sep 25, 2021. It is now read-only.

Override render to replace the entire body given the option of turbolinks => true #20

Closed
wants to merge 1 commit into from

Conversation

minimul
Copy link

@minimul minimul commented May 10, 2017

SCRIPT
self.status = 200
self.response_body = script
response.content_type = "text/javascript"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should call super with options here ?

Turbolinks.clearCache();
var parser = new DOMParser();
var doc = parser.parseFromString("#{ActionController::Base.helpers.j(html)}", "text/html");
document.documentElement.replaceChild(doc.body, document.body);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this should be Turbolinks.replace or some other public method and not a hand-crafted replacement

@@ -20,6 +20,26 @@ def redirect_to(url = {}, options = {})
end
end

def render(*args)
options = args.extract_options!
return super unless options[:turbolinks]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we turn this option into more of a turbolinks if possible then users could just sprinkle it into the format.html and would not need to worry about another response type ...

format.html { render turbolinks: true }

return super unless options[:turbolinks] && request.xhr?

@grosser
Copy link

grosser commented May 10, 2017

I'd prefer if tubolinks could somehow handle the form submission so we don't have to add more server logic ... like having a data-turbolinks=true on forms ...

@minimul
Copy link
Author

minimul commented May 15, 2017

Having problems with this in that render json: { exampleJson: 'text' } calls are asking for a template.

Pulling PR for now.

In my app I have instead added this to application_controller.rb

  def render_with_turbolinks(options = nil, &block)
    html = render_to_string(options, &block)
    script = <<-SCRIPT
      (function(){
        Turbolinks.clearCache();
        var parser = new DOMParser();
        var doc = parser.parseFromString("#{ActionController::Base.helpers.j(html)}", "text/html");
        document.documentElement.replaceChild(doc.body, document.body);
        Turbolinks.dispatch("turbolinks:load");
        window.scroll(0, 0);
      })();
    SCRIPT
    self.status = 200
    self.response_body = script
    response.content_type = "text/javascript"
  end

Example call from a controller:

def create
  #.......
  if customer.save
    # ...
  else
    # ....
    render_with_turbolinks :new
  end

@minimul minimul closed this May 15, 2017
@hsgubert
Copy link

@minimul, if you are interested, I just packaged a solution to this problem into a gem turbolinks-form.

The solution is a little tricker than just using DOMParser and replacing the body because that doesn't work on all browsers, and also we want to run eventual script tags on the new body.

My solution still needs some polishing but it is already working properly on Chrome, Firefox, IE>=8 and PhantomJS (for tests).

@jondavidchristopher
Copy link

jondavidchristopher commented Sep 28, 2017

I have a really simple CoffeeScript class that handles form submission nicely. Keep in mind that this is used with the new version of turbolinks that does not require jQuery. Redirects if successful, and renders and replaces entire page if not. It uses Turbolinks internal classes to do the rendering so all things work as expected like running scripts, and all data-turbolinks options. It also does a great job of handling the browser history. Hope this helps someone!

class @FormHelper
  constructor: ->
    document.addEventListener "turbolinks:load", @dom_refresh

  dom_refresh: =>
    for element in document.querySelectorAll "form[data-remote='true']"
      element.addEventListener "ajax:send", @remote_form_submit
      element.addEventListener "ajax:complete", @remote_form_complete

  remote_form_submit: (event) ->
    document.activeElement.blur()
    Turbolinks.dispatch "turbolinks:click", event

  remote_form_complete: (event) =>
    xhr = event.detail.filter((object) -> object.constructor is XMLHttpRequest)[0]

    unless xhr.getResponseHeader "Location"
      current_snapshot = Turbolinks.Snapshot.fromElement document
      new_snapshot = Turbolinks.Snapshot.wrap xhr.responseText
      Turbolinks.controller.currentVisit = Turbolinks.controller.createVisit window.location.href, "replace", {}
      Turbolinks.SnapshotRenderer.render Turbolinks.controller, @complete, current_snapshot, new_snapshot

  complete: =>
    Turbolinks.dispatch "turbolinks:load"
    document.body.querySelector(".has-error input, .has-error select")?.focus()

new FormHelper

in controllers that do the redirect you will want to tell turbolinks to advance the history rather than replace it:

  def create
    @team = Team.create team_params.merge users: [current_user]
    if @team.persisted?
      redirect_to icon_team_path(@team), turbolinks: "advance"
    else
      render :new
    end
  end

@hsgubert
Copy link

Hi @jondavidchristopher, that is a cool solution.

One downside though is that your controller must be turbolinks-aware when there are redirects (this could be fixed). And also, for me specifically, I need a solution that works with forms displayed on dialogs too, where only the dialog content is replaced. I solved this issues in turbolinks-form.

On the other hand I am not so comfortable with CoffeeScript, so I wrote my solution in plain Javascript, but it would be good to reuse some of the internal methods of Turbolinks like you did.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants