Lecture overview¶
- Form related HTML elements
- Adding some structure
- Some useful JavaScript tricks
- More on events
- Clicking on nested elements - event bubbeling
- Writing more general event handlers
- Animating using anime.js
Form related HTML elements¶
HTML forms¶
- Often used to enter data that is to be submitted to a server.
<form>
element (parent) contains the form e.g. labels, text fields, checkboxes (children).- Form children have a
name
attribute that are used to identify them when the form is submitted. - Form children can also have an
id
which is used e.g. to identify them within the DOM. - Attributes
name
andid
can be the same, but do not have to be the same.
Some form related HTML elements¶
<form>
(parent)<button>
(can have content inside, e.g. image)<datalist>
(parent) +<option>
(children) use for autocompleted text input<fieldset>
grouping form elements +<legend>
caption<label>
caption for form elements<select>
(parent) +<option>
(children) dropdown option<textarea>
multiple lines of text
Some form related HTML elements¶
<input type="inputtype">
- where
inputtype
is one of e.g.button
,checkbox
,color
,date
,file
,hidden
,number
,password
,radio
,range
,search
,text
,time
- Not all input types can have children of their own, e.g.
<input type='button'>
Finding documentation¶
- Each HTML element has a corresponding JavaScript WebAPI interface (a class for each element type).
- E.g.
<select>
elements correspond to objects with theHTMLSelectElement
interface. - There we can e.g. find out that events that
HTMLSelectElement
elements can fire arechange
andinput
. - We can also find out that
HTMLSelectElement
object have the propertyvalue
that we can read to get the selected option.
Adding a little structure to your JavaScript code
Structure, examples¶
- Run script when DOM has loaded
- A
init()
function for initializing things
Run script when DOM has loaded (without defer)
<!DOCTYPE html>
<html lang="en">
<head>
<script src="script.js"></script>
<title>Loaded example</title>
</head>
<body>
</body>
</html>
document.addEventListener("DOMContentLoaded",
(event) => {
// code to run when DOM has been loaded
});
- Note: Using
defer
will only run the script after the DOM has been created, but beforeDOMContentLoaded
fires.
Add an init function for startup code¶
- It is good practice to define an
init
function that is executed when the script should "start".- Often this is when
DOMContentLoaded
fires.
- Often this is when
// declare global variables if you need them
document.addEventListener("DOMContentLoaded", (event) => {
init();
});
function init() {
// collect all initialization code here
}
- We might also set other other conditions for when
init
is called or simply call it from the last line of the script.
Some useful JavaScript tricks¶
Specify arguments for a function used as an event handler¶
- An event handler function only receives an
Event
object as an argument. TheEvent
object contains information about the event (more on this later).
function handleEvent(event) {
// code for handling event
}
element.addEventListener('click', handleEvent);
- If we want to pass specific arguments to our event handler function, we can wrap it using an anonymous function.
function handleOtherEvent(event, param1, param2 ...) {
// code for handling event
}
element.addEventListener('click', (event) => {
handleOtherEvent(event, "argument1", "argument2", ...)
});
Inline style¶
- Inline styles are styles written using the HTML elements style attribute, e.g.
<p style="font-weight: bold">Hello!</p>
- Read or set inline CSS property from JavaScript
element.style.PROPERTY = String
PROPERTY
should be written in camelCase, e.g.
element.style.backgroundColor = 'lightblue'
element.style.display = 'none'
- Remove inline CSS property
element.style.removeProperty(property-name : String)
property-name
should be written in kebab-case, e.g.
element.style.removeProperty('background-color")
Manipulate an element's classlist¶
- Manually styling an element from JavaScript to reflect changes in state is usually avoided as it becomes very difficult to maintain.
- The recommended approach is to create element classes (the ones you write in HTML and select in CSS) for different states.
- E.g. in a system where we can click to select elements that then become active and get highlighted in the GUI:
element.classList.add("selected") // when an element is selected
element.classList.remove("selected") // when an element is deselected
- Or we might use
toggle
to explicitly turnhighlight
on or off:
element.classList.toggle("highlight")
- Or if we want to be explicit about both the active and inactive states we might use
replace
:
element.classList.replace("active", "inactive")
String interpolation: Template literals¶
- Use backticks (
`
) instead of quotes - Expressions within
${}
will be evaluated - Useful for creating strings!
element.style.width = `${width}px`
- Compare with using f-strings in Python or Ada (Yes,
gnat
supports string interpolation since May 2024, welcome to the 1990s Ada!)
print(f"The sum is {5 + 5}")
Ada.Text_IO.Put_Line (f"The sum is {5 + 5}")
In [4]:
console.log(`The sum is ${5 + 5}`)
The sum is 10
Delayed execution¶
- Run function
func
after waiting - Run function func after waiting delay milliseconds milliseconds
setTimeout(function[, delay])
- Run function
func
after waitingdelay
milliseconds with argumentsarg1
,arg2
, ...
setTimeout(func[, delay, arg1, arg2, ...])
- Note that
setTimeout
does not pause execution, it will only delay its own argument (see example).
Delayed execution example
- In js-settimeout.html:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="js-settimeout.js"></script>
<title>setTimeout example</title>
</head>
<body>
</body>
</html>
- In js-settimeout.js:
console.log("Before");
setTimeout(() => {
console.log("Delayed");
}, 5000);
console.log("After");
Nodelist.forEach()
¶
- (returned by e.g.
querySelectorAll
)
NodeList.forEach(func);
- The function
func
is called once for each element in theNodeList
object and the current element is passed as an argument tofunc
.
document.querySelectorAll(selector).forEach((element) => {
// do something with element, e.g. add event listeners
});
CSS selectors repetition - good to know if using e.g. querySelector()
¶
selector | result set |
---|---|
* |
all elements |
TagName |
all elements of a specific type |
.ClassName |
all elements with a specific class |
#idName |
all elements with a specific id |
TagA TagB |
all TagB elements that are descended from a TagA element |
TagA > TagB |
all TagB elements that are direct children of a TagA element |
TagA ~ TagB |
all TagB elements that are proceeded by a sibling TagA element |
TagA + TagB |
all TagB elements that are immediately proceeded by a sibling TagA element |
TagA:has(TagB) |
all TagA elements that has a TagB child element |
TagA.Classname |
all TagA elements that also have the class Classname |
TagA[attr] |
all TagA elements that have the attribute attr |
TagA[attr=val] |
all TagA elements that have the attribute attr with the value val |
TagA[attr^=val] |
all TagA elements that have the attribute attr with a value that starts with val |
TagA[attr$=val] |
all TagA elements that have the attribute attr with a value that ends with val |
TagA[attr*=val] |
all TagA elements that have the attribute attr with a value that contains val |
More on event handling¶
Event handling in general¶
- Observer design pattern.
- A subject has a set of observers and methods to add and remove observers.
- The state of the subject changes by its
setState
-method being called. - At the end of the
setState
method, the subjectsnotifyObservers
-method is invoked. - The
notifyObservers
-method informs each of the subject's observers that a state change has taken place by calling theirupdate
-method. - Each observer's
update
-method calls their subject'sgetState
-method to get the subject's new state and change their behavior accordingly.
Observer pattern (simplified)¶
Adding an event handler as an event listener¶
- We can use the
element.addEventListener()
method to add an event handler for a specific type of event - We specify the event type and the listener function, e.g.
element.addEventListener("keydown", keyhandler)
- Multiple listeners can be added to the same element.
Using .addEventListener()
¶
// using an anonymous function
element.addEventListener('click', function(event) {
alert("Hello World");
});
// using an arrow expression
element.addEventListener('click',(event) => {
alert("Hello World");
});
// using a function object
function hello(event) {
alert("Hello World!");
}
element.addEventListener('click', hello);
Removing an event listener¶
- You can remove an event handler from an element using
element.removeEventListener(event, handler)
- Needs reference to
handler
- i.e. does not work if you set an anonymous function as the handler when adding it. - Why removing listeners?
- In order to remove or change what happens when the event occurs depending on current state.
Example of event listeners using id:s
Box size sliders using id
Writing more general event handlers
Using event.target
and DOM structure
Event objects¶
- Event handler functions recieve an
Event
object that contains information about the event. - The kind of event decides what event type is sent
- All events have
Event.target
: reference to origin of event
- MouseEvents have e.g.
MouseEvent.screenX
,MouseEvent.screenY
: X and Y coordinates of the mouse
- See https://developer.mozilla.org/en-US/docs/Web/API/Event#interfaces_based_on_event
Example using structure relative to event.target
¶
- The element that was the target of the event is referenced from the event object that is sent to the event listener.
- Using an arrow function expression to create an anonymous listener function:
element.addEventListener('click', (event) => {
event.target.innerHTML = "I am the target.";
});
- Using a named function as event listener instead:
function changeTargetText(event) {
event.target.innerHTML = "I am the target.";
}
element.addEventListener('click', changeTargetText);
Element relatives¶
- Get the first element from a selector result:
let myElement = document.querySelector(".box p");
- Get an element's relatives:
myElement.firstElementChild;
myElement.lastElementChild;
myElement.parentElement;
myElement.nextElementSibling;
myElement.previousElementSibling;
Use query on element¶
- Both
.querySelector()
and.querySelectorAll()
can be called from an element. - Example: We get the first element with the class
box
that is a descendant of an element with the classleft
:
let element = document.querySelector('.left .box')
- Then we find the closest descendant of that element that is a paragraph (a
p
element):
element.querySelector('p')
Find closest ancestor matching selector¶
element.closest(selector)
- Example: We get the first paragraph that is a descendant of the element with the id
left-panel
:
let element = document.querySelector("#left-panel p");
- Then we find the losest ancestor of that paragraph with the class
header
element.closest('.header')
Clicking on nested elements
Event Bubbling
Event flow: capture, target, bubble¶
- capture phase: event object propagates from the top (window) down to the target
- target phase (at-target): event object arrives at target
- bubble phase: event object propagates up the hierarchy ending at the window
Event flow: capture and bubbling¶
Listening for events¶
- Listening handlers are notified during the bubble phase by default
- Using options to
.addEventListener()
we can e.g.- listen during capture
- only listen once
- Handlers can also invoke the
Event.stopPropagation()
to e.g. prevent handlers on ancestors from activating or to conserve resources (e.g. in a game with a lot of clicks)
Demo, event bubbling¶
- https://www.ida.liu.se/~729G87/course-material/lectures/examples/card-bubble/card.html
- Colors
- body: peachpuff
- .card: orange
- .background: pink
- .description: lightgreen
- p: lightblue
- What to look at:
- how events bubble
mouseover
/mouseout
- event bubbles upmouseenter
/mouseleave
- event does not bubble (best practice; less resource intensive)
Alternative to adding listeners to each child: adding a single event listener on a ancestor¶
- Listen for clicks on document that bubble up.
document.addEventListener('click', (event) => {
// element.matches(selector) returns true if element matches the selector
if (event.target.matches('button')) {
// code for button
} else if (event.target.matches(".circle")) {
// code for circle
} else if (event.target.matches(".box")) {
// code for box
}
});
- Use this only when it makes sense. Don't use this as your default solution.
- Unnecessary resource use if it manages lots of elements compared to only the appropriate smaller handler being called when needed.
JS Trick: data-* attributes¶
- We can add data-* attributes in our HTML code, e.g
<div class="card" data-date-of-birth="Nov 1" data-name="Cleo">...</div>
- And then access all of them via the
element.dataset
property (both bracket and .-syntax are possible)
let card = document.querySelector(".card");
console.log(card.dataset["dateOfBirth"]);
console.log(card.dataset.dateOfBirth);
console.log(card.dataset["name"]);
console.log(card.dataset.name);
- Note: Keys are subject to dash-style to camelCase conversion.
- Use-case: Storing information for various use in scripts.
Creating & removing elements using JavaScript¶
Creating elements¶
- Create new elements using
let newElement = document.createElement(elementName : String)
- Add it to the DOM using e.g.
// add newElement as last child of parentElement:
parentElement.append(newElement)
// insert newElement before siblingElement:
siblingElement.before(newElement)
- Remove an element
element.remove()
Animations with anime.js¶
About anime.js¶
- https://animejs.com
- "Anime.js (/ˈæn.ə.meɪ/) is a lightweight JavaScript animation library with a simple, yet powerful API."
- Open source, well documented + many examples available.
- Can animate
- CSS properties
- SVG
- DOM attributes
- JavaScript Objects
- More flexible than CSS animations.
Setup¶
- Download minified js-file (
anime.min.js
) from - Link to it before your JavaScript code.
Create an animation¶
anime(options)
returns an animation object and executes the animation- Where
options
is a JavaScript object containing e.g.- targets - element(s) to animate
- properties - what to animate
- animation parameters - how the animation is played
- easing - rate of change of a parameter over time
- options can also contain e.g.
- animation parameters (direction, loop, autoplay)
- duration
Object data type¶
- Objects have properties.
- Properties are accessed using dot notation:
object.property
- Properties can also be accessed using square bracket notation:
object["property name"]
- Objects can also have functions that can behave as methods.
- Functions in objects are called using dot notation:
object.function()
- Similar to both instances in general and dictionaries in particular in Python.
- Similar to records or tagged types in Ada.
Object example¶
- Here we use an Object literal to explicitly describe what properties the Object should have.
In [ ]:
let image = {
filename: "cat1.jpg",
sayHello: function() {
console.log("Hello!");
}
}
console.log(image.filename);
image.sayHello();
Animation targets¶
- CSS selector (String)
- DOM
Node
orNodeList
- Example of targets (incomplete options)
anime( {targets: '.box' } )
anime( {targets: element })
anime( {targets: elements })
anime( {targets: document.querySelector('#left-panel') })
Animating CSS properties¶
- Animate change of the style property
top
. The.box
elements start withtop: -58vh
anime({
targets: '.box',
top: '0', // animate top CSS property
color: '#F0F', // animate color CSS property
duration: 5000
});
Animate when clicked¶
- We can define functions that apply specific animations that we can apply to any element later:
function fly(element) {
anime({
targets: element,
top: '-58vh',
duration: 2000
});
}
// Apply `fly` animation to `.box` elements on click
document.querySelectorAll(".box").forEach((box) => {
box.addEventListener('click', (event) => {
fly(event.target);
});
});
Animating transforms¶
- Especially powerful when combined with CSS transformations like
translate
,scale
,rotate
, etc.
anime({
targets: '.box',
translateX: '80vmin', // move along x-axis
rotate: '1turn',
duration: 5000 // duration is in milliseconds
});
Animation parameters - configure how the animation will play¶
anime({
targets: '.box',
translateX: '20vw',
rotate: '1turn',
duration: 2000,
direction: 'alternate', // 0% -> 100% -> 0%
loop: true // loop animation
});
Easings - rate of change over time¶
- Easings affect the rate of change in the animation.
- E.g. start slow and accelerate, or start slow, accelerate and also slow down before end.
anime({
targets: '.box',
translateX: '31vw',
duration: 2000,
easing: 'linear',
//easing: 'easeInCubic',
//easing: 'easeInOutSine',
//easing: 'easeInOutBack',
direction: 'alternate',
loop: true
});
Stopping animation¶
- remove animation completely from targets
anime.remove(targets)
- pause/resume animation
// `animation` is the animation object returned by `anime()`
animation.pause()
animation.play()
- run when animation is done
- set
complete
option to a callback function or use
- set
anime({...}).finished.then(callback)
Doing something when the animation is done¶
- Use complete option
anime({
...
complete: callback
}
- Or use JavaScript "promise" feature
anime({...}).finished.then(callback)
- Promise using reference to the animation
let animation = anime({...});
animation.finished.then(callback);
- Note:
callback
is a function
Click and hide¶
function hide(element) {
anime({
targets: element,
opacity: 0,
duration: 2000,
}).finished.then(() => {
element.classList.add("hidden");
});
}
document.querySelectorAll(".box").forEach((box) => {
box.addEventListener('click', (event) => {
hide(event.target);
});
});
- Example using complete property: https://codepen.io/jodyfoo/pen/mdBbzzB
- Example using promise: https://codepen.io/jodyfoo/pen/abLoRxN