Week 9
CSS Grid, jQuery and AJAX

CSS Grid

We have previously reviewed CSS Flexbox as a key layout tool in modern HTML/CSS development. It is a teriffic improvement over using older techniques like floats. However, it does have some limitations.

CSS Grid was developed to overcome these limitations and is a newer specification that has now reached sufficiently wide browser support to be safely used in new prodcution sites and applications.

Flexbox v.s. Grid

The simplest way to describe the difference between Flexbox and Grid is that Flexbox works best for use-cases that layout items primarily along a single axis. Whereas Grid excels at controlling layouts in two dimensions.

They should not be seen as presenting an either-or choice. They can be used effectively together, as we will see in some of the examples below.

Documentation

Flexbox is a very powerful layout tool and introduces more than 20 new CSS directives and functions. We will review how to use some of them in the examples below, but you should review the official CSS Grid documentation at the MDM Web Docs site.

As a great primer or quick reference guide I reccomend A Complete Guide to Grid from the CSS-Tricks website.

To illustrate some of the basic principles of CSS Grid, lets build a basic responsive photo gallery. We'll start with this simple HTML structure ...

<body>
  <h1>Responsive CSS-Grid</h1>
  <div class="container">
    <div><img src="http://www.gstatic.com/webp/gallery/1.jpg" alt="" /></div>
    <div><img src="http://www.gstatic.com/webp/gallery/2.jpg" alt="" /></div>
    <div><img src="http://www.gstatic.com/webp/gallery/3.jpg" alt="" /></div>
    <div><img src="http://www.gstatic.com/webp/gallery/4.jpg" alt="" /></div>
    <div><img src="http://www.gstatic.com/webp/gallery/5.jpg" alt="" /></div>
    <div><img src="http://www.gstatic.com/webp/gallery/1.jpg" alt="" /></div>
  </div>
</body>

Let's make sure that our images automatically scale with the size of their parent div element. We can use the same object-fit technique that we used in earlier in-class exercises.

.container > div > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Now we will designate the div.container element as our Grid Container. The inner div elements will then automatically become Grid Items.

.container {
  display: grid;
}

If we check this in the browser, we don't really see any change. The images are all stacked vertically at full size. Let' start adding some additional instructions to tell our grid how to behave.

Let's start with some white space between the images. We can use the grid-gap directive.

.container {
  display: grid;
  grid-gap: 5px;
}

This will apply a consistent spacing of 5px on both the horizontal and vertical axises. If your design calls for it, you can specify these separately.

.container {
  display: grid;
  grid-row-gap: 5px;
  grid-column-gap: 5px;
}

Take note of how much more elegent this syntax is compared to Flexbox, where we would need to apply margins to each of the child elements.

OK. Now let's change the size of the column. For that we use the grid-template-columns directive. It lets us describe the size and number of columns that we want. We will start with one column of 300px.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: 300px;
}

If you check that in the browser now, you will see the images are stacked in a narrow column with a gap of 5px between them. Let's make that three columns ...

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: 300px 300px 300px;
}

The images are now arranged in rows of three. Grid automatically flows the content to the next row as needed. This is another difference from Flexbox where we would need to explicitly invoke the flex-wrap directive.

Fractional units

If you resize the browser you will see that we have a problem. The content overflows the window width on smaller screen sizes. Let's change the size of the columns from fixed px width to a variable width based on the new fr unit of measurement.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: 1fr 1fr 1fr;
}

Now we will see three columns of equal fractions of the available space. We could change the middle column to be 2fr and it would be twice the size of the other two.

We can also mix units. Let's change that middle column again to be 100px.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: 1fr 100px 1fr;
}

The browser assigns the fixed width space first and then devides the remaining space among the fractional units.

Repeat function

When creating a grid layout with a number of equally sized columns, we can use the repeat() function like this ...

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(3, 1fr);
}

Now we are back to three equal columns.

Minmax function

Let's tackle another problem. When we shrink the browser to mobile size, we still have three equal but very small columns. That is not the optimal behaviour for mobile devices. Instead we would probably like to have the layout change to a single column and maintain a larger image size.

Enter the new minmax() function. We can tell the browser, "make my images at least 250px wide and if there is more space, grow them to be 1fr."

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(3, minmax(250px, 1fr));
}

Almost there but not quite right. We are getting three columns of at least 250px and it grows bigger for larger screens, but doesn't stack on smaller ones ... yet.

We can use a special value for the number of columns called auto-fit.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}

Now the browser will add as many 250px columns as fit on the row, and then scale up the images to fill the full row. As the borwser size shrinks, the number of columns decreases.

Row height

One last optimization ... as it stands the rows are differnt heights based on the content of the items in the row. Let's say that we wanted them all to be 200px high. We can use the grid-template-rows directive to set the rows just as we did for columns.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-template-rows: repeat(2, 200px);
}

That sets an explicit height for the first two rows but not for the auto-flow rows. They default to the content height. We can control that with one more line.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-template-rows: repeat(2, 200px);
  grid-auto-rows: 200px;
}

In fact we can now remove the previous line, since we want all rows to be the same.

.container {
  display: grid;
  grid-gap: 5px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-auto-rows: 200px;
}

et Voilà

We now have a fully responsive photo gallery in just four lines of CSS and not a single @media query to be seen!!

mind = blown

Whant to learn more?

Read Common Responsive Layouts with CSS Grid on Medium.

Try out this interactive CSS Grid tutorial called Grid Garden

Intro to jQuery

What is it

From the official documentation

jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript.

Inspired by the Behaviour library, John Resig released the first version of the jQuery JavaScript library in early 2006. At that time, the underlying JavaScript language was lacking in maturity and jQuery filled the gaps for the most common and repetetive use-cases.

Times, they are a change'n

Over time, JavaScript has continued to evolve as a language and now most direct DOM manipulation use-cases are just as simple to implement in plain JavaScript as they are with the jQuery library. Additionally, modern JavaScript libraries / frontend framworks like Vue, React and Angular have changed how we approach building interactive websites and applications. These modern libraries follow a more data-centric approach that lets us build more efficient (faster user experience) and easier to maintain solutions.

There is a ton of legacy code out in the wild that you will likely work on at some point and therefore, you should know about jQuery. But, a new project that is starting with a clean slate would be more likely to use one of the modern frameworks like Vue or React.

Why do we want to use it

  • It is a dependancy for Bootstrap's interactive components
  • When we want to trigger some custom css animation in response to a user input

How does it work?

I could write up a whole bunch of tutorials here but there are some very good ones already written. Start with The basics of jQuery on the main jQuery documentation site.

When you have completed that, watch the jQuery for web designers video tutorial on Lynda.com

Code example: Grid Card Reveal

OK. Let's see a practical example of jQuery in action. One of the most common use-cases is reacting to a user interaction event like a mouse click or mouse-over (hover) on an HTML element. Often you will want to add or remove a CSS class for the HTML element in question in order to trigger an animation or change the visual appearance.

For example, let's go back to our photo gallery and update it to display the photos in a "card" component that has some descriptive text and some action buttons.

The HTML markup looks like this.

<div class="container">
  <div class="card">
    <div class="card-image">
      <img src="http://www.gstatic.com/webp/gallery/1.jpg" alt="" />
    </div>
    <div class="card-body cover">
      <p class="card-title">Mountain lake</p>
      <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum nostrum
        sequi sed officia consequuntur debitis illum sint ipsam provident
        pariatur.
      </p>
    </div>
    <div class="card-footer cover"><button type="button">Buy now</button></div>
  </div>
  <!-- repeat the above card structure for each image -->
</div>

The .card element has three direct children: .card-image, .card-body, and .card-footer which should appear stacked vertically. To ensure consistent height of the various sub-components, I have used display: flex on the .card and given the child elements specific heights: 200px, 200px and 40px.

To give our gallery a little flair and return the focus to the images, we will have the image cover the entire card until the user hovers over it. Then we will reveal the additional content.

To achieve that effect we will add a .cover class to the .card-body and .card-footer elements by default and then remove that class when the user hovers over the card.

.cover {
  visibility: hidden;
  height: 0;
  margin: 0;
  opacity: 0;
  padding: 0;
  transition: all 0.4s;
}

We set the visibility to hidden and the height to zero. Now let's add flex-grow: 1 to the .card-image so that it will expand to fill the card when it's sibling flex-items have zero height.

.card-image {
  flex-grow: 1;
  height: 200px;
}

JavaScript magic

In order to add and remove the .cover class based on the hover we need a little JavaScript help. We need to find all of the .card elements in our DOM tree and then attach event listeners so we know when the mouse enters and leaves the element.

We can use jQuery to simplify this task. At the bottom of the <body> section we will add ...

<!-- Load jQuery from CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<!-- Our custom script -->
<script>
  const toggleCover = function() {
    $(this) // 'this' is the .card element responding to the mouse event
      .children('.card-body') // find the child element with class .card-body
      .toggleClass('cover') // remove the .cover class if present or add it
    $(this)
      .children('.card-footer')
      .toggleClass('cover')
  }
  $(document).ready(
    $('.card') // find all of the HTML elements with class .card
      .on('mouseenter', toggleCover) // add mouse event listeners and trigger
      .on('mouseleave', toggleCover) // the toggleCover function defined above
  )
</script>

That will work! However, in this case we want to call the exact same function for both the mouseenter and mouseleave events. jQuery gives us a hover() shorthand method for exactly this case. We can replace the document ready block with this ...

$(document).ready($('.card').hover(toggleCover))

The complete source code for the Gallery Card example ...

<!DOCTYPE html>
<html lang="en-CA">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Responsive CSS Grid</title>
  <style>
    html {
      box-sizing: border-box;
      font-size: 14px;
      font-family: 
        -apple-system, 
        BlinkMacSystemFont, 
        'Segoe UI', 
        Roboto, 
        Oxygen, 
        Ubuntu, 
        Cantarell, 
        'Open Sans', 
        'Helvetica Neue', 
        sans-serif;
      margin: 0;
      padding: 0;
    }
    body {
      background-color: rgba(75,0,130,0.06);
      padding: 0.5rem;
    }
    h1 {
      color: rgba(0,0,0,.54);
      font-size: 1.6rem;
    }
    .container {
      display: grid;
      grid-gap: 0.75rem;
      grid-template-columns: repeat(auto-fit, minmax(225px, 1fr));
      grid-auto-rows: 420px;
      grid-auto-flow: row dense;
    }
    .card {
      background-color: #feffff;
      border-radius: 0.25rem;
      box-shadow: 0 1px 0.75rem rgba(0,0,0,.23);
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
    .card-image {
      flex-grow: 1;
      height: 200px;
    }
    .card-image > img,
    .container > div > img {
      object-fit: cover;
      height: 100%;
      width: 100%;
    }
    .card-body {
      height: 200px;
      padding: 0.5em;
      transition: all .25s;
    }
    .card-title {
      font-size: 1.25em;
      font-weight: 600;
      margin-bottom: 0.5rem;
    }
    .card-footer { 
      margin: auto 0 1em 0; 
      padding: 0 0.5em;
      transition: all .25s; 
    }
    .card-footer button {
      background-color: transparent;
      border: 1px solid indigo;
      border-radius: 0.25rem;
      color: indigo;
      line-height: 1em;
      padding: 0.5em;
      text-align: center;
      text-transform: uppercase;
    }
    .card-footer button:focus,
    .card-footer button:hover {
      background-color: indigo;
      color: rgba(255,255,255,0.9);
    }
    .cover {
      visibility: hidden;
      height: 0;
      margin: 0;
      opacity: 0;
      padding: 0;
      transition: all .4s;
    }
    @media screen and (min-width: 500px) {
      .wide {
        grid-column: span 2;
      }
    }
  </style>
</head>

<body>
  <h1>CSS-Grid Cards</h1>
  <div class="container">
    <div class="card">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/1.jpg" alt="">
      </div>
      <div class="card-body cover">
        <p class="card-title">Mountain lake</p>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.
          Earum nostrum sequi sed officia consequuntur debitis illum
          sint ipsam provident pariatur.</p>
      </div>
      <div class="card-footer cover">
        <button type="button">Buy now</button>
      </div>
    </div>
    <div class="card wide">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/2.jpg" alt="">
      </div>
      <div class="card-body cover">
        <p class="card-title">White water kyaking</p>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.
        </p>
      </div>
      <div class="card-footer cover">
        <button type="button">Buy now</button>
      </div>
    </div>
    <div class="card">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/3.jpg" alt="">
      </div>
    </div>
    <div class="card">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/4.jpg" alt="">
      </div>
    </div>
    <div class="card">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/5.jpg" alt="">
      </div>
    </div>
    <div class="card">
      <div class="card-image">
        <img src="http://www.gstatic.com/webp/gallery/1.jpg" alt="">
      </div>
    </div>
  </div>

  <!-- Load jQuery from CDN -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

  <!-- Our custom script -->
  <script>
    const toggleCover = function () {
      $(this).children('.card-body').toggleClass('cover')
      $(this).children('.card-footer').toggleClass('cover')
    }
    $(document).ready(
      $('.card').hover(toggleCover)
      // the .hover method is a shortcut for aplying the
      // same function to these two separate events:
      // $('.card')
      // .on('mouseenter', toggleCover)
      // .on('mouseleave', toggleCover)
    )
  </script>
</body>

</html>

A note about Bootstrap and jQuery

From the getting started documentation

These Bootstrap compontents need jQuery:

  • Alerts for dismissing
  • Buttons for toggling states and checkbox/radio functionality
  • Carousel for all slide behaviors, controls, and indicators
  • Collapse for toggling visibility of content
  • Dropdowns for displaying and positioning (also requires Popper.js)
  • Modals for displaying, positioning, and scroll behavior
  • Navbar for extending our Collapse plugin to implement responsive behavior
  • Tooltips and popovers for displaying and positioning (also requires Popper.js)
  • Scrollspy for scroll behavior and navigation updates

Although you can augment the interactive features with your own Javascript. However, you can use these jQuery dependant components without writing any new Javascript. All of the code examples in the documentation include the use of data-* attributes.

For example, the close button on the alert component has the data-dismiss="alert" attribute. The bootstrap.js automatically looks for these attribute tags and adds the appropriate Javascript event listeners.

<button type="button" class="close" data-dismiss="alert" aria-label="Close">
  <span aria-hidden="true">&times;</span>
</button>

AJAX

AJAX in an achronym for Asynchronous JavaScript And XML and refers to the method of asynchronously exchanging data with a remote server. This is facilitated by allowing the JavaScript engine in the browser to make a network connection without reloading the main HTML document.

The technique was initially implementation by Microsoft in 1999 but not widely adopted until 2005 when it was implemented by Goodle in gmail and maps. The first official W3C specification was published in 2006.

Although the original implementation used XML as the standardized data format, the vast majority of modern AJAX implementations use JSON (JavaScript Object Notation) documents to package the data.

See this Wikipedia article for a more in depth explanation and history.

Fetch example

The orginal AJAX specification utilized the JavaScript XMLHttpRequest() object and the syntax was a little cumbersome. Modern JavaScript has added a new fetch() API that has a simplfied sytax and uses Promises rather than nested callback functions.

In this example we will use fetch to get data about a Star Wars character from an open source database.

fetch('https://swapi.co/api/people/3')
  // when the promise is resolved,
  // decode the JSON response
  .then(response => response.json())
  .then(jsonData => {
    console.log(jsonData) // display the decoded data
  })
  .catch(console.log) // if there was an error

Note: this code example is using the modern ES6+ "arrow function" syntax. It is more compact but functionally equivilant to ...

fetch('https://swapi.co/api/people/3')
  .then(function(response) {
    const jsonData = response.json()
    return jsonData
  })
  .then(function(jsonData) {
    console.log(jsonData)
  })
  .catch(function(error) {
    console.log(error)
  })

Either way, the result as logged to the console would be this JSON object ...

{
  "name": "R2-D2",
  "height": "96",
  "mass": "32",
  "hair_color": "n/a",
  "skin_color": "white, blue",
  "eye_color": "red",
  "birth_year": "33BBY",
  "gender": "n/a",
  "homeworld": "https://swapi.co/api/planets/8/",
  "films": [
    "https://swapi.co/api/films/2/",
    "https://swapi.co/api/films/5/",
    "https://swapi.co/api/films/4/",
    "https://swapi.co/api/films/6/",
    "https://swapi.co/api/films/3/",
    "https://swapi.co/api/films/1/",
    "https://swapi.co/api/films/7/"
  ],
  "species": ["https://swapi.co/api/species/2/"],
  "vehicles": [],
  "starships": [],
  "created": "2014-12-10T15:11:50.376000Z",
  "edited": "2014-12-20T21:17:50.311000Z",
  "url": "https://swapi.co/api/people/3/"
}

XMLHttpRequest example

This is the ugly old way ...

// Initialize the HTTP request.
const xhr = new XMLHttpRequest()
xhr.open('GET', 'https://swapi.co/api/people/3/')
xhr.responseType = 'json'

// Track the state changes of the request.
xhr.onreadystatechange = function() {
  const DONE = 4 // readyState 4 means the request is done.
  const OK = 200 // status 200 is a successful return.
  if (xhr.readyState === DONE) {
    if (xhr.status === OK) {
      console.log(JSON.parse(xhr.response)) // 'This is the output.'
    } else {
      console.log('Error: ' + xhr.status) // An error occurred during the request.
    }
  }
}

// Send the request to swapi.co
xhr.send(null)

jQuery example

Before the fetch API was added to JavaScript, jQuery provided the ajax() function as a wrapper around the underlying XMLHttpRequest() object to make it easier to use. Our remote API call above would look like this ...

$.ajax({
  type: 'GET',
  url: 'https://swapi.co/api/people/3',
  dataType: 'JSON', // data type expected from server
  success: function(data) {
    console.log(data)
  },
  error: function() {
    console.log('Error: ' + data)
  }
})

Of the three examples above, it is definately preferable to use fetch!

Last Updated: 11/9/2018, 2:32:26 PM