JavaScript
- It is useful to distinguish between code that is run by the client, the user interacting with a web application, and the server, which is the Flask application running the website. The client makes an HTTP request to the server, which is a running some Python code. The server processes the response to understand what the client is asking for, and ultimately sends back some HTML and CSS content which is rendered in the client’s browser. It is often useful, however, to have code that does run client-side. Client-side processes reduce load on the server and are often faster.
Using JavaScript with HTML and CSS
- JavaScript is a programming language designed to be run inside a web browser that is run client-side. There are many different versions of JavaScript that are supported by different browsers, but there are certain standard versions that are supported by most. In this class, one of the more popular, recent versions, ES6, will be used.
- When embedded directly inside the HTML code for a webpage, it is enclosed in
<script></script>
tags.<script> alert('Hello, world!'); </script>
- The previous code example, if placed in the
head
element, for example, would run as soon as the page is loaded. JavaScript can also be run in response to events.<html> <head> <script> function hello() { alert('Hello!'); } </script> <title>My Website</title> </head> <body> <h1>Welcome!</h1> <button onclick="hello()">Click Here!</button> </body> </html>
- Now, the JavaScript code is contained inside a function. Note that the function is delimited by curly braces.
- The function
hello
is never called inside thescript
element. Rather, there is abutton
element with theonclick
attribute which has thehello
function as its value. The clicking of a button is one events that JavaScript understands which can be used as a trigger. In this case, that trigger runs thehello
function.
- Some other JavaScript events include:
onmouseover
: triggers when an element is hovered overonkeydown
: triggers when a key is pressedonkeyup
: triggers when a key is releasedonload
: triggers when a page is loadedonblur
: triggers when an object loses focus (when moving away from an input box, for example)
Manipulating the DOM
- Beyond just displaying alerts, JavaScript has the power to actually change the contents of a webpage.
<html> <head> <script> // Function to change heading to say goodbye function hello() { document.querySelector('h1').innerHTML = 'Goodbye!'; } </script> </head> <body> <h1>Welcome!</h1> <button onclick="hello()">Click Here!</button> </body> </html>
document
refers to the web page currently being displayed.querySelector('tag')
is a function that searches through the webpage for a particular CSS selector and returns that element. If there are multiple results, only the first result is returned.- This function can also be called as
document.querySelector('#id')
anddocument.querySelector('.class')
. More sophisticated selectors, selecting only descendants of certain element,s for example, can also be used.
- This function can also be called as
- The
innerHTML
property of an element is the HTML content contained within the element’s tags. - When the
button
is clicked, the textWelcome!
changes toGoodbye!
.
- This slightly more advanced example showcases the use of variables in JavaScript.
<html> <head> <script> let counter = 0; function count() { counter++; document.querySelector('#counter').innerHTML = counter; } </script> </head> <body> <h1 id="counter">0</h1> <button onclick="count()">Click Here!</button> </body> </html>
let
is a keyword used to define variables.counter++
is a shorthand to incrementcounter
by 1.
- Conditional statements in JavaScript look like this:
<script> let counter = 0; function count() { counter++; document.querySelector('#counter').innerHTML = counter; if (counter % 10 === 0) { alert(`Counter is at ${counter}!`); } } </script>
%
is the modulus operator, which returns the remainder of the first number divided by the second.===
checks for exact equality in JavaScript; two things must be identical for it to return true.- The argument to
alert
is a template literal, which is like a Python format string.${counter}
is replaced with whatever the value of the variablecounter
is. Backticks are used to delimit a template literal.
- JavaScript can also be factored out of the HTML code entirely.
<html> <head> <script> document.addEventListener('DOMContentLoaded', function() { document.querySelector('button').onclick = count; }); let counter = 0; function count() { counter++; document.querySelector('#counter').innerHTML = counter; if (counter % 10 === 0) { alert(`Counter is at ${counter}!`); } } </script> </head> <body> <h1 id="counter">0</h1> <button>Click Here!</button> </body> </html>
- Note that there is no
onclick
attribute in the HMTL tags for thebutton
element. Nonetheless, the functionaddEventListener
, which, likes it name suggests, ‘listens’, or waits, until the eventDOMContentLoaded
occurs. This event occurs when the entire HTML structure is loaded by the browser. Then, the second argument, a function, is called. - JavaScript makes use of ‘higher order functions’, which means functions can be passed around like any other value. The function being passed is called a ‘callback’ function. The callback is called when the event being listened for occurs. In this case, that callback sets the
onclick
property of thebutton
element to thecount
function, ultimately resulting in the same functionality as before.
- Note that there is no
- Going one step further, the JavaScript code can be factored out of the
.html
file entirely into a separate.js
file. Everything that’s inside thescript
element from the last example would go into the.js
file, and the.html
would look like this:<html> <head> <script src="counter3.js"></script> </head> <body> <h1 id="counter">0</h1> <button>Click Here!</button> </body> </html>
- This is exactly the same paradigm that was seen in factoring out CSS.
Variables
- There are three main keywords used to define variables in JavaScript.
const
: defines a constant variable that cannot be redefined laterlet
: defines a variable is local to the scope of the innermost pair of curly braces surrounding itvar
: defines a variable that is local to the function it is defined in
- Here is an example showcasing these different ways to define variables:
<script> // This variable exists even outside the loop if (true) { var message = 'Hello!'; } alert(message); </script>
- Because
var
was used to definemessage
, there will be no errors running this code.<script> // This variable does not exist outside the loop if (true) { let message = 'Hello!'; } alert(message); </script>
- Because
let
was used to definemessage
, it cannot be passed toalert
, which is outside the scope ofmessage
. If this were in an HTML page, when the page was opened, no alert would pop up. If the console were opened in the browser, there would be anUncaught ReferenceError
.<script> // The value of const variables cannot change const message = 'Hello!'; message = 'Goodbye!'; alert(message); </script>
- Similar to the last example, no alert will pop up. In the console, there would be an
Uncaught TypeError
, since there was an attempt to redefine a variable defined withconst
.
- Because
- The JavaScript console, accessable in the Develop or Inspect menu in a web browser, allows for the interactive entry of JavaScript, similiar to the Python console.
- Here’s another example which uses JavaScript to read info from a form.
<html> <head> <script> document.addEventListener('DOMContentLoaded', function() { document.querySelector('#form').onsubmit = function() { const name = document.querySelector('#name').value; alert(`Hello ${name}!`); }; }); </script> </head> <body> <form id="form"> <input id="name" autocomplete="off" autofocus placeholder="Name" type="text"> <input type="submit"> </form> </body> </html>
- The callback function here selects the element with the id
form
and sets itsonsubmit
(another event) property to another callback function which sets theconst
variablename
to thevalue
property returned from the elemnt with idname
.name
is the input box of a form, sovalue
will be whatever the user has entered into the form. - So, this code produces an alert that says hello to whatever name the user entered into the form.
- The callback function here selects the element with the id
Modifying Style
- JavaScript can also modify the CSS properties of elements.
<html> <head> <script> document.addEventListener('DOMContentLoaded', function() { // Change font color to red document.querySelector('#red').onclick = function() { document.querySelector('#hello').style.color = 'red'; }; // Change font color to blue document.querySelector('#blue').onclick = function() { document.querySelector('#hello').style.color = 'blue'; }; // Change font color to green document.querySelector('#green').onclick = function() { document.querySelector('#hello').style.color = 'green'; }; }); </script> </head> <body> <h1 id="hello">Hello!</h1> <button id="red">Red</button> <button id="blue">Blue</button> <button id="green">Green</button> </body> </html>
- There are three buttons, each of which (after the initial callback from loading the webpage) have their
onclick
properties set to a function which sets thestyle.color
property of thehello
element to a different color. Any CSS property could be modified, e.g.style.background-color
,style.margin
, etc.
- There are three buttons, each of which (after the initial callback from loading the webpage) have their
- The repetitiveness of the last example can be reduced.
<html> <head> <script> document.addEventListener('DOMContentLoaded', function() { // Have each button change the color of the heading document.querySelectorAll('.color-change').forEach(function(button) { button.onclick = function() { document.querySelector('#hello').style.color = button.dataset.color; }; }); }); </script> </head> <body> <h1 id="hello">Hello!</h1> <button class="color-change" data-color="red">Red</button> <button class="color-change" data-color="blue">Blue</button> <button class="color-change" data-color="green">Green</button> </body> </html>
document.querySelectorAll('.color-change')
returns an array of all elements of the classcolor-change
.forEach
is a built-in JavaScript function that can be called on an array that runs a function passed to it on each element of an array. The function being passed takes as an argument one particular element of the array.- Having all three buttons with the same class,
color-change
, allows for them to be selected together withquerySelectorAll
. data-color
is a data attribute. Data attributes allow for the association of additional information with an element without changing how the element is rendered by the browser. Data attributes can have any name as long as they start withdata-
.- Data atrributes can be accessed in the
dataset
property of an element. In this example,data-color
is accessed indataset.color
.
Arrow Functions
- Since functions, especially anonymous functions, are so common in JavaScript, ES6 has introduced a new syntax for functions called arrow notation that allows for the definition of so-called arrow functions.
() => { alert('Hello, world!'); } x => { alert(x); } x => x * 2;
- An arrow function is defined without using the word
function
, but rather just with a pair of parentheses enclosing any arguments the function takes, followed by an arrow, and finally the function body, enclosed in curly braces. - Functions with only one argument can be defined without the use of parentheses enclosing the argument list.
- Functions that have only one line in the body can drop the curly braces and have the body on the same line as the argument list and arrow.
- An arrow function is defined without using the word
- The previous example could be rewritten more succintly with arrow functions.
document.addEventListener('DOMContentLoaded', () => { // Have each button change the color of the heading document.querySelectorAll('.color-change').forEach(button => { button.onclick = () => { document.querySelector('#hello').style.color = button.dataset.color; }; }); });
- One last variation of this color example could use a drop-down menu to select colors instead of buttons.
<html> <head> <script> document.addEventListener('DOMContentLoaded', () => { // Change the color of the heading when dropdown changes document.querySelector('#color-change').onchange = function() { document.querySelector('#hello').style.color = this.value; }; }); </script> </head> <body> <h1 id="hello">Hello!</h1> <select id="color-change"> <option value="black">Black</option> <option value="red">Red</option> <option value="blue">Blue</option> <option value="green">Green</option> </select> </body> </html>
onchange
is the event fired when the selection in a drop-down menu is changed.this
refers to whatever value the function is operating on, which in this case isdocument.querySelector('#color-change')
, which is the drop-down menu itself. The selected item is extracted using thevalue
attribute of the drop-down menu, which corresponds to one of the color options.- Note that using
this
with arrow functions will produce different behavior.this
inside an arrow function will be bound to whatever valuethis
would have taken on inside the code that is enclosing the arrow function. By writing outfunction ()
, thenthis
takes on the value of whatever the function is being called on.
- Note that using
More with JavaScript
- In the next example, the goal will to be create a to-do list application. Here’s the starting point:
<html> <head> <script> document.addEventListener('DOMContentLoaded', () => { document.querySelector('#new-task').onsubmit = () => { // Create new item for list const li = document.createElement('li'); li.innerHTML = document.querySelector('#task').value; // Add new item to task list document.querySelector('#tasks').append(li); // Clear input field document.querySelector('#task').value = ''; // Stop form from submitting return false; }; }); </script> <title>Tasks</title> </head> <body> <h1>Tasks</h1> <ul id="tasks"> </ul> <form id="new-task"> <input id="task" autocomplete="off" autofocus placeholder="New Task" type="text"> <input type="submit"> </form> </body> </html>
- The
tasks
unordered list starts empty, but will be populated with user input. - In the JavaScript code, when the form is submitted, a new
li
element is assigned to theconst
variableli
using the functiondocument.createElement('li')
. Then, theinnerHTML
of thatli
is set to be whatever thevalue
of thetask
input field is. - The new
li
is then added to theul
tasks
with theappend(li)
function, called on theul
. - Finally, the input field is cleared and the default behavior for a form, which is to go to some other website and reload the page, is suppressed by returning
false
.
- The
- Blank submissions can be omitted by conditionally enabling the submit button.
// By default, submit button is disabled document.querySelector('#submit').disabled = true; // Enable button only if there is text in the input field document.querySelector('#task').onkeyup = () => { document.querySelector('#submit').disabled = false; // ...same code as before... // Disable button again after submit document.querySelector('#submit').disabled = true; // Stop form from submitting return false; };
- This results in the button only being pressable once some keypress has been registered, assuming that the field is then populated.
- The previous implementation would still allow for submission if text was entered and then erased from the form. This can be remedied by checking that the
length
property of thevalue
attribute of the form input is indeed greater than 0 after every keystroke.// Enable button only if there is text in the input field document.querySelector('#task').onkeyup = () => { if (document.querySelector('#task').value.length > 0) document.querySelector('#submit').disabled = false; else document.querySelector('#submit').disabled = true; };
- Another feature of JavaScript is the ability to wait for a certain amount of time.
<html> <head> <script> document.addEventListener('DOMContentLoaded', () => { setInterval(count, 1000); }); let counter = 0; function count() { counter++; document.querySelector('#counter').innerHTML = counter; } </script> </head> <body> <h1 id="counter">0</h1> </body> </html>
- The
setInterval
function takes another function and then the interval (in milliseconds), after which the passed function will be automatically called over and over. - The result, in this example, is an automatically incrementing counter without the need for any buttons.
- The
- If the previous example were reloaded, the counter would be reset. The maintain some persistence, JavaScript can use local storage to keep track of some state information.
<html> <head> <script> // Set starting value of counter to 0 if (!localStorage.getItem('counter')) localStorage.setItem('counter', 0); // Load current value of counter document.addEventListener('DOMContentLoaded', () => { document.querySelector('#counter').innerHTML = localStorage.getItem('counter'); // Count every time button is clicked document.querySelector('button').onclick = () => { // Increment current counter let counter = localStorage.getItem('counter'); counter++; // Update counter document.querySelector('#counter').innerHTML = counter; localStorage.setItem('counter', counter); } }); </script> </head> <body> <h1 id="counter"></h1> <button>Click Here!</button> </body> </html>
localStorage
is the variable that JavaScript can store information at.getItem
andsetItem
can be called onlocalStorage
to either load or save data. This example first tries to loadcounter
, and if it’s not there, saves a newcounter
with value0
.- Then, the
counter
element is initially set to thatcounter
item in storage. After that, a variable calledcounter
is used to reference thecounter
item, and after every update of thecounter
variable, thecounter
item inlocalStorage
has its value updated. - Now, closing and reloading the page will not reset the value of the counter.
Integrating JavaScript with Python and Flask
- Ajax, is used to get more information from server without needing to reload an entirely new page. As an example, Ajax can be used with the currency conversion example from last week to display a conversion without needing to load a new page. This is not done by pre-loading all possible exchange rates, but by making an Ajax request to the Flask server, which will get a particular exchange rate whenever it is asked for. JavaScript can then be used to update the DOM to render the new content.
- Here’s the interesting part of
application.py
. There’s not much different here from last week, but note that what’s being returned is not a new webpage, but rather just a JSON object.@app.route("/convert", methods=["POST"]) def convert(): # Query for currency exchange rate currency = request.form.get("currency") res = requests.get("https://api.fixer.io/latest", params={ "base": "USD", "symbols": currency}) # Make sure request succeeded if res.status_code != 200: return jsonify({"success": False}) # Make sure currency is in response data = res.json() if currency not in data["rates"]: return jsonify({"success": False}) return jsonify({"success": True, "rate": data["rates"][currency]})
- The HTML is simply a basic form. The JavaScript code is in a different file, but linked in the
head
.<html> <head> <script src=""></script> <title>Currency Converter</title> </head> <body> <form id="form"> <input id="currency" autocomplete="off" autofocus placeholder="Currency" type="text"> <input type="submit" value="Get Exchange Rate"> </form> <br> <div id="result"></div> </body> </html>
url_for('static', filename='index.js')
is Flask’s way of incorporating.js
files.static
is a separate folder.- The
result
div
will contain the conversion, but is currently blank.
- The interesting code is inside of
index.js
.document.addEventListener('DOMContentLoaded', () => { document.querySelector('#form').onsubmit = () => { // Initialize new request const request = new XMLHttpRequest(); const currency = document.querySelector('#currency').value; request.open('POST', '/convert'); // Callback function for when request completes request.onload = () => { // Extract JSON data from request const data = JSON.parse(request.responseText); // Update the result div if (data.success) { const contents = `1 USD is equal to ${data.rate} ${currency}.` document.querySelector('#result').innerHTML = contents; } else { document.querySelector('#result').innerHTML = 'There was an error.'; } } // Add data to send with request const data = new FormData(); data.append('currency', currency); // Send request request.send(data); return false; }; });
- An
XMLHttpRequest
is just an object that will allow an Ajax request to be made. request.open
is where the new request is actually initialized, with the HTTP method and route being specified.JSON.parse
converts the raw response (request.responseText
) into an object that can be indexed by keys and values.- The rest of the callback simply updates the HTML using template literals to reflect the result of the conversion.
FormData
is just an object that holds whatever the user input is.
- An
Websockets
- The request-response model, which has been the basis for how HTTP requests and client-server interaction has been discussed so far, is useful as long as data is only being passed when a request is made. But, with ‘full-duplex communication’, more simply described as real-time communication, there is (or shouldn’t be) a need for reloading a webpage and making a new request just to check, for example, if someone sent a message in a chat room. Websockets are a protocol that allow for this type of communication, and Socket.IO is a particular JavaScript library that supports this protocol.
- This example will be based around a voting application that will count and display votes in real-time. Here’s the full
application.py
, with all the setup and import statements.import os import requests from flask import Flask, jsonify, render_template, request from flask_socketio import SocketIO, emit app = Flask(__name__) app.config["SECRET_KEY"] = os.getenv("SECRET_KEY") socketio = SocketIO(app) @app.route("/") def index(): return render_template("index.html") @socketio.on("submit vote") def vote(data): selection = data["selection"] emit("announce vote", {"selection": selection}, broadcast=True)
flask_socketio
is a library that allows for websockets inside a Flask application. This library allows for the web server and client to be emitting events to all other users, while also listening for and receiving events being broadcasted by others.submit vote
is an event that will be broadcasted whenever a vote is submitted. The code for this will be in JavaScript.- Once a vote is received, the vote is announced to all users (
broadcast=True
) with theemit
function.
index.html
:<html> <head> <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script> <script src=""></script> <title>Vote</title> </head> <body> <ul id="votes"> </ul> <hr> <button data-vote="yes">Yes</button> <button data-vote="no">No</button> <button data-vote="maybe">Maybe</button> </body> </html>
index.js
:document.addEventListener('DOMContentLoaded', () => { // Connect to websocket var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port); // When connected, configure buttons socket.on('connect', () => { // Each button should emit a "submit vote" event document.querySelectorAll('button').forEach(button => { button.onclick = () => { const selection = button.dataset.vote; socket.emit('submit vote', {'selection': selection}); }; }); }); // When a new vote is announced, add to the unordered list socket.on('announce vote', data => { const li = document.createElement('li'); li.innerHTML = `Vote recorded: ${data.selection}`; document.querySelector('#votes').append(li); }); });
- First, the websocket connection is established using a standard line to connect to wherever the application is currently running at.
submit vote
is the name of the event that’s being submitted on a button click. That event just sends whatever the vote was.announce vote
is an event received from the Python sever, which triggers the updating of the vote list.
- An improvement to this application would be to display a total vote count, instead of just listing every individual vote, and making sure that new users can see past votes.
- Changes to
application.py
:votes = {"yes": 0, "no": 0, "maybe": 0} @app.route("/") def index(): return render_template("index.html", votes=votes) @socketio.on("submit vote") def vote(data): selection = data["selection"] votes[selection] += 1 emit("vote totals", votes, broadcast=True)
- Now, any vote submissions are first used to update the
votes
dictionary to keep a record of vote totals. Then, that entire dictionary is broadcasted.
- Now, any vote submissions are first used to update the
- Changes to
index.html
:<body> <div>Yes Votes: <span id="yes"></span></div> <div>No Votes: <span id="no"></span></div> <div>Maybe Votes: <span id="maybe"><span></div> <hr> <button data-vote="yes">Yes</button> <button data-vote="no">No</button> <button data-vote="maybe">Maybe</button> </body>
- The
span
elements allocate a space for vote tallies to be filled in later.
- The
- Changes to
index.js
:socket.on('vote totals', data => { document.querySelector('#yes').innerHTML = data.yes; document.querySelector('#no').innerHTML = data.no; document.querySelector('#maybe').innerHTML = data.maybe; });