1. Introduction

From Michael Nielsen (emphasis mine):

Struggling with a project you care about will teach you far more than working through any number of set problems. Emotional commitment is a key to achieving mastery.

Before we learn to develop web applications, we need a basic understanding of the Internet and the World Wide Web. For a more in-depth explanation of these two terms, take a look at en.wikipedia.org/wiki/Internet and en.wikipedia.org/wiki/Hypertext_Transfer_Protocol. The Internet is a globally distributed network of networks. It connects billions of computers and devices allowing them to communicate with each other. The World Wide Web (WWW) is just one of many services running on the Internet. It is a huge collection of documents and other resources interlinked via hyperlinks. Each resource has a uniform resource locator (URL), which gives access to this resource. Typically we use browsers (e.g. Mozilla Firefox, Google Chrome, Microsoft Edge, Apple Safari) to access the Internet. Browsers use the Hypertext Transfer Protocol (HTTP) to communicate with other computers, so called web servers, on the Internet. The Internet uses a whole suite of protocols that are split into several layers. At the top level, the application layer, we have HTTP and many other protocols. Below, on the transport layer, we have the Transport Control Protocol (TCP). Beneath this layer we have the Internet Protocol (IP) on the Internet layer.

The WWW has evolved significantly since the early nineties. Today the web browser and related technologies are increasingly becoming the platform of choice for application development, for a number of reasons:

  1. Write once run anywhere. A web browser is installed by default on virtually every desktop, tablet, smartphone and other devices. A web application will run on all of these devices without requiring the user to download and install anything or the developer to provide executables for different operating systems.

  2. Updates are instantaneous, i.e. the next time the user uses the application, he/she will automatically be using the latest version.

  3. The performance of browser JavaScript engines rivals the best Java just in time compilers (JIT) and the gap to compiled C++ and assembler is dwindling. Today’s web apps use multithreading, accelerated 3D graphics and many other techniques that make full use of the available hardware.

  4. There are a large amount of standard application programming interfaces (API) as well as highly sophisticated open source libraries for all kinds of purposes.

  5. A virtually unlimited amount of documentation is available.

The following is a small sample list of web applications to provide a glimpse of what can be done: .. codepen.io .. mail.google.com .. www.draw.io .. www.cubeslam.com/qoxerr

2. Operating systems

Some recommendations on how to choose between Linux and Windows as a server platform can be found at www.singlehop.com/blog/linux-servers-vs-microsoft-windows-servers and www.1and1.com/digitalguide/server/know-how/linux-vs-windows-the-big-server-check among many others.

2.1. Ubuntu

2.1.1. Installation and configuration

The server guide provides the details. Get the ISO from Ubuntu and create a bootable USB stick using for instance Rufus.

If you have your own domain, use Certbot to get a free certificate. Use certbot --apache to create, certbot --apache --expand to expand and certbot renew to renew all certificates. Follow SSL v3 goes to the dogs - POODLE kills off protocol and forward secrecy:

SSLProtocol All -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
a2enmod headers

Add the following line to default-ssl.conf in the <VirtualHost default:443> directive:

Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"

Make sure that the file headers.load is in the mods-enabled folder. If it isn’t, copy it from mods-available. Add your site to hstspreload.appspot.com.

Test your server security.

To set umask permanently, add umask 0027 to /etc/profile or change the following in /etc/pam .d/common-session (cf. serverfault.com/questions/231717/how-to-get-full-control-of-umask-pam-permissions):

session optional pam_umask.so umask=0027

If you get the error apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 when restarting Apache, edit your /etc/hosts file and make sure it contains 127.0.0.1 localhost servername.domain.com servername (cf. source).

.htaccess

In order to be able to use .htaccess files:

  1. The Apache rewrite module needs to be enabled, if it isn’t already:

    a2enmod rewrite
    apache2ctl restart
  2. The AllowOverride All directive needs to be in your Apache config file (usually in /etc/apache2) for the directory tree where your access file is located.

For the rest see Security.

Missing .Xauthority file

If you log in via SSH and get this message use

ssh -X user@host

to have the file created (source).

2.1.2. Administration

To simplify package management you might want to install wajig.

Command line

To learn the Linux command line study linuxcommand.org.

Useful commands
Recursively search for strings in files
grep -rHn "string" /path
Kernel version
uname -r
Last logged in users
last
Determine Ubuntu version
lsb_release -a

or

cat /etc/issue
Locate a file
whereis <filename>
find without permission denied messages
find / -name <name> -print 2>&-

which is equivalent to (cf. source):

find / -name <name> -print 2>/dev/null
chmod all directories but not files

find . -type d -exec chmod o+rx {} +

chmod all executable files

find . -executable -type f -exec chmod o+rx {} +

Activate root

sudo passwd root and give root a password. Afterwards, you can for instance run su -.

Setting umask permanently
Reconfigure package

dpkg-reconfigure package

Uninstall package completely

apt purge package

Nano jump to beginning or end of file

To jump to the beginning use Ctrl+W followed by Ctrl+Y. To jump to the end use Ctrl+W followed by Ctrl+V.

Monitor socket connections

ss

Exclude files/folders from archives
# https://stackoverflow.com/questions/984204/shell-command-to-tar-directory-excluding-certain-files-folders
# Note the files/folders excluded are relatively to the root of your tar.
tar cfz /media/Backup232/www`date +%a`.tar.gz --exclude='owncloud' --exclude='everling.lu/WAD' /var/www 2>/dev/null
Redirect output streams
  1. Redirect stdout to one file and stderr to another file: command > out 2>error

  2. Redirect stderr to stdout (&1), and then redirect stdout to a file: command >out 2>&1

  3. Redirect both to a file: command &> out

Main directories
Using USB drives

Find out what the drive is called using fdisk -l, then mount the drive using mount <drive> /media/usb. To unmount use umount /media/usb.

To have a drive mounted automatically, add it to /etc/fstab. Use lsblk -O or fdisk -l to get the required information for your drive. After a system reboot, your drive should be available.

Backup

System backup is essential. Install storeBackup, create a directory for your backups and add a crontab task using crontab -e. Here is an example crontab entry where an email is sent after backup completion (cf. how-to-sendmail):

* 3 * * 1 /opt/storeBackup/bin/storeBackup.pl --sourceDir /var/www --backupDir /root/backup
| sed 's/^/To: mail address\nSubject: backup\n\n/' | sendmail -t

Alternatively you can set up a systemd timer. A discussion about pros and cons can be found at cron vs systemd timers.

Instead of ro in addition to local backup you might consider cloud backup using Duplicity, preferably with encryption.

Recover deleted files

Install and use extundelete.

Mail

Whilst you may not want to run your own mail server, if you want to enable your server to send emails, install Postfix or better a complete mail server. For Postfix configuration read steam.io/2013/04/01/postfix-rate-limiting and easyengine.io/tutorials/mail/postfix-debugging. To send an email, create a file with content structured as in the following example and then use sendmail recipient < file:

Subject: everling.lu backup job

Backup has been run

You can manage emails using mail or more comfortably using mutt.

Remote copy
rcp -prv source target
Fail2ban

Alternative to stopping fail2ban:

From the Linux command prompt type: service fail2ban stop

To start fail2ban: service fail2ban start

To reload fail2ban if you have a banned IP: service fail2ban restart Restarting will clear the ban.

To prevent fail2ban from banning IPs on the local network or other places: Modify /etc/fail2ban/jail.conf look for the line:

#ignoreip 127.0.0.1 192.168.1.24/24 …​.

uncomment it by removing the # and then change the IP addresses. To have fail2ban ignore network 192.168.20.0 (255.255.255.0), add 192.168.20.0/24 to the above line. You can add as many networks as you like. Just leave a space.

Just a note, if you have a VPN or a tunnel, you should add its network too. I’ve had the tunnel banned!

You can see if fail2ban has banned an IP by checking /var/log/fail2ban.log. It will indicate banned and unbanned IP addresses.

To unban an IP address, use fail2ban-client set YOURJAILNAMEHERE unbanip IPADDRESSHERE

The hard part is finding the right jail: Use iptables -L -n to find the rule name then use fail2ban-client status to get the actual jail names. The rule name and jail name may not be the same but it should be clear which one is related to which (source).

Stop Apache DOS attacks as described in r3dux.org/2013/06/how-to-stop-apache-dos-attacks-with-fail2ban.

Grub2

Grub2 is the default boot loader and manager for Ubuntu.

cURL
git clone https://github.com/curl/curl.git
apt install autoconf libtool
./buildconf
./configure
Check disk health
apt install smartmontools
fdisk -l
smartctl -c /dev/sdX
smartctl -t short /dev/sdX
smartctl -H /dev/sdX

2.2. Windows

2.2.1. Server

www.microsoft.com/en-us/cloud-platform/windows-server

info.microsoft.com/rs/157-GQE-382/images/IntroducingWindowsServer2016_ebook.pdf

info.microsoft.com/rs/157-GQE-382/images/HybridCloud_WS_UltimateGuidetoWindowsServer2016.pdf

docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/ad-ds-design-and-planning

Planning

In Windows Server 2016, we cannot convert between Core and Desktop anymore. Quote from docs.microsoft.com/en-us/windows-server/get-started/getting-started-with-server-with-desktop-experience:

Unlike some previous releases of Windows Server, you cannot convert between Server Core and Server with Desktop Experience after installation. If you install Server with Desktop Experience and later decide to use Server Core, you should do a fresh installation.
Migrations scenarios
Scenario Comments

Greenfield

Start from scratch. This implies:

  1. making sure all required system and networking hardware is installed and configured.

  2. the Active Directory environment has been designed. .

2.2.2. Client

Administration
DNS

Display data in the DNS resolver cache:

ipconfig /displaydns

Flush DNS resolver cache:

ipconfig /flushdns

Renew all DHCP leases and reregister all DNS names:

ipconfig /registerdns

Trace a route to a server:

tracert hostname
Robocopy

Useful options include /S /PURGE /COPYALL /DCOPY:T /R:2 /W:1 /A-:SH /xj.

To get rid of undeletable recursive directories (cf. answers.microsoft.com/en-us/windows/forum/windows_7-files/windows-7-infinite-loop-while-using-robocopy/20f32f0c-4cb9-4125-923d-6a57e4d27232) we create an empty dir and then robocopy empty dest /MIR.

Tips & Tricks

Get rid of language bar icon

www.askvg .com/fix-input-indicator-icon-comes-back-in-taskbar-notification-area-after-restarting-windows[^]

www.howtogeek.com/howto/windows/bring-misplaced-off-screen-windows-back-to-your-desktop-keyboard-trick

Fix DPC WATCHDOG VIOLATION

www.youtube.com/watch?v=VXgAFmPI21g

How to Add Programs, Files, and Folders to System Startup in Windows

www.howtogeek.com/208224/how-to-add-programs-files-and-folders-to-system-startup-in-windows-8.1

List of Windows tools

www.ghacks.net/2017/06/11/list-of-windows-tools

windowsreport.com/weather-app-live-tile-not-working-windows-10

How to access the BIOS

www.addictivetips.com/windows-tips/access-bios-pc

Get rid of hiberfil.sys

www.howtogeek.com/howto/15140/what-is-hiberfil.sys-and-how-do-i-delete-it

How to convert Windows installations to virtual machine images

lt3000.blogspot.com/2018/09/on-chinas-putative-real-estate.html

3. Tools of the trade

3.1. Portable work environment

3.1.1. Vagrant

Vagrant serves to isolate dependencies and their configuration within a single disposable, consistent environment, without sacrificing any of the tools you are used to working with (editors, browsers, debuggers, etc.). Once you or someone else creates a single Vagrantfile, you just need to vagrant up and everything is installed and configured for you to work. Other members of your team create their development environments from the same configuration, so whether you are working on Linux, Mac OS X, or Windows, all your team members are running code in the same environment, against the same dependencies, all configured the same way. Say goodbye to "works on my machine" bugs.

3.1.2. Docker

Docker containers wrap a piece of software in a complete filesystem that contains everything needed to run: code, runtime, system tools, system libraries – anything that can be installed on a server. This guarantees that the software will always run the same, regardless of its environment.

If you run into docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: run usermod -a -G docker <user> (cf. techoverflow.net/2017/03/01/solving-docker-permission-denied-while-trying-to-connect-to-the-docker-daemon-socket).

Kill all docker containers at once: gist.github.com/evanscottgray/8571828

3.1.3. VirtualBox

The techniques and features that VirtualBox provides are useful for several scenarios:

  • Running multiple operating systems simultaneously. VirtualBox allows you to run more than one operating system at a time. This way, you can run software written for one operating system on another (for example, Windows software on Linux or a Mac) without having to reboot to use it. Since you can configure what kinds of "virtual" hardware should be presented to each such operating system, you can install an old operating system such as DOS or OS/2 even if your real computer’s hardware is no longer supported by that operating system.

  • Easier software installations. Software vendors can use virtual machines to ship entire software configurations. For example, installing a complete mail server solution on a real machine can be a tedious task. With VirtualBox, such a complex setup (then often called an "appliance") can be packed into a virtual machine. Installing and running a mail server becomes as easy as importing such an appliance into VirtualBox.

  • Testing and disaster recovery. Once installed, a virtual machine and its virtual hard disks can be considered a "container" that can be arbitrarily frozen, woken up, copied, backed up, and transported between hosts.

  • On top of that, with the use of another VirtualBox feature called "snapshots", one can save a particular state of a virtual machine and revert back to that state, if necessary. This way, one can freely experiment with a computing environment. If something goes wrong (e.g. after installing misbehaving software or infecting the guest with a virus), one can easily switch back to a previous snapshot and avoid the need of frequent backups and restores.

  • Any number of snapshots can be created, allowing you to travel back and forward in virtual machine time. You can delete snapshots while a VM is running to reclaim disk space.

  • Infrastructure consolidation. Virtualization can significantly reduce hardware and electricity costs. Most of the time, computers today only use a fraction of their potential power and run with low average system loads. A lot of hardware resources as well as electricity is thereby wasted. So, instead of running many such physical computers that are only partially used, one can pack many virtual machines onto a few powerful hosts and balance the loads between them.

3.2. Integrated Development Environments

3.2.1. Codiad

Codiad is a web-based IDE framework with a small footprint and minimal requirements.

3.2.2. Atom

Get the editor from atom.io. Installation instructions can be found in the flight manual. It is recommended to install the two Asciidoc packages for Atom as mentioned in Asciidoc live preview. Activate live preview with ctrl-shift-a as described in asciidoc-preview.

Portable installation

Follow Portable Mode in flight-manual.atom.io/getting-started/sections/installing-atom. To create the .atom folder for the portable installation see gist.github.com/ozh/4131243.

3.2.3. PhpStorm

PhpStorm is the ideal IDE for web app development. It provides full database and server integration.

Portable installation

To install PhpStorm on a portable drive, go to JetBrains and click the Download button. Cancel the automatic download of the .exe file and right click direct link, select Copy Link Location, paste the link into a new tab and replace the exe extension with zip, then press enter. This will download and open the zipped version of PhpStorm. Extract it to your portable drive.

Open the file bin/idea.properties, replace the line starting with #idea.config.path with idea.config.path=${idea.home}/.WebIde/config, #idea.system.path with idea.system.path=${idea.home}/.WebIde/system, #idea.plugins.path with idea.plugins.path=${idea.config.path}/plugins and #idea.log.path with idea.log.path=${idea.system.path}/log.

In order to avoid having to reenter your SSH password after every logout set the following:

phpStormPortable1

This works only for project deployment servers, not for global ones. Unfortunately this does not work for DB sources for which you’ll have to reenter the SSH password.

If you have settings from another PhpStorm installation that you’d like to import, you can do this via File → Import Settings…​.

Project setup

First, we set all file encodings to UTF-8 in order to avoid any problems with special characters. Search for file encodings in the search box. Given the constant PhpStorm UI changes, your file encoding settings may be located in a different place:

File encodings

Now we configure a new project:

phpStorm1
phpStorm2
phpStorm3
phpStorm4
phpStorm5
phpStorm6
phpStorm7
phpStorm8
phpStorm9
phpStorm10
phpStorm11
phpStorm12
phpStorm13
Database connection setup

First we need to make sure that the drivers are loaded:

createDB2

Then we need to create a data source:

createDB3

Make sure to right click the connection and select Make Global so that you don’t need to configure it for each project:

createDB4
Template adjustment
PhpStorm template adjustment

3.2.4. NetBeans

Download NetBeans.

NetBeansDownload1

If this is the first time you install NetBeans on your device, you need to install the Java Development Kit (JDK) first (point 1 on the screenshot). This will open the following screen:

JDKDownload1
JDKDownload2

Accept the license agreement and select the right JDK version for your operating system.

When the JDK is installed you can install NetBeans. For our purposes we only need the HTML5 + PHP version (point 2).

Alternatively you can install the NetBeans Java SE bundle (point 3), which includes the JDK and NetBeans. This will taker you to the following screen:

NetBeansJDKDownload1

Accept the license agreement and select the right JDK version for your operating system.

Portable installation

If you want to install NetBeans on a portable device, you can download it as a zip file (point 4 on the first screenshot in the previous subsection.) This will take you to the following screen:

NetBeansPortable1
HTML5 Project setup

Click on NetBeansNewProjectIcon or File → New Project…​ or Ctrl + Shift + N:

NetBeansHTML5Project1

Specify the name of your project and where you’d like to save it:

Name and locate a new NetBeans HTML5 project

We don’t use site templates:

Site template selection

We also don’t use a JavaScript library, so we can just click on Finish:

JavaScript library selection

You can now see your new HTML5 project structure in the upper left corner. NetBeans has also opened the index.html file, which is the default name of the main project HTML5 file:

Our new HTML5 project

The content of this file is defined in the corresponding template. See NetBeans templates for guidance on how to change this.

In order to add a new file to your project, right click on Site Root and select the file type:

Adding a new file to the HTML5 project
PHP Project setup

Click on NetBeansNewProjectIcon or File → New Project…​ or Ctrl + Shift + N. If you are using an older version of NetBeans (< 7.4), you may need to install the PHP plugin via Tools → Plugins → Available Plugins and select PHP. After restarting NetBeans, you should get the following screen when creating a new project:

Create a new NetBeans PHP project

Specify the name of your project and where you’d like to save it. Choose the latest PHP version and keep UTF-8 as the default encoding. The latter makes sure, that non-English characters such as é or ä are handled correctly:

Name and locate a new NetBeans project

If you want to run your project on your local web server, select the corresponding option. We’ll run our projects on Foxi, thus we specify Remote Web Site (FTP, SFTP):

NetBeans run configuration

In order to be able to upload our files to Foxi, we need to define a remote connection, thus we need to click on Manage…​.

Create new connection

The host name is foxi.ltam.lu. The port number needs to be 22. Enter your IAM code as user name and your 11-digit matricule as password (cf. FOXI_login_2017.pdf) to learn how to change your password). The initial directory should be set to /www/your class/your IAM code:

Manage remote NetBeans connection

Now click the connection test button. You should get the following prompt, which you should confirm:

Foxi certificate confirmation

The following dialog should appear:

Foxi connection success

Now you can click on OK. We do not use any PHP framework, so you can click the Finish button in the final dialog. In the projects window (top left) you should now see your new PHP project. You can expand the project structure by clicking on the +-sign in front of it. Under Source Files you’ll see your new PHP project:

New PHP project

You can now start working on your project. When you save your changes, you’ll have to confirm that you want to connect to Foxi. You’ll then see confirmation in the output window that the file has been uploaded to Foxi:

NetBeans output window

We can verify this using our SFTP client (cf. SFTP):

Check NetBeans file upload with WinSCP

We can add a new file to our project by right clicking on Source Files and selecting New  PHP File…​:

NetBeans project setup
Database connection setup

In order to connect to MySQL on Foxi we need to open a secure shell (SSH) tunnel. Given that NetBeans does not provide a built-in tunnel functionality, we need to use an external SSH client, for instance Putty.

Enter the tunnel data:

Tunnel data

Then add the tunnel:

Tunnel

Save the session:

Tunnel session

Create a new DB connection:

New DB connection 1
New DB connection 2
New DB connection 3

Set the default DB:

Default DB 1
Default DB 2

View table data:

View table data 1
View table data 2
Template adjustment
Template 1
Template 2
Template 3
Useful NetBeans shortcuts

Pressing Alt+Shift+F or selecting Source  Format will reformat the source code according to the settings in Tools  Options:

Reformat the source code

3.3. SSH

3.3.1. Clients

You can connect to your server using a secure shell (SSH) client such as Putty, which is however rather rudimentary. Therefore you might prefer to use more convenient GUIs, most of which use Putty in the background:

3.3.2. SFTP

SFTP is the secure, i.e. encrypted, version of FTP, the file transfer protocol. As its name suggests, it is used to transfer files from our development machine to the server and vice versa. There are a number of SFTP clients freely available, e.g. Filezilla. I prefer WinSCP. Download the portable executable and unzip it to a folder of your choice. Set the configuration storage option on the storage page in the preferences dialog to INI file. This will lead WinSCP to save its configuration in an ini file in the same folder where the program itself is located. If you have stored WinSCP on your USB stick, the configuration will also be stored there, so you won’t have to reenter the server data every time you use the program:

Set WinSCP storage preferences

Set up your server connection and click save. Click login to connect to the server and begin transferring your files:

Configure WinSCP login

3.4. Browsers

3.4.1. Firefox

If you incur problems loading a web page that works with other browsers, the source is often to be found with a specific add-on. To confirm this is the case, close Firefox and start it in safe mode, for instance by holding the Shift key pressed whilst launching Firefox (cf. Safe Mode).

To see all preferences that can be set, enter about:config in the address bar.

3.5. Documentation with Asciidoctor

3.5.2. Tips & tricks

3.6. Content Management Systems

According to Wikipedia:

A content management system (CMS) is a computer application that supports the creation and modification of digital content using a simple interface to abstract away low-level details unless required, usually supporting multiple users working in a collaborative environment.

Basically a CMS aims to enable the creation and maintenance of a web site without any knowledge of HTML, CSS, JavaScript etc. There are many CMS out there, butWordpress seems to be the most popular by a wide margin. According to W3Techs as of September 24, 2018:

47% of the websites use none of the content management systems that we monitor. WordPress is used by 31.8% of all the websites, that is a content management system market share of 60%.

However, it is important to choose a CMS based on your specific requirements. As this article argues, there’s no "best" CMS, each CMS has its strengths and weaknesses and is best suited for specific types of projects and environments.

Top Ten Reviews provide a useful comparison of the main CMS.

Using an open-source CMS such as WordPress may not always be the best solution, see for instance 7 Solid Reasons not to Use WordPress.

3.6.1. Wordpress

Wordpress provide detailed installation instructions. After installation you should study first steps and codex.wordpress.org/Roles_and_Capabilities

4. Client side programming

4.1. HTML5

HTML stands for Hyper Text Markup Language. HTML5 is the latest version of this markup language for describing web documents or pages. It consists of a series of tags. An element or tag is like a command or instruction that tells the browser about the structure and meaning (also called semantics) of the content of a specific part of our web page.

The official standard can be found here and the complete set of HTML elements here. Cheat sheets can be very helpful.

4.1.1. Basic structure

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>HTML5 Skeleton</title>
    <meta charset=UTF-8>
    <link href=style1.css rel=stylesheet>
    <script src=script1.js></script>
  </head>
  <body>

  </body>
</html>
body {
  background-color: lightseagreen;
}
window.alert("Our first JavaScript has been loaded and executed!");

The first line of an HTML5 document should tell the browser how to process the document by specifying the Document Type Definition (DTD).

After the DTD comes the <html> tag, which specifies the language using the lang attribute. See tag list for a list of available language codes. The <html> tag encompasses the whole HTML document consisting of a <head> and a <body> part.

<head>

In the head part we specify the title and the character encoding, which for our purposes will be UTF-8. UTF-8 has the advantage that it handles special characters, e.g. ö and é, correctly. To learn more about character encodings, see www.w3.org/International/tutorials/tutorial-char-enc. Then we include our external CSS and JavaScript files (more on those in the following chapters).

The following elements can go inside the <head> element:

  • <title> (this element is required in the head section)

  • <style>

  • <base>

  • <link>

  • <meta>

  • <script>

  • <noscript>

<body>

The body part contains the actual page content.

Opening and closing tags

For most, but not all, of the HTML5 tags, there is an opening and a closing tag, as in <body></body>. There are a few standalone tags, such as <hr> to display a horizontal line.

Tabs, new lines and spaces

Browsers ignore tabs, new lines and most spaces. For instance, the following two HTML documents produce exactly the same output except for the words "a well", which have been replaced with "an ill":

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Example of a well formatted HTML document</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      This is a well formatted HTML document.
    </main>
  </body>
</html>
<!DOCTYPE html>
<html



              lang=en>




  <head><title>Example

    of       an ill          formatted
  HTML document</title>
    <meta charset=UTF-8></head><body><main>This is    an
  ill formatted HTML document.</main></body></html>
Comments

In order to help others (and ourselves) understand our HTML documents, it is a good idea to include comments where appropriate. Comments are embedded between <!-- and -->. A comment can span several lines and is not displayed by the browser.

<!DOCTYPE html> <!-- The document type is compulsory. -->
<html lang=en> <!-- Don't forget to specify the language. -->
  <head>
    <!--
      This is a very simple illustration of comment usage in HTML5.
      You do not have to use comments.
      Use them where it makes sense.
    -->
    <title>This is an example of comment usage in HTML5</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <!-- The main part is currently empty. If you have a good idea
         on how to fill it, let me know! :D
      -->
    </main>
  </body>
</html>

4.1.2. Validation

In order to be sure that our HTML5 document complies with the official standard and should thus run according to our plans in all compliant browsers, we need to validate our HTML5 and CSS3 files either using the official validators at validator.w3.org and jigsaw.w3.org/css-validator or using the Firefox extension users.skynet.be/mgueury/mozilla/index.html. The latter will install a validation button in the Firefox add-on bar. A simple double click will display the source code of the current page and run it automatically through the official HTML5 validator. With a right click on the button we launch the CSS3 validator via the Advanced menu. Here are the outputs of the validators for the solution of the next exercise:

The W3C HTML validator
The HTML validation Firefox extension
Launching the W3C CSS validator
The W3C CSS validator

The warning messages can be safely ignored. They just tell us that the validator is still experimental. Given that the HTML5 standard is not expected to be finalized for many years, this is unlikely to change any time soon.

4.1.3. Planning

In order to produce a top notch web site, we need to plan our work carefully.

Brain storming

First we need to think about the purpose of our web site. What are the big concepts and ideas that will drive our content? A useful tool in this respect is a mind map. We’ll be using the open source FreeMind software, which is available from freemind.sourceforge.net/wiki/index.php/Main_Page.

As an example, here’s the mind map for WMOTU Lab v1:

mind map
Blueprint

Now that we have clarified the big picture content of our site, it’s time to sketch out the rough and basic structure. For this purpose we’ll use an open source vector drawing software named Inkscape.

WMOTU Lab v1 structure
Requirements specification

The standard professional approach to project planning is to produce a requirements specification. Such a document specifies the project requirements, including:

  1. Functionality

  2. Prototype/model

  3. Logical site structure

  4. Physical site structure

  5. Time plan

  6. Development environment and technologies

Here is a minimalist example for the WMOTU Address Book app developed in WMOTU Address Book:

Functionality

The app serves as an electronic address book. New users need to sign up by providing a login name and password. After logging in, the user enters the main page, where he can logout and view a listing of all his addresses. He can delete or edit each address as well as add a new one. All addresses are stored in a MySQL database on the server.

Physical site structure
WMOTU Address Book Physical
Time plan

The final product will be delivered electronically on 24.6.14.

Development environment and technologies

Development will be done mainly with PhpStorm. Main technologies used will be HTML5, CSS3, PHP5 and MySQL5.

4.1.4. <br>

As we have seen, new lines in our source code are converted to a single space by the browser. To split our text into different lines, we use the <br> tag, which inserts a line break. As already mentioned in this chapter, for most, but not all, HTML5 tags there is an opening and a closing tag. <br> is one of the exceptions. It is a so called empty tag, meaning it has no closing tag. This tag should not be used to separate paragraphs. For the latter purpose we use the <p> tag, cf. <p>.

Example:

br
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Break row example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      This is the first line.
      This should be the second one.<br>
      This is the second one.
    </main>
  </body>
</html>

4.1.5. <p>

This tag is used to mark up a paragraph. The browser automatically adds margin above and below each paragraph (see section Block vs inline elements).

Example:

p1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Paragraph example</title>
    <meta charset=UTF-8>
    <style>
      #p1 {
        background-color: green;
      }

      #p2 {
        background-color: gold;
      }
    </style>
  </head>
  <body>
    <main>
      <p id=p1>
        This is the first paragraph. Note that the text always occupies the full
        width of the browser window. If you change the width of your browser window,
        the number of lines that your paragraph occupies changes too.
      </p>
      <p id=p2>
        This is the second paragraph. Use the Firefox console to inspect the margins
        used by your browser.
      </p>
    </main>
  </body>
</html>

Resize your browser and observe the behavior of your paragraphs. Note that we’ve given each paragraph an id attribute. This allows us to style each paragraph’s background color individually using CSS. More on this in CSS3.

4.1.6. Phrase tags

Phrase tags are used to convey special meaning to text:

Name Description

<em>

emphasized text

<strong>

important text

<dfn>

definition term

<code>

computer code

<samp>

sample output from a computer program

<kbd>

keyboard input

<var>

variable

Here is a simple application:

Phrase tags
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Phrase tags example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <p>I'd like to emphasize <em>the following</em>. This is particularly
      <strong>important</strong>.</p>
      <p><dfn>HTML5</dfn> is the definition of greatness!</p>
      <p>The secret of the universe looks like this:
        <code>if (sunshine === true) window.alert("Smile!");</code>
      This will hopefully produce this output: <samp>Smile!</samp></p>
      <p>Enter <kbd>WMOTU</kbd> as your user name. It will be stored in
        <var>userName</var></p>
    </main>
  </body>
</html>

4.1.7. HTML entities

Some characters, such as < or >, are reserved in HTML. We thus cannot use them directly in our text as the browser would try to interpret them as part of a tag.

To get around this problem, we use character entities. A character entity has the form &entity_name or &#entity_number. The following table lists the reserved characters and their corresponding entities (see dev.w3.org/html5/html-author/charref for the complete list and digitalmediaminute.com/reference/entity for the Unicode codes):

Character Entity number Entity name Description

"

&#34;

&quot;

quotation mark

'

&#39;

&apos;

apostrophe

&

&#38;

&amp;

ampersand

<

&#60;

&lt;

less than

>

&#62;

&gt;

greater than

Application example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Entities usage example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      The HTML expert said &#34;Use HTML entities to display special characters in
      HTML&quot;. As you know, HTML tags start with an &lt; and close with a &gt;, as in
      <code>&lt;a&gt;</code>.
    </main>
  </body>
</html>

4.1.8. <header>

The <header> tag specifies a header for a document or section. It should be used for introductory content or navigation elements. You can have several of these in one document, but they cannot be placed within a <footer>, <address> or another <header> element.

<h1> …​ <h6>

These tags specify headings at different levels:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Heading Example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <header>
      <h1>Heading level 1</h1>
      <h2>Heading level 2</h2>
      <h3>Heading level 3</h3>
      <h4>Heading level 4</h4>
      <h5>Heading level 5</h5>
      <h6>Heading level 6</h6>
    </header>
  </body>
</html>

A heading

4.1.9. Lists

We can choose between unordered and ordered lists. In each case, every list item is enclosed in <li></li> tags.

A paragraph may not contain lists.

<ul>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Unordered List Example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <header>
        <h1>Today's shopping list</h1>
      </header>
      <ul>
        <li>Meat</li>
        <li>Cheese</li>
        <li>Vegetables</li>
        <li>Water</li>
        <li>Bin bags</li>
      </ul>
    </main>
  </body>
</html>

ul1

<ol>

This element supports the following particular attributes:

Name Value Description

reversed

descending list order

start

number

start value

type

1, A, a, I, i

list marker

Ordered list
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Ordered List Example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <header>
        <h1>Today's shopping lists</h1>
      </header>
      <ol style="float: left">
        <li>Meat</li>
        <li>Cheese</li>
        <li>Vegetables</li>
        <li>Water</li>
        <li>Bin bags</li>
      </ol>
      <ol style="float: left" reversed>
        <li>Meat</li>
        <li>Cheese</li>
        <li>Vegetables</li>
        <li>Water</li>
        <li>Bin bags</li>
      </ol>
      <ol style="float: left" start=3 type=A>
        <li>Meat</li>
        <li>Cheese</li>
        <li>Vegetables</li>
        <li>Water</li>
        <li>Bin bags</li>
      </ol>
    </main>
  </body>
</html>

The style attribute value of float: left means that the list is floated left. As a result, the following element is placed right to the list instead of underneath. Remove the three style attributes and compare the result.

Nested lists

We can nest lists. This means we can have a list inside a list inside a list inside a list …​ as many times as we want. The only thing we need to watch is the correct nesting, i.e. we need to close the last opened list tag before we close the second last etc.

Example:

Nested list
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Nested list example</title>
    <meta charset=UTF-8>
    <style>
      body {
        background-color: black;
        color:            gold;
      }

      main {
        position:  relative;
        width:     550px;
        height:    230px;
        animation: move 30s linear 0s infinite alternate;
      }

      /* cf. http://www.w3schools.com/css/tryit.asp?filename=trycss3_animation5 */
      @keyframes move {
        0% {
          background-color: blue;
          left:             0px;
          top:              0px;
        }
        25% {
          background-color: white;
          left:             550px;
          top:              0px;
        }
        50% {
          background-color: red;
          left:             550px;
          top:              230px;
        }
        75% {
          background-color: darkgray;
          left:             0px;
          top:              230px;
        }
        100% {
          background-color: red;
          left:             0px;
          top:              0px;
        }
      }
    </style>
  </head>
  <body>
    <main>
      <ul type=A>
        <li>Human life has several purposes:
          <ol>
            <li>To learn:
              <ol type=a>
                <li>HTML</li>
                <li>CSS</li>
                <li>JavaScript</li>
                <li>PHP</li>
                <li>MySQL</li>
              </ol>
            </li>
            <li>To have fun.</li>
            <li>To become a WMOTU.</li>
            <li>But the mother of all purposes is to become the ultimate problem
              solver.
            </li>
          </ol>
        </li>
        <li>Let's do it!</li>
      </ul>
    </main>
  </body>
</html>

Like the animation? We’ll do plenty of these in section Animation.

<dl>
The dl element represents an association list consisting of zero or more name-value groups (a description list). A name-value group consists of one or more names (dt elements) followed by one or more values (dd elements), ignoring any nodes other than dt and dd elements. Within a single dl element, there should not be more than one dt element for each name.
dl1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Description List Example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <dl>
        <dt>HTML5</dt>
        <dd>HTML5 is a markup language used for structuring and presenting content for the
          World Wide Web and a core technology of the Internet (cf.
          <a href=http://en.wikipedia.org/wiki/HTML5>http://en.wikipedia.org/wiki/HTML5).
          </a>
        </dd>
        <dt>CSS3</dt>
        <dd>Cascading Style Sheets (CSS) is a style sheet language used for describing the
          presentation semantics (the look and formatting) of a document written in a
          markup language. Its most common application is to style web pages written in
          HTML and XHTML, but the language can also be applied to any kind of XML
          document, including plain XML, SVG and XUL (cf. <a
            href=https://en.wikipedia.org/wiki/CSS>
            https://en.wikipedia.org/wiki/CSS</a>).
        </dd>
        <dt>JavaScript</dt>
        <dd>JavaScript (JS) is an interpreted computer programming language. As part of
          web browsers, implementations allow client-side scripts to interact with the
          user, control the browser, communicate asynchronously, and alter the document
          content that is displayed. It has also become common in server-side
          programming, game development and the creation of desktop applications
          (cf. <a href=https://en.wikipedia.org/wiki/JavaScript>https://en.wikipedia
            .org/wiki/JavaScript</a>).
        </dd>
        <dt>PHP5</dt>
        <dd>PHP is a server-side scripting language designed for web development but
          also used as a general-purpose programming language. PHP is now installed on
          more than 244 million websites and 2.1 million web servers (cf. <a
            href=https://en.wikipedia.org/wiki/PHP5>
            https://en.wikipedia.org/wiki/PHP5</a>).
        </dd>
      </dl>
    </main>
  </body>
</html>

4.1.10. <a>

The <a> tag defines a hyperlink, which is used to link from one page to another.

The most important attribute of the <a> element is the href attribute, which indicates the link’s destination.

By default, links will appear as follows in all browsers:

  1. unvisited link

  2. visited link

  3. active link

This element supports the following particular attributes:

Name Value Description

download

filename

target will be downloaded instead of opened. If filename is omitted it will be saved under the original filename. This works only for files located on the same server than the current page.

href

URL

URL of the page

hreflang

language_code

language of the linked document

media

media_query

the medium that the document is optimized for

rel

alternate, author, bookmark, help, license , next, nofollow, noreferrer, noopener, prefetch, prev, search, tag

Relationship between the current and the linked document. rel=noreferrer will prevent the destination page from learning which page you came from. See developer.mozilla.org/en-US/docs/Web/HTML/Link_types for an excellent overview of all link types. Why are noreferrer or noopener important? See mathiasbynens.github.io/rel-noopener.

target

_blank`, _parent, _self, _top framename

where to open the linked document

Here is an example that illustrates different values for the href attribute:

a1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>This is a simple hyperlink example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <header>
      <h1>Welcome to <a href=http://www.ltam.lu target=_blank>LAM</a>.</h1>
    </header>
    <main>
      <a href=a1contact.html>Contact us</a>
    </main>
  </body>
</html>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>This is a simple hyperlink example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <a href="http://www.ltam.lu/index.php?portal=26" target=_blank>How to find us</a>
      <a href=a1.html>Home</a>
      <a
        href="mailto:gilles.everling@education.lu?cc=everybody@world.com&amp;
        bcc=spy@NSA.gov&amp;subject=Top%20Secret%20Message&amp;
        body=This%20is%20the%20message%20body">Email the author</a>
    </main>
  </body>
</html>

Note that we need to provide the correct path to the file that the hyperlink is linking to via the href attribute. If we are linking to a page on the Internet, we need to specify the complete Unified Resource Locator (URL, cf. en.wikipedia.org/wiki/URL), which consists of the protocol, a colon, two slashes, a host, normally given as a domain name but sometimes as a literal Internet Protocol (IP) address, optionally a port number and finally the full path of the resource. The protocol used to access Internet pages is called Hypertext Transfer Protocol (HTTP).

If we link to a file within our web site, we use a relative URL, as shown in line 12 of a1.html. If we link to a file on another server, we need to provide an absolute URL as shown in line 9.

If we want to allow the user to send an email by clicking on a hyperlink, we use mailto, as shown in lines 11 to 12 of a1contact.html. Note that we can pass additional parameters such as carbon copy (cc) and black carbon copy (bcc) as well as the subject and body. We put a ? in front of the parameters. We assign a value to a parameter using = and each parameter/value pair is separated by a &. Also note that we need to encode spaces using %20, which is the corresponding hexadecimal (32 decimal) ASCII code (cf. www.w3schools.com/tags/ref_ascii.asp). The example above gives a validation error because of the new lines in the href attribute string. The whole string should be in a single line, but this would not allow the clean printing of the code in the book.

A hyperlink can also point to another place on the same page. For this purpose, we can use the id attribute on any tag. This is useful if our page contains a huge amount of content and we want to give the user the option to jump directly to a specific location on the page, instead of having to scroll down manually.

To nest a hyperlink inside another hyperlink we can use the <object> tag:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>This is a nested hyperlink example</title>
    <meta charset=UTF-8>
    <style>
      main > a > div {
        margin:           10px;
        padding:          20px;
        background-color: greenyellow;
      }

      main > a > div > object > a {
        background-color: yellowgreen;
      }
    </style>
  </head>
  <body>
    <main>
      <a href=https://students.btsi.lu target=_blank>
        <div>students.btsi.lu (<object><a
                  href=https://students.btsi.lu/evegi144/WAD/WAD.html target=_blank>WAD</a>)
          </object>
        </div>
      </a>
    </main>
  </body>
</html>

4.1.11. <img>

The <img> tag is used to insert an image. This element supports the following particular attributes:

Name Value Description

alt

text

alternate text for image, required for successful validation

crossorigin

anonymous, use-credentials

use third-party site images with canvas (cf. developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes)

height

pixels

image height

ismap

ismap

image is a server-side map (cf. www.w3schools.com/tags/att_img_ismap.asp)

src

URL

image URL

usemap

#mapname

image is a client-side map (cf. www.w3schools.com/tags/att_img_usemap.asp

width

pixels

image width

The alt attribute is required for successful validation. It can be used by screen readers, search engines and others. It will also be displayed by the browser in case the image cannot be displayed.

We should always specify the exact width and height of an image, as they allow the browser to allocate the space required to display the image before the image is loaded. Without them, the browser will have to adjust the page layout after the image has finished loading.

We do not use the width and height attributes to change the size of an image, as the full image will still be loaded by the browser. In order to produce a thumbnail for instance, we use a drawing program such as GIMP (www.gimp.org) or an online editor, e.g. pixlr.com/editor (cf. Image resizing). Be careful to specify the correct path and name of the image. Use .. to go up to the parent folder and / to separate folder and file names. We can embed images inside hyperlinks, like so:

img1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Image example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <a href="https://www.iconfinder.com/icons/131462/
 automatic_automatic_machine_automaton_dog_machine_machine_gun_robot_shadow_with_icon#size=512">
        <img src=dog_robot_with_shadow.png width=512 height=512
           alt="https://www.iconfinder.com/icons/131462/
 automatic_automatic_machine_automaton_dog_machine_machine_gun_robot_shadow_with_icon#size=512">
      </a>
    </main>
  </body>
</html>

Notice that we have to use quotes here for the value of the href and alt attributes, as they span several lines. The validator won’t like this.

Image formats

The three main image formats used on the Web are JPEG, PNG and GIF. There is a new kid on the block, called Scalable Vector Graphics (SVG), which requires a much deeper understanding to handle but offers a number of advantages, that we’ll look into in section SVG.

The key characteristics of the main image formats are summarized in the following table:

Format Compression Colors Transparency Animation

PNG

lossless

256 (8 bit), 16.7 million (24 bit) or 4.3 billion (32 bit)

yes

no

JPEG

lossy

16.7 million (24 bit)

no

no

GIF

lossless

256 (8 bit)

yes

yes

If you don’t need animation, PNG is the preferred format, particularly for web graphics.

If you want to create animated GIFs, take a loot at GifCam (blog.bahraniapps.com/gifcam), which allows you create and edit screencasts and save them as compact GIF images that can be easily embedded in your HTML5, like so:

Image resizing

Resizing an image with GIMP is easy:

GIMP1
GIMP2

After you’ve resized the image, export it under a new name:

GIMP3
Adding transparency

We can either use a program such as Online Image Editor (cf. www.online-image-editor.com/help/transparency) or use GIMP (www.bogotobogo.com/Gif/gimp-tutorial-transparency.php).

<map>

The <map> tag is used to define a client-side image-map. See www.w3schools.com/tags/att_img_usemap.asp for the details.

Here is a simple example application:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Image map example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <img src=dog_robot_with_shadow.png width=512 height=512 usemap=#dogmap
         alt="https://www.iconfinder.com/icons/131462#size=512">
      <map name=dogmap>
        <area shape=rect coords=10,0,250,150 href=map1head.html alt=Head>
        <area shape=rect coords=50,151,400,340 href=map1body.html alt=Body>
        <area shape=rect coords=0,350,512,512 href=map1leg.html alt=Leg>
      </map>
    </main>
  </body>
</html>
Logo creation

With Inkscape we can create a logo very easily.

inkscape1
inkscape1a
inkscape1b
inkscape2
inkscape2a
inkscape3
inkscape4
inkscape5
inkscape6

Export the bitmap using Ctrl+Shift+E and select page as export area.

inkscape7
figure and figcaption

4.1.12. <nav>

This element is used to create the main navigation on a site:

navigation1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Navigation</title>
    <meta charset=UTF-8>
    <link href=navigation1.css rel=stylesheet>
  </head>
  <body>
    <header>
      <h1>Navigation</h1>
      <nav>
        <ul>
          <li><a href=#>Home</a></li>
          <li><a href=#>About</a></li>
          <li><a href=#>Team</a></li>
          <li><a href=#>Shareholders</a></li>
          <li><a href=#>Contact</a></li>
        </ul>
      </nav>
    </header>
  </body>
</html>

Here the CSS3 file (don’t worry, we’ll address that topic in the next chapter):

body {
  background-color: lightseagreen;
}

h1 {
  margin: 0;
}

ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

li {
  display: inline;
}

4.1.13. <main>

The main element represents the main content section of the body of a document or application. The main content section consists of content that is directly related to or expands upon the central topic of a document or central functionality of an application.

Note: the main element is not sectioning content and has no effect on the document outline.

The main content section of a document includes content that is unique to that document and excludes content that is repeated across a set of documents such as site navigation links, copyright information, site logos and banners and search forms (unless the document or applications main function is that of a search form).

Authors MUST NOT include more than one main element in a document.

Authors MUST NOT include the main element as a child of an article, aside, footer, header or nav element.

4.1.14. <section>

The section element represents a generic section of a document or application. A section, in this context, is a thematic grouping of content, typically with a heading.

Example:

section1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Section example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <header>
    </header>
    <main>
      <section>
        <h1>Section 1</h1>
        This is a section with some content.
      </section>
      <section>
        <h1>Section 2</h1>
        This is another section with some content.
      </section>
    </main>
  </body>
</html>

The footer element represents a footer for its nearest ancestor sectioning content or sectioning root element. A footer typically contains information about its section such as who wrote it, links to related documents, copyright data, and the like.

When the footer element contains entire sections, they represent appendices, indexes, long colophons, verbose license agreements, and other such content.\end{quote}

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Footer example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <header>
      <nav></nav>
    </header>
    <main>

    </main>
    <footer>&copy; 2014 WMOTU</footer>
  </body>
</html>

4.1.16. <article>

The article element represents a complete, or self-contained, composition in a document, page, application, or site and that is, in principle, independently distributable or reusable, e.g. in syndication. This could be a forum post, a magazine or newspaper article, a blog entry, a user-submitted comment, an interactive widget or gadget, or any other independent item of content.
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Article example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <article>
        <h1>HTSTA</h1>
        <p>HTSTA is the first step to a fulfilling web developer career.</p>
      </article>
    </main>
  </body>
</html>

4.1.17. <aside>

The aside element represents a section of a page that consists of content that is tangentially related to the content around the aside element, and which could be considered separate from that content. Such sections are often represented as sidebars in printed typography.

The element can be used for typographical effects like pull quotes or sidebars, for advertising, for groups of nav elements, and for other content that is considered separate from the main content of the page.

The link above provides usage examples.

4.1.18. <div>

The div element has no special meaning at all. It represents its children. It can be used with the class, lang, and title attributes to mark up semantics common to a group of consecutive elements.

Note: Authors are strongly encouraged to view the div element as an element of last resort, for when no other element is suitable. Use of more appropriate elements instead of the div element leads to better accessibility for readers and easier maintainability for authors.

4.1.19. <q>

To mark up a short quotation, we use the <q> tag. This element has one special attribute, cite, that can be used to specify the source URL of the quote.

Example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Quote example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      George Orwell's <q>Politics and the English Language</q>  from 1946
    </main>
  </body>
</html>

4.1.20. <blockquote>

To mark up a longer quotation from another source, use the <blockquote> tag:

blockquote1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Blockquote example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      George Orwell, in his <q>Politics and the English Language</q> from 1946,
      provided the following insight:
      <blockquote>
        Political language… is designed to make lies sound truthful and murder
        respectable,
        and to give an appearance of solidity to pure wind.
      </blockquote>
    </main>
  </body>
</html>

4.1.21. Firefox console and Firebug

By pressing Shift+F2 you open the Firefox console, which is a great tool to analyse web pages:

FirefoxConsole1

The console tab displays information, warning and error messages and will be one of our most important development tools throughout our web app development journey. The inspector enables us to take a closer look at the styling of a particular element. The debugger will be very helpful to track errors in our JavaScript adventures. The style editor permits the real time changing of the current web page’s styles. Try it! The profiler serves to analyse the performance of our app and detect bottleneck. The network tab displays detailed information about what happens on the network. This will be very helpful once we start using HTTP forms and Ajax.

By pressing F12 you open Firebug, if this plugin is installed in your Firefox browser. If not, you can install it by selecting Tools  Add-ons. Search for Firebug and install it.

Firebug provides even more advanced analysis functionality than the console.

Style sheet

We have already seen at the beginning of this chapter an example of an external style sheet inclusion. We just have to include a link with the correct relationship attribute in the head of our document:

<link rel=stylesheet href=style.css>
Favicon
favicon1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Favicon example 1</title>
    <meta charset=UTF-8>
    <link rel=icon href=favicon.ico>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>

We can even have animated favicons, at least in Firefox:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Favicon example 2</title>
    <meta charset=UTF-8>
    <link rel=icon href=bear.gif type=image/gif>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>

Icon files can be 16×16, 32×32, 48×48, or 64×64 pixels in size, and 8-bit, 24-bit, or 32-bit in color depth.

4.1.24. <meta>

The <meta> tag is used to provide metadata, i.e. data that describes the document. This data is not displayed on the page, but can be processed by the browser, search engines or other web services.

This element supports the following particular attributes:

Name Value Description

charset

character set

character encoding for the document, we use utf-8

content

text

value associated with the http-equiv or name attribute

http-equiv

content-type, default-style, refresh

create HTTP header for content attribute

name

application-name, author, description, generator, keywords

name for the metadata

We use the charset attribute to specify the character encoding of our document. This should be set to Unicode, i.e. utf-8, as it allows us to use language specific characters such as é and ä. A list of all available character encodings can be found at www.iana.org/assignments/character-sets/character-sets.xhtml.

Before the advent of HTML5, http-equiv was used to set the character encoding, but no more. The value default-style can be used to specify the preferred stylesheet from a selection of link or style elements in case there are several in your document. The value refresh can be used to specify after how many seconds a page should be automatically refreshed (i.e. reloaded) or if it should redirect to another page. This can be useful for a site whose content changes rapidly and where you don’t want to use JavaScript. For instance, let’s assume you have a new web site and you want users to be automatically transferred from your old site to the new one. On your old site, you’d have the following main page: students.btsi.lu/evegi144/WAD/HTML5/metaredirect1.html

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Redirection example</title>
    <meta charset=UTF-8>
    <meta http-equiv=refresh content="3; url=metaredirect2.html">
  </head>
  <body>
    <main>
      <header>
        <h1>You'll be redirected to my new site in 3 seconds.</h1>
      </header>
    </main>
  </body>
</html>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Redirection example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <header>
        <h1>Welcome to my new site.</h1>
        Hope you like it!
      </header>
    </main>
  </body>
</html>

The name attribute can take one of the following values:

Value Description

application-name

name of the Web application

author

document author

description

description of the page content, can be used by search engines

generator

if the page was generated by a specific software

keywords

comma-separated list of keywords relevant to the page content targeted at search engines

Let’s look at an example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Meta name usage example</title>
    <meta charset=UTF-8>
    <meta name=application-name content="Meta name usage example">
    <meta name=author content="Gilles Everling">
    <meta name=description content="Meta name usage">
    <meta name=keywords content=meta,name,HTML5>
  </head>
  <body>
    <main>
      <header>
        <h1>A simple application of the <code>meta name</code> attribute.</h1>
      </header>
    </main>
  </body>
</html>

4.1.25. <table>

Tables are used to display tabular data, for instance the current national football league rankings. HTML5 tables must not be used for layout purposes. We’ll look at a number of appropriate ways to create a tabular layout later on.

A table consists of rows (<tr>) and cells (<td>). Thus, a Tic Tac Toe table would look like this:

table1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A simple Tic Tac Toe table</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <table>
        <tr>
          <td>X</td>
          <td>X</td>
          <td>O</td>
        </tr>
        <tr>
          <td>X</td>
          <td>X</td>
          <td>O</td>
        </tr>
        <tr>
          <td>O</td>
          <td>O</td>
          <td>X</td>
        </tr>
      </table>
    </main>
  </body>
</html>

The <table> tag supports only one attribute, border. It can have no value, "" or 1. In each of these cases, the table and each cell will have a border 1 pixel wide. Without this attribute the table and cells will have no border.

In most cases it is useful to have a table header, which contains the name or description of the data for each column. We may also need a table footer (cf. www.w3.org/TR/html51/tabular-data.html#the-tfoot-element). For this purpose, we split the table into a head, a body and a footer part, using the <thead>, <tbody> and <tfoot> tags. Header cells are displayed centered and bold using the <th> tag instead of <td>. We can add a caption using the `<caption> tag. Here’s an example:

table2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A simple table with caption, header, body and footer</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <table border>
        <caption>This term's grades</caption>
        <thead>
          <tr>
            <th>Subject</th>
            <th>Grade</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>SYSEX1</td>
            <td>51</td>
          </tr>
          <tr>
            <td>MATHE1</td>
            <td>45</td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <td>Average</td>
            <td>48</td>
          </tr>
        </tfoot>
      </table>
    </main>
  </body>
</html>

We can have cells span several columns and/or several rows, using the colspan and rowspan attributes. Example:

table3
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>colspan and rowspan example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <table border>
        <thead>
          <tr>
            <th>Name</th>
            <th>Village</th>
            <th colspan=2>Phone numbers</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Asterix</td>
            <td rowspan=2>Gaul</td>
            <td>123 456</td>
            <td>621 123 456</td>
          </tr>
          <tr>
            <td>Obelix</td>
            <td>123 457</td>
            <td>621 123 457</td>
          </tr>
        </tbody>
      </table>
    </main>
  </body>
</html>

The <th> and <td> tags can have a headers attribute. It links a cell to a given header cell. For this to work, the header cell needs an id. This has no impact on the page display, but may be used by screen readers. See www.w3schools.com/tags/att_th_headers.asp for an example.

The <th> tag can have a scope attribute, which indicates whether a header cell is a header for a column, row, or group of columns or rows. See www.w3schools.com/tags/att_th_scope.asp and developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Advanced.

For formatting purposes, we can use the <colgroup> tag, see www.w3schools.com/tags/tag_colgroup.asp.

Soon, we’ll see how we can style tables with CSS. Here’s a little foretaste:

table4
<!DOCTYPE html>
<html lang=de>
  <head>
    <title>HTML and CSS Table Demo</title>
    <meta charset=UTF-8>
    <style>
      body {
        background: radial-gradient(rgb(200, 50, 20), rgb(255, 255, 60), rgb(255, 50, 20),
        black) no-repeat fixed;
        overflow:   hidden;
      }

      h1 {
        font:        6em impact fantasy;
        text-shadow: 3px 3px white;
        animation:   introAnimation 7s;
        -webkit-animation: introAnimation 7s;
        overflow:    hidden;
        text-align:  center;
        position:    absolute;
      }

      @keyframes introAnimation {
        0% {
          top:       400px;
          font-size: 0.1em;
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(720deg);
          top:       0;
        }
      }

      @-webkit-keyframes introAnimation {
        0% {
          top:       400px;
          font-size: 0.1em;
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(720deg);
          top:       0;
        }
      }

      table {
        position:       absolute;
        overflow:       hidden;
        top:            200px;
        left:           0;
        color:          white;
        border:         2px ridge red;
        border-spacing: 0;
        transition:     left 5s;
        text-shadow:    1px 1px black;
      }

      table:hover {
        color: gold;
        left:  5000px;
      }

      table caption {
        font-size: 2em;
      }

      th {
        background-color: lightblue;
        text-align:       left;
        padding:          5px;
        margin:           0;
      }

      td {
        padding: 5px;
      }

      tr {
        padding: 5px;
      }

      tr:nth-of-type(odd) {
        background-color: green;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>T0IF HTSTA</h1>
    </header>
    <main>
      <table>
        <caption>Nationale Fußballtabelle</caption>
        <thead>
          <tr>
            <td></td>
            <th>Rang</th>
            <th>Mannschaft</th>
            <th>Punkte</th>
            <th>Tore A</th>
            <th>Tore B</th>
            <th>Diff.</th>
            <th>Spiele</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td rowspan=14 style="padding: 0; transform: rotate(-90deg);
            font-size: 3em;">22.5.13
            </td>
            <td>1.</td>
            <td>Fola</td>
            <td>53</td>
            <td>58</td>
            <td>20</td>
            <td>38</td>
            <td>24</td>
          </tr>
          <tr>
            <td>2.</td>
            <td>F91</td>
            <td>49</td>
            <td>45</td>
            <td>17</td>
            <td>28</td>
            <td>24</td>
          </tr>
          <tr>
            <td>3.</td>
            <td>Jeunesse</td>
            <td>47</td>
            <td>50</td>
            <td>25</td>
            <td>25</td>
            <td>24</td>
          </tr>
          <tr>
            <td>4.</td>
            <td>RM Hamm Benfica</td>
            <td>42</td>
            <td>47</td>
            <td>34</td>
            <td>13</td>
            <td>24</td>
          </tr>
          <tr>
            <td>5.</td>
            <td>Déifferdeng03</td>
            <td>41</td>
            <td>46</td>
            <td>31</td>
            <td>15</td>
            <td>24</td>
          </tr>
          <tr>
            <td>6.</td>
            <td>Gréiwemaacher</td>
            <td>36</td>
            <td>40</td>
            <td>32</td>
            <td>8</td>
            <td>24</td>
          </tr>
          <tr>
            <td>7.</td>
            <td>Kanech</td>
            <td>34</td>
            <td>38</td>
            <td>33</td>
            <td>5</td>
            <td>24</td>
          </tr>
          <tr>
            <td>8.</td>
            <td>Käerjéng</td>
            <td>31</td>
            <td>42</td>
            <td>48</td>
            <td>-6</td>
            <td>24</td>
          </tr>
          <tr>
            <td>9.</td>
            <td>RFCUL</td>
            <td>30</td>
            <td>38</td>
            <td>37</td>
            <td>1</td>
            <td>24</td>
          </tr>
          <tr>
            <td>10.</td>
            <td>Wolz 71</td>
            <td>30</td>
            <td>39</td>
            <td>61</td>
            <td>-22</td>
            <td>24</td>
          </tr>
          <tr>
            <td>11.</td>
            <td>Progrès Nidderkuer</td>
            <td>24</td>
            <td>26</td>
            <td>41</td>
            <td>-15</td>
            <td>24</td>
          </tr>
          <tr>
            <td>12.</td>
            <td>Etzella</td>
            <td>24</td>
            <td>35</td>
            <td>55</td>
            <td>-20</td>
            <td>24</td>
          </tr>
          <tr>
            <td>13.</td>
            <td>Peiteng</td>
            <td>14</td>
            <td>20</td>
            <td>50</td>
            <td>-30</td>
            <td>24</td>
          </tr>
          <tr>
            <td>14.</td>
            <td>Kayl/Teiteng Union05</td>
            <td>12</td>
            <td>27</td>
            <td>67</td>
            <td>-40</td>
            <td>24</td>
          </tr>
        </tbody>
      </table>
    </main>
    <script>
      document.querySelector("h1").style.left = (window.innerWidth - 566) / 2 + "px";
      addEventListener('resize', () => {
        document.querySelector("h1").style.left = (window.innerWidth - 566) / 2 + "px";
      });
    </script>
  </body>
</html>

4.1.26. Forms

HTML forms (www.w3.org/TR/html51/semantics.html#forms) are used to pass data to a server. They are the building blocks that allow the user to provide data to our application. When you use a search engine or buy something in an online shop, you use forms to enter your information.

An HTML form can consist of different input elements such as text fields, check boxes, radio-buttons, submit buttons, selection lists, text areas, labels etc.

Here’s the full list of form tags to be used within <form>:

Tag Description

<input>

single line text field

<textarea>

multi line text area

<label>

label, i.e. a text to be displayed next to a form element

<fieldset>

groups related elements

<legend>

caption (short description) for a field set

<select>

drop-down list

<optgroup>

group of related options in a drop-down list

<option>

option in a drop-down list

<button>

clickable button

<datalist>

option list

<keygen>

key-pair generator field

<output>

result of a calculation

All form elements are enclosed within the <form> tag. Here’s a simple example:

HTML forms1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A first form example using post</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <form method=post action=forms1.php name=x>
        First name: <input name=first_name required><br>
        Last name: <input name=last_name required><br>
        <input type=submit>
      </form>
    </main>
  </body>
</html>

When you submit your input, the forms1.php script gets executed on the server:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A first form example using post</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <?php
        if (isset($_POST['first_name'], $_POST['last_name']))
          echo '<p>Hi ' . $_POST['first_name'] . ' ' . $_POST['last_name'] . '</p>';
      ?>
    </main>
  </body>
</html>

Don’t worry about the PHP part, we’ll get into that later. Our example uses two of the most important <form> tag attributes: action and method. The former specifies the script on the server that should receive the form data. The latter indicates the method that should be used to send the data to the server, either GET, which sends the data via the URL or POST, which sends the data embedded within the HTTP request. Run our first form example, open the Firefox console and select the Network tab. Now enter your first and last names and submit the form. You should see something similar to this:

forms1

Now click on the forms1.php line and select the Params tab on the right:

forms2

As you can see, your input has been sent as form data, i.e. embedded inside the form.

Now let’s check what happens if we use the GET method: students.btsi.lu/evegi144/WAD/HTML5/forms2.html

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A first form example using get</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <form action=forms2.php>
        First name: <input name=first_name required><br>
        Last name: <input name=last_name required><br>
        <input type=submit>
      </form>
    </main>
  </body>
</html>

When you submit your input, the forms2.php script gets executed on the server:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A first form example using get</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <?php
        if (isset($_GET['first_name']) && isset($_GET['last_name']))
          echo '<p>Hi ' . $_GET['first_name'] . ' ' . $_GET['last_name'] . '</p>';
      ?>
    </main>
  </body>
</html>

If you perform the same network analysis as before, you get this:

forms3

You should notice two changes:

  1. The URL contains a ? and a & as well as the data you entered in the form.

  2. The parameter tab says that the data has been sent as query string, i.e. as part of the URL and not embedded as form data.

This means that the form data is visible to everyone and can be easily intercepted, whereas for the POST method this is a little bit more difficult. GET submissions can be bookmarked and URLs are usually stored in log files on the server, whereas the body of HTTP requests usually is not and can also not be bookmarked. We therefore prefer to use the POST method.

Here is an overview of all the attributes specific to the <form> tag:

Attribute Value Description

accept-charset

character set

character encoding to be used for form submission

action

URL

script to receive the form data

autocomplete

on/off

turn autocomplete on or off

enctype

application/x-www-form-urlencoded, multipart/form-data, text/plain

how the data should be encoded (only for POST method)

method

GET or POST

HTTP method to be used

name

text

name of the form

novalidate

the form should not be validated upon submission

target

_blank, _self, _parent, _top

display response from server in a new, the current, the parent or the top window/tab

Given the excellent documentation on W3Schools regarding all form elements, I will not repeat the details here but instead refer you to their site www.w3schools.com/html/html_forms.asp. Go through the examples and get a feel for forms. We’ll use them throughout our Web app development journey.

Here is a more complete example:

HTML forms3
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A more advanced form example</title>
    <meta charset=utf-8>
    <style>
      form {
        width:        350px;
        margin-left:  auto;
        margin-right: auto;
      }

      form label {
        float:         left;
        width:         150px;
        text-align:    right;
        padding-right: 10px;
        margin-top:    10px;
      }

      form input {
        margin-top:    10px;
        text-shadow:   1px 1px 1px white;
        border-radius: 5px;
      }

      form input:focus {
        background-color: yellow;
      }

      form input[type=submit], form input[type=button], form input[type=reset] {
        background:  linear-gradient(to bottom right, yellow, red);
        margin-left: 160px;
        width:       190px;
      }

      form input[type=submit]:focus, form input[type=button]:focus,
      form input[type=reset]:focus {
        border: 2px solid grey;
      }

      form input::-moz-focus-inner {
        border: 0;
      }

      form legend {
        font-weight: bold;
      }

      form select {
        border-radius: 5px;
      }

      form select optgroup {
        background-color: yellow;
      }

      form select optgroup option {
        background-color: greenyellow;
      }
      form fieldset {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <main>
      <form method=post>
        <fieldset>
          <legend>Personal data</legend>
          <label for=first_name>First name:</label>
          <input id=first_name list=names name=first_name placeholder=Pitty required autofocus>
          <datalist id=names>
            <option>Donald</option>
            <option>Mickey</option>
          </datalist><br>
          <label>Last name:</label>
          <input name=last_name required><br>
          <label>Password:</label>
          <!-- oncopy, onpaste and oncut are not part of the HTML5 standard,
          so they should not be used! -->
          <input type=password name=password required oncopy='return false;'
               onpaste='return false;' oncut='return false;'><br>
          <label>Email:</label>
          <input type=email name=email required><br>
          <label></label> <!-- Just used for layout purposes. -->
          <input type=radio name=sex value=male checked>Male<br>
          <label></label>
          <input type=radio name=sex value=female>Female<br>
          <label></label>
          <input type=checkbox name=bike value=Bike>I have a bike<br>
          <label></label>
          <input type=checkbox name=car value=Car>I have a car
        </fieldset>
        <fieldset>
          <legend>Other data</legend>
          <select>
            <optgroup label="Swedish Cars">
              <option value=volvo>Volvo</option>
              <option value=saab selected>Saab</option>
            </optgroup>
            <optgroup label="German Cars">
              <option value=mercedes>Mercedes</option>
              <option value=audi>Audi</option>
            </optgroup>
          </select>
          <textarea rows=3 cols=50 name=my_area maxlength=500
                placeholder="Short description of yourself (max. 500 chars)">
          </textarea><br>
          <label>Color:</label>
          <input type=color name=color><br>
          <label>Number (1-10):</label>
          <input type=number name=number min=1 max=10><br>
          <label>Number range:</label>
          <input type=range name=range min=1 max=100 value=50 step=5><br>
        </fieldset>
        <input type=submit value=Submit>
        <input type=reset>
      </form>
      <form oninput="x.value = parseInt(a.value) + parseInt(b.value)">0
        <input type=range id=a value=50>
        + <input type=number id=b value=50>
        =
        <output name=x for="a b"></output>
      </form>
    </main>
  </body>
</html>
<input>
The input element represents a typed data field, usually with a form control to allow the user to edit the data.

Excellent examples can be found at www.w3schools.com/tags/tag_input.asp.

Please note that you can prevent the copying, pasting and cutting of input field contents by setting the corresponding event handlers (cf. Events) as shown in the password input in the example above.

<textarea>

The textarea element represents a multiline plain text edit control for the element’s raw value. The contents of the control represent the control’s default value.

The raw value of a \texttt{textarea} control must be initially the empty string.

<label>
The label element represents a caption in a user interface. The caption can be associated with a specific form control, known as the label element’s labeled control, either using the for attribute, or by putting the form control inside the label element itself.
<fieldset>

The fieldset element represents a set of form controls optionally grouped under a common name.

The name of the group is given by the first legend element that is a child of the fieldset element, if any. The remainder of the descendants form the group.

<legend>
The legend element represents a caption for the rest of the contents of the legend element’s parent fieldset element, if any.
<select>

The select element represents a control for selecting amongst a set of options.

The multiple attribute is a boolean attribute. If the attribute is present, then the select element represents a control for selecting zero or more options from the list of options. If the attribute is absent, then the select element represents a control for selecting a single option from the list of options.

The size attribute gives the number of options to show to the user. The size attribute, if specified, must have a value that is a valid non-negative integer greater than zero.

The display size of a select element is the result of applying the rules for parsing non-negative integers to the value of element’s size attribute, if it has one and parsing it is successful. If applying those rules to the attribute’s value is not successful, or if the size attribute is absent, then the element’s display size is 4 if the element’s multiple content attribute is present, and 1 otherwise.

The list of options for a select element consists of all the option element children of the select element, and all the option element children of all the optgroup element children of the select element, in tree order.

<optgroup>
The optgroup element represents a group of option elements with a common label.
<option>
The option element represents an option in a select element or as part of a list of suggestions in a datalist element.
<button>
The button element represents a button labeled by its contents.
<datalist>
The datalist element represents a set of option elements that represent predefined options for other controls.
<keygen>
The keygen element represents a key pair generator control. When the control’s form is submitted, the private key is stored in the local keystore, and the public key is packaged and sent to the server.
<output>
The output element represents the result of a calculation or user action.

4.1.27. Block vs inline elements

A block element is an element that takes up the full width available, and has a line break before and after it.

Examples of block elements:

  • <h1>

  • <p>

  • <div>

An inline element only takes up as much width as necessary, and does not force line breaks.

Examples of inline elements:

  • <span>

  • <a>

display1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Inline vs block display example</title>
    <meta charset=utf-8>
    <style>
      main {
        border:  2px solid red;
        padding: 5px;
      }

      h1 {
        border:  1px inset blue;
        padding: 3px;
      }

      span {
        border: 1px dashed green;
      }

      .inlineList {
        display: inline;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>This is a heading level 1, a <strong>block</strong> element.</h1>
      This is a <span>span</span>, which is an <strong>inline</strong> element.
      <p>Sometimes it is useful to change the default display settings. For
      example, we might want list items to be stacked horizontally instead of
      vertically. So instead of this:
      </p>
      <ul>
        <li>Item1</li>
        <li>Item2</li>
        <li>Item3</li>
        <li>Item4</li>
      </ul>
      We might prefer this:
      <ul>
        <li class=inlineList>Item1</li>
        <li class=inlineList>Item2</li>
        <li class=inlineList>Item3</li>
        <li class=inlineList>Item4</li>
      </ul>
    </main>
  </body>
</html>

A complete list of block elements can be found at https://developer.mozilla .org/en-US/docs/Web/HTML/Block-level_elements. Likewise a list of inline elements is at developer.mozilla.org/en-US/docs/Web/HTML/Inline_elemente.

4.1.28. <video>

The <video> tag does what its name suggests, i.e. it defines video. It supports the following particular attributes:

Name Value Description

autoplay

play video automatically

controls

display video controls

height

pixels

height of the video player

loop

video will loop indefinitely (if supported by browser)

muted

muted video output

poster

URL

image to be shown while video is downloading and playback has not started

preload

auto, metadata, none

how the video should be loaded (cf. www.w3schools.com/tags/att_audio_preload.asp)

src

URL

video URL

width

pixels

width of the video player

Video file formats

There are three video file formats currently supported. They are MP4, WebM and OGG. Here is a comparison of their main features:

MP4 WebM OGG

Firefox

yes

yes

yes

Chrome

yes

yes

yes

Internet Explorer

yes

no

no

Safari

yes

no

no

MIME type

video/mp4

video/webm

video/ogg

Within the <video> tag we use the <source> tag to specify multiple video sources that the browser can choose from, based on its file format support.

If we need to convert between the different video file formats we can use a number of free tools, such as Miro Video Converter (www.mirovideoconverter.com) or Handbrake (handbrake.fr).

Here is a simple example of the <video> tag in action:

video1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Video example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <video controls autoplay loop
        src=http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4>
        Your browser does not support the video tag.
      </video>
    </main>
  </body>
</html>

Text inside the <video> opening and closing tag will be shown in browsers that do not support the tag.

4.1.29. <audio>

The <audio> tag does what its name suggests, i.e. it defines sound. It supports the following particular attributes:

Name Value Description

autoplay

play audio automatically

controls

display audio controls

loop

audio will loop indefinitely (if supported by browser)

muted

muted audio output

preload

auto, metadata, none

how the audio should be loaded (cf. www.w3schools.com/tags/att_audio_preload.asp)

src

URL

audio URL

Audio file formats

There are three audio file formats currently supported. They are MP3, WAV and OGG. Here is a comparison of their main features:

MP3 OGG WAV

Firefox

yes

yes

yes

Chrome

yes

yes

yes

Internet Explorer

yes

no

no

Safari

yes

yes

no

MIME type

audio/mpeg

audio/ogg

audio/wav

lossless

no

no

usually yes

compressed

yes

yes

usually no

If we need to convert between the different audio file formats we can use a number of free tools, such as Audacity (audacity.sourceforge.net). To produce our own music, we can use the outstanding LMMS (lmms.sourceforge.net) or AudioTool (www.audiotool.com).

Here is a simple example of the <audio> tag in action:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Audio example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <audio controls autoplay loop>
        <source src=http://api.audiotool.com/track/ge_trance_1/9/mixdown.ogg
                type=audio/ogg>
        <source src=http://api.audiotool.com/track/ge_trance_1/9/mixdown.mp3
            type=audio/mpeg>
        Your browser does not support the audio tag.
      </audio>
    </main>
  </body>
</html>

Note that the browser will use the first file format that it supports from those listed. The current versions of the main browsers all support MP3. If we only want to support those we can omit the <source> tag:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Audio example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <audio controls autoplay loop
           src=http://api.audiotool.com/track/ge_trance_1/9/mixdown.mp3>
        Your browser does not support the audio tag.
      </audio>
    </main>
  </body>
</html>

Text inside the <audio> opening and closing tag will be shown in browsers that do not support the tag.

4.1.30. Additional elements

<pre>

The <pre> tag defines preformatted text. Text is displayed in a fixed-width font and preserves both spaces and line breaks (cf. www.w3schools.com/tags/tag_pre.asp).

Example:

pre1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Preformatted Text Example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <pre>
This    is           preformatted text.
Spaces and linebreaks are preserved.
The font used by the browser has fixed width.
    </pre>
  </body>
</html>
<mark>

This element is used to highlight text (cf. www.w3schools.com/tags/tag_mark.asp). Example:

mark1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Mark example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <p>The <mark>mark element</mark> is used to highlight text in HTML5.</p>
    </main>
  </body>
</html>
<address>

This element is used to display contact information for the nearest <article> or <body> element (cf. www.w3.org/html/wg/drafts/html/CR/sections.html#the-address-element). Example:

address1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Address example</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <footer>
      <address>
        <a href=mailto:gilles.everling@education.lu>Gilles Everling</a>
      </address>
    </footer>
  </body>
</html>
<time>
<ins>
<del>
<iframe>
iframe1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>iframe example</title>
    <meta charset=utf-8>
    <style>
      nav {
        position:         fixed;
        left:             0;
        top:              0;
        bottom:           0;
        width:            100px;
        background-color: lightgoldenrodyellow;
      }

      main {
        position:          absolute;
        left:              100px;
        top:               0;
        bottom:            0;
        right:             0;
        background-color:  lightblue;
        animation:         animate 60s linear 5s infinite;
        -webkit-animation: animate 60s linear 5s infinite;
      }

      iframe {
        display: block;
        width:   100%;
        height:  100%;
        border:  none;
      }

      @keyframes animate {
        to {
          transform: rotateX(360deg) rotateY(360deg);
        }
      }

      @-webkit-keyframes animate {
        to {
          -webkit-transform: rotateX(360deg) rotateY(360deg);
        }
      }

      ul {
        list-style-type: none;
        padding-left:    10px;
      }

      li {
        padding-top: 5px;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li><a href=https://wsers.foxi.lu/WAD/WMOTUInvaders
             target=myFrame>WMOTU Invaders</a></li>
        <li><a href=https://wsers.foxi.lu/WAD/WMOTUQuack
             target=myFrame>WMOTU Quack</a></li>
      </ul>
    </nav>
    <main>
      <iframe name=myFrame></iframe>
    </main>
  </body>
</html>
iframe2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title></title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <iframe width=560 height=315 src=https://www.youtube.com/embed/-H2x_tGAxSM?rel=0
          allowfullscreen></iframe>
    </main>
  </body>
</html>

4.1.31. <embed>

This tag creates a container for a plug-in, such as Adobe Reader:

embed1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Embed example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <embed src=T-IF-WEB2-WSERS1_LP.pdf width=1000 height=800>
    </main>
  </body>
</html>

4.1.35. Quiz

Take the w3schools quiz at www.w3schools.com/quiztest/quiztest.asp?qtest=HTML5 as a fun way to check you are as good as you think you are.

4.2. CSS3

CSS is a style sheet language that allows authors and users to attach style (e.g., fonts and spacing) to structured documents (e.g., HTML documents and XML applications). By separating the presentation style of documents from the content of documents, CSS simplifies Web authoring and site maintenance.

The official CSS3 specifications can be found at www.w3.org/Style/CSS/current-work.

What can be done with CSS? Here's an example.

4.2.1. Include CSS3

There are four ways we can style our web page with CSS3.

Inline

We use the style attribute of a specific element to style it.

Example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Inline styling with CSS3</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main style='background-color: gold; color: black'>
      This element is style with inline CSS3.
    </main>
  </body>
</html>
Embedded

We use the <style> tag to embed CSS in the head of our document.

Example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Embedded CSS3</title>
    <meta charset=utf-8>
    <style>
      main {
        background-color: lightcoral;
      }

      p {
        border: 2px dashed green;
        color: lime;
      }
    </style>
  </head>
  <body>
    <main>
      <p>All elements styled by embedded CSS3.</p>
    </main>
  </body>
</html>
External

We use the <link> tag to include an external CSS style sheet in the head of our document.

Example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>External CSS3 style sheet</title>
    <meta charset=utf-8>
    <link href=external1.css rel=stylesheet>
  </head>
  <body>
    <main>
      <p>All elements styled via an external CSS3 style sheet.</p>
    </main>
  </body>
</html>
main {
  background-color: lightcoral;
}

p {
  border: 2px dashed green;
  color: lime;
}
Imported

We use the @import rule to include an external CSS style sheet in the current style sheet. See developer.mozilla.org/en-US/docs/Web/CSS/@import and www.w3.org/TR/CSS2/cascade.html#at-import.

Example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Imported CSS3 style sheet</title>
    <meta charset=utf-8>
    <style>
      @import url('external1.css');
      aside {
        padding: 10px;
        background-color: mediumorchid;
      }
    </style>
  </head>
  <body>
    <main>
      <p>Styled via an imported CSS3 style sheet.</p>
      <aside>Styled via embedded CSS.</aside>
    </main>
  </body>
</html>
main {
  background-color: lightcoral;
}

p {
  border: 2px dashed green;
  color: lime;
}
Rules of precedence

A browser processes styles in the order described at www.w3.org/TR/css3-cascade/#cascading-origins. A somewhat simplified illustration of the cascade looks like this:

CSSCascade

However, we can influence rule precedence by either changing the order of inclusion or by using the !important annotation.

If we include an external style sheet after the declaration of the embedded style sheet in the head of our document, the external styles will overwrite the embedded ones.

In the following example, the paragraph background color would normally be green, given that inline styling takes precedence over embedded styling. However, the !important annotation gives the embedded style a higher priority:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Changing the cascade with !important</title>
    <meta charset=utf-8>
    <style>
      p {
        background-color: gold !important;
      }
    </style>
  </head>
  <body>
    <main>
      <p style='background-color: green;'>Some text</p>
    </main>
  </body>
</html>

You can find the default style sheets used by the main browsers at the following links (cf. stackoverflow.com/questions/6867254/browsers-default-css-for-html-elements):

Firefox

mxr.mozilla.org/mozilla-central/source/layout/style/html.css

Internet Explorer

www.iecss.com

Chrome

trac.webkit.org/browser/trunk/Source/WebCore/css/html.css

Opera

www.iecss.com/opera-10.51.css

HTML5 recommendation

www.w3.org/TR/html5/rendering.html

4.2.2. Syntax

CSSSyntax
This site is a CSS3 learner’s paradise: www.w3schools.com/css/css_examples.asp

4.2.4. Properties

At www.w3schools.com/cssref/default.asp you can find easy to understand explanations as well as examples for pretty much every CSS property. This is an extremely useful resource that you should use.

www.w3.org/TR/css-2010/#properties and meiert.com/en/indices/css-properties provide an almost complete list of CSS properties with links to the specifications and in-depth explanations, which are useful for WMOTUs.

background

Scaling an image to its maximum displayable size without distorting the proportions can be done like this:

backgroundmaxsize1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Maximum size background image scaling without distortion</title>
    <meta charset=utf-8>
    <style>
      main {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-image: url(DSC00538.JPG);
        background-repeat: no-repeat;
        background-size: contain;
      }
    </style>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>
content

A great list of Unicode symbols can be found at inamidst.com/stuff/unidata.

content1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Content demo</title>
    <meta charset=UTF-8>
    <style>
      button::before {
        content: '\2709';
      }
    </style>
  </head>
  <body>
    <button> Email us</button>
  </body>
</html>
word-wrap

word-wrap allows us to solve some space constraint problems, as shown in this example:

word wrap1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Word wrap example 1</title>
    <meta charset=utf-8>
    <style>
      section {
        width: 50px;
        background-color: blueviolet;
      }

      #section2 {
        word-wrap: break-word;
      }
    </style>
  </head>
  <body>
    <main>
      <section>
        <h1>Section 1</h1>
        dassssssssssssssssssssssssssssssssssssssssss
      </section>
      <hr>
      <section id=section2>
        <h1>Section 2</h1>
        dassssssssssssssssssssssssssssssssssssssssss
      </section>
    </main>
  </body>
</html>
contenteditable
The contenteditable attribute specifies whether the content of an element is editable or not.

4.2.5. Selectors

Study the excellent table at www.w3schools.com/cssref/css_selectors.asp to learn the power and expressiveness of CSS selectors.

Here is a simple example:

hover1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Hover demo</title>
    <meta charset=utf-8>
    <style>
      p {
        display: none;
      }

      main {
        width: 500px;
        height: 500px;
        background-color: black;
      }
      main:hover > p {
        background-color: red;
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <main>
      <p>Paragraph</p>
    </main>
  </body>
</html>
Form validation

4.2.6. Box model

boxmodel1
boxmodel2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>CSS3 box model illustration</title>
    <meta charset=utf-8>
    <style>
      body {
        background-color: black;
      }

      header {
        background-color: green;
        border: 20px groove red;
        text-align: center;
      }

      h1 {
        background-color: gold;
        margin: 50px;
        padding: 30px;
        border: 10px dotted steelblue;
      }

      section {
        background-color: khaki;
        text-align: center;
        margin: 20px;
        padding: 40px;
        border: 10px double blue;
      }
    </style>
  </head>
  <body>
    <header>
      This is the header.
      <h1>This is the heading (h1).</h1>
    </header>
    <main>
      <section>
        This is a section.
      </section>
    </main>
  </body>
</html>
By default, i.e. if you have not changed box-sizing, the width and height properties in CSS3 do NOT include the padding, border and margin! Thus, if an element has content width of 200px, padding of 5px, a border width of 2px and a margin of 10px, the real width of the element will be 200 + 2 * 5 + 2 * 2 + 2 * 10 = 234px.

4.2.7. Comments

You can comment out parts of a line or even several lines by enclosing them between /* and */:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>CSS comment example 1</title>
    <meta charset=utf-8>
    <style>
      body {
        background-color: green;
        /*display: none;
        opacity: 0.5;*/
      }
    </style>
  </head>
  <body>
  </body>
</html>

Comments cannot be nested, i.e. this won’t work:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>CSS comment example 1</title>
    <meta charset=utf-8>
    <style>
      body {
        background-color: green;
        /*display: /*block*/none;
        opacity: 0.5;*/
      }
    </style>
  </head>
  <body>
  </body>
</html>

4.2.8. Layout

Normal flow

A browser renders our HTML code line by line in the order it appears in our HTML document. This is called normal flow. The following is a simple example, illustrating normal flow and the nesting of elements:

normalflow1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Normal flow example 1</title>
    <meta charset=utf-8>
    <style>
      section {
        width: 400px;
        height: 250px;
        border: 2px dashed green;
        margin: 5px;
        padding: 5px;
      }

      article {
        width: 300px;
        height: 150px;
        border: 1px inset blue;
        margin: 3px;
        padding: 3px;
      }

      p, aside {
        background-color: lightgoldenrodyellow;
        border: 1px groove gold;
        margin: 3px;
        padding: 3px;
      }
    </style>
  </head>
  <body>
    <main>
      <section>
        Section 1 does not contain any elements.
      </section>
      <section>
        Section 2 contains 3 nested elements:
        <article>
          This is an article
          <p>This is a paragraph nested inside an article nested inside a
            section nested inside the main element nested inside the body.</p>
        </article>
        <aside>This is an aside nested inside section 2 ...</aside>
      </section>
    </main>
  </body>
</html>

Now let’s try to build the following layout:

float
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Normal flow example 2</title>
    <meta charset=utf-8>
    <style>
      nav {
        border: 2px dotted blueviolet;
        width: 10%;
        height: 80px;
        margin: 5px;
        padding: 5px;
      }

      section {
        width: 40%;
        border: 2px solid #90ff65;
        margin: 5px;
        padding: 5px;
      }

      header {
        border: 2px ridge black;
        text-align: center;
      }

      footer {
        border: 2px outset hotpink;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <header>Header</header>
    <nav>Navigation</nav>
    <main>
      <section>Section 1</section>
      <section>Section 2</section>
    </main>
    <footer>Footer</footer>
  </body>
</html>

Unfortunately, this is not exactly what we want.

Floats

To solve our layout problem from the previous subsection, we need to take the navigation and two section elements out of the normal flow. We can do this using the float and clear properties:

float1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Float example 1</title>
    <meta charset=utf-8>
    <style>
      nav {
        float:   left;
        border:  2px dotted blueviolet;
        width:   10%;
        height:  80px;
        margin:  5px;
        padding: 5px;
      }

      section {
        float:   left;
        width:   40%;
        border:  2px solid #90ff65;
        margin:  5px;
        padding: 5px;
      }

      header {
        border:     2px ridge black;
        text-align: center;
      }

      footer {
        clear:      left;
        border:     2px outset hotpink;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <header>Header</header>
    <nav>Navigation</nav>
    <main>
      <section>Section 1</section>
      <section>Section 2</section>
    </main>
    <footer>Footer</footer>
  </body>
</html>

It is important to understand that float takes the floated element out of the normal flow and allows to float it on the left or right side of its container. Only block elements can be floated. To return to the normal flow, we use the clear property. We can clear only the left, only the right or both sides.

Remove the clear property from the footer rule and see what happens.

Instead of using clear we can use the overflow property on the containing element:

float2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Float example 2</title>
    <meta charset=utf-8>
    <style>
      nav {
        float:   left;
        border:  2px dotted blueviolet;
        width:   10%;
        height:  80px;
        margin:  5px;
        padding: 5px;
      }

      section {
        float:   left;
        width:   40%;
        border:  2px solid #90ff65;
        margin:  5px;
        padding: 5px;
      }

      header {
        border:     2px ridge black;
        text-align: center;
      }

      footer {
        border:     2px outset hotpink;
        text-align: center;
      }

      main {
        overflow: auto;
      }

      article {
        background-color: chartreuse;
      }
    </style>
  </head>
  <body>
    <header>Header</header>
    <nav>Navigation</nav>
    <main>
      <section>Section 1</section>
      <section>Section 2
        <header>Header</header>
        <article>Article 1</article>
        <article>Article 2</article>
        <footer>Footer</footer>
      </section>
    </main>
    <footer>Footer</footer>
  </body>
</html>

Floats can be very helpful in solving some simple layout problems, like in this vertical alignment example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Float example 3</title>
    <meta charset=utf-8>
    <style>
      #a1 {
        float: left;
      }

      span {
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <main> <!-- How do we get link1 to be aligned at the top instead of at the bottom?-->
      <a>link1</a><span>a<br>b</span>
      <br>
      <a id=a1>link1</a><span>a<br>b</span>
    </main>
  </body>
</html>
Positioning

The position property allows us to take an element out of the normal flow and position it exactly as we like (cf. developer.mozilla.org/en-US/docs/Web/CSS/position).

It is important to note that an element with position absolute is positioned relative to the nearest positioned ancestor (instead of positioned relative to the viewport, like fixed). However; if an absolute positioned element has no positioned ancestors, it uses the document body, and moves along with page scrolling. Note: A "positioned" element is one whose position is anything except static.

Let’s have a look at some examples:

Positioning1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Positioning example 1</title>
    <meta charset=UTF-8>
    <style>
      body {
        margin: 0;
      }

      header {
        background-color: gold;
        text-align:       center;
      }

      nav {
        background-color: navajowhite;
      }

      section {
        background-color: lightgrey;
      }

      footer {
        background-color: aqua;
        text-align:       center;
      }

      #article1 {
        background-color: yellow;
        opacity:          0.3;
      }

      #article2 {
        background-color: indianred;
        opacity:          0.3;
      }

      #article3 {
        background-color: yellowgreen;
      }

      #article4 {
        background-color: white;
      }

      aside {
        background-color: red;
      }

      h1 {
        margin: 0;
      }

      ul {
        padding: 0;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>LAM T0IF WMOTU</h1>
    </header>
    <nav>
      <ul>
        <li><a href=#>Link 1</a></li>
        <li><a href=#>Link 2</a></li>
        <li><a href=#>Link 3</a></li>
        <li><a href=#>Link 4</a></li>
      </ul>
    </nav>
    <main>
      <section>
        <article id=article1></article>
        <aside>I'm so sticky</aside>
        <article id=article2></article>
        <article id=article3></article>
        <article id=article4></article>
      </section>
    </main>
    <footer>&copy; 2018 LAM T0IF</footer>
  </body>
</html>
Positioning2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Positioning example 2</title>
    <meta charset=UTF-8>
    <style>
      body {
        margin:   0;
        overflow: hidden;
      }

      main {
        overflow: auto;
        position: absolute;
        left:     100px;
        top:      40px;
        right:    0;
        bottom:   20px;
      }

      header {
        background-color: gold;
        text-align:       center;
        position:         fixed;
        top:              0;
        left:             0;
        width:            100%;
        height:           40px;
      }

      nav {
        background-color: navajowhite;
        position:         fixed;
        top:              40px;
        left:             0;
        width:            100px;
        bottom:           20px;
      }

      section {
        background-color: lightgrey;
      }

      footer {
        background-color: aqua;
        text-align:       center;
        position:         fixed;
        bottom:           0;
        left:             0;
        height:           20px;
        width:            100%;
      }

      #article1 {
        background-color: yellow;
        position:         relative;
        left:             50px;
        top:              100px;
      }

      #article2 {
        background-color: indianred;
        position:         absolute;
        left:             20px;
        top:              420px;
      }

      #article3 {
        background-color: yellowgreen;
        position:         fixed;
        left:             50px;
        top:              300px;
      }

      #article4 {
        background-color: white;
      }

      aside {
        position:         sticky;
        top:              0;
        background-color: red;
      }

      h1 {
        margin: 0;
      }

      ul {
        padding: 0;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>LAM T0IF WMOTU</h1>
    </header>
    <nav>
      <ul>
        <li><a href=#>Link 1</a></li>
        <li><a href=#>Link 2</a></li>
        <li><a href=#>Link 3</a></li>
        <li><a href=#>Link 4</a></li>
      </ul>
    </nav>
    <main>
      <section>
        <article id=article1></article>
        <aside>I'm so sticky</aside>
        <article id=article2></article>
        <article id=article3></article>
        <article id=article4></article>
      </section>
    </main>
    <footer>&copy; 2018 LAM T0IF</footer>
  </body>
</html>

On scroll-linked effects:

developer.mozilla.org/en-US/docs/Mozilla/Performance/Scroll-linked_effects

staktrace.com/spout/entry.php?id=834

Resize images automatically while preserving aspect ratio
Table layout
Scrollable HTML5 table with fixed header

This is a classic problem that has not had many fully satisfying solutions so far. Some interesting discussions on the topic can be found here:

stackoverflow.com/questions/673153/html-table-with-fixed-headers/25902860

stackoverflow.com/questions/673153/html-table-with-fixed-headers/25818428

stackoverflow.com/questions/17584702/how-to-add-a-scrollbar-to-an-html5-table

stackoverflow.com/questions/8423768/freeze-the-top-row-for-an-html-table-only-fixed-table-header-scrolling

www.sitepoint.com/community/t/flexible-html-table-with-fixed-header-and-footer-around-a-scrollable-body/271162/31

stackoverflow.com/questions/19559197/how-to-make-scrollable-table-with-fixed-headers-using-css

salzerdesign.com/test/fixedTable.html

github.com/chrisbrantley/fixed-header-table

blog.freestylecoding.com/archive/2011/04/19/html-table-with-fixed-header-and-scrollable-body.aspx

The following table lists some solutions I’ve found and gives a brief comment:

students.btsi.lu/evegi144/WAD/CSS3/table1.html

Does not work if the window is too small and a horizontal scroll bar appears.

students.btsi.lu/evegi144/WAD/CSS3/table2.html

Same issue.

students.btsi.lu/evegi144/WAD/CSS3/table3.html

Same issue.

students.btsi.lu/evegi144/WAD/CSS3/table4.html

Needs setting column width.

students.btsi.lu/evegi144/WAD/CSS3/table5.html

Simple but does not work.

students.btsi.lu/evegi144/WAD/CSS3/table6.html

Works very well but requires a huge amount of CSS and divs.

students.btsi.lu/evegi144/WAD/CSS3/table7.html

Very simple solution that works but not very smoothly on mobiles as for the reasons explained above under scroll-linked effects. Until position:sticky works correctly in the main browsers this will be my first choice.

students.btsi.lu/evegi144/WAD/CSS3/table8.html

This is the ideal solution but currently only works in a few browsers such as Chrome but not Firefox.

doctorDestructo’s fiddles are worth having a look.

CSS tables

CSS allows us to style elements as tables, which opens up a very powerful layout mechanism. See www.w3.org/TR/CSS21/visuren.html#propdef-display and www.w3.org/TR/CSS21/tables.html for the details.

Example:

TableLayout1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Table layout example 1</title>
    <meta charset=UTF-8>
    <style>
      main {
        display: table;
      }

      section {
        display: table-row;
      }

      article {
        display: table-cell;
      }
    </style>
  </head>
  <body>
    <main>
      <section>
        <article></article>
        <article></article>
      </section>
      <section>
        <article>afadsfadsfdas</article>
        <article>afadsfadsadasdsadasddsfdas</article>
      </section>
      <section>
        <article>sadsa</article>
        <article>sadsadsasadsadsa</article>
      </section>
    </main>
  </body>
</html>
Vertical centering

www.w3.org/Style/Examples/007/center

www.vanseodesign.com/css/vertical-centering

Flexbox, as described in a later section, is by far the easiest way to center any element.

Example:

VerticalCentering1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Centering example</title>
    <meta charset=UTF-8>
    <style>
      main {
        position:         absolute;
        top:              0;
        left:             0;
        height:           100%;
        width:            100%;
        background-color: lightblue;
        display:          flex;
      }

      img {
        margin: auto;
      }
    </style>
  </head>
  <body>
    <main>
      <img src=logo_ltam.gif alt="LTAM Logo">
    </main>
  </body>
</html>

Here a more generic way to center any element without styling it: students.btsi.lu/evegi144/WAD/CSS3/centering2.html

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Centering example</title>
    <meta charset=UTF-8>
    <style>
      main {
        position:         absolute;
        top:              0;
        left:             0;
        height:           100%;
        width:            100%;
        background-color: lightblue;
        display:          flex;
        align-items:      center;
        justify-content:  center;
        flex-flow:        column;
      }
    </style>
  </head>
  <body>
    <main>
      <section>
        <img src=logo_ltam.gif alt="LTAM Logo">
      </section>
      <section>Section 2</section>
    </main>
  </body>
</html>
Flexible box layout

According to www.w3.org/TR/css3-flexbox, flexible box layout is

a CSS box model optimized for user interface design. In the flex layout model, the children of a flex container can be laid out in any direction, and can “flex” their sizes, either growing to fill unused space or shrinking to avoid overflowing the parent. Both horizontal and vertical alignment of the children can be easily manipulated. Nesting of these boxes (horizontal inside vertical, or vertical inside horizontal) can be used to build layouts in two dimensions.

It is a big step forward in terms of GUI development and will be our preferred approach.

This feature is only supported in the latest browser versions (cf. caniuse.com/#feat=flexbox) and even then implementation is not fully consistent across browsers. You can find in-depth information and examples at the following links:

To learn Flexbox in a fun way, take a look at www.flexboxdefense.com and flexboxfroggy.com.

developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout

www.w3schools.com/cssref/css3_pr_flex.asp

bocoup.com/weblog/dive-into-flexbox

www.w3.org/TR/css3-flexbox

css-tricks.com/snippets/css/a-guide-to-flexbox

html5please.com/\#flexbox

philipwalton.github.io/solved-by-flexbox

www.sketchingwithcss.com/samplechapter

www.sketchingwithcss.com/samplechapter/cheatsheet.html

www.smashingmagazine.com/2013/05/22/centering-elements-with-flexbox

designshack.net/articles/css/build-a-web-page-with-css3-flexbox

www.html5rocks.com/en/tutorials/flexbox/quick

Let’s start with a simple header - main - footer layout, where the main part contains a navigation, a section and an aside. Note that the order of the navigation and aside are changed using CSS.

Study the comments in the CSS file together with the links above to gain a deeper understanding of flex layout.

FlexTest1
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Flexible Box Layout Test 1</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style>
      /* This will make sure that our GUI fills the whole browser window. */
      html, body {
        width:  100%;
        height: 100%;
        margin: 0;
      }

      body {
        background: #999999;
        display:    flex; /* This element is a flex container. */
        flex-flow:  column; /* Change the main axis to column instead of row. */
        overflow:   hidden; /* We don't want scrollbars. */
      }

      main {
        margin:   0;
        padding:  0;
        display:  flex; /* This is another flex container. */
        /* It will grow 3 times faster and shrink at the same speed as the others. */
        flex:     auto;
        overflow: hidden; /* We don't want scrollbars. */
      }

      main > section {
        margin:        4px;
        padding:       5px;
        border:        1px solid #cccc33;
        border-radius: 7pt;
        background:    #dddd88;
        flex:          3 1 60%;
        order:         2; /* This element will be displayed in second position. */
        overflow:      auto; /* We want scrollbars when required by the content. */
      }

      main > nav {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        flex:          1 6 20%;
        order:         3; /* This element will be displayed in third position. */
      }

      main > aside {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        flex:          1 6 20%;
        order:         1; /* This element will be displayed in first position. */
      }

      header, footer {
        margin:        4px;
        padding:       5px;
        border:        1px solid #eebb55;
        border-radius: 7pt;
        background:    #ffeebb;
        height:        50px;
      }

      /* Too narrow to support three columns */
      @media all and (max-width: 640px) {
        main {
          flex-flow: column;
        }

        main > section, main > nav, main > aside {
          /* Return them to document order */
          order: 0;
        }

        main > nav, main > aside, header, footer {
          min-height: 50px;
          max-height: 50px;
        }
      }

    </style>
  </head>
  <body>
    <header>header</header>
    <main>
      <nav>nav</nav>
      <section>section</section>
      <aside>aside</aside>
    </main>
    <footer>footer</footer>
  </body>
</html>

Now we let the user resize some of the elements:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Flexible Box Layout Test 2</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style>
      body {
        position:   absolute;
        top:        0;
        left:       0;
        width:      100%;
        height:     100%;
        background: #999999;
        display:    flex;
        flex-flow:  column;
        margin:     0;
        overflow:   hidden;
      }

      main {
        min-height: 50px;
        margin:     0;
        padding:    0;
        display:    flex;
        flex:       auto;
      }

      main > article {
        margin:        4px;
        padding:       5px;
        border:        1px solid #cccc33;
        border-radius: 7pt;
        background:    #dddd88;
        flex:          auto;
        min-width:     50px;
      }

      main > nav {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        min-width:     150px;
      }

      main > aside {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        min-width:     50px;
        flex:          auto;
      }

      header, footer {
        display:       block;
        margin:        4px;
        padding:       5px;
        min-height:    100px;
        border:        1px solid #eebb55;
        border-radius: 7pt;
        background:    #ffeebb;
      }

      .splitter {
        border-left: 2px solid grey;
        width:       2px;
        min-width:   2px;
        cursor:      col-resize;
      }

      /* Too narrow to support three columns */
      @media all and (max-width: 640px) {
        main {
          flex-flow:      column;
        }

        main > nav, main > aside, header, footer {
          min-height: 50px;
          max-height: 50px;
        }
      }

    </style>
    <script src=flextest2.js></script>
  </head>
  <body>
    <header>header</header>
    <main>
      <nav>nav</nav>
      <article>article</article>
      <div class="splitter"></div>
      <aside>aside</aside>
    </main>
    <footer>footer</footer>
  </body>
</html>
'use strict';

let lastFirstElWidth = 0, lastThirdElWidth = 0;

// https://hacks.mozilla.org/2013/12/application-layout-with-css3-flexible-box-module
class Splitter {
  constructor(handler, leftEl, rightEl) {
    this.lastX = 0;
    this.dragListener = null;
    this.endDragListener = null;
    this.leftEl = leftEl;
    this.rightEl = rightEl;

    handler.addEventListener('mousedown', evt => {
      evt.preventDefault();
      this.lastX = evt.clientX;
      // http://msdn.microsoft.com/en-us/library/windows/apps/hh703713.aspx
      this.dragListener = this.drag;
      this.endDragListener = this.endDrag;
      addEventListener('mousemove', this.dragListener);
      addEventListener('mouseup', this.endDragListener);
    });

    this.drag = evt => {
      let wL, wR;
      const wDiff = evt.clientX - this.lastX;
      wL = getComputedStyle(this.leftEl).width;
      wR = getComputedStyle(this.rightEl).width;
      wL = parseInt(wL) + wDiff;
      wR = parseInt(wR) - wDiff;
      this.leftEl.style.width = wL + 'px';
      this.rightEl.style.width = wR + 'px';
      this.lastX = evt.clientX;
      lastFirstElWidth = wL;
      lastThirdElWidth = wR;
    };

    this.endDrag = () => {
      removeEventListener('mousemove', this.dragListener);
      removeEventListener('mouseup', this.endDragListener);
    };
  };
}

const init = () => {
  const splitter = new Splitter(document.getElementsByClassName('splitter')[0],
    document.getElementsByTagName('article')[0],
    document.getElementsByTagName('aside')[0]);

  /* Our CSS switches flex flow to column if window width <= 640.
   In this case we need to remove the width set by the splitter dragging,
   otherwise the layout won't be right aligned on small screens.
   If window width increases again, we want the previous widths back.
   */
  const handleResize = () => {
    if (innerWidth <= 640) {
      document.querySelector('article').style.width = '';
      document.querySelector('aside').style.width = '';
    }
    else {
      document.querySelector('article').style.width = lastFirstElWidth + 'px';
      document.querySelector('aside').style.width = lastThirdElWidth + 'px';
    }
  };

  window.addEventListener('resize', handleResize);
};

window.addEventListener('load', init);

Some more resizability:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Flexible Box Layout Test 3</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style>
      body {
        position:   absolute;
        top:        0;
        left:       0;
        width:      100%;
        height:     100%;
        background: #999999;
        display:    flex;
        flex-flow:  column;
        margin:     0;
        overflow:   hidden;
      }

      main {
        min-height: 50px;
        margin:     0;
        padding:    0;
        display:    flex;
        flex:       auto;
      }

      main > article {
        margin:        4px;
        padding:       5px;
        border:        1px solid #cccc33;
        border-radius: 7pt;
        background:    #dddd88;
        flex:          auto;
        min-width:     50px;
      }

      main > nav {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        min-width:     50px;
      }

      main > aside {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        flex:          auto;
        min-width:     50px;
      }

      header, footer {
        margin:        4px;
        padding:       5px;
        min-height:    50px;
        border:        1px solid #eebb55;
        border-radius: 7pt;
        background:    #ffeebb;
      }

      .verticalSplitter {
        border-left: 2px solid grey;
        width:       2px;
        min-width:   2px;
        cursor:      col-resize;
      }

      .horizontalSplitter {
        border-top: 2px solid grey;
        height:     2px;
        min-height: 2px;
        cursor:     row-resize;
      }

      /* Too narrow to support three columns */
      @media all and (max-width: 640px) {
        main {
          flex-flow: column;
        }

        main > nav, header, footer {
          height: 50px;
        }

        .horizontalSplitter, .verticalSplitter {
          display: none;
        }
      }

    </style>
  </head>
  <body>
    <header>header</header>
    <div id=hs1 class=horizontalSplitter></div>
    <main>
      <nav>nav</nav>
      <article>article</article>
      <div class=verticalSplitter></div>
      <aside>aside</aside>
    </main>
    <div id=hs2 class=horizontalSplitter></div>
    <footer>footer</footer>
    <script src=flextest3.js></script>
  </body>
</html>
'use strict';

let lastFirstElWidth = 0, lastThirdElWidth = 0;

// https://hacks.mozilla.org/2013/12/application-layout-with-css3-flexible-box-module
class Splitter {
  constructor(vertical) {
    this.vertical = vertical;
    this.lastCoord = 0;

    this.init = (splitter, firstEl, thirdEl) =>
    {
      this.firstEl = firstEl;
      this.thirdEl = thirdEl;
      splitter.addEventListener('mousedown', evt => {
        evt.preventDefault();
        if (vertical) this.lastCoord = evt.clientX;
        else this.lastCoord = evt.clientY;
        this.dragListener = this.drag;
        this.endDragListener = this.endDrag;
        addEventListener('mousemove', this.dragListener);
        addEventListener('mouseup', this.endDragListener);
      });
    };

    this.drag = evt => {
      let coord1, coord3, coordDiff;
      if (vertical) {
        coordDiff = evt.clientX - this.lastCoord;
        coord1 = getComputedStyle(this.firstEl).width;
        coord3 = getComputedStyle(this.thirdEl).width;
      }
      else {
        coordDiff = evt.clientY - this.lastCoord;
        coord1 = getComputedStyle(this.firstEl).height;
        coord3 = getComputedStyle(this.thirdEl).height;
      }
      coord1 = parseInt(coord1) + coordDiff;
      coord3 = parseInt(coord3) - coordDiff;
      if (vertical) {
        this.firstEl.style.width = coord1 + 'px';
        this.thirdEl.style.width = coord3 + 'px';
        this.lastCoord = evt.clientX;
        lastFirstElWidth = coord1;
        lastThirdElWidth = coord3;
        console.log(lastFirstElWidth + ' ' + lastThirdElWidth);
      }
      else {
        this.firstEl.style.height = coord1 + 'px';
        this.thirdEl.style.height = coord3 + 'px';
        this.lastCoord = evt.clientY;
      }
    };

    this.endDrag = () => {
      removeEventListener('mousemove', this.dragListener);
      removeEventListener('mouseup', this.endDragListener);
    };
  }
}

const init = () => {
  const verticalSplitter = new Splitter(true);
  verticalSplitter.init(document.getElementsByClassName('verticalSplitter')[0],
    document.querySelector('article'), document.querySelector('aside'));
  const horizontalSplitter1 = new Splitter(false);
  horizontalSplitter1.init(document.getElementById('hs1'),
    document.querySelector('header'), document.querySelector('main'));
  const horizontalSplitter2 = new Splitter(false);
  horizontalSplitter2.init(document.getElementById('hs2'),
    document.querySelector('main'), document.querySelector('footer'));

  /* Our CSS switches flex flow to column if window width <= 640.
     In this case we need to remove the width set by the splitter dragging,
      otherwise the layout won't be right aligned on small screens.
     If window width increases again, we want the previous widths back.
   */
  const handleResize = () => {
    if (innerWidth <= 640) {
      document.querySelector('article').style.width = '';
      document.querySelector('aside').style.width = '';
    }
    else {
      document.querySelector('article').style.width = lastFirstElWidth + 'px';
      document.querySelector('aside').style.width = lastThirdElWidth + 'px';
    }
  };

  window.addEventListener('resize', handleResize);
};

window.addEventListener('load', init);

The following example illustrates how to achieve specific scrollbar behavior:

<!DOCTYPE html>
<html>
  <head>
    <title>Flexible Box Layout Test 4</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style type="text/css">
      html, body {
        height:  100%;
        width:   100%;
        padding: 0;
        margin:  0;
      }

      body {
        overflow:       hidden;
        display:        flex;
        flex-direction: column;
      }

      header {
        height:     75px;
        min-height: 75px;
      }

      footer {
        height:     25px;
        min-height: 25px;
      }

      main {
        display:      flex;
        flex:         auto;
        border:       solid grey;
        border-width: 1px 0;
        overflow:     hidden;
      }

      nav {
        width:     150px;
        min-width: 150px;
      }

      section {
        border:       solid grey;
        border-width: 0 0 0 1px;
        flex:         auto;
        overflow:     auto;
      }
    </style>
  </head>
  <body>
    <header>header</header>
    <main>
      <nav>nav</nav>
      <section>article<br>read <a
        href="https://hacks.mozilla.org/2013/12/application-layout-with-css3-flexible-box-module/"
        target="_blank">Application Layout with CSS3 Flexible Box Module</a>
        <p style="width:1000px;">
          <?php require_once 'flextest4.txt'; ?>
        </p>
      </section>
    </main>
    <footer>footer</footer>
  </body>
</html>

Finally a more advanced example combining flexbox, splitters and scrollbars:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Flexible Box Layout Test 5</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style>
      html, body {
        width:  100%;
        height: 100%;
      }

      body {
        background: #999999;
        display:    flex;
        flex-direction:  column;
        margin:     0;
        overflow:   hidden;
      }

      main {
        margin:  0;
        padding: 0;
        display: flex;
        flex:    auto;
        overflow: auto;
      }

      main > nav {
        margin:        4px;
        padding:       5px;
        border:        1px solid #8888bb;
        border-radius: 7pt;
        background:    #ccccff;
        min-width:     50px;
        flex:          auto;
      }

      main > section {
        margin:         4px;
        padding:        5px;
        border:         1px solid #8888bb;
        border-radius:  7pt;
        background:     #ccccff;
        display:        flex;
        flex-direction: column;
        min-width:      50px;
        overflow:       hidden;
      }

      main > section > section {
        display:   flex;
        flex-flow: column;
        flex:      auto;
        overflow:  hidden;
      }

      header, footer {
        margin:        4px;
        padding:       5px;
        min-height:    50px;
        border:        1px solid #eebb55;
        border-radius: 7pt;
        background:    #ffeebb;
      }

      #s1 {
        flex:     auto;
        flex-flow: column;
        overflow: hidden;
      }

      #s1 > article, #s2 {
        overflow: auto;
      }

      .verticalSplitter {
        border-left: 2px solid grey;
        width:       2px;
        min-width:   2px;
        cursor:      col-resize;
      }

      .horizontalSplitter {
        border-top: 2px solid grey;
        height:     2px;
        min-height: 2px;
        cursor:     row-resize;
      }

      /* Too narrow to support three columns */
      @media all and (max-width: 640px) {
        main {
          flex-flow: column;
        }

        .horizontalSplitter, .verticalSplitter {
          display: none;
        }
      }
    </style>
    <script src=flextest5.js></script>
  </head>
  <body>
    <header>header</header>
    <main>
      <nav>nav</nav>
      <div class=verticalSplitter></div>
      <section>
        <section id=s1>
          <header>Header s1</header>
          <article><?php require 'flextest4.txt'; ?></article>
        </section>
        <div id=hs1 class=horizontalSplitter></div>
        <section id=s2><?php require 'flextest4.txt'; ?></section>
      </section>
    </main>
    <footer>footer</footer>
  </body>
</html>
'use strict';

let verticalSplitter, horizontalSplitter;

// https://hacks.mozilla.org/2013/12/application-layout-with-css3-flexible-box-module
class Splitter {
  constructor(vertical) {
    this.vertical = vertical;
    this.lastCoord = 0;
    if (vertical) {
      this.lastFirstElWidth = 0;
      this.lastThirdElWidth = 0;
    }

    this.init = (splitter, firstEl, thirdEl) =>
    {
      this.firstEl = firstEl;
      this.thirdEl = thirdEl;
      splitter.addEventListener('mousedown', evt => {
        evt.preventDefault();
        if (vertical) this.lastCoord = evt.clientX;
        else this.lastCoord = evt.clientY;
        this.dragListener = this.drag;
        this.endDragListener = this.endDrag;
        addEventListener('mousemove', this.dragListener);
        addEventListener('mouseup', this.endDragListener);
      });
    };

    this.drag = evt => {
      let coord1, coord3, coordDiff;
      if (vertical) {
        coordDiff = evt.clientX - this.lastCoord;
        coord1 = getComputedStyle(this.firstEl).width;
        coord3 = getComputedStyle(this.thirdEl).width;
      }
      else {
        coordDiff = evt.clientY - this.lastCoord;
        coord1 = getComputedStyle(this.firstEl).height;
        coord3 = getComputedStyle(this.thirdEl).height;
      }
      coord1 = parseInt(coord1) + coordDiff;
      coord3 = parseInt(coord3) - coordDiff;
      if (vertical) {
        this.firstEl.style.width = coord1 + 'px';
        this.thirdEl.style.width = coord3 + 'px';
        this.lastCoord = evt.clientX;
        this.lastFirstElWidth = coord1;
        this.lastThirdElWidth = coord3;
      }
      else {
        this.firstEl.style.height = coord1 + 'px';
        this.thirdEl.style.height = coord3 + 'px';
        this.lastCoord = evt.clientY;
      }
    };

    this.endDrag = () => {
      removeEventListener('mousemove', this.dragListener);
      removeEventListener('mouseup', this.endDragListener);
    };
  }
}

const init = () => {
  const verticalSplitter = new Splitter(true);
  const horizontalSplitter = new Splitter(false);
  verticalSplitter.init(document.getElementsByClassName('verticalSplitter')[0],
    document.querySelector('nav'), document.querySelector('section'));
  horizontalSplitter.init(document.getElementById('hs1'),
    document.getElementById('s1'), document.getElementById('s2'));

  /* Our CSS switches flex flow to column if window width <= 640.
     In this case we need to remove the width set by the splitter dragging,
      otherwise the layout won't be right aligned on small screens.
     If window width increases again, we want the previous widths back.
   */
  const handleResize = () => {
    if (innerWidth <= 640) {
      document.querySelector('article').style.width = '';
      document.querySelector('aside').style.width = '';
    }
    else {
      document.querySelector('article').style.width = lastFirstElWidth + 'px';
      document.querySelector('aside').style.width = lastThirdElWidth + 'px';
    }
  };

  window.addEventListener('resize', handleResize);
};

window.addEventListener('load', init);

A flexible navigation menu:

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>Flexible Box Layout Test 6</title>
    <style>
      html { /* http://www.paulirish.com/2012/box-sizing-border-box-ftw */
        box-sizing: border-box;
      }
      *, *:before, *:after {
        box-sizing: inherit;
      }

      html, body {
        width:  100%;
        height: 100%;
        margin: 0;
      }

      body {
        display:   flex;
        flex-flow: column;
        overflow:  hidden;
      }

      main {
        margin:   0;
        overflow: auto;
      }

      nav {
        padding: 0;
      }

      ul {
        list-style:       none;
        padding:          0;
        display:          flex;
        flex:             auto;
        background-color: pink;
        margin:           0;
      }

      li {
        flex: auto;
      }

      nav > ul > li > a {
        text-decoration:  none;
        margin:           0;
        padding:          0;
        display:          inline-block;
        width:            100%;
        text-align:       center;
        background-color: orange;
        border:           solid black 1px;
      }

      nav > ul > li > a:hover {
        background-color: yellow;
      }

      main > section > a {
        text-decoration: none;
        background-color: tomato;
        border-radius: 5px;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li><a href=#about>About</a></li>
        <li><a href=#portfolio>Portfolio</a></li>
        <li><a href=#contact>Contact</a></li>
      </ul>
    </nav>
    <main>
      <section id=about>
        <h1>About</h1>
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
      </section>
      <section id=portfolio>
        <h1>Portfolio</h1>
        <?php require_once 'flextest4.txt'; ?>
      </section>
      <section id=contact>
        <h1>Contact</h1>
        <form>
          Name: <input required>
          Email: <input required><button>Send</button>
        </form>
      </section>
    </main>
  </body>
</html>

Another layout variation:

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>Flexible Box Layout Test 7</title>
    <style>
      nav {
        display:          flex;
        background-color: #0a73a7;
      }

      ul {
        display:          flex;
        flex:             auto;
        list-style:       none;
        background-color: lightgrey;
      }

      li {
        text-align: center;
        display:    block;
      }

      #l1 {
        justify-content:  center;
        background-color: lightgreen;
      }

      #l2 {
        max-width:        130px;
        background-color: lightblue;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul id=l1>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
      <ul id=l2>
        <li>Item 4</li>
        <li>Item 5</li>
        <li>Item 6</li>
      </ul>
    </nav>
  </body>
</html>
Grid layout

Grid layout makes it easy to build the big picture aspects of our layout, i.e. the row AND column layout. We can then use Flexbox to manage the horizontal OR vertical alignment of the content inside the grid elements.

css-tricks.com/snippets/css/complete-guide-grid

caniuse.com/\#search=Grid

www.mozilla.org/en-US/developer/css-grid

developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout

gridbyexample.com/

bitsofco.de/how-the-minmax-function-works

tutorialzine.com/2017/03/css-grid-vs-flexbox

Here’s a very simple example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>CSS Grid example</title>
    <style>
      html, body {
        width:  100%;
        height: 100%;
        margin: 0;
      }

      main {
        height:                100%;
        display:               grid;
        grid-template-columns: 3fr 1fr 2fr;
        grid-template-rows:    1fr 3fr;
        align-items:           stretch;
      }

      section {
        background-color: lawngreen;
        border:           darkmagenta solid 2px;
      }
    </style>
  </head>
  <body>
    <main>
      <section>One</section>
      <section>Two</section>
      <section>Three</section>
      <section>Four</section>
      <section>Five</section>
      <section>Six</section>
    </main>
  </body>
</html>

When should we use Grid and when Flexbox? This article dives into the question.

To support browsers without grid see:

www.smashingmagazine.com/2017/11/css-grid-supporting-browsers-without-grid

developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/CSS_Grid_and_Progressive_Enhancement

rachelandrew.co.uk/archives/2016/11/26/should-i-try-to-use-the-ie-implementation-of-css-grid-layout

Responsive design

Responsive web design aims at building websites that work on mobile devices, tablets, and desktop screens.

It is important to set the viewport to optimize the user’s experience:

<meta name=viewport content="width=device-width, initial-scale=1">

developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries

drafts.csswg.org/mediaqueries

css-tricks.com/dont-overthink-flexbox-grids

www.w3schools.com/cssref/css3_pr_mediaquery.asp

developer.mozilla.org/en-US/docs/Web_Development/Responsive_Web_design

blog.teamtreehouse.com/modern-field-guide-responsive-web-design

screensiz.es/phone

developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag

developer.mozilla.org/en-US/docs/Web/HTML/Element/meta

webdesignerwall.com/tutorials/viewport-meta-tag-for-non-responsive-design

www.smashingmagazine.com/2011/07/22/responsive-web-design-techniques-tools-and-design-strategies

www.quirksmode.org/mobile/metaviewport

googlewebmastercentral.blogspot.co.uk/2012/04/responsive-design-harnessing-power-of.html

responsivedesign.is/develop/html/viewport-meta-element

In Firefox, pressing Ctrl+Shift+M or Tools  Web Developer  Responsive Design View

responsivedesignview1

Let’s look at a practical example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Responsive layout example 1</title>
    <style>
      html { /* http://www.paulirish.com/2012/box-sizing-border-box-ftw */
        box-sizing: border-box;
      }

      *, *:before, *:after {
        box-sizing: inherit;
      }

      ul {
        display:    flex;
        flex-wrap:  wrap;
        padding:    0;
        list-style: none;
      }

      ul li {
        flex:       auto;
        text-align: center;
        padding:    2px;
      }

      @media (min-width: 10em) {
        ul li {
          flex-basis: 33%;
        }
      }

      @media (min-width: 28em) {
        ul li {
          flex-basis: 0;
        }
      }

      ul li a {
        display:         block;
        text-decoration: none;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li><a href=#>Item 1</a></li>
        <li><a href=#>Item 2</a></li>
        <li><a href=#>Item 3</a></li>
        <li><a href=#>Item 4</a></li>
        <li><a href=#>Item 5</a></li>
        <li><a href=#>Item 6</a></li>
      </ul>
    </nav>
    <main>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut et tempus urna. In sed
      sagittis arcu. Cras in sapien diam. Aenean massa ipsum, rutrum eu facilisis vitae,
      semper in arcu. Morbi vitae tortor sit amet turpis feugiat aliquam. Aliquam eu
      rhoncus odio, quis rutrum magna.
    </main>
  </body>
</html>
Responsive menus
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Responsive layout example 2</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <style>
      html { /* http://www.paulirish.com/2012/box-sizing-border-box-ftw */
        box-sizing: border-box;
      }

      *, *:before, *:after {
        box-sizing: inherit;
      }

      nav {
        margin:           4px;
        padding:          4px;
        border:           1px solid #8888bb;
        border-radius:    7pt;
        background-color: #ccccff;
        min-width:        50px;
        flex:             auto;
      }

      ul {
        display:    flex;
        flex-wrap:  wrap;
        margin:     0;
        padding:    0;
        list-style: none;
      }

      ul li {
        flex:             auto;
        text-align:       center;
        padding:          2px;
        background-color: #ffeebb;
      }

      @media (min-width: 10em) {
        ul li {
          flex-basis: 33%;
        }
      }

      @media (min-width: 28em) {
        ul li {
          flex-basis: 0;
        }
      }

      ul li a {
        display:         block;
        text-decoration: none;
      }

      html, body {
        width:  100%;
        height: 100%;
      }

      body {
        background-color: #999999;
        display:          flex;
        flex-direction:   column;
        margin:           0;
        overflow:         hidden;
      }

      main {
        margin:   0;
        padding:  0;
        display:  flex;
        flex:     auto;
        overflow: auto;
      }

      main > nav {
        margin:           4px;
        padding:          5px;
        border:           1px solid #8888bb;
        border-radius:    7pt;
        background-color: #ccccff;
        min-width:        50px;
        flex:             auto;
      }

      main > section {
        margin:           4px;
        padding:          5px;
        border:           1px solid #8888bb;
        border-radius:    7pt;
        background-color: #ccccff;
        display:          flex;
        flex-direction:   column;
        min-width:        50px;
        overflow:         hidden;
      }

      main > section > section {
        display:   flex;
        flex-flow: column;
        flex:      auto;
        overflow:  hidden;
      }

      header, footer {
        margin:           4px;
        padding:          5px;
        min-height:       50px;
        border:           1px solid #eebb55;
        border-radius:    7pt;
        background-color: #ffeebb;
      }

      #s1 {
        flex:      auto;
        flex-flow: column;
        overflow:  hidden;
      }

      #s1 > article, #s2 {
        overflow: auto;
      }

      .verticalSplitter {
        border-left: 1px solid grey;
        width:       1px;
        min-width:   1px;
        cursor:      col-resize;
      }

      .horizontalSplitter {
        border-top: 1px solid grey;
        height:     1px;
        min-height: 1px;
        cursor:     row-resize;
      }

      /* Too narrow to support three columns */
      @media all and (max-width: 640px) {
        main {
          flex-flow: column;
        }

        main > nav, header, footer {
          height: 50px;
        }

        .horizontalSplitter, .verticalSplitter {
          display: none;
        }
      }
    </style>
    <script src=flextest5.js></script>
  </head>
  <body>
    <nav>
      <ul>
        <li><a href=#>Item 1</a></li>
        <li><a href=#>Item 2</a></li>
        <li><a href=#>Item 3</a></li>
        <li><a href=#>Item 4</a></li>
        <li><a href=#>Item 5</a></li>
        <li><a href=#>Item 6</a></li>
      </ul>
    </nav>
    <main>
      <nav>nav</nav>
      <div class=verticalSplitter></div>
      <section>
        <section id=s1>
          <header>Header s1</header>
          <article><?php require 'flextest4.txt'; ?></article>
        </section>
        <div id=hs1 class=horizontalSplitter></div>
        <section id=s2><?php require 'flextest4.txt'; ?></section>
      </section>
    </main>
    <footer>footer</footer>
  </body>
</html>

Here is a hamburger icon menu example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <meta name=viewport content="width=device-width, initial-scale=1.0">
    <title>Responsive layout example 3</title>
    <style>
      /* Inspiration from http://codepen.io/ricardozea/pen/OPaRZO */
      html { /* http://www.paulirish.com/2012/box-sizing-border-box-ftw */
        box-sizing: border-box;
      }

      *, *:before, *:after {
        box-sizing: inherit;
      }

      #menu-button {
        padding:         0 0.2em 0.2em 0.2em;
        background:      #f6f6f6;
        text-decoration: none;
        color:           #333;
        cursor:          pointer;
        font-size:       3em;
      }

      #menu-button.active {
        background-color: #333;
        color:            #fff;
      }

      #menu {
        overflow:   hidden;
        max-height: 0;
        padding:    0;
        clear:      both;
        transition: all .3s ease-out;
      }

      #menu.active {
        max-height: 17em;
      }

      #menu ul {
        margin:          0;
        padding:         0;
        list-style-type: none;
        border:          1px #999 dotted;
        border-bottom:   none;
        text-align:      center;
      }

      #menu li a {
        display:          block;
        padding:          1em;
        border-bottom:    1px #999 dotted;
        text-decoration:  none;
        color:            #2963BD;
        background-color: #fff;
      }

      @media (min-width: 40em) {
        #menu-button {
          display: none;
        }

        #menu {
          max-height: inherit;
        }

        #menu ul {
          background: #fff;
        }

        #menu li {
          display: inline-block;
          margin:  0 .2em;
        }
      }
    </style>
    <script>
      'use strict';

      const init = () => {
        document.querySelector('button').addEventListener('click', () => {
          document.querySelector('button').classList.toggle('active');
          document.querySelector('#menu').classList.toggle('active');
        });
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <button id=menu-button>≡</button>
    <nav id=menu>
      <ul>
        <li><a href=#>Item 1</a></li>
        <li><a href=#>Item 2</a></li>
        <li><a href=#>Item 3</a></li>
        <li><a href=#>Item 4</a></li>
        <li><a href=#>Item 5</a></li>
      </ul>
    </nav>
    <main>
      <section><?php require 'flextest4.txt'; ?></section>
    </main>
  </body>
</html>

4.2.9. Color selection

4.2.10. Making elements resizable

The resize (www.cssportal.com/css-properties/resize.php and developer.mozilla.org/en-US/docs/Web/CSS/resize) property allows us to make an element resizable. Note that in order for the resizing to work, you need to set the overflow property to something different from the standard visible.

4.2.11. Navigation menu

navigationmenu1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Navigation Menu</title>
    <meta charset=UTF-8>
    <style>
      nav {
        margin:      0;
        white-space: nowrap;
      }

      nav ul {
        padding:         0;
        margin:          0;
        list-style-type: none;
      }

      nav ul li {
        display: inline-block;
        cursor:  pointer;
      }

      nav ul li ul {
        position: absolute;
        display:  none;
      }

      nav > ul > li ul li, nav > ul > li:hover > ul {
        display: block;
      }

      nav > ul > li ul li:hover > ul {
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li>
          Menu1
          <ul>
            <li>
              Submenu1
              <ul>
                <li>Subsubmenu1</li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          Menu2
          <ul>
            <li>
              Submenu1
              <ul>
                <li>Subsubmenu1</li>
                <li>
                  Subsubmenu2
                  <ul>
                    <li>Subsubsubmenu1</li>
                    <li>
                      Subsubsubmenu2
                      <ul>
                        <li>Subsubsubsubmenu1</li>
                      </ul>
                    </li>
                    <li>Subsubsubmenu3</li>
                  </ul>
                </li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </nav>
  </body>
</html>
navigationmenu2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Navigation Menu</title>
    <meta charset=UTF-8>
    <style>
      nav a {
        text-decoration: none;
        cursor:          pointer;
        border:          2px solid blue;
      }

      nav ul {
        padding:    0;
        margin:     0;
        list-style: none;
      }

      nav > ul > li {
        float:       left;
        clear:       left;
        white-space: nowrap;
      }

      nav ul li ul {
        position:   absolute;
        display:    inline-block;
        visibility: hidden;
      }

      nav li:hover > ul {
        visibility: visible;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li>
          <a>Menu 1</a>
          <ul>
            <li>
              <a>Menu 1 Sub 1</a>
              <ul>
                <li><a>Menu 1 Sub Sub 1</a></li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a>Menu 2</a>
          <ul>
            <li>
              <a>Menu 2 Sub 1</a>
              <ul>
                <li><a>Menu 2 Sub Sub 1</a></li>
                <li>
                  <a>Menu 2 Sub Sub 2</a>
                  <ul>
                    <li><a>Menu 2 Sub Sub Sub 1</a></li>
                    <li>
                      <a>Menu 2 Sub Sub Sub 2</a>
                      <ul>
                        <li><a>Menu 2 Sub Sub Sub Sub 1</a></li>
                      </ul>
                    </li>
                    <li><a>Menu 2 Sub Sub Sub 3</a></li>
                  </ul>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a>Menu 3</a>
        </li>
        <li>
          <a>Menu 4</a>
        </li>
      </ul>
    </nav>
  </body>
</html>
navigationmenu3
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Navigation Menu</title>
    <meta charset=UTF-8>
    <style>
      body {
        background: linear-gradient(red, white, blue) fixed;
      }

      nav {
        position:   absolute;
        top:        0;
        left:       0;
        transition: 50s;
      }

      nav:hover {
        left: 500px;
        top:  500px;
      }

      nav a {
        text-decoration:       none;
        cursor:                pointer;
        padding:               5px;
        background:            radial-gradient(rgb(0, 255, 0), rgb(255, 0, 255));
        display:               inline-block;
        border:                2px outset black;
        /* stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-
        highlighting */
        -webkit-touch-callout: none;
        -webkit-user-select:   none;
        -khtml-user-select:    none;
        -moz-user-select:      none;
        -ms-user-select:       none;
        /*user-select:           none;*/
      }

      nav a:hover {
        background-color: green;
      }

      nav ul {
        padding:    0;
        margin:     0;
        list-style: none;
      }

      nav > ul ul li {
        padding-left: 10px;
      }

      nav > ul ul li:before {
        content:       '';
        position:      absolute;
        left:          1px;
        top:           9px;
        border-bottom: 8px solid transparent;
        border-top:    8px solid transparent;
        border-left:   8px solid lightgreen;
      }

      nav > ul > li {
        float:       left;
        clear:       left;
        white-space: nowrap;
      }

      nav ul li ul {
        position: absolute;
        display:  none;
      }

      nav > ul li:hover > ul {
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul>
        <li>
          <a>Menu 1</a>
          <ul>
            <li>
              <a>Menu 1 Sub 1</a>
              <ul>
                <li><a>Menu 1 Sub Sub 1</a></li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a>Menu 2</a>
          <ul>
            <li>
              <a>Menu 2 Sub 1</a>
              <ul>
                <li><a>Menu 2 Sub Sub 1</a></li>
                <li>
                  <a>Menu 2 Sub Sub 2</a>
                  <ul>
                    <li><a>Menu 2 Sub Sub Sub 1</a></li>
                    <li>
                      <a>Menu 2 Sub Sub Sub 2</a>
                      <ul>
                        <li><a>Menu 2 Sub Sub Sub Sub 1</a></li>
                      </ul>
                    </li>
                    <li><a>Menu 2 Sub Sub Sub 3</a></li>
                  </ul>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a>Menu 3</a>
        </li>
        <li>
          <a>Menu 4</a>
        </li>
      </ul>
    </nav>
  </body>
</html>

A useful tutorial and templates can be found at cssmenumaker.com/blog/responsive-menu-tutorial.

4.2.12. Gradients

Gradients are a great alternative to standard images. www.w3schools.com/css/css3_gradients.asp and developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_gradients provide excellent information and examples on the subject. The official specification can be found at dev.w3.org/csswg/css-images-3.

Example:

gradient1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Gradient example 1</title>
    <meta charset=UTF-8>
    <style>
      body {
        background: repeating-radial-gradient(red, blue 20px, red 40px) fixed;
      }

      section {
        position: absolute;
        width: 500px;
        height: 500px;
        background: linear-gradient(90deg, black, transparent, white);
        border-radius: 100px;
      }

      article {
        width: 30px;
        height: 30px;
        background: repeating-linear-gradient(-45deg, red, red 5px, white 5px, white 10px);
        border-radius: 15px;
      }
    </style>
  </head>
  <body>
    <main>
      <section>
        <article>

        </article>
      </section>
    </main>
  </body>
</html>

An excellent gradient generator can be found at www.colorzilla.com/gradient-editor.

4.2.13. Buttons

With CSS3 it’s very easy to create great looking buttons without using pictures. There are many button generators available on the Web that produce nice results, for instance:

www.cssbuttongenerator.com

dabuttonfactory.com

css-button-generator.com

css3button.net

4.2.14. Animation

For examples of what can be done, take a look at the following:

anthonycalzadilla.com/css3-ATAT/index-bones.html

www.marcofolio.net/css/css_animated_profile_cards.html

www.marcofolio.net/css/animated_wicked_css3_3d_bar_chart.html

To find out which CSS properties can be animated, take a look at developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties.

Here is a simpler example:

animation1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Animation example 1</title>
    <meta charset=UTF-8>
    <style>
      body {
        background: repeating-radial-gradient(red, blue 20px, red 40px) fixed;
      }

      section {
        position:          absolute;
        width:             500px;
        height:            500px;
        background:        linear-gradient(90deg, black, transparent, white);
        animation:         sectionAnimation 5s infinite alternate;
        border-radius:     100px;
      }

      @keyframes sectionAnimation {
        from {
          left: 0;
          top:  0;
        }
        to {
          left: 500px;
          top:  100px;
        }
      }

      aside {
        position:          absolute;
        width:             30px;
        height:            30px;
        background:        repeating-linear-gradient(-45deg, red, red 5px, white 5px, white 10px);
        border-radius:     15px;
        animation:         asideAnimation 5s infinite alternate;
      }

        /* Standard syntax */
      @keyframes asideAnimation {
        0% {
          left: 500px;
          top:  0;
        }

        50% {
          left: 250px;
          top:  300px;
        }

        100% {
          left: 0px;
          top:  100px;
        }
      }
    </style>
  </head>
  <body>
    <main>
      <section></section>
      <aside></aside>
    </main>
  </body>
</html>

4.2.15. Fonts

websitesetup.org/web-safe-fonts-html-css

www.typewolf.com/google-fonts

www.dvginteractive.com/serif-vs-sans-serif-how-to-increase-your-websites-readability-by-more-than-50-2

@font-face

You can find lots of free fonts at www.google.com/fonts. Also read www.creativebloq.com/typography/free-web-fonts-1131610.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using the @font-face rule</title>
    <meta charset=utf-8>
    <link href=http://fonts.googleapis.com/css?family=Shadows+Into+Light rel=stylesheet>
    <style>
      body {
        font-family: 'Shadows Into Light', cursive;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>Test header</h1>
    </main>
  </body>
</html>

4.2.16. Variables

Variables are available since Firefox 29 and enabled by default starting in Firefox 31. Here are the details: developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables

4.2.18. Quiz

Take the w3schools quiz at www.w3schools.com/quiztest/quiztest.asp?qtest=CSS as a fun way to check you are as good as you think you are.

4.2.19. Tests

Computer Shop
ComputerShop

Create the following validated page:

The following information is not complete:

  1. body: no margin and padding, background from black to grey, font color white, black shadow of 2 pixels h, v and blur.

  2. header: height of 100 pixels.

  3. nav: 150 pixels wide.

  4. main: right padding of 10 pixels, overflow auto.

  5. footer: 20 pixels high, font size half normal.

  6. ul with no margin and padding.

  7. li with 10 pixels padding above and below.

  8. Hyperlinks with color gold and 1 pixel black shadow h, v and blur.

  9. Navigation hyperlinks with font size twice normal.

  10. h1 with color gold and font size three times normal.

  11. Definition term with color hex 22bb22, bottom border of 2 pixels blueviolet, top margin of 10 pixels and bottom margin of 5 pixels.

  12. Table data items padding of 20 pixels.

You can copy paste the following text:

Computer Shop
Buy

Contact us

Welcome to our shop.

We offer the following:

Desktops

A desktop computer is a personal computer in a form intended for regular use at a single location desk/table due to its size and power requirements (cf. http://en.wikipedia.org/wiki/Desktop\_computer Wikipedia).

Laptops

A laptop or a notebook is a portable personal computer with a clamshell form factor, suitable for mobile use. There was a difference between laptops and notebooks in the past, but nowadays it has gradually died away. Laptops are commonly used in a variety of settings, including at work, in education, and for personal multimedia.
A laptop combines the components and inputs of a desktop computer, including display, speakers, keyboard and pointing device (such as a touchpad or a trackpad) into a single device. Most modern-day laptops also have an integrated webcam and a microphone. A laptop can be powered either from a rechargeable battery, or by mains electricity via an AC adapter. Laptop is a diverse category of devices and other more specific terms, such as rugged notebook or convertible, refer to specialist types of laptops, which have been optimized for specific uses. Hardware specifications change significantly between different types, makes and models of laptops (cf. http://en.wikipedia.org/wiki/Laptop Wikipedia).

Device

Brand

Price

LTAM

299.99

LTAM

349.99

2015 LTAM T0IF2
Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Computer Shop</title>
    <meta charset=utf-8>
    <style>
      body {
        margin:      0;
        padding:     0;
        background:  linear-gradient(to bottom right, black, grey) fixed;
        color:       white;
        text-shadow: 2px 2px 2px black;
      }

      header {
        position: fixed;
        width:    100%;
        height:   100px;
      }

      nav {
        position: fixed;
        top:      100px;
        width:    150px;
      }

      main {
        position:      fixed;
        top:           100px;
        left:          150px;
        right:         0;
        bottom:        20px;
        text-align:    justify;
        padding-right: 10px;
        overflow:      auto;
      }

      footer {
        position:   fixed;
        bottom:     0;
        width:      100%;
        height:     20px;
        text-align: center;
        font-size:  0.5em;
      }

      ul {
        margin:  0;
        padding: 0;
      }

      li {
        padding-top:    10px;
        padding-bottom: 10px;
      }

      a {
        text-decoration: none;
        color:           gold;
        text-shadow:     1px 1px 1px black;
      }

      nav a {
        font-size: 2em;
      }

      h1 {
        text-align: center;
        font-size:  3em;
        color:      gold;
      }

      dt {
        color:         #22bb22;
        border-bottom: 2px dashed blueviolet;
        margin-top:    10px;
        margin-bottom: 5px;
      }

      th {
        text-align: center;
      }

      td {
        padding: 20px;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>Computer Shop</h1>
    </header>
    <nav>
      <ul>
        <li><a href=#>Buy</a></li>
        <li><a href=#>Contact us</a></li>
      </ul>
    </nav>
    <main>
      <h2>Welcome to our shop.</h2>
      We offer the following:
      <dl>
        <dt>Desktops</dt>
        <dd>A desktop computer is a personal computer in a form intended for regular use at a
          single location desk/table due to its size and power requirements (cf. <a
            href=http://en.wikipedia.org/wiki/Desktop_computer target=_blank>Wikipedia</a>).
        </dd>
        <dt>Laptops</dt>
        <dd>A laptop or a notebook is a portable personal computer with a clamshell form
          factor, suitable for mobile use. There was a difference between laptops and
          notebooks in the past, but nowadays it has gradually died away. Laptops are
          commonly used in a variety of settings, including at work, in education, and for
          personal multimedia.
          A laptop combines the components and inputs of a desktop computer, including display,
          speakers, keyboard and pointing device (such as a touchpad or a trackpad) into a
          single device. Most modern-day laptops also have an integrated webcam and a
          microphone. A laptop can be powered either from a rechargeable battery, or by mains
          electricity via an AC adapter. Laptop is a diverse category of devices and other more
          specific terms, such as rugged notebook or convertible, refer to specialist types of
          laptops, which have been optimized for specific uses. Hardware specifications change
          significantly between different types, makes and models of laptops (cf. <a
            href=http://en.wikipedia.org/wiki/Laptop target=_blank>Wikipedia</a>).
        </dd>
      </dl>
      <table>
        <tr>
          <th>Device</th>
          <th>Brand</th>
          <th>Price</th>
        </tr>
        <tr>
          <td><img src=1432254774_mycomputer.png alt=Comp1></td>
          <td>LTAM</td>
          <td>299.99 &euro;</td>
        </tr>
        <tr>
          <td><img src=1432254808_Computer2.png alt=Comp2></td>
          <td>LTAM</td>
          <td>349.99 &euro;</td>
        </tr>
      </table>
    </main>
    <footer>&copy; 2015 LTAM T0IF2</footer>
  </body>
</html>
Video Viewer
VideoViewer

Create the validated site exactly as shown:

It consists of 2 HTML files (index.html and viewer.html) and one CSS file.

The following information is not complete:

  1. Form data is sent to the file viewer.html.

  2. The form box has a black shadow of 3 pixels h, v and blur.

  3. The user name field is focused automatically when the page is loaded.

  4. html and body have no margin and padding and use the whole browser window width and height.

  5. body: repeating radial gradient from black to yellow 100px to white 200px, white text shadow of 2 pixels h, v and blur.

  6. nav: 40 pixels high, full width.

  7. main: display flex, full width. In index.html, there is no navigation, so main starts at the top. Therefore the login box is centered.

  8. footer: 15 pixels high, font size half normal.

  9. ul with 10 px horizontal and no vertical margin. No padding.

  10. Navigation links with 10 pixel padding, 2 pixel golden border.

  11. The form has automatic margin.The animation lasts 5 seconds and starts with 0 opacity.

  12. Form inputs have 0.5 opacity and white text shadow of 2 pixels h, v, and blur.

  13. The table takes 20% of the total width and has a margin of 5 pixels.

  14. Table headings have red text color and a font size 25% bigger than normal.

  15. The iframe takes 80% of the total width and also has a margin of 5 pixels.

You can copy paste the following text:

Login

Title
Module
Link

Windows 7 installation
SYSEX1
https://www.youtube.com/embed/NP3cPmC-08A

Computer Shop
HTSTA
https://www.youtube.com/embed/C99FqKlnD1s

T1IF Invaders
CLISS2
https://www.youtube.com/embed/c--I9podO0s

2015 WMOTU
Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Video Viewer</title>
    <meta charset=utf-8>
    <link href=style.css rel=stylesheet>
    <style>
      main {
        top: 0;
      }
    </style>
  </head>
  <body>
    <main>
      <form action=viewer.html>
        <fieldset>
          <legend>Login</legend>
          <input placeholder="user name" required autofocus>
          <input type=password placeholder=password required>
          <button>Login</button>
        </fieldset>
      </form>
    </main>
    <footer>&copy; 2015 WMOTU</footer>
  </body>
</html>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Video Viewer</title>
    <meta charset=utf-8>
    <link href=style.css rel=stylesheet>
  </head>
  <body>
    <nav>
      <ul>
        <li><a href=index.html>Log out</a></li>
        <li><a
          href="mailto:t0if2@ltam.lu?subject=Information%20request">Contact us</a></li>
      </ul>
    </nav>
    <main>
      <table>
        <thead>
          <tr>
            <th>Title</th>
            <th>Module</th>
            <th>Link</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>Windows 7 installation</td>
            <td>SYSEX1</td>
            <td><a href=https://www.youtube.com/embed/NP3cPmC-08A target=myFrame>View</a></td>
          </tr>
          <tr>
            <td>Computer Shop</td>
            <td>HTSTA</td>
            <td><a href=https://www.youtube.com/embed/C99FqKlnD1s target=myFrame>View</a></td>
          </tr>
          <tr>
            <td>T1IF Invaders</td>
            <td>CLISS2</td>
            <td><a href=https://www.youtube.com/embed/c--I9podO0s target=myFrame>View</a></td>
          </tr>
        </tbody>
      </table>
      <iframe name=myFrame></iframe>
    </main>
    <footer>&copy; 2015 WMOTU</footer>
  </body>
</html>
html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

body {
  background: repeating-radial-gradient(black, yellow 100px, white 200px) fixed;
  text-shadow: 2px 2px 2px white;
}

nav {
  position: fixed;
  top: 0;
  width: 100%;
  height: 40px;
}

main {
  position: fixed;
  top: 40px;
  width: 100%;
  bottom: 15px;
  display: flex;
  overflow: auto;
}

footer {
  position: fixed;
  bottom: 0;
  width: 100%;
  height: 15px;
  text-align: center;
  font-size: 0.5em;
}

nav > ul {
  list-style: none;
  margin: 10px 0;
  padding: 0;
}

nav > ul > li {
  display: inline;
}

nav > ul > li > a {
  padding: 10px;
  background-color: black;
  border: 2px inset gold;
  color: gold;
}

nav > ul > li > a:hover {
  background-color: blue;
}

a {
  text-decoration: none;
}

form {
  margin: auto;
  box-shadow: 3px 3px 3px black;
  animation: formAnim 5s;
}

@keyframes formAnim {
  from {
    opacity: 0;
  }
}

form input {
  opacity: 0.5;
  text-shadow: 2px 2px 2px white;
}

table {
  width: 20%;
  margin: 5px;
}

iframe {
  border: none;
  width: 80%;
  margin: 5px;
}

th {
  color: red;
  font-size: 1.25em;
}

4.3. JavaScript

4.3.1. Introduction to programming

In order to tell a computer what to do, we use a special language, called a "programming" language. Like "human" languages, this is a set of instructions that are translated into codes that have specific meaning to the computer.

More formally, according to en.wikipedia.org/wiki/Computer_programming:

Computer programming (often shortened to programming) is a process that leads from an original formulation of a computing problem to executable programs. It involves activities such as analysis, understanding, and generically solving such problems resulting in an algorithm, verification of requirements of the algorithm including its correctness and its resource consumption, implementation (commonly referred to as coding) of the algorithm in a target programming language. Source code is written in one or more programming languages (such as C, C++, C#, Java, Python, Smalltalk, JavaScript, etc.). The purpose of programming is to find a sequence of instructions that will automate performing a specific task or solve a given problem. The process of programming thus often requires expertise in many different subjects, including knowledge of the application domain, specialized algorithms and formal logic.

4.3.2. Getting started with JavaScript

JavaScript is the programming language used to interact with the Document Object Model (DOM) that the browser creates from HTML and CSS files. It thereby allows the programmatic control of the web page’s appearance and behavior on the client side. It is also increasingly used on the server side using Node.js. It is the foundation for the development of full fledged web applications.

javascript

JavaScript is based on the ECMAScript language specification, the latest version of which can be found at (cf. www.ecma-international.org/ecma-262). Browser support is excellent (cf. kangax.github.io/compat-table).

A number of very insightful articles on JavaScript can be found at javascript.crockford.com/javascript.html.

To get an in-depth overview of the latest developments in the real world application of JS, have a look at The state of JavaScript.

An excellent free online book on ECMAScript 5 can be found at speakingjs.com/es5/index.html.

To find out what’s new in ECMAScript 6 take a close look at the following resources:

Before we get started, open the console of your browser.

Firefox Chrome Internet Explorer

Console

Shift+F2

Ctrl+Shift+J or I

F12

console1
You should always keep the console open, as any error messages will only be visible there.

4.3.3. Adding JavaScript to HTML documents

The HTML tag to include a script is, for obvious reasons, the <script> tag. The user has the possibility to disable JavaScript in his browser. There’s nothing we can do about this except detect it using the <noscript> tag and inform the user that our app won’t run without JavaScript. In Firefox, you can disable JavaScript by going to the page about:config and setting javascript.enabled to false. Now run the example and turn JavaScript back on and rerun the example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Detect whether JavaScript is disabled</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <noscript>Sorry, but this application requires JavaScript to run!</noscript>
    </main>
  </body>
</html>
External JavaScript

The most common approach is to create a separate file with the extension .js (the extension is not mandatory, but recommended) and include it in the HTML file. Here’s an example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using external JS</title>
    <meta charset=utf-8>
    <script src=js1.js></script>
  </head>
  <body></body>
</html>
"use strict";

alert('Hello world!'); // Display a message box with the text 'Hello world!'.

The strict mode "use strict"; instruction turns on strict mode (cf. Strict mode), which we should always do in order to make our life easier and to write better quality code. You can put this instruction into the default JavaScript template of your IDE, so that you do not have to type it in every time (cf. NetBeans templates or PhpStorm templates).

The <script> tag can be placed into the document’s head or body. Remember that your browser processes an HTML document from top to bottom. So if the script is put into the head, it is loaded and executed before the HTML body exists. If the script tries to access HTML elements, it will fail, as shown in this example (remember, you need to have the console open to see the error):

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using external JS the wrong way</title>
    <meta charset=utf-8>
    <script src=js2.js></script>
  </head>
  <body><main></main></body>
</html>
"use strict";

// Save the first (and only) main element in a constant named main.
const main = document.getElementsByTagName('main')[0];
// Set the text color of the main element to gold.
main.style.color = 'gold';
// Set the background color of the main element to black.
main.style.backgroundColor = 'black';
// Set the text 'Hello world!' as content of the main element.
main.innerHTML = 'Hello world!';

Here is the error message you should see:

firebug1

One solution to this problem is to include the script at the end of the HTML document, but this is not recommended, as it is difficult to see which script gets included. A cleaner approach is to have the script loaded in the head of the document but put all instructions that should be executed immediately into a function (don’t worry, we’ll look at those in detail in JS functions, but for now you just need to know that a function is a series of instructions that get executed when we "call" the function using its name) which gets run when the browser has either finished loading and parsing the initial HTML document, without waiting for stylesheets, images, and subframes to finish loading or when the full document has been loaded and parsed. To achieve the former, we use the DOMContentLoaded (cf. developer.mozilla.org/en/docs/Web/Events/DOMContentLoaded) and for the latter the load event (cf. Events where we talk about events in detail):

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using external JS the right way</title>
    <meta charset=utf-8>
    <script src=js3.js></script>
  </head>
  <body><main></main></body>
</html>
"use strict";

// We declare but do not execute the function.
const init = () => {
  const main = document.querySelector('main'); // Get the main element.
  main.style.color = 'gold'; // Set text color to gold.
  main.style.backgroundColor = 'black'; // Set background color to black.
  main.innerHTML = 'Hello world!'; // Set main content.
};

/* Only after the browser has finished loading and parsing the initial HTML document, without
 waiting for stylesheets, images, and subframes or the whole document,
 meaning that the DOM is available, will the init function be called.
 There are 2 events we can use and 2 ways to do this. The first one is the preferred one, as it
 allows to install several listeners for the same event. The second one is shorter. */
window.addEventListener('DOMContentLoaded', init);
//window.addEventListener('load', init);
//window.onload = init;
async and defer

The async attribute is only for external scripts (and should only be used if the src attribute is present).

Note: There are several ways an external script can be executed:

  • If async is present: The script is executed asynchronously with the rest of the page (the script will be executed while the page continues the parsing).

  • If async is not present and defer is present: The script is executed when the page has finished parsing.

  • If neither async or defer is present: The script is fetched and executed immediately, before the browser continues parsing the page.

Embedded JavaScript

If we have only a small script that we want to use in our document, we can embed it directly using the <script> tag, like so:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using embedded JS</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const sayHello = () => {
        alert('Hello world!');
      };

      window.addEventListener('load', sayHello);
    </script>
  </head>
  <body></body>
</html>

We can embed JavaScript in the head and/or in the body part of the document.

Inline JavaScript

If we need to execute only one or very few instructions, for instance when a link is clicked, we can use JavaScript inline as an event handler (cf. Events).

Here’s a simple illustration using a clickable link:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using inline JS</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <a href=# onclick="alert('Hello world!');">
        If you click me I'll greet the world!</a>
    </main>
  </body>
</html>
Combinations

In the real world, you will often use combinations of some or all of the three methods, for instance an external script that defines functions, which are called via embedded or inline script.

Here is an example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Combining external and inline JS</title>
    <meta charset=utf-8>
    <script src=js6.js></script> <!-- include an external script -->
  </head>
  <body>
    <main>
      <a href=# onclick=modifyMain();>If you click me I'll change the world!</a>
    </main>
  </body>
</html>
"use strict";

const modifyMain = () => {
  const main = document.getElementsByTagName('main')[0];
  main.style.color = 'gold';
  main.style.backgroundColor = 'black';
  main.innerHTML = 'Hello world!';
};

4.3.4. Comments

In order to make our scripts easier to understand, it is a good idea to add comments that explain what the purpose of a piece of code is if it is not obvious. Comments should not repeat the code or explain trivialities. JavaScript provides single line comments using // and multiline comments using /* */. You have already seen examples of both in External JavaScript.

4.3.5. Semicolons

Most ECMAScript statements and declarations must be terminated with a semicolon. Such semicolons may always appear explicitly in the source text. For convenience, however, such semicolons may be omitted from the source text in certain situations. These situations are described by saying that semicolons are automatically inserted into the source code token stream in those situations.

In order to avoid needless errors, we’ll always terminate the JS statements listed above with semicolons.

4.3.6. Basic input and output

console.log

The console object provides access to the browser’s debugging console, which we use to detect errors in our programs. For the details see developer.mozilla.org/en-US/docs/Web/API/console and developer.mozilla.org/en-US/docs/Tools/Web_Console. Here is a simple example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Console usage example</title>
    <meta charset=utf-8>
    <script src=console1.js></script>
  </head>
  <body>
  </body>
</html>
document.write

document.write writes a given text verbatim into the document at the current position. Example: students.btsi.lu/evegi144/WAD/JS/document_write1.html

"use strict";

document.write('<table><tr><th>Club</th><th>Score</th></tr>');
document.write('<tr><td>Bayern München</td><td>17</td></tr>');
document.write('<tr><td>FC Liverpool</td><td>15</td></tr></table>');
alert

In some of the previous examples we have already used the alert method. This method is part of the global window object, which we’ll discuss in Browser Object Model (BOM). Normally we would have to write window.alert to use this method, but the browser considers window as the default object, thereby allowing us to simply write the method name. alert takes the text, quoted in "" or '', as parameter and displays it in a dialog box, which will block the screen until the user confirms the message.

confirm

The confirm method, just like alert, displays a dialog box with a specified message, but with an OK and a Cancel button, so that the user has a choice. The method returns true if the user clicked OK and false if the user clicked Cancel. Example:

"use strict";

if (confirm('Do you really want to format your hard drive?') === true)
  console.log('OK, as you wish!');
else {
  console.log('Pweh!');
}
prompt

prompt is used to get input from the user. The first parameter specifies the text to display. The second parameter is optional and can be used to display a default value. Take a look at the example and experiment with it. Note the \' in the prompt message. This is used to tell the browser that the apostrophe does not terminate the string but is to be displayed as such:

"use strict";

const input = prompt('Congratulations, you\'re the master of the universe. ' +
  'Please enter your name:', 'Donald Duck');
if (input) console.log(`Well done ${input}`);

4.3.7. Constants

If we want to use a specific immutable value throughout our script, we can declare it as a constant using const. Constants have block scope, i.e. they are only visible within the block that they are defined. If we define a constant in the main part of our script it will be visible everywhere. The advantage of using the constant compared to using the value directly is the ease with which we can change it.

For instance, if we decide that the player in our latest space shooter should have 5 lives instead of 3, we simply change the value of our constant. Without constant we would have to find all the occurrences of the value in our script and replace them individually, risking to accidentally change other values.

const LIVES = 3;
console.log(LIVES);

4.3.8. Variables

A program spends most of its time manipulating some kind of data, e.g. integer and decimal (or floating point) numbers, text (called string) or other, potentially more complex, data types. The information we need, for internal calculations or for display to the user, has to be stored somehow. To store data, we use variables. A variable is simply a place in computer memory where some information is stored.

To work with a variable we first need to declare it using the let or var keyword. The former has block scope whereas the latter has function scope. For an illustration of the difference, see let vs var. For a more detailed explanation with examples see developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/let. After declaration the variable value is undefined. We can store some information in it using the = operator. Let’s take a look at an example:

"use strict";

// We want to recalculate the health of our spaceship after it has been hit by an enemy bullet.
// We declare the variable for the initial health level and assign it a value of 100.
let health; // Declare a variable with block scope. Default value undefined.
alert(`Current health: ${health}`); //
health = 100; // We assign the value 100 to the variable.
alert(`Current health: ${health}`);
/* We declare a constant to store the damage caused by an enemy bullet hit. A constant cannot
be changed. */
const SHOT_IMPACT = 20;

// *** Boom, we have been hit ***
// Now we calculate the new health value
health = health - SHOT_IMPACT;
alert(`We've been hit! Current health: ${health}`);
Variable names
  • Variable names can contain letters, digits, underscores, and dollar signs.

  • Variable names must begin with a letter.

  • Variable names can also begin with $ and _.

  • Variable names are case sensitive (y and Y are different variables).

  • Reserved words (like JavaScript keywords) cannot be used as variable names.

It is recommended to use camelCase, e.g. firstFraction instead of first_fraction or firstfraction etc. Please read the quick overview of JavaScript code conventions at www.w3schools.com/js/js_conventions.asp.

4.3.9. Data types

JavaScript is a dynamically typed language, meaning that we can store any type of data in a variable or pass it as an argument to a function. This is in contrast to traditional programming languages such as C, C++, Java etc. Nevertheless, we need to be aware of the main data types that we will use. In our programs we can use the typeof operator to determine the current type of a variable, as in this example:

"use strict";

let x = 7; // integer number
console.log(`Current type of x: ${typeof x}`);
x = 5.6e-2; // decimal number
console.log(`Current type of x: ${typeof x}`);
x = 'abc sdfsd'; // string
console.log(`Current type of x: ${typeof x}`);
x = false; // boolean
console.log('Current type of x: ' + typeof x);
Strings

A string is simply text, i.e. a sequence of one or more characters. Strings are always embedded within simple (') or double (") quotes. We can combine several strings using the + operator, as shown in the examples below. Strings have a number of properties and methods that make working with them much easier. Study www.w3schools.com/jsref/jsref_obj_string.asp for a full reference with many examples.

Examples:

"use strict";

const s1 = 'This is a string';
/* Now we write the combination of two strings, which is a new string, into the body.
 Note that we use the + operator to concatenate, i.e. to combine, three strings. */
document.body.innerHTML = `<p>Content of constant s1: ${s1}</p>`;

let firstName = 'Donald', lastName = 'Duck';
document.body.innerHTML += `<p>Hello ${firstName} ${lastName}, how are you today?</p>`;
firstName = 'Dagobert'; // We change the content of this variable.
document.body.innerHTML += `<p>Hello ${firstName} ${lastName}, how are you today?</p>`;
document.body.innerHTML += `<p>Hello ${firstName} ${lastName.toUpperCase()}, how are you
today?</p>`;
Numbers

JavaScript does not distinguish between integers and decimals. Numbers are always stored as double precision floating point numbers, following the international IEEE 754 standard. This format stores numbers in 64 bits, where the number (the fraction) is stored in bits 0 to 51, the exponent in bits 52 to 62, and the sign in bit 63 (cf. www.w3schools.com/js/js_numbers.asp). This has practical implications, some of which are problematic. It leads for instance to mathematical imprecision, which you can confirm by typing 0.1 + 0.2 in the console. To avoid this problem, we can use decimal.js. This problem is however common in almost all programming languages and not limited to JS. For details on the problem in different programming languages see 0.30000000000000004.com.

Numbers have properties and methods that make working with them much easier. Study www.w3schools.com/jsref/jsref_obj_number.asp for a full reference with many examples.

"use strict";

// We declare 4 variables and initialize them with 2 integer and 2 decimal numbers.
const i1 = 5000, i2 = 7345, d1 = 32.456787, d2 = -2.3e5; // exponential notation -> 2.3 * 10^5
/* We display their values as well as the results of simple operations.
   Note that the + operator automatically converts the value after the + into a string if
   the value in front of the + is a string.
   On the other hand, if there is nothing in front of the + operator and it is followed by a
   string, then this string will be converted to a number, if possible, otherwise to NaN
   (not a number).
 */
console.log(`i1: ${i1} i2: ${i2} d1: ${d1} d2: ${d2}`);
/* Note that we need to put the calculations into parentheses, otherwise i1 + i2 would
 give the string '50007345'. Try it!
  */
console.log('i1 + i2: ' + (i1 + i2));
console.log(`i1 - i2: ${i1 - i2}`);
console.log('i1 / i2: ' + (i1 / i2));
console.log(`i1 * i2: ${i1 * i2}`);
console.log('d1 + d2: ' + (d1 + d2));
console.log(`d1 - d2: ${d1 - d2}`);
console.log('d1 / d2: ' + (d1 / d2));
console.log(`d1 * d2: ${d1 * d2}`);

let input1 = prompt('Please enter your first number: ');
let input2 = prompt('Please enter your second number: ');
console.log(`input1: ${input1} input2: ${input2}`);
console.log(`input1 + input2: ${input1 + input2}`);
console.log(`input1 - input2: ${input1 - input2}`);
console.log(`input1 / input2: ${input1 / input2}`);
console.log(`input1 * input2: ${input1 * input2}`);
console.log('Oops! Something went wrong. Can you spot the problem?');
console.log('Apparently the + operator treats our numbers as strings!');
console.log("Let's fix it:");
input1 = parseFloat(input1); // Convert string to floating point number.
input2 = parseFloat(input2); // Ditto.
console.log(`input1: ${input1} input2: ${input2}`);
console.log(`input1 + input2: ${input1 + input2}`);
console.log(`input1 - input2: ${input1 - input2}`);
console.log(`input1 / input2: ${input1 / input2}`);
console.log(`input1 * input2: ${input1 * input2}`);

console.log('Problem:');
// Given the limited precision of floating point numbers, we need to be careful with decimals.
let i = 0;
// The toFixed() method converts a number into a string with a specified number of decimals.
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
i = i + 0.2;
console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
console.log('The same with a loop:');
// This is a for loop, which we'll discuss later.
for (let i = 0; i < 2; i += .2) console.log(`i: ${i} i.toFixed(1): ${i.toFixed(1)}`);
Booleans

A boolean value is either true or false (cf. www.w3schools.com/js/js_booleans.asp). This data type is used in conditions and comparisons. Example:

"use strict";

const flag = true, x = 7, y = 34.6;
console.log(flag);
console.log(`x: ${x} y: ${y} => x is bigger than y: ${x > y}`);
console.log(`x - y + 20 is negative: ${(x - y + 20) < 0}`);
Conversions

JavaScript provides several options to convert between data types. To convert strings into numbers, we can put the + operator in front of the string. If the string cannot be fully converted into a number, the result will be NaN, meaning not a number. To transform user input into a number, it is usually preferable to use parseInt or parseFloat. These functions take a string as input and return an integer or a decimal (float, shortcut for floating point number). The advantage of these functions is that even if there are non-numerical characters in the string, as long as there is at least one digit at the beginning of the string, they will return a number and simply ignore the other characters. To convert anything into a string, we can use toString, which is a method that every object has by default. Let’s look at a couple of examples:

"use strict";

const a1 = '66.76', a2 = '5'; // Declare and define 2 string variables.
console.log(a1 + a2); // Concatenation of 2 strings.
console.log(+a1 + +a2); // Conversion to numbers.
const s1 = prompt('Please enter your first number: '); // string 1
const s2 = prompt('Please enter your second number: '); // string 2
console.log(s1 + s2); // Concatenation of 2 strings.
console.log(parseInt(s1) + parseInt(s2)); // Conversion to integers.
console.log(parseFloat(s1) + parseInt(s2)); // Conversion to 1 float + 1 int.
console.log(parseInt(s1) + parseFloat(s2)); // Conversion to 1 int + 1 float.
console.log(parseFloat(s1) + parseFloat(s2)); // Conversion to floats.
const x1 = 123, x2 = 456; // Declare and define 2 number variables.
console.log(x1 + x2); // Addition of 2 numbers.
console.log(x1.toString() + x2); // Conversion to strings.
console.log(x1 + x2.toString()); // Conversion to strings.
console.log(x1.toString() + x2.toString()); // Conversion to strings.
const b1 = false, b2 = true; // Declare and define 2 booleans.
console.log(b1 + b2); // Addition of 2 booleans (0 + 1 -> 1).
console.log(b1.toString() + b2); // Conversion to strings.
console.log(b1 + b2.toString()); // Conversion to strings.
console.log(b1.toString() + b2.toString()); // Conversion to strings.
Math

The Math object offers a number of useful methods. Study www.w3schools.com/js/js_math.asp and www.w3schools.com/jsref/jsref_obj_math.asp.

Regular expressions

Regular expressions provide a very powerful means to search for patterns in strings. Study www.w3schools.com/js/js_regexp.asp and www.w3schools.com/js/js_regexp.asp.

Maps and sets

4.3.10. Operators

Study the JavaScript operator documentation on w3schools: www.w3schools.com/js/js_operators.asp and www.w3schools.com/js/js_comparisons.asp.

eval

eval takes a string argument and interprets it as JavaScript code.

"use strict";

console.log(eval('if (5 < 7) true; else false;'));
For security and performance reasons usage of eval should be avoided (see developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval).

4.3.11. Conditional statements

if

Oftentimes the behavior of our program depends on a condition. For example, if we have developed a social network and we want to congratulate the user on his birthday, we first need to verify the condition of the user’s birthday being true. Let’s look at a couple of examples:

"use strict";

// We ask the user a very tough question and store his answer.
const correctAnswer = 7;
const userAnswer = parseInt(prompt('3 + 4 = '));
// Now we check whether his answer is correct or not and react accordingly.
if (userAnswer === correctAnswer) document.body.innerHTML += 'Well done!<br>';
else document.body.innerHTML += 'Please check your answer.<br>';

/* Let's assume the user's birthday is 1.2.1983.
 First we need to get the current date.
 Then we need to check whether day and month correspond
 to the user's birthday.
 If that's the case, then we will congratulate him, otherwise
 we'll tell him how far away his birthday is.
 */
// getMonth() returns a number between 0 and 11, so we need to subtract 1!
const userMonth = 1; // February, January would be 0.
const userDay = 1; // User's month and day of birth.
const date = new Date(); // Get the current date.
if (date.getMonth() === userMonth && date.getDate() === userDay)
  alert('Happy birthday!');
/* The else part is optional. In this case we don't need it, as we only want
 to do something if the condition is true.
 */

// Now we create a little script that displays the name of the current month.
const month = date.getMonth(); // We reuse the date variable created above.
if (month === 0) document.body.innerHTML += 'January<br>';
else if (month === 1) document.body.innerHTML += 'February<br>';
else if (month === 2) document.body.innerHTML += 'March<br>';
else if (month === 3) document.body.innerHTML += 'April<br>';
else if (month === 4) document.body.innerHTML += 'May<br>';
else if (month === 5) document.body.innerHTML += 'June<br>';
else if (month === 6) document.body.innerHTML += 'July<br>';
else if (month === 7) document.body.innerHTML += 'August<br>';
else if (month === 8) document.body.innerHTML += 'September<br>';
else if (month === 9) document.body.innerHTML += 'October<br>';
else if (month === 10) document.body.innerHTML += 'November<br>';
else if (month === 11) document.body.innerHTML += 'December<br>';
switch

Another instruction that can be used if there are many alternatives to choose from is switch. A detailed explanation can be found at www.w3schools.com/js/js_switch.asp. Here’s is an example:

"use strict";

const date = new Date(); // Get the current date.
const month = date.getMonth(); // We reuse the date variable created above.
switch (month) {
  case 0:
    document.body.innerHTML += 'January<br>';
    break;
  /* If we leave this out, the following instructions will be executed,
   even though month is 0 and not 1.
   */
  case 1:
    document.body.innerHTML += 'February<br>';
    break;
  case 2:
    document.body.innerHTML += 'March<br>';
    break;
  case 3:
    document.body.innerHTML += 'April<br>';
    break;
  case 4:
    document.body.innerHTML += 'May<br>';
    break;
  case 5:
    document.body.innerHTML += 'June<br>';
    break;
  case 6:
    document.body.innerHTML += 'July<br>';
    break;
  case 7:
    document.body.innerHTML += 'August<br>';
    break;
  case 8:
    document.body.innerHTML += 'September<br>';
    break;
  case 9:
    document.body.innerHTML += 'October<br>';
    break;
  case 10:
    document.body.innerHTML += 'November<br>';
    break;
  case 11:
    document.body.innerHTML += 'December<br>';
    break;
  default:
    document.body.innerHTML += 'Invalid month!<br>';
}

4.3.12. Loops

Loops are used to execute a block of, i.e. one or several, statements, several times. JavaScript provides the for, while, for in and do while loops. Each loop consists of a head and a body part.

for

The for loop head specifies the start value, a condition and what should be done after each iteration of the body. This type of loop is the preferred choice if we know the number of iterations in advance. We already saw an example in Numbers. Let’s study a couple more examples:

"use strict";

// A very basic loop.
for (var i = 0; i < 4; i++) {
  // Variable i is defined for the whole function, in this case the whole script as there
  // is no function.
  document.body.innerHTML += `i: ${i}<br>`;
}

/*
 Let's calculate the sum of all numbers between a and b, except those that
 can be divided by 5.
 */
let a = 2, b = 123, sum = 0;
for (i = a; i <= b; i++) { // i was defined using var -> function scope
  if (i % 5 !== 0) {
    sum += i;
  }
}
console.log('sum: ' + sum);

console.log('Here is an illustration of what block scope means.');
// A very basic loop.
for (let j = 0; j < 4; j++) { // Variable j is only defined for this block.
  document.body.innerHTML += `i: ${i}<br>`;
}

/*
 Let's calculate the sum of all numbers between a and b, except those that
 can be divided by 5.
 */
sum = 0;
for (j = a; j <= b; j++) { // j was defined using let -> block scope
                           // To solve this we would have to define j for this block using let j = a;
  if (j % 5 !== 0) {
    sum += j;
  }
}
console.log(`sum: ${sum}`);
while

The while loop head only specifies the condition that needs to be true for the loop body to be executed. The condition can be any boolean expression, i.e. we do not necessarily need a counter variable. Let’s look at an example:

"use strict";

const result = 2 + 2;
let input, count = 1; // Define variables.
/* As long as the user does not enter the correct answer, we loop.
 Given that prompt returns a string, we need to convert it to an integer.
 */
while ((input = parseInt(prompt('2 + 2 = '))) !== result) {
  document.body.innerHTML += 'Wrong answer, please try again.<br>';
  count++; // Increase the attempt counter.
}
console.log(`Correct, it took you ${count} attempt(s).`);
for in

for in loops through the enumerable properties of an object and includes the enumerable properties of the prototype chain (cf. Objects and classes).

"use strict";

const car = {
  color: 'black',
  weight: 1500,
  length: 4.5
};

for (const prop in car) {
  console.log(`Property name: ${prop} property value: ${car[prop]}`);
}

You should not use for in to loop through an array, as the following example inspired by "Effective JavaScript" p. 132 shows:

"use strict";

const grades = [23, 45, 56, 43, 32, 29];
let total = 0;
for (const grade in grades) {
  total += grade;
}
let average = total / grades.length;
console.log(`Average: ${average}`); // 2057.5 -> wrong, but why?

// Let's add a debugging instruction to our loop, so that we can see what happens:
total = 0;
for (const grade in grades) {
  total += grade;
  console.log(`${grade} ${total}`);
}

// Now we see that for in loops through the KEYS of the array, not the VALUES.
// The correct approach is to use a for loop:
total = 0;
for (let i = 0; i < grades.length; i++) {
  total += grades[i];
  console.log(`${grades[i]} ${total}`);
}
average = total / grades.length;
console.log(`Average: ${average}`); // 38 -> correct
do while

This is identical to the while loop, except that the body of do while is always executed at least once, as the condition is only checked after the execution instead of before.

4.3.13. Jumps and exceptions

Jump statements instruct the JavaScript interpreter to jump to a different source code location.

Labeled statements

A JavaScript statement can be labeled by writing the label followed by a colon.

break

Used alone, break causes the innermost enclosing loop or switch statement to exit immediately. If followed by a label, it terminates the enclosing statement that has the specified label. Take a close look at these examples:

"use strict";

const matrix = [
  [1, 3, 5, 7, 9], // sum 25
  [2, 2, 'a', 2, 2], // sum 8
  [3, 4, 5, 6, 7], // sum 25
  [9, 8, 7, 6, 5], // sum 35
  [4, 4, 4, 4, 4] // 20
];
let sum = 0, error = false;
outerloop: for (let i = 0; i < matrix.length; i++)
  for (let j = 0; j < matrix[0].length; j++) {
    if (typeof matrix[i][j] !== 'number') {
      error = true;
      break outerloop;
    }
    sum += matrix[i][j];
  }
console.log(`Sum: ${sum} error: ${error}`);

sum = 0;
error = false;
for (let i = 0; i < matrix.length; i++)
  for (let j = 0; j < matrix[0].length; j++) {
    if (typeof matrix[i][j] !== 'number') {
      error = true;
      break;
    }
    sum += matrix[i][j];
  }
console.log(`Sum: ${sum} error: ${error}`);
continue

continue is similar to break, except that it does not exit the loop but restarts it at the next iteration, i.e. it terminates only the current iteration, not the whole loop. Example:

"use strict";

const matrix = [
  [1, 3, 5, 7, 9], // sum 25
  [2, 2, 'a', 2, 2], // sum 8
  [3, 4, 5, 6, 7], // sum 25
  [9, 8, 7, 6, 5], // sum 35
  [4, 4, 4, 4, 4] // 20
];
let sum = 0, error = false;
outerloop: for (let i = 0; i < matrix.length; i++)
  for (let j = 0; j < matrix[0].length; j++) {
    if (typeof matrix[i][j] !== 'number') {
      error = true;
      continue outerloop;
    }
    sum += matrix[i][j];
  }
console.log(`Sum: ${sum} error: ${error}`);
Exception handling

Often our programs rely on the user to provide reasonable input. But what happens if the user provides invalid input? For instance, we have a function that calculates the product of all given parameters, assuming they are all numbers. If one of the parameters is a string, our script might not perform as expected. That’s not very professional and chances are, our users won’t like it. A better way is to throw an exception using the throw instruction and catch it using try catch finally. In the try block we put the code that is at risk of breaking, for instance on wrong user input. In the catch block we put the code that should be executed if something went wrong in the try block, i.e. an exception occurred. The finally block is optional and will always be executed as the final block. JavaScript has a built-in Error object (cf. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), that serves to signal an error. Let’s study an example:

"use strict";

const product = () => {
  let prod = 1;
  for (let i = 0; i < arguments.length; i++) {
    if (typeof arguments[i] !== 'number')
      throw new Error('All arguments must be numbers!');
    prod *= arguments[i];
  }
  return prod;
};

try {
  console.log(product(2, 34, 5)); // OK
  console.log(product(2, 'abc', 5)); // Wrong arg -> trouble.
}
catch (error) {
  console.error(error);
}
finally {
  console.log('Job done');
}

4.3.14. Functions

A function is a block of code that is executed by "calling" the function. Functions are of fundamental importance in software development, as they permit to reuse code, i.e. we solve a problem once and can then use the solution as often as we want without having to reinvent the wheel.

Normal functions

A function is defined using the function keyword, followed by parentheses (), followed by curly brackets {}. A function can take one or several parameters, which represent information that is given to the function by the caller.

Parameters of primitive types are passed by value, whereas objects are passed by reference.

Within the curly brackets we find the body of the function, i.e. the code that is executed when the function is called. A function can return an object using the return keyword. Please note that return does 2 things:

  • Returns the given object, or nothing if no object is provided.

  • Terminates the function. This means that any instructions following return will not be executed.

Normal functions have a built-in arguments object, which contains an array of the arguments used when the function was called.

Also note that a normal function can be called before its declaration. This is called hoisting.

Let’s study a couple of examples:

"use strict";

// A simple sum function
function sum(a, b) {
  return a + b;
}
const a = 34, b = 67.89;
console.log(`The sum of ${a} and ${b} is ${sum(a, b)}.`);

/* We define a function without parameters that adds a paragraph and returns
 nothing.
 */
function sayHelloWorld() {
  const r = Math.floor(Math.random() * 256); // Generate random red component.
  const g = Math.floor(Math.random() * 256); // Random green component.
  const b = Math.floor(Math.random() * 256); // Random blue component.
  const p = document.createElement('p'); // Create new <p> element.
  p.style.color = 'blue'; // Set text and background colors.
  p.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
  p.innerHTML = 'Hello world!'; // Set content.
  const main = document.querySelector('main'); // Get main element.
  main.appendChild(p); // Append the new <p> element to <main>.
}

// Now we call the function, i.e. we reuse the code to display the text 5 times.
for (let i = 1; i <= 5; i++) sayHelloWorld();

// Here we call the factorial function even before it's defined -> hoisting.
console.log(`The factorial of 7 is ${factorial(7)}.`);

/* The factorial function takes one parameter, a positive integer, and returns
 the number's factorial. Remember the formula: n! = n * (n - 1) * ... * 2.
 */
function factorial(x) {
  // If x is not a number, we return false.
  if (typeof x !== 'number') return false;
  x = x.toFixed(0); // Make sure x is an integer.
  let fact = 1; // In this variable we store the current result.
  for (let i = x; i >= 2; i--) fact *= i; // Loop to calculate factorial.
  return fact; // Return the result.
}

/* We can also define a function that works with a variable number of
 parameters, a so called variadic function.
 */
function printArgs() {
  for (let i = 0; i < arguments.length; i++)
    console.log(`Argument ${i}: ${arguments[i]} of type ${typeof arguments[i]}`);
  console.dir(arguments);
}

printArgs('Hi', 5.6, 5, false);

function printFromTo(values, {start=0, end=-1, step=1} = {}) {
  if (step === 0)
    console.dir(new Error('Infinite loops are not a good idea...'));
  else for (let i = start; i < end; i += step) console.log(values[i]);
}

printFromTo([1,2,3,4,5,6]);
printFromTo([1,2,3,4,5,6], {start: 1, end: 6, step: 0});
printFromTo([1,2,3,4,5,6], {start: 1, end: 6, step: 2});
Arrow functions
An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

An arrow function is created using . It means that the value in the parentheses, if any, is passed as parameter to the block of code that the arrow is pointing to. An arrow function is defined like a variable.

Contrary to normal functions, which are read at compile-time, arrow functions are read at run-time. Therefore they must be defined in the code before they can be called.

Also contrary to a function, an arrow function does not have an arguments object (cf. point 18.a in www.ecma-international.org/ecma-262/6.0/#sec-functiondeclarationinstantiation). Instead we can use so called rest parameters using the …​ operator, which can also be used with normal functions.

"use strict";

// A simple sum function
const sum = (a, b) => a + b;

const a = 34, b = 67.89;
console.log(`The sum of ${a} and ${b} is ${sum(a, b)}.`);

/* We define a function without parameters that adds a paragraph and returns
 nothing.
 */
const sayHelloWorld = () => {
  const r = Math.floor(Math.random() * 256); // Generate random red component.
  const g = Math.floor(Math.random() * 256); // Random green component.
  const b = Math.floor(Math.random() * 256); // Random blue component.
  const p = document.createElement('p'); // Create new <p> element.
  p.style.color = 'blue'; // Set text and background colors.
  p.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
  p.innerHTML = 'Hello world!'; // Set content.
  const main = document.querySelector('main'); // Get main element.
  main.appendChild(p); // Append the new <p> element to <main>.
};

// Now we call the function, i.e. we reuse the code to display the text 5 times.
for (let i = 1; i <= 5; i++) sayHelloWorld();

/* The factorial function takes one parameter, a positive integer, and returns
 the number's factorial. Remember the formula: n! = n * (n - 1) * ... * 2.
 */
const factorial = x => {
  // If x is not a number, we return false.
  if (typeof x !== 'number') return false;
  x = x.toFixed(0); // Make sure x is an integer.
  let fact = 1; // In this variable we store the current result.
  for (let i = x; i >= 2; i--) fact *= i; // Loop to calculate factorial.
  return fact; // Return the result.
};

// A function expression can only be called after its definition.
console.log('The factorial of 7 is ' + factorial(7) + '.');

const printArgs = (...args) => {
  for (let i = 0; i < args.length; i++)
    console.log(`Argument ${i}: ${args[i]} of type ${typeof args[i]}`);
  console.dir(args);
};

printArgs('Hi', 5.6, 5, false);

const printFromTo = (values, {start=0, end=-1, step=1} = {}) => {
  if (step === 0)
    console.dir(new Error('Infinite loops are not a good idea...'));
  else for (let i = start; i < end; i += step) console.log(values[i]);
};

printFromTo([1,2,3,4,5,6]);
printFromTo([1,2,3,4,5,6], {start: 1, end: 6, step: 0});
printFromTo([1,2,3,4,5,6], {start: 1, end: 6, step: 2});

A major difference between a normal function and an arrow function is that in the case of a normal function, the scope is local whereas in the case of an arrow function, the scope is the surrounding code.

Use non-arrow functions for methods that will be called using the object.method() syntax. Those are the functions that will receive a meaningful this value from their caller. Use arrow functions for everything else.

The following example illustrates this difference (cf. this):

"use strict";

function localScope() {
  console.dir(this);
}

let globalScope = () => console.dir(this);

localScope();
globalScope();

For a more in-depth illustration of why this is very important, study stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback.

Variable scope

It is important to understand the concept of variable scope. A local variable, i.e. a variable that is declared inside the body of a function using the let or var keyword is called a local variable and only exists within the function body. A variable declared outside a function (with or without let or var) or a variable implicitly declared without let or var inside a function is a global variable, i.e. it exists even after the function execution has finished. Furthermore, if a global variable x is declared and inside a function we declare a local variable x, the local x will hide the global x, meaning that when we use x in the function, it will be the local and not the global one.

If you declare a variable without the var or let keyword, it will always be global, even if declared within a function! This can lead to errors that are hard to detect and should be avoided.

Let’s illustrate this with a few examples:

"use strict";

let x = 7; // Declare and initialize a global variable.

f1(4); // We can call the normal function before it is defined -> function hoisting.
console.log('x: ' + x); // x is the GLOBAL variable.

function f1(a) {
  let x = a / 2;
  console.log(`x: ${x}`); // x is the LOCAL variable.
  // The global variable x is not usable here.
}

let f2 = () => {
  x++; // We modify the global variable x, which is a very bad idea.
}

console.log(`x: ${x}`); // x is the GLOBAL variable.
f2();
console.log(`x: ${x}`); // x is the GLOBAL variable.

let f3 = () => {
  let x = 23;
  console.log(`x: ${x}`); // x is the LOCAL variable.
}

f3();
// x is GLOBAL, the local one from f3 does not exist outside of f3.
console.log(`x: ${x}`);

Note in the examples above that we called normal function f1 before its declaration. This behavior is possible because of function hoisting, i.e. a normal function is read at run-time, as mentioned above.

let vs var

This first example does not work as expected:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Lexical scoping 1</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const init = () => {
        const buttons = document.querySelectorAll('button');
        for (var i = 0; i < 3; i++) {
          buttons[i].addEventListener('click', () => {
            console.log(`Button ${i + 1} clicked`);
          });
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <button>B1</button>
      <button>B2</button>
      <button>B3</button>
    </main>
  </body>
</html>

The reason is that the scope of var is the entire enclosing function, so the value that will be used in the event handler is the last value of i, which is 3 in this case.

The second example solves this problem using let.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Lexical scoping 2</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const init = () => {
        const buttons = document.querySelectorAll('button');
        for (let i = 0; i < 3; i++) {
          buttons[i].addEventListener('click', () => {
            console.log(`Button ${i + 1} clicked`);
          });
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <button>B1</button>
      <button>B2</button>
      <button>B3</button>
    </main>
  </body>
</html>

let has block scope, which means that the value used in the event handler is the value of i at that point and time in the block, as one would intuitively expect.

Anonymous functions

We can pass a nameless function as a parameter to a function or assign it to a variable. This will be particularly useful when we deal with event handlers as we’ll see later on.

Here is an example of an anonymous function:

"use strict";

// This function takes as parameter a function and a number.
function f1(f, x) {
  // We call the function x times, each time passing i as parameter.
  for (let i = 0; i < x; i++) f(i);
}

// We call f1 with an anonymous function and a number as parameters.
f1(function (x) {
  console.log(x);
}, 5);

// The same using an anonymous function expression.
f1(x => {
  console.log(x);
}, 5);
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>x1</title>
    <meta charset=UTF-8>
  </head>
  <body>
  <script>
    'use strict';

    const init = () => {
      let i = 7;
      setInterval(() => {
        console.log(i--);
      }, 500);
    };

    addEventListener('load', init);
  </script>
  </body>
</html>
Asynchronous programming
Promises
A Promise is an object representing the eventual completion or failure of an asynchronous operation.

developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

hackernoon.com/transforming-callbacks-into-promises-and-back-again-e274c7cf7293

'use strict';

const httpGET = URL => {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.addEventListener('load', e => {
      resolve(e.target.response);
    });
    req.addEventListener('error', e => {
      reject(new Error(e.target.statusText));
    });
    req.open('GET', URL);
    req.send();
  });
};

console.log('x1');
httpGET('https://students.btsi.lu').then(res => {
    console.log(`Contents: ${res}`);
  },
  err => {
    console.log(`Something went wrong: ${err}`);
  }
);
console.log('x2');

const myfun4 = (x, resolve, reject) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (x > 5) resolve('All good');
      else reject('Not good');
    }, 2000);
  });
};
async and await
Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.
Remember, the await keyword is only valid inside async functions. If you use it outside of an async function’s body, you will get a SyntaxError.

hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

exploringjs.com/es2016-es2017/ch_async-functions.html

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

ponyfoo.com/articles/understanding-javascript-async-await

tc39.github.io/ecmascript-asyncawait

stackoverflow.com/questions/42964102/syntax-for-async-arrow-function

medium.com/front-end-hacking/async-await-is-not-about-making-asynchronous-code-synchronous-ba5937a0c11e

'use strict';

const httpGET = URL => {
  return new Promise((resolve, reject) => {
    const req = new XMLHttpRequest();
    req.addEventListener('load', e => {
      resolve(e.target.response);
    });
    req.addEventListener('error', e => {
      reject(new Error(e.target.statusText));
    });
    req.open('GET', URL);
    req.send();
  });
};

(async () => {
  console.log('x1');
  await httpGET('https://students.btsi.lu').then(res => {
      console.log(`Contents: ${res}`);
    },
    err => {
      console.log(`Something went wrong: ${err}`);
    }
  );
  console.log('x2');
})();
'use strict';

const resolveAfter2Seconds = x => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}

(async () => {
  console.log('x1');
  console.log(await resolveAfter2Seconds(10));
  console.log(await resolveAfter2Seconds(15));
  console.log('x2');
})();

4.3.15. Debugging

Debugging is a methodical process of finding and reducing the number of bugs, or defects, in a computer program.

The short video at youtu.be/0zWiq8FB3Xg shows how this works in practice using the following HTML document:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Debugging example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <script>
        "use strict";

        function f1() {
          // A very basic loop.
          for (let i = 0; i < 4; i++) {
            document.write('i: ' + i + '<br>');
          }
        }

        function f2() {
          // A very basic loop.
          for (var i = 0; i > 4; i++) {
            document.write('i: ' + i + '<br>');
          }
        }

        f1();
        f2();
      </script>
    </main>
  </body>
</html>

4.3.16. Arrays

Arrays are ordered value collections. Each value or element has its position, which is called index. The index of the first element is 0 and the position of the last element is the length of the array minus 1. The index is a 32 bit number, so the maximum number of elements in an array is 2^32 - 1. Arrays are dynamic and we do not have to specify any initial size.

Creating and iterating

We create an array either as an array literal, which is the recommended way, or using the Array constructor. The latter method is conducive to errors, as explained in the example:

"use strict";

const a1 = []; // Create an empty array.
const a2 = [1, 4, 2, 5, 3]; // Create an array with 5 numbers.
const a3 = ['Donald', 45, false]; // Create an array with 3 elements of different types.
console.log(`Length of a1: ${a1.length}`);
console.log(`Length of a2: ${a2.length}`);
console.log(`Length of a3: ${a3.length}`);
console.log('Elements of a1:');
for (let i = 0; i < a1.length; i++) console.log(a1[i]);
console.log('Elements of a2:');
/* This is a dangerous way of looping through an array, as all enumerable properties
 of the array and its prototype object will be returned and the order is not guaranteed
 by the ECMAScript specification.
 */
for (let i in a2) console.log(a2[i]);
console.log('Elements of a3:');
/* If the order of the returned elements is not important, we can use the in operator by
 adding a test to exclude inherited enumerable properties.
 */
for (let i in a3) {
  if (!a3.hasOwnProperty(i)) continue;
  console.log(a3[i]);
}

console.log('Elements of a3:');
// The most concise method is for of
for (const elem of a3) console.log(elem);

const a4 = new Array(); // Create an empty array.
/* You need to be careful when creating arrays with new. If there is only one parameter, it
 indicates the number of elements. If there are several parameters, these will be the
 elements of the array. This adds unneeded complexity and encourages errors.
 */
const a5 = new Array(8); // Create an array with no elements and length 8.
// Create an array with 3 elements of different types.
const a6 = new Array('Donald', 'Duck', 45, true);
console.log(`Length of a4: ${a4.length}`);
console.log(`Length of a5: ${a5.length}`);
console.log(`Length of a6: ${a6.length}`);
console.log('Elements of a4:');
for (const elem of a4) console.log(elem);
console.log('Elements of a5 using a normal for:');
for (let i = 0; i < a5.length; i++) console.log(a5[i]);
// Note the different behavior: for/in simply skips undefined elements.
console.log('Elements of a5 using for/in:');
for (let i in a5) console.log(a5[i]);
console.log('Elements of a6:');
for (const elem of a6) console.log(elem);

It is important to note that arrays are objects. Therefore the in operator will use all enumerable properties of the object itself and its prototype, as described in Testing properties. This will include methods that have been added to the prototype unless they are not enumerable. Furthermore, the ECMAScript specification does not fix the order in which the in operator returns elements. If element order is important, we should use a standard for loop to iterate through an array. If order does not matter, but we cannot guarantee that the array prototype is not polluted with enumerable properties, we should include a test to filter these, as shown in the examples above.

The preferred and most concise method to iterate over arrays is for of.
Adding and deleting
"use strict";

const a = [];
a[5] = 'Donald';
/* The array has only one element, at index 5, but the length of the array is 6!
   This is called a sparse array. */
console.log(`Contents of array a: ${a} length: ${a.length}`);
a[1] = false;
console.log(`Contents of array a: ${a} length: ${a.length}`);
a.push('Duck'); // Add an element at the end.
console.log(`Contents of array a: ${a} length: ${a.length}`);
a[a.length] = 23e4; // Another way of adding an element at the end.
console.log(`Contents of array a: ${a} length: ${a.length}`);
delete a[a.length-1]; // Delete the last element. This does not alter the array length!
console.log(`Contents of array a: ${a} length: ${a.length}`);
a.pop(); // Remove the last element and return it. This does adjust the length.
console.log(`Contents of array a: ${a} length: ${a.length}`);
console.log('Just removed ' + a.pop());
console.log(`Contents of array a: ${a} length: ${a.length}`);
Multidimensional arrays

An array element can be another array. So we can easily create multidimensional arrays like so: students.btsi.lu/evegi144/WAD/JS/array_multidim.html

"use strict";

const init = () => {
  const MATRIXLENGTH = 10; // 10 by 10
// Create an empty 10 x 10 matrix.
  const matrix = new Array(MATRIXLENGTH);
  for (let i = 0; i < MATRIXLENGTH; i++) matrix[i] = new Array(MATRIXLENGTH);
// Fill the matrix with random integers from [0, 99].
  for (let i = 0; i < MATRIXLENGTH; i++)
    for (let j = 0; j < MATRIXLENGTH; j++) matrix[i][j] = Math.floor(100 * Math.random());
// Display the matrix as a table.
  let table = '<table><caption>Random matrix</caption>';
  for (let i = 0; i < MATRIXLENGTH; i++) {
    table += '<tr>';
    for (let j = 0; j < MATRIXLENGTH; j++)
      table += `<td>${matrix[i][j]}</td>`;
    table += '</tr>';
  }
  document.body.innerHTML += `${table}</table>`;
};

addEventListener('load', init);
Methods

We have already seen push and pop methods. A complete reference with examples can be found at www.w3schools.com/jsref/jsref_obj_array.asp.

Turning arrays into parameters

Using the spread operator …​ we can turn arrays into parameters. Why would we want to do this? Let’s assume for instance, we want to determine the highest value in an array of numbers. Math.max() does not work on arrays. Using the spread oeprator, this is no problem: students.btsi.lu/evegi144/WAD/JS/array_spread.html

"use strict";

const arr = [];
for (let i = 0; i < 100; i++) arr.push(Math.random());
console.log(`The biggest number is ${Math.max(...arr)}`);

4.3.17. Template literals

A template literal is a new kind of string literal that can span multiple lines and interpolate expressions (include their results).
'use strict';

const firstName = prompt('Please enter your first name');
const lastName = prompt('Please enter your last name');
alert(`Hello ${firstName} ${lastName}, how are you?`);
The literal itself is delimited by backticks (`), the interpolated expressions inside the literal are delimited by ${ and }. Template literals always produce strings.

There’s much more to template literals, for the details please check the link above.

4.3.18. Objects and classes

In JavaScript, anything that is not a string, number, boolean, null or undefined is an object. Objects are collections of properties (cf. www.ecma-international.org/ecma-262/#sec-object-type). They allow us to regroup data and functions that belong together under one name and to reuse them. One purpose is the representation of real world objects. A good introduction can be found at developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects.

JS comes with a number of standard built-in objects, details of which can be found at developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects.

Prototypes
If you have traditional object-oriented programming experience, e.g. with C++ or Java, it is imperative to understand that JS is not class- but prototype-based. To get a good understanding of what the difference is and why it matters study You Don’t Know JS.

Most objects are derived from another object. This other object is called prototype in JavaScript. Object is the basic object in JavaScript from which normally all other objects inherit, directly or indirectly. To determine the prototype of an object, we use Object.getPrototypeOf(), where we pass the object as parameter.

Object creation

There are three ways to create objects.

as literals

From "JavaScript The Definitive Guide" p. 117: "An object literal is a comma-separated list of colon-separated name:value pairs, enclosed within curly braces." this refers to the object itself, whose property we want to use. Object literals are often used to avoid declaring a large number of global variables and functions by creating a namespace. This helps to document that a set of properties belong together and serve a common purpose. When we create an object literal, its prototype is Object.prototype. WMOTU Invaders object-oriented provides a sample application. Examples:

"use strict";

const myMerc = {
  brand: 'Mercedes',
  price: 100000,
  addVAT() {
    this.price = Math.ceil(this.price * 1.15);
  }
};
const myBMW = {
  brand: 'BMW',
  price: 60000,
  addVAT() {
    this.price = Math.ceil(this.price * 1.15);
  }
};

document.body.innerHTML += `Mercedes price without VAT: ${myMerc.price.toLocaleString()}<br>`;
myMerc.addVAT();
document.body.innerHTML += `Mercedes price with VAT: ${myMerc.price.toLocaleString()}<br>`;
document.body.innerHTML += `BMW price without VAT: ${myBMW.price.toLocaleString()}<br>`;

const empty = {}; // We create an empty object the REFERENCE to which is saved in this
// constant.
// We save the REFERENCE to object empty in constant myDog. This means that empty and myDog
// point to the same object!
const myDog = empty;
myDog.name = 'Idefix'; // We give the myDog, which is the same as the empty, object a name
myDog.bark = () => { // and a function.
  document.body.innerHTML += 'Wouf wouf!<br>';
};

document.body.innerHTML += `myDog's name is ${myDog.name}<br>`;
myDog.bark();
document.body.innerHTML += `Objects myDog and empty are identical: ${myDog === empty}<br>`;
document.body.innerHTML += `empty's name is ${empty.name}<br>`;
empty.bark();

const doggy = Object.create(myDog);
doggy.bark();
myDog.name = 'Toto';
document.body.innerHTML += `empty's name is ${empty.name}<br>`;
document.body.innerHTML += `myDog's name is ${myDog.name}<br>`;
document.body.innerHTML += `doggy's name is ${doggy.name}<br>`;
console.log('Prototype of empty: ');
console.dir(Object.getPrototypeOf(empty));
console.log('Prototype of myDog: ');
console.dir(Object.getPrototypeOf(myDog));
console.log('Prototype of doggy: ');
console.dir(Object.getPrototypeOf(doggy));
from a constructor with new

new must be followed by a constructor, which is a function that initializes the newly created object. As noted above, this refers to the object itself, whose property we want to use. The advantage of this approach compared to the previous one is that we can create as many objects as we like using the same constructor. When we use a constructor, the prototype is the prototype property of the constructor. Example:

"use strict";

class Car {
  constructor(brand, price) {
    this.brand = brand;
    this.price = price;
  }

  addVAT() {
    this.price = Math.ceil(this.price * 1.15);
  }
}

const myMerc = new Car('Mercedes', 100000), myBMW = new Car('BMW', 60000);

document.body.innerHTML += `Mercedes price without VAT: ${myMerc.price.toLocaleString()}<br>`;
myMerc.addVAT();
document.body.innerHTML += `Mercedes price with VAT: ${myMerc.price.toLocaleString()}<br>`;
document.body.innerHTML += `BMW price without VAT: ${myBMW.price.toLocaleString()}<br>`;
myMerc.price = 80000; // This only changes the price of myMerc, but not myBMW.
document.body.innerHTML += `Mercedes price without VAT: ${myMerc.price.toLocaleString()}<br>`;
document.body.innerHTML += `BMW price without VAT: ${myBMW.price.toLocaleString()}<br>`;
document.body.innerHTML += `Prototype of myMerc: ${Object.getPrototypeOf(myMerc)}<br>`;
console.log('Prototype of myMerc: ');
console.dir(Object.getPrototypeOf(myMerc));
document.body.innerHTML += `Prototype of myBMW: ${Object.getPrototypeOf(myBMW)}<br>`;
console.log('Prototype of myBMW: ');
console.dir(Object.getPrototypeOf(myBMW));
from a prototype with Object.create()

Almost all JavaScript objects inherit the properties of another object, their prototype. Object.create is a method that allows us to create a new object with a given prototype.

"use strict";

const myMerc = Object.create({
  brand: 'Mercedes',
  price: 100000,
  addVAT() {
    this.price = Math.ceil(this.price * 1.15);
  }
});
const myBMW = Object.create({
  brand: 'BMW',
  price: 60000,
  addVAT() {
    this.price = Math.ceil(this.price * 1.15);
  }
});

const init = () => {
  document.body.innerHTML += `Mercedes price without VAT: ${myMerc.price.toLocaleString()}<br>`;
  myMerc.addVAT();
  document.body.innerHTML += `Mercedes price with VAT: ${myMerc.price.toLocaleString()}<br>`;
  document.body.innerHTML += `BMW price without VAT: ${myBMW.price.toLocaleString()}<br>`;
  myMerc.price = 80000; // This only changes the price of myMerc, but not myBMW.
  document.body.innerHTML += `Mercedes price without VAT: ${myMerc.price.toLocaleString()}<br>`;
  document.body.innerHTML += `BMW price without VAT: ${myBMW.price.toLocaleString()}<br>`;
  document.body.innerHTML += 'The prototype of myBMW has the following properties:<br>';
  for (let o in Object.getPrototypeOf(myBMW)) document.body.innerHTML += `${o}<br>`;
  document.body.innerHTML += `Prototype of myBMW: ${Object.getPrototypeOf(myBMW)}<br>`;
  console.log('Prototype of myBMW: ');
  console.dir(Object.getPrototypeOf(myBMW));
}

addEventListener('load', init);

It is important to understand the difference between a constructor and a prototype. The prototype of an object contains all the properties that are common to all objects that have this prototype. They exist only once in the browser memory. Thus any changes to any of these properties are automatically reflected in all the objects that inherit from this prototype! Take a close look at the following examples:

"use strict";

const animal = {
  speciesName: '',
  sayHello() {
    document.write(`Hi, I am a ${this.speciesName}<br>`);
  }
};

// Object animal is the prototype of objects myDog and myCat.
const myDog = Object.create(animal), myCat = Object.create(animal);
animal.sayHello();
myDog.sayHello();
myCat.sayHello();
// If we change a property of the prototype, it will affect all objects with this prototype.
animal.speciesName = 'dog';
animal.sayHello();
myDog.sayHello();
myCat.sayHello();

// Now let's use constructors.
// Each object constructed with this constructor will have its own name and bark properties.
function Dog(name) {
  this.name = name;
  this.bark = () => {
    document.write(`${this.name} says wouf wouf!<br>`);
  };
}

function Cat(name) {
  this.name = name;
  this.miaow = function () {
    document.write(`${this.name} says mmmmiiiiaaaaoooowwww!<br>`);
  };
}

class Ape {
  constructor(name) {
    this.name = name;
  }

  scream() {
    document.write(`${this.name} screams Ouaaaaaaaahhhhhhhh!<br>`);
  }

  crackNut() {
    document.write(`${this.name} cracked a nut.<br>`);
  }
}

class Gorilla extends Ape {
  constructor(name) {
    super(name);
  }

  scream() {
    document.write(`${this.name} screams I am BOSS!!!<br>`);
  }
}

const Toto = new Gorilla('Toto');
Toto.scream();
Toto.crackNut();
Ape.prototype.crackNut = function() {
  document.write(`${this.name} smashed a nut.<br>`);
};
Toto.crackNut();

// Create 2 new dogs using the dog constructor.
let myDog1 = new Dog('Bello'), myDog2 = new Dog('Toro');
// Create 2 new cats using the cat constructor.
let myCat1 = new Cat('Kitty'), myCat2 = new Cat('Tiger');
myDog1.bark();
myDog2.bark();
myCat1.miaow();
myCat2.miaow();
myDog2.name = 'Idefix'; // Change a property of a dog object.
myDog1.bark(); // The change only affects the other dog object.
myDog2.bark();

// Now let's combine constructor and prototype.
// All dogs and cats will have the same species_name and sayHello properties
Dog.prototype = animal;
Cat.prototype = animal;
// Create 2 new dogs using the dog constructor.
myDog1 = new Dog('Bello');
myDog2 = new Dog('Toro');
// Create 2 new cats using the cat constructor.
myCat1 = new Cat('Kitty');
myCat2 = new Cat('Tiger');

// Assign prototype.
myDog1.sayHello();
myDog2.sayHello();
myCat1.sayHello();
myCat2.sayHello();
animal.speciesName = 'bird';
myDog1.sayHello();
myDog2.sayHello();
myCat1.sayHello();
myCat2.sayHello();
myDog1.bark();
myDog2.bark();
myCat1.miaow();
myCat2.miaow();
console.log('Prototype of myDog1:');
console.dir(Object.getPrototypeOf(myDog1));
console.log('Prototype of myDog2:');
console.dir(Object.getPrototypeOf(myDog2));
console.log('Prototype of myCat1:');
console.dir(Object.getPrototypeOf(myCat1));
console.log('Prototype of myCat2:');
console.dir(Object.getPrototypeOf(myCat2));
this

Mastery of the this keyword is a key requirement for understanding JS objects. You should therefore study the excellent explanation at developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this.

Getting and setting properties

To get or set properties of an object we can either use the dot (.) or the square bracket ([]) operators. There is an important difference between these two: when using the dot operator, the right-hand must be an identifier, i.e. the name of the property. This cannot be a programmatically generated dynamic value. When using the square bracket operator, the value between the brackets must be an expression that evaluates to a string or to something that can be converted to a string. This opens up endless possibilities, so let’s look at a few examples:

"use strict";

// The [] operator allows us to use names with spaces as property names:
const company = {
  'chief executive officer': 'Donald Duck'
};

//document.innerHTML += `${company.chief executive officer}<br>`; // Does not work!
document.innerHTML += `${company['chief executive officer']}<br>`; // Works!


class Student {
  constructor(name) {
    this.name = name;
    this.branches = {};
    // If the function is already defined, no need to define it again. This check is optional.
    if (!Student.prototype.addBranch)
      Student.prototype.addBranch = (name, average) => {
        this.branches[name] = average;
      };
    // If the function is already defined, no need to define it again. This check is optional.
    if (!Student.prototype.getTotalAverage)
      Student.prototype.getTotalAverage = () => {
        let avg = 0, count = 0;
        for (let branch in this.branches) {
          avg += this.branches[branch];
          count++;
        }
        if (count) return avg / count;
        else return undefined;
      }
  }
}

const st1 = new Student('Bill'), st2 = new Student('Bob');
document.body.innerHTML += `${st1.name} average: ${st1.getTotalAverage()}<br>`;
st1.addBranch("INFOR", 49);
document.body.innerHTML += `${st1.name} average: ${st1.getTotalAverage()}<br>`;
st1.addBranch("MATHE", 34);
document.body.innerHTML += `${st1.name} average: ${st1.getTotalAverage()}<br>`;
st2.addBranch("INFOR", 57);
document.body.innerHTML += `${st2.name} average: ${st2.getTotalAverage()}<br>`;
st2.addBranch("MATHE", 44);
document.body.innerHTML += `${st2.name} average: ${st2.getTotalAverage()}<br>`;
Deleting properties

delete removes a property from an object. If you invoke delete on a prototype property, then all objects inheriting from this prototype lose this property.

"use strict";

class Dog {
  constructor(name) {
    this.name = name;
    this.bark = () => {
      document.write(`${this.name} says wouf wouf!<br>`);
    };
  }
}

const myDog1 = new Dog('Bello');
myDog1.bark();
delete myDog1.name;
myDog1.bark();
const myDog2 = new Dog('Idefix');
myDog2.bark();
Testing properties

We have different options to test whether an object has a given property:

  1. The in operator requires the name of a property as a string on its left and an object on its right side. It returns true if the object has an enumerable own or inherited property by that name (cf. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for…​in).

  2. Object.keys(obj) returns an array of a given object’s own enumerable properties (cf. https://developer.mozilla .org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys).

  3. Object.getOwnPropertyNames(obj) returns an array of all properties (enumerable or not) found directly upon a given object (cf. https://developer.mozilla .org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames).

  4. The hasOwnProperty() of an object checks whether the object has a property of the given name. It does not consider inherited properties for which it returns false (cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object /hasOwnProperty).

  5. propertyIsEnumerable() returns a Boolean indicating whether the specified property is enumerable (cf. https://developer.mozilla .org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable).

The following example illustrates the different approaches:

"use strict";

class Dog {
  constructor(name) {
    this.name = name;
    this.bark = () => {
      document.write(`${this.name} says wouf wouf!<br>`);
    };
  }
}

const myDog1 = new Dog('Bello');
document.body.innerHTML += `toString is a property of myDog1: ${"toString" in myDog1}<br>`;

let keys = Object.keys(myDog1); // Get the enumerable own properties of the object.
let output = '';
for (let i = 0; i < keys.length; i++) // Display the property/value pairs.
  output += `${keys[i]}: ${myDog1[keys[i]]}<br>`;
document.body.innerHTML += `<p>${output}</p>`;

const s = [1, 2, 3, 4, 5]; // Create an array object.
keys = Object.keys(s); // Get the enumerable own properties of the object.
output = '';
for (let i = 0; i < keys.length; i++) // Display the property/value pairs.
  if (s.propertyIsEnumerable(keys[i])) output += `Enumerable ${keys[i]}: ${s[keys[i]]}<br>`;
document.body.innerHTML += `<p>${output}</p>`;

const props = Object.getOwnPropertyNames(s); // Get all own properties of the object.
output = '';
for (let i = 0; i < props.length; i++) {
  if (s.propertyIsEnumerable(props[i])) output += 'Enumerable ';
  output += `${props[i]}: ${s[props[i]]}<br>`;
}
document.body.innerHTML += `<p>${output}</p>`;
Object attributes

Every object has prototype, class and extensible attributes.

prototype

This attribute specifies the object’s parent, i.e. the object that it inherits from. As we’ve seen above, for object literals the prototype is Object.prototype, for objects created with new the prototype is the value of the constructor’s prototype property and for objects created with Object.create, the prototype is the first parameter passed to this method.

Every object has an isPrototypeOf method, which checks whether the object if the prototype of another object passed as parameter.

class

This internal read-only attribute tells us the type of the object. This attribute can take one of the following values: "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp" or "String".

extensible

This attribute determines whether new properties can be added to the object. Object.isExtensible() tells us whether an object is extensible or not. Object.preventExtensions allows us to make an object nonextensible.

Here is an example that illustrates the three object attributes:

"use strict";

const animal = {
  species: 'animal',
  displaySpecies () {
    document.body.innerHTML += `I'm a ${this.species}<br>`;
  }
};
const dog = Object.create(animal);
document.body.innerHTML += `Animal is the prototype of dog: ${animal.isPrototypeOf(dog)}<br>`;
document.body.innerHTML += `The class of animal is: ${Object.prototype.toString.call(animal).slice(8, -1)}<br>`;
console.dir(Object.getPrototypeOf(animal));
document.body.innerHTML += `The class of dog is: ${Object.getPrototypeOf(dog).toString().slice(8, -1)}<br>`;
console.dir(Object.getPrototypeOf(dog));
document.body.innerHTML += `animal is extensible: ${Object.isExtensible(animal)}<br>`;
Object.preventExtensions(animal);
document.body.innerHTML += `animal is extensible: ${Object.isExtensible(animal)}<br>`;
document.body.innerHTML += `dog is extensible: ${Object.isExtensible(dog)}<br>`;
Property attributes

Each object property has, in addition to its value, the following three attributes:

  1. configurable: true if and only if the type of this property may be changed and if the property may be deleted from the corresponding object.

  2. enumerable

  3. writable.

Using Object.defineProperty(obj, prop, descriptor) we can set these attributes (cf. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object /defineProperty).

Closures
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.

Study the MDN article, it explains the subject in detail with good examples. Then take a look at bonsaiden.github.io/JavaScript-Garden.

Private instance and prototype members

Here is a more advanced example, illustrating how we can have both private prototype and private object members and inheritance:

'use strict';

const init = () => {
  class Base {
    constructor() {
      let instance_day = "Tuesday"; // Private instance variable.

      this.getInstanceDay = () => { // Public instance variable getter.
        return instance_day;
      };

      this.setInstanceDay = day => { // Public instance variable setter.
        instance_day = day;
      };

      // Private prototype variable.
      let dayName = "Friday";

      // Private prototype methods
      const getPrototypeDayName = () => {
        return dayName;
      };

      const setPrototypeDayName = day => {
        dayName = day;
      };

      if (!Base.prototype.getDayName)
        Base.prototype.getDayName = () => {
          return getPrototypeDayName();
        };

      if (!Base.prototype.setDayName)
        Base.prototype.setDayName = day => {
          setPrototypeDayName(day);
        };
    }
  }

  class SubType extends Base {
    constructor() {
      super();
    }
  }

  const a = new Base(), b = new Base(), c = new SubType(), d = new SubType();
// Returns "Tuesday"
  document.body.innerHTML += `${a.getInstanceDay()}<br>`;

// Returns "Tuesday"
  document.body.innerHTML += `${b.getInstanceDay()}<br>`;

// Sets dayName to "Wednesday"
  a.setInstanceDay("Wednesday");

// Returns "Wednesday"
  document.body.innerHTML += `${a.getInstanceDay()}<br>`;

// Returns "Tuesday"
  document.body.innerHTML += `${b.getInstanceDay()}<br>`;

  document.body.innerHTML += `${a.getDayName()}<br>`;
  document.body.innerHTML += `${b.getDayName()}<br>`;
  a.setDayName('Saturday');
  document.body.innerHTML += `${a.getDayName()}<br>`;
  document.body.innerHTML += `${b.getDayName()}<br>`;

// Returns "Tuesday"
  document.body.innerHTML += `${c.getInstanceDay()}<br>`;

// Returns "Tuesday"
  document.body.innerHTML += `${d.getInstanceDay()}<br>`;

// Sets dayName to "Wednesday"
  c.setInstanceDay("Wednesday");

// Returns "Wednesday"
  document.body.innerHTML += `${c.getInstanceDay()}<br>`;

// Returns "Tuesday"
  document.body.innerHTML += `${d.getInstanceDay()}<br>`;

  document.body.innerHTML += `${c.getDayName()}<br>`;
  document.body.innerHTML += `${d.getDayName()}<br>`;
};

addEventListener('load', init);
call, apply and bind

Given that functions are objects in JavaScript, these three methods allow us to call a function as if it were a method of another object. Detailed descriptions of the Function object and examples can be found at developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function.

"use strict";

function printProperties() {
  document.body.innerHTML += 'Here come the properties...<br>';
  for (let p in this) document.body.innerHTML += `Property ${p}<br>`;
}

const o = {
  x: 1
};

printProperties();
printProperties.call(o);
Object

The global Object object has a number of properties. The details can be found at developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object. For instance, if we want to determine the number of an object’s own enumerable properties, we can use Object.keys(object).

4.3.19. Events

So far we have seen how to write scripts that execute sequentially, i.e. instructions are processed one after the other. In real life, however, our browser spends most of its time waiting for something to happen, e.g. a key or mouse button is pressed, a picture is loaded, a given timespan has expired, etc. This "something" is called an event in JavaScript. See the references below for the full list of available events.

For our scripts to be able to react to an event, we need to declare functions, called event listeners or handlers, that are invoked by the browser when a specific event occurs. Our handler will be automatically passed an Event object (cf. references).

www.w3schools.com/jsref/dom_obj_event.asp

developer.mozilla.org/en-US/docs/Web/Events

developer.mozilla.org/en-US/docs/Web/API/Event

www.javascriptkit.com/script/script2/xeye.shtml

Registering event listeners

In order to tell the browser what it should do if an event occurs, we need to register an event listener. There are 3 ways to do this.

addEventListener

The recommended way is to use the addEventListener method (cf. https://developer. mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener) of the object that will be the target of the event. The first parameter is a string containing the name of the event, the second one will be either an anonymous function or the name (without apostrophes) of the function that will handle the event. The optional third parameter allows to have the listener triggered already in the capture phase as described below. We can register several listeners for the same event using this method:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Handling events using addEventListener</title>
    <meta charset=utf-8>
    <script src=event_listeners1.js></script>
  </head>
  <body>
    <main>
      <button>Click me!</button>
    </main>
  </body>
</html>
"use strict";

const init = () => {
  const clickHandler1 = () => {
    alert("I'm the first click event handler");
  };

  const clickHandler2 = (event, text) => { // This is the function that will handle the click
    // event.
    console.dir(event);
    alert(`I'm the second click event handler. You asked me to show this text: ${text}`);
  };

  const myButton = document.querySelector('button'); // Get the button element from the DOM.
  /* Register the first button click handler.
   Note that clickHandler1 is just a pointer to the function.
   This works because clickHandler1 takes no parameter except the event, which it receives
   by default. */
  myButton.addEventListener('click', clickHandler1);
  /* Register the button's second click event handler using an anonymous function.
   The function receives the event as a parameter by default.
   Our event handler takes a text to display as second parameter.
   To pass this to our event handler, we need to use an anonymous function.
   */

  myButton.addEventListener('click', event => {
    clickHandler2(event, 'some random text');
  });
};

window.addEventListener('load', init); // Register the event handler for the load event.
Object property

Another option for event listener registration is the use of the corresponding object property. The name of this property is "on" followed by the event name, e.g. "onclick", "onload" etc. If a handler for this event has already been registered using this approach, it will be replaced:

"use strict";

const init = () => {
  const handleClick = event => { // This is the function that will handle the click event.
    alert('You clicked me!');
  };

  const myButton = document.querySelector('button'); // Get the button element from the DOM.
  myButton.onclick = handleClick; // Register the click event handler as an object property.
};

onload = init; // Register the load event handler as an object property.
HTML attribute

The third and least preferable registration method is the use of an HTML attribute. This way should be avoided as it violates the separation of content and behavior:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Handling events using HTML attributes</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <button onclick="alert('You clicked me!');">Click me!</button>
    </main>
  </body>
</html>
Invocation order

Handlers registered via an object property or HTML attribute are always executed first. Handlers registered with addEventListener are called in the order in which they were registered.

Event flow

The chart at www.w3.org/TR/DOM-Level-3-Events/#dom-event-architecture illustrates the three event phases: capture, target and bubble. If the target of an event is a standalone object (e.g. window), only the event handler on that object will be triggered. If the target object is a tree, for instance the document element, most, but not all, events bubble up the tree as illustrated in the chart. This avoids the need to register event handlers on lots of different elements. We can register a single event handler at the top of the tree, which can deal with the events triggered on its children. If we set the third parameter of addEventListener to true, events are captured before reaching the target object. As seen in the chart, this is the inverse process of bubbling. It provides an opportunity for the parent objects to analyze the event before it reaches the target. This is useful in certain situations, for instance for the implementation of drag and drop, as we’ll see in Drag and drop:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Event propagation with capturing demo</title>
    <meta charset=utf-8>
    <style>
      body {
        background-color: black;
      }

      main {
        background-color: gold;
      }

      p {
        background-color: green;
      }
    </style>
    <script>
      'use strict';

      /* Note that we use event CAPTURING in all cases here to illustrate the concept.
         We would normally leave out the third parameter of addEventListener.
       */
      const init = () => {
        const mouseHandler = event => {
          console.dir(this);
          console.log(`mouseHandler current event target: ${event.currentTarget}`);
        };

        window.addEventListener('click', mouseHandler, true);
        document.body.addEventListener('click', mouseHandler, true);
        document.getElementsByTagName('main')[0].addEventListener('click', mouseHandler, true);
        document.getElementsByTagName('p')[0].addEventListener('click', mouseHandler, true);
      };

      window.addEventListener('load', init, true);
    </script>
  </head>
  <body>
    <main>
      <p>This is a paragraph</p>
    </main>
  </body>
</html>

The following example is identical to the previous one, except that the third parameter of addEventListener is not set: students.btsi.lu/evegi144/WAD/JS/events2.html

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Event propagation without capturing demo</title>
    <meta charset=utf-8>
    <style>
      body {
        background-color: black;
      }

      main {
        background-color: gold;
      }

      p {
        background-color: green;
      }
    </style>
    <script>
      'use strict';

      // Note that we don't use event CAPTURING.
      const init = () => {
        const mouseHandler = event => {
          console.dir(this);
          console.log(`mouseHandler current event target: ${event.currentTarget}`);
        };

        window.addEventListener('click', mouseHandler);
        document.body.addEventListener('click', mouseHandler);
        document.getElementsByTagName('main')[0].addEventListener('click', mouseHandler);
        document.getElementsByTagName('p')[0].addEventListener('click', mouseHandler);
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <p>This is a paragraph</p>
    </main>
  </body>
</html>
Preventing event propagation and/or default actions

In some cases we might want to prevent the default action for a given event to occur, for instance, in order to prevent a form from being submitted when the user clicks the submit button. For this purpose we can call the preventDefault method. We can also stop event propagation using stopPropagation. In most cases our event handlers will not return a value. However, if we want to prevent default action and stop propagation, we can return false in our event handler. This works only if the event handler has been registered as an object property or an HTML attribute. With addEventListenener we can call the preventDefault and/or stopPropagation methods:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Preventing event propagation and default action</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const init = () => {
        document.forms[2].addEventListener('submit', e => {
          e.preventDefault();
          //e.stopPropagation();
          console.log('Form 3 not submitted');
        });
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <form action=f1.php onsubmit="console.log('Form 1 submitted');">
        <input name=i1>
        <input type=submit name=s1 value=Send>
      </form>
      <form action=f2.php onsubmit="console.log('Form 2 not submitted'); return false;">
        <input name=i2>
        <input type=submit name=s2 value=Send>
      </form>
      <form action=f3.php>
        <input name=i3>
        <input type=submit name=s3 value=Send>
      </form>
    </main>
  </body>
</html>
Keyboard events

It is important to understand the difference between the three keyboard events keydown, keypress and keyup: When you press a key down, a keydown event is triggered, followed by a keypress event. If you keep the key pressed, keypress events will continue to be generated at regular time intervals until you release the key, in which case a keyup event will be triggered. The following program displays all the attributes of the key events:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Key event handler example 1</title>
    <meta charset=utf-8>
    <style>
      table {
        border-collapse: collapse;
        width:           100%;
        padding:         5px;
      }

      table, th, td {
        border:     1px solid red;
        text-align: center;
      }

      table caption {
        font-weight: bold;
        color:       blue;
      }
    </style>
    <script>
      'use strict';

      const init = () => {
        const key = event => {
          let table, i;
          if (event.type === 'keydown') i = 0;
          else if (event.type === 'keypress') i = 1;
          else i = 2;
          table = document.getElementsByTagName('table')[i];
          table.querySelector('caption').innerHTML = event.type +
            ' ' + new Date().toTimeString();
          table.querySelector(`#altKey${i}`).innerHTML = event.altKey;
          table.querySelector(`#char${i}`).innerHTML = event.char;
          table.querySelector(`#charCode${i}`).innerHTML = event.charCode;
          table.querySelector(`#code${i}`).innerHTML = event.code;
          table.querySelector(`#ctrlKey${i}`).innerHTML = event.ctrlKey;
          table.querySelector(`#isComposing${i}`).innerHTML = event.isComposing;
          table.querySelector(`#key${i}`).innerHTML = event.key;
          table.querySelector(`#keyCode${i}`).innerHTML = event.keyCode;
          table.querySelector(`#locale${i}`).innerHTML = event.locale;
          table.querySelector(`#location${i}`).innerHTML = event.location;
          table.querySelector(`#metaKey${i}`).innerHTML = event.metaKey;
          table.querySelector(`#repeat${i}`).innerHTML = event.repeat;
          table.querySelector(`#shiftKey${i}`).innerHTML = event.shiftKey;
          table.querySelector(`#which${i}`).innerHTML = event.which;
          console.dir(event);
        };

        window.addEventListener('keydown', key);
        window.addEventListener('keypress', key);
        window.addEventListener('keyup', key);
      };

      window.onload = init;
    </script>
  </head>
  <body>
    <main>
      <table>
        <caption></caption>
        <thead>
          <tr>
            <th>altKey</th>
            <th>char</th>
            <th>charCode</th>
            <th>code</th>
            <th>ctrlKey</th>
            <th>isComposing</th>
            <th>key</th>
            <th>keyCode</th>
            <th>locale</th>
            <th>location</th>
            <th>metaKey</th>
            <th>repeat</th>
            <th>shiftKey</th>
            <th>which</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td id=altKey0></td>
            <td id=char0></td>
            <td id=charCode0></td>
            <td id=code0></td>
            <td id=ctrlKey0></td>
            <td id=isComposing0></td>
            <td id=key0></td>
            <td id=keyCode0></td>
            <td id=locale0></td>
            <td id=location0></td>
            <td id=metaKey0></td>
            <td id=repeat0></td>
            <td id=shiftKey0></td>
            <td id=which0></td>
          </tr>
        </tbody>
      </table>
      <table>
        <caption></caption>
        <thead>
          <tr>
            <th>altKey</th>
            <th>char</th>
            <th>charCode</th>
            <th>code</th>
            <th>ctrlKey</th>
            <th>isComposing</th>
            <th>key</th>
            <th>keyCode</th>
            <th>locale</th>
            <th>location</th>
            <th>metaKey</th>
            <th>repeat</th>
            <th>shiftKey</th>
            <th>which</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td id=altKey1></td>
            <td id=char1></td>
            <td id=charCode1></td>
            <td id=code1></td>
            <td id=ctrlKey1></td>
            <td id=isComposing1></td>
            <td id=key1></td>
            <td id=keyCode1></td>
            <td id=locale1></td>
            <td id=location1></td>
            <td id=metaKey1></td>
            <td id=repeat1></td>
            <td id=shiftKey1></td>
            <td id=which1></td>
          </tr>
        </tbody>
      </table>
      <table>
        <caption></caption>
        <thead>
          <tr>
            <th>altKey</th>
            <th>char</th>
            <th>charCode</th>
            <th>code</th>
            <th>ctrlKey</th>
            <th>isComposing</th>
            <th>key</th>
            <th>keyCode</th>
            <th>locale</th>
            <th>location</th>
            <th>metaKey</th>
            <th>repeat</th>
            <th>shiftKey</th>
            <th>which</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td id=altKey2></td>
            <td id=char2></td>
            <td id=charCode2></td>
            <td id=code2></td>
            <td id=ctrlKey2></td>
            <td id=isComposing2></td>
            <td id=key2></td>
            <td id=keyCode2></td>
            <td id=locale2></td>
            <td id=location2></td>
            <td id=metaKey2></td>
            <td id=repeat2></td>
            <td id=shiftKey2></td>
            <td id=which2></td>
          </tr>
        </tbody>
      </table>
    </main>
  </body>
</html>

Here is the same program, but instead of hard coded HTML tables, they are generated using JavaScript. This shows how we can modify the DOM (cf. Document Object Model (DOM)). The result is 75 lines instead of 176:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Key event handler example 1 optimized</title>
    <meta charset=utf-8>
    <style>
      table {
        border-collapse: collapse;
        width:           100%;
        padding:         5px;
      }

      table, th, td {
        border:     1px solid red;
        text-align: center;
      }

      table caption {
        font-weight: bold;
        color:       blue;
      }
    </style>
    <script>
      'use strict';

      const init = () => {
        const keyAttrs = ['altKey', 'char', 'charCode', 'code', 'ctrlKey', 'isComposing',
          'key', 'keyCode', 'locale', 'location', 'metaKey', 'repeat', 'shiftKey',
          'which'];

        const key = event => {
          let table, i;
          if (event.type === 'keydown') i = 0;
          else if (event.type === 'keypress') i = 1;
          else i = 2;
          table = document.getElementsByTagName('table')[i];
          table.querySelector('caption').innerHTML =
            `${event.type} ${new Date().toTimeString()}`;
          for (let j = 0; j < keyAttrs.length; j++)
            table.querySelector(`#${keyAttrs[j]}${i}`).innerHTML = event[keyAttrs[j]];
          console.dir(event);
        };

        let table, caption, thead, tbody, tr, th, td;
        for (let i = 0; i < 3; i++) {
          table = document.createElement('table');
          caption = document.createElement('caption');
          thead = document.createElement('thead');
          tr = document.createElement('tr');
          tbody = document.createElement('tbody');
          table.appendChild(caption);
          table.appendChild(thead);
          thead.appendChild(tr);
          for (let j = 0; j < 14; j++) {
            th = document.createElement('th');
            th.innerHTML = keyAttrs[j];
            tr.appendChild(th);
          }
          table.appendChild(tbody);
          tr = document.createElement('tr');
          tbody.appendChild(tr);
          for (let j = 0; j < 14; j++) {
            td = document.createElement('td');
            td.id = keyAttrs[j] + i;
            tr.appendChild(td);
          }
          document.querySelector('main').appendChild(table);
        }
        window.addEventListener('keydown', key);
        window.addEventListener('keypress', key);
        window.addEventListener('keyup', key);
      };

      window.onload = init;
    </script>
  </head>
  <body>
    <main></main>
  </body>
</html>

To determine the code for a specific key, take a look at the following links:

www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes

developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode

msdn.microsoft.com/en-us/library/windows/desktop/dd375731\%28v=vs.85\%29.aspx

unload and beforeunload

If we want to have some code executed when a user navigates away from our page, we can use the beforeunload (cf. developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload) or the unload (cf. https://developer.mozilla .org/en-US/docs/Web/API/WindowEventHandlers/onunload) events.

Here is a simple example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Unload test</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <script>
      'use strict';

      const beforeunload = e => {
        console.dir(e);
        const req = new XMLHttpRequest();
        req.open('POST', 'beforeunload.php', false);
        req.send();
        const confirmationMessage = 'Are you sure you want to leave this shiny page?';
        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage; //Webkit, Safari, Chrome etc.
      };

      addEventListener('load', () => addEventListener('beforeunload', beforeunload));
    </script>
  </body>
</html>
<?php
  error_log('Before unload');
?>
error

When we include images in our HTML document, which cannot be found by the browser, the browser will display an ugly icon. In Firefox we can solve this issue by setting the alt attribute to the empty string. To solve the issue for all browsers, we can handle the error event, like so (test it in Chrome and Firefox to see the difference):

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Handling the error event</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <img src=dontexist.png alt=''>
      <!-- http://stackoverflow.com/questions/8987428/image-placeholder -->
      <img src=dontexist.png alt='' onerror="this.style.display='none';">
    </main>
  </body>
</html>

4.3.20. Document Object Model (DOM)

The Document Object Model (DOM) is an API defined by the W3C to represent and interact with any HTML or XML document. The DOM is a model of an HTML or XML document that is loaded in a web browser. It represents a document as a tree of nodes, where each node represents a portion of the document, such as an element, a portion of text or a comment. The DOM is one of the most used APIs on the web because it allows code running in a web browser to access and interact with every node in the document. Nodes can be created, moved and changed. Event listeners can be added to nodes. Once a given event occurs all of its event listeners are triggered.

We have already looked at the DOM in several instances using the browser console or Firebug.

Here is what the DOM of a very basic HTML document looks like:

DOM

A basic introduction can be found at www.w3schools.com/js/js_htmldom.asp.

HTMLElement represents any HTML element. Specific elements are children of this object. For instance, a div element is represented via an HTMLDivElement object (cf. developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model).

HTMLDocument defines some specific properties that are often quite handy. document.body is the <body>, document.head the <head> and document.documentElement the root, i.e. <html> element of the document.

Creating a new DOM document from a string
DOMParser can parse XML or HTML source stored in a string into a DOM Document.
Properties and methods of the HTML DOM Element Object

At www.w3schools.com/jsref/dom_obj_all.asp you can find a list of the properties and methods of the HTML DOM Element object with examples, which is very helpful.

Selecting DOM elements

Before we can change a DOM element in JavaScript, we need to select it, i.e. we need to get a pointer to the element. For this purpose, the document object allows us to select an element by id, name, tag, CSS class or selector.

document.getElementById

This method takes a string parameter specifying the ID of the element that we want.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>getElementById examples</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const doSomeWork = () => {
        const myMain = document.getElementById('myMain'); // Store the main element.
        console.dir(myMain); // Display the main element in the console.
        console.dir(document.getElementById('myHeader')); // Display the header.
      };

      // The script should only be executed after the whole document has been loaded, otherwise
      // the HTML elements are not yet available.
      window.addEventListener('load', doSomeWork);
    </script>
  </head>
  <body>
    <main id=myMain>
      <header id=myHeader>Header</header>
    </main>
  </body>
</html>
document.getElementsByName

This method takes a string parameter specifying the name of the element that we want and returns a NodeList object, which we can access using indices, like arrays (cf. developer.mozilla.org/en-US/docs/Web/API/NodeList). Remember that the name attribute is used during form submission to send data to the server. Not every HTML element can have a name attribute.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>getElementsByName examples</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const doSomeWork = () => {
        // Store ALL elements with name myForm in an array.
        const myForms = document.getElementsByName('myForm');
        console.log(`There are ${myForms.length} form elements.<br>`);
        // Display the first (and only) form element in the console.
        console.dir(myForms[0]);
        const myInputs = document.getElementsByName('gender');
        console.log(`There are {myInputs.length} radio input elements.<br>`);
        // Display the first (and only) header element in the console.
        for (let i = 0; i < myInputs.length; i++) console.dir(myInputs[i]);
      };

      window.addEventListener('load', doSomeWork);
    </script>
  </head>
  <body>
    <main> <!--Note that the main element cannot have a name attribute.-->
      <form name=myForm>
        <input type=radio name=gender>male<br>
        <input type=radio name=gender>female
      </form>
    </main>
  </body>
</html>
document.getElementsByTagName

This method works like the previous one, except that the parameter is the HTML tag for which we want to select all elements.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>getElementsByTagName examples</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const doSomeWork = () => {
        // Store ALL main elements in an array.
        const myMains = document.getElementsByTagName('form');
        console.log(`There are ${myMains.length} main elements.<br>`);
        // Store ALL form elements in an array.
        const myForms = document.getElementsByTagName('form');
        console.log(`There are ${myForms.length} form elements.<br>`);
        // Display the first (and only) form element in the console.
        console.dir(myForms[0]);
        const myInputs = document.getElementsByTagName('input');
        console.log(`There are ${myInputs.length} input elements.<br>`);
        // Display the first (and only) header element in the console.
        for (let i = 0; i < myInputs.length; i++) console.dir(myInputs[i]);
      };

      window.addEventListener('load', doSomeWork);
    </script>
  </head>
  <body>
    <main>
      <form>
        <input type=radio name=gender>male<br>
        <input type=radio name=gender>female
      </form>
    </main>
  </body>
</html>
document.getElementsByClassName

This method works like the previous two, except that the parameter is the CSS class for which we want to select all elements.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>getElementsByClassName examples</title>
    <meta charset=utf-8>
    <style>
      .magic {
        background-color: lightgreen;
      }
    </style>
    <script>
      "use strict";

      const doSomeWork = () => {
        // Store ALL elements of the magic class in an array.
        const myMagics = document.getElementsByClassName('magic');
        console.log(`There are ${myMagics.length} magic elements.<br>`);
        for (let i = 0; i < myMagics.length; i++) console.dir(myMagics[i]);
      };

      window.addEventListener('load', doSomeWork);
    </script>
  </head>
  <body>
    <main>
      <form>
        <input type=radio><span class=magic>male</span><br>
        <input type=radio>female
      </form>
    </main>
  </body>
</html>
document.querySelector and document.querySelectorAll

These methods work like the previous three, except that the parameter is the CSS selector for which we want to select all elements (cf. www.w3.org/TR/selectors-api). querySelector returns the first element that matches the given CSS selector, whereas querySelectorAll returns a NodeList object with all the matching elements. These are the most powerful selectors available. We can use the whole gamut of CSS selectors described in www.w3schools.com/cssref/css_selectors.asp.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>querySelectorAll examples</title>
    <meta charset=utf-8>
    <style>
      .magic {
        background-color: lightgreen;
      }
    </style>
    <script>
      "use strict";

      const doSomeWork = () => {
        // Store the FIRST element of the magic class.
        const myFirstMagic = document.querySelectorAll('.magic');
        console.dir(myFirstMagic);
        // Store all magic articles in an array.
        const myMagicArticles = document.querySelectorAll('.magic article');
        console.log(`There are ${myMagicArticles.length} magic articles.<br>`);
        for (let i = 0; i < myMagicArticles.length; i++) console.dir(myMagicArticles[i]);
      };

      window.addEventListener('load', doSomeWork);
    </script>
  </head>
  <body>
    <header><h1>Header</h1></header>
    <main>
      <section>
        <h2>Section 1 header</h2>
        <article>
          <h3>Section 1 article 1 header</h3>
          Section 1 article 1
        </article>
        <article>
          <h3>Section 1 article 2 header</h3>
          Section 1 article 2
        </article>
      </section>
      <section class=magic>
        <h2>Section 2 header</h2>
        <article>
          <h3>Section 2 article 1 header</h3>
          Section 2 article 1
        </article>
        <article>
          <h3>Section 2 article 2 header</h3>
          Section 2 article 2
        </article>
      </section>
    </main>
  </body>
</html>
Direct access via the document object

We can directly access the following HTML objects (and object collections):

  • document.anchors

  • document.body

  • document.documentElement

  • document.embeds

  • document.forms

  • document.head

  • document.images

  • document.links

  • document.scripts

  • document.title

Traversing the DOM

Depending on what we want to do, we can traverse the DOM as a node or as an element tree.

Node trees

In the HTML DOM, everything is a node:

  • The document itself is a document node.

  • All HTML elements are element nodes.

  • All HTML attributes are attribute nodes.

  • Texts inside HTML elements are text nodes.

  • Comments are comment nodes.

The link above provides extensive information on the NodeList object’s methods and properties. In particular, we can determine a node’s type via the nodeType property (cf. www.w3schools.com/jsref/prop_node_nodetype.asp).

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates DOM traversal using nodes</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      // We traverse the DOM tree recursively, using all Node elements.
      const traverseDOM = e => {
        for (let child = e.firstChild; child; child = child.nextSibling) {
          console.log(`Node name: ${child.nodeName} value: ${child.nodeValue}`);
          if (child.nodeType === 1) traverseDOM(child);
        }
      };

      window.addEventListener('load', () => traverseDOM(document.querySelector('main')));
    </script>
  </head>
  <body>
    <main>
      <header><h1>This is the main header</h1></header>
      <section>
        <header><h2>Section 1 header</h2></header>
        <article>
          <header><h3>Section 1 article 1 header</h3></header>
        </article>
        <article>
          <header><h3>Section 1 article 2 header</h3></header>
        </article>
      </section>
      <section>
        <header><h2>Section 2 header</h2></header>
        <article>
          <header><h3>Section 2 article 1 header</h3></header>
        </article>
        <article>
          <header><h3>Section 2 article 2 header</h3></header>
        </article>
      </section>
    </main>
  </body>
</html>
Element trees
The Element interface represents an object within a DOM document.

Text and comment nodes are not treated as objects in this context and are ignored. This API allows us therefore to traverse the DOM element tree, without bothering with text and comments.

The children property of an element is particularly useful, as it contains an array of all of its HTML element children.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates DOM traversal using elements</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      // We traverse the DOM tree recursively, using only HTML elements.
      const traverseDOM = e => {
        for (let i = 0; i < e.children.length; i++) {
          console.log(e.children[i].nodeName);
          traverseDOM(e.children[i]);
        }
      };

      window.addEventListener('load', () => traverseDOM(document.querySelector('main')));
    </script>
  </head>
  <body>
    <main>
      <header><h1>This is the main header</h1></header>
      <section>
        <header><h2>Section 1 header</h2></header>
        <article>
          <header><h3>Section 1 article 1 header</h3></header>
        </article>
        <article>
          <header><h3>Section 1 article 2 header</h3></header>
        </article>
      </section>
      <section>
        <header><h2>Section 2 header</h2></header>
        <article>
          <header><h3>Section 2 article 1 header</h3></header>
        </article>
        <article>
          <header><h3>Section 2 article 2 header</h3></header>
        </article>
      </section>
    </main>
  </body>
</html>
Getting and setting attributes
as element properties

The attributes of HTML elements are available as properties of the corresponding HTMLElement in JavaScript. However, whereas HTML attributes are not case sensitive, JavaScript properties use camel case (cf. en.wikipedia.org/wiki/CamelCase). For instance the usemap attribute of an img element can be accessed via the useMap property. There are two exceptions: given that some attribute names are reserved words in JavaScript, the property name has an html prefix, for instance the for attribute can be accessed via the htmlFor property. The exception to the exception is the class attribute, which can be accessed via the className property.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates access of HTML element attributes using
      element properties</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const toggleImage = () => {
        let image = document.querySelector('img'); // Get the image element.
        console.log(image.src); // Display the complete URL.
        // Extract the image filename from the complete URL.
        let imageName = image.src.substring(image.src.lastIndexOf('/') + 1);
        // Given that the image is in the same directory as this document,
        // we do not need to specify the complete URL, just the image filename.
        if (imageName === 'camaro256x256.png') {
          image.src = 'ferrari256x256.png';
          image.title = 'https://www.iconfinder.com/icons/67532/'
              + 'car_ferrari_red_small_car_sports_car_icon#size=256';
        }
        else {
          image.src = 'camaro256x256.png';
          image.title = 'https://www.iconfinder.com/icons/67528/'
              + 'camaro_car_sports_car_icon#size=256';
        }
      };
    </script>
  </head>
  <body>
    <button onclick='toggleImage();'>Toggle image</button>
    <br>
    <img src=camaro256x256.png alt=Car
       title=https://www.iconfinder.com/icons/67528/camaro_car_sports_car_icon#size=256>
  </body>
</html>

Here is an example of how to scroll to the end of an element’s content using JS. Note that the height of the element needs to be set to something smaller than the height taken by the content, otherwise this won’t work.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Scroll example 1</title>
    <meta charset=utf-8>
    <style>
      main {
        position:         fixed;
        top:              0;
        bottom:           0;
        left:             0;
        right:            0;
        background-color: greenyellow;
        overflow:         auto;
      }
    </style>
    <script>
      'use strict';

      const generateRandomText = (length, lineWidth) => {
        let string = "";
        const charset = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        for (let i = 1; i <= length; i++) {
          string += charset.charAt(Math.floor(Math.random() * charset.length));
          if (i % lineWidth === 0) string += '<br>';
        }
        return string;
      };

      const init = () => {
        const main = document.querySelector('main');
        main.innerHTML = generateRandomText(10000, 60);
        main.scrollTop = main.scrollHeight;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main></main>
  </body>
</html>
using getAttribute and setAttribute

Instead of using element properties, we can use two HTMLElement methods, one to get and one to set the value of an attribute. Note that attribute values are treated as strings, i.e. getAttribute always returns and setAttribute takes a string. The attribute names used are the standard HTML ones, not the camel case versions used with element properties. These methods can also be used with non standard attributes as well as attributes of XML documents.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates access of HTML element attributes using methods</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const toggleImage = () => {
        const image = document.querySelector('img'); // Get the image element.
        console.log(image.src); // Display the complete URL.
        // Extract the image filename from the complete URL.
        const imageName =
            image.getAttribute('src').substring(image.getAttribute('src').lastIndexOf('/')
                + 1);
        // Given that the image is in the same directory as this document,
        // we do not need to specify the complete URL, just the image filename.
        if (imageName === 'camaro256x256.png') {
          image.setAttribute('src', 'ferrari256x256.png');
          image.setAttribute('title', 'https://www.iconfinder.com/icons/67532/'
              + 'car_ferrari_red_small_car_sports_car_icon#size=256');
        }
        else {
          image.setAttribute('src', 'camaro256x256.png');
          image.setAttribute('title', 'https://www.iconfinder.com/icons/67528/'
              + 'camaro_car_sports_car_icon#size=256');
        }
      };

      const toggleTitle = () => {
        const image = document.querySelector('img'); // Get the image element.
        const imageName =
            image.getAttribute('src').substring(image.getAttribute('src').lastIndexOf('/')
                + 1);
        // If the image has a title attribute, we remove it.
        if (image.hasAttribute('title')) image.removeAttribute('title');
        // Otherwise, we need to determine the correct title to add.
        else if (imageName === 'camaro256x256.png')
          image.setAttribute('title', 'https://www.iconfinder.com/icons/67528/'
              + 'camaro_car_sports_car_icon#size=256');
        else
          image.setAttribute('title', 'https://www.iconfinder.com/icons/67532/'
              + 'car_ferrari_red_small_car_sports_car_icon#size=256');
      };
    </script>
  </head>
  <body>
    <button onclick='toggleImage();'>Toggle image</button>
    <button onclick='toggleTitle();'>Toggle image title attribute</button>
    <br>
    <img src=camaro256x256.png alt=Car
       title=https://www.iconfinder.com/icons/67528/camaro_car_sports_car_icon#size=256>
  </body>
</html>
using dataset attributes

On occasion we might want to add our own attributes to HTML elements in order to store specific information. In order to be HTML5-compliant, we need to prefix our attribute names with data-. Our attribute names may not contain capital letters A to Z or semicolons and may not start with xml (cf. developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-data-*). A nice example can be found at www.w3schools.com/tags/tryit.asp?filename=tryhtml5_global_data. We can access dataset attributes using methods or using the dataset property. In the latter case, attribute names are mapped to camel case property names. See also developer.mozilla.org/en-US/docs/Web/API/HTMLElement.dataset.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates access of HTML element dataset attributes</title>
    <!-- Based on http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_global_data-->
    <meta charset=utf-8>
    <style>
      li {
        width:  65px;
        cursor: pointer;
      }
    </style>
    <script>
      'use strict';

      const showDetails1 = animal => {
        const animalType = animal.getAttribute("data-animal-type");
        alert(`The ${animal.innerHTML} is a ${animalType}.`);
      };

      const showDetails2 = animal => {
        const animalType = animal.dataset.animalType;
        alert(`The ${animal.innerHTML} is a ${animalType}.`);
      };

    </script>
  </head>
  <body>
    <h1>Species</h1>

    <p>Click on a species to see what type it is:</p>
    <ul>
      <li onclick="showDetails1(this);" id=owl data-animal-type=bird>Owl</li>
      <li onclick="showDetails2(this);" id=salmon data-animal-type=fish>Salmon</li>
      <li onclick="showDetails1(this);" id=tarantula data-animal-type=spider>Tarantula</li>
    </ul>
  </body>
</html>
as Attr objects

From p. 378 of the 6th edition of "JavaScript The Definitive Guide":

The Node type defines an attributes property. This property is null for any nodes that are not Element objects. For Element objects, attributes is a read-only array-like object that represents all the attributes of the element.

The Attr object has name and value properties representing the name and value of the attribute.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates access of HTML element dataset attributes</title>
    <!-- Based on http://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_global_data-->
    <meta charset=utf-8>
    <script>
      'use strict';

      const doWork = () => {
        const children = document.getElementsByTagName('ul')[0].childNodes;
        for (let i = 0; i < children.length; i++)
          if (children[i].attributes)
            for (let j = 0; j < children[i].attributes.length; j++)
              console.log(`Name: ${children[i].attributes[j].name} value: `
                + children[i].attributes[j].value);
      };

      window.addEventListener('load', doWork);
    </script>
  </head>
  <body>
    <h1>Species</h1>
    <ul>
      <li id=owl data-animal-type=bird>Owl</li>
      <li id=salmon data-animal-type=fish>Salmon</li>
      <li id=tarantula data-animal-type=spider>Tarantula</li>
    </ul>
  </body>
</html>
Manipulating an element’s classes

element.classList is a highly useful property to manipulate an element’s classes. See developer.mozilla.org/en-US/docs/Web/API/Element/classList for the details.

Element content

We can view the content of an element as HTML or plain text.

as HTML

The innerHTML property of an Element object contains the content as an HTML string. outerHTML contains the content, including the opening and closing tag of the element. With insertAdjacentHTML we can insert HTML before or after the beginning or before or after the end of a given element (cf. developer.mozilla.org/en-US/docs/Web/API/Element.insertAdjacentHTML).

insertAdjacentHTML() parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on and thus it does not corrupt the existing elements inside the element. This, and avoiding the extra step of serialization make it much faster than direct innerHTML manipulation.
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Element content as HTML</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const toggleInnerHTML = () => {
        // First we need to know whether the outer element is a <main> or a <div>.
        const el = document.querySelector('main') ? 'main' : 'div';
        if (document.querySelector(el).innerHTML !== '')
          document.querySelector(el).innerHTML = '';
        else document.querySelector(el).innerHTML =
            '<section><h1>Content header</h1>Content</section>';
      };

      const toggleOuterHTML = () => {
        let el, el_replace;
        if (document.querySelector('main')) {
          el = 'main'; // We have a <main>
          el_replace = 'div'; // and want to replace it with a <div>.
        }
        else {
          el = 'div'; // We have a <div>
          el_replace = 'main'; // and want to replace it with a <main>
        }
        document.querySelector(el).outerHTML = `<${el_replace}>` +
            `<section><h1>Content header</h1>Content</section></${el_replace}>`;
      };

      const addBeforeBegin = () => {
        document.querySelector('header').insertAdjacentHTML('beforebegin',
            '<h1>Header</h1>');
      };

      const addAfterBegin = () => {
        document.querySelector('header').insertAdjacentHTML('afterbegin',
            '<h1>Header</h1>');
      };

      const addBeforeEnd = () => {
        document.querySelector('header').insertAdjacentHTML('beforeend',
            '<h1>Header</h1>');
      };

      const addAfterEnd = () => {
        document.querySelector('header').insertAdjacentHTML('afterend',
            '<h1>Header</h1>');
      };

      const init = () => { // Register button event handlers.
        const handlers = [toggleInnerHTML, toggleOuterHTML, addBeforeBegin,
          addAfterBegin, addBeforeEnd, addAfterEnd];
        for (let i = 0; i < handlers.length; i++)
          document.getElementsByTagName('button')[i].addEventListener('click',
              handlers[i]);
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <button>Toggle innerHTML</button>
    <button>Toggle outerHTML</button>
    <button>Header add before begin</button>
    <button>Header add after begin</button>
    <button>Header add before end</button>
    <button>Header add after end</button>
    <header>Header content</header>
    <main></main>
  </body>
</html>
as plain text

The textContent property of an Element object contains the content as plain text.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Element content as plain text</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const changeText = () => document.querySelector('main').textContent =
        prompt('Please enter the new text');

      const init = () => // Register button event handler.
        document.getElementsByTagName('button')[0].addEventListener('click',
          changeText);

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <button>Change text</button>
    <main>Text</main>
  </body>
</html>
Managing nodes

We create a new element with createElement and a new text node with createTextNode. There are other creation methods available, as detailed in developer.mozilla.org/en-US/docs/Web/API/Document.

With appendChild we add a node as the last child of the given node. With insertBefore we insert the new node (first parameter) before a given node (second parameter). If the second parameter is null, the method behaves like appendChild.

removeChild is invoked on the parent node and given the child node that is to be removed as parameter. replaceChild is also invoked on the parent node. It takes the new node as first and the node to be replaced as second parameter.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Node management example</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const init = () => {
        const button = document.createElement('button');
        button.textContent = 'Click to kill me!';
        // With a normal function, we have 3 options:
        button.addEventListener('click', function(e) {
          // this.parentElement.removeChild(this); // Does not work with arrow function.
          e.target.parentElement.removeChild(e.target);
          // This should be avoided as button may not exist in the event handler.
          // button.parentElement.removeChild(button);
        });
        document.body.appendChild(button);
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
  </body>
</html>
Document fragments

DocumentFragments are DOM Nodes. They are never part of the main DOM tree. The usual use case is to create the document fragment, append elements to the document fragment and then append the document fragment to the DOM tree. In the DOM tree, the document fragment is replaced by all its children.

Since the document fragment is in memory and not part of the main DOM tree, appending children to it does not cause page reflow (computation of element’s position and geometry). Consequently, using document fragments often results in better performance.

Manipulating CSS

www.w3.org/wiki/Dynamic_style_-_manipulating_CSS_with_JavaScript

www.w3schools.com/js/js_htmldom_css.asp

www.w3schools.com/jsref/dom_obj_style.asp

developer.mozilla.org/en-US/docs/Web/API/Window.getComputedStyle

developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration

We can easily manipulate CSS via JavaScript, which opens up some interesting applications, for instance moving HTML objects or changing colors dynamically.

In order to access internal or external stylesheets, we can use document.styleSheets, which gives us an array with all stylesheets used by the document.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>DOM CSS manipulation example 1</title>
    <meta charset=UTF-8>
    <style>
      body {
        background-color: black;
      }
    </style>
    <script>
      'use strict';

      const switchToWhite = () => {
        document.styleSheets[0].cssRules[0].style.backgroundColor = "white";
        /* Or we can set the inline style of the body:
        document.body.style.backgroundColor = "white";
        */
      };
    </script>
  </head>
  <body>
    <button onclick=switchToWhite();>Set background color to white</button>
  </body>
</html>

In order to access the inline styling of a particular element, we can use the style attribute of that element. For instance, if we want to change the color of the second p element in our document, we could write:

document.querySelectorAll('p')[1].style.color = "#F0F";
Gorilla1

Let’s look at a more dynamic example, where we move a gorilla:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>DOM CSS manipulation example 2</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let gorilla;

      const init = () =>  {
        gorilla = document.querySelector('img');
        gorilla.style.cssText = "position: relative; top: 0; left: 0";
      };

      const moveRight = () => {
        const x = parseInt(gorilla.style.left);
        gorilla.style.left = x + 10 + 'px';
      };

      const moveLeft = () => {
        const x = parseInt(gorilla.style.left);
        gorilla.style.left = x - 10 + 'px';
      };

      const moveUp = () => {
        const y = parseInt(gorilla.style.top);
        gorilla.style.top = y - 10 + 'px';
      };

      const moveDown = () => {
        const y = parseInt(gorilla.style.top);
        gorilla.style.top = y + 10 + 'px';
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <button onclick=moveLeft();><</button>
    <button onclick=moveRight();>></button>
    <button onclick=moveUp();>^</button>
    <button onclick=moveDown();>v</button>
    <img src=gorilla236x256.png>
  </body>
</html>
getComputedStyle
The Window.getComputedStyle() method gives the values of all the CSS properties of an element after applying the active stylesheets and resolving any basic computation those values may contain.

This method is particularly useful if we want to query an element’s CSS value that we have not set programmatically.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>DOM CSS manipulation example 3</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      addEventListener('load', () =>
        alert(getComputedStyle(document.querySelector('body')).getPropertyValue('color')));
    </script>
  </head>
  <body>
  </body>
</html>
Inserting a new style sheet
To create a new stylesheet, insert a <style> or <link> element into the document.
Inserting JS dynamically
<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>Insert JS</title>
  </head>
  <body>
    <script>
      const init = () => {
        const script = document.createElement('script');
        script.innerHTML = 'alert("Test");';
        document.body.appendChild(script);
      };

      addEventListener('load', init);
    </script>
  </body>
</html>
Observing DOM mutations
'use strict';

const init = () => {
  // select the target node
  const target = document.querySelector('body');

  // create an observer instance
  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) console.dir(mutation);
  });

  // configuration of the observer:
  const config = {
    attributes: true, childList: true, characterData: true, subtree: true,
    attributeOldValue: true, characterDataOldValue: true
  };

  // pass in the target node, as well as the observer options
  observer.observe(target, config);

  document.querySelector('button').addEventListener('click', () => {
    observer.disconnect();
  });
};

window.addEventListener('load', init);

4.3.21. Browser Object Model (BOM)

window is the global object put at our disposal by the browser. It is of central importance and described in detail in developer.mozilla.org/en-US/docs/Web/API/Window. In Basic input and output we have already met some useful window methods.

Timers

We can measure time, accurate to one microsecond, using the Performance.now method (cf. developer.mozilla.org/en-US/docs/Web/API/Performance.now()).

We can choose between three timer methods.

setTimeout

setTimeout runs a given function (first parameter) after a specified number of milliseconds (second parameter) and returns the ID of the timeout, which can be used with clearTimeout if we change our mind and do not want the timer to execute. setTimeout is ideal if we just want to execute a function once after a given delay, as in the following example. Note that we need to use an anonymous function if we want to pass parameters to the function that is to be called by the timer:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>setTimeout example 1</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <script>
        'use strict';

        const displayAlert = msg => alert(msg);

        setTimeout(() => displayAlert('Test'), 2000);
      </script>
    </main>
  </body>
</html>

We can, however, also repeat the function call by invoking setTimeout within the function that is to be executed repeatedly:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>setTimeout example 2</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <script>
        'use strict';

        const countDown = x => {
          console.log(`Current counter: ${x}`);
          if (x > 0) setTimeout(() => countDown(x - 1), 1000);
        };

        countDown(10);
      </script>
    </main>
  </body>
</html>
setInterval

setInterval is identical to setTimeout except that the given function gets invoked repeatedly until clearInterval is called with the timer ID.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>setInterval example</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      let counter = 10, timer; // Initialise.

      const countDown = () => {
        console.log(`Current counter: ${counter--}`); // Display and decrement.
        if (counter < 0) clearInterval(timer); // Stop timer.
      };

      timer = setInterval(countDown, 1000); // Start timer.
    </script>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>
RadialGradiantAnim1

This is an example of how we can create a background color animation using DOM CSS manipulation inside a timer function.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Radial gradient animation</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      /*
       We create a radial gradient with an origin and a target.
       We set random origin colors. Target color are created via 255 - origin color.
       The red, green and blue components of the origin are animated upwards or downwards.
       The RGB components of the target are animated in opposite directions.
       */
      const RGBColorsOrigin = [Math.floor(Math.random() * 256), Math.floor(Math.random() *
        256),
          Math.floor(Math.random() * 256)],
        RGBColorsTarget = [255 - RGBColorsOrigin[0], 255 - RGBColorsOrigin[1], 255 -
        RGBColorsOrigin[2]];
      const RGBDirections = [Math.random() >= 0.5 ? 1 : -1, Math.random() >= 0.5 ? 1 : -1,
        Math.random() >= 0.5 ? 1 : -1], RGBDirectionsOrigin = [-RGBDirections[0],
        -RGBDirections[1], -RGBDirections[2]];

      const changeBackground = () => {
        for (let i = 0; i <= 2; i++) {
          if (RGBColorsOrigin[i] >= 255 && RGBDirectionsOrigin[i] === 1 || RGBColorsOrigin[i]
            <= 0 && RGBDirectionsOrigin[i] === -1)
            RGBDirectionsOrigin[i] = -RGBDirectionsOrigin[i];
          RGBColorsOrigin[i] += RGBDirectionsOrigin[i] * Math.floor(Math.random() * 2 + 0.5);
          if (RGBColorsTarget[i] >= 254 && RGBDirections[i] === 1 || RGBColorsTarget[i] <= 1 &&
            RGBDirections[i] === -1) RGBDirections[i] = -RGBDirections[i];
          RGBColorsTarget[i] += RGBDirections[i] * Math.floor(Math.random() * 2 + 0.5);
        }
        const originColor = `rgb(${RGBColorsOrigin[0]}, ${RGBColorsOrigin[1]}, ` +
            `${RGBColorsOrigin[2]})`,
          targetColor = `, rgb(${RGBColorsTarget[0]}, ${RGBColorsTarget[1]}, ` +
            `${RGBColorsTarget[2]})) no-repeat fixed`;
        document.querySelector('body').style.background =
          `radial-gradient(${originColor}${targetColor}`;
      };

      addEventListener('load', () => setInterval(changeBackground, 100));
    </script>
  </head>
  <body>

  </body>
</html>
requestAnimationFrame
You should call this method whenever you’re ready to update your animation onscreen. This will request that your animation function be called before the browser performs the next repaint. The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation. The callback rate may be reduced to a lower rate when running in background tabs.

developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame

www.w3.org/TR/animation-timing/\#requestAnimationFrame

requestAnimationFrame1
<!DOCTYPE html>
<html lang=en>
  <head>
    <!-- https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame-->
    <title>requestAnimationFrame example</title>
    <meta charset=utf-8>
    <style>
      div {
        position:   absolute;
        left:       10px;
        padding:    50px;
        background: crimson;
        color:      white
      }
    </style>
  </head>
  <body>
      <script>
        "use strict";

        let requestId = 0, animationStartTime;

        const animate = time => {
          document.getElementById("animated").style.left =
              `${(time - animationStartTime) % 2000 / 4}px`;
          requestId = window.requestAnimationFrame(animate);
        };

        const start = () => {
          if (!requestId) {
            animationStartTime = window.performance.now();
            requestId = window.requestAnimationFrame(animate);
          }
        };

        const stop = () => {
          if (requestId)
            window.cancelAnimationFrame(requestId);
          requestId = 0;
        };
      </script>
      <button onclick="start()">Click me to start!</button>
      <button onclick="stop()">Click me to stop!</button>
      <div id="animated">Hello there.</div>
  </body>
</html>
The location object

This object allows us to redirect the browser to another page. See www.w3schools.com/jsref/obj_location.asp and developer.mozilla.org/en-US/docs/Web/API/Window.location.

4.3.22. Strict mode

The strict mode, according to ECMA, "provides enhanced error checking and program security". To enter this mode just put 'use strict' or "use strict"; at the top of any script. See developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode and www.ecma-international.org/ecma-262/#sec-strict-mode-of-ecmascript.

To find out whether we are currently in strict mode, we can use this one liner from page 167 of the 6th edition of "JavaScript The Definitive Guide":

const strict = (function() { return !this; } ());

This works because in non-strict mode, the invocation context of a function is the global object, therefore !this will be false. In strict mode, the invocation context is undefined, thus !this will be true.

In Firefox, you can set strict mode to default by changing javascript.options.strict to true in about:config.

4.3.24. Modules

The import statement is used to import functions, objects, or primitives which are defined in and exported by an external module, script, or the like.

You might have to change a flag in your browser in order to enable this feature (cf. caniuse.com/#search=export and jakearchibald.com/2017/es-modules-in-browsers).

See exploringjs.com/es6/ch_modules.html for a detailed explanation of JS modules.

4.3.27. AJAX

Asynchronous JavaScript And XML (AJAX) is a term used to describe the programming practice of using HTML, CSS, JavaScript, the Document Object Model (DOM), and the XMLHttpRequest object together to build complex Web pages that update their content without reloading the entire Web page. This makes the application faster and more responsive to user actions.

Study the gentle introduction to AJAX at www.w3schools.com/xml/ajax_intro.asp.

XMLHttpRequest

The key object that enables AJAX is XMLHttpRequest (cf. developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), which allows us to take control of HTTP communication with a server, whereas normally this is handled in the background by the browser.

The required steps are the following:

  1. Create an XMLHttpRequest object.

  2. Prepare the request using open and register the event handler to handle the response.

  3. Send the request to the server using send.

  4. When the server sends a response, an event gets triggered and our event handler reads the data and takes the required action, for instance update some parts of our web without a page reload.

It’s as easy as this:

AJAX1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Very basic AJAX example</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const displayResponse = e =>
        document.querySelector('main').innerHTML = e.target.response;

      const AJAX = () => {
        const req = new XMLHttpRequest(); // Create new request.
        req.open('POST', 'AJAX1.php'); // Specify the method and script to be used.
        // Add the event listener for successful request completion.
        req.addEventListener('load', displayResponse);
        req.send(); // Send the request to the server.
      };
    </script>
  </head>
  <body>
    <main>
      <button onclick=AJAX();>Click me and something wonderful will happen without page
        reload!
      </button>
    </main>
  </body>
</html>
<?php
  echo '<h1>Hello world!</h1>';
?>
open(method, url, async, user, password)

For our purposes the relevant methods are POST and GET(cf. Forms). The second parameter represents the location of the server script that will receive the request. The third parameter is optional and true by default, which specifies an asynchronous request, i.e. our script will not block to wait for the response. Instead, when the server sends a response, an event will be triggered to which our script can react. If this parameter is set to false, our script will block and wait for the server response. Parameters four and five are only needed to access password protected resources.

send(data)

send sends the request to the server. Any data that we want to send is given as argument. The following types can be used: ArrayBuffer, ArrayBufferView, Blob, Document, DOMString and FormData. To send binary data we should use Blob (cf. developer.mozilla.org/en-US/docs/Web/API/Blob) or ArrayBufferView (cf. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) objects.

The FormData object (cf. developer.mozilla.org/en-US/docs/Web/API/FormData) allows us to programmatically send form data to the server just like the user can when sending an HTML form. To use it we simply create a new FormData object and then add name/value pairs to it using the append(name, value) method. The name parameter is a string whereas the value parameter can be a string or a Blob or a File. In the latter two cases, a third optional parameter specifies the filename to be reported to the server. For Blob objects the default filename is "blob". We can also use FormData to submit an HTML form asynchronously (cf. developer.mozilla.org/en-US/docs/Web/Guide/Using_FormData_Objects).

abort

This method aborts the request if it has already been sent.

Events

The following events are relevant for the XMLHttpRequest object:

loadstart

triggered on request start

progress

triggered periodically during request execution

abort

triggered on request abortion

error

triggered if a request error occurs

load

triggered on successful request completion

timeout

triggered on request timeout

loadend

triggered after load, abort or error have been dispatched

We need to register a handler that takes care of the load event. As we have seen in Events, all event handlers automatically receive an Event object when invoked. The target attribute of the event corresponds to our XMLHttpRequest object, given that we registered the event handler on this object. The status attribute contains the HTTP response code (cf. developer.mozilla.org/en-US/docs/Web/HTTP/Response_codes) and statusText the status in text form. If this code has the value 200, the request has succeeded and we can use the response data. A specific response header can be queried using getResponseHeader(header) or we can retrieve a string with all response headers using getAllResponseHeaders. Note that cookie headers are automatically filtered out. The XMLHttpRequest object has three properties to contain the response data. response contains the response in the format specified by the responseType property. responseText has the response in text format and responseXML as a Document object in parsed XML format, which can then be traversed as described in Traversing the DOM, if applicable.

Let’s look at a couple more examples:

AJAX2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates the programmatic creation and submission of a form without
      page reload</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      // The event handler reads the data from the server and displays it.
      const displayData = e => {
        console.dir(e);
        const p = document.createElement('p'); // Create new paragraph element
        // and set its content to server response data.
        p.innerHTML = e.target.response + '<br>Response headers:';
        p.innerHTML += '<pre>' + e.target.getAllResponseHeaders() + '</pre>';
        // Append the paragraph to the main element.
        document.querySelector('main').appendChild(p);
      };

      // This event handler gets triggered when the HTML document has finished loading.
      const init = () => {
        // Here we create a form, fill it with data and send it to the server.
        let data = new FormData(), req = new XMLHttpRequest();
        // Fill in the form.
        data.append('first_name', 'Mickey');
        data.append('last_name', 'Mouse');
        // Set the event listener to be triggered upon successful request completion.
        req.addEventListener('load', displayData);
        // Open the HTTP connection to the server script using POST method.
        req.open('POST', 'AJAX2.php');
        req.send(data); // Send the form to the server.

        // When the user clicks on submit, we submit the form asynchronously.
        document.forms[0].addEventListener('submit', e => {
          e.preventDefault(); // Prevent default form submission
          // Create a new form and a new XMLHttpRequest.
          data = new FormData(document.forms[0]), req = new XMLHttpRequest();
          req.addEventListener('load', displayData);
          // Open the HTTP connection to the server script using POST method.
          req.open('POST', 'AJAX2.php');
          req.send(data); // Send the form to the server.
        });
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <form method=post>
        <label>First name<input name=first_name required></label><br>
        <label>Last name<input name=last_name required></label><br>
        <button>Log in</button>
      </form>
    </main>
  </body>
</html>
<?php
  if (isset($_POST['first_name']) && isset($_POST['last_name']))
    echo 'Hello ' . $_POST['first_name'] . ' ' . $_POST['last_name'];
?>
AJAX3
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Illustrates reading comma separated files and displaying them</title>
    <meta charset=utf-8>
    <style>
      select {
        position: fixed;
        top:      0;
        left:     0;
      }

      table {
        border-collapse: collapse;
        position:        relative;
        left:            100px;
      }

      th, td {
        border:     1px solid red;
        padding:    5px;
        text-align: left;
      }

      aside {
        position:      absolute;
        width:         30px;
        height:        30px;
        background:    repeating-linear-gradient(-45deg, red, red 5px, white 5px, white 10px);
        border-radius: 15px;
        animation:     asideAnimation 5s infinite alternate;
      }

      @keyframes asideAnimation {
        0% {
          left: 500px;
          top:  0;
        }

        50% {
          left: 250px;
          top:  300px;
        }

        100% {
          left: 0px;
          top:  100px;
        }
      }
    </style>
    <script>
      "use strict";

      const displayData = () => {
        const index = document.querySelector('select').selectedIndex;
        const req = new XMLHttpRequest();
        req.open('POST', `testdata${index + 1}.csv`);
        req.addEventListener('load', e => {
          const oldTable = document.querySelector('table');
          const newTable = document.createElement('table');
          if (oldTable) document.querySelector('main').replaceChild(newTable, oldTable);
          else document.querySelector('main').appendChild(newTable);
          const lines = e.target.responseText.split('\n'); // Explode rows.
          let line, tableRow, cellTag, cell;
          for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
            line = lines[lineIdx].split('|'); // Explode data fields.
            tableRow = document.createElement('tr');
            if (lineIdx === 0) cellTag = 'th'; // head cell
            else cellTag = 'td'; // body cell
            for (let cellIdx = 0; cellIdx < line.length; cellIdx++) {
              cell = document.createElement(cellTag);
              cell.textContent = line[cellIdx];
              tableRow.appendChild(cell);
            }
            newTable.appendChild(tableRow);
          }
          console.log(`Response type: ${e.target.responseType}`);
          console.log('e.target.response:');
          console.log(e.target.response);
          console.log('e.target.responseText:');
          console.log(e.target.responseText);
          console.log('e.target.responseXML:');
          console.log(e.target.responseXML);
        });
        req.send();
      };

      const init = () => {
        document.querySelector('select').addEventListener('change', displayData);
        displayData(); // Make sure the first file gets displayed at startup.
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <select>
        <option>Data set 1</option>
        <option>Data set 2</option>
        <option>Data set 3</option>
      </select>
      <aside></aside>
    </main>
  </body>
</html>

We can easily use AJAX to upload files to a server. If we want to monitor the upload progress, all we need to do is assign an event handler to the upload property of our XMLHttpRequest. This handler will automatically receive a ProgressEvent (cf. developer.mozilla.org/en-US/docs/Web/API/ProgressEvent) object as argument. Using the three properties lengthComputable, loaded and total we can monitor the upload progress.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>File upload to server</title>
    <meta charset=utf-8>
    <style>
      section {
        width: 500px;
        resize: both;
        overflow: auto;
        border: 2px groove darkorange;
        text-align: center;
      }
    </style>
    <script>
      // Adapted from the 2nd edition of "HTML5 for Masterminds" p. 396-398.
      'use strict';

      let dataBox;

      const init = () => {
        dataBox = document.querySelector('section');
        dataBox.addEventListener('dragenter', e => e.preventDefault());
        dataBox.addEventListener('dragover', e => e.preventDefault());
        dataBox.addEventListener('drop', dropped);
      };

      const dropped = e => {
        e.preventDefault();
        const files = e.dataTransfer.files;
        if (files.length) {
          let list = '';
          for (let f = 0; f < files.length; f++) {
            list += `<div>File: ${files[f].name}`;
            list += '<br><span><progress value=0 max=100>0%</progress></span>';
            list += '</div>';
          }
          dataBox.innerHTML = list;
          let count = 0;
          const upload = () => {
            const myfile = files[count];
            const data = new FormData();
            data.append('file', myfile);
            const url = 'AJAX4.php';
            const request = new XMLHttpRequest();
            const xmlupload = request.upload;
            xmlupload.addEventListener('progress', e => {
              if (e.lengthComputable) {
                let child = count + 1;
                const per = parseInt(e.loaded / e.total * 100);
                const progressBar =
                  dataBox.querySelector(`div:nth-child(${child}) > span > progress`);
                progressBar.value = per;
                progressBar.innerHTML = `${per}%`;
              }
            });
            request.addEventListener('load', () => {
              const child = count + 1;
              const elem = dataBox.querySelector(`div:nth-child(${child}) > span`);
              elem.innerHTML = 'done!';
              count++;
              if (count < files.length) upload();
            });
            request.open('POST', url);
            request.send(data);
          };
          upload();
        }
      };

      window.addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <section>
        <p>Drop files here</p>
      </section>
    </main>
  </body>
</html>
<?PHP
  // Only allow authenticated users to upload files to your server!
  //move_uploaded_file($_FILES['file']['tmp_name'], 'upload/'.$_FILES['file']['name']);
?>
setRequestHeader(header, value)

In some cases we need to specify specific HTTP headers to give the server additional information with regards to the data we want to send and/or receive. setRequestHeader sets the value of the HTTP request header. If used, it must be called after open but before send. If this method is called several times with the same header, the values are merged into one single request header. The official header list can be found at www.iana.org/assignments/message-headers/message-headers.xml#perm-headers and the official value list for the Content-Encoding header, which is the most often used one for our purposes, can be found at www.iana.org/assignments/media-types/media-types.xhtml.

Cross-origin requests
The same-origin policy restricts how a document or script loaded from one origin can interact with a resource from another origin.

Cross-Origin Resource Sharing (CORS) is one way to get around these restrictions. The details can be found at developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS.

In its simplest form, to give everyone access, we can just add the following at the top of our PHP script:

header('Access-Control-Allow-Origin: *');

Another possibility is to use JSONP (JSON with padding) as explained in en.wikipedia.org/wiki/JSONP. For a practical application example, study WMOTU Invaders.

Fetch

The Fetch API provides a JavaScript interface for accessing and manipulating parts of the HTTP pipeline, such as requests and responses. It also provides a global fetch() method that provides an easy, logical way to fetch resources asynchronously across the network.

This kind of functionality was previously achieved using XMLHttpRequest. Fetch provides a better alternative that can be easily used by other technologies such as Service Workers. Fetch also provides a single logical place to define other HTTP-related concepts such as CORS and extensions to HTTP.

<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>Fetch demo</title>
    <script>
      'use strict';

      const init = () => {
        fetch('../camaro256x256.png').then(response => {
          if (response.ok)
            response.blob().then(blob =>
              document.querySelector('img').src = URL.createObjectURL(blob));
          else console.log('Network response was not ok.');
        }).catch(error =>
          console.log(`There has been a problem with your fetch operation: ${error.message}`));

        // Old fashioned alternative
        const req = new XMLHttpRequest();
        req.open('GET', '../ferrari256x256.png');
        // cf. http://stackoverflow.com/questions/20035615/using-raw-image-data-from-ajax-request-for-data-uri
        req.responseType = 'arraybuffer';
        req.addEventListener('load', e => {
          const blob = new Blob([e.target.response]);
          document.querySelectorAll('img')[1].src = URL.createObjectURL(blob);
        });
        req.send();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <img>
    <img>
  </body>
</html>

Study the following links for a detailed demonstration and explanation of the differences between fetch and XMLHttpRequest:

jakearchibald.com/2015/thats-so-fetch

developers.google.com/web/updates/2015/03/introduction-to-fetch

4.3.28. JSON

JavaScript Object Notation (JSON) is a data serialization format often used to exchange data, including complex objects, between server and client. Objects are converted into a JSON string, which is sent to/from the server from/to the client.

JSON is based on a subset of JavaScript (cf. json.org, www.w3schools.com/json/default.asp and en.wikipedia.org/wiki/JSON). Data consists of name/value pairs separated by commas and embedded within {}. [] are used for arrays.

Key names and strings need to be enclosed in double quotes.

Use jsonlint.com to verify that a given string is valid JSON.

If you use a version of Firefox that is older than version 53, you should turn the JSON viewer on as described in www.ghacks.net/2017/01/12/firefox-53-json-viewer-on-by-default.

In JavaScript, we use JSON.stringify to encode and JSON.parse to decode a JSON string.

You can play around with JSON at www.tutorialspoint.com/online_json_editor.htm.

Example:

const myObj = {firstName: "Donald", lastName: "Duck"};
// Convert JS object to JSON string: '{"firstName":"Donald","lastName":"Duck"}'
console.log(JSON.stringify(myObj));
// -> Convert string back to JS object: {firstName: "Donald", lastName: "Duck"}
JSON.parse(JSON.stringify(myObj));

In PHP, we should first tell the browser to expect to receive JSON by sending the header application/json. We use json_encode to encode a PHP object into a JSON string and json_decode to decode a JSON string into a PHP object. Note that the latter can be given a second parameter in order to have returned objects converted into associative arrays.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Simple JSON data retrieval via AJAX and PHP</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      let dataJSON;

      const displayData = data => {
        const output = document.querySelector('p');
        console.dir(data);
        const keys = Object.keys(data); // Get the keys of the object.
        for (let i = 0; i < keys.length; i++) { // Display the key/value pairs.
          output.innerHTML += `${keys[i]}: ${data[keys[i]]}<br>`;
        }
      };

      const init = () => {
        // Old approach
        const req = new XMLHttpRequest();
        req.open('POST', 'JSON1.php');
        req.addEventListener('load', e => {
          displayData(JSON.parse(e.target.response));
        });
        req.send();

        // New approach
        fetch('JSON1.php', {
          headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
          },
          method: "POST",
        }).then(response => response.json()).then(data => {
          displayData(data);
        }).catch(error =>
          console.log(`There has been a problem with the fetch operation: ${error.message}`));
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main><p></p></main>
  </body>
</html>
<?php
  header("Content-Type: application/json; charset=UTF-8");
  $arr = array("INFOR" => 53, "MATHE" => 45);
  echo json_encode($arr); // Send the array as a JSON string to the client browser.
?>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Sending JavaScript array and object to PHP and back</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const displayData = data => {
        const output = document.querySelector('p');
        const keys = Object.keys(data); // Get the keys of the object.
        for (let i = 0; i < keys.length; i++) { // Display the key/value pairs.
          output.innerHTML += `${keys[i]}: ${data[keys[i]]}<br>`;
        }
      };

      const init = () => {
        // Old approach
        const req1 = new XMLHttpRequest(), req2 = new XMLHttpRequest();
        const arr = ['s1', 's2']; // A simple array.
        const obj = {width: 15, name: 'abc'}; // A simple object.
        req1.open('POST', 'JSON2a.php');
        req1.addEventListener('load', e => displayData(JSON.parse(e.target.response)));
        req1.send(JSON.stringify(arr)); // Send array as JSON string to the server script.
        req2.open('POST', 'JSON2b.php');
        req2.addEventListener('load', e => displayData(JSON.parse(e.target.response)));
        req2.send(JSON.stringify(obj)); // Send object as JSON string to server script.

        // New approach
        const headers = {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        };
        fetch('JSON2a.php', {
          headers: headers,
          method: "POST",
          body: JSON.stringify(arr)
        }).then(response => response.json()).then(data => displayData(data)).catch(
          error =>
            console.log(`There has been a problem with the fetch operation: ${error.message}`));
        fetch('JSON2b.php', {
          headers: headers,
          method: "POST",
          body: JSON.stringify(obj)
        }).then(response => response.json()).then(data => displayData(data)).catch(
          error =>
            console.log(`There has been a problem with the fetch operation: ${error.message}`));
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main><p></p></main>
  </body>
</html>
<?php
  header("Content-Type: application/json; charset=UTF-8");
  // http://stackoverflow.com/questions/8599595/send-json-data-from-javascript-to-php
  $arr = json_decode(file_get_contents('php://input')); // Decode the JSON string.
  $arr[0] = 'OK'; // Modify it.
  echo json_encode($arr); // Send the array as a JSON string to the client browser.
?>
<?php
  header("Content-Type: application/json; charset=UTF-8");
  // http://stackoverflow.com/questions/8599595/send-json-data-from-javascript-to-php
  $obj = json_decode(file_get_contents('php://input')); // Decode the JSON string
  $obj->name = 'OK'; // Modify it.
  echo json_encode($obj); // Send the object as a JSON string to the client browser.
?>

Here is a simple example of reading JSON data from a text file and displaying it in an HTML table:

[
  {
    "brand": "BMW",
    "model": "120d",
    "colour": "black",
    "consumption": 4.5
  },
  {
    "brand": "Audi",
    "model": "A5",
    "colour": "green",
    "consumption": 6.7
  }
]
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Reading JSON data from a text file</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const displayData = e => {
        const data = JSON.parse(e.target.response);
        const output =
          `<table><tr><th>brand</th><th>model</th><th>colour</th><th>consumption</th>
            </tr><tr><td>${data[0]['brand']}</td><td>${data[0]['model']}</td>
            <td>${data[0]['colour']}</td><td>${data[0]['consumption']}</td></tr>
            <tr><td>${data[1]['brand']}</td><td>${data[1]['model']}</td>
            <td>${data[1]['colour']}</td><td>${data[1]['consumption']}</td></tr></table>`;
        console.dir(data);
        document.querySelector('p').innerHTML = output;
      };

      const init = () => {
        const req = new XMLHttpRequest();
        req.open('POST', 'cars.json');
        req.addEventListener('load', displayData);
        req.send();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main><p></p>
    </main>
  </body>
</html>

Here is an example of exchanging more complex objects between client and server:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Sending complex JS objects to PHP and back</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const displayData = e => {
        const dataJSON = JSON.parse(e.target.response);
        console.dir(dataJSON);
      };

      const init = () => {
        const req = new XMLHttpRequest();
        const obj1 = {elems: [1, 2, 3, 4, 5], name: 'abc'};
        const obj2 = {elems: [6, 7, 8, 9, 10], name: 'def'};
        const arr = [obj1, obj2];
        req.open('POST', 'JSON4.php');
        req.addEventListener('load', displayData);
        console.log(JSON.stringify(arr));
        req.send(JSON.stringify(arr)); // Send array as JSON string to the server script.
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main><p></p></main>
  </body>
</html>
<?php
  header("Content-Type: application/json; charset=UTF-8");
  // http://stackoverflow.com/questions/8599595/send-json-data-from-javascript-to-php
  $obj = json_decode(file_get_contents('php://input')); // Decode the JSON string.
  $obj[0]->name = 'OK'; // Modify it.
  echo json_encode($obj); // Send the object as a JSON string to the client browser.
?>

4.3.29. Application Programming Interfaces (API)

html5-overview.net/current

platform.html5.org

html5demos.com

To find out which HTML5 APIs your browser supports, use html5test.com.

If you want to develop your own JSON API, take a look at jsonapi.org.

File

The File API allows our app to represent and access file objects.

www.w3.org/TR/FileAPI

www.html5rocks.com/en/tutorials/file/dndfiles

www.codeproject.com/Articles/668351/HTML-File-API-Capability-and-Compatibility

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>File API demo 1</title>
    <script>
      'use strict';

      const init = () => {
        document.querySelector('input').addEventListener('change', handleFile);
      };

      const handleFile = e => {
        const file = e.target.files[0];
        const reader = new FileReader();
        const display = e => document.querySelector('section').innerHTML = e.target.result;
        reader.addEventListener('load', display);
        reader.readAsBinaryString(file);
        document.querySelector('span').innerHTML = `${file.size} bytes`;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <header>
      <input type=file>
      File size: <span></span>
    </header>
    <section></section>
  </body>
</html>
Drag and drop

To make any object drag and droppable it needs to have its position attribute set to absolute or fixed. We can then use Peter-Paul Koch’s dragDrop object (cf. www.quirksmode.org/js/dragdrop.html), which does not use the drag and drop API but implements a solution based on the classic mousemove and mouseup events. It works very well across browsers. A stripped-down version is used in the following example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Drag and drop example 1</title>
    <meta charset=UTF-8>
    <style>
      .draggable {
        position:         fixed;
        cursor:           move;
        background-color: lightgreen;
      }
    </style>
    <script> // Based on http://www.quirksmode.org/js/dragdrop.html
    'use strict';

     const dragDrop = {
      initialMouseX: undefined,
      initialMouseY: undefined,
      startX: undefined,
      startY: undefined,
      draggedObject: undefined,
      initElement: element => {
        if (typeof element == 'string') element = document.getElementById(element);
        element.onmousedown = dragDrop.startDragMouse;
        element.className += ' draggable';
      },
      startDragMouse: e => {
        dragDrop.startDrag(e.target);
        var evt = e || window.event;
        dragDrop.initialMouseX = evt.clientX;
        dragDrop.initialMouseY = evt.clientY;
        document.addEventListener('mousemove', dragDrop.dragMouse);
        document.addEventListener('mouseup', dragDrop.releaseElement);
        return false;
      },
      startDrag: obj => {
        if (dragDrop.draggedObject) dragDrop.releaseElement();
        dragDrop.startX = obj.offsetLeft;
        dragDrop.startY = obj.offsetTop;
        dragDrop.draggedObject = obj;
        obj.className += ' dragged';
      },
      dragMouse: e => {
        var evt = e || window.event;
        var dX = evt.clientX - dragDrop.initialMouseX;
        var dY = evt.clientY - dragDrop.initialMouseY;
        dragDrop.setPosition(dX, dY);
        return false;
      },
      setPosition: (dx, dy) => {
        dragDrop.draggedObject.style.left = `${dragDrop.startX + dx}px`;
        dragDrop.draggedObject.style.top = `${dragDrop.startY + dy}px`;
        console.log(`${dx} ${dy}`);
      },
      releaseElement: () => {
        document.removeEventListener('mousemove', dragDrop.dragMouse);
        document.removeEventListener('mouseup', dragDrop.releaseElement);
        dragDrop.draggedObject.className =
          dragDrop.draggedObject.className.replace(/dragged/, '');
        dragDrop.draggedObject = null;
      }
    };

    const init = () => {
      dragDrop.initElement('art1');
      dragDrop.initElement('art2');
    }

    addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <article id=art1>
        This is a draggable article
      </article>
      <article id=art2>
        This is another draggable article
      </article>
    </main>
  </body>
</html>

The following two examples show a Window class to create a draggable and resizable window:

window1
"use strict";

class Window {
  constructor(id, width, height, top, left, bgColor, resizable) {
    this.elem = document.createElement('aside');
    this.elem.style.position = 'absolute';
    this.elem.style.width = width >= 0 ? width + 'px' : '100px';
    this.elem.style.height = height >= 0 ? height + 'px' : '100px';
    this.elem.style.minWidth = '30px';
    this.elem.style.minHeight = '30px';
    this.elem.style.border = '2px solid black';
    this.elem.style.resize = 'both';
    this.elem.style.overflow = 'auto';
    this.elem.style.cursor = 'move';
    this.elem.id = id;
    this.dragging = false;
    // cf. http://stackoverflow.com/questions/18942402/unable-to-remove-an-bound-event-listener
    this.mousemoveListener = undefined;
    this.initialMouseX = undefined;
    this.initialMouseY = undefined;
    this.initialElemX = undefined;
    this.initialElemY = undefined;
    this.startDrag = e => {
      if (this.dragging) return;
      this.dragging = true;
      this.initialMouseX = e.clientX;
      this.initialMouseY = e.clientY;
      this.initialElemX = this.elem.offsetLeft;
      this.initialElemY = this.elem.offsetTop;
      this.elem.addEventListener('mousemove', this.mousemoveListener);
    };
    this.drag = e => {
      this.elem.style.left = (this.initialElemX + e.clientX - this.initialMouseX) + 'px';
      this.elem.style.top = (this.initialElemY + e.clientY - this.initialMouseY) + 'px';
    };
    this.stopDrag = e => {
      this.dragging = false;
      this.elem.removeEventListener('mousemove', this.mousemoveListener);
    };
    const btn = document.createElement('button');
    btn.innerHTML = 'X';
    btn.addEventListener('click', e => {
      document.body.removeChild(e.target.parentElement)
    });
    btn.style.position = 'absolute';
    btn.style.right = '0';
    btn.style.top = '0';
    this.elem.appendChild(btn);
    this.elem.addEventListener('mousedown', this.startDrag.bind(this));
    this.elem.addEventListener('mouseup', this.stopDrag.bind(this));
    document.body.appendChild(this.elem);
    this.mousemoveListener = this.drag;
  }
}

addEventListener('load', () => new Window('w1', 300, 200, 10, 10, 'green', true));
window2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Window class demo 2</title>
    <meta charset=utf-8>
    <style>
      #w1, #w2, #w3 {
        position:         absolute;
        overflow:         auto;
        width:            300px;
        height:           200px;
        min-width:        30px;
        min-height:       30px;
        background-color: green;
        resize:           both;
        border:           2px solid black;
      }
    </style>
    <script src=window2.js></script>
  </head>
  <body></body>
</html>
"use strict";

class Window {
  constructor(id) {
    this.elem = document.createElement('aside');
    this.elem.id = id;
    this.dragging = false;
    // cf. http://stackoverflow.com/questions/18942402/unable-to-remove-an-bound-event-listener
    this.mousemoveListener = undefined;
    this.mouseUpListener = undefined;
    this.initialMouseX = undefined;
    this.initialMouseY = undefined;
    this.initialElemX = undefined;
    this.initialElemY = undefined;
    this.nav = undefined;
    this.startDrag = e => {
      if (this.dragging) return;
      this.dragging = true;
      this.initialMouseX = e.clientX;
      this.initialMouseY = e.clientY;
      this.initialElemX = this.elem.offsetLeft;
      this.initialElemY = this.elem.offsetTop;
      addEventListener('mousemove', this.mousemoveListener);
      addEventListener('mouseup', this.mouseupListener);
    };
    this.drag = e => {
      let x = this.initialElemX + e.clientX - this.initialMouseX;
      let y = this.initialElemY + e.clientY - this.initialMouseY;
      const elWidth = this.elem.getBoundingClientRect().width;
      const elHeight = this.elem.getBoundingClientRect().height;
      if (x < 0) x = 0;
      else if ((x + elWidth) > innerWidth) x = innerWidth - elWidth;
      if (y < 0) y = 0;
      else if ((y + elHeight) > innerHeight) y = innerHeight - elHeight;
      this.elem.style.left = `${x}px`;
      this.elem.style.top = `${y}px`;
    };
    this.stopDrag = e => {
      this.dragging = false;
      removeEventListener('mousemove', this.mousemoveListener);
      removeEventListener('mouseup', this.mouseupListener);
    };
    const nav = document.createElement('nav');
    nav.style.backgroundColor = 'lightblue';
    nav.style.height = '25px';
    nav.style.cursor = 'move';
    this.nav = nav;
    nav.addEventListener('mousedown', this.startDrag);
    const btn = document.createElement('button');
    btn.innerHTML = 'X';
    btn.addEventListener('click', e =>
      document.body.removeChild(e.target.parentElement.parentElement));
    btn.style.position = 'absolute';
    btn.style.right = '0';
    btn.style.top = '0';
    nav.appendChild(btn);
    this.elem.appendChild(nav);
    document.body.appendChild(this.elem);
    this.mousemoveListener = this.drag;
    this.mouseupListener = this.stopDrag;
  }
}

const init = () => {
  const win1 = new Window('w1');
  const win2 = new Window('w2');
  const win3 = new Window('w3');
};

addEventListener('load', init);

If we want to use the HTML5 drag and drop API instead of this object, we need to set the draggable attribute to true. See developer.mozilla.org/en-US/docs/DragDrop/Drag_and_Drop and developers.whatwg.org/dnd.html#dnd for an in-depth explanation of drag and drop.

Web Workers
This script will use 100% of the processing power of a 4 core CPU.
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Web Workers Demo 1</title>
    <meta charset=utf-8>
  </head>
  <body>
    <script>
      'use strict';

      // http://stackoverflow.com/questions/11871452/can-web-workers-utilize-100-of-a-
      // multi-core-cpu
      //window.URL = window.URL || window.webkitURL;
      const blob = new Blob(["while(true){}"], {type: 'text/javascript'});
      const code = window.URL.createObjectURL(blob);
      new Worker(code);
      new Worker(code);
      new Worker(code);
      new Worker(code);
    </script>
  </body>
</html>
Server-Sent Events

This API allows the opening of an HTTP connection for receiving push notifications from a server in the form of DOM events. The specification can be found at www.w3.org/TR/2009/WD-eventsource-20091029. Good descriptions and examples can be found at:

www.html5rocks.com/en/tutorials/eventsource/basics

html5doctor.com/server-sent-events

www.sitepoint.com/server-sent-events

developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events

chimera.labs.oreilly.com/books/1230000000545/ch16.html

html.spec.whatwg.org/multipage/comms.html\#server-sent-events

Here is a simple example that sends the current server time to the client every second. Take a look at the networking tab of the browser console. The communication takes place without new HTTP requests, as the existing one is kept alive. This is more efficient than using AJAX polling on the client side, i.e. each client checking with the server every second to see whether any new data has arrived.

When using sessions, we must call session_write_close, otherwise the session object will be locked and no other script can use it, given that our SSE-server runs an endless loop.
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Server-Sent Events Demo</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const init = () => {
        const source = new EventSource('SSE_server.php');
        source.addEventListener('message', e =>
          document.querySelector('main').innerHTML = `${e.data}<br>`);
        source.addEventListener('open', e =>
          document.querySelector('main').innerHTML += 'Connection opened<br>');
        source.addEventListener('error', e =>
          document.querySelector('main').innerHTML += 'Error<br>');
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>
<?php
  // From http://stackoverflow.com/questions/9070995/
  // html5-server-sent-events-prototyping-ambiguous-error-and-repeated-polling
  header('Content-Type: text/event-stream');
  header('Cache-Control: no-cache');

  function sendMsg($id, $msg) {
    //echo "id: $id" . PHP_EOL;
    echo "data: $msg" . PHP_EOL;
    echo PHP_EOL;
    ob_flush();
    flush();
  }

  date_default_timezone_set('Europe/Luxembourg');
  while (true) {
    $serverTime = time();
    $msg        = 'Server time: ' . date("h:i:s", time());
    sendMsg($serverTime, $msg);
    //session_write_close(); // Only needed if we work with sessions in this script.
    sleep(1);
  }
?>

Nicolas Detombes has developed a chat app that nicely illustrates how SSE can be used:

<?php
  // Created by Nicolas Detombes
  require_once 'chat_database.php';
?>
<!DOCTYPE html>
<html lang='en'>
  <head>
    <meta charset='UTF-8'>
    <title>Chat</title>
    <style>
      body {
        font-family: Arial, sans-serif;
      }

      textarea {
        width:       700px;
        height:      200px;
        border:      3px solid #cccccc;
        padding:     5px;
        font-family: Arial, sans-serif;
        resize:      none;
      }

      input {
        border:      3px solid #cccccc;
        padding:     5px;
        font-family: Arial, sans-serif;
      }
    </style>
  </head>
  <body>
    <h2>Chat</h2>
    <textarea disabled></textarea>
    <form method='post'><br>
      <input name='comment' id='comment' required>
      <input type='submit' value='Send'>
    </form>
    <script>
      'use strict';

      function init() {
        var source = new EventSource('chat_load.php');
        source.addEventListener('message', function (e) {
          //get the 20 first chars of the last line in the textarea
          var textarea = document.querySelector('textarea');
          var content = textarea.value;
          var lastLine = content.substr(content.lastIndexOf("[ ID ]"), 20);
          //console.log(e.data.substr(0, 20));
          //console.log(lastLine);
          if (e.data.substr(0, 20) !== lastLine) {//if first 10 chars do not match (id's)
            textarea.value += e.data + '\n';
            //auto-scroll down textarea
            textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
          }
        });

        document.forms[0].addEventListener('submit', function (e) {
          e.preventDefault();
          var data = new FormData(document.forms[0]), req = new XMLHttpRequest();
          req.addEventListener('load', displayData);
          req.open('POST', 'chat_comment.php');
          req.send(data);
          document.getElementById('comment').value = '';
        });
      }

      function displayData(e) {
        document.querySelector('textarea').innerHTML += e.target.response + '\n';
      }

      addEventListener('load', init);
    </script>
    <?php
      echo '<script>document.querySelector("textarea").value = "";</script>';
      //load the last 5 posts
      $posts = Database::listPost();
      foreach ($posts as $post) {
        $data = '[ ID ] ' . $post[0] . ' [ TIME ] ' . $post[1] . ' [ CONTENT ] ' . $post[2] .
          '\n';
        echo '<script>document.querySelector("textarea").value += "' . $data . '";</script>';
      }
    ?>
  </body>
</html>
<?php
  require_once 'chat_database.php';

  if (isset($_POST['comment'])) {
    //shouldn't be needed since comment input has required set to yes
    Database::post($_POST['comment']);
  }
?>
<?php

  require_once 'chat_credentials.php';

  class Database {

    private static $DB_HOST;
    private static $DB_USER;
    private static $DB_PASSWORD;
    private static $DB_NAME;
    private static $DB_TABLE_POST = 'tblPost';

    static function set_credentials($db_host, $db_user, $db_password, $db_name) {
      self::$DB_HOST     = $db_host;
      self::$DB_USER     = $db_user;
      self::$DB_PASSWORD = $db_password;
      self::$DB_NAME     = $db_name;
    }

    static function connect() {
      $dbc = @mysqli_connect(self::$DB_HOST, self::$DB_USER,
        self::$DB_PASSWORD, self::$DB_NAME) or
        die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
      mysqli_set_charset($dbc, "utf8");
      return $dbc;
    }

    static function post($dtPost) {
      $dbc   = self::connect();
      $query = 'INSERT INTO ' . self::$DB_TABLE_POST . ' (dtPost, fiUser, fiGroup) VALUES (?,
        1, 1)';
      $stmt  = $dbc->prepare($query);
      if (!$stmt)
        trigger_error('Wrong SQL: ' . $query . ' Error: ' . $dbc->error, E_USER_ERROR);
      $stmt->bind_param('s', $dtPost);
      $stmt->execute();
      $stmt->close();
      $dbc->close();
    }

    static function listPost() {
      //select last 5 records WHERE [id] > (SELECT MAX([id]) - 5 FROM [MyTable]) NOT ORDER BY
      // idPost ASC limit 5
      $idPost     = '';
      $tTimestamp = '';
      $Post       = '';
      $dbc        = self::connect();
      $query      = 'SELECT idPost, dtTimestamp, dtPost FROM ' . self::$DB_TABLE_POST .
        ' WHERE idPost > (SELECT MAX(idPost) - 5 FROM ' . self::$DB_TABLE_POST . ')';
      $stmt       = $dbc->prepare($query);
      $stmt->execute();
      $stmt->store_result();
      for ($i = 0; $i < $stmt->num_rows; $i++) {
        $stmt->bind_result($idPost, $tTimestamp, $Post);
        $stmt->fetch();
        $result[] = array($idPost, $tTimestamp, $Post);
      }
      $stmt->close();
      $dbc->close();
      return $result;
    }

    static function lastPost() {
      $idPost     = '';
      $tTimestamp = '';
      $Post       = '';
      $dbc        = self::connect();
      $query      = 'SELECT idPost, dtTimestamp, dtPost FROM ' . self::$DB_TABLE_POST .
        ' WHERE idPost = (SELECT MAX(idPost)  FROM ' . self::$DB_TABLE_POST . ')';
      $stmt       = $dbc->prepare($query);
      $stmt->execute();
      $stmt->store_result();
      $stmt->bind_result($idPost, $tTimestamp, $Post);
      $stmt->fetch();
      $result = array($idPost, $tTimestamp, $Post);
      $stmt->close();
      $dbc->close();
      return $result;
    }
  }
?>
<?php
  require_once 'chat_database.php';

  header('Content-Type: text/event-stream');
  header('Cache-Control: no-cache');

  function sync($data) {
    echo 'data: [ ID ] ' . $data[0] . ' [ TIME ] ' . $data[1] . ' [ CONTENT ] ' . $data[2] .
      PHP_EOL;
    echo PHP_EOL;
    ob_flush();
    flush();
  }

  while (true) {
    sync(Database::lastPost());
    sleep(1);
  }
?>
Canvas

Here is a pacman trying to catch the mouse cursor:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Pacman</title>
    <meta charset=UTF-8>
    <style>
      body {
        background-color: black;
        overflow:         hidden;
        cursor:           url("mouseWithCheese64x64.cur"), auto;
      }
    </style>
  </head>
  <body>
    <script>
      'use strict';

      const canvas = document.createElement("canvas");

      const sizeCanvas = () => {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
      };
      sizeCanvas();
      document.querySelector("body").appendChild(canvas);
      const context = canvas.getContext('2d');
      const pacman = new Image();
      pacman.src = "pacman128x128.png";
      pacman.alt = "pacman128x128.png";

      const TO_RADIANS = Math.PI / 180, TIMEOUT = 20;
      let mouseX = 0, mouseY = 0, currX = 100, currY = 100, timerID = null;

      const drawRotatedImage = (image, x, y, angle) => {
        // Save the current co-ordinate system before we play with it.
        context.save();

        // Move to the middle of where we want to draw our image.
        context.translate(x, y);

        // Rotate around that point, converting our angle from degrees to radians.
        context.rotate(angle * TO_RADIANS);

        // Draw it up and to the left by half the width and height of the image.
        context.drawImage(image, -(image.width / 2), -(image.height / 2));

        // Restore the co-ords to what they were when we began.
        context.restore();
      };

      const getMouseXY = e => {
        mouseX = e.pageX;
        mouseY = e.pageY;
      };

      const animate = () => {
        const dX = currX - mouseX, dY = currY - mouseY;
        const a = Math.floor(dX / 20), b = Math.floor(dY / 20);
        currX -= a;
        currY -= b;
        if ((Math.abs(a) >= 1) || (Math.abs(b) >= 1)) {
          context.clearRect(0, 0, canvas.width, canvas.height);
          drawRotatedImage(pacman, currX, currY, (Math.atan2(dY, dX) - Math.PI) * 180 / Math.PI);
        }
      };

      const toggle = () => {
        if (timerID === null) timerID = setInterval("animate()", TIMEOUT);
        else {
          clearInterval(timerID);
          timerID = null;
        }
      };

      addEventListener('mousemove', getMouseXY);
      addEventListener('click', toggle);
      addEventListener('resize', sizeCanvas);
      timerID = setInterval(animate, TIMEOUT);
    </script>
  </body>
</html>

Here is a skeleton for a pong game:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Pong</title>
    <meta charset=utf-8>
    <script src=pong.js></script>
  </head>
  <body>
    <canvas width=1000 height=780></canvas>
    <button>Stop</button>
  </body>
</html>
"use strict";

const BAT_WIDTH = 10, BAT_HEIGHT = 100;
let timer, posLX, posLY, posRX, posRY, posBallX, posBallY, ballDX, ballDY, posbatLX, posbatLY,
  posbatRX, posbatRY, radius, canvas, ctx, running = true;

const init = () => {
  canvas = document.querySelector('canvas');
  ctx = canvas.getContext("2d");
  posLX = 2;
  posLY = 340;
  posRX = 988;
  posRY = 340;
  posBallX = 500;
  posBallY = 360;
  ballDX = 3;
  ballDY = 3;
  radius = 20;
  posbatLX = 2;
  posbatLY = 340;
  posbatRX = 988;
  posbatRY = 340;
  document.querySelector('button').addEventListener('click', toggleAnimation);
  requestAnimationFrame(gameLoop);
};

const gameLoop = () => {
  ctx.fillStyle = "#000000";
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#FFFFFF";
  ctx.fillRect(posLX, posLY, 10, 100);
  ctx.fillRect(posRX, posRY, 10, 100);
  ctx.beginPath();
  ctx.arc(posBallX, posBallY, radius, 0, 2 * Math.PI);
  ctx.fill();
  // Check for collision with canvas borders.
  if (((posBallX + radius) >= canvas.width) || ((posBallX - radius) <= 0)) ballDX = -ballDX;
  if (((posBallY + radius) >= canvas.height) || ((posBallY - radius) <= 0)) ballDY = -ballDY;
  ctx.fillRect(posbatLX, posbatLY, BAT_WIDTH, BAT_HEIGHT);
  ctx.fillRect(posbatRX, posbatRY, BAT_WIDTH, BAT_HEIGHT);
  if ((posBallX - radius) <= (posbatLX + BAT_WIDTH) &&
    (BAT_HEIGHT + posbatLY >= (posBallY - radius)) && (posbatLY <= (posBallY + radius)))
    ballDX = Math.abs(ballDX);
  if ((posBallX + radius) >= posbatRX && (BAT_HEIGHT + posbatRY >= (posBallY - radius))
    && (posbatRY <= (posBallY + radius))) ballDX = -Math.abs(ballDX);
  posBallX += ballDX;
  posBallY += ballDY;
  if (running) requestAnimationFrame(gameLoop);
};

const toggleAnimation = () => {
  running = !running;
  if (running) {
    requestAnimationFrame(gameLoop);
    document.querySelector('button').innerHTML = 'Stop';
  }
  else document.querySelector('button').innerHTML = 'Resume';
};

addEventListener('load', init);

And here is a random maze generator class:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Canvas 2D maze demo</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      /**
       * Construct a maze cell.
       * @param x {number} horizontal position in the maze (0 <= x < maze.width)
       * @param y {number} vertical position in the maze (0 <= y < maze.height)
       * @param topWall {boolean} does the cell have a top wall?
       * @param rightWall {boolean} does the cell have a right wall?
       * @param bottomWall {boolean} does the cell have a bottom wall?
       * @param leftWall {boolean} does the cell have a left wall?
       * @constructor
       */
      class Cell {
        constructor(x, y, topWall, rightWall, bottomWall, leftWall) {
          this.x = x;
          this.y = y;
          this.topWall = topWall;
          this.rightWall = rightWall;
          this.bottomWall = bottomWall;
          this.leftWall = leftWall;
          this.visited = false;
        }
      }

      /**
       * Generate a maze using dept-first search with backtracking.
       * cf. http://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
       * @param width {number} number of cells in a row
       * @param height {number} number of cells in a column
       * @param cellSize {number} side length of a cell in pixels
       * @param wallThickness {number} wall thickness in pixels
       * @constructor
       */
      class Maze {
        constructor(width, height, cellSize, wallThickness) {
          this.width = width;
          this.height = height;
          this.cellSize = cellSize;
          this.wallThickness = wallThickness;
          this.numCells = width * height;
          this.cells = []; // Arranged in x, y order, i.e. first column, then row index.

          this.getUnvisitedNeighbors = (x, y) => {
            const unvisitedNeighbors = [];
            if (x >= 1 && !this.cells[x - 1][y].visited)
              unvisitedNeighbors.push(this.cells[x - 1][y]); // left neighbour
            if (x < (this.width - 1) && !this.cells[x + 1][y].visited)
              unvisitedNeighbors.push(this.cells[x + 1][y]); // right
            if (y >= 1 && !this.cells[x][y - 1].visited)
              unvisitedNeighbors.push(this.cells[x][y - 1]); // top
            if (y < (this.height - 1) && !this.cells[x][y + 1].visited)
              unvisitedNeighbors.push(this.cells[x][y + 1]); // bottom
            return unvisitedNeighbors;
          };

          this.draw2D = () => {
            const canvas = document.querySelector('canvas');
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'red';
            let cell;
            const cs = this.cellSize, wt = this.wallThickness;
            for (let i = 0; i < this.width; i++)
              for (let j = 0; j < this.height; j++) {
                cell = this.cells[i][j];
                if (cell.topWall) ctx.fillRect(cell.x * cs - wt / 2, cell.y * cs - wt / 2, cs +
                  wt, wt);
                if (cell.rightWall) ctx.fillRect(cell.x * cs + cs - wt / 2, cell.y * cs - wt /
                  2, wt, cs + wt);
                if (cell.bottomWall) ctx.fillRect(cell.x * cs - wt / 2, cell.y * cs + cs - wt
                  / 2, cs + wt, wt);
                if (cell.leftWall) ctx.fillRect(cell.x * cs - wt / 2, cell.y * cs - wt / 2, wt,
                  cs + wt);
              }
            // Draw maze border.
            ctx.lineWidth = 2 * wt;
            ctx.strokeStyle = 'red';
            ctx.strokeRect(0, 0, canvas.width, canvas.height);
            // Draw exit.
            ctx.strokeStyle = 'green';
            ctx.beginPath();
            ctx.moveTo(0, 0);
            ctx.lineTo(cs, 0);
            ctx.stroke();
            // Draw entry.
            ctx.lineWidth = wt;
            ctx.strokeStyle = 'pink';
            ctx.beginPath();
            ctx.moveTo(this.width * cs, (this.height - 1) * cs);
            ctx.lineTo(this.width * cs, this.height * cs);
            ctx.stroke();
          };

          // Initialize maze with all walls present.
          for (let i = 0; i < width; i++) {
            this.cells[i] = [];
            for (let j = 0; j < height; j++)
              this.cells[i][j] = new Cell(i, j, true, true, true, true);
          }
          // We start at a random place and mark it as visited.
          let currentCell = new Cell(Math.floor(Math.random() * width),
            Math.floor(Math.random() * height), true, true, true, true);
          currentCell.visited = true;
          let numCellsVisited = 1, unvisitedNeighbors, chosenNeighbor;
          const stack = [];
          // While there are unvisited cells.
          while (numCellsVisited <= this.numCells) {
            // If the current cell has any neighbors which have not been visited.
            unvisitedNeighbors = this.getUnvisitedNeighbors(currentCell.x, currentCell.y);
            if (unvisitedNeighbors.length > 0) {
              // Choose randomly one of the unvisited neighbors.
              chosenNeighbor =
                unvisitedNeighbors[Math.floor(Math.random() * unvisitedNeighbors.length)];
              // Push the current cell to the stack.
              stack.push(currentCell);
              // Remove the wall between the current cell and the chosen cell.
              if (chosenNeighbor.x < currentCell.x) { // Neighbor is left from current cell.
                currentCell.leftWall = false;
                chosenNeighbor.rightWall = false;
              }
              else if (chosenNeighbor.x > currentCell.x) {
                // Neighbor is right from current cell.
                currentCell.rightWall = false;
                chosenNeighbor.leftWall = false;
              }
              else if (chosenNeighbor.y < currentCell.y) {
                // Neighbor is above current cell.
                currentCell.topWall = false;
                chosenNeighbor.bottomWall = false;
              }
              else if (chosenNeighbor.y > currentCell.y) {
                // Neighbor is below current cell.
                currentCell.bottomWall = false;
                chosenNeighbor.topWall = false;
              }
              // Make the chosen cell the current cell and mark it as visited.
              currentCell = chosenNeighbor;
              currentCell.visited = true;
              numCellsVisited++;
            }
            // Else if stack is not empty.
            else if (stack.length > 0) {
              // Pop a cell from the stack and make it the current cell.
              currentCell = stack.pop();
            }
            else {
              // Pick a random unvisited cell, make it the current cell and mark it as visited.
              const unvisitedCells = [];
              for (let i = 0; i < this.width; i++)
                for (let j = 0; j < this.height; j++)
                  if (!this.cells[i][j].visited) unvisitedCells.push(this.cells[i][j]);
              currentCell = unvisitedCells[Math.floor(Math.random() * unvisitedCells.length)];
              currentCell.visited = true;
              numCellsVisited++;
            }
          }
          this.cells[0][0].topWall = false; // Open up exit.
          this.cells[this.width - 1][this.height - 1].rightWall = false; // Open up entry.
          this.draw2D();
        }
      }

      addEventListener('load', () => new Maze(20, 20, 40, 3));
    </script>
  </head>
  <body>
    <main>
      <canvas width=1000 height=1000></canvas>
    </main>
  </body>
</html>
Web Sockets

So far we’ve used HTTP POST or GET requests to send data to the server, who responded with new HTML. This is a very inefficient and limited approach. If we want real-time two way communication between the server and a potentially large number of clients, we should take advantage of the new JavaScript WebSocket API that is available in Firefox and Chrome. By establishing a bidirectional communication channel between the server and each client, we can for instance implement real-time chat.

The easiest way to get started can be found at websocketd.com. The recommended way is to use WebSockets with Node.js.

Client

On the client side, we need to implement something like the following:

const socket = new WebSocket("wss://foxi.ltam.lu:35000");
socket.addEventListener('open', opened);
socket.addEventListener('message', received);
socket.addEventListener('close', closed);
socket.addEventListener('error', error);

const opened = () => {

}

const received = event => {
  console.log("Received: ", event.data);
}

const closed = () => {

}

const error = event => {
  console.error("Error: ", event.data);
}

const send = event => {
  socket.send("Hello world!");
}

opened will be called once the web socket connection is ready. received will be called when a message from the server has been received.

Server
PHP

Currently the Apache web server does not come with a web socket module. We could write our own web socket server in PHP, but why reinvent the wheel when there are good open source libraries available. We’ll use github.com/Devristo/phpws and adapt it to our needs. Download the zip file and extract it on the web server. If you are interested in the implementation details, take a look at the files. For our purposes, we only need to modify the demo.php file, particularly the onMessage function. Make a copy of the file and save it as server.php.

Here is a sample implementation of onMessage, which simply sends the message received to all other clients:

public function onMessage(IWebSocketConnection $user, IWebSocketMessage
$msg) {
  $thisuser = $user->getId();
  $msg      = trim($msg->getData());
  $msgback  = WebSocketMessage::create($msg);

  foreach ($this->server->getConnections() as $user)
    if ($user->getId() !== $thisuser) $user->sendMessage($msgback);
}

There are other libraries around, such as github.com/walkor/phpsocket.io.

But, as mentioned before, in order to really work with WebSockets you should consider using Node.js as it is much better suited for this purpose.

WebGL
WebGL (Web Graphics Library) is a JavaScript API for rendering interactive 3D graphics and 2D graphics within any compatible web browser without the use of plug-ins.

Relevant web pages:

www.khronos.org/registry/webgl/specs/1.0

www.khronos.org/webgl/wiki/Main_Page

developer.cdn.mozilla.net/media/uploads/demos/a/z/azakai/3baf4ad7e600cbda06ec46efec5ec3b8/bananabread_1373485124_demo_package/index.html

www.spacejack.ca/spacejack

hexgl.bkcore.com/play

htmlchess.sourceforge.net/demo/example.html

www.chromeexperiments.com/webgl

playwebgl.com

Three.js

Given the complexity of direct WebGL programming, we’ll start by using the Three.js JavaScript library, which can be downloaded from github.com/mrdoob/three.js and greatly simplifies the development of 3D web apps.

The best way to get started is to study the documentation at threejs.org/docs/#manual/introduction/Creating-a-scene.

Let’s start with a very simple example and walk it through step by step:

Threejs1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>My first Three.js app</title>
    <meta charset=utf-8>
    <script src=three.js></script>
  </head>
  <body>
    <script>
      "use strict"; // This will be standard in the future and makes debugging easier.
      let scene, camera, renderer, cube, plane, spotlight, animation = true;

      window.addEventListener('load',
        // This function will be executed after the whole document is loaded.
        () => {
          const canvas = document.querySelector('canvas');
          scene = new THREE.Scene();
          camera = new THREE.PerspectiveCamera(50, canvas.width / canvas.height, 0.1, 1000);
          renderer = new THREE.WebGLRenderer();
          renderer.setSize(canvas.width, canvas.height);
          document.body.appendChild(renderer.domElement);
          let geometry = new THREE.BoxGeometry(1, 1.8, 0.3);
          let material = new THREE.MeshBasicMaterial({color: 'green'});
          cube = new THREE.Mesh(geometry, material);
          cube.castShadow = true;
          scene.add(cube);
          geometry = new THREE.PlaneGeometry(5, 5);
          material = new THREE.MeshBasicMaterial({color: 'red'});
          const plane = new THREE.Mesh(geometry, material);
          plane.receiveShadow = true;
          plane.rotation.x = -0.5 * Math.PI;
          plane.position.y = -1;
          scene.add(plane);
          camera.position.y = 4;
          camera.position.z = 5;
          camera.lookAt(scene.position);
          spotlight = new THREE.AmbientLight('blue');
          scene.add(spotlight);
          render();
        });

      // This is our animation loop that will ideally be executed 60 times per second.
      const render = () => {
        if (animation) requestAnimationFrame(render);
        cube.rotation.x += 0.1;
        cube.rotation.y += 0.1;
        renderer.render(scene, camera);
      };

      // Stop/restart the animation.
      const toggleAnimation = () => {
        animation = !animation;
        if (animation) render();
      };
    </script>
    <canvas width=500 height=500></canvas>
    <button onclick=toggleAnimation();>Stop/start</button>
  </body>
</html>

As explained in the Three.js introductory example, we need a scene, a camera and a renderer to display the scene using the camera. The renderer needs the canvas element, which is where the whole scene will be displayed.

Our script consists of 3 functions:

  1. An initialization function that creates the scene, the camera and the renderer. It then adds them to the DOM and starts the rendering. This function is executed only once, after the document has been loaded.

  2. The rendering function, which calls itself using requestAnimationFrame (cf. developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame). Here we perform the animation, but only if the animation is supposed to be running as indicated by the boolean global variable. In this simple example we rotate our cube around the x and y axes.

  3. toggleAnimation simply toggles a boolean global variable, which indicates whether the animation is currently meant to be running or stopped. In the former case, the render function gets executed.

Here’s a slightly more evolved example:

Threejs2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>My second Three.js app</title>
    <meta charset=utf-8>
    <style>
      body {
        margin:   0;
        padding:  0;
        overflow: hidden;
      }

      button {
        position: fixed;
        top:      0;
        left:     0;
      }

      #stats {
        position: fixed;
        top:      20px;
        left:     0;
      }
    </style>
    <script src=three.js></script>
    <script src=stats.min.js></script>
  </head>
  <body>
    <script>
      "use strict"; // This will be standard in the future and makes debugging easier.

      let scene, camera, renderer, geometry, material, mesh, animation = true, stats,
        stepX = 0.1, stepY = 0.1, stepZ = 0.1, directionX = 1, directionY = 1, directionZ = 1;

      const init = () => {
        stats = new Stats();
        document.getElementById('stats').appendChild(stats.domElement);
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(75, window.innerWidth /
          window.innerHeight, 1, 10000);
        camera.position.x = 0;
        camera.position.y = 70;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        // Add a texture to our sphere.
        const mapUrl = "LTAM256x256.png";
        const map = THREE.ImageUtils.loadTexture(mapUrl);
        geometry = new THREE.SphereGeometry(20, 50, 50);
        material = new THREE.MeshLambertMaterial({color: 0xff00ff, map: map});
        mesh = new THREE.Mesh(geometry, material);
        mesh.position.y = 20;
        mesh.castShadow = true;
        scene.add(mesh);

        const spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 100, 50);
        spotLight.castShadow = true;
        scene.add(spotLight);
        renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000000);
        renderer.shadowMapEnabled = true;

        // When the window gets resized, we need to remove the old canvas
        // and add a new one with the correct size.
        const childNodes = document.body.childNodes;
        let n = childNodes.length;
        while (n > 0) {
          if (childNodes[n - 1].tagName && childNodes[n - 1].tagName === 'CANVAS') {
            document.body.removeChild(childNodes[n - 1]);
          }
          n--;
        }
        document.body.appendChild(renderer.domElement);
        render();
      };

      // This is our animation loop that will ideally be executed 60 times per second.
      const render = () => {
        if (animation) requestAnimationFrame(render);
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.02;
        mesh.rotation.z += 0.01;
        if (mesh.position.x > 100) directionX = -1;
        else if (mesh.position.x < -100) directionX = 1;
        if (mesh.position.y > 50) directionY = -1;
        else if (mesh.position.y < -50) directionY = 1;
        if (mesh.position.z > 50) directionZ = -1;
        else if (mesh.position.z < -50) directionZ = 1;
        mesh.position.x += stepX * Math.random() * directionX;
        mesh.position.y += stepY * Math.random() * directionY;
        mesh.position.z += stepZ * Math.random() * directionZ;
        stats.update();
        renderer.render(scene, camera);
      };

      const resize = () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      };

      // Stop/restart the animation.
      const toggleAnimation = () => {
        animation = !animation;
        if (animation) render();
      };

      window.addEventListener('load', init);
      window.addEventListener('resize', resize);
    </script>
    <div id=stats></div>
    <button onclick=toggleAnimation();>Stop/start</button>
  </body>
</html>

Instead of declaring a canvas element, we’ll let the renderer take care of that. We’ll let the scene take up the whole browser window width and height. We can even switch to full screen mode using F11. Finally we use Mr. Doob’s performance monitor, available from github.com/mrdoob/stats.js.

As a further evolutionary step, we can add user controls (cf. code.google.com/p/dat-gui), mouse interaction and a funny background plane:

cover4
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Three.js Skeleton</title>
    <meta charset=utf-8>
    <style>
      body {
        margin:   0;
        padding:  0;
        overflow: hidden;
      }

      button {
        position: fixed;
        top:      0;
        left:     0;
      }

      #stats {
        position: fixed;
        top:      20px;
        left:     0;
      }
    </style>
    <script src=three.js></script>
    <script src=OrbitControls.js></script>
    <script src=stats.min.js></script>
    <script src=dat.gui.min.js></script>
    <script>
      "use strict";

      let scene, camera, renderer, geometry, material, mesh, animation = true, stats,
        projector = new THREE.Projector(),
        directionX = 1, directionY = 1, directionZ = 1, controls, cameraControl;

      const init = () => {
        stats = new Stats();
        document.getElementById('stats').appendChild(stats.domElement);
        controls = {
          stepX: 0.1,
          stepY: 0.1,
          stepZ: 0.1
        };

        const gui = new dat.GUI();
        gui.add(controls, 'stepX', 0, 3);
        gui.add(controls, 'stepY', 0, 3);
        gui.add(controls, 'stepZ', 0, 3);
        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera(75, window.innerWidth /
          window.innerHeight, 1, 10000);
        camera.position.x = 0;
        camera.position.y = 70;
        camera.position.z = 30;
        camera.lookAt(scene.position);
        cameraControl = new THREE.OrbitControls(camera);
        let mapUrl = "LAM256x256.png";
        let map = THREE.ImageUtils.loadTexture(mapUrl);
        geometry = new THREE.BoxGeometry(20, 20, 20);
        material = new THREE.MeshLambertMaterial({color: 0xff00ff, map: map});
        mesh = new THREE.Mesh(geometry, material);
        mesh.position.y = 20;
        mesh.castShadow = true;
        scene.add(mesh);

        mapUrl = "checker_large.gif";
        map = THREE.ImageUtils.loadTexture(mapUrl);
        map.wrapS = map.wrapT = THREE.RepeatWrapping;
        map.repeat.set(8, 8);
        const color = 0xffffff;
        const ambient = 0x888888;
        // Put in a ground plane to show off the lighting
        geometry = new THREE.PlaneGeometry(200, 200, 50, 50);
        const chessboard = new THREE.Mesh(geometry,
          new THREE.MeshPhongMaterial({
            color: color,
            ambient: ambient, map: map, side: THREE.DoubleSide,
            opacity: 0.5, transparent: true
          }));
        chessboard.rotation.x = -Math.PI / 2;
        chessboard.position.y = -4.02;
        scene.add(chessboard);

        const spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(-40, 100, 50);
        spotLight.castShadow = true;
        scene.add(spotLight);
        const hemisphereLight = new THREE.HemisphereLight(0xdd00dd, 0x00aa00, 0.3);
        scene.add(hemisphereLight);
        renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000000);
        renderer.shadowMapEnabled = true;
        /*const childNodes = document.body.childNodes;
         let n = childNodes.length;
         while (n > 0) {
         if (childNodes[n - 1].tagName && childNodes[n - 1].tagName === 'CANVAS') {
         document.body.removeChild(childNodes[n - 1]);
         }
         n--;
         }*/
        document.body.appendChild(renderer.domElement);
        animate();
      };

      const animate = () => {
        // note: three.js includes requestAnimationFrame shim
        if (animation) requestAnimationFrame(animate);
        stats.update();
        mesh.rotation.x += 0.01;
        mesh.rotation.y += 0.02;
        mesh.rotation.z += 0.01;
        if (mesh.position.x > 100) directionX = -1;
        else if (mesh.position.x < -100) directionX = 1;
        if (mesh.position.y > 50) directionY = -1;
        else if (mesh.position.y < -50) directionY = 1;
        if (mesh.position.z > 50) directionZ = -1;
        else if (mesh.position.z < -50) directionZ = 1;
        mesh.position.x += Math.random() * controls.stepX * directionX;
        mesh.position.y += Math.random() * controls.stepY * directionY;
        mesh.position.z += Math.random() * controls.stepZ * directionZ;
        cameraControl.update();
        renderer.render(scene, camera);
      };

      const resize = () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      };

      const toggleAnimation = () => {
        animation = !animation;
        if (animation) animate();
      };

      const onDocumentMouseDown = event => {
        event.preventDefault();
        const vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1,
          -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
        projector.unprojectVector(vector, camera);
        const raycaster = new THREE.Raycaster(camera.position,
          vector.sub(camera.position).normalize());
        const intersects = raycaster.intersectObjects([mesh]);
        if (intersects.length > 0) {
          const r = Math.floor(Math.random() * 256);
          const g = Math.floor(Math.random() * 256);
          const b = Math.floor(Math.random() * 256);
          intersects[0].object.material.color =
            new THREE.Color("rgb(" + r + ", " + g + ", " + b + ")");
        }
      };

      window.addEventListener('load', init);
      window.addEventListener('resize', resize);
      document.addEventListener('mousedown', onDocumentMouseDown);
    </script>
  </head>
  <body>
    <div id=stats></div>
    <button onclick=toggleAnimation();>Stop/start</button>
  </body>
</html>

Loading a Collada model

threejscollada1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Load Collada model with Three.js</title>
    <meta charset=utf-8>
    <script src=three.js></script>
    <script src=ColladaLoader.js></script>
    <script>
      'use strict';

      const init = () => {
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(45,
          window.innerWidth / window.innerHeight, 0.1, 1000);
        const webGLRenderer = new THREE.WebGLRenderer();
        webGLRenderer.setClearColorHex(0xcccccc, 1.0);
        webGLRenderer.setSize(window.innerWidth, window.innerHeight);
        webGLRenderer.shadowMapEnabled = true;

        // position and point the camera to the center of the scene
        camera.position.x = 15;
        camera.position.y = 15;
        camera.position.z = 15;
        camera.lookAt(new THREE.Vector3(0, 2, 0));

        // add spotlight for the shadows
        const spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(150, 150, 150);
        spotLight.intensity = 2;
        scene.add(spotLight);

        document.querySelector('main').appendChild(webGLRenderer.domElement);

        const loader = new THREE.ColladaLoader();
        loader.options.convertUpAxis = true;
        let mesh;
        loader.load("Boat2.dae", result => {
          console.dir(result);
          mesh = result.scene;
          mesh.position.set(0, -1, 0);
          //mesh.scale.set(1, 1, 1);
          scene.add(mesh);
        });

        const render = () => {
          requestAnimationFrame(render);
          webGLRenderer.render(scene, camera);
        };

        render();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main></main>
  </body>
</html>

Physijs

FPS

Let’s have some real fun and create a first person shooter (FPS) from scratch.

threejs3
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>FPS</title>
    <meta charset=utf-8>
    <link rel=stylesheet href=style.css>
    <script src=three.js></script>
    <script src=FirstPersonControls.js></script>
    <script src=stats.min.js></script>
    <script src=dat.gui.min.js></script>
    <script src=main.js></script>
  </head>
  <body>
    <aside id=stats></aside>
    <button onclick="game.toggleAnimation()">Stop/start</button>
  </body>
</html>
"use strict";

const game = {};
game.aspectRatio = window.innerWidth / window.innerHeight;
game.scene = new THREE.Scene();
game.camera = new THREE.PerspectiveCamera(60, game.aspectRatio, 1, 10000);
game.cameraControls = new THREE.FirstPersonControls(game.camera);
game.renderer = new THREE.WebGLRenderer();
game.projector = new THREE.Projector();
game.stats = new Stats();
game.animation = true;
game.clock = new THREE.Clock();
game.map = [ // 1  2  3  4  5  6  7  8  9
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // 0
  [1, 0, 0, 0, 0, 0, 0, 1, 0, 1], // 1
  [1, 1, 0, 0, 2, 0, 0, 0, 0, 1], // 2
  [1, 0, 0, 0, 0, 2, 0, 0, 0, 1], // 3
  [1, 0, 0, 2, 0, 0, 2, 0, 0, 1], // 4
  [1, 0, 0, 0, 2, 0, 0, 0, 1, 1], // 5
  [1, 0, 1, 0, 0, 0, 0, 1, 1, 1], // 6
  [1, 0, 1, 0, 0, 1, 0, 0, 0, 1], // 7
  [1, 0, 1, 0, 1, 0, 0, 0, 0, 1], // 8
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]  // 9
];
game.mapW = game.map.length;
game.mapH = game.map[0].length;
game.UNITSIZE = 50;
game.WALLHEIGHT = game.UNITSIZE / 3;
game.MOVESPEED = 100;
game.LOOKSPEED = 0.075;
game.init = () => {
  document.getElementById('stats').appendChild(game.stats.domElement);
  game.cameraControls.movementSpeed = game.MOVESPEED;
  game.cameraControls.lookSpeed = game.LOOKSPEED;
  game.cameraControls.lookVertical = false;
  game.cameraControls.noFly = true;
  game.camera.position.y = game.UNITSIZE * .2;
  game.scene.add(game.camera);
  const geometry = new THREE.BoxGeometry(game.UNITSIZE * game.mapW, 1,
    game.UNITSIZE * game.mapW);
  const material = new THREE.MeshLambertMaterial(
    {map: THREE.ImageUtils.loadTexture('images/texture-91829_1920.jpg')});
  const floor = new THREE.Mesh(geometry, material);
  game.scene.add(floor);
  const cube = new THREE.BoxGeometry(game.UNITSIZE, game.WALLHEIGHT, game.UNITSIZE);
  const materials = [
    new THREE.MeshLambertMaterial({map: THREE.ImageUtils.loadTexture('images/wall-1.jpg')}),
    new THREE.MeshLambertMaterial({map: THREE.ImageUtils.loadTexture('images/wall-2.jpg')}),
    new THREE.MeshLambertMaterial({color: 0xFBEBCD})
  ];
  for (let i = 0; i < game.mapW; i++) {
    for (let j = 0, m = game.map[i].length; j < m; j++) {
      if (game.map[i][j]) {
        const wall = new THREE.Mesh(cube, materials[game.map[i][j] - 1]);
        wall.position.x = (.5 + i - game.mapW / 2) * game.UNITSIZE;
        wall.position.y = game.WALLHEIGHT / 2;
        wall.position.z = (.5 + j - game.mapW / 2) * game.UNITSIZE;
        game.scene.add(wall);
      }
    }
  }

  const directionalLight1 = new THREE.DirectionalLight(0xF7EFBE, 0.7);
  directionalLight1.position.set(.5, 1, .5);
  game.scene.add(directionalLight1);
  const directionalLight2 = new THREE.DirectionalLight(0xF7EFBE, 0.5);
  directionalLight2.position.set(-0.5, -1, -0.5);
  game.scene.add(directionalLight2);
  game.scene.fog = new THREE.FogExp2(0xa6a1aF, 0.0005);
  game.renderer.setSize(window.innerWidth, window.innerHeight);
  game.renderer.setClearColor(0x2222ff);
  game.renderer.shadowMapEnabled = true;
  document.body.appendChild(game.renderer.domElement);
  game.animate();
};

game.animate = () => {
  if (game.animation) requestAnimationFrame(game.animate);
  game.stats.update();
  game.cameraControls.update(game.clock.getDelta());
  game.renderer.render(game.scene, game.camera);
};

game.toggleAnimation = () => {
  game.animation = !game.animation;
  if (game.animation) game.animate();
};

game.resize = () => {
  game.camera.aspect = window.innerWidth / window.innerHeight;
  game.camera.updateProjectionMatrix();
  game.renderer.setSize(window.innerWidth, window.innerHeight);
};

window.addEventListener('load', game.init);
window.addEventListener('resize', game.resize);

carvisualizer.plus360degrees.com/threejs

hexgl.bkcore.com

portableapps.com/apps/graphics_pictures/blender_portable

github.com/tparisi/Vizi

www.peter-strohm.de/webgl/index.php

www.khronos.org/webgl/wiki/User_Contributions

GLAM

GLAM (GL And Markup) is a declarative language for 3D web content (cf. tparisi.github.io/glam).

Cesium

This is a JavaScript library for creating 3D globes and 2D maps (cf. cesiumjs.org).

Cesium1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>A first Cesium experiment</title>
    <meta charset=utf-8>
    <style>
      @import url(Build/Cesium/Widgets/widgets.css);

      html, body, #cesiumContainer {
        width:    100%;
        height:   100%;
        margin:   0;
        padding:  0;
        overflow: hidden;
      }
    </style>
    <script src=Build/Cesium/Cesium.js></script>
  </head>
  <body>
    <div id=cesiumContainer></div>
    <script>
      'use strict';

      const viewer = new Cesium.Viewer('cesiumContainer', {
        imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
          url:
            'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer'
        }),
        baseLayerPicker: false
      });
    </script>
  </body>
</html>
Direct WebGL programming

All WebGL drawing happens inside the HTML canvas element (cf. developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement.getContext). Before using the WebGL API we need a WebGLRenderingContext object (cf. developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext), which manages the whole 3D drawing process. To get it we call the getContext method of the canvas element and pass webgl as parameter to get the 3D context. If we pass 2d we get a 2D context. If we wanted to ensure compatibility with older browsers, we would have to check experimental-webgl, webkit-3d and moz-webgl as parameters to get a 3D context, but we will assume that the user uses an up to date browser.

WebGL methods correspond to OpenGL methods documented at www.khronos.org/opengles/sdk/docs/man.

Useful WebGLRenderingContext methods:

  • createShader(type) creates an empty shader object of the given type (VERTEX_SHADER or FRAGMENT_SHADER) and returns it’s reference.

  • createProgram() creates an empty program object and returns it’s reference.

  • clearColor(red, green, blue, alpha) sets color for drawing area. Values from 0 to 1, alpha === 1 → opaque, alpha === 0 → fully transparent.

  • shaderSource(shader, source) replaces the source code in a shader object.

  • compileShader(shader) compiles the shader object.

  • attachShader(program, shader) attaches a shader to a program.

  • linkProgram(program) links a program.

  • useProgram(program) installs the program as part of the current rendering state.

  • clearColor(red, green, blue, alpha) specifies clear values for the color buffers.

  • clear(buffer) clears the buffer(s) specified. It takes a single argument. If several buffers are to be cleared it is the bitwise OR of several values from GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT.

  • drawArrays(mode, first, count) renders primitives from array data. The first parameter specifies what kind of primitives to render. Choices are GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN and GL_TRIANGLES.

  • getAttribLocation(program, name) returns the location of an attribute variable.

  • vertexAttrib3f(index, v0, v1, c2) specifies the value of a generic vertex attribute. Similar methods end with 1f, 2f or 4f. From www.khronos.org/opengles/sdk/docs/man:

These commands can be used to specify one, two, three, or all four components of the generic vertex attribute specified by index. A 1 in the name of the command indicates that only one value is passed, and it will be used to modify the first component of the generic vertex attribute. The second and third components will be set to 0, and the fourth component will be set to 1. Similarly, a 2 in the name of the command indicates that values are provided for the first two components, the third component will be set to 0, and the fourth component will be set to 1. A 3 in the name of the command indicates that values are provided for the first three components and the fourth component will be set to 1, whereas a 4 in the name indicates that values are provided for all four components.

  • getUniformLocation(program, name) returns the location of a uniform variable.

  • uniform[1, 2, 3, 4]f(index, v0 [, v1, v2, v3]) specifies the values of a uniform variable.

  • createBuffer() creates a buffer object.

  • deleteBuffer(buffer) deletes a buffer object.

  • bindBuffer(target, buffer) binds a buffer object telling WebGL what type of data it contains. target must be GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.

  • bufferData(target, data, usage) creates and initializes a buffer object’s data store. target must be GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER. usage is one of GL_STREAM_DRAW, GL_STATIC_DRAW, or GL_DYNAMIC_DRAW.

  • vertexAttribPointer(location, size, type, normalized, stride, offset)

WebGL uses typed arrays for maximum performance:

Array Bytes

Int8Array

1

Uint8Array

1

Int16Array

2

Uint16Array

2

Int32Array

4

Uint32Array

4

Float32Array

4

Float64Array

8

To create a typed array, we call the constructor.

Typed arrays have the following methods, properties and constants:

get(index)

set(index, value)

set(array, offset)

length

BYTES_PER_ELEMENT

Let’s look at two simple examples:

WebGLdirect1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>WebGL Example 1</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      let vertexShaderSource, fragmentShaderSource;

      const handleShader = (e, isFragment) => {
        if (isFragment) fragmentShaderSource = e.target.response;
        else vertexShaderSource = e.target.response;
        if (vertexShaderSource && fragmentShaderSource) runShaders();
      };

      const init = () => {
        const req1 = new XMLHttpRequest(), req2 = new XMLHttpRequest();
        req1.open('POST', 'vertex_shader1.js');
        req1.addEventListener('load', e => {
          handleShader(e, false);
        });
        req1.send();
        req2.open('GET', 'fragment_shader1.js');
        req2.addEventListener('load', e => {
          handleShader(e, true);
        });
        req2.send();
      };

      const runShaders = () => {
        const canvas = document.querySelector('canvas');
        let gl;
        try {
          gl = canvas.getContext('webgl');
        } catch (e) {
          alert('Your browser does not seem to support WebGL');
        }
        if (gl) {
          const vertexShader = gl.createShader(gl.VERTEX_SHADER);
          const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
          const program = gl.createProgram();
          gl.shaderSource(vertexShader, vertexShaderSource);
          gl.shaderSource(fragmentShader, fragmentShaderSource);
          gl.compileShader(vertexShader);
          gl.compileShader(fragmentShader);
          gl.attachShader(program, vertexShader);
          gl.attachShader(program, fragmentShader);
          gl.linkProgram(program);
          gl.useProgram(program);
          //gl.program = program;
          gl.clearColor(0, 0, 0, 1);
          /*gl.enable(gl.DEPTH_TEST);
          gl.depthFunc(gl.LEQUAL);*/
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          gl.drawArrays(gl.POINTS, 0, 1);
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <canvas width=640 height=480>No canvas support.</canvas>
    </main>
  </body>
</html>
void main() {
  gl_Position = vec4(0.5, 0.0, 0.0, 1.0);
  gl_PointSize = 10.0;
}
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
WebGLdirect2
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>WebGL Example 1</title>
    <meta charset=utf-8>
    <script>
      "use strict";

      const VSHADER_FILE = 'vertex_shader2.js', HSHADER_FILE = 'fragment_shader2.js';
      let vertexShaderSource, fragmentShaderSource;

      const handleShader = (e, isFragment) => {
        if (isFragment) fragmentShaderSource = e.target.response;
        else vertexShaderSource = e.target.response;
        if (vertexShaderSource && fragmentShaderSource) runShaders();
      };

      const init = () => {
        const req1 = new XMLHttpRequest(), req2 = new XMLHttpRequest();
        req1.open('POST', VSHADER_FILE);
        req1.addEventListener('load', e => {
          handleShader(e, false);
        });
        req1.send();
        req2.open('POST', HSHADER_FILE);
        req2.addEventListener('load', e => {
          handleShader(e, true);
        });
        req2.send();
      };

      const runShaders = () => {
        const canvas = document.querySelector('canvas');
        let gl;
        try {
          gl = canvas.getContext('webgl');
        } catch (e) {
          alert('Your browser does not seem to support WebGL');
        }
        if (gl) {
          const vertexShader = gl.createShader(gl.VERTEX_SHADER);
          const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
          const program = gl.createProgram();
          gl.shaderSource(vertexShader, vertexShaderSource);
          gl.shaderSource(fragmentShader, fragmentShaderSource);
          gl.compileShader(vertexShader);
          gl.compileShader(fragmentShader);
          gl.attachShader(program, vertexShader);
          gl.attachShader(program, fragmentShader);
          gl.linkProgram(program);
          gl.useProgram(program);
          //gl.program = program;
          const a_Position = gl.getAttribLocation(program, 'a_Position');
          gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
          const a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
          gl.vertexAttrib1f(a_PointSize, 5.0);
          gl.clearColor(0, 0, 0, 1);
          /*gl.enable(gl.DEPTH_TEST);
           gl.depthFunc(gl.LEQUAL);*/
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
          gl.drawArrays(gl.POINTS, 0, 1);
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <canvas width=640 height=480>No canvas support.</canvas>
    </main>
  </body>
</html>
attribute vec4 a_Position;
attribute float a_PointSize;

void main() {
  gl_Position = a_Position;
  gl_PointSize = a_PointSize;
}
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
Blender

According to www.blender.org:

Blender is a free and open source 3D animation suite. It supports the entirety of the 3D pipeline—modeling, rigging, animation, simulation, rendering, compositing and motion tracking, even video editing and game creation.

Blender is an extremely powerful application. Unfortunately, it’s user interface is not necessarily the easiest one to master.

Page Visibility
The Page Visibility specification defines a means for site developers to programmatically determine the current visibility of a document and be notified of visibility changes.

This API is very useful for instance in the case of WMOTU Invaders, where we do not want the aliens to continue moving and shooting in the background whilst we are not playing the game! Take a look at WMOTU Invaders object-oriented to see how it’s done. Further details and examples can be found at developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API.

WebAudio

Here is a simple example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Web Audio Example</title>
    <meta charset=utf-8>
    <script src=webaudio1.js></script>
  </head>
  <body>
    <main>
      <input type=button value=Play>
    </main>
  </body>
</html>
"use strict";

let context = null;

const init = () => {
  try {
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    context = new AudioContext();
  }
  catch (e) {
    window.alert('Web Audio API is not supported in this browser.');
  }

  let mybuffer = null;
  const button = document.querySelector('input');
  button.addEventListener('click', () => {
    play(mybuffer);
  });

  if (context) {
    const url = 'gunshot.wav';
    const request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.responseType = "arraybuffer";
    request.addEventListener('load', () => {
      context.decodeAudioData(request.response, buffer => {
        mybuffer = buffer;
      });
      button.disabled = false;
    });
    request.send();
  }
};

const play = buffer => {
  var sourceNode = context.createBufferSource();
  sourceNode.buffer = buffer;
  sourceNode.connect(context.destination);
  sourceNode.start(0);
};

addEventListener("load", init);

4.3.30. Tools

Dealing with old browsers: graceful degradation, polyfills and transpilers

Older browsers don’t support the latest HTML, CSS and JS syntax and features as you can see at kangax.github.io/compat-table/es2016plus and html5please.com. If we want to support old browsers and still use the latest features, we can use polyfills and/or transpilers as described in hackernoon.com/polyfills-everything-you-ever-wanted-to-know-or-maybe-a-bit-less-7c8de164e423. Note that this works to a certain degree but is far from perfect.

www.htmlgoodies.com/beyond/javascript/js-ref/the-2017-guide-to-polyfills.html

www.w3.org/community/webed/wiki/Optimizing_content_for_different_browsers:_the_RIGHT_way

outdatedbrowser.com

browser-update.org

developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent

There are ways to change the user agent string that our browser sends to the server: www.howtogeek.com/113439/how-to-change-your-browsers-user-agent-without-installing-any-extensions

Minimizers, optimizers, obfuscators, deobfuscation, compressors and beautifiers

Obfuscation means making the code unreadable for human beings whilst preserving its function. A short but insightful article on obfuscation can be found at blog.qburst.com/2012/10/dont-tell-what-you-are-capable-of-javascript-obfuscation. An excellent survey of obfuscation techniques can be found in www.cse.psu.edu/~szhu/papers/malware.pdf. A good overview of minification resources can be found at developers.google.com/speed/docs/insights/MinifyResources#overview.

A list of JS performance improvement techniques can be found at kongaraju.blogspot.lu/2016/05/101-javascript-performance-improvement.html.

Deobfuscation

Obfuscated code that uses some form of encryption usually starts with eval and can be deobfuscated by replacing eval with for instance alert or console.log. Here is a simple deobfuscator for encrypted JavaScript:

<html>
  <head>
    <title>JavaScript Decoder</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <div>
      <textarea id="inputTA" rows="20" cols="150"></textarea>
    </div>
    <div>
      <button onclick="DecodeJS()">Decode</button>
    </div>
    <div>
      <textarea id="outputTA" rows="20" cols="150"></textarea>
    </div>
    <script>
      'use strict';

      const DecodeJS = () => {
        const str = document.getElementById("inputTA").value;
        if (str.slice(0, 4) === 'eval') {
          eval(`let value = String${str.slice(4)}`);
          document.getElementById("outputTA").value = value;
        }
        else {
          const str1 = '\x3c\x61\x20\x69\x64\x3d\x22\x73\x75\x67\x67\x65\x73\x74\x22\x20\x68' +
            '\x72\x65\x66\x3d\x22\x23\x22\x20\x61\x6a\x61\x78\x69\x66\x79\x3d\x22\x2f\x61' +
            '\x6a\x61\x78\x2f\x73\x6f\x63\x69\x61\x6c\x5f\x67\x72\x61\x70\x68\x2f\x69\x6e' +
            '\x76\x69\x74\x65\x5f\x64\x69\x61\x6c\x6f\x67\x2e\x70\x68\x70\x3f\x63\x6c\x61' +
            '\x73\x73\x3d\x46\x61\x6e\x4d\x61\x6e\x61\x67\x65\x72\x26\x61\x6d\x70\x3b\x6e' +
            '\x6f\x64\x65\x5f\x69\x64\x3d\x31\x30\x38\x34\x36\x33\x39\x31\x32\x35\x30\x35' +
            '\x33\x35\x36\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x20\x70\x72\x6f\x66\x69\x6c' +
            '\x65\x5f\x61\x63\x74\x69\x6f\x6e\x20\x61\x63\x74\x69\x6f\x6e\x73\x70\x72\x6f' +
            '\x5f\x61\x22\x20\x72\x65\x6c\x3d\x22\x64\x69\x61\x6c\x6f\x67\x2d\x70\x6f\x73' +
            '\x74\x22\x3e\x53\x75\x67\x67\x65\x73\x74\x20\x74\x6f\x20\x46\x72\x69\x65\x6e' +
            '\x64\x73\x3c\x2f\x61\x3e","\x73\x75\x67\x67\x65\x73\x74';
          console.log(str1);
        }
      };
    </script>
  </body>
</html>
Online JavaScript beautifier

The best tool to beautify, unpack or deobfuscate JavaScript and HTML cn be found at jsbeautifier.org.

Another very useful site is codebeautify.org.

Google Closure Compiler
The Closure Compiler is a tool for making JavaScript download and run faster. It is a true compiler for JavaScript. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what’s left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.

Be careful with the advanced mode, as it will often break your script. Careful testing of the compiled script is recommended.

Esmangle
Esmangle is mangler / minifier for Mozilla Parser API AST.
UglifyJS
UglifyJS is a JavaScript compressor/minifier written in JavaScript.
JavaScript Utility

The tool at jsutility.pjoneil.net allows the testing, validating, formatting, obfuscating, compacting and compressing of JavaScript code.

Dean Edwards Packer

dean.edwards.name/packer allows to easily reduce the size of your script by shrinking variable names. Be careful with base 62 encoding, as it can easily break your script. Careful testing of the packed script is recommended.

Editor components and online editors
JSFiddle
CodeMirror
CodeMirror is a code-editor component that can be embedded in Web pages.

It is the component that I’ve used in the training section of cliss.foxi.lu.

ACE

From ace.c9.io:

Ace is an embeddable code editor written in JavaScript.
Cloud-based editors

www.hongkiat.com/blog/cloud-ide-developers provides a good overview of the rapidly evolving cloud IDE space.

4.3.32. Frameworks

jQuery

From jquery.com:

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.

The API documentation can be found at api.jquery.com.

Pros and cons

For a description of the advantages offered by jQuery, have a look at www.w3schools.com/jquery/jquery_intro.asp. Drawbacks include the inclusion of additional code (jquery-2.1.1.min.js has a file size of 82 KB), the slower execution speed due to the additional compatibility and ease of use translations under the hood, the large amount of warnings that appear in the console and the need to learn another syntax.

Download

jQuery is available in versions 1.x and 3.x, compressed and uncompressed. The difference between versions 1.x and 3.x is that the former support Internet Explorer going back to version 6, whereas Internet Explorer support in the latter only goes back to version 9 (cf. jquery.com/browser-support).

To use jQuery, we can download the version we want from the site or have our HTML document retrieve it from a content delivery network (CDN) at runtime, as explained at jquery.com/download with links for the different jQuery versions at code.jquery.com. Be careful to avoid "slim" versions, as they exclude AJAX and effects (blog.jquery.com/2016/06/09/jquery-3-0-final-released).

Let’s look at these 2 options:

<script src=jquery-3.1.0.min.js></script>
<script src="//code.jquery.com/jquery-3.1.0.min.js"></script>
Selecting elements

The basic principle of jQuery is to select some elements and then do something with them. For this purpose, we can use the $(selector) or jQuery(selector) functions. These two are identical. The former one is used most often as it is shorter. It may however pose problems when we try to use several frameworks, with another framework also defining a global $(selector) function. In this case, we can use jQuery(selector) to avoid any conflicts. The selector passed as parameter is a standard CSS selector (cf. Selectors). See www.w3schools.com/jquery/jquery_selectors.asp for examples. The resulting set of elements is a jQuery object, which is very easy to work with.

Changing the DOM

See the documentation starting with www.w3schools.com/jquery/jquery_dom_get.asp.

DOM traversal

www.w3schools.com/jquery/jquery_ref_traversing.asp provides a great overview of the numerous jQuery DOM traversal methods.

AJAX

load

$(selector).load(URL [, data] [, callback]) is a very easy to use method to load data from the server directly into an HTML element (cf. api.jquery.com/load).

post

$.post(url [, data] [, success] [, dataType]) loads data from a server using a HTTP POST request (cf. api.jquery.com/jQuery.post).

get

$.get(url [, data] [, success] [, dataType]) loads data from a server using a HTTP GET request (cf. api.jquery.com/jQuery.get).

getJSON

$.getJSON(url [, data] [, success]) loads JSON-encoded data from a server using a GET HTTP request (cf. api.jquery.com/jQuery.getJSON).

ajax

Differentiating between AJAX and standard form requests

jQuery adds a X_Requested_With header to every AJAX request. This allows our server script to detect whether data comes from an AJAX request or a standard form submission. To see the difference, you can run the test page jQAJAXTest.html, analyze the server response and compare it with the AJAX response received in the main example jQAJAX1.html:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>jQuery AJAX example 1</title>
    <meta charset=utf-8>
    <script src=jquery-2.1.1.min.js></script>
    <!--<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>-->
    <script>
      'use strict';

      const init = () => {
        // First we use jQuery to add an event listener to the first button.
        $('button:eq(0)').on('click', () => {
          $('section').load('jQAJAX1_text.txt');
        });
        // Then we use the conventional approach for the others.
        const buttons = document.querySelectorAll('button');
        buttons[1].addEventListener('click', () => {
          $('section').load('jQAJAX1_html.html');
        });
        buttons[2].addEventListener('click', () => {
          $('section').load('jQAJAX1_html.html #p2');
        });
        buttons[3].addEventListener('click', () => {
          $('section').load('jQAJAX1_html.html', (response, statusText, req) => {
            alert('Status: ' + statusText);
          });
        });
        buttons[4].addEventListener('click', () => {
          $('section').load('jQAJAX1.php', {
            "first_name": "Donald",
            "last_name": "Duck"
          });
        });
        buttons[5].addEventListener('click', () => {
          $('section').load('jQAJAX1.php', {
            "first_name": $('#i1')[0].value,
            "last_name": $('#i2')[0].value
          });
        });
        buttons[6].addEventListener('click', () => {
          $.post('jQAJAX2.php', {"first_name": "Donald"}, result => {
            $('section').html(result);
          });
        });
        buttons[7].addEventListener('click', () => {
          $.getJSON('jQAJAX1.json', data => {
            console.dir(data);
            alert(`The last name of ${data[0]['first name']} is ${data[0]['last name']}`);
          })
        });
        buttons[8].addEventListener('click', () => {
          $.getScript('jQAJAX1.js');
        });
      }

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <button>Load text</button>
      <button>Load HTML</button>
      <button>Load HTML part 2</button>
      <button>Load HTML with callback</button>
      <button>Load greeting from server for constant first and last name</button>
      <br>
      <input id=i1 placeholder='First name'>
      <input id=i2 placeholder='Last name'>
      <button>Load greeting from server for given first and last name</button>
      <br>
      <button>Post first name and get last name from server via callback</button>
      <button>Get JSON data from file</button>
      <button>Execute script</button>
      <section></section>
    </main>
  </body>
</html>
<?php
  $AJAX = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']
    === 'XMLHttpRequest';

  if (!$AJAX) {
?>
  <!DOCTYPE html>
<html lang=en>
  <head>
    <title>Full document</title>
    <meta charset=utf-8>
  </head>
  <body>
    <?php
      }
      if (isset($_POST['first_name'], $_POST['last_name']))
        echo '<h1>Hello ' . $_POST['first_name'] . ' ' .$_POST['last_name'] . '</h1>';
      if (!$AJAX) {
    ?>
  </body>
</html>
<?php
  }
?>
[
    {
        "first name": "Donald",
        "last name": "Duck",
        "age": 35
    },
    {
        "first name": "Mickey",
        "last name": "Mouse",
        "age": 30
    }
]
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>jQuery AJAX test AJAX/simple form differentation</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <main>
      <form method=post action=jQAJAX1.php>
        <input name=first_name>
        <input type=submit>
      </form>
    </main>
  </body>
</html>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>jQuery AJAX example 2</title>
    <meta charset=UTF-8>
    <script src=jquery-2.1.1.min.js></script>
    <!--<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>-->
    <script>
      'use strict';

      const init = () => {
        $('form').on('submit', function(e) {
          e.preventDefault();
          $.post('jQAJAX2.php', $(this).serialize(), result => {
            $('section').text(`The result is ${result}`);
          });
        });
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <form>
      <input name=first_name value=Donald>
      <input type=submit>
    </form>
    <section></section>
  </body>
</html>
<?php
  if (isset($_POST['first_name'])) {
    $names = array('Donald' => 'Duck', 'Mickey' => 'Mouse');
    if (isset($names[$_POST['first_name']])) echo $names[$_POST['first_name']];
  }
?>
Qooxdoo

4.3.34. JSDoc

JSDoc is a markup language used to annotate JavaScript source code files.

JSDoc annotations are embedded within /** and */. See the URL or usejsdoc.org for details.

In Node.js you can install jsdoc using npm i -g jsdoc.

4.3.36. Problems

Show/hide HTML element
show hide element

Write a page that displays an image and a button. Clicking on the button makes the image appear, clicking it again makes the image disappear.

Color preview
color preview

Write an app that displays three sliders, one for red, one for green and one for blue. The body background color is always the color of the currently selected red, green and blue values, each one between 0 and 255. The current value of each slider is shown.

Puzzle

Write an app that randomly selects a picture from a directory and cuts it in a random number of pieces. It then shows a random piece to the user who has to place it on the right spot in the solution area. The user can rearrange the pieces of the solution area at any time.

Path tracker

The path tracker mobile app provides a start and a stop button. After the start button has been pressed, the app records the current position of the device every second and stores it. When the stop button is pressed, the user is shown a map with his itinerary and some statistical information, e.g. time taken, average speed, etc.

Paint app

Write a paint app, that allows the drawing of basic shapes and text as well as freehand. Drawings can be saved.

4.3.37. Problem solutions

Show/hide HTML element

Watch the solution video:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Show/hide element</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      let isVisible = false;

      const toggleImage = () => {
        if (!isVisible) {
          document.querySelector('button').innerHTML = "Hide image";
          document.querySelector('img').style.display = "block";
        } else {
          document.querySelector('button').innerHTML = "Show image";
          document.querySelector('img').style.display = "none";
        }
        isVisible = !isVisible;
      };

      const init = () => {
        document.querySelector('button').addEventListener('click', toggleImage);
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <button>Show image</button>
      <img src=Robot_Santa130x256.png width=130 height=256 alt=Robot_Santa130x256.png
           style="display: none">
    </main>
  </body>
</html>
Color preview

Watch the solution video:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Color preview</title>
    <meta charset=utf-8>
    <style>
      span {
        text-shadow: white 1px 1px;
      }
    </style>
    <script>
      'use strict';

      let inputs, spans;

      const updateDisplay = () => {
        const red = inputs[0].value, green = inputs[1].value, blue = inputs[2].value;
        document.body.style.backgroundColor = `rgb(${red}, ${green}, ${blue})`;
        for (let i = 0; i < inputs.length; i++) spans[i].innerHTML = inputs[i].value;
      };

      const init = () => {
        inputs = document.querySelectorAll('input');
        spans = document.querySelectorAll('span');
        for (let i = 0; i < inputs.length; i++)
          inputs[i].addEventListener('change', updateDisplay);
        updateDisplay();
      }

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <input type=range min=0 max=255 value=0 style='background-color: red'><span></span><br>
      <input type=range min=0 max=255 value=0 style='background-color: green'><span></span><br>
      <input type=range min=0 max=255 value=0 style='background-color: blue'><span></span>
    </main>
  </body>
</html>

4.3.38. Tests

Currency Converter
CurrencyConverter1

Create this currency converter:

The user can choose EUR, USD, GBP, JPY or CHF. Get the current exchange rates from the Internet. Currency names and values are stored in arrays.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Currency Converter</title>
    <meta charset=UTF-8>
    <style>
      section {
        background-color: #EEE;
        width:            250px;
        padding:          10px;
      }

      div {
        margin-top: 10px;
      }

      label {
        float:         left;
        width:         80px;
        text-align:    right;
        padding-right: 10px;
      }

      button {
        margin-left: 90px;
      }
    </style>
    <script>
      'use strict';

      let sourceSelect, destinationSelect;
      const arCurrencyName = ["EUR", "USD", "GBP", "JPY", "CHF"];
      const arCurrencyQuote = [1, 0.76, 1.23, 0.0087, 0.83];

      const init = () => {
        sourceSelect = document.getElementById("sourceSelect");
        destinationSelect = document.getElementById("destinationSelect");
        for (let i = 0; i < arCurrencyName.length; i++) {
          sourceSelect.options[i].text = arCurrencyName[i];
          sourceSelect.options[i].value = arCurrencyName[i];
          destinationSelect.options[i].text = arCurrencyName[i];
          destinationSelect.options[i].value = arCurrencyName[i];
        }
      };

      const convert = () => {
        const amountInput = document.querySelector("input");
        const sourceIndex = sourceSelect.selectedIndex;
        const destinationIndex = destinationSelect.selectedIndex;
        const sourceQuote = arCurrencyQuote[sourceIndex];
        const destinationQuote = arCurrencyQuote[destinationIndex];
        amountInput.value *= sourceQuote / destinationQuote;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <h1>Currency converter</h1>
    <section>
      <div>
        <label>Source:</label>
        <select id=sourceSelect>
          <option></option>
          <option></option>
          <option></option>
          <option></option>
          <option></option>
        </select>
      </div>
      <div>
        <label>Destination:</label>
        <select id=destinationSelect>
          <option></option>
          <option></option>
          <option></option>
          <option></option>
          <option></option>
        </select>
      </div>
      <div>
        <label>Amount:</label>
        <input>
      </div>
      <div>
        <button onclick=convert();>Convert</button>
      </div>
    </section>
  </body>
</html>
Pure JS solution

Here is a solution that illustrates how we can create the whole calculator in JS:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Currency Converter</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let sourceSelect, destinationSelect;
      const arCurrencyName = ["EUR", "USD", "GBP", "JPY", "CHF"];
      const arCurrencyQuote = [1, 0.76, 1.23, 0.0087, 0.83];

      const init = () => {
        const h1 = document.createElement("h1");
        h1.innerHTML = "Currency converter";
        document.body.appendChild(h1);
        const section = document.createElement("section");
        section.style.cssText = "background-color: #EEE; width: 250px; padding: 10px;";

        // Source div
        const div1 = document.createElement("div");
        div1.style.cssText = "margin-top: 10px;";
        const l1 = document.createElement("label");
        l1.style.cssText = "float: left; width: 80px; text-align: right; padding-right: 10px;";
        l1.innerHTML = "Source:";
        div1.appendChild(l1);
        const select1 = document.createElement("select");
        select1.id = "sourceSelect";
        for (let i = 1; i <= 5; i++) select1.appendChild(document.createElement("option"));
        div1.appendChild(select1);

        // Destination div
        const div2 = document.createElement("div");
        div2.style.cssText = "margin-top: 10px;";
        const l2 = document.createElement("label");
        l2.style.cssText = "float: left; width: 80px; text-align: right; padding-right: 10px;";
        l2.innerHTML = "Destination:";
        div2.appendChild(l2);
        const select2 = document.createElement("select");
        select2.id = "destinationSelect";
        for (let i = 1; i <= 5; i++) select2.appendChild(document.createElement("option"));
        div2.appendChild(select2);

        // Amount div
        const div3 = document.createElement("div");
        div3.style.cssText = "margin-top: 10px;";
        const l3 = document.createElement("label");
        l3.style.cssText = "float: left; width: 80px; text-align: right; padding-right: 10px;";
        l3.innerHTML = "Amount:";
        div3.appendChild(l3);
        div3.appendChild(document.createElement("input"));

        // Convert div
        const div4 = document.createElement("div");
        div4.style.cssText = "margin-top: 10px;";
        const button = document.createElement("button");
        button.innerHTML = "Convert";
        button.onclick = convert;
        button.style.cssText = "margin-left: 90px;";
        div4.appendChild(button);

        // Assemble divs into section.
        section.appendChild(div1);
        section.appendChild(div2);
        section.appendChild(div3);
        section.appendChild(div4);
        document.body.appendChild(section);

        sourceSelect = document.getElementById("sourceSelect");
        destinationSelect = document.getElementById("destinationSelect");
        for (let i = 0; i < arCurrencyName.length; i++) {
          sourceSelect.options[i].text = arCurrencyName[i];
          sourceSelect.options[i].value = arCurrencyName[i];
          destinationSelect.options[i].text = arCurrencyName[i];
          destinationSelect.options[i].value = arCurrencyName[i];
        }
      };

      const convert = () => {
        const amountInput = document.querySelector("input");
        const sourceIndex = sourceSelect.selectedIndex;
        const destinationIndex = destinationSelect.selectedIndex;
        const sourceQuote = arCurrencyQuote[sourceIndex];
        const destinationQuote = arCurrencyQuote[destinationIndex];
        amountInput.value *= sourceQuote / destinationQuote;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body></body>
</html>
Space Ship
SpaceShip1

Create the page shown at youtu.be/lCOYnvVK6vY taking the following into account:

  1. Use the skeleton at students.btsi.lu/evegi144/WAD/JS/Tests/SpaceShip/index.html.

  2. Create an array with 50 random integers from [1, 200]. If a calculated integer is divisible by 5, it will be doubled. For example, if the random integer is 15, 30 will be the value stored in the array.

  3. All array elements are inserted into the drop down list. For this step you may not use more than 30 instructions.

  4. The integer selected in the drop down list determines the step size the space ship moves when one of the 4 arrows is clicked.

  5. Obviously the space ship may not cross the boundaries of the universe. If it were to based on the step size, it will be placed at the corresponding border.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Space Ship</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let space, spaceShip, spaceShipWidth, spaceShipHeight, stepSelect, optionsArray;

      const init = () => {
        space = document.querySelector('main');
        spaceShip = document.getElementById("spaceShip");
        spaceShipWidth = spaceShipHeight = 256;
        stepSelect = document.querySelector("select");
        for (let i = 1; i <= 50; i++) stepSelect.appendChild(document.createElement('option'));
        optionsArray = [];
        for (let i = 0; i < 50; i++) {
          optionsArray[i] = Math.floor(Math.random() * 200) + 1;
          if (optionsArray[i] % 5 === 0) optionsArray[i] = optionsArray[i] * 2;
          stepSelect.options[i].text = optionsArray[i];
        }
      };

      const moveLeft = () => {
        const left = parseInt(spaceShip.style.left);
        const step = stepSelect.options[stepSelect.selectedIndex].text;

        if (left - step < 0) spaceShip.style.left = "0px";
        else spaceShip.style.left = left - step + "px";
      };

      const moveUp = () => {
        const top = parseInt(spaceShip.style.top);
        const step = optionsArray[stepSelect.selectedIndex];

        if (top - step < 0) spaceShip.style.top = "0px";
        else spaceShip.style.top = top - step + "px";
      };

      const moveRight = () => {
        const left = parseInt(spaceShip.style.left);
        const step = optionsArray[stepSelect.selectedIndex];

        if ((left + step + spaceShipWidth) > space.offsetWidth)
          spaceShip.style.left = space.offsetWidth - spaceShipWidth + "px";
        else spaceShip.style.left = left + step + "px";
      };

      const moveDown = () => {
        const top = parseInt(spaceShip.style.top);
        const step = optionsArray[stepSelect.selectedIndex];

        if (top + step + spaceShipHeight > space.offsetHeight)
          spaceShip.style.top = space.offsetHeight - spaceShipHeight + "px";
        else spaceShip.style.top = top + step + "px";
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <header
      style="position: absolute; left: 0; top: 0; width: 100%; height: 100px;
      background-color: gold">
      <div style="position: absolute; left: 0; top: 0; width: 150px; height: 90px;">
        <img src=arrowLeft21x29.png onclick='moveLeft();' alt=arrowLeft21x29.png
             style="position: absolute; top: 30px; left: 5px;">
        <img src=arrowUp29x21.png onclick='moveUp();' alt=arrowUp29x21.png
             style="position: absolute; top: 5px; left: 30px;">
        <img src=arrowRight21x29.png onclick='moveRight();' alt=arrowRight21x29.png
             style="position: absolute; top: 30px; left: 63px;">
        <img src=arrowDown29x21.png onclick='moveDown();' alt=arrowDown29x21.png
             style="position: absolute; top: 63px; left: 30px">
      </div>
      <div style="position: absolute; left: 120px; top: 30px; height: 90px;">
        <label style="font-family: sans-serif;">Step (pixel): </label>
        <select style="background-color: gold;"></select>
      </div>
      <div style="position: absolute; top: 0; left: 400px; font-family: sans-serif;
             font-size: 400%">Space Ship
      </div>
    </header>
    <main style="position: absolute; left: 0; top: 90px; right: 0; bottom: 0;
             background: linear-gradient(to bottom right, gold, black) fixed;">
      <img id=spaceShip src=spaceship256x256.png
           style="position: absolute; left: 0; top: 0;" alt=spaceship256x256.png>
    </main>
  </body>
</html>
Space Circuit
SpaceCircuit1

Create the page shown at youtu.be/axrTXBHDbXQ taking the following into account:

  1. Use the skeleton at students.btsi.lu/evegi144/WAD/JS/Tests/SpaceCircuit/index.html.

  2. Create an empty array pointXArray as well as an array pointYArray. The latter gets filled with the values at the end of the skeleton.

  3. Create function fillXArray, which does the following:

    1. Define a variable xOffset with a random integer from [1, 400].

    2. pointXArray is filled with values as follows:

      1. Positions 0 to 9 get the value xOffset + pos * 40, with pos representing the position in the array.

      2. Positions 10 to 19 get the value xOffset + 400 - (pos -10) * 40.

  4. Clicking the button changes its text to "Stop" und calls fillXArray. The selected car will then run through the X- and Y-positions stored in the arrays, with a time interval of 100 ms. When the end of the array is reached, positions start again at the beginning.

  5. Clicking the button again changes the text back to "Loop" and the car animation stops.

  6. Clicking the button again …​

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Space Circuit</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let car, carWidth = 256, carHeight = 256, loopButton, carSelect, pointXArray = [];
      const pointYArray = [200, 160, 120, 80, 40, 0, 40, 80, 120, 160, 200, 240, 280,
        320, 360, 400, 360, 320, 280, 240];
      let idx = 0, arrayPoints = 20, timeout = 100, timerID;

      const init = () => {
        loopButton = document.querySelector('button');
        car = document.getElementById("car");
        carSelect = document.querySelector('select');
        selected();
      };

      const fillXArray = () => {
        const xOffset = Math.floor(Math.random() * 400) + 1;
        for (let i = 0; i < arrayPoints / 2; i++) pointXArray[i] = xOffset + i * 40;
        for (let i = arrayPoints / 2; i < arrayPoints; i++)
          pointXArray[i] = xOffset + 400 - (i - 10) * 40;
      };

      const selected = () => {
        car.src = carSelect.options[carSelect.selectedIndex].value;
        car.alt = carSelect.options[carSelect.selectedIndex].value;
      };

      const loop = () => {
        if (idx >= arrayPoints) idx = 0;
        car.style.left = pointXArray[idx] + "px";
        car.style.top = pointYArray[idx++] + "px";
      };

      const startLoop = () => {
        fillXArray();
        timerID = setInterval(loop, timeout);
        loopButton.innerHTML = "Stop";
        loopButton.onclick = stopLoop;
      };

      const stopLoop = () => {
        clearInterval(timerID);
        loopButton.innerHTML = "Loop";
        loopButton.onclick = startLoop;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;
             background: radial-gradient(rgb(20, 50, 20), rgb(60, 255, 60), rgb(20, 50,20),
             black) fixed;">
      <div style="position: absolute; left: 0; top: 0; width: 100%; height: 20px;
            opacity: 0.75;">
        <div style="position: absolute; left: 0; top: 0; height: 90px;">
          <select style="opacity: 0.75;" onchange=selected();>
            <option value=camaro256x256.png>Camaro</option>
            <option value=ferrari256x256.png>Ferrari</option>
            <option value=cabrio256x256.png>Cabrio</option>
          </select>
          <button onclick=startLoop();>Loop</button>
        </div>
        <div style="position: absolute; top: 0; left: 400px; color: lightblue;
                font-family: sans-serif; font-size: 400%">Space Circuit
        </div>
      </div>
      <div style="position: absolute; left: 0; top: 90px; right: 0; bottom: 0;">
        <img id=car src=camaro256x256.png style="position: absolute; left: 200px;
                 top: 200px;" alt=camaro256x256.png>
      </div>
    </div>
  </body>
</html>
Targeting Practice
TargetingPractice1
Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Targeting Practice</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let timerID, timeout = 20, battleFieldHeight = 630;
      let yPosArray = [], arraySize = 100, currIdx = 0, currDirection = 1;
      let button, target, targetHeight = 91, tank, tankHeight = 66, select, tankStep;

      const init = () => {
        button = document.querySelector('button');
        target = document.getElementById("target");
        tank = document.getElementById("tank");
        select = document.querySelector("select");
        tankStep = parseInt(select.options[select.selectedIndex].text);
        for (let i = 0; i < arraySize; i++)
          yPosArray[i] = Math.floor(Math.random() * (5 * i + 1));
        addEventListener('keydown', keyHandler);
      };

      const selectChange = () => {
        tankStep = parseInt(select.options[select.selectedIndex].text);
        select.blur();
      };

      const moveTarget = () => {
        target.style.top = yPosArray[currIdx] + "px";
        if ((currIdx >= arraySize - 1) || (currIdx <= 0 && currDirection === -1))
          currDirection = -currDirection;
        currIdx += currDirection;
      };

      const start = () => {
        timerID = setInterval("moveTarget()", timeout);
        button.innerHTML = "Stop";
        button.onclick = stop;
      };

      const stop = () => {
        clearInterval(timerID);
        button.innerHTML = "Start";
        button.onclick = start;
      };

      const keyHandler = event => {
        const top = parseInt(tank.style.top);
        if (event.keyCode === 38) // up
          if (top - tankStep < 0) tank.style.top = "0px";
          else tank.style.top = top - tankStep + "px";
        if (event.keyCode === 40) // down
          if (top + tankStep + tankHeight > battleFieldHeight)
            tank.style.top = battleFieldHeight - tankHeight + "px";
          else tank.style.top = top + tankStep + "px";
        if (event.keyCode === 70) // F for fire
          if ((parseInt(target.style.top) <= top) && (parseInt(target.style.top) +
              targetHeight >= top)) alert("Hit!");
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <button onclick=start();>Start</button>
    <select onchange=selectChange();>
      <option>10</option>
      <option>25</option>
      <option>50</option>
    </select>
    <div style="background: linear-gradient(to bottom right, white, green) fixed;
             position: absolute; top: 35px; left: 0; right: 0; bottom: 0; width: 800px;
             height: 630px;">
      <img id=tank src=tank128x66.png alt=tank128x66.png width=128 height=66
           style="position: absolute; left: 650px; top: 0;">
      <img id=target src=target91x91.png alt=target91x91.png width=91 height=91
           style="position: absolute; left: 0; top: 0;">
    </div>
  </body>
</html>
Hockenheim Ring
HockenheimRing1
Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Hockenheim Ring</title>
    <meta charset=UTF-8>
    <style>
      label {
        float:         left;
        width:         15px;
        text-align:    right;
        padding-right: 4px;
      }
    </style>
    <script>
      'use strict';

      let mouseXArray = [], mouseYArray = [], record = false, currCarXIdx = 0, currCarYIdx = 0;
      let timerID, button, car, name;

      const init = () => {
        button = document.getElementById("startStopButton");
        car = document.getElementById("car");
        name = prompt("Please enter your name:");
        alert(`Welcome ${name} to the Hockenheim Ring. Today is ` +
          `${(new Date()).toLocaleString()} Enjoy!`);
        addEventListener('keyup', handleKey);
      };

      const handleKey = event => {
        if (event.keyCode === 83) { // S key
          record = !record;
          if (record) document.onmousemove = getMouseXY;
          else document.onmousemove = null;
        }
      };

      const getMouseXY = e => {
        const mouseX = e.pageX;
        const mouseY = e.pageY;
        document.getElementById("mouseX").value = mouseX;
        document.getElementById("mouseY").value = mouseY;
        mouseXArray.push(mouseX);
        mouseYArray.push(mouseY);
      };

      const animate = () => {
        if (currCarXIdx < mouseXArray.length) {
          car.style.left = mouseXArray[currCarXIdx++] - 64 + "px";
          car.style.top = mouseYArray[currCarYIdx++] - 42 + "px";
        }
      };

      const startAnimation = () => {
        timerID = setInterval(animate, 10);
        button.innerHTML = "Stop";
        button.onclick = stopAnimation;
      };

      const stopAnimation = () => {
        clearInterval(timerID);
        button.innerHTML = "Vroam";
        button.onclick = startAnimation;
      };

      addEventListener('load', init);
    </script>
  </head>
  <body
    style="background: url('Hockenheim20121052x744.svg') no-repeat;
     background-color: lightgreen;">
    <div>
      <label>X:</label>
      <input id=mouseX value=0 size=4 style="background-color: yellow" readonly>
    </div>
    <div>
      <label>Y:</label>
      <input id=mouseY value=0 size=4 style="background-color: yellow" readonly>
    </div>
    <button id=startStopButton onclick=startAnimation(); style="margin-top: 10px;">Vroam
    </button>
    <img id=car src=vroum128x84.png width=128 height=84 alt=vroum128x84.png
         style="position: absolute">
  </body>
</html>
Football Magic
FootballMagic1

Create a page (youtu.be/v5zB0ecaCok) with a button and a football (https://foxi.ltam.lu/PROF/evegi144/T2IF2_WSERS/WAD/JS/Tests/FootballMagic/ football352x352.png). The CSS for the body background is background: linear-gradient(darkgreen, lightgreen) fixed;.

After the document has loaded, an array with 10 randomly generated football positions (x from [0, 600[ and y from [0, 300[) is created.

When the button is clicked, it changes its text from 'Start' to 'Stop' and the ball jumps to the first position in the array, then after 20 ms to the second, after another 20 ms to the third etc. When the ball has reached the last array position, it moves to the first, then the second and so on.

When the button is clicked again, the animation stops and the button text changes back to 'Start'. Clicking it again resumes the animation from the position where it was stopped and changes the text to 'Stop'.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Football Magic</title>
    <meta charset=utf-8>
    <style>
      body {
        background: linear-gradient(darkgreen, lightgreen) fixed;
      }

      img {
        position: relative;
      }
    </style>
    <script>
      'use strict';

      let timerHandle, positions = [], currIdx = 0;

      const init = () => {
        for (let i = 0; i < 10; i++)
          positions.push([Math.floor(Math.random() * 600), Math.floor(Math.random() * 300)]);
        document.querySelector('button').addEventListener('click', buttonHandle);
      };

      const move = () => {
        const ball = document.querySelector('img');
        ball.style.left = positions[currIdx][0] + "px";
        ball.style.top = positions[currIdx][1] + "px";
        currIdx++;
        if (currIdx >= positions.length) currIdx = 0;
      };

      const buttonHandle = () => {
        if (timerHandle) {
          clearInterval(timerHandle);
          timerHandle = undefined;
          document.querySelector('button').innerHTML = "Start";
        }
        else {
          timerHandle = setInterval(move, 20);
          document.querySelector('button').innerHTML = "Stop";
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <header>
      <button>Start</button>
    </header>
    <main>
      <img src=football352x352.png alt=football352x352>
    </main>
  </body>
</html>
Football Magic v2
FootballMagicv21

Create the web page shown at youtu.be/mRUPjjmpehM taking the following into account:

  1. The football field has a green background, starts 50 pixels below the upper border and always uses the full window width as well as the remaining window height (i.e. height - 50). To achieve this you need to style the top, right, bottom and left distances of the football field. You can use the offsetWidth and offsetHeight attributes of the football field.

  2. The header includes a label, an input as well as two button elements, only one of which is visible at any point in time.

  3. After the start button has been clicked, the ball moves every 20 ms a certain amount of pixels both horizontally and vertically. The pixel number is calculated as a random number between 1 and the value the user has specified in the input. If the ball were to move beyond any of the borders, it will be placed at this border and the corresponding direction (vertical or horizontal) changed.

  4. After the page has loaded, the user is asked for the password CLISS1 using the text "Please enter the magic word.". The password will be asked repeatedly until the user enters the correct one. Only thereafter can the animation be started.

  5. Your page must pass the HTML5-validator without errors.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Football Magic v2</title>
    <meta charset=UTF-8>
    <script>
      'use strict';

      let football, directionX = 1, directionY = 1, timerID, timeout = 20;
      let footballField, footballWidth = 352, footballHeight = 352;

      const init = () => {
        football = document.querySelector('img');
        footballField = document.querySelector('main');
        document.getElementById("startButton").addEventListener('click', start);
        document.getElementById("stopButton").addEventListener('click', stop);
        checkPassword();
      };

      const checkPassword = () => {
        const text = "Please enter the magic word:";
        let password = prompt(text);
        while (password !== "CLISS1") password = prompt(text);
      };

      const timer = () => {
        const maxStep = document.querySelector('input').value;
        const step = Math.floor(Math.random() * maxStep) + 1;
        const left = parseInt(football.style.left), top = parseInt(football.style.top);

        if (left + step * directionX < 0) {
          football.style.left = "0";
          directionX = 1;
        }
        else if (left + step * directionX + footballWidth > footballField.offsetWidth) {
          football.style.left = footballField.offsetWidth - footballWidth + "px";
          directionX = -1;
        }
        else football.style.left = left + step * directionX + "px";
        if (top + step * directionY < 0) {
          football.style.top = "0";
          directionY = 1;
        }
        else if (top + step * directionY + footballHeight > footballField.offsetHeight) {
          football.style.top = footballField.offsetHeight - footballHeight + "px";
          directionY = -1;
        }
        else football.style.top = top + step * directionY + "px";
      };

      const start = () => {
        document.getElementById("startButton").style.display = "none";
        document.getElementById("stopButton").style.display = "inline";
        timerID = setInterval(timer, timeout);
      };

      const stop = () => {
        document.getElementById("startButton").style.display = "inline";
        document.getElementById("stopButton").style.display = "none";
        clearInterval(timerID);
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <header>
      <label for=inp>Max speed (pixels / 20 ms):</label>
      <input id=inp value=10>
      <button id=startButton>Start</button>
      <button id=stopButton style="display: none">Stop</button>
    </header>
    <main style="background-color: green; left: 0; top: 50px; right: 0; bottom: 0;
             position: absolute">
      <img src=football352x352.png alt=football352x352.png width=352 height=352
           style="position: absolute; left: 0; top: 0;">
    </main>
  </body>
</html>
Calculator
Calculator1

Create the web page shown at youtu.be/Fw7PvkvrYss taking the following into account:

  1. Use the skeleton at students.btsi.lu/evegi144/WAD/JS/Tests/Calculator/index.html.

  2. MC stands for memory clear, MR for memory read and MS for memory store. The calculator has two independent memories with initial value 0.

  3. The binary operators +, -, * and % work with the two upper inputs fields and write the result into the lower text field. The unary operators uses the upper input field and writes the result there too.

  4. The factorial function checks whether the value of the input value is larger than 1000. If so, nothing happens.

  5. After the page has loaded, the user is asked for the password CLISS1 using the text "Please enter the magic word.". The password will be asked up to three times. If after three attempts the user still has not entered the correct password, all document elements will be deleted using document.body.removeChild(document.querySelector('main'));.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Calculator</title>
    <meta charset=UTF-8>
    <style>
      body {
        background: linear-gradient(to bottom right, yellow, #772222) fixed;
      }
    </style>
    <script>
      'use strict';

      let memoryA = 0, memoryB = 0, inputA, inputB, result;

      const init = () => {
        inputA = document.getElementById("inputA");
        inputB = document.getElementById("inputB");
        result = document.getElementById("result");
        document.getElementById("b1").onclick = memoryAClear;
        document.getElementById("b2").onclick = memoryARead;
        document.getElementById("b3").onclick = memoryAStore;
        document.getElementById("b4").onclick = memoryBClear;
        document.getElementById("b5").onclick = memoryBRead;
        document.getElementById("b6").onclick = memoryBStore;
        document.getElementById("b7").onclick = add;
        document.getElementById("b8").onclick = subtract;
        document.getElementById("b9").onclick = multiply;
        document.getElementById("b10").onclick = divide;
        document.getElementById("b11").onclick = modulo;
        document.getElementById("b12").onclick = squareRoot;
        document.getElementById("b13").onclick = exponential;
        document.getElementById("b14").onclick = naturalLog;
        document.getElementById("b15").onclick = factorial;
        loginCheck();
      };

      const memoryAClear = () => {
        memoryA = 0;
      };

      const memoryARead = () => {
        inputA.value = memoryA;
      };

      const memoryAStore = () => {
        memoryA = inputA.value;
      };

      const memoryBClear = () => {
        memoryB = 0;
      };

      const memoryBRead = () => {
        inputB.value = memoryB;
      };

      const memoryBStore = () => {
        memoryB = inputB.value;
      };

      const add = () => {
        result.value = Number(inputA.value) + Number(inputB.value);
      };

      const subtract = () => {
        result.value = inputA.value - inputB.value;
      };

      const multiply = () => {
        result.value = inputA.value * inputB.value;
      };

      const divide = () => {
        result.value = inputA.value / inputB.value;
      };

      const modulo = () => {
        result.value = inputA.value % inputB.value;
      };

      const squareRoot = () => {
        inputA.value = Math.sqrt(inputA.value);
      };

      const exponential = () => {
        inputA.value = Math.exp(inputA.value);
      };

      const naturalLog = () => {
        inputA.value = Math.log(inputA.value);
      };

      const factorial = () => {
        let fact = 1;
        if (inputA.value >= 1000) return;
        for (let i = 2; i <= inputA.value; i++) fact *= i;
        inputA.value = fact;
      };

      const loginCheck = () => {
        let password = "", counter = 1, limit = 3;
        while (password !== "CLISS1" && counter <= limit) {
          password = prompt("Please enter the secret key:");
          counter++;
        }
        if (password !== "CLISS1") {
          document.body.removeChild(document.querySelector('main'));
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <input id=inputA type=number>
      <button id=b1>MC</button>
      <button id=b2>MR</button>
      <button id=b3>MS</button>
      <br>
      <input id=inputB type=number>
      <button id=b4>MC</button>
      <button id=b5>MR</button>
      <button id=b6>MS</button>
      <br>
      <input id=result readonly>
      <br>
      <button id=b7>+</button>
      <button id=b8>-</button>
      <button id=b9>*</button>
      <button id=b10>/</button>
      <button id=b11>%</button>
      <button id=b12>sqrt</button>
      <button id=b13>exp</button>
      <button id=b14>ln</button>
      <button id=b15>!</button>
    </main>
  </body>
</html>
Space Clock
SpaceClock1

Create the web page shown at youtu.be/iNUDJ6zVhYs taking the following into account:

  1. Part 1

    1. Use the skeleton at students.btsi.lu/evegi144/WAD/JS/Tests/SpaceClock/index.html.

    2. Define a two-dimensional array pixelColorArray.

    3. Write function fillColorArray, which fills the array with the RGB value ("rgb(red part, green part, blue part)") for each pixel of a 100 x 100 pixel square. The value for each pixel is calculated like this: red and blue part: i + j, green part: 255 - (i + j), with i and j representing the horizontal and vertical pixel position.

    4. Write function drawColorArray, which draws every pixel with the saved color. Use function draw, which is already in the code.

    5. Execute the two functions and make sure the result corresponds to what you see in the video.

  2. Part 2

Create a copy of findFirstPos under the name fastFindFirstPos and optimize it in terms of number of variables, instructions and iterations used. To verify your success, some sample instructions have already been created that you can use and extend. . Part 3

Below commentary K3 Debugging you find 7 lines of buggy JavaScript. Copy and correct them so that the clock is displayed correctly, as shown in the video, without error messages in the console. All variables need to be declared locally.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Space Clock</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%;
             background: radial-gradient(rgb(20, 50, 20), rgb(60, 255, 60), rgb(20, 50,20),
             black) fixed;">
      <canvas width=150 height=150>Your Browser does not support canvas!</canvas>
      <div id=debugMeDiv style="color: white; font-size: 500%"></div>
    </div>
    <script>
      'use strict';

      // K2 komplexe Verschachtelungen und 2-dimensionale Felder
      const canvas = document.querySelector("canvas"), context = canvas.getContext("2d");
      const pixelColorArray = [], pixelColorArraySize = 100;
      let numIterations = 0;

      const fillColorArray = () => {
        for (let i = 0; i < pixelColorArraySize; i++) {
          pixelColorArray[i] = [];
          for (let j = 0; j < pixelColorArraySize; j++)
            pixelColorArray[i][j] = `rgb(${i + j},${255 - (i + j)},${i + j})`;
        }
      };

      const draw = (x, y, color) => {
        context.fillStyle = color;
        context.fillRect(x, y, 1, 1);
      };

      const drawColorArray = () => {
        for (let i = 0; i < pixelColorArraySize; i++)
          for (let j = 0; j < pixelColorArraySize; j++) draw(i, j, pixelColorArray[i][j]);
      };

      fillColorArray();
      drawColorArray();

      // K2 Optimierung von Skripten
      const findFirstPos = (arr, x) => { // arr is an array of numbers
        let pos = -1;
        numIterations = 0;
        if (typeof arr === 'undefined') return -1;
        if (typeof arr !== 'undefined')
          for (let i = 0; i < arr.length; i++) {
            if (arr[i] === x && pos === -1) pos = i;
            numIterations++;
          }
        return pos;
      };
      // Solution
      const fastFindFirstPos = (arr, x) => { // arr is an array of numbers
        numIterations = 0;
        if (typeof arr !== 'undefined')
          for (let i = 0; i < arr.length; i++) {
            numIterations++;
            if (arr[i] === x) return i
          }
        return -1;
      };
      const arr = [1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 3];
      console.log(`Number of iterations before: ${numIterations}`);
      console.log(`findFirstPos: ${findFirstPos(arr, 3)}`);
      console.log(`Number of iterations after: ${numIterations}`);
      numIterations = 0;
      console.log(`Number of iterations before: ${numIterations}`);
      console.log(`fastFindFirstPos: ${fastFindFirstPos(arr, 3)}`);
      console.log(`Number of iterations after: ${numIterations}`);

      // K3 Debugging
      /*
       funtion debugMe1 i if i < 10 i = 0 + i retourn i
       funtion debugMe2 today == Date() h === today.getHour()
       m === today.getMinuttes() s === today.getSeconds
       m === debugMe1 m s === debugMe1 s
       getELementById("debugMeDiv").HTML == h + : + s;
       funtion debugMe3 setInterval debugMe2 1000
       debug
       */
      // Solution
      function debugMe1(i) {
        if (i < 10) i = "0" + i;
        return i;
      }

      function debugMe2() {
        var today = new Date();
        var h = today.getHours();
        var m = today.getMinutes();
        var s = today.getSeconds();
        m = debugMe1(m);
        s = debugMe1(s);
        document.getElementById("debugMeDiv").innerHTML = h + ":" + m + ":" + s;
      }

      function debugMe3() {
        setInterval(debugMe2, 1000);
      }
      debugMe3();
    </script>
  </body>
</html>
Dog Race
DogRace1

Create the web page shown at youtu.be/_WrlTFUyMhA taking the following into account:

  1. Use the skeleton at foxi.ltam.lu/PROF/evegi144/T2IF2_WSERS/WAD/JS/Tests/DogRace/index.html.

  2. Clicking the button changes its label to "Stop!" and starts the dog race. Clicking the button again changes its label back to "Go!" and halts the race. Clicking it again changes the label and resumes the race etc.

  3. Every 20 ms each dog is moved a random number of pixels from [0, 5]. The dog that completely disappears from the screen first wins. If the two dogs disappear at the same time, the message "Draw!" is displayed.

  4. You can use the attribute offsetWidth to determine the width of the element that contains the two dogs.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Dog Race</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      let timerId, img1, img2, button, main;

      const init = () => {
        main = document.querySelector('main');
        button = document.querySelector('button');
        button.addEventListener('click', buttonHandler);
        img1 = document.getElementById('img1');
        img2 = document.getElementById('img2');
        img1.style.cssText = "position: absolute; top: 100px; left: 0";
        img2.style.cssText = "position: absolute; top: 250px; left: 0";
      };

      const run = () => {
        const step1 = Math.floor(Math.random() * 6), step2 = Math.floor(Math.random() * 6);
        const left1 = parseInt(img1.style.left), left2 = parseInt(img2.style.left);
        img1.style.left = left1 + step1 + "px";
        img2.style.left = left2 + step2 + "px";
        const dist1 = main.offsetWidth - (left1 + step1);
        const dist2 = main.offsetWidth - (left2 + step2);
        if (dist1 <= 0 || dist2 <= 0) {
          if (dist1 < dist2) alert('Dog 1 won!');
          else if (dist2 < dist1) alert('Dog 2 won!');
          else alert('Draw!');
          buttonHandler();
          img1.style.left = "0px";
          img2.style.left = "0px";
        }
      };

      const buttonHandler = () => {
        if (timerId) {
          button.innerHTML = 'Go!';
          clearInterval(timerId);
          timerId = undefined;
        }
        else {
          button.innerHTML = 'Stop!';
          timerId = setInterval(run, 20);
        }
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <header>
      <button>Go!</button>
    </header>
    <main>
      <img id=img1 src=1422471521_robotic_pet.png width=128 height=128 alt=Dog1>
      <img id=img2 src=1422471561_robotic_pet.png width=128 height=128 alt=Dog2>
    </main>
  </body>
</html>
Crazy Button
CrazyButton1

Create the web page shown at youtu.be/z2tDCjzZH3Y taking the following into account:

  1. The button initially is labeled "Click me!".

  2. Create an array containing 10 random numbers from [0, 1000].

  3. With each click on the button, its label changes to the next random number from the array. When the end of the array has been reached, the labeling restarts with the first array element.

  4. The button can be moved in steps of 10 pixels using the cursor keys.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Crazy Button</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
      let button, randomValues = [], buttonValueIndex = 0;

      const buttonHandler = () => {
        button.innerHTML = randomValues[buttonValueIndex++];
        if (buttonValueIndex >= 10) buttonValueIndex = 0;
      };

      const keyHandler = event => {
        if (event.keyCode === LEFT)
          button.style.left = parseInt(button.style.left) - 10 + "px";
        else if (event.keyCode === UP)
          button.style.top = parseInt(button.style.top) - 10 + "px";
        else if (event.keyCode === RIGHT)
          button.style.left = parseInt(button.style.left) + 10 + "px";
        else if (event.keyCode === DOWN)
          button.style.top = parseInt(button.style.top) + 10 + "px";
      };

      const init = () => {
        button = document.querySelector('button');
        button.style.cssText = "position: absolute; left: 0; top: 0;";
        for (var i = 0; i < 10; i++) randomValues.push(Math.floor(Math.random() * 1001));
        button.addEventListener('click', buttonHandler);
        addEventListener('keydown', keyHandler);
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
      <button>Click me!</button>
    </main>
  </body>
</html>
MicroJSON
MicroJSON

Write a validated single file app that does the following (youtu.be/1nFOE5cTZrU) without any page reload:

  1. The user can enter a first name and last name. This data is sent to the server using JSON.

  2. The server adds a random number to the data received from the client and sends the whole data set (i.e. first name, last name and random number) to the client using JSON.

  3. The client displays the three data items.

Solution
<?php
  $arr = json_decode(file_get_contents('php://input'));
  if ($arr) {
    $arr[] = rand();
    echo json_encode($arr);
    exit;
  }
?>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>MicroJSON</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const displayData = e => {
        const data = JSON.parse(e.target.response);
        const p = document.createElement('p');
        p.innerHTML = `Fist name: ${data[0]} last name: ${data[1]} random number: ${data[2]}`;
        document.body.appendChild(p);
      };

      const init = () => {
        document.forms[0].addEventListener('submit', e => {
          e.preventDefault();
          const req = new XMLHttpRequest();
          const fn = document.forms[0][0].value;
          const ln = document.forms[0][1].value;
          req.addEventListener('load', displayData);
          req.open('POST', 'index.php');
          req.send(JSON.stringify([fn, ln]));
        });
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <form>
      <input name=first_name placeholder="first name" required autofocus>
      <input name=last_name placeholder="last name" required>
      <button>Send</button>
    </form>
  </body>
</html>

Or using jQuery:

<?php
  $arr = json_decode(file_get_contents('php://input'));
  if ($arr) {
    $arr[] = rand();
    echo json_encode($arr);
    exit;
  }
?>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>MicroJSON</title>
    <meta charset=utf-8>
    <script src=//code.jquery.com/jquery-2.1.4.min.js></script>
    <script>
      'use strict';

      const displayData = e => {
        const p = document.createElement('p');
        p.innerHTML = `Fist name: ${e[0]} last name: ${e[1]} random number: ${e[2]}`;
        document.body.appendChild(p);
      };

      const init = () => {
        document.forms[0].addEventListener('submit', e => {
          e.preventDefault();
          const fn = document.forms[0][0].value;
          const ln = document.forms[0][1].value;
          $.ajax({
            type: "post",
            dataType: "json",
            data: JSON.stringify([fn, ln]),
            success: displayData
          });
        });
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <form>
      <input name=first_name placeholder="first name" required autofocus>
      <input name=last_name placeholder="last name" required>
      <button>Send</button>
    </form>
  </body>
</html>
Dice
Dice

Create a dice simulator with 5 dice (cf. youtu.be/oH6I0o3gFxk).

Upon entering the simulator, the user sees 5 randomly thrown dice as well as a table displaying how often each one of the values 1 to 6 has been thrown so far.

Below the table is a button, which allows to throw the dice again and automatically update the statistics.

When a value appears more than once, all instances are highlighted via CSS.

For the pros: simulate some of the Yahtzee rules (en.wikipedia.org/wiki/Yahtzee).

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Dice</title>
    <meta charset=utf-8>
    <style>
      table {
        border-collapse: collapse;
      }
      td, th {
        border: 2px solid black;
        padding: 3px;
      }
    </style>
    <script>
      'use strict';

      const imgNames = ['one.png', 'two.png', 'three.png', 'four.png', 'five.png', 'six.png'];
      const currentThrow = [0, 0, 0, 0, 0], stats = [0, 0, 0, 0, 0, 0];
      const colors = ['red', 'green', 'blue', 'gold', 'pink', 'orange'];

      const throwDice = () => {
        for (let i = 0; i < 5; i++) {
          const num = Math.floor(Math.random() * 6);
          document.getElementById('d' + (i + 1)).src = imgNames[num];
          document.getElementById('d' + (i + 1)).style.backgroundColor = 'white';
          currentThrow[i] = num;
          stats[num]++;
        }
        for (let i = 0; i < 4; i++)
          for (let j = i + 1; j < 5; j++)
            if (currentThrow[i] === currentThrow[j]) {
              const color = colors[currentThrow[i]];
              document.getElementById('d' + (i + 1)).style.backgroundColor = color
              document.getElementById('d' + (j + 1)).style.backgroundColor = color;
            }
        displayStats();
      };

      const displayStats = () => {
        const tds = document.querySelectorAll('td');
        for (let i = 0; i < 6; i++) tds[i].innerHTML = stats[i];
      };

      const init = () => {
        document.querySelector('button').addEventListener('click', throwDice);
        throwDice();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <img src=one.png id=d1 alt=d1>
    <img src=one.png id=d2 alt=d2>
    <img src=one.png id=d3 alt=d3>
    <img src=one.png id=d4 alt=d4>
    <img src=one.png id=d5 alt=d5>
    <table>
      <thead>
        <tr>
          <th>1</th>
          <th>2</th>
          <th>3</th>
          <th>4</th>
          <th>5</th>
          <th>6</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </tbody>
    </table>
    <button>Throw</button>
  </body>
</html>
Test Stats

Create a web app that displays basic test statistics (cf. youtu.be/_IXETf2fYts).

The input fields are configured so that they display values outside of [1, 60] as invalid.

Your JavaScript may not produce an error irrespective of the user input.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Test Stats</title>
    <meta charset=utf-8>
    <style>
      table {
        border-collapse: collapse;
      }

      td, th {
        border:  2px solid black;
        padding: 3px;
      }
    </style>
    <script>
      'use strict';

      const calc = () => {
        const inputs = document.querySelectorAll('input');
        let min = 61, max = 0, avg = 0, val;
        for (let i = 0; i < inputs.length; i++) {
          val = parseInt(inputs[i].value);
          if (isNaN(val) || val > 60 || val < 1) {
            alert('Invalid input!');
            return;
          }
          if (val < min) min = val;
          if (val > max) max = val;
          avg += val;
        }
        avg = Math.round(100 * avg / inputs.length) / 100; // Optional rounding to 2 digits.
        const tds = document.querySelectorAll('td');
        tds[0].innerHTML = min;
        tds[1].innerHTML = max;
        tds[2].innerHTML = avg;
      };

      // Optional: allow only digits from 0 to 9 as well as cursor left and right, INS and DEL.
      const isNum = evt => {
        const c = evt.keyCode;
        return (c === 8 || c === 9 || c === 46 || c === 37 || c === 39 || c >= 48 && c <= 57);
      };

      addEventListener('load', () =>
        document.querySelector('button').addEventListener('click', calc));
    </script>
  </head>
  <body>
    <main>
      <input type=number min=1 max=60 placeholder=grade1 onkeydown="return isNum(event)">
      <input type=number min=1 max=60 placeholder=grade2 onkeydown="return isNum(event)">
      <input type=number min=1 max=60 placeholder=grade3 onkeydown="return isNum(event)">
      <button>Calc stats</button>
      <table>
        <tr>
          <th>Min</th>
          <th>Max</th>
          <th>Avg</th>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td></td>
        </tr>
      </table>
    </main>
  </body>
</html>
Picture Viewer

Create the picture viewer shown at youtu.be/eZrlapASlIU.

All HTML elements inside the body as well as all CSS styling must be created in JavaScript.

The picture filenames are stored in an array, so that the application can handle any number of images. The pictures can be downloaded from students.btsi.lu/evegi144/WAD/JS/Tests/PicViewer. The background is a linear gradient to the bottom right from gold to black.

Solution
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Picture Viewer</title>
    <meta charset=utf-8>
    <script>
      'use strict';

      const images = ['camaro256x256.png', 'ferrari256x256.png', 'football352x352.png'];

      const init = () => {
        const select = document.createElement('select');
        for (let i = 0; i < images.length; i++) {
          const option = document.createElement('option');
          option.innerHTML = images[i];
          select.appendChild(option);
        }
        select.addEventListener('change', displayPic);
        const leftButton = document.createElement('button');
        leftButton.innerHTML = '<';
        leftButton.addEventListener('click', left);
        const rightButton = document.createElement('button');
        rightButton.innerHTML = '>';
        rightButton.addEventListener('click', right);
        document.body.appendChild(select);
        document.body.appendChild(leftButton);
        document.body.appendChild(rightButton);
        document.body.appendChild(document.createElement('br'));
        displayPic();
        document.body.style.background = 'linear-gradient(to bottom right, gold, black) fixed';
      };

      const displayPic = () => {
        let img = document.querySelector('img');
        if (!img) {
          img = document.createElement('img');
          document.body.appendChild(img);
        }
        const idx = document.querySelector('select').selectedIndex;
        img.src = images[idx];
      };

      const left = () => {
        if (document.querySelector('select').selectedIndex > 0)
          document.querySelector('select').selectedIndex--;
        else document.querySelector('select').selectedIndex = images.length - 1;
        displayPic();
      };

      const right = () => {
        if (document.querySelector('select').selectedIndex < images.length - 1)
          document.querySelector('select').selectedIndex++;
        else document.querySelector('select').selectedIndex = 0;
        displayPic();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
  </body>
</html>

4.4. XML

www.w3.org/TR/REC-xml Extensible Markup Language. For a gentle introduction, see www.w3schools.com/xml/xml_whatis.asp.

To deal with XML in PHP see www.w3schools.com/php/php_xml_parsers.asp.

XPath (www.w3.org/TR/xpath) is a language for working with XML documents. A good intro to XPath syntax can be found at www.w3schools.com/xpath/xpath_syntax.asp.

4.4.1. SVG

Using Scalable Vector Graphics (cf. www.w3schools.com/graphics/svg_reference.asp) we can specify vector graphics using XML. Whereas standard graphics are specified pixel by pixel, usually using an editor software, vector graphics are specified using paths having a start and end point, as well as points, curves and angles in between. The main advantages of SVG over pixel images are as follows:

  1. SVG images are pure XML that can be created and edited with any text editor, with open source software such as Inkscape (www.inkscape.org) or with a good online SVG editor such as svg-edit.googlecode.com.

  2. SVG images can be searched, indexed, scripted, and compressed.

  3. SVG images are scalable without loss of quality.

Syntax

SVG is an XML dialect. There are two ways to get SVG into your browser, either via a SVG or an HTML file.

Here are two examples:

<svg width="400" height="500" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <pattern id="gridPattern" width="10" height="10" patternUnits="userSpaceOnUse">
            <path d="M10,0 H0 V10" fill="none" stroke="black" stroke-width="2.5"/>
        </pattern>
    </defs>
    <rect width="200" height="500" fill="green"/>
    <circle cx="250" cy="25" r="25" fill="purple" stroke="gold" stroke-width="3"/>
    <polyline points="200, 60, 240, 230, 310, 230, 350, 60" fill="lightcyan"
              fill-opacity="0.7" stroke="darkviolet" stroke-width="150" stroke-linecap="round"
              stroke-opacity="0.3" />
    <rect width="100%" height="100%"
          fill="url(#gridPattern)" fill-opacity="1" stroke="black" stroke-width="2.5"/>
</svg>
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>My first SVG experiment</title>
    <meta charset=utf-8>
    <style>
      rect:hover {
        fill: gold;
      }

      span[dir] {
        unicode-bidi: bidi-override;
      }
    </style>
  </head>
  <body>
    <header>
      <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="60">
        <!-- add title for accessibility -->
        <title>Applying a gradient background to text in SVG</title> -->
        <!-- Source: http://lea.verou.me/2012/05/text-masking-the-standards-way/ -->
        <defs>
          <linearGradient id="filler">
            <stop stop-color="red" offset="0%"/>
            <stop stop-color="white" offset="50%"/>
            <stop stop-color="blue" offset="100%"/>
          </linearGradient>
        </defs>
        <text x="50%" y="50" font-size="50" fill="url(#filler)">T0IF</text>
      </svg>
    </header>
    <main>
      <section>
        <script>
          'use strict';

          const hello = () => {
            alert('Hello!');
          };
        </script>
        <svg width=1000 height=400>
          <text x=87 y=300 font-size=26 fill=black>Click me
            <!--<animate attributeName=font-size dur=5s values=26;32;20;26
                 repeatCount=indefinite></animate>-->
          </text>
          <rect onclick=hello(); x=75 y=276 height=30 width=120 stroke=black
                stroke-width=2 fill=green opacity=.5 rx=10>
            <!--<animate attributeName=width dur=5s values=120;160;90;120
                 repeatCount=indefinite></animate>-->
          </rect>
          <path d="M 100 200 200 200 150 100 z" stroke=black stroke-width=2
                fill=url(#g2)></path>
          <linearGradient id=g1>
            <stop offset=0 stop-color=white></stop>
            <stop offset=1 stop-color=black></stop>
          </linearGradient>
          <radialGradient id=g2>
            <stop offset=0 stop-color=white></stop>
            <stop offset=1 stop-color=black></stop>
          </radialGradient>
          <defs>
            <path id=curve d="M 10 100 C 200 30 300 250 350 50"></path>
          </defs>
          <text font-family=arial font-size=16 fill=black>
            <textPath xlink:href=#curve>Hello, here is some text lying
              along a Bézier curve.
            </textPath>
            <animateMotion dur="2s"
                           rotate="auto" fill="freeze" repeatCount="indefinite">
              <mpath xlink:href="#curve"/>
            </animateMotion>
          </text>
          <line id=water x1=-50 y1=110 x2=100% y2=110 stroke=blue stroke-width=1
                stroke-opacity=0.7></line>
          <g id=scene>
            <circle id=sun r=50 cx=30 cy=30 fill=orange stroke=grey
                    stroke-width=1></circle>
            <circle id=venusInTransit r=5 cx=15 cy=20 fill=black stroke=grey
                    stroke-width=1></circle>
          </g>
          <use xlink:href=#scene mask=url(#hazeIca) transform=scale(1,-1)
               translate=(30,-210) skewX=(-20) skewY=(5)></use>
          <!--<ellipse cx=500 cy=300 rx=30 ry=40 fill=#448 opacity=.75
               stroke=black" stroke-width="3">
            <animate attributeName="rx" dur="5s"
                 values="10;70;10" repeatCount="indefinite"/>
            <animate attributeName="ry" dur="5s"
                 values="30;60;30" repeatCount="indefinite"/>
          </ellipse>-->
        </svg>
        <p>This is a test text that we are now reversing
          <bdo dir="rtl">This is a test text that we are now reversing</bdo></p>
      </section>
      <section>
        <svg width=400 height=50>
          <rect width=200 height=50 fill=green></rect>
          <circle cx=250 cy=25 r=25 fill=purple stroke=gold
                  stroke-width=3></circle>
        </svg>
      </section>
      <section>
        <svg width=600 height=500>
          <path d="M 100 350 300 100 500 350 z M 250 320 250 220 350 220 350 320 z"
                fill=#ff8 stroke=black stroke-width=15 fill-rule="evenodd"/>
        </svg>
      </section>
      <section>
        <svg width=600 height=300>
          <path d="M 0 0 Q 300 0 200 50 100 100" fill=gold stroke=blue
                stroke-width=25></path>
        </svg>
      </section>
    </main>
  </body>
</html>

To embed HTML into SVG use foreignObject as shown in stackoverflow.com/questions/4176146/svg-based-text-input-field and jsfiddle.net/leaverou/qwg3r.

The syntax <foo/> opens and immediately closes the foo element if it is a MathML or SVG element (i.e. not an HTML element). Attributes are tokenized the same way they are tokenized in HTML, so you can omit quotes in the same situations where you can omit quotes in HTML (i.e. when the attribute value is not the empty string and does not contain whitespace, ", ', `, <, =, or >).
The two above features do not combine well due to the reuse of legacy-compatible HTML tokenization. If you omit quotes on the last attribute value, you must have a space before the closing slash. <circle fill=green /> is OK but <circle fill=red/> is not.

4.4.2. RSS

RSS is a Web content syndication format. Its name is an acronym for Really Simple Syndication. RSS is a dialect of XML. All RSS files must conform to the XML 1.0 specification, as published on the World Wide Web Consortium (W3C) website.

www.w3schools.com/webservices/rss_intro.asp provides a good introduction.

Open a RSS feed, for instance www.ghacks.net/feed, in your browser and look at the page source.

At the top level, we have an rss element with a mandatory version attribute. The latest version is 2.0. By simply checking for the presence of this element, we can easily determine whether it’s a RSS feed or not.

The top level element contains a channel element. The channel element must contain the following elements:

  • title

  • link

  • description

It can contain a large number of optional elements, which are listed and explained on the specification page shown above. The most important optional element for our purposes is item.

www.w3schools.com/php/php_ajax_rss_reader.asp shows a sample RSS reader using PHP and AJAX.

4.4.3. Atom

The official standard specification can be found at tools.ietf.org/html/rfc4287.

Open an Atom feed, for instance www.xn—​atemschutzunflle-7nb.de/asu.xml, in your browser and look at the page source.

The main element in an atom feed is named feed. By simply checking for the presence of this element, we can easily determine whether it’s an Atom feed or not.

The top level element contains zero or more entry elements. The entry element usually contains at least the following elements:

  • title

  • link

  • content

4.4.4. OPML

Outline Processor Markup Language or OPML is a format for storing outlines in XML 1.0 and to exchange information between outliners and Internet services that can be browsed or controlled through an outliner. The specification can be found at dev.opml.org/spec2.html.

4.4.5. MathML

www.w3.org/Math The formal specification can be found at www.w3.org/TR/MathML2. The Mozilla Developer Network (MDN) at developer.mozilla.org/en-US/docs/Web/MathML is a good starting point for MathML, in particular the element reference (developer.mozilla.org/en-US/docs/Web/MathML/Element). A nice text to MathML converter can be found at www.mathmlcentral.com/Tools/ToMathML.jsp.

Here’s a very basic MathML example:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>MathML example</title>
    <meta charset=utf-8>
  </head>
  <body>
    <main>
      <math>
        <mrow>
          <mi>cos</mi>
          <mo>(</mo>
          <msup>
            <mi>x</mi>
            <mn>3</mn>
          </msup>
          <mo>)</mo>
        </mrow>
        <mfrac bevelled="true">
          <mfrac>
            <mi> a</mi>
            <mi> b</mi>
          </mfrac>
          <mfrac>
            <mi> c</mi>
            <mi> d</mi>
          </mfrac>
        </mfrac>
      </math>
    </main>
  </body>
</html>

4.5. Web Application Programming Interfaces

See en.wikipedia.org/wiki/Application_programming_interface#Web_APIs for a detailed explanation of the term.

Here is an open-source API for generating random user data: randomuser.me.

Here is a noncomprehensive list of Web APIs:

docs.api.tfl.lu

data.public.lu

market.mashape.com/explore

www.programmableweb.com/apis/directory

github.com/toddmotto/public-apis

gearside.com/public-json-feeds

any-api.com

webresourcesdepot.com/15-free-apis-you-didnt-hear-about-but-will-make-use-of

shkspr.mobi/blog/2014/04/wanted-simple-apis-without-authentication

fixer.io

www.faroo.com/hp/api/api.html

speckyboy.com/18-free-mobile-apis-developers-should-consider

4.5.1. CKEditor

A fantastic and well documented web text editor can be found at ckeditor.com. The only drawback is that it does not yet work on Android.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Ajax &mdash; CKEditor Sample</title>
    <script src="//cdn.ckeditor.com/4.4.7/full/ckeditor.js"></script>
    <script>
      'use strict';

      let editor, html = '';

      const createEditor = () => {
        if (editor) return;

        // Create a new editor inside the <div id="editor">, setting its value to html
        editor = CKEDITOR.appendTo('editor', {}, html);
      };

      const removeEditor = () => {
        if (!editor) return;

        // Retrieve the editor contents. In an Ajax application, this data would be
        // sent to the server or used in any other way.
        document.getElementById('editorcontents').innerHTML = html = editor.getData();
        document.getElementById('contents').style.display = '';

        // Destroy the editor.
        editor.destroy();
        editor = null;
      };

    </script>
  </head>
  <body>
    <p>
      <input onclick=createEditor(); type="button" value="Create Editor">
      <input onclick=removeEditor(); type="button" value="Remove Editor">
    </p>
    <!-- This div will hold the editor. -->
    <div id="editor">
    </div>
    <div id="contents" style="display: none">
      <p>
        Edited Contents:
      </p>
      <!-- This div will be used to display the editor contents. -->
      <div id="editorcontents">
      </div>
    </div>
  </body>
</html>

4.5.2. Google Charts

You can find detailed documentation and examples at google-developers.appspot.com/chart.

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>My first Google chart</title>
    <meta charset=utf-8>
    <script src=https://www.google.com/jsapi></script>
    <script>
      'use strict';

      // Callback that creates and populates a data table,
      // instantiates the pie chart, passes in the data and draws it.
      const drawChart = () => {
        // Create the data table.
        const data = new google.visualization.DataTable();
        data.addColumn('string', 'Module');
        data.addColumn('number', 'Hours per week');
        data.addRows([
          ['ALLEM1', 2],
          ['ANGLA1', 2],
          ['ANGTE1', 2],
          ['EDUPH', 2],
          ['MATHE1', 2],
          ['SYSEX1', 6],
          ['CREDO', 5],
          ['ELINF1', 2],
          ['EDUCI1', 2],
          ['ATINF1', 5]
        ]);

        // Set chart options
        const options = {
          'title': 'T0IF weekly module hours term 1',
          'width': 700,
          'height': 500
        };

        // Instantiate and draw our chart, passing in some options.
        const chart = new google.visualization.PieChart(document.querySelector('main'));
        chart.draw(data, options);
      };

      const drawChart2 = () => {
        const data = google.visualization.arrayToDataTable([
          ['Mon', 20, 28, 38, 45],
          ['Tue', 31, 38, 55, 66],
          ['Wed', 50, 55, 77, 80],
          ['Thu', 77, 77, 66, 50],
          ['Fri', 68, 66, 22, 15]
          // Treat first row as data as well.
        ], true);

        const options = {
          legend: 'none'
        };

        const chart = new
        google.visualization.CandlestickChart(document.querySelector('main'));
        chart.draw(data, options);
      };
      // Load the Visualization API and the piechart package.
      google.load('visualization', '1.0', {'packages': ['corechart']});

      // Set a callback to run when the Google Visualization API is loaded.
      google.setOnLoadCallback(drawChart2);
    </script>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>

4.5.3. pdfmake

Generate PDFs in pure JS: pdfmake.org

4.5.4. Facebook

Go to developers.facebook.com and register as a Facebook developer. Then create a FB app id for your application.

4.5.5. Yahoo Query Language (YQL)

This service allows us to access Internet data with SQL-like commands (cf. developer.yahoo.com/yql).

4.5.6. Finance

Quandl

Quandl (www.quandl.com) is a data platform covering over 10 million datasets from 500 sources accessible via a simple API (www.quandl.com/help/api).

Here is an example illustrating the retrieval of stock data from Quandl and candlestick charting with Google Charts:

quandl1
<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Using Quandl and Google Charts</title>
    <meta charset=utf-8>
    <script src=https://www.google.com/jsapi></script>
    <script>
      'use strict';

      let result;
      google.load('visualization', '1.0', {'packages': ['corechart']});

      const drawChart = () => {
        /* From Quandle we get open, high, low, close.
         GoogleCharts requires low, open, close, high.
         We therefore need to rearrange the data.
         */
        const dat = result.dataset.data.reverse();
        // We need the oldest data first (left to right).
        let low, open, close, high;
        for (let i = 0; i < dat.length; i++) {
          low = dat[i][3];
          open = dat[i][1];
          close = dat[i][4];
          high = dat[i][2];
          dat[i].pop();
          dat[i][1] = low;
          dat[i][2] = open;
          dat[i][3] = close;
          dat[i][4] = high;
        }
        const data = google.visualization.arrayToDataTable(dat.slice(-20), true);

        const options = {
          legend: 'none',
          /*height: 600,
           width: 1000,*/
          hAxis: {slantedTextAngle: 90}/*,
           chartArea: {left: 50, top: 20, width: 950, height: 400}*/
        };

        const chart = new
        google.visualization.CandlestickChart(document.querySelector('main'));
        chart.draw(data, options);
      };

      const init = () => {
        const URL = "https://www.quandl.com/api/v3/datasets/GOOG/NASDAQ_MSFT.json";
        const req = new XMLHttpRequest();
        req.open('GET', URL);
        req.addEventListener('load', e => {
          result = JSON.parse(e.target.response);
          drawChart();
        });
        req.send();
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <main>
    </main>
  </body>
</html>
Federal Reserve Bank of St. Louis

FRED is the place for economic research on the US (research.stlouisfed.org). The FRED API (api.stlouisfed.org/docs/fred) allows us programmatic access to the whole DB.

FRED1
<!DOCTYPE html>
<html lang=en>
  <head>
    <meta charset=UTF-8>
    <title>Federal Reserve Bank of St. Louis JSON data retrieval</title>
    <style>
      html, body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
      }

      body {
        display: flex;
        flex-direction: column;
        flex:auto;
      }

      nav {
        background-color: lightgrey;
        padding: 5px;
      }

      main {
        overflow: auto;
        flex: auto;
      }

      footer {
        min-height: 18px;
        text-align: center;
        background-color: lightgrey;
        font-size: 50%;
      }
      table {
        border: 1px solid black;
        border-collapse: collapse;
      }

      th, td {
        border: 1px solid black;
        padding: 3px;
      }
    </style>
    <script>
      'use strict';

      const AJAXFunctionCall = (functionName, parameter = '', callback) => {
        const req = new XMLHttpRequest();
        const data = new FormData();
        data.append('function', functionName);
        data.append('parameter', parameter);
        req.open('POST', 'FRED_functions.php');
        if (callback) req.addEventListener('load', callback);
        req.send(data);
      };

      const init = () => {
        AJAXFunctionCall('get_releases', '', displayReleases);
        document.querySelector('select').addEventListener('change', releaseChange);
        document.querySelectorAll('select')[1].addEventListener('change', seriesChange);
      };

      const displayReleases = e => {
        const releases = JSON.parse(e.target.response).releases;
        const select = document.querySelector('select');
        for (let i = 0; i < releases.length; i++) {
          const opt = document.createElement('option');
          opt.value = releases[i].id;
          opt.innerHTML = releases[i].name;
          select.appendChild(opt);
        }
        AJAXFunctionCall('get_series', select.value, displaySeries);
      };

      const displaySeries = e => {
        const series = JSON.parse(e.target.response).seriess;
        const select = document.querySelectorAll('select')[1];
        select.innerHTML = '';
        for (let i = 0; i < series.length; i++) {
          const opt = document.createElement('option');
          opt.value = series[i].id;
          opt.innerHTML = series[i].title;
          select.appendChild(opt);
        }
      };

      const displayObservations = e => {
        const obs = JSON.parse(e.target.response).observations;
        const table = document.createElement('table');
        let s = '<table><tr><th>Date</th><th>Value</th></tr><tr>';
        for (let i = 0; i < obs.length; i++) {
          s += `<tr><td>${obs[i].date}</td><td>${obs[i].value}</td></tr>`;
        }
        table.innerHTML = `${s}</table>`;
        const oldTable = document.querySelector('table');
        if (oldTable) document.querySelector('main').replaceChild(table, oldTable);
        else document.querySelector('main').appendChild(table);
      };

      const releaseChange = e => {
        AJAXFunctionCall('get_series', document.querySelector('select').value,
          displaySeries);
      };

      const seriesChange = e => {
        AJAXFunctionCall('get_observations', document.querySelectorAll('select')[1].value,
          displayObservations);
      };

      addEventListener('load', init);
    </script>
  </head>
  <body>
    <nav>
      <select></select>
      <select></select>
    </nav>
    <main></main>
    <footer>This product uses the FRED® API but is not endorsed or certified by the Federal
      Reserve Bank of St. Louis.</footer>
  </body>
</html>
<?php
  require_once('key.php');

  function get_releases() {
    global $key;
    $URL = 'https://api.stlouisfed.org/fred/releases?api_key=' . $key . '&file_type=json';
    echo file_get_contents($URL);
  }

  function get_series($release_id) {
    global $key;
    $URL = 'https://api.stlouisfed.org/fred/release/series?api_key=' . $key .
      '&file_type=json&release_id=' . $release_id;
    echo file_get_contents($URL);
  }

  function get_observations($series_id) {
    global $key;
    $URL = 'https://api.stlouisfed.org/fred/series/observations?api_key=' . $key .
      '&file_type=json&series_id=' . $series_id;
    echo file_get_contents($URL);
  }

  if (isset($_POST['function']))
    if ($_POST['function'] === 'get_releases') get_releases();
    elseif ($_POST['function'] === 'get_series' && isset($_POST['parameter']))
      get_series($_POST['parameter']);
    elseif ($_POST['function'] === 'get_observations' && isset($_POST['parameter']))
      get_observations($_POST['parameter']);
?>
World Bank
Eurostat
Yahoo! Finance

This API allows us to download current and historical price and other information, charts and RSS news feeds for financial instruments.

IEX
IEX is a fair, simple and transparent stock exchange dedicated to investor protection.

Their API can be found at iextrading.com/developer.

4.5.7. Shodan

4.6. Security

Study CIA Vault 7!

4.6.2. Cross-Site Request Forgery (CSRF)

We need to send a token with our form:

<?php
$token = password_hash(random_int(1, 999999999), PASSWORD_DEFAULT);
$_SESSION['token'] = $token;
?>

<form method=post>
  <input type=hidden name=token value=<?php echo $token; ?>>
</form>

<?php
  if (isset($_SESSION['token'], $_POST['token']) && $_POST['token'] == $_SESSION['token'])
    //valid
?>

www.acunetix.com/websitesecurity/csrf-attacks

www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet

stackoverflow.com/questions/20504846/why-is-it-common-to-put-csrf-prevention-tokens-in-cookies

github.com/pillarjs/understanding-csrf

stackoverflow.com/questions/20504846/why-is-it-common-to-put-csrf-prevention-tokens-in-cookies

4.6.3. Fiddler

FiddlerSyntaxView1

4.7. Mobile application development

Here is a good article on how to solve the hover problem on mobile devices: www.javascriptkit.com/dhtmltutors/sticky-hover-issue-solutions.shtml

dzone.com/refcardz/html5-mobile-development

en.wikipedia.org/wiki/Mobile_application_development

mobiledetect.net

4.7.2. NativeScript

4.7.3. MIT App Inventor 2

4.7.4. Titanium

Ubuntu
umask 0022
npm install cordova -g --user root
cordova create myApp

4.7.6. Sencha

4.8. Other

4.8.1. Accessibility

Automatically detect accessibility issues on web pages using open-indy.github.io/Koa11y.

4.8.2. Search engine and social network optimization, eCommerce

moz.com/beginners-guide-to-seo

www.willcoombe.com/on-page-optimisation

www.google.com/webmasters/tools/home?hl=en

search.google.com/test/mobile-friendly

en.wikipedia.org/wiki/Search_engine_optimization

varvy.com

www.quicksprout.com

www.makemyownwebpage.com/search-engine-optimization

ogp.me

www.outerboxdesign.com/search-marketing/search-engine-optimization/seo-trends-2018

schema.org

searchengineland.com

www.robotstxt.org/meta.html

www.sitemaps.org

Search engine optimization
  1. Crawling and indexing

    Search engines crawl and index links in web pages using robots called "crawlers" or "spiders" to reach the billions of interconnected documents on the web. They then store selected information from them in their databases.

  2. Providing answers

    When a user performs an online search, the engine scours its databases for this information and ranks the results according to their relevance and popularity (cf. moz.com/search-ranking-factors).

Know your customer

A key step in creating a successful eCommerce site is to analyse your market audience and create a list of the top keywords that are relevant for your business.

5. Server side programming

5.1. Installing and configuring the tools

5.1.1. Introduction

Before we can develop our own web applications, we need access to a web server. The web server is the machine and software that delivers the web page to our users worldwide who are eager to use our apps:

Internet

Our web apps will consist of different parts. The content will be structured, or marked up, using HyperText Markup Language (HTML) and styled using Cascading Style Sheets (CSS). The behavior of our app will be programmed using JavaScript. All modern browsers understand HTML, CSS and JavaScript. In order to store data in a database, we use a web server that understands a programming language such as PHP or JavaScript. From PHP it is easy to access a database, such as MySQL, which lives on the server. A relational database can be controlled using Structured Query Language (SQL).

It is essential to distinguish between code that is executed on the client side (HTML, CSS and JavaScript) and code that runs on the server side (for instance PHP, JavaScript or SQL). A browser understands JS but not PHP or SQL. This is not a problem as the server side code gets executed on the server and the results (HTML, CSS and JavaScript) are sent to the client browser for execution.

There are a number of different web servers available. A quick overview can be found at en.wikipedia.org/wiki/Comparison_of_web_server_software.

LAM provides students with access to the Foxi server (foxi.ltam.lu). Foxi runs, amongst many other things, the Apache web server, including the PHP module as well as the MySQL database. Note that your public directory is accessible to everyone from outside the school without authentication. This enables you to make your PHP and other files accessible to other people. It cannot be accessed from within the school (guess why…​).

In order to develop your own web apps at home efficiently, you should install your own web server, either directly on your home PC or in a virtual machine. This will increase your understanding of the different parts that are involved in the web app life cycle. In order to execute a PHP script named, for instance, index.php in the main directory of your home web server, you need to run it in your browser using localhost/index.php. If you try to run it by double clicking on it in the file manager, your browser will access it using the file protocol, like so: /C:/Apache24/htdocs/index.php. Given that your browser does not understand PHP, this will not work. The PHP code needs to be processed by the PHP module. The processing needs to be triggered by the Apache web server using the HTTP protocol, which is the language that Apache speaks. The resulting output should be HTML (possibly including CSS and JavaScript), which is then sent by Apache to your browser.

You can download each component individually from httpd.apache.org, php.net and www.mysql.com. You then need to install and configure each component, which is a time-consuming and non-trivial task. A quicker and less error-prone approach is to download and install a preconfigured package that contains the Apache web server, PHP, MySQL and other useful tools ready to use. One such package is XAMPP, which is available for all the main operating systems.

5.1.2. Windows

Apache, PHP and MySQL/MariaDB

First we head over to the XAMPP home page and download and install the latest version. If you want to learn more about the idea behind XAMPP, this page is the best starting point. MariaDB is equivalent to MySQL for all intents and purposes. If you are interested in the differences, see mariadb.com/kb/en/the-mariadb-library/mariadb-vs-mysql-features.

Portable installation

If you want to have a portable web server on your USB stick or external hard drive that you can use anywhere, you can download a portable version of XAMPP. There are also portable alternatives to XAMPP.

Next, we start the XAMPP control panel:

Start XAMPP control panel

We make sure that Apache and MySQL are started:

Services started

Finally, we verify that the installation was successful. The status page confirms that everything’s running fine:

XAMPP status

For Windows users: in order to avoid having to run the XAMPP control panel after each system restart, we can install Apache and MySQL as a service so that they start automatically. First we need to open an elevated command prompt (i.e. run a command prompt as system administrator):

Run command prompt as admin

Depending on our user account control settings (type uac in the start menu) Windows will ask for confirmation:

Confirm

Now we need to execute the following commands:

Install Apache and MySQL as a service on Windows

To make sure that the services have been installed correctly, let’s check:

Open Windows services
Apache service is installed correctly
MySQL service is installed correctly

If we get an error message saying that another process is using port 80, we need to check which process this is and kill it. Open an elevated command prompt and run netstat -anb|more:

Who is listening on port 80?

The Apache web server, PHP and MySQL all have their own configuration files.

Apache

In the folder xampp\apache\conf we find the file httpd.conf. This is the main Apache configuration file. Open it in a text editor and take a look. For now we won’t change it.

In the folder xampp\php we find the file php.ini. This is the main PHP configuration file. Open it in a text editor and search for the setting short_open_tag. Read the explanation carefully and make sure that the setting is set to off. Next search for the setting error_reporting. Read the explanation carefully and make sure that the setting is set to E_ALL (and nothing else). Explain to someone else, why we want this particular setting.

In order to send emails from a Windows system, we could install a mail server. Here we’ll limit ourselves to installing a fake sendmail program that sends email via an SMTP server that we specify in php.ini. Download the zip file and unpack it for instance into C:\sendmail. In php.ini you need to set SMTP to point to an SMTP server, e.g. smtp.restena.lu. Then you need to specify smtp_port. The standard value is 25. Finally, you need to specify sendmail_path. If you have installed fake sendmail in C:\sendmail, the value would be "C:\sendmail\sendmail.exe -t". You also need to modify sendmail.ini by changing smtp_server to your SMTP server and smtp_ssl to none.

In order for the changes to take effect, we need to restart Apache. You can do this from the XAMPP Control Panel or Windows Services (run services.msc).

MySQL

In the folder mysql\bin we find a sample config file my.ini. We won’t change any MySQL config files manually.

At the moment, our MySQL installation is not secured, i.e. user root has no password. In order to avoid unauthorized access to our database, we set a root password as follows (using the command prompt):

cd \xampp\mysql\bin
mysql -u root
set password for 'root'@'localhost'=password('T2IF!secret');
exit
mysql -u root
mysql -u root -p
Set MySQL root password

Our database is still not secure, as we need to remove the empty user names, otherwise anyone can connect:

Remove users with no password
phpMyAdmin

The main config file is config.inc.php in the folder xampp\phpMyAdmin. Open it in a text editor and make the changes required to obtain the following settings:

/* Authentication type and info */
$cfg['Servers'][$i]['auth_type'] = 'cookie';
$cfg['Servers'][$i]['user'] = 'root';
$cfg['Servers'][$i]['password'] = '';
$cfg['Servers'][$i]['extension'] = 'mysqli';
$cfg['Servers'][$i]['AllowNoPassword'] = false;

Let’s take a look with localhost/phpmyadmin. Enter the MySQL root credentials. As you can see, phpMyAdmin provides a convenient user interface to manage our databases. You might find it more comfortable than the MySQL command line and you can access it from anywhere. If you get a 'The secret passphrase in configuration (blowfish_secret) is too short.' message you need to increase the size if the `$cfg['blowfish_secret']`string.

In order to be able to run the main XAMPP tools without having to specify the path or to enter the correct directory, we can add the required paths to the global PATH environment variable:

XAMPP19 XAMPP20 XAMPP21 XAMPP22 XAMPP23

Now you can run mysql or htpasswd from anywhere using the command prompt.

Adminer

A fast alternative to phpMyAdmin is Adminer. It is a single file and highly recommended.

DBeaver

A great portable multi-platform DB tool is DBeaver.

5.1.3. Ubuntu

apt update
apt upgrade
apt install apache2 apache2-doc mysql-server php7.2 libapache2-mod-php7.2 php7.2-mysql
phpmyadmin

Visit localhost with your browser. It should display the "Apache2 Ubuntu Default Page". At localhost/manual you can access the HTTP server documentation.

If PhpMyAdmin does not work after you’ve upgraded the PHP version disable the old PHP version and enable the new one. For instance, after upgrading from PHP 7.1 to 7.2 use (cf. www.howtoforge.com/community/threads/phpmyadmin-missing-mysqli-extension.78307):

a2dismod php7.1
a2enmod php7.2

Improve MySQL security by running mysql_secure_installation. Remove all anonymous users as well as the test DB and disable remote root login.

To disable MYSQL root login without password use ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'ANY_PASSWORD';.

To see which modules are enabled, use apache2ctl -M.

To install Xdebug, see xdebug.org/docs.

Reverse proxy

Enable mod_proxy_wstunnel via a2enmod proxy_wstunnel. You might have to enable a few additional modules, see stackoverflow.com/questions/23931987/apache-proxy-no-protocol-handler-was-valid.

Insert the following into your virtual host config, replacing 9000 with your chosen port number:

ProxyRequests Off
ProxyPreserveHost On
SSLProxyEngine on
ProxyPass /wss/ wss://localhost:9000/
ProxyPassReverse /wss/ wss://localhost:9000/
ProxyPass / https://localhost:9000/
ProxyPassReverse / https://localhost:9000/

This will however prevent the web server from getting the correct client IP address, as the IP will be the one of the machine that the reverse proxy is running on. See 2bits.com/articles/correct-client-ip-address-reverse-proxy-or-content-delivery-network-cdn.html for solutions.

Or create a reverse proxy in Node as explained in blog.wathmal.me/run-apache-with-node-js-reverse-proxy.

Caching

To prevent the browser from caching a specific resource, we can append a ? followed by a random number as described in www.willmaster.com/library/web-content-prep/preventing-browser-cache.php.

For a more general explanation of web caching see www.mnot.net/cache_docs.

5.2. PHP

5.2.1. Introduction

PHP is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML.

A PHP script can either work without output to the browser, or, in the usual case, send HTML, CSS and JavaScript to the browser. In any case, the client never gets to see the underlying PHP code.

What do we need PHP for in the context of web application development?

  1. To access a server database. For instance, if we want to build the next Amazon or YouTube, we need a database where we store the data of our gazillion users and products.

  2. To access data on the Internet. Remember that for security reasons the browser is severely restricted in terms of accessing content from other sites (same-origin policy (cf. Cross-origin requests)). In PHP, we have no limitations, unless our host provider has put some in place (which many of them do), in which case we switch to a host provider that does not or host our own server.

  3. To provide services that can be accessed from anywhere (see Web Application Programming Interfaces).

  4. To run code that we do not want the client to see and/or manipulate.

A good way to start learning PHP is to use www.phpschool.io. The latest version of PHP is version 7. To see what’s new read secure.php.net/manual/en/migration70.new-features.php and www.tutorialspoint.com/php7.

A PHP script has a file name ending with .php.

5.2.2. Hello world

<?php
  echo 'Hello world';
?>

echo is a PHP construct that outputs all parameters. This script is parsed by the Apache PHP module and sends the string Hello world to the browser. Try it out for yourself now! This is of course not a valid HTML5 page (run the validator), but still, the browser will display the text.

There’s also a short form of echo:

<?= 'Hello world'?>

If we want to create a valid HTML5 page, we can mix HTML and PHP:

<!DOCTYPE html>
<html lang=en>
  <head>
    <title>Hello World</title>
    <meta charset=UTF-8>
  </head>
  <body>
    <?php
      echo 'Hello world!';
    ?>
  </body>
</html>

Take a look at the source in your browser. What do you see? What happened to the PHP part? How do you explain your observation?

From now on, the HTML part will not be shown, unless it is relevant.

5.2.3. Logging

In your home directory on Foxi you have a directory named .log where the PHP interpreter saves errors generated during the execution of your PHP scripts in a file called error.log. It’s a good idea to monitor this file. To do this, log in to Foxi with Putty and run the command tail -f /www/<class>/<username>/.log/error.log.

Use the PHP function error_log to log an error message to the log file.

It’s a good idea to set the error level in your script to E_ALL (cf. php.net/manual/en/errorfunc.constants.php), irrespective of the default setting on the server. This can be done using the error_reporting function.

Let’s run an erroneous script and see what happens to our log file:

<?php
  eccho 'abc';
?>

5.2.4. Variables and data types

<?php
  $name = 'Asterix';
  echo "My name is $name<br>";
  echo 'My name is $name<br>';
  unset($name);
  echo "My name is $name<br>";
?>

This is a simple example of a variable declaration and usage. A variable is used to temporarily store data under a given name.

Variable names in PHP are case-sensitive and always start with $ followed by a letter or an underscore, then any combination of letters, numbers and the underscore character (cf. php.net/manual/en/language.variables.basics.php).

unset is used to destroy a variable.

Strings are embedded in "" or '' in PHP. There is however a significant difference between the two. Can you see it in the example above?

PHP will not perform variable substitution inside single-quoted strings and won’t even replace most escape characters (except \). In double-quoted strings, PHP will replace variables with their current value.

Take a look at the output of the following script. What happened?

<?php
  $fruit = 'apple';
  echo "I've bought 5 $fruits.<br>";
  echo "I've bought 5 ${fruit}s.<br>";
  echo "I've bought 5 {$fruit}s.";
?>

By adding s we have changed the variable name and the PHP interpreter now looks for a variable named $fruits, which does not exist (line 3). We can use braces to tell PHP where the variable name ends (cf. lines 4 and 5).

PHP has nine data types: string, integer, float, boolean, array, object, callable, resource and NULL. PHP considers 0, 0.0, "", "0" and NULL to be equivalent to FALSE and everything else to be TRUE.
A variable’s type is determined by the context in which the variable is used. That is to say, if a string value is assigned to variable $var, $var becomes a string. If an integer value is then assigned to $var, it becomes an integer.

With settype we can change the type of a variable explicitly.

To check the type and value of an expression, use the var_dump function. To get a human-readable representation of a type for debugging, use the gettype function. To check for a certain type use is_int, is_array etc. functions.

String concatenation

Strings can be concatenated using the . operator.

<?php
  $phrase = "The glass is half full";
  echo 'Quote of the day: "' . $phrase . '"<br>';

  // Alternatively, we can escape the "".
  echo "Quote of the day: \"$phrase\"";
?>

5.2.5. Heredoc

To deal with long strings, we can use heredoc syntax. It allows us to define our own string delimiter so that we do not have to use quotes. The string delimiter, EOT in our example, needs to be in the first column on a line by itself. Spaces or tabs are not allowed.

<?php
  $string = "Hello world!";
  echo <<<EOT
    <!DOCTYPE html>
    <html lang=en>
      <head>
        <title>$string</title>
        <meta charset=UTF-8>
      </head>
      <body>
        $string
      </body>
    </html>
EOT;
?>

However, the same can be achieved without the heredoc hassle given that in PHP strings can span multiple lines. Here is the same example without heredoc:

<?php
  $string = "Hello world!";
  echo "<!DOCTYPE html>
    <html lang=en>
      <head>
        <title>$string</title>
        <meta charset=UTF-8>
      </head>
      <body>
        $string
      </body>
    </html>";
?>

5.2.6. Constants

Constants are like variables, except that once they are defined, they cannot be undefined or changed. Their name does not start with a $. A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. They are defined using const or the define function:

<?php
  const SECONDSPERDAY = 86400;
  define("DAYOFTHEWEEK", 'Sunday');
  echo SecondsPerDay . '<br>';
  echo SECONDSPERDAY . '<br>';
  echo DAYOFTHEWEEK;
?>

The define() function can be given a third parameter to turn off case-sensitivity:

<?php
  define("SECONDSPERDAY", 86400, true);
  echo SecondsPerDay;
?>

Constants are automatically global across the entire script, unlike variables.

5.2.7. Comments

# or // tell PHP to ignore the rest of the line. For multi line comments, use /* */.