Would it be possible to add an option to replace something other than the <body>?
I've built many site where the header / footer needed to stick around and only the content inbetween needs to be replaced. I've tried using data-turbolinks-permanent in beta 3 but it doesn't seem to have the same effect.
I'm thinking something like this:
<head>
...
<meta name="turbolinks-container" content="#main">
</head>
Hi Wayne,
Part of what makes Turbolinks so simple is that it "just" swaps the <body>. I don't think we should deviate from that.
I'm curious why permanent elements aren't working for you. Could you elaborate?
Thanks for responding @packagethief. I love the default zero-configuration behaviour of swapping the contents of the body, it's amazing!
I've been trying to use permanent elements to persist dom created by 3rd party javascript, which I have no control over unfortunately. That hasn't been working for me. I think it might be because turbolinks is looking for data-turbolinks-permanent once, on initialisation and I'm trying to dynamically add the attribute later on?
I think it might be because turbolinks is looking for data-turbolinks-permanent once, on initialisation and I'm trying to dynamically add the attribute later on?
That sounds like the problem. Permanent elements must match between pages. If you add one with JavaScript that isn't present on the next page rendered from the server, it won't be carried over.
What about creating a permanent container element that you do render on the server, and placing your 3rd party DOM inside it?
That's the thing though @packagethief, I'm currently facing a scenario where I can't tell the javascript where to render it's dom :(
@packagethief I got a similar problem using the toastr plugin
the idea was bump a toastr after the model was successfully created, so on create.js.erb I just triggered and everything should work fine.
My case was similar to @wayneashleyberry one, once I don't have control about the rendering, then my solution was
$( document ).one( "page:load", function() {
toastr.success("message", "title", {timeOut: '5000', closeButton: true, progressBar: true});
});
Turbolinks.visit('/path/to/models');
@packagethief is it possible to stop the body swapping somehow in turbolinks:before-render? If so, people could use event.data.newBody to replace some elements themselves. This would allow advanced usage for those who need it while still keeping the easy-to-use default way of replacing the entire body.
I'm currently facing a scenario where I can't tell the javascript where to render it's dom
@wayneashleyberry oh the tangled webs we weave! I'm sure there's a solution, but it's hard to say without seeing your code. Feel free to email me at [email protected] with details and I'll be happy to help.
I'm going to close this issue because I think specifying an alternative replacement element is off the table.
is it possible to stop the body swapping somehow in turbolinks:before-render? If so, people could use event.data.newBody to replace some elements themselves.
That's an interesting idea @MSchmidt. However, I'd rather we didn't encourage people to pick and choose elements to replace. The beauty of just replacing the <body> is that you don't need to worry about specific elements or fiddle with the DOM.
Of course, you can always modify event.data.newBody in turbolinks:before-render and perform any custom replacement you wish. I just wouldn't recommend getting carried away ;-)
A lot of third party plugins would just work of this was an option
FYI Workaround to not re-send static elements
<%= permanent :header do %>
<%= render 'layouts/header' %>
<% end %>
... dynamic things go here ...
<%= permanent :footer do %>
<%= render 'layouts/footer' %>
<% end %>
# do not re-render and re-send permanent page content when navigating via turbolink
# data-turbolinks-permanent tags only stay around if they are exactly the same, so we fill them with javascript
# https://github.com/turbolinks/turbolinks/issues/43
def turbolink_permanent(id, &block)
anchor = content_tag(:div, "permanent #{id}", "data-turbolinks-permanent": true, id: id)
return anchor if request.env["HTTP_TURBOLINKS_REFERRER"]
html = capture(&block)
anchor << javascript_tag("document.getElementById('#{id}').innerHTML = #{html.inspect};")
end
My use case is that I'd like to keep iframes dynamically attached inside the body laying around. For example, GTM preview and debug, LivePerson, etc. Often times some tools that set all this up do not have public JavaScript interfaces in order to recreate them. Also, you can not detach and move iframes around using the before-cache and before-render events because moving iframes around like that is just not a thing, even all the hacks are out of data, Chrome & Safari. So instead, I'd like to explore swapping the only child we have in the body, a page wrapper.
For anyone interested, my app was on v5.1.0 tag and this was my diff below. I did a build with this change and added the built's resulting dist file to my app's vendor/assets/javascripts folder. I'm not going to use this in production, but I can say it is WONDERFUL to have the GTM Preview & Debug iframe now hanging around on each Turbolinks visit while I have my marketing hat on :)
--- a/src/turbolinks/snapshot_renderer.coffee
+++ b/src/turbolinks/snapshot_renderer.coffee
@@ -61,7 +61,9 @@ class Turbolinks.SnapshotRenderer extends Turbolinks.Renderer
replaceableElement.parentNode.replaceChild(element, replaceableElement)
assignNewBody: ->
- document.body = @newBody
+ docRoot = @getRootElement document.body
+ newRoot = @getRootElement @newBody
+ docRoot?.replaceWith(newRoot)
focusFirstAutofocusableElement: ->
@findFirstAutofocusableElement()?.focus()
@@ -89,3 +91,6 @@ class Turbolinks.SnapshotRenderer extends Turbolinks.Renderer
findFirstAutofocusableElement: ->
document.body.querySelector("[autofocus]")
+
+ getRootElement: (node) ->
+ node?.querySelector '.my-PageWrapper'
@metaskills, did you have success with your patch? Were there any unforeseen issues with this approach?
I totally did. No issues for me either. Just know I only used it in local development and our staging servers. Never had a need to ship the patched version to prod but it did work like a champ.
Hi everyone,
my $0.02: instead of patching the Turbolinks build, I suggest overriding the assignNewBody function in the SnapshotRenderer prototype. Patching the Turbolinks build will prevent you from updating Turbolinks itself whereas overriding the function lives in your codebase and worst case will stop working in future releases (in case there's a method rename or a refactoring for example).
The code:
Turbolinks.SnapshotRenderer.prototype.assignNewBody = function () {
var newBody = this.newBody;
var currentBody = document.body;
// Custom logic to replace DOM Elements here...
// ...
};
Turbolinks.start();
....
I have a use case for targetting an element other than body: I have some CSS animations on a container element which kick in when its class changes, but these get bypassed when the whole body gets swapped out. Being able to only swap the #main element, which is a child of the container, would avoid the CSS animations on the container being interrupted/reset.
Most helpful comment
A lot of third party plugins would just work of this was an option