Site web = ensemble de pages HTML "statiques"
Exemple : mathpages.com
Limitations :
Apparition (presque) conjointe de PHP et JavaScript.
PHP : dynamisme côté serveurRésout les deux limitations mentionnées :
Permet entre autres de :
Note utile sur l'usage du point virgule.
Début des années 2000 : arrivée d'"AJAX" :
demande/envoi d'informations au serveur sans
recharger la page.
Exemple : enregistrer du texte au fur et à mesure qu'il est tapé.
<textarea onInput="sendText(event.target.value)"
placeholder="Type text here">
</textarea>
<script>
function sendText(value) {
fetch(
'https://httpbin.org/post',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({value: value})
}
)
.then(res => res.json())
.then(res => console.log(res));
}
</script>
WebRTC : peer-to-peer (P2P) dans le navigateur
→ communication audio/vidéo "sans serveur intermédiaire".
Exemples : discord.com, zoom.us, meet.jit.si, etc...
WebSockets : communication client ↔ serveur temps réel.
Exemples :
socket.send('{"code":"move", "value":"p"}') //client
socket.on(message, (data) => { //serveur
if (data.code == 'move') { socket.send(...) }
})
Envoi d'une notification depuis le serveur vers les clients connectés.
Cas d'utilisation :
/***** Exemple *****/
// Serveur (express.js)
app.get('/stream', function(req, res) {
res.set({'Content-Type': 'text/event-stream'})
res.flushHeaders()
setTimeout(() => res.write(`data: 32\n\n`), 2000)
setTimeout(() => res.write(`data: 42\n\n`), 4000)
})
// Client (JavaScript)
const eventSource = new EventSource('/stream')
eventSource.onmessage = console.log
Visualiser une page web telle qu'elle apparaissait dans le passé...
Exemple : facebook.com fin 2005 :)
D'autres n'ont guère changé : france-echecs.com fin 2000.
Single-page application : tout dans une "unique" page.
But = rendre l'expérience utilisateur plus fluide en minimisant son temps d'attente (pas de chargement complet de pages HTML)
Inconvénient : complexifie le développement côté client.
Imaginons que l'on veuille sur une même page :
- Alice
- Bob
- Bob (veut jouer)
Scénario possible :
4 manipulations explicites de DOM
(appendChild(), insertBefore(), removeChild() ...)
On aurait envie de maintenir deux listes (JavaScript) décrivant les contenus des éléments (DOM).
→ Après chaque mise à jour des listes (simple), on redessine tout l'élément DOM (...facile mais reste lourd).
Les librairies précitées (Angular etc) permettent de ne manipuler que ces listes JavaScript : elles s'occupent de mettre à jour l'affichage quand c'est nécessaire.
- {{ todo.text }}
const ListRendering = { data() { return { todos: [ { text: 'Learn JavaScript' }, { text: 'Learn Vue' }, { text: 'Build something awesome' } ] } }, methods: { newTodo: function() { this.$data.todos.push({ text: 'Call Alice' }) } } } Vue.createApp(ListRendering).mount('#list-rendering')
(Utiles pour l'exercice et le TP).
But = normaliser l'écriture du code pour qu'il soit plus facile à comprendre, étendre, réutiliser...
Note : il existe plein d'autres "design patterns" (Google...)
Note : l'application telle qu'écrite ne suit pas exactement ce schéma...
...Exercice :)
Mini-site permettant de jouer à Rock-Paper-Scissors-Lizard-Spock
(en 7 points). "Timeline" :
Étapes 2 à 4 non exécutées si joueur connu (localStorage).
Il y aussi sessionStorage (comme localStorage mais avec effacement à la fermeture de l'onglet), a priori moins utilisé.
Écrire une API permettant de récupérer
Outil suggéré : plumber.
Bonus : corriger deux défauts du site.
Le principe est simple : vous écrivez dans un fichier plumber.R les fonctions s'exécutant lorsque l'utilisateur navigue vers une URL.
# Exemple. Run with 'pr("plumber.R") %>% pr_run()'
#* @param msg The message to echo back.
#* @get /echo
function(msg="") {
list(msg = paste0("The message is: '", msg, "'"))
}
#* @get /data
function() {
iris
}
Possibilité d'ajouter des filtres s'exécutant avant (logging, authentication...), et de spécifier le sérialiseur (par défaut : JSON).
#* @param msg The message to echo back.
#* @get /echo
#* @serializer text
function(msg="") {
paste0("The message is: '", msg, "'")
}
#* @get /plot
#* @serializer png
function() {
plot(iris)
# Note: essayez un plot() sans "@serializer png"
}
# ...etc
Cf. documentation.
library(DBI)
con <- dbConnect(RSQLite::SQLite(), ":memory:") #or dbname
dbWriteTable(con, "mtcars", mtcars)
# SELECT FROM WHERE:
res <- dbGetQuery(con, "SELECT * FROM mtcars WHERE cyl = 4")
# Ou dbSendQuery / dbFetch / dbClearResults (+ bas niveau)
# UPDATE/CREATE/DELETE:
nbRows <- dbExecute(con, "UPDATE mtcars SET am=10 WHERE cyl=4")
nbRows <- dbExecute(con, "DELETE FROM mtcars WHERE cyl=6")
dbExecute(con, "CREATE TABLE test(a INTEGER, b VARCHAR)")
dbExecute(con, "INSERT INTO test VALUES (32,'a'),(42,'b')")
# Ou dbSendStatement / dbGetRowsAffected / dbClearResult (+ bas niveau)
Voir cette vignette et le basic usage.
Jusqu'ici on s'est contenté de @get car c'est la seule méthode accessible en naviguant. Il en existe d'autres : "The annotations that generate an endpoint include:"
# Exemple.
#* @param cyl The cyl value.
#* @put /cars
function(cyl=-1) {
con <- dbConnect(RSQLite::SQLite(), "cars.sqlite")
dbExecute(con, "UPDATE mtcars SET mpg=mpg+1.5 WHERE cyl=:x", list(x=mpg))
dbDisconnect(con)
}
#* @param mpg Delete cars with mpg lower than this.
#* @delete /cars
function(mpg=0) {
con <- dbConnect(RSQLite::SQLite(), "cars.sqlite")
dbExecute(con, "DELETE FROM mtcars WHERE mpg<:x", list(x=mpg))
dbDisconnect(con)
}
#* @param mpg Add a line to the iris dataset.
#* @param pl Petal.Length [...]
#* @post /iris
function(pl, pw, sl, sw) {
con <- dbConnect(RSQLite::SQLite(), "iris.sqlite")
dbExecute(con, "INSERT INTO iris VALUES (:pl, :pw, :sl, :sw)",
list(pl=pl, pw=pw, sl=sl, sw=sw))
dbDisconnect(con)
}
Pour tester POST, PUT ou DELETE il faut par exemple utiliser
curl dans un terminal :
curl -X DELETE http://localhost:8000/cars/20
Il existe plein d'autres façons d'écrire une API :