Barba: Update body class?

Created on 14 Aug 2016  ·  31Comments  ·  Source: barbajs/barba

Is it possible to update the body class between transitions?

Most helpful comment

Got it working with events too:

js Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) { var response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML) var bodyClasses = $(response).filter('notbody').attr('class') $('body').attr('class', bodyClasses) })

All 31 comments

Yes! Use this:

var body = document.querySelector('body');
body.classname = "The new class";

Thanks @mtreik.

To clarify, I'd like to pull the body classes from the new URL into the existing DOM (outside of the Barba container).

Hi @mikespainhower!
Have a look to this issue: https://github.com/luruke/barba.js/issues/49

Ah, thank you!

I'm still getting to grips with barba.js but this code seems to be working in terms of swapping the <body> classes over on page transition (using jQuery):

    // Update body classes by replacing the barba.js internal function
    // See: https://github.com/luruke/barba.js/issues/49#issuecomment-237966009
    var originalFn = Barba.Pjax.Dom.parseResponse;
    Barba.Pjax.Dom.parseResponse = function(response) {
        // Because jQuery will strip <body> when parsing a HTML DOM, change
        // <body> to <notbody>, then we can grab the classes assigned to it
        // See: http://stackoverflow.com/a/14423412/4081305
        response = response.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', response);
        // Get the classes on the <notbody> element
        var bodyClasses = $(response).filter('notbody').attr('class');
        // Apply the classes to the current body
        $('body').attr('class', bodyClasses);
        // Call the original barba.js function
        return originalFn.apply(Barba.Pjax.Dom, arguments);
    };

@mtwalsh 👍

In the next version of barba, i'm planning to add a new argument to the newPageReady that returns the raw HTML, so you will not need to replace .parseResponse.

Stay updated :)

@luruke That sounds great, thanks for sharing your work on barba.js, it's awesome!

Hi, Thanks for the fix. Is there anyway to make sure it only changes the body classes once the new page has loaded? I'm working on a new site and if you click a link that's already cached the body classes change before the page transition is complete and you can see the styling removed from the page?

@hartey11 Yes, you should be able to assign the body classes to a global variable, which you then apply later on. For example:

    // Create a global variable to store the bodyClasses
    var bodyClasses;
    // Update body classes by replacing the barba.js internal function
    // See: https://github.com/luruke/barba.js/issues/49#issuecomment-237966009
    var originalFn = Barba.Pjax.Dom.parseResponse;
    Barba.Pjax.Dom.parseResponse = function(response) {
        // Because jQuery will strip <body> when parsing a HTML DOM, change
        // <body> to <notbody>, then we can grab the classes assigned to it
        // See: http://stackoverflow.com/a/14423412/4081305
        response = response.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', response);
        // Get the classes on the <notbody> element
        bodyClasses = $(response).filter('notbody').attr('class');
        // Call the original barba.js function
        return originalFn.apply(Barba.Pjax.Dom, arguments);
    };

And then later on...

// Apply the classes to the current body
$('body').attr('class', bodyClasses);

Thanks that worked!

Got it working with events too:

js Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) { var response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML) var bodyClasses = $(response).filter('notbody').attr('class') $('body').attr('class', bodyClasses) })

@nicooprat events works well, thanks.

@nicooprat I use barba.js with Wordpress, and this code really works, but the problem is that the classes change before the animation starts, which creates certain difficulties when working, if in css there are dependencies on body classes ...

Examples

// Page-1
<body class="content-full">
  ...
</body>

// Page-2
<body class="content-right">
  ...
</body>

when going from page-2 to page-1, on page-2, you can see how the class changes even before it disappears ...

This event should be fired right between the two transitions. So the class should change when no content is visible. I think there is an issue in your transition declaration, are you using Promises to ensure the In transtion starts when the Out has finished (and the content is loade)? Can you try with the basic fadeInOut transition in the docs?

@nicooprat Hi, thanks for the feedback.

I use the base transitions from the documentation, here is an example of the code that I have at the moment

const getUrl = window.location;
const getHomeUrl = getUrl.protocol + "//" + getUrl.host;

// •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
// Barba.js
// •••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
Barba.Pjax.Dom.wrapperId = 'wrap-main';
Barba.Pjax.Dom.containerClass = 'wrap-main-container';

$('a[href^="' + getHomeUrl + '"]').each(function() {
  this.addEventListener("click", (event) => {
    event.preventDefault();
  });
});

Barba.Pjax.start();
Barba.Prefetch.init();

let FadeTransition = Barba.BaseTransition.extend({
  start: function() {
    Promise
      .all([this.newContainerLoading, this.fadeOut()])
      .then(this.fadeIn.bind(this));
  },

  fadeOut: function() {
    return $(this.oldContainer).animate({
      opacity: 0,
    }).promise();
  },

  fadeIn: function() {
    let _this = this;
    let $el = $(this.newContainer);

    $(this.oldContainer).hide();

    $el.css({
      visibility: 'visible',
      opacity: 0,
    });

    $el.animate({
      opacity: 1,
    }, 500, 'linear', function() {
      _this.done();
    });
  },
});

Barba.Pjax.getTransition = function() {
  return FadeTransition;
};

Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) {
  $('a[href="' + currentStatus.url + '"]').each(function() {
    this.addEventListener("click", (event) => {
      event.preventDefault();
    });
  });

  let response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML);
  let bodyClasses = $(response).filter('notbody').attr('class');
  $('body').attr('class', bodyClasses);
});

Ok first thing you could rewrite you first block like this:

$('body').click('a[href^="' + getHomeUrl + '"]', (event) => {
  event.preventDefault()
})

This way, the listener is alway live on body and check if element matches when the event happens. So it would automatically works on newly added elements without anything more. And you can remove all semi-colon too :)

Back to your issue, as per the docs it should work... Maybe @luruke will have a better idea of what's going on here?

@nicooprat Yes, thanks, your version of the code I like more.

Regarding the problem, I added styles for clarity and increased the animation time to 1s, below you can see how it works.

.archive #wrap-main {
  background: red;
}

kapture 2017-10-08 at 14 47 19

The event is triggered as soon as a new container is ready, and judging by the name 'newPageReady' it should be ...

I tried to delay the event via setTimeout(), but this solution does not work so long as the delay sometimes slips ...

Can anyone have any ideas on how to stick this event between the old container and the new container will start to appear?

It's pretty weird, my code is almost the same as yours and works as expected. I agree that the name newPageReady is a bit confusing!

Could you try to set some debugger in your code to see what's going on? For example, add a breakpoint at the start of the event, and check in the Elements tab in the inspector to see what the HTML looks like at this moment. In my case, I see this:

capture d ecran 2017-10-09 a 14 06 47

It's at this precise moment that the body classes should be swapped.

Or you could create a Codepen with a small test to reproduce the issue?

@nicooprat I created CodePen, the behavior is the same ...

I'm really confused, indeed the event seems to be fired as soon as the content has been loaded, even if the Out transition hasn't finished. But it doesn't work like this on my project. I really think Barba API should be cleaned up...

Anyway, got this working: https://codepen.io/nicooprat/project/editor/ZMYGVe (have a look at newBodyClasses). It's really dirty but can't think of anything better at the moment.

Btw, you shouldn't have to preventDefault on links as Barba does it already.

@nicooprat I also thought about this option, but Linter does not miss it ...

img 2017-10-09 16 35 33

and about the links thank you, I did not know that Barba already forbids himself.

In general while as a temporary solution hung the body_class on .wrap-main-conteiner ...

I actually defined the variable at the beginning of the file in order to be globally scoped ;)

@nicooprat yes, indeed, I did not notice :)

I rewrote @nicooprat 's solution so it could work without jQuery:

Barba.Dispatcher.on('newPageReady', (currentStatus, oldStatus, container, newPageRawHTML) => {
    let regexp = /\<body.*\sclass=["'](.+?)["'].*\>/gi,
        match = regexp.exec(newPageRawHTML);
    if(!match || !match[1]) return;
    document.body.setAttribute('class', match[1]);
});

Hello, I was wondering if anyone was able to get this working so that the new body classes are added after the fade out transition without using a setTimeout()?

Hello, how can I achieve this with Barba v2?

I'm doing this with Barba v2

barba.hooks.afterLeave((data) => {
  // Set <body> classes for "next" page
  var nextHtml = data.next.html;
  var response = nextHtml.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', nextHtml)
  var bodyClasses = $(response).filter('notbody').attr('class')
  $("body").attr("class", bodyClasses);
});

Don't know if it's the best way but it seems work.

@dangelion work well in my case and the code look clean to me, thanks :-)

thanks @dangelion works great for me too.

Got it working with events too:

Barba.Dispatcher.on('newPageReady', function(currentStatus, oldStatus, container, newPageRawHTML) {
  var response = newPageRawHTML.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', newPageRawHTML)
  var bodyClasses = $(response).filter('notbody').attr('class')
  $('body').attr('class', bodyClasses)
})

Thanks this works perfect.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bobbyballard picture bobbyballard  ·  3Comments

luglio7 picture luglio7  ·  4Comments

pburdylo picture pburdylo  ·  3Comments

hugobqd picture hugobqd  ·  3Comments

iamtompickering picture iamtompickering  ·  3Comments