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

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

Closed
wants to merge 1 commit into
base: master
from

Conversation

Projects
None yet
4 participants
@minimul
Copy link

minimul commented May 10, 2017

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

This comment has been minimized.

@grosser

grosser May 10, 2017

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);

This comment has been minimized.

@grosser

grosser May 10, 2017

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]

This comment has been minimized.

@grosser

grosser May 10, 2017

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

This comment has been minimized.

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

hsgubert commented Sep 28, 2017

@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

This comment has been minimized.

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

This comment has been minimized.

Copy link

hsgubert commented Sep 29, 2017

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 join this conversation on GitHub. Already have an account? Sign in to comment