Javascript: Change Parent Styling if Child Element Exists

SASS/CSS can accomplish magic. Sometimes, however, its logic doesn't go quite far enough, and you need a little help from Javascript.

In Sass/Css, the rules of specificity allow us to create styling at a high level and then override it with minimal effort when it comes to more specific cases. For example, I could go into my site theme and write out a general rule: color all links red. When an exception to that rule arises, I can target the parent element of that specific link (a div, for example) to create a new rule: color all links red, but color any link with the parent X blue.

On a recent project, however, I encountered an interesting task. I needed to change a high level rule in order to accommodate the appearance of child element. Now, for most people, this might be a simple fix (just change the high level rule), but in my case the child element is only meant to be temporary. With so much going on in the day to day, I feared that I wouldn't remember to go back and revert this high level rule change once we'd removed the temporary child element; I also worried that the change was so small, if I forgot to make the reversion I might not even notice.

What if there were a way to, in a sense, reverse the order of Sass/Css specificity? In essence: what if we could style the parent based on whether the child existed? If this kind of logic exists in Sass/Css, it's something I'm unfamiliar with. But, I thought, with JavaScript, we might just be able to do it--and it probably wouldn't require much code.

So, let's look at my use case. I'm running a Drupal 8 site that needs a temporary block in the Navigation Block Section of the site and the block has a couple different parent elements. Let's have a quick look at the HTML I'm dealing with--something more or less like this:


<header id="navbar">
  <div class="navbar-header">
    <div class="region-navigation">
      <section id="block-temporary">This is a temporary block</section>
    </div>
  </div>
</header>

There are a few more <section> blocks parented by the .region-navigation <div>, but we'll leave them out of it as they really have no bearing here. Here is some of the CSS I'm dealing with; pay special attention to the padding on .navbar-header:


.navbar-header {
  margin-top: 20px;
  padding: 15px 0px;
  background: red;
}

section#block-temporary {
  display: flex;
  justify-content: center;
  align-items: center;
  background: orange;
  color: #333;
  font-weight: 700;
  height: 50px;
  width: 100%;
}

That should give you something like this:

This is a temporary block

In the above example, the temporary block (section#block-temporary) is noted in orange; the second parent <div> above it (.navbar-header) is noted with red; this is simply to help visualize its padding. The challenge, here at last, is that I'd like to keep the padding on .navbar-header when the temporary block is gone; while it's there, though, I'd like to remove the top padding.

Enter JavaScript: typeof()

The logic we need here is utterly simple: if the temporary block exists, do X.

We can determine whether the block exists with the typeof() operator; typeof() basically evaluates the datatype of it's operand (i.e.; whatever variable you feed it). If we assign a variable to our block, and the block fails to appear on the page, typeof() will evaluate the variable as undefined--in other words, non-existent; conversely, if the block does appear on the page, typeof() can evaluate it as NOT undefined--i.e., existent. We can wrap the typeof() operator in an if statement to realize the first part of our logic: if the temporary block exists...

"Do X" is basically to change the styling of .navbar-header without actually rewriting it. In order to do that, we'll need to look at another variable. The second variable in our logic will be the <header> element parenting .navbar-header; if our temporary block exists, we'll want to add a class to the <header> that we can use to create an exception for the styling of .navbar-header.  Simple--right?

There's just one thing we need to be careful of. If we create variables for our temporary block and <header> but those objects cease to actually exist (i.e., when we remove the temporary block, or when we're on a page that doesn't have the <header> element) we'll get an error in our JavaScript. We can avoid that by adding a few extra conditions to our if logic--namely, we'll want to require that our variables aren't null.  Let's look at the JavaScript (which, in my case, will get embedded on all pages with Drupal's Asset Injector module):


function checkNav() {
  //create variable for temporary block
  var tempBlock = document.getElementById('block-temporary');
  //create variable for <header> element
  var navbar = document.getElementById('navbar');
  //if the temporary block is not undefined (and our variables are not null)...
  if(navbar != null && tempBlock != null && typeof(tempBlock) !== undefined) {
    //add a class to the <header> element called .special
    navbar.classList.add('special'); 
  }
}

//run the function
checkNav();

... and all that's left is to style it appropriately. Let's add a class called .special to our stylesheet and we should be good to go:


.navbar-header {
  margin-top: 20px;
  padding: 15px 0px;
  background: red;
}

header.special .navbar-header {
  padding-top: 0px;
}

section#block-temporary {
  display: flex;
  justify-content: center;
  align-items: center;
  background: orange;
  color: #333;
  font-weight: 700;
  height: 50px;
  width: 100%;
}

And that should do the trick! Now, when the block is present, .navbar-special will get styled out without that top padding--basically this:

This is a temporary block

Not much more to it than that--but I imagine some might ask: isn't this wasteful?--wouldn't it just be easier to edit .navbar-header and leave yourself a note to revert the changes once the temporary block is gone? I fully understand this, and there's part of me that worries that this safeguard may lead to it's own problems: what if the time comes to remove the temporary block and I forget that I have this script running? What if it just becomes more dead javascript & css weight on the site contributing (albeit in a small way) to a slower page load? It's actually a gamble in this particular instance: I doubt this will be the first or last time my client asks for a block in this specific place, and it won't take much to update the script if that's the case. In the meantime the padding of the .navbar-header won't get screwed up if I remove the block and forget what all this entailed.

Feel free to test this with the fiddle below. Either change the ID on the temporary block (in the HTML) or just delete it; you'll see that the padding on .navbar-header changes. Additionally, you can just comment out the .special class bit in the CSS to see the difference: