CSS, HTML, JavaScript

“The Walrus” Layout Reconstruction Exercise

This exercise is meant to get you thinking about transitions, menus, responsive states, layouts, and as always web typography.

First, please download the files you will need.

In it, you will build a layout that was used to great effect by the Canadian literary magazine The Walrus.

I’ve put in some of the HTML, but not much. You’ll need to add more. Feel free to modify any I have put in.

There are two big editorial images. The first acts like a feature image. I’ve already put these two, and two ads, into the HTML, but notice the spacing of the images and the text at the various breakpoints.

For the three links in the header, use HTML button elements, and Font-Awesome—the new version that loads SVG instead of icon fonts.

The fonts used are two from Google — Source Sans Pro and Playfair Display — and the Georgia, Times, etc font-stack.

Make all your layouts as close as possible to the ones shown in the screenshots.

I will add mobile menu screenshots at a later date, but this should be enough to work with for now.

All menu states have transitions (from off-screen to on-screen) that take place over .5s.

You can use grid, flexbox, etc. Do not worry too much about backward compatibility.

You will definitely need JavaScript for:

– the classList method, to add classes to elements such as the body

– at least one condition to test how many menus are active

WORK in groups of two or three. When you are done, make a TEXT FILE name “group-members” and include the names of each group member. Only one person per group needs to hand in the project: you will be marked for your group work.

Standard
CSS, HTML, JavaScript, Tools & Generators

ATOM Packages

A lot of good text editors, like Sublime Text and Atom, have the ability to easily install packages that will extend their core functionality.

Sometimes, people will make packages to replicate functionality found in other editors. For example, I am a longtime Sublime Text user. However, since Atom is free, I’ve started using it so students don’t feel compelled to buy Sublime Text. But there are a few features of Sublime that I miss in Atom. Fortunately, a number of packages allow me to add those features.

To install a package in Atom, do the following:

  • Bring up the Command Palette (command-shift-p on Mac, control-shift-p on Windows or Linux)
  • Type install and then select Install Packages And Themes
  • Search for the package you want to install
  • When you find one, press the INSTALL button

Here are some of the packages I always install:

  • Emmet: a brilliant, and essential, HTML/CSS macro utility.
    And here’s the official Emmet documentation and the excellent cheatsheet.
    Emmet practice exercise 1 | answer key | Emmet practice exercise 2
  • Open in Browser / Open in Browsers. There are number of packages that make testing the page in the browser quicker.
  • Col0r-Picker: command-shift-c (mac) or control-alt-c (win/linux) brings up a color picker
  • Atom-Live-Server: for testing dynamically generated pages
  • Atom-Beautify: for tidying up html, css, javascript, php, etc.
  • Auto-Update-Packages: does what you think it would do
  • Wrap in Tag: this replicates Sublime Text’s wrap in tag command. Which is really useful.
  • Linter: linters provide error messages. ( You will need to install language-specific linters in addition to this “base” linter )
  • Linkter-htmlhint: this provides HTML error messages on save (look at the bottom of the editor. Click for error panel.)
  • Linter-CSSLint: this provides CSS error messages on save (look at the bottom of the editor. Click for error panel.)
  • Linter-jshint: for JavaScript hinting on save (look at the bottom of the editor. Click for error panel.)
  • Pigments: A CSS color viewer. It adds a background color to all your color declarations. It even understands SASS variables and color-changing functions.

That’s just a few of the available useful packages.

Note, also, that most packages also have settings that can be configured from the Preferences / Settings menu.

 

Standard
CSS, HTML, JavaScript

JavaScript Shakespeare Exercise Solution

Since posting the original exercise, I have made a few changes to the look of the page, so if you download the completed version, do not be surprised that some features are different from those described in the exercise.

First, download the slim minified version of jQuery and add a link to it just before the closing of the body tag. Then add a link to a new .js file after the jQuery link:

<script src="js/jquery-3.1.1.min.js"></script>
<script src="js/shakespeare.js"></script>

Planning the Script

Before coding anything we need to figure out what the page actually needs to do. Each component of the task can be coded as a function for invocation in the (document).ready() handler as well as in other functions.

Here are most of the planned functions:

  • Sliding instructions panel up when it is clicked
  • Sliding instructions panel down when button is clicked
  • Hiding quotes and titles
  • Selecting and displaying a random quote on click of genre panel
  • Displaying the titles in the clicked genre panel
  • Styling the active panel and titles
  • Comparing clicked title to title of random quote
  • Updating score
  • Generating random insult
  • Displaying random insult

The Instructions Panel

As noted above, the instructions panel will slide up when it is clicked, and slide down when the instructions button is clicked. When down (as at page load), it will take up the full width and height of the screen.

So first let’s style it.

/* INSTRUCTIONS SECTION ======================================= */
.instructions-text {
	color: white;
	background-color: rgba(0,0,0,.85);

	font-size: 1.5rem;
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;

	display: flex;
	flex-flow: column nowrap;
	justify-content: center;
	align-items: center;
}

.instructions-text:hover {
	cursor: pointer;
}

.instructions-text ul {
	display: flex;
	flex-flow: column nowrap;
}


By using four values on the positioned instructions, we can get it to cover the entire width and height of the browser window. The flex values are to center the text horizontally and vertically. By setting the cursor to pointer, we provide a hint that the panel is clickable (when the user wants to hide the instructions).

Now lets add the jQuery, beginning with the $(document).ready() handler: the function you will start most jQuery scripts with. I like to keep that handler clean by making functions of separate tasks and then calling them from the ready() handler, but there are other ways to do it.

$(document).ready(function(){	
	showHideInstructions();
});

function showHideInstructions(){
	$('.instructions-text, header button').on('click', function(){
		$('.instructions-text').toggleClass('instructions-toggled');
	});
}

The “on” method is the jQuery way of adding an EventListener (if you’re familiar with “vanilla” js). The first element passed to that method is the event to listen for (passed as a string), then the function that happens when that event is triggered.

Here we have added an EventListener to each of two items (the instructions panel and the button in the header). The toggleClass method does what you might expect: adds a class if the targeted element does not have it, and removes it if it does.

Obviously, though, we need here to add the instructions-toggled class to our stylesheet.

.instructions-toggled {
	transform: translateY(-100%);
}

This transformation will move the panel up the Y-axis 100% of its height, hiding it off screen.

Test it in the browser.

You will see that the menu disappears when clicked, and reappears when the button in the header is clicked. However, we need to make a transition so the change is not instaneous. Although jQuery has transition methods, it is typically more processor-friendly to apply transitions via CSS. So add the following to your .instructions-text style: transition: transform .5s;

Test again: the panel slides up and down when the panel or the button are clicked.

Hiding Titles and Quotations

If you examine the HTML I gave you, you will see that I have put the play titles and associated quotes in a Description List (DL), with the titles marked up as DT elements and the quotes as DD elements. Hiding them is again as easy as selecting them and toggling a class:

function hideAll(){
	// hide all quotations and titles
	$('dd, dt').addClass('hidden');

	// clear out quotation box
	$('.quotation').text('');
}

If you know JavaScript, you will no doubt note here that you do not need to loop through the returned node list to toggle the classses (if you don’t know JS, that comment won’t make sense, of course).

If you further examine the markup, you will see that I have put an empty quotation DIV in each panel, between the heading and the DL. Later when we generate the random quote, we will put the quote into that DIV, but in this hideAll() function, we will insert an empty string.

The reason we will do this is so that the box empties of text every time the function is invoked. That way, whenever we generate a new random quote for example, it won’t be added to the previous quote).

Now add this function to the (document).ready() handler:

$(document).ready(function(){	
	showHideInstructions();
        hideAll()
});

Test your page in the browser. The main panels should now only show the genres (HISTORIES, TRAGEDIES, COMEDIES, PROBLEM PLAYS).

Showing Titles and Random Quotation in One Panel

When a user clicks on a genre panel, it needs to “open” and show the titles and a random quote. As well, any open panels must close.

Each genre panel has a class of menu-section. We will attach an EventListener to each via the jQuery on method.

When a panel is clicked, the handler will first of all invoke the hideAll() function we wrote earlier. This will close the panels, which means that when we open the clicked one, we won’t have two open.

Then we save in a variable a reference to the panel that was clicked: the $(this) construction saves a reference to the target of the click event. By wrapping the JavaScript THIS in $(), we convert it into a jQuery object, which means that we can use a range of jQuery methods on it.

For example, in the next line, we add a class to the clicked panel, so that we can use styles that apply to the currently-selected panel. As well, with the $(this) object (saved here as genre), we can search within the active panel (using the <b>find</b> method) for DTs, which hold the play titles.

Having returned a jQuery list of DTs in this clicked panel, we can then toggle the HIDDEN class on these DTs only. This means, in short, that the DTs in the other panels will remain hidden, while those in the clicked panel will now be visible.

function onGenreClick(){
	$('.menu-section').on('click', function(){
		
		hideAll();

		var genre = $(this);

		// FOR STYLING PURPOSES, we can then a use descendent selector 
		// to style the titles.
		genre.addClass('active');
		
		// Make TITLES visible in this panel only
		genre.find('dt').toggleClass('hidden');

                // Check how many titles there are
		var titleNum = genre.find('dt').length;

                genre.find('.quotation').text("sample text");
		// genre.find('.quotation').text(randomQuote(genre, titleNum));

	});
}

Add the onGenreClick() function to the document ready handler.

Test the file.

Everything should work. If something does not work, go to the Console in the Inspector and see if you can work out what the error message is telling you.

Obviously, we will need to set our  hidden class to display: none.

As well, if you look in the QUOTATION div, you will see that we have just written the words sample text into the quotation box.  (Using the text() method, incidentally, we can set text and we can get text.)

Delete the line that is writing the text into the quotation DIV, and uncomment the line after it.

Now when you test the file, you will definitely get an error. The reason is in the last part of this function. Instead of writing sample text we will write a random quote by passing a function called randomQuote to the text() method.

The problem, however, is that we have not yet written that randomQuote function.

Generating the Random Quote

If you look at the reference to the randomQuote function in the code above, you will see that inside the brackets there are two variables being passed to the function: genre and titleNum.

The first, genre, is the saved reference to the clicked panel itself. The second is the number of DTs inside that panel, which we determined using the length property.

So here is the actual function:

function randomQuote(genre, titleNum){

	var randomNumber = Math.floor(Math.random() * titleNum);
	var theQuote = genre.find('dd').eq(randomNumber).text();
	
	return(theQuote);
}

In the first line inside the function, we generate a random number between 0 and .999999 (etc) which we then multiply by the number of titles passed in via the titleNum variable. Then we use Math.floor() to round the number down.

The result is that our random number will be between 0 and (the number of titles minus 1). This is useful, because the jQuery set of returned elements is indexed like an array (starting at 0).

The logic of the line before the return function is this

  • within this panel (genre), find all the DD elements
  • with this list of DD elements, chose the one whose index is the same as the random number. ( EQ(number) will return the element at the specified index (location) in a set.)
  • get the text from inside the randomly selected DD

By passing the theQuote to the return function, we pass the quote back to where the randomQuote was called. That was in the previous function.

Note here, also, that we do not invoke this randomQuote function inside the $(document).ready() handler. The reason is that this function does not need to run immediately on page load. Rather, it is invoked by another function when needed.

Making the Titles Clickable

We now need to make the titles (which reside inside DTs) clickable.

To do that we will add the following code to our script:

function onTitleClick(){
	$('dt').on('click', function(evt){

	// Prevent CLICK event from BUBBLING UP and triggering the section LINK
	evt.stopPropagation();
	
	var title = $(this);

	// Find the text of the next nearest sibling of the title DT.
	var associatedQuote = title.next().text();

	// Find what is in the QUOTATIONS box right now.
	var visibleQuote = title.parent().parent().find('.quotation').text();

	if ( associatedQuote == visibleQuote ){
		shakespeareScore++;
		shakespeareAttempts++;
	}
	else {
		shakespeareAttempts++;
	}
	
	updateScore();

	});

}

Add a reference to this function to the document ready function. It should now look like this:

$(document).ready(function(){
	
	showHideInstructions();
	hideAll();
	onGenreClick();
	onTitleClick();
});

Test the page. A number of errors will be flagged in the console.

Obviously, we have not yet written the updateScore function, so just comment it out for now.

As well, when we test whether the quotation has been identified correctly, we increment one or both of two variables: shakespeareScore and shakespeareAttempts. Those variables do not yet exist. To remedy that, add the following code above the document ready handler:

var shakespeareScore = shakespeareAttempts = 0;

Now the script should work.

The most important part of the above script is the evt.stopPropagation() code. What is happening here is that we are passing the event object (you can call it anything you like, like any variable, but it is usually called e or evt) and using stopPropagation with it.

This solves a frustrating problem: the DTs are clickable, but they are inside another element (the genre panel) which is also clickable. The problem is that a click event “bubbles up”: the click will trigger the event handler attached to the DT, but also the event handler attached to the .menu-section (genre panel). This means that clicking a title will trigger a new random quote.

stopPropagation, in other words, will prevent a click on a child element from also applying to any of the child’s ancestors.

Because of the structure of our HTML (DT + DD), the use of the next() method (along with the text() method, of course) will return the quote associated with the clicked title.

In a similar way, we will travel up the DOM from the clicked DT to the parent DL then the parent .instructions-text DIV. This will allow us to search the text of the QUOTATIONS DIV in order to compare title choice to actual title. ( We could have just stored that information in a variable, of course, which would be more efficent…)

Then we test to see if our two strings (guess title text vs actual title text) match. If so, the user gains a point.

Which means that it is time to write the updateScore() function.

Updating the Score

This is easy:

function updateScore(){
	$('.score span').text(shakespeareScore + '/' + shakespeareAttempts);
}

This function does not need to be added to the document ready handler, as it is invoked by another function (and we do not need it to run immediately when the page is fully loaded).

Test the page. Hopefully everything is working as expected.

The Insult Generator

To make the insult, we will write a function that when triggered does the following things:

  • takes a random adjective from the adjectives array
  • takes a second random adjective from the adjectives array
  • takes a random noun from the nouns array
  • joins these together in a sentence
  • displays that sentence on the page

Since we will need to generate random numbers multiple times, we will make a function that does this when invoked and passed the array length.

The generation itself is pretty easy: since items in an array can be accessed by number enclosed in square brackets, if we call our randomize function, passing it the length of the array itself, we will get a random word.


function generateInsult() {

var insult = "";

var adjectives = ["artless","bawdy","beslubbering","bootless","brutish","churlish","cockered","clouted","craven","currish","dankish","dissembling","droning","errant","fawning","fobbing","froward","frothy","gleeking","goatish","gorbellied","impertinent","infectious","jarring","loggerheaded","lumpish","mammering","mangled","mewling","paunchy","pribbling","puking","puny","quailing","rank","reeky","roguish","ruttish","saucy","spleeny","spongy","surly","tottering","unmuzzled","vain","venomed","villainous","warped","wayward","weedy","yeasty","base-court","bat-fowling","beef-witted","beetle-headed","boil-brained","clapper-clawe","clay-brained","common-kissing","crook-pated","dismal-dreaming","dizzy-eyed","doghearted","dread-bolted","earth-vexing","elf-skinned","fat-kidneyed","fen-sucked","flap-mouthed","fly-bitten","folly-fallen","fool-born","full-gorged","guts-griping","half-faced","hasty-witted","hedge-born","hell-hated","idle-headed","ill-breeding","ill-nurtured","knotty-pated","milk-livered","motley-minded","onion-eyed","plume-plucked","pottle-deep","pox-marked","reeling-ripe","rough-hewn","rude-growing","rump-fed","shard-borne","sheep-biting"];

var nouns = ["apple-john", "baggage", "barnacle", "bladder", "boar-pig", "bugbear", "bum-bailey", "canker-blossom", "clack-dish", "clotpole", "coxcomb", "codpiece", "death-token", "dewberry", "flap-dragon", "flax-wench", "flirt-gill", "foot-licker", "fustilarian", "giglet", "gudgeon", "haggard", "harpy", "hedge-pig", "horn-beast", "hugger-mugger", "jolthead", "lewdster", "lout", "maggot-pie", "malt-worm", "mammet", "measle", "minnow", "miscreant", "moldwarp", "mumble-news", "nut-hook", "pigeon-egg", "pignut", "puttock", "pumpion", "ratsbane", "scut", "skainsmate"];
var firstAdjective = adjectives[randomizeMe(adjectives.length)];
var secondAdjective = adjectives[randomizeMe(adjectives.length)];
var noun = nouns[randomizeMe(nouns.length)];

console.log(firstAdjective, secondAdjective, noun);

}

function randomizeMe(arg){
var randomNumber = Math.floor(Math.random() * arg);
return(randomNumber);
}

Test the page and go to the console to see if you are getting the three words output.

Modify the Function For Grammar

Add the to our generateInsult function, replacing the console.log operation.


var article = "a";

insult = "Thou art " + article + " " + firstAdjective + " " + secondAdjective + " " + noun + ".";
console.log(insult);

The reason I have turned the grammatical article into a variable will be become apparent if you go to the console and reload the page a number of times. Eventually, you will generate a firstAdjective that begins with a vowel. At that point, you will need to change the value of article to <b>an</b>.

The following code will do that.

The first line gets the first character of the firstAdjective variable. The next line tests if it is vowel. If so, it changes it.

var firstLetter = firstAdjective.charAt(0);
if (firstLetter == "a" || firstLetter =="e" || firstLetter == "i" || firstLetter == "o" || firstLetter == "u" ) {
article = "an";
}

(Make sure that you add the code before the line that creates the full sentence, so the sentence uses the correct article.)

Reload the page a number of times and check that the correct article is ending up in the sentence.

Wire Up the InsultMe Button

If we look in the footer of the document, you will see a button with an ID of <b>insult-trigger</b>and a DIV with an ID of <b>insult</b>. Let’s wire up that button so that it outputs the insult when clicked.

First change the generateInsult function to add a return(insult) in the last line.

Then add the following.


function onInsultMeOnClick() {
$('#insult-trigger').on('click', function(){
$('#insult').text(generateInsult());
});
}

Finally, now add the onInsultMeOnClick() function to the document ready handler.

Test your page.

Hopefully everything is working.

Download the solution file. 

 

 

Standard
CSS, HTML, JavaScript

Flex Table Sort Solution

In order to use the power of Flex for this exercise, it is very important to remember one thing:

because Flexbox is all about the relationship between parent and child elements, we need the tbody to be included  in this table. The “sort” will involve shuffling the order of the tr elements inside the tbody. Without the tbody, if we use table as the flex parent, our header row will move when we click buttons.

Building the Table

I am omitting here a bunch of rows from the tbody to save space, but here is our initial table layout. Hopefully, you used Sublime Text or Atom selection efficiencies to build it.

<table>
    
    <thead>
        <tr>
            <th>Term</th>
            <th>Prime Minister</th>
            <th>Party</th>
        </tr>
    </thead>

    <tbody>
        <tr data-party="conservative">
            <td>1867–1873</td>
            <td>Sir John A. Macdonald</td>
            <td>Conservative</td>
        </tr>
        <tr data-party="liberal">
            <td>1873–1878</td>
            <td>Alexander Mackenzie</td>
            <td>Liberal</td>
        </tr>
       
        <tr data-party="liberal">
            <td>2015–</td>
            <td>Justin Trudeau</td>
            <td>Liberal</td>
        </tr>

       <!-- more rows here -->

    </tbody>

</table>

You will likely note the use of the data- attribute here. This html5 attribute allows you to add attribute names of your choice, as long as they start with data-. Here I have used party as the second part of that attribute. However, for this exercise, you could just as easily use a class attribute to do this.

And here is how we can code the buttons in each of the two table headers that require them:

<th>Term
<button id="termsort">
    <img src="images/maple-leaf.svg" alt="Sort">
</button>
</th>

<th>Prime Minister</th>

<th>Party
<button id="partysort">
    <img src="images/maple-leaf.svg" alt="Sort">
</button>
</th>

Styling the Table

To save space and time, I will discuss only some of the styling considerations here. However, the completed file contains all the CSS required to produce the desired effects.

To stripe the table, we need to give the TR element a background-color and then use the nth-of-type selector to make even or odd rows lighter or darker than the tr style:

tr {
  background-color: #ddd;	
}
tr:nth-of-type(even) {
	background-color: #eee;
}

To style the button, we could do something like this:

button img {
   width: 16px;
   height: 16px;
}

button {
   margin-top: 10px;
   height: 20px;
   width: 40px;
   background-color: transparent;
   border: none;
   float: right;
}

button:hover {
   cursor: pointer;
}

button:focus {
   outline: white;
}

Making the Buttons Work

If we leverage Flex for this task, the JavaScript required becomes quite minimal. Since both aspects of the task really just involve changing the order of child rows of the tbody, all we need to do is toggle classes on the tbody in response to click events:

var btnTerm = document.getElementById('btn-term');
var btnParty = document.getElementById('btn-party');
var tBody = document.getElementsByTagName('tbody')[0];

btnTerm.addEventListener('click', function(){
	tBody.classList.toggle('term-sort');
});

btnParty.addEventListener('click', function(){
		tBody.classList.toggle('party-sort');
});

Test your page, click each button, then use the Inspector to make sure that your classes are indeed being added:

Now you can add the CSS that does the rearranging of the rows. First of all, we make the tbody a flex parent, so we can then arrange the rows how we want them to go when the buttons are clicked:

.term-sort {
	flex-flow: column-reverse nowrap;
}


.party-sort tr[data-party="liberal"]{
	order: 1;
	background-color: #D71920;
	color: white;
}

.party-sort tr[data-party="conservative"]{
	order: 2;
	background-color: #1A4782;
	color: white;
}

.party-sort tr[data-party="unionist"]{
	order: 3;
	background-color: #99f;
	color: white;
}

Test the page. Click one button, then the other. We soon discover that our page is not behaving quite the way we want it to. Specifically, when the Prime Ministers are grouped by party, clicking the TERM button does not remove the party grouping (and it is therefore making nonsense of the sort-by-term option).

To fix that, add two conditions to your event handlers:

btnTerm.addEventListener('click', function(){

	if (tBody.classList.contains('party-sort')){
		tBody.classList.remove('party-sort');
	}

	tBody.classList.toggle('term-sort');
});

btnParty.addEventListener('click', function(){

	if (tBody.classList.contains('term-sort')){
		tBody.classList.remove('term-sort');
	}
	
	tBody.classList.toggle('party-sort');
});

Responsive Strategy

And here is how I would approach the responsive strategy. Here I am not doing a Mobile First layout because the assignment made the phone layout a bonus question rather than part of the main exercise.

@media screen and (max-width: 600px) {
	
	th {
		display: none;
	}

	tr {
		display: flex;
		flex-flow: column nowrap;
		margin-bottom: .75rem;
	}
	/* Create FLEX relationship in the TD so we can align each TD's
	generated and original content nicely. */
	td {
		display: flex;
		width: 100%;
	}

	/* Generated content to compensate for hidden table headers */
	tr td:first-of-type:before {
		content: "TERM: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

	tr td:nth-of-type(2):before {
		content: "PM: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

	tr td:nth-of-type(3):before {
		content: "PARTY: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

}

There are still one or two things to clean up here, but this much should definitely give you an idea of ways to approach the task.

Download a completed version of the file.

Standard
CSS, JavaScript

Flex Table Sort Exercise

First of all, please download this file and double click it to unpack it.

In this exercise, you will make a sortable table using your knowledge of CSS Flexbox and the JavaScript classList method.

You can use the Internet or your notes. I would recommend the CSS Tricks Guide to Flexbox and the Mozilla Developer Network’s discussion of the classList method, but you may have other sources you prefer.

 

Task One: Initial Table SetUp

Inside the ZIP archive you’ve downloaded are two files: prime-ministers.html and maple-leaf.svg. As well, there is a folder of screenshots (the same as are in this page).

The HTML file is a list of Canadian Prime Ministers.  In this part of the exercise, you will turn it into a table.

In the table, you will need to use the following tags:

  • table
  • thead
  • tbody
  • tr
  • th
  • td

In the HTML file, the table headers and rows are identified by HTML comments.

 

Set up and style the table as in the screenshot below. Use advanced selectors to stripe the table.

There are maple leaf images in two of the three table headers (the first and third). Each needs to be wrapped inside a BUTTON element. You will need to scale the SVG down with CSS (as well as style the BUTTON and the rest of the table, of course).

Initial Table Setup

Task Two: Sort By Term

Add JavaScript that does the following:

  • when the user clicks the term maple leaf button, the order of Prime Ministers reverses
  • when the user clicks the term maple leaf button again, the Prime Ministers return to their original order

Sorted By Term

Task Three: Sort By Party

Add the following functionality to your JavaScript:

  • when the user clicks the party maple leaf, the candidates are sorted by party, and their rows are colored as below. The parties will appear in this order: Liberal, Conservative, Unionist.
  • when the user clicks the party maple leaf again, the page returns to its original order and appearance

Sorted by Party

Marks

  • Initial Table Setup: 6
  • Term Sort: 7
  • Party Sort: 7

Bonus Marks

For 5 bonus marks, alter your code to make sure that this never happens, no matter what the user does. Obviously, you will only get this bonus if you did Tasks Two and Three.

Confused Sort

And for 5 more bonus marks, use media queries to hide the table header and makes a phone layout like this. Obviously, we lose the sort functionality here, but it’s just an exercise…

 

 

Standard
CSS, HTML, JavaScript

Responsible Responsive Design: WebP Images

The largest contributor to file size on nearly every website is the images, so in trying to make our websites perform better, it’s a very good idea to start there.

As always, there’s a fine line between performance and visually compelling work, but we do a a lot with less if we consider some of the following ways to minimize our file sizes:

  • icon fonts
  • svg
  • svg sprites
  • gradients rather than images
  • pixel-density media queries targets
  • webP graphics

Elsewhere on this site, there are exercises on icon fonts, svg, svg sprites, and gradients. This exercise will focus on the last of those listed: the webP file format.

You might not have heard of webP, but it has lots of potential—for both lossy and lossless compression.

Developed by Google and released in 2010, webP is, at the time of this writing, supported by about 72% of browsers worldwide (and oddly enough, by one 50% in Canada).  [ Here’s caniuse.com’s latest stats for the format. ]

Support right now is Chrome, Android Browser, and Opera. According to caniuse.com, Apple and Mozilla are currently experimenting with the format for Safari and Firefox.

Please download the files we’ll use for this exercise.

Once you unpack the files you’ve downloaded, look inside the image-formats folder: it has a folder of jpegs and two folders of the same images saved in webp format (with different quality settings). Compare the file sizes.

You’ll note that WebP allows us to cut file sizes by 30 to 50% or more.

How to make WebP images

Photoshop doesn’t natively support WebP, but you can get a plugin to add that support.  I haven’t tested it in later versions of Photoshop, though, so you’ll have to do that. The interface of that plugin isn’t particularly great, anyway, and you’d have to set up a batch process if you wanted to convert multiple files (which isn’t hard, but still kind of annoying).

A better solution is XnConvert, which is free for Mac, Windows, and Linux. It actually supports over 500 file formats so it’s a good utility to have anyway.

There are also a number of command line tools you can use.

If you use build tools, you can also add gulp or grunt packages to automatically process your images into WebP on Save.

Make A Test Page

Make a new HTML page. If you look in the images folder you will see that the file names are sequential, so that will make using Emmet to put all 15 in the page, using two formats, easy.

Here’s the syntax. You will no doubt notice the use of SOURCE (which you may remember from the VIDEO element) and SRCSET (which also works with the IMAGE tag).

<picture>
<source srcset="image-formats/webp/q80/image-01-q80.webp"  type="image/webp">
<img src="image-formats/jpg/image-01.jpg" alt="jpg and webp demo">
</picture>

What happens here is that if the browser supports WebP, it will download that image and skip the jpg. If the browser doesn’t support the WebP format or doesn’t understand the PICTURE element, it will download the jpg as a fallback.

To test that, load your page in Chrome, then load it in Safari, FireFox, IE, or Edge.

In Chrome, you will see images that have WebP80 written in the top right. In the other browsers, you will see that JPEGs are loaded.

A win-win situation.

To squeeze even more file size savings, change the link to the WebP images: use the lower quality ones. See if you can spot a difference.

Using WebP In CSS

As noted, with the SOURCE element nested inside the PICTURE element, the browser gets to pick which file to download.

In CSS, it’s a bit more complicated. There is no CSS equivalent of the SOURCE element.

Modernizr to the rescue

Fortunately, a famous javascript utility called Modernizr will help us target the right browser. Modernizr allows us to test whether a browser supports almost anything.

To get it, go to Modernizr.com, click Add Detects, and search for WebP. You’ll likely see four. Click on three: all but animation. ( Yes, I know the screenshot below has animation selected too… )

Modernizr

Once you’ve added what you want to detect, click BUILD and then download the script. Put a to it (using the <script> tag) in the <head> of your document: we typically put links to scripts in the bottom of the document, but with Modernizr you want it to load before the elements we’re testing for.

Test your page. Inspect it. Take a look at the HTML element, in particular:

Modernizr Classes

In this screenshot from Chrome, we see that a bunch of additional classes have been added to the HTML element.

Now inspect it in another browser, like Safari (to use the inspector in Safari, go to Preferences > Advanced > Show Develop Menu first):

Safari Inspector

If we look at the HTML element in the Safari inspector, we see added classes, but these have no-prepended to the classes.

Target CSS Elements Using Modernizr Classes

We can now use WebP and JPEG as background images, for example:

.webp body {
background-image: url('image-formats/webp/q80/image-01-q80.webp');
}

.no-webp body {
background-image: url('image-formats/jpg/image-01.jpg');
}

Your Remaining Task

That still creates a problem. We want to:

  • serve only one image to any browser
  • still serve a background image if JavaScript is not available

Find a solution and show me during the lab.

Standard
CSS, HTML, JavaScript

HTML5 Video Custom Controller

In this exercise, we’ll go over how to add a custom controller to an HTML5 video element, while making sure we don’t disadvantage users without JavaScript.

First, please download these files. Unzip the package and open the folder up in your code editor.

You will see that I’ve given you an index file, and css, video, and javascript files (in their respective folders). In order to keep the download small, I haven’t included the non-mp4 video fallback formats that are referenced in the video code itself.

CSS Preparation

In the CSS file are a couple rudimentary styles for text and responsive video. We’ll add to them here.

The HTML file starts simple, too. Test it in a browser. Resize the page to check that the responsive code is working.

Video screenshot

Build the HTML Components

We will use JavaScript to turn off the native controller, but for now, turn it off by removing the controls attribute from the video element in the HTML. This will make it less obtrusive as we test our work.

After the video element but before the close of the .video-container element, add the following code



<div class="video-controls" id="video-controls">
   <button id="btn-play-stop" class="btn-play-stop">Play</button>
   
   <div class="progress-container">
     <div id="progress-value" class="progress-value"></div>
   </div>

   <div id="scoreboard" class="scoreboard">0%</div>

</div>
<!-- .video-controls -->

We are, in other words, making a box for the controller, a PLAY button, a box (.progress-container’) with another box inside it which will act as a slider element, and a box (.scoreboard) in which we will update the percentage of the video played.

An Aside…

You might be wondering why I am not using the HTML5 Progress element for the slider. It’s perfectly reasonable to do so, and perfect semantic. However, it is very difficult to style the progress element consistently cross-browser, so I’m making something that will look like a progress bar. We lose a bit of semantics here, but gain greater CSS control.

You might also wonder why I’m using IDs here. It just makes it easier to select the element knowing that it is the only one in the page. However, if I had multiple videos on the page, I would have to use classes, or different IDs. However, since IDs trump classes in the CSS cascade, if I do all my styling with classes rather than IDs I won’t have to worry about that issue.

Style The Controller

We are going to want the controller to look like this:
Video Controller Screenshot

 

Add the following code to your stylesheet. ( Don’t worry if there are minor differences in appearance ).

/* Video Player =========*/

.video-container {
   position: relative;
}

.video-controls {
   width: 70%;
   background-color: rgba(0,0,0,.5);
   
   position: absolute;
   bottom: 10px;
   left: 15%;
}

.btn-play-stop, .progress-container, .scoreboard {
   
   border: 1px solid rgba(255,255,255, .6);
   height: 20px;
   width: 9%;
   margin: .5%;

   float: left;

   box-sizing: border-box;
}

.progress-container {
   width: 79%;
}

.progress-value {
   width: 20%;
   height: 100%;
   background-color: rgba(255,255,255,.2);

}

.btn-play-stop {
   background-color: black;
   color: white;
}

.btn-play-stop:hover,  .btn-play-stop:focus{
   color: white;
   outline: #f50 solid 1px;
   border: #f50 solid 1px;;
   background-color: rgba(0, 0, 0, .6)
}


.scoreboard {
   color: white;
   font-size: 15px;
   line-height: 20px;
   text-align: right;
   padding-right: 6px;
}

What we are doing here is the setting the .video-container as the positioning content for the controller, then putting the controller almost at the bottom of the video-container. The rest of the code should make sense.

If you’re not seeing the background-color on the .video-controls element, remember that its children are floated, so you need to add the clearfix class to it (clearfix is included in the stylesheet).

We will also change the width of the progress-value element. We’re giving it 20% here so we can see what it will look like in action.

Finally, in the HTML file add the controls attribute back to the video element. Remember that it is a single-word attribute rather than an attribute= “value” type of syntax.

So now we see our custom controls sitting on top of the “native” controls:

Both sets of controls

Add The JavaScript

So now, let’s turn to the JavaScript. The downloaded HTML file has a link to the blank JS file: video-controls.js. Open that file.

First, let’s select the elements we want to work with and store each in a variable, then remove the video player’s native controls:


var videoPlayer = document.getElementById('video-player');
var btnPlayStop = document.getElementById('btn-play-stop');
var progressValue = document.getElementById('progress-value');
var scoreboard = document.getElementById('scoreboard');

videoPlayer.controls = false;

Now let’s wire up an EventListener for our button:


 btnPlayStop.addEventListener('click', function(){

	if ( videoPlayer.paused || videoPlayer.ended ) {
		videoPlayer.play();
		btnPlayStop.innerHTML = "Stop";	
	}
	else {
		videoPlayer.pause();
		btnPlayStop.innerHTML = "Play";	
	}
	
	videoPlayer.classList.toggle('playing');

	});

You will probably want to build the above in stages, but I’ll explain what’s happening. We want the button to be multi-functional: if the video is playing, the click event should stop it, but if it is paused or completed, the click event must start playback. The button text must change to reflect what will happen when the button is clicked.

If you’re wondering about which methods are available, consult the Mozilla Developer Network page on the HTMLMediaElement API.

The last line in the script will toggle a class on and off the videoPlayer using the classList property. ( We might or might not use this later in the exercise….)

So test the page. You should see that the button text does change and the video does play or stop on click.

Make the “Progress” Slider Work

To make the slider work, we will use the timeupdate event, which fires when the currentTime property is updated. Every time the browser updates that value, we will determine what percentage of the entire video (videoPlayer.duration) has been played. Then we will update the scoreboard and increase the width of the progress-value div.

 

videoPlayer.addEventListener('timeupdate', updateProgress);

function updateProgress() {

var percentPlayed = Math.round( ( videoPlayer.currentTime / videoPlayer.duration ) * 100 ) + "%";

scoreboard.innerHTML = percentPlayed;
progressValue.style.width = percentPlayed;	

if (percentPlayed == "100%"){
   btnPlayStop.innerHTML = "Play";
}

}

The last part tests if we’ve reached the end of the video: if so, it changes the button text.

Additional Things We Could Do

Ways we could refine this rather basic controller:

  • Add Volume controls
  • Add a FullScreen button
  • Leverage the canplaythrough event, to see if enough data has downloaded to allow uninterrupted playthrough. Display a “loading” animation while waiting for that event to fire.

More on the HTMLMediaElement API.

I will add to this discussion at a later date, but at this point I have time only to add a final feature: showing and hiding the controller.

Hiding the Controller

In your CSS, change the bottom value on the .video-controls to a value like -30px. This will push the controller beyond the bottom edge of the video. To hide it, add overflow:hidden; to your .video-container style.

Now add the following CSS:


.video-container:hover .video-controls, .video-container:focus .video-controls {
   bottom: 10px;
}

.video-controls:blur {
   bottom: 30px;
}

This means that when a user hovers over the video, the controller will be displayed. As well, if a user tabs to the video, the controls will display (the focus state). Finally, when the video loses focus (either by the mouse no longer hovering over the video, or by the user focussing elsewhere via keyboard events).

Of course, we could also add transition: bottom .5s to our .video-controls class, in order to smoothen the transition between the two states.

Here, though, you may encounter an interesting issue: a strange effect wherein the controller appears just a tiny bit before it’s over the video.

To see why, put a border on the video-container element. Now look closely at the bottom border: notice how the video isn’t coming right to the border?

Video Border Issue

This effect also happens with images.

The solution?

Add Display: block; to your video style.

 

Standard
CSS, HTML, JavaScript

HTML Video Embedding

Two common ways to put video into a webpage are using a hosted service like YouTube or using the HTML5 video element.

There are advantages to each method, which we’ll discuss in each section.

Using YouTube or Vimeo

Using YouTube, Vimeo or other services is easy, as they will supply the code to embed in your own document. On YouTube, find the video you want to use, then click on Share underneath the video window and then choose Embed.

YouTube Embed

Copy the code that’s selected above. If you want additional options, click on the SHOW MORE link.

YouTube Embed Options

This will allow you to change the size, or turn off the suggested videos feature at the end of the video, etc.

If you paste the iframe code into your html document, you’ve added the video to your page.

Making YouTube or Vimeo  Content Responsive

The only problem with this process, however, is that by default the YouTube or Vimeo embed isn’t responsive, as the screenshot below demonstrates:

YouTube In Page, Not Responsive

 

One might think that the solution would be similar to that used for responsive images: setting the iframe’s max-width to 100%.

This will not work, however. The trouble is that without a specified height value (and “auto” doesn’t work), a browser will make the iframe 150px tall. After adding that code, we see in the screenshot below that the width changes but the height does not keep the aspect ratio.

YouTube-Display Issue

 

The solution is described in a number of places, including CSS Tricks.

In a nutshell, you absolutely position the iframe inside another element like a DIV, then use padding to increase the height of the container element (DIV, in this case) as its width increases.

In the code below, that DIV is given a class of videoWrapper:

.videoWrapper {
	position: relative;
	padding-bottom: 56.25%; /* 16:9 */
	padding-top: 25px;  /* for video controller */
	height: 0;
}
.videoWrapper iframe {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
}

You might wonder where the number 56.25% came from.

That’s derived from the aspect ratio of the video. By making the container the same aspect ratio as the video, and then making the the iframe the same width and height as the container, the video is made responsive.

If your video is in 16:9 format, this number will work. The video in the screenshots was 560 by 315.  If you divide the height by the width you get the percentage you need.

If your video has another aspect ratio, just do the math (height/width) and change the padding-bottom percentage.

Quite a clever solution.

Advantages of Hosted Video Services

  • Speed: YouTube’s CDNs will typically be faster than your own hosting. If your site is going to do a lot of video, you will need a more robust server infrastructure than you might ordinarily need.
  • Ease: Copy some code for the embed and the CSS to make it responsive and you’re pretty much done.
  • Compatibility: YouTube will serve the content in a form the browser wants it in.

Advantages of Hosted Video Services

  • If you put your video on YouTube, you have less control over how it’s used. Other people can embed it in their pages (although that can be turned off in your YouTube account settings).
  • YouTube makes its money serving ads, so you might have them show up before your video plays.
  • Interface: if you want to use a custom interface, it’s more work than with HTML5 native video.

 

The HTML5 Video Element

Putting video on your page, without going the hosted route described above, has gotten easier with the introduction of the HTML5 video tag.

In its simplest form, it behaves like the image tag, except that it isn’t self-closing.

<video src="my_video.mp4"></video>

This will display the movie in the page. However, the movie won’t have controls, it won’t play automatically, and won’t be triggered to play (without javascript).

This means that at least one attribute is pretty much mandatory (unless we want to create our own interface elements): controls.

<video src="my_video.mp4" controls></video>

You might not have seen attributes specified in the manner before, but a number of boolean attributes in HTML5 are specified with single words rather than the traditional attribute= “value” format.

In HTML4, we would write things like controller = “true” or autoplay = “true” and loop = “false”, but in HTML5, the presence of the attribute gives a true value while the absence gives a false value.

An Example Video

Go to pexels.com and download a sample video. In the examples below, I’m using a time lapse of traffic around the Arc de Triomphe in Paris.

Make an HTML page and add the following code to the page (using the video you downloaded, of course):


<h1>HTML5 Video Example</h1>

<video src="videos/arc-de-triomphe.mp4" controls></video>

Test the page. You’ll see that the video is displayed and if you scroll down, you’ll see that the controller is there too.

However, the video isn’t innately responsive. Fortunately, our responsive images solution will work when it’s applied to the video tag. Add the following CSS to your styles:

video {
  width: 100%;
  height: auto;
}

Test the page. Resize it. The video should shrink and expand proportionately.

HTML5 Video - Responsive

Other Useful Attributes

Boolean Attributes

As noted above, with the boolean (true or false) attributes, we need only a single word.

  • autoplay: start playing automatically
  • muted: sound turned off
  • loop: video will replay automatically

Non-Boolean Attributes

With non-boolean attributes, we need to specify values for attributes

  • poster: a URL for an image to represent the video until it plays
  • preload: none, metadata, auto

For a good discussion of these and other attributes you can use, consult the Mozilla Developer Network page on the video element.

So, What’s The Problem?

The above example used a single video file, an h264-codec mp4 file.

If you read online discussions of the video element from a few years ago, you will learn that initially there was a lot of confusion over which video format to use, as different browsers supported different formats.

This has simplified considerably in recent years. If you search caniuse.com for video, you will see that nearly every modern browser supports  it. And happily, if you search for mp4 or the h264 video codec, you’ll see that most browsers will support it.

For the browsers that don’t support mp4, however, there are a couple approaches we can take:

Put A Download Link In The Code

If a person’s browser does not support the HTML5 video tag, we can leave a download link for them to get the video and watch it outside their browser


<video src="videos/arc-de-triomphe.mp4" control>

   Your browser does not support appear to support HTML5 video. However, you can download this video and watch it in your favorite video player:<br>
<a href="videos/arc-de-triomphe.mp4">Download video</a>

<video>

We can even go further, by encoding our video in other formats and then delivering them with multiple SOURCE ELEMENTS. This element, used with a SRC ATTRIBUTE (which might seem a bit confusing), allows us to specify multiple formats. This way, we can serve video to those browsers that don’t support mp4. The browser will play the first video it supports.

In the example files I’ve given you, there are videos encoded as webM (.webm) and ogg vorbis (.ogm) formats. I’ve used different videos for each format in order to demonstrate which is being loaded by which browser, but normally, we would use the same video encoded into multiple formats.


<video controls>
     <source="videos/arc-de-triomphe.mp4">
     <source="videos/stockholm-night.org">
     <source="city-night.webm">

    <Your browser does not appear to support HTML5 video. However, you can download this video and watch it in your favorite video player: <br>
    <a href="videos/arc-de-triomphe.mp4">Download video</a>

<video>

This might be overkill at this point, though: according to CanIUse.com, mp4 has about 94% support. It lists Opera Mini as the only major current browser being supported, but I know from testing that at least in iOS, even that browser will play mp4.

Things may change in future, however, as disputes over patents on the mp4 compression process play out.

If you need to convert between formats, one quick way to do so is to use the encoder at firefogg.org. The app must be used in Firefox to work, though.

To be posted shortly—part two: adding a custom controller to your video.

Standard
CSS, HTML, JavaScript

Basic Flexbox Form

For this exercise, you will produce a basic layout and wire up a very basic interactivity.

You will code all the HTML & CSS.

 

First of all, download the image you need for the exercise.

I am not specifying the font, but you must chose one that looks as close as possible to what’s used in the screenshots. This includes sizes (as seen most completely in the third screenshot).

Document Structure

When the document loads, the user should see a layout like this, which covers the entire height and width of the browser.

Layout with header, main, footer areas.

Layout with header, main, footer areas. Click for a very large example, if you need it.

There are three areas in the above layout. Combined, they take the entire height of the browser.

  • a header with a menu with four links (to the right). There is a border between links, but not at the beginning or the end of the menu.
  • a main content area with, as a background image, the image you downloaded above. This main area should take approximately 80% of the browser height. Depending on browser height or width, the user may see the photo more or less times.
  • a footer with a menu identical to the menu in the header

When the user clicks either contact link, a form should appear, fading in over 1 second, looking like this once fully faded in.

Screen Shot 2016-03-30 at 2.34.11 AM

Form revealed upon click of CONTACT link in menu. Click if you need a large example.

When the user types into the form, the size of the type must be close to the size in the screenshot below. When the user hovers over the SUBMIT button, it must turn salmon color (just use that color name) over 1 second.

Type size, hover effect. Click for (much) bigger example, if needed.

Type size, hover effect. Click for (much) bigger example, if needed.

Responsive Component

Under 600px, the form must arrange itself like this:

Screen Shot 2016-03-30 at 3.11.24 AM

Download Completed Exercise 

Standard
CSS, HTML, JavaScript

Midterm: WordPress Mockup Exercise Solution Part Six: Browser Support

Finally, let’s figure out what to do with respect to browsers which don’t support Flex—or support it badly.

Test In A Range of Browsers

First of all, let’s get a screenshot of the likely suspects.

Here’s a link to a range of screenshots generated using the crossbrowsertesting.com service. I’m not sure how long these links stay live, so if this link is broken, download this image of the screenshots. Hopefully the actual link works, though, because you can actually filter the results by OS, Browser, etc. Try chromeless and large, for example. Filter by OS, or browser, etc.

There are a number of such services—some paid, like crossbrowsertesting, and some free but typically more limited—that let you take screenshots of, or interact with, your pages running in a range of operating systems, devices, and browsers. The range of screenshots and testing options, combined with some very useful ways of filtering and/or downloading your data and screenshots, makes crossbrowsertesting an excellent service if you work on a lot of sites, use more cutting-edge techniques, or guarantee browser support minimums to clients.

Anyway, we see in the screenshot that our our layout is working quite well in a range of browsers. I work on a Mac, so testing Windows browsers, particularly IE, is important at this stage. Filtering for IE only produces these results:

IEtestScreenshots

Viewed at Full Size, we see these results (click for a bigger image of the screenshot):

CBTesting-IE-All-Full

So there are a few things to fix, here.

  • IE8-10 not recognizing Flex, or Flex-Basis in Articles
  • IE8-10 not recognizing Flex, or Flex Basis in Social Nav menu
  • Extra space after Article block in IE11
  • IE8 photo stretching

Let’s tackle them now.

First of all, let’s see if we can make IE10 behave.

Add Vendor Prefixes

With anything newish in CSS, you will encounter the use of vendor prefixes. These are additions to CSS properties (or sometimes, values) that browser makers add while the spec is in development.

Typically the vendors use these prefixes:

  • -webkit- (Safari, some versions of Chrome, newer versions of Opera.)
  • -moz- (Firefox)
  • -o- (Old versions of Opera)
  • -ms- (Internet Explorer)

The recommended pattern is to put necessary prefixes first, followed by the official spec, as below:


.some-thing-of-some-sort {
-webkit-transform: rotate(30deg);
-ms-transform: rotate(30deg);
transform: rotate(30deg);
}

So how to decide which prefixes to use?

You can of course look them up at caniuse.com, but that is exceptionally tedious. Instead, your best bet is to use utilities like AutoPrefixer, which will automate the use of vendor-prefixes, using data from caniuse.com.

Fortunately, there is an AutoPrefixer plugin for Sublime.  Before you install it however, install node.js if you don’t already have it ( to see if you have it, in Terminal type node –version ).

Once you’re ready to add prefixes to your CSS, in Sublime press Command-Shift-P to bring up the Command Palette.

The option to Autoprefix CSS should be right near the top. Choose it.

Another option, particularly if you’re a hardcore developer type, is to use build tools like gulp or grunt. Build tools are a better way, because they keep your CSS clean (compiling your CSS or SASS into stylesheets you upload but never edit). But setting one up is beyond the scope of this article.

A simpler online AutoPrefixer, although much less methodical, will also work for this exercise. If you’re Autoprefixing that way, once you’ve got your layout working in a modern browser like the latest Chrome, copy your CSS into the AutoPrefixer form at that web page, then copy the processed CSS back over your original CSS.

All the above ways of using AutoPrefixer will let you choose (either on the web page or via config options) how many browser versions to support, etc. Most people recommend going back two browser versions, as a lot of browsers (such as Chrome) will automatically update themselves now.

Whatever way you choose, make your CSS autoprefixed. Now test on your target browsers. Here’s a new view of our crossbrowsertesting.com array of IE screenshots. Notice how IE10 is now behaving? Its layout looks as good as that for the Edge browser, or for Chrome, etc.

IE10 Now Behaving

Now we have IE9 and IE8 to take care of, if we decide that we’re still supporting Windows XP.

We can target IE in a number of ways.

Because it’s more useful going forward (IE conditional comments have legacy application only, as they’re not used in modern Microsoft browsers), I’ll here focus on browser support detection.

Test For Browser Capabilities: Modernizr.js

Years ago, browser sniffing was the rage. The idea was that different code could be delivered to different browsers (as IE and Netscape, etc often had greatly different ways of rendering content). The modern practice is to test for capabilities. 

And the easiest way to do that is to use modernizr.js. At this website, you can select a feature, or features, that you wish to see if the browser supports. The modernizr website will create a script that when included in your web page will programmatically test the browser’s support for your selected features.

If a browser does support said feature or features, the modernizr script will add a class to the HTML tag. If the browser doesn’t support a feature, the script will add the same class (but prefixed with no- ).  For an example, look at this screenshot:

IE9-Modernizr-Classes-No

Here we see our page loaded in IE9 in a Virtual Machine, open to the IE Developer Tools. Notice, in the Dev Tools, the HTML tag: three classes ( no-flexbox, no-flexbox-legacy, and no-flexboxtweener ) have been appended by Modernizr.

This illustrates, in other words, how Modernizr enables us to deliver alternate or additional styles to browsers that don’t support whatever feature we want to test for.

More information on the various ways you can use modernizr’s feature detection.

Add Your Detects

At the modernizr.com website, click on the add your detects button. In the SEARCH field, type flex and select all four flex-related properties that are returned:

  • Flexbox
  • Flexbox (legacy)
  • Flexbox (tweener)
  • Flex Line Wrapping

If you click on any of these, you can find relevant information and history (and examples of usage) about the property.

If you wish to support IE8, make sure that you also click the html5shiv option at the side of the page. This makes IE8 correctly treat html5 block elements (such as Article, Header, Section, Footer, etc) as block elements, rather than inline elements (which can really bork a layout!)

Now click the big pink BUILD button.

When the dialog box comes up, choose Download in the first row (the build row, in other words).

Add this script to the <head> of your HTML page(s), not at the bottom of the page, like you might do with a lot of other scripts. 

Keeping in mind the classes added above, we can modify our CSS with the following code, targeting browsers (like IE9) that don’t support FLEXBOX (the first rule is actually for IE8, which doesn’t correctly treat HTML5 block elements, as discussed above).

 


/* FLEX FALLBACKS =============================== */

article, header, section, footer, figure {
 display: block;
}

.no-flexbox main article {
 width: 47%;
 margin: 1.5%;
 float: left;
 box-sizing: border-box;
 }

.no-flexbox .nav-social li {
 display: inline-block;
 float: left;
 margin: 1%;
}

.no-flexbox .nav-social ul {
 width: 190px;
 margin: auto;
}

.no-flexbox .site-banner {
 clear: both;
 margin: auto;
}


.no-flexbox #site-footer {
 clear:both;

}

 

Run your screenshot tests again. We should be getting closer:

IE9 Almost Behaving

It’s improved: the articles are now side by side. But IE9 and IE8 have some weird issue with the footer not clearing correctly: the black background is likely the result of the floated articles causing their container (main) to collapse.

Let’s remedy that by using clearfix on the container holding all the articles. First, copy the Nicholas Gallagher micro-clearfix code into your stylesheet, then add the clearfix class to the main element in the HTML:


<main class="content cf">

I would also clear the footer just to be safe. So here now is what the page should look like in IE9 (loaded on my Mac via Parallels Desktop):

IE9-MedRes-MostlyWorking

It looks good at first, but then when we scroll down, we find the annoying “float drop” that can occur if floated elements aren’t the same height (if you remember the GuitarMania exercise, an image being 1px shorter than other ones caused the issue there).

IE9FloatIssue

In a nutshell, the fifth article is a tiny bit taller than the sixth. As a result, the seventh article is getting stuck right on the protruding bottom of the fifth article, so it’s not able to get all the way over to the left side.

The solution is to use the clear property to force it underneath. In fact, so that the problem doesn’t occur again, I would clear all of the odd articles


.no-flexbox article:nth-child(odd){

clear: left;

}

IE9 Float Drop Fixed

This means, in short, that any odd article (1, 3, 5, 7, etc) will refuse to have any floated element beside it on the left. In this way, the odd elements will always be pushed below the preceeding odd ones. As a result, the odd articles will never have article to their left.

( An interesting little side note: notice how the IE9 Developer Tools are showing my nth-child(odd) as nth-child(2n+1)? Strange. )

This returns the flow of articles to what we were expecting in the original layout.

This last bit of IE9 troubleshooting demonstrates well why FlexBox is so important. With browsers that support it, we no longer need to be cowboys at the  float rodeo. 

And for that reason, as browsers like IE9 fade into history so too will some of the pains associated with traditional ways of laying out a page in CSS.

As For IE8

As for IE8 and its stretched image, one fix might be to use a conditional comment and hardcode the width and height of the image to IE8 only. To do that, put this in the head of your document.

<!--[if IE 6]>
<style>
    .site-banner {width: 300px; height: 300px}
</style>
<![endif]-->

If you want to do more with IE8 fixes, consider making an IE8-only stylesheet and put the above code in it, then using a link tag rather than inlining the styles via the style tag.

Remaining Work

We looked at only one breakpoint here. Test your page at wider and narrower ones. There will still be a lot of things to fix, but what we’ve done here should point the way.

And as much as possible test on real devices.

Standard