Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
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
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
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
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
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