Question: Is it possible to get data attribute from fragment or fragment's parent?
I have a body class I need to update when people click on links. I'm using full pages as fallbacks and customizing my fragment as #content to update only the portion of the page whose content changes. But when I do this, the body's class isn't changed. Is there a way I can put a data-attribute on #content and grab it with my PJAX calls? Then I could apply that value to the body as a class or an attribute.
Body class changing sounds like application-specific logic, so it would be best handled by adding pjax lifecycle event handlers tailored for your app. The "pjax:end" event is suitable since it will be triggered after every pjax navigation, including browser back/forward.
For example, one approach might be to ensure on the server that each #content element has the appropriate data-attribute that reflects what the body class should be:
<div id="content" data-body-class="foo bar baz"> ... </div>
And then add an event handler like this:
$(document).on("pjax:end", function(){
var bodyClass = $("#content").attr("data-body-class")
if (bodyClass !== undefined) document.body.className = bodyClass
})
However, this approach won't quite work. I'm assuming that the #content element is your pjax container. In that case, all the content within #content will get swapped out during pjax navigation, but the container element itself will remain untouched. Therefore, the data-attribute changes on it will not have effect, even if such attributes are present in the HTML response from the server.
The solution to this would be to have an element with data-attribute somewhere within the container element. For instance:
<div id="content">
<meta name="body-class" content="foo bar baz">
... rest of the content ...
</div>
The JS code above would have to be updated:
$(document).on("pjax:end", function(){
var bodyClass = $("meta[name=body-class]").attr("content")
if (bodyClass !== undefined) document.body.className = bodyClass
})
Thank you for your detailed response! Hmm, I was hoping there would be a way to sift through the HTML for meta information before PJAX returns just the fragment.
Yes, your initial idea sounds simpler even to me, but might be slightly harder to implement. You could sift through the original HTML (before pjax extracts the fragment) like so:
$(document).on('pjax:success', function(event, data) {
var html = $.parseHTML(data)
// sift through `html` to find information you're looking for
})
However because this is in a "pjax:success" handler, it will only happen after pjax load from the server (such as clicking a pjaxed link), but not on brower back navigation. On browser back/forward navigation, pjax doesn't perform fetches from the server, but restores previous page content from its internal cache. That cached content only includes the contents of the pjax container and not the original ajax responses, I'm afraid.
Oh that's the ticket I was looking for! I could put this data into local storage and call it up on popstate then, could I not?
Does your server serve full HTML pages on pjax navigation, where <body> elements already have appropriate classnames that you want to preserve even after pjax extracts just the fragment of the page? Then an approach like this might work:
var bodyClassCache = {}
function updateBodyClassCache(className) {
bodyClassCache[document.location.pathname] = className
}
$(function(){
updateBodyClassCache(document.body.className)
})
$(document).on("pjax:success", function(event, data) {
var html = $.parseHTML(data)
var bodyClass = $(html).find("body").attr("class")
updateBodyClassCache(bodyClass)
})
$(document).on("pjax:end", function() {
document.body.className = bodyClassCache[document.location.pathname]
}
It's more JS code, but it frees you from adding extra element or data-attributes to HTML pages.
I just experienced first-hand how much it's a PITA to read HTML elements/attributes outside from pjax fragment. My solution was to put everything inside the fragment element and read it during pjax:beforeReplace and pjax:end events as needed. It still feels hackish.
The next major release of pjax will make this easier, but for that we'll have to break backwards compatibility in a few places. That's still a long way off, so in the meantime I hope you worked around out your issue? Was my comment above helpful?
Thanks for you swift attention to this issue, and my apologies for the long period of silence.
- Yes, I serve full HTML pages. Backwards compatibility and all that ;)
- I ended up just using JavaScript to read the URL and update based on that. It is decoupled from PJAX that way, though, so I may give your recommendation a shot in round 2 of development.
Thanks again!