77
Responsive Web Design Niels Olof Bouvin 1

Responsive Web Design - users-cs.au.dkbouvin/itWoT/2019/slides... · Web design and development was the realisation that you could break free of the pure client/server model Rather

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Responsive Web DesignNiels Olof Bouvin

1

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

2

Responsiveness?

A Website should adapt to its user providing a quickly updating user interface adapt its presentation to the user’s device

We will look at this from two sides adding JavaScript to Web pages in order to avoid complete reloads extending our CSS layout, so that we can support both wide and narrow screens

responsiveness | rɪˈspɒnsɪvnəs |noun [mass noun]the quality of reacting quickly and positively

3

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

4

Back to the Web browser

One of the most transformational developments in Web design and development was the realisation that you could break free of the pure client/server model

Rather than having to reload the entire Web page from the server, parse, and render it (again) to reflect some change, it became possible to have a script on the page retrieve changes from the server, and update the Web page in situ

much smaller download, no need to re-render ⇒ much faster, fluid and responsive

5

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

6

Events, events, events

The Web browser is governed by events the page has loaded, or a resource has loaded the user has clicked something, or dragged something, or dropped something the user has resized the browser window

If we want something to happen when an event occurs, we have to subscribe to that event, or, in proper Web browser parlance register an event handler

7

An event listener: main.js

Whenever the button is clicked, the registered function is called (with the event as argument), and it modifies the button’s background colour

app └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

const btn = document.querySelector('#button')

function randomInt (limit = 256) { return Math.floor(limit * Math.random()) }

function bgcChange (e) { const rndCol = `rgb(${randomInt()}, ${randomInt()}, ${randomInt()})` console.log(e) e.target.style.backgroundColor = rndCol }

btn.addEventListener('click', bgcChange)

… <body> <button id="button">Press this Button!</button> </body> </html>

8

A selection of events

for all elements click

dblclick

focus

blur

mouseover

mouseout

for windows keypress

keydown

keyup

9

Adding content via JavaScript

Just as we can change the styling of existing elements on a page, we can add new elements programmatically

you will recall the Document Object Model, which forms a tree of element rooted in <html> here, we added new children to the <body> element

for (let i = 1; i <= 32; i++) { const myDiv = document.createElement('div') document.body.appendChild(myDiv) }

const divs = document.querySelectorAll('div')

for (let myDiv of divs) { myDiv.addEventListener('mouseover', bgcChange) }

app └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

10

Form validation

It can be a great convenience to be able to validate form entry before it actually makes it to the server (where we of course also have to validate it)

never, ever trust the user’s input

But, there is already a default action for pressing the Submit button on a form, so what to do?

Events can be prevented from happening, until such time we are ready to permit them to execute

11

The form: form.hbs

I have added a couple of empty, but named, <div> which will be used for error messages

<form method="POST" action="/greetings" id="greetings-form"> <div> <label for="recipient">Recipient:</label> <input type="text" id="recipient" name="recipient"> <div id="recipient-feedback" class="feedback"></div> </div> <div> <label for="message">Message:</label> <textarea id="message" name="message"></textarea> <div id="message-feedback" class="feedback"></div> </div> <div> <button type="submit">Submit</button> </div> </form>

app └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

12

The code: main.js

If a field is empty, we put out an error message, and prevent the submit event from occurring

const form = document.querySelector('#greetings-form') const recipient = document.querySelector('#recipient') const message = document.querySelector('#message') const recipientFeedback = document.querySelector('#recipient-feedback') const messageFeedback = document.querySelector('#message-feedback') form.addEventListener('submit', (e) => { if (recipient.value === '') { e.preventDefault() recipientFeedback.textContent = 'Please add a recipient' } else { recipientFeedback.textContent = '' } if (message.value === '') { e.preventDefault() messageFeedback.textContent = 'Please add a message' } else { messageFeedback.textContent = '' } })

app └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

13

Form validation in action

14

Event capturing and bubbling

For all elements on a Web page, we can add event listeners

But what happens if we add the same kind of event listener (e.g., ‘click’) on elements that are contained within each other?

Who gets the event? the parent element (the ‘outer’ element), or the child element (‘the ‘inner’ element)?

15

Bad bunny

Clicking the video is supposed to play it

Clicking the surrounding area is supposed to hide it16

The body: index.html

The video element is contained within the <div> which has the class ‘hidden’ from the start

(incidentally, this shows you how to add video to your pages, and how to support multiple formats)

app └── index.js public ├── index.html ├── js │   └── main.js ├── style │   └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm

<body> <button>Display video</button>

<div class="hidden"> <video> <source src="video/rabbit320.webm" type="video/webm"> <source src="video/rabbit320.mp4" type="video/mp4"> <p>Your browser doesn't support HTML5 video. Here is a <a href="video/rabbit320.mp4">link to the video</a> instead.</p> </video> </div> </body>

17

The style: main.cssapp └── index.js public ├── index.html ├── js │   └── main.js ├── style │   └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm

div { position: absolute; top: 50%; transform: translate(-50%,-50%); width: 480px; height: 380px; border-radius: 10px; background-color: #eee; background-image: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.1)); } .hidden { left: -50%; /* off screen */ } .showing { left: 50%; /* on screen */ } div video { display: block; width: 400px; margin: 40px auto; }

18

The script: main.js

Seems straight forward enough: click on the button: show the videoBox click on the videoBox: hide the videoBox click on the video: play the video so why does it not work?

app └── index.js public ├── index.html ├── js │   └── main.js ├── style │   └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm

const btn = document.querySelector('button') const videoBox = document.querySelector('div') const video = document.querySelector('video') btn.addEventListener('click', (e) => { videoBox.setAttribute('class', 'showing') }) videoBox.addEventListener('click', (e) => { videoBox.setAttribute('class', 'hidden') }) video.addEventListener('click', (e) => { video.play() })

19

What happens when an event occurs?

Oddly enough, two things: Event capturing: the browser starts from the outer-most ancestor (<html>), checks if it is registered for the event in the capturing phase, runs it if so, and then continues to the next element and so on, until it hits the inner-most element where the event occurred Event bubbling: the browser checks if the element which had the event occur is registered for that event in the bubbling phase, runs it if so, and then continues outwards until it hits the outer-most element (<html>)

20

So what is happening in the code?

In modern browsers, the default is event bubbling

When the user clicks on the video, it starts playing, but the event continues upwards until the hits <html>

thus, the event listener on the <div> is also triggered, causing the <div> to be hidden

Happily, it is possible to stop events from propagating e.stopPropagation()

21

The fixed script: main.js

Sometimes it can be convenient that events bubble up, and sometimes you will want to curtail that

app └── index.js public ├── index.html ├── js │   └── main.js ├── style │   └── main.css └── video ├── rabbit320.mp4 └── rabbit320.webm

const btn = document.querySelector('button') const videoBox = document.querySelector('div') const video = document.querySelector('video') btn.addEventListener('click', (e) => { videoBox.setAttribute('class', 'showing') }) videoBox.addEventListener('click', (e) => { videoBox.setAttribute('class', 'hidden') }) video.addEventListener('click', (e) => { e.stopPropagation() video.play() })

22

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

23

So, that’s events. What’s else you got?

Web browsers can do a lot of things these days manipulate the DOM, obviously draw on the page manipulate sound and video do 3D graphics access sensors found on the machine the Web browser is running on talk to servers

24

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

25

JavaScript in the Web browser

The central object in the Web browser, document, offer access to the Web page, enabling us to setup event listeners, read state of elements, modify, and add content to the page

The Number Guessing Game is a small example of what is possible

Follow the MDN page on the game, but note the changes I have made to the game in the following pages

26

A game on a Web page

It generates a random number as a target

It accepts the user's guess through a text field

It checks the user's guess against the target

It updates the Web page by adding elements

This is repeated until the game is won or lost

The game can then be restarted and the page reset

The Number Guessing Game

27

The Web page<!DOCTYPE html> <html lang=“en”>

<head> <meta charset="utf-8"> <title>Number guessing game</title> <link rel="stylesheet" href="game.css"> </head>

<body> <h1>Number guessing game</h1> <p>We have selected a random number between 1 and 100. See if you can guess it in 10 turns or fewer. We'll tell you if your guess was too high or too low.</p>

<div class="form"> <label for="guessField">Enter a guess: </label> <input type="text" id="guessField"> <input type="submit" value="Submit guess" id="guessSubmit"> </div>

<div id="resultParas"> <p id="guesses"></p> <p id="lastResult"></p> <p id="lowOrHi"></p> </div> <script src="game.js"></script> </body>

</html>28

The stylesheet

html { font-family: sans-serif; }

body { width: 50%; max-width: 800px; min-width: 480px; margin: 0 auto; }

#lastResult { color: white; padding: 3px; }

29

The codelet randomNumber = Math.ceil(Math.random() * 100)

const guesses = document.querySelector('#guesses') const lastResult = document.querySelector('#lastResult') const lowOrHi = document.querySelector('#lowOrHi')

const guessSubmit = document.querySelector('#guessSubmit') const guessField = document.querySelector('#guessField')

let guessCount = 1 let resetButton

function checkGuess () { let userGuess = Number(guessField.value) if (guessCount === 1) { guesses.textContent = 'Previous guesses: ' } guesses.textContent += userGuess + ' '

if (userGuess === randomNumber) { lastResult.textContent = 'Congratulations! You got it right!' lastResult.style.backgroundColor = 'green' lowOrHi.textContent = '' setGameOver() } else if (guessCount === 10) { lastResult.textContent = '!!!GAME OVER!!!' setGameOver() } else { lastResult.textContent = 'Wrong!' lastResult.style.backgroundColor = 'red' if (userGuess < randomNumber) { lowOrHi.textContent = 'Last guess was too low!' } else if (userGuess > randomNumber) { lowOrHi.textContent = 'Last guess was too high!' }

} guessCount++ guessField.value = '' guessField.focus() }

guessSubmit.addEventListener('click', checkGuess)

function setGameOver () { guessField.disabled = true guessSubmit.disabled = true resetButton = document.createElement('button') resetButton.textContent = 'Start new game' document.body.appendChild(resetButton) resetButton.addEventListener('click', resetGame) }

function resetGame () { guessCount = 1

const resetParas = document.querySelectorAll('#resultParas p') for (let rP of resetParas) { rP.textContent = '' }

resetButton.parentNode.removeChild(resetButton)

guessField.disabled = false guessSubmit.disabled = false guessField.value = '' guessField.focus()

lastResult.style.backgroundColor = 'white'

randomNumber = Math.ceil(Math.random() * 100) }

30

The game code

The game demonstrates several things accessing the Web page through the Document Object Model finding, adding, modifying, and removing elements from the DOM adding event listeners to elements in the DOM, and reacting when triggered

This forms the basis for much of what goes on in interactive Web pages

minus getting data from the server, but we will get to that next

31

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

32

Talking to servers

The XMLHttpRequest class makes it possible to access Web servers from JavaScript in the Web browser

This is crucial functionality, as it allows to retrieve information from Web servers as well as initiating HTTP requests that we cannot do through the location bar

such as invoking the HTTP DELETE method

33

Greetings: index.js & greeting.hbs

I have added a new route to the Express server, and I have added a ‘Delete’ button to the greetings’ view

… app.delete('/greetings/:id', (request, response, next) => { const id = request.params.id Greetings.delete(id, (err) => { if (err) return next(err) }) }) …

app └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

{{#each greetings}} <h2>Hello {{recipient}}</h2> <p>{{message}} <button data-id="{{id}}" class="delete">Delete</button></p> {{/each}}

<div><a href="/form">Add a greeting</a></div>

34

data- attributes

You may add data-whatever attributes to any element

It is a very handy way to insert data for later use into a Web page

35

Sending a DELETE request: main.js

I create a XMLHttpRequest object with the DELETE method and a URL of the form /greetings/:id

after it has been sent, I force a reload of the page to show the remaining greetings

app └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

… const greetings = document.querySelectorAll('.delete')

function deleteThisGreeting (e) { const requestURL = `${document.URL}/${e.target.attributes['data-id'].value}` const request = new XMLHttpRequest() request.open('DELETE', requestURL) request.send() window.location.replace(document.URL) }

if (greetings) { for (let myGreet of greetings) { myGreet.addEventListener('click', deleteThisGreeting) } }

36

The greetings site in action

37

DOM manipulation, continued

We have seen that it is easy, using createElement() and appendChild(), to create and add elements to the DOM

Using removeChild(), we can remove an element, provided that we know its parent:

parentElement.removeChild(unwantedChild)

So, if we can arrange it so that we do know the parent, things are pretty straightforward

38

New greetings: greeting.hbs

I have wrapped all greetings in an IDed <div>, as well as wrapping every greeting in a <div> with an ID of the form ‘greeting-id’, so it will be easy to find

app └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

<div id="greetings"> {{#each greetings}} <div id="greeting-{{id}}"> <h2>Hello {{recipient}}</h2> <p>{{message}} <button class="delete" data-id="{{id}}">Delete</button></p> </div> {{/each}} </div>

<div><a href="/form">Add a greeting</a></div>

39

DELETE on server and page: main.jsapp └── index.js db ├── db.js └── greetings.sqlite public ├── js │   └── main.js └── style └── main.css views ├── form.hbs ├── greeting.hbs └── layouts └── main.hbs

const deleteButtons = document.querySelectorAll('.delete') const greetings = document.querySelector('#greetings')

function deleteThisGreetingOnServer (e) { const id = e.target.attributes['data-id'].value const requestURL = `${document.URL}/${id}` const request = new XMLHttpRequest() request.open('DELETE', requestURL) request.send() deleteThisGreetingOnPage(id) }

function deleteThisGreetingOnPage (id) { const selector = `#greeting-${id}` const theGreeting = document.querySelector(selector) greetings.removeChild(theGreeting) }

if (deleteButtons) { for (let myButton of deleteButtons) { myButton.addEventListener('click', deleteThisGreetingOnServer) } }

40

DOM deletion in action

41

The DOM is there for your use

We can dynamically add and delete from the DOM as we see fit

This enables flexible and responsive Web pages

42

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

43

Layout in CSS

Layout has always been difficult on the Web for many years, there was not any good support for it

Then CSS came along, and some things improved a lot

But, most layout was based on a flawed assumption:

“We know the proportions & size of the users’ displays”

Which was true in the PC era, but as smartphones progressed it became increasingly problematic

44

Flexbox: one dimensional layout

Many layouts involve elements that should adapt to the available space, possibly sharing it in an equitable way with other elements

The flexbox is well suited for vertically aligning an element within its parent making all children of a container take an equal part of the available space making a multi-column layout where the columns are of equal height

45

An MDN example: index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Flexbox Basics</title> <link rel="stylesheet" type="text/css" media="screen" href="style/main.css" /> </head> <body> <header> <h1>Sample flexbox example</h1> </header> <section> <article> <h2>First article</h2> <p>…</p> </article> <article> <h2>Second article</h2> <p>…</p> </article> <article> <h2>Third article</h2> <p>…</p> <p>…</p> </article> </section> </body> </html>

app └── index.js public ├── index.html └── style └── main.css

46

An MDN example: main.csshtml { font-family: sans-serif; }

body { margin: 0; }

header { background: purple; height: 100px; }

h1 { text-align: center; color: white; line-height: 100px; margin: 0; }

article { padding: 10px; margin: 10px; background: aqua; }

app └── index.js public ├── index.html └── style └── main.css

47

Adding a bit of flexhtml { font-family: sans-serif; }

body { margin: 0; }

header { background: purple; height: 100px; }

h1 { text-align: center; color: white; line-height: 100px; margin: 0; }

article { padding: 10px; margin: 10px; background: aqua; }

section { display: flex; }

app └── index.js public ├── index.html └── style └── main.css

48

Changing directions: columnhtml { font-family: sans-serif; }

body { margin: 0; }

header { background: purple; height: 100px; }

h1 { text-align: center; color: white; line-height: 100px; margin: 0; }

article { padding: 10px; margin: 10px; background: aqua; }

section { display: flex; flex-direction: column; }

app └── index.js public ├── index.html └── style └── main.css

49

Changing directions: rowhtml { font-family: sans-serif; }

body { margin: 0; }

header { background: purple; height: 100px; }

h1 { text-align: center; color: white; line-height: 100px; margin: 0; }

article { padding: 10px; margin: 10px; background: aqua; }

section { display: flex; flex-direction: row; }

app └── index.js public ├── index.html └── style └── main.css

50

Changing proportionshtml { font-family: sans-serif; } body { margin: 0; } header { background: purple; height: 100px; } h1 { text-align: center; color: white; line-height: 100px; margin: 0; } article { padding: 10px; margin: 10px; background: aqua; } section { display: flex; } article { flex: 1; } article:nth-of-type(3) { flex: 2; }

app └── index.js public ├── index.html └── style └── main.css

51

CSS Grid

A (part of a) page is divided into a grid by numbered lines, and content can then be distributed between the cells within those lines

❶❶ ❷ ❸

52

An example: index.html

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Grid Basics</title> <link rel="stylesheet" type="text/css" media="screen" href="style/main.css" /> </head> <body> <div class="wrapper"> <div class="box">One</div> <div class="box">Two</div> <div class="box">Three</div> <div class="box">Four</div> <div class="box">Five</div> <div class="box">Six</div> </div> </body> </html>

app └── index.js public ├── index.html └── style └── main.css

53

An example: main.css

This is just as you would expect: the divs naturally expand to fill the width of the screen

body { background-color: whitesmoke; }

div { border: solid 1px saddlebrown; padding: 1em; border-radius: 5px; }

.box { background-color: indianred; } .wrapper { }

app └── index.js public ├── index.html └── style └── main.css

54

An example: main.css

We are now using grid layout, but it is implicitly or automatically created, leaving no discernible visual difference

body { background-color: whitesmoke; }

div { border: solid 1px saddlebrown; padding: 1em; border-radius: 5px; }

.box { background-color: indianred; } .wrapper { display: grid; }

app └── index.js public ├── index.html └── style └── main.css

55

An example: main.css

Let’s add some columns… the layout implicitly/automatically repeats for the remaining divs

.wrapper { display: grid; grid-template-columns: 100px 200px 300px; }

app └── index.js public ├── index.html └── style └── main.css

56

An example: main.css

Let’s add some different columns… the layout automatically repeats for the remaining divs

.wrapper { display: grid; grid-template-columns: 200px 300px; }

app └── index.js public ├── index.html └── style └── main.css

57

An example: main.css

Let’s add both columns and rows ‘fr’ defines a fraction of the available space within the enclosing container

.wrapper { display: grid; grid-template-columns: 100px 300px 100px; grid-template-rows: 1fr 2fr }

app └── index.js public ├── index.html └── style └── main.css

58

Header, sidebar, main

<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div>

app └── index.js public ├── index.html └── style └── main.css

.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); }

.header { grid-column-start: 1; grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; background-color: #85BDFD; }

.main { grid-column-start: 1; grid-column-end: 3; grid-row-start: 2; grid-row-end: 4; background-color: #66B312; }

.sidebar { grid-column-start: 3; grid-column-end: 4; grid-row-start: 2; grid-row-end: 4; background-color: D25EA8; }

❶❶ ❷ ❸

59

Header, sidebar, main

<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div>

app └── index.js public ├── index.html └── style └── main.css

.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr) }

.header { grid-column: 1 / 3; grid-row: 1 / 2; background-color: #85BDFD; }

.main { grid-column: 1 / 3; grid-row: 2 / 4; background-color: #66B312; }

.sidebar { grid-column: 3 / 4; grid-row: 2 / 4; background-color: #D25EA8; }

❶❶ ❷ ❸

60

Header, sidebar, main

<div class="wrapper"> <div class="header">Header</div> <div class="sidebar">Sidebar</div> <div class="main">Main</div> </div>

app └── index.js public ├── index.html └── style └── main.css

.wrapper { border: solid 2px saddlebrown; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr) }

.header { grid-column: 1 / 3; grid-row: 1 / 2; background-color: #85BDFD; }

.main { grid-column: 1 / 3; grid-row: 2 / 4; background-color: #66B312; }

.sidebar { grid-column: 3 / 4; grid-row: 2 / 4; background-color: #D25EA8; }

61

Becoming truly responsive

While both flexbox and grid help with general layout and can create some really versatile designs, we are not quite responsive yet

We need to take the available display (or ‘viewport’) into account

This can be done directly in CSS using the @media() selector

62

A responsive Grid exampleapp └── index.js public ├── index.html └── style └── main.css

<!DOCTYPE html> <html> <head> <title>Hello!</title> <meta charset="utf-8"> <link rel="stylesheet" href="style/main.css"> </head> <body class="container"> <header>Header</header> <nav>Navigation</nav> <main> <h1>Main</h1> <p>…</p> </main> <aside>Related links</aside> <footer>Footer</footer> </body> </html>

❶❶ ❷ ❸

63

A responsive Grid exampleapp └── index.js public ├── index.html └── style └── main.css

body { font-family: sans-serif; margin: 0 }

.container { display: flex; flex-direction: column; min-height: 100vh; }

@media (min-width: 768px) { .container { display: grid; grid-template-columns: 200px 1fr 200px; grid-template-rows: auto 1fr auto; } }

header { grid-column: 1 / 4; padding: 30px; text-align: center; background-color: #369; color: white; }

main { flex: 1; padding: 20px; }

nav { background-color: #f90; padding: 20px; }

aside { padding: 20px; background-color: #936; }

footer { grid-column: 1 / 4; padding: 30px; text-align: center; background-color: #690; color: white; }

64

A responsive Grid example

65

Responsive Design Mode

66

Responsive design

A major breakthrough in the past few years is that it is now possible to specify the layout of a Web page purely in CSS without the use of JavaScript

With JavaScript, we add, remove, or modify content

With CSS, we style it and adapt to the user’s display

67

Overview

Responsiveness? Responsiveness through scripting Events in the Web browser Web browser APIs The Number Guessing Game Talking to servers Responsiveness through styling Maps in the Browser

68

Maps in the Web browser

Considering the complexities and enormous amount work behind, it is pretty straightforward to display a map in your Web browser

I will be using Open Street Maps in my examples, as they are open source

69

Maps: index.html & main.css

Note how we include external JS and CSS resources here: this is quite common when using 3rd party APIs

app └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

<!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">

<link rel="stylesheet" href="style/main.css" type="text/css"> <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>

<script defer src="js/main.js" type="text/javascript"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> </body> </html>

.map { height: 400px; width: 100%; }

70

The code behind maps: main.js

The first line defines my home’s position, and then instantiates a new OpenLayer’s Map object set to retrieve map tiles from OpenStreetMap, and a view centred on my humble abode

app └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

const position = [10.155, 56.154] const map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat(position), zoom: 12 }) })

71

What about our actual position?

Web browsers support a geolocation API, enabling us to access the user’s actual position (measured using GPS or some other means)

navigator.geolocation.getCurrentPosition()

However, this is sensitive information, and Web browsers will not permit the use of this API on unsecured Web sites

So, if we are using HTTP, we are out of luck

72

The navigator object

The document is the root object that represents the document shown on the page

The navigator object represents the Web browser and its capabilities

You will probably mainly access it to get the user’s position

73

Switching to HTTPS

Developing Web sites using HTTPS is quite a bit more labour intensive, as it is necessary to generate server certificates, that authenticates the Web server to the user, and enables encryption of the transmitted data

We may later go into how you can use Let’s Encrypt’s (free) services to build secure Web sites, but for the purposes of this lecture, we will be using a Node module called ‘pem’

however, it does require a bit of clicking in the Web browser, as they are, justifiably, paranoid about self-signed certificates it is ok for testing and development, but not for production

74

A secure server: index.jsapp └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

'use strict' const https = require('https') const express = require('express') const pem = require('pem') const path = require('path') const app = express() const port = 4430

pem.createCertificate({ days: 1, selfSigned: true }, function (err, keys) { if (err) throw err app.use(express.static(path.join(__dirname, ‘../public')))

https.createServer({ key: keys.serviceKey, cert: keys.certificate }, app).listen(4430, () => { console.log(`Listening on https://localhost:${port}/`) }) })

75

Location detection: main.jsapp └── index.js public/ ├── index.html ├── js │   └── main.js └── style └── main.css

if ('geolocation' in navigator) { navigator.geolocation.getCurrentPosition(function (position) { const myPosition = [position.coords.longitude, position.coords.latitude] const map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat(myPosition), zoom: 12 }) }) }) } else { const para = document.createElement('p') para.textContent = 'Geolocation is not available' document.body.appendChild(para) }

76

Self-signed and location in action

77