Lecture 10.1 (Tuesday, March 29)

  • P3 due date pushed back until Thursday!

Review

We've added two new things from CDNs:

  • Font Awesome (it's awesome)
  • jQuery

We will eventually start self-hosting these kinds of things, but for now, we're using CDNs so we can get started quickly.

jQuery

We'll be using the jQuery library. This library provides a lot of useful utilities to make it easier to write JavaScript that manipulates web pages in interesting ways.

We are using version 2.2.2; the primary difference is that it has less compatibility with old browsers.

Browsers have come a long way, so that many features that were originally exclusive to jQuery are now provided directly by the browser's JavaScript engine. However, jQuery is generally more concise, and it uses the built-in capabilities when possible for performance boosting.

Setting Up JavaScript

We're going to create our JavaScript in a .js file that we link externally.

It's also possible to inline JavaScript. This does decrease network traffic. However, it's generally more convenient to keep JS separate, and if it is inlined to do so programatically.

We will include the JavaScript with a script element:

<script type="text/javascript" src="/static/animal-page.js"></script>

Note that script must have a closing tag, as it can have content! However, if it has a src attribute, it needs to be empty.

We're going to put this at the end of the page, so that it loads after all of our content.

Responding to Events

Our client-side JavaScript is generally written to respond to events, such as 'page is loaded' and 'item is clicked'.

We respond to the 'page is fully loaded' event like this:

$(window).on('load', function() {
    console.log('The page is loaded!');
});

There are a few things going on:

  • $ is the main entry point to jQuery. It is a function object that we can call to 'jQuery-ify' things, such as elements or the window.
  • $(window) returns a jQuery object that can do things with the browser window.
  • .on registers an event handler
  • we register our handler with the load event.

We can also call $(window).load(function() {…}).

Selecting Elements

We can pass a CSS selector to $ to grab (all) elements matching it. The methods called on the resulting objects will affect all matched elements.

This is a very common operation.

$('.star').on('clicked', function() {
    // the widget is clicked
})

This will register a clicked handler on each element with the class star — in our case, our star widget. We can be a little more selective by also adding a widget class to the element, and then selecting .widget.star.

When the widget is clicked, the special JavaScript variable this will be bound to the clicked widget, so we can do things!

Additional State

In HTML5, we can have arbitrary additional attributes attached to elements. Let's augment our star widgets with a data-state attribute.

<span class="widget star" data-state="unchecked"></span>

Note that we dropped the fa styles — we will replicate them in custom CSS so that everything stays nice and semantic.

Let's look up the Unicode values for our stars:

We can write CSS that renders this:

.star {
    /* make effects work properly */
    display: inline-block;
    /* use Font Awesome */
    font-family: FontAwesome;
}
.star[data-state="checked"]::after {
    /* use the 'filled star' icon */
    content: "\f005";
}
.star[data-state="unchecked"]::after {
    /* use the 'filled star' icon */
    content: "\f006";
}

A few things going on here:

  • We select stars with .star
  • We select checked stars with [data-state="checked"], an attribute selector
  • We select the pseudo-element at the end of the element with ::after
  • There aren't any spaces, so these selectors are all combined with ‘and’.

Note

content only works on ::before and ::after pseudo-elements.

Detour: Storing Data

  • Add user starring to the data model
  • Check if animal is starred to render thing

What do we do with the click?

Goal:

  • send click data to server
  • when successfully stored, update the star
  • show updated star on reload

Need:

  • star endpoint
  • javascript

Sending Results

We need to send request:

  • send 'star this' to server
  • handle response
    • success
    • failure

JavaScript is all asynchronous: the response doesn't happen right away.

Instead, we effectively wait for a 'succeeded' event.

We should also wait for 'failure'.

So we can do:

$.ajax('/update-star', {
    data: {
        animal_id: animal,
        starred: true,
        _csrf_token: csrfToken,
    },
    success: function(response) {
        // success!
        console.log('starring animal succeeded');
    },
    failure: function() {
        console.error('Starring animal failed');
    }
})

CSRF token

Where do we get it?

Let's modify base.html to make it available to JavaScript:

<script type="text/javascript">
var csrfToken = '{{ session.csrf_token }}';
</script>

We'll also modify our before_request hook to set the CSRF token there.

Updating

We can change the attribute:

$(elt).attr('data-state', 'checked');

Waiting

One more thing: we want to indicate the action happening. So we'll replace the star with a spinner:

.star[data-state="waiting"]::after {
    /* use the 'spinner' icon */
    content: "\f110";
}
/* and make it spin */
.star[data-state="waiting"] {
    /* prefixed for compatibility */
    -webkit-animation: fa-spin 2s infinite linear;
    animation: fa-spin 2s infinite linear;
}

Integrate all of this with our handler logic.

Add a sleep to the update-star endpoint to see it in action

REST endpoints

/update-star doesn't return a page — instead, it is called from JavaScript, and returns a little object that indicates its status.

This is very common for implementing our JavaScript support. This is the underpinnings of web APIs.

Sets of URLs that are intended to be used programmatically, and are designed around HTTP's native capabilities, are called REST APIs. In addition to GET and POST, you will sometimes see DELETE and PUT methods with these APIs.

When you are writing interactive apps, you'll make a lot of little URL handlers that are intended to respond to JavaScript.