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:
- U+F006 is open star
- U+F005 is closed star
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.