Getting the “abc” right

When you open your new umbrella for the first time, it feels good because no drop of rain has touched it yet: it’s the first and the last time you’ll see it that clean!

G is for Google

A minimalist style for “a game-changing news”

I am still not 100% sure what these days alphabet news is all about, but I’ve noticed the minimalism, simple, and blazing fast way, in proper Google style, the news has been delivered:

a clean and simple website that does not even load a single external JavaScript file.

A nobel Web intent …

The Alphabet front page is a rare sort of beast you can find these days online:

This front page, is probably what the Internet should have always been, or what it is in a parallel universe ironically free from Google itself (analytics, ads, adwords, alimortacci, etc)

… yet far from perfection

Of course I have used my developer tools to check the source, and of course I am here now to demystify few choices made to create that page.

Let’s start from the top of the page, shall we?

<!doctype html>
<!--[if lt IE 7]> <html class="ie6 lt-ie10 lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]>    <html class="ie7 lt-ie10 lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]>    <html class="ie8 lt-ie10 lt-ie9" lang="en"> <![endif]-->
<!--[if IE 9]>    <html class="ie9 lt-ie10" lang="en"> <![endif]-->
<!--[if gt IE 9]><!--> <html lang="en"> <!--<![endif]-->
  <head>
    <title>Alphabet</title>

Ignoring the fact that AFAIK the declaration should be capitalized as <!DOCTYPE html>, but eventually ignored anyway as it should be so I guess it’s OK, if you are wondering what are all those HTML comments, you might find this good old article an answer to your questions.

Yes, this page has been tested, and it works indeed, down to jurassic legacy browsers such Internet Explorer 6 (can’t help myself feeling bad even typing that browser in 2015).

Remember the purpose of the front page? Inform every person of the planet about this new umbrella corporate.

What are conditional classes usually for?

Here the reason you might want to use extra classes, something I should have mentioned when I’ve posted about the Web bare necessities, conditional comments can be used to target effectively old browsers and fix their behavior, either through different CSS, sometimes even via JavaScript, which is always capable to understand if the always available document.documentElement.className contains some legacy to take care of.

This technique is an indirect UA sniffing, but at the same time, it’s extremely handy and safe, for the simple reason these are not evergreen browsers, and all problems are well known upfront, and most of the time, patch-able through hacks and libraries.

How are conditional classes used in there?

This is the number one glitch I’ve found in this page, legacy browsers are targeted in order to brute force the natural semantic of the CSS.

CSS classes names such .hide and .hide-inline, are forced to actually display: block; or display: inline;.

This means that there are superfluous classes on the layout, capable of interfering with the natural way a browser would render. The semantic becomes completely useless, and if you use this practice daily for your sites too, you are making your CSS a clear mess.

Why is that?“ … imagine one component brings in, with all rights of this semantic world, the following CSS:

.hide {
  display: none !important;
}

You’d be surprised how many sites out there use that kind of style to be sure that no other CSS can change that behavior!

An impostor in the layout !

The smart page full of CSS hacks ain’t that smart after all for the simple reason that it carries an element which is completely useless if the browser does not support CSS and JavaScript.

In few words, the page coupled its JS driven “special effects“ behavior to its own content.

Alphabet via lynx

This time is not about saving bytes or performance, this time is about delivering a content that in textual browsers looks broken, since clicking that link brings nowhere.

Crawlers might also have to understand what is that more link about, but I care less about robots.

JavaScript after all

The page delivered without external JavaScript has an inline one right before the end of its body. This is a common, also old style, technique, there’s nothing bad about it beside again some standard semantic that would like to have scripts in the head of the page and not in the wild … but we can live with it ‘cause it works today and it always did.

So what’s the glitch in the JavaScript part? The TL;DR answer is: it’s full of hacks!

My apologies in advance ‘cause I will be quite picky here.

// everything is defined globally
// OKish since there are no other scripts
function getHeight(el) {
  var

    // please note the underscore style
    // on a camelCase based PL
    // like max_height to retrieve maxHeight
    // hilariously confusing and boring in terms
    // of greppability ... not my style ... anyway ..

    // using a global `window.` to access a global property?
    // You don't need that, specially if you are using the method
    // because if it's not there it will break in any case
    // el_style = getComputedStyle(el), would do the same
    el_style = window.getComputedStyle(el),
    el_display = el_style.display,

    // every browser that supports getComputedStyle
    // supports also RegExp
    // the following could have been a better looking
    // el_max_height = el_style.maxHeight.replace(/px|%/,''),
    // or even
    // el_max_height = parseFloat(el_style.maxHeight)
    // since NaN is always !== from  '0' anyway
    el_max_height = el_style.maxHeight.replace("px","").replace("%",""),
    wanted_height = 0
  ;

  // these checks are needed because the page
  // by default is NOT FULLY VISIBLE
  if (el_display !== "none" && el_max_height !== "0") {
    return el.offsetHeight;
  }
  // so this script needs to hack the style
  el.style.display = "block";
  // trigger the render
  wanted_height = el.offsetHeight;
  // put the initial style back
  el.style.display = el_display;
  // and finally return the height
  // of the element which is needed
  // once we'll click the [more] link
  return wanted_height;
}

// a whole toggle function
// for an action that happens/toggle only once
// the link is indeed made impossible to be clicked again
// so this feels like it was here to debug
// a non needed behavior like closing back
// However, I like DRY functions, so maybe
// that is why this one is here.
function toggleSlide(el) {
  var el_max_height = 0;
  if (el.getAttribute("data-max-height")) {
    // again the double replace ... is this an over-thought performance thing?
    if (el.style.maxHeight.replace("px","").replace("%","") === "0") {
      el.style.maxHeight = el.getAttribute("data-max-height");
    } else {
      el.style.maxHeight="0";
    }
  } else {
    el_max_height = getHeight(el) + "px";
    // el.style.transition but I gues there was ['-webkit-transition']
    el.style["transition"] = "max-height 0.5s ease-in-out";
    el.style.overflowY = "hidden";
    el.style.maxHeight = "0";
    el.setAttribute("data-max-height", el_max_height);
    el.style.display = "block";
    // HACK, one of the most annoying thing these days about CSS
    // we can't easily animate properties 'cause
    // these will be resolved all at once, resulting
    // in an element with maxHeight already different
    setTimeout(function () {
      // this is, unfortunately, a needed hack
      el.style.maxHeight = el_max_height;
    }, 10);
    // this HACK though, someone has to explain it  to me.
    // So the element is kept visible with a class .hide
    // but its styles that cannot be possibly changed later on  anyway
    // are reset ... and everything after a static random 700 milliseconds
    // indicating this function wasn't even a copy and paste
    // it was craeted ad-hoc for this page
    setTimeout(function () {
      document.querySelector(".hide").style["transition"] = "all 0s 0s ease";
      document.querySelector(".hide").style["max-height"] = "none";
    }, 700);
  }
}


// finally the check, with the assumption
// that if window.addEventListener is available
if (window.addEventListener) {

  // document.querySelector must be in too
  // NOPE, this will break in older browsers
  // since addEventListener has way more legacy support
  // than document.querySelector
  document.querySelector(".read-more").addEventListener(
    "click",
    function (e) {
      // the link is made invisible
      // so that is not possible to click it again
      // meaning the entire ad-hoc toggle is a bit over-engineered
      // and full of needless operations as this page is now
      this.style.display = "none";
      // because removing `.hide-inline` class is too mainstream
      // let's keep it there and force the style to display inline
      document.querySelector(".hide-inline").style.display = "inline";
      // same will be done here
      toggleSlide(document.querySelector(".hide"));

      // if the following is needed and it's what you want to do,
      // you better do it as the very first thing in your listener
      // because everything else might break
      // and you still don't want the click of a pointless link
      // to react as a regular click
      e.preventDefault();

      // Hello jQuery user,
      //   how are you doing with return false today?
      return false;
    },
    false
  );
}

Thanks to all the described logic, which will fit once minified into about 1KB plain text, the page will break with browsers that support addEventListener but not document.querySelector.

This means that the content will not be delivered so the initial nobel intent, fade out due obtrusive techniques used in order to give an effect that could easily break.

From the future umbrella of the entire IT, and with a staff of thousand excellent and ultra skilled engineers, can I expect a bit more from you, my dear Alphabet? Can we get the abc right?

At least please let me try!

Revisiting the Alphabet page intent

Let’s recap: we want to deliver some content to every single capable browser on this planet.

We also want to be able to add a little modern, cool, entertaining effect, for most modern browsers.

Here is what I’ve created instead, and you can see the result in this page.

<!DOCTYPE html>
<html>
  <head>
    <title>Getting the abc right: example</title>
    <!--[if lt IE 9]><script>document.createElement('main')</script><![endif]-->
    <style>
    body { font-family: sans-serif; text-align: center; }
    h1 { font-size: 1.5em; color: #333; }
    main { display: block; margin: auto; max-width: 640px; text-align: left; color: #555; }
    .read-more { text-decoration: none; color: #ed1c24; }
    .read-more:hover { color: #000; }
    .hide { display: none; }
    </style>
    <script>
// a closure is always a good starting point
// to be minifier friendly and to reduce accidental pollution
// of the global scope if named function expressions are used
// and IE < 9 is part of supported targets
(function (document) {

  // in case there is no addEventListener
  // or no querySelector
  // there's no point to go on. Let the old browser see the content as is
  if (!document.addEventListener || !document.querySelector) return;

  // otherwise, every known browser with those two standard methods
  // should also be compatible with the standard DOMContentLoaded event
  // Such event triggers as soon as the content is available
  // and before it gets painted for the first time
  // the perfect moment to hide undesired parts of the page
  document.addEventListener('DOMContentLoaded', function DCL(e) {

    // parts that should be shown are addressed via classes
    var
      inlineContent = document.querySelector('.content-inline'),
      extraContent = document.querySelector('.content'),
      eCStyle = extraContent.style,

      // since the content is already fully shown
      // we don't need tricks to get its height
      contentHeight = extraContent.offsetHeight,

      // we also can use the inline content container ...
      inlineContainer = inlineContent.parentNode,
      // ... to create our "more" link
      readMore = inlineContainer.appendChild(
        document.createElement('a')
      )
    ;

    // let's use some semantic right
    // we are hiding inline and extra content
    // in every browser through the `hide` class name
    extraContent.className = 
    inlineContent.className = 'hide';

    // for styling purpose, we add a class to our more link
    readMore.className = 'read-more';

    // we can change the rest of the CSS
    // to prepare the element to be shown in the future
    eCStyle.maxHeight = '0';
    eCStyle.overflowY = 'hidden';
    eCStyle.transition = 'max-height 0.5s ease-in-out';

    // if the current body height plus the content height
    // is more than the available height ...
    if (innerHeight < (document.body.offsetHeight + contentHeight)) {
      // ... we can force the scroll bar to be visible
      // so that once the content expands it will not  jump
      // the moment the scrollbar becomes visible and usable
      document.body.style.overflowY = 'scroll';
    }

    // instead of having a static string here, we can use a `data-more`
    // attribute so that eventual language changes will be reflected
    // in this very same script
    readMore.textContent = inlineContent.getAttribute('data-more');
    // let's make the link intent explicit through its visualized url
    readMore.href = '#' + readMore.textContent;

    // now we can add the click listener to this new element
    readMore.addEventListener('click', function click(e) {

      // let's avoid any default click action
      // remember? do this at the top of a listener
      // never at the bottom
      e.preventDefault();

      // let's remove this link since it won't be needed anymore
      // removing it, we also implicitly clean from its click listener
      // once the element will be garbage collected
      inlineContainer.removeChild(readMore);

      // we can show the inline rest of the paragraph
      // and the rest of the content too
      inlineContent.className =
      extraContent.className = '';

      // however, we might still need this ugly hack
      // to be sure that the effect will be visible
      // we don't need to address this timer here
      // but only because we never want to be able to drop it
      // it should trigger in 10ms or whenever is possible after
      setTimeout(function () {
        // so that we can see the transition
        // in  case the browser supports it
        eCStyle.maxHeight = contentHeight + 'px';
      }, 10);
    });
  });

}(document));
    </script>
  </head>
  <body>
    <main>
      <h1>W is for Web</h1>
      <div>
        <p>
          initial content <span
            data-more="read more"
            class="content-inline"
          > ... extra inline ... </span>
        </p>
      </div>
      <div class="content">
        ... extra content ...
      </div>
    </main>
  </body>
</html>

Following the “achievements unlocked“ with this version:

Finally, we could even skip the nice and clean DOMContentLoaded handler, and place the script right before the end of the body.

However, I like to think that if every loading page benchmark is based on the DOMContentLoaded deadline, and if this event is used in-line on top of the page, there’s no reason on earth to not place our little bootstrap logic right there, right?

As Summary

With 20+ years of Web technologies, even the smallest page could be rich in evil details.

However, beside style choices, little hacks that might just work, broken semantics no user might ever realize, there’s surely at least one lesson we have learned today:

if you want to deliver a message, on your page, and you want to reach every person on the planet, be sure that such message is crystal clean readable by default and add only after, eventually, and testing or feature detecting the right things, every special effect you might think would fit for such message.

Last, but not least, I hope nobody got actually offended, after all I’ve simply done some reporting and provided a PR for a little page that is not hosted in any public repository, but is publicly available and as such readable … well, if the more link works, of course ;-)

Thanks for reading.

Andrea Giammarchi

Fullstack Web Developer, Senior Software Engineer, Team Leader, Architect, Node.js, JavaScript, HTML5, IoT, Trainer, Publisher, Technical Editor