Lorsque vous sélectionnez plusieurs éléments HTML en se basant sur le nom de la balise ou d’un nom de classe, vous recevez plusieurs éléments. Par exemple on a le code ci-dessous :
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
let liste = document.querySelectorAll('li')
let liste2 = document.getElementsByTagName('li')
A priori liste 1 et liste2 sont pareil, mais si nous loggons chaque variable on a HTMLCollection pour liste2 et NodeList pour liste. Quelle est la différence entre les deux?
NodeList est plus général que HTMLCollection
En effet un node peut désigner un élément HTML certes, mais peut aussi désigner un commentaire HTML qui est on le sait pas un élément HTML. Le texte à l’intérieur d’un élément HTML est un node, mais pas un élément HTML
<div>
Je suis un texte
<p>Une phrase</p>
<p>une seconde phrase</p>
</div>
Dans l’exemple ci-dessus « Je suis un texte » n’est pas un élément HTML mais un Node.
Un HTMLCollection ou un Nodelist n’est pas un tableau mais un Iterable !
Vous connaissez sans doute la méthode map relative aux tableaux, si vous essayer d’appliquer map à une Nodelist ou un HTMLCollection, vous aurez une erreur.
liste.map( e=> e ) // erreur
for(let i=0;i< liste.length;i++){
console.log(liste[i])
}
ça marchera aussi pour liste2.
Les index et la propriété length marchent mais c’est tout, ce n’est donc pas un tableau ! On peut boucler dessu, donc c’est un Itérable. Un tableau est un Iterable mais avec plus de propriété.
On peut cependant transformer ces Iterable en tableau avec la fonction Array.from.
Les méthodes d’instance ne sont invocable que sur un objet instantié.
hasOwnProperty(prop)
Vérifie si la propriété existe directement dans l’objet.
const user = { name: "Alice" };
console.log(user.hasOwnProperty("name")); // true
isPrototypeOf(obj)
Vérifie si l’objet existe dans la chaine d eprototype d’un autre objet
function Animal() {}
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
const d = new Dog();
console.log(Animal.prototype.isPrototypeOf(d)); // true
Si vous faites des applications Javascript modernes, et requêtez avec la fonction fetch(), vous êtes sans doute confronté à des problèmes de CORS (Cross Origin Request Forgery), en clair vous faites une requête AJAX depuis une nom de domaine différent du nom de domaine du webservice.
Le CORS est une feature et pas un bug : Par exemple, avec le site OpenWeatherMap.org, vous pouvez requêter des données météo depuis votre ordinateur local, qui a un nom de host forcément différent de OpenWeatherMap.org. On fait du CORS. Mais parfois c’est un peu délicat, notamment beaucoup n’ont pas les connaissances théoriques sous jacentes. ça peut devenir compliqué, surtout avec le requêtes PREFLIGHT qui consistent à envoyer un pré-requête pour « tester » la température de l’eau.
Mais le JSONP peut nous être utile, on va voir comment.
Le principe du JSONP
Le JSONP n’est pas une forme de JSON, c’est du JSON, mais utilisé de façon astucieuse en conjonction entre le serveur et le navigateur.
La balise script du front inclut le fichier du back jsonp.php, Ce fichier s’il est exécuté, revoit un réponse texte myFunc({ « name »: »John », « age »:30, « city »: »New York » })
myFunc est définie plus haut dans le script du front, donc en fait elle va être exécutée au moment du chargement de la page.
Un exemple plus intéressant dynamique de JSONP
Ok on a vu que ça marchait, mais c’est un peu statique tout ça, par exemple le snippet d’inclusion du est statique, on va le rendre dynamique, après tout lorsque qu’on fait une requête AJAX c’est souvent sur demande de l’utilisateur non?
Il faut cliquer sur le bouton pour inclure à la demande le fichier jsonp.php.
Et le nouveau fichier du backend, qui renvoit un content-type application/json, comme une vraie réponse AJAX du serveur. De plus on passe au fichier du backend un paramètre GET, pour simuler une demande d’une information particulière (par exemple les informations d’un utilisateur.). On simule succinctement une requêtes à la base de données en fait.
Ok ça marche aussi ! La réponse du serveur est à la demande, comme une requête de type GET.
Quid du cross domaine?
Je vous avait promis du cross domaine au début voyons voir comment ! C’est tout simple on va dans le fichier front modifier le endpoint de fichier jsonp.php, et mettre ce dernier sur un serveur sur Internet. Mon front sera toujours en local, donc on est bien en situation de cross domaine.
function clickButton() {
let s = document.createElement("script");
s.src = "http://glottr.com/jsonp.php?x=Yvon";
document.body.appendChild(s);
}
C’est là que c’est intéressant. Vous voyez comment on peut inclure le fichier jsonp.php ? C’est dans la balise <script> et dans ce cas, on peut inclure un script de n’importe quel domaine.
Bougeons le fichier et regardons, le résultat est le même !
On peut faire plus sophistiqué avec JSONP
Jusqu’ici la fonction myFunc était codée dans le serveur, comment ferait on si on ne sait pas à l’avance la fonction à invoquer côté client? Vous pouvez passer le nom de la fonction callback en paramètre GET
Avec NodeJS, il existe plusieurs façon de faire une application web
#créer le répertoire de projet
mkdir nodeexpressts
# initaliser le projet en répondant oui à tout
npm init -y
npm init -y // dit oui à tout
{
"name": "nodeexpressts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
// Ensuite créer un fichier index.js pour commencer
touch index.js
ouvrir VSCode dans ce répertoire projet
code .
Nous devons transpiler le Typescript en Javascript avant de démarrer le serveur. Invoquons tsc
npx tsc --init // npx si on n'a pas tsc en global
Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
Un fichier tsconfig.json est apparu, pour configurer la manière dont Typescript sera transpilé par exemple. Nous devons indiqué où se trouvent les fichier Js de sortie, ouvrons ce fichier
"outDir": "./dist"
Pour transpiler taper simplement npx tsc ou tsc (si on a installé en global, vérifiez le)
Si vous avez l’erreur suivante:
error TS18003: No inputs were found in config file 'E:/<chemin>/tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude' paths were '["E:/<chemin>/multiservice/dist"]'.
C’est que vous n’avez pas de fichier .ts (typescript) dans le projet, il faut renommer les fichier js en ts.
tsc
un répertoire dist est apparu, regarez le contenu ce sont des fichiers JS.
Maintenant pour démarrer nodeJS
node dist/index.js
Faisons comme les pro, comment avoir npm build par exemple
Ouvron spackage.json et configurons l’objet scripts:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node dist/index.js"
},
Modifions le script
app.get('/', (req: Request, res: Response) => {
res.send('Hello World! in Typescript Now WITH NPM RUN');
});
Et en ligne de commande faisons
npm run build
> nodeexpressts@1.0.0 build
> tsc
//puis
npm run start
Installons Nodemon ! (en devDependencies)
npm install -D nodemon
Maintenant ça va redémarrer à chaque fois qu’on va modifier un fichier.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsc -w & nodemon dist/index.js"
},
npm run dev
Le & simple va exécuter en background
Il se peut que ça ne marche pas, en effet le & tout seul n’est pas une commande Windows mais Linux. Remplacer par :
"dev": "tsc -w && nodemon dist/index.js
il est probable que ça ne marche pas non plus, car tsc -w (w comme watch) peut ne pas rendre la main à nodemon. Dans ce cas il faut installer concurrently
C’est un peu lourd comme syntaxe mais ça devrait résoudre vos problèmes.
Nettoyer le dossier dist avec rimraf
Si on veut s’assurer que le dossier soit entièrement nettoyer, on va installer rimraf (comme rm -rf !)
npm i -D rimraf
puis on modifie package.json
"build": "rimraf dist &&tsc",
Mettez un fichier dans dist puis exécutez npm run build, le fichier va disparaître. Modifions le script pour ajouter un prestart et preserve, (et renommer dev en serve), qui vont simplement nettoyer le répertoire dist.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rimraf dist &&tsc",
"prestart": "npm run build",
"start": "node dist/index.js",
"preserve": "npm run build",
"serve": "concurrently \"tsc -w\" \"nodemon dist/index.js\""
},
En lançant npm run serve, c’est preserve qui est lancé en premier. Start est pour la production, serve est pour le développement.
Vous savez sans doute uploader via un formulaire une image vers un serveur, et aussi via ajax (c’est un peu plus compliqué). Mais il est possible d’uploader en Ajax progressivement par morceau (chunk en anglais).
La fonction uploadBlob() va prendre le fichier qui est passé en argument, puis le décomposer dans une boucle en plusieurs chunk de taille fixe. Notre boucle se termine quand on a atteint la fin du fichier.
C’est plutôt simple côté backend, le code est synchrone. Il retourne la quantité de bytes écrits ( 1 byte = 1 octet)
Essai d’upload par paquet en javascript
Nous allons prendre un fichier qui fait plus de 100000 octets pour déclencher la découpe. Pour l’exemple j’ai une image de 811 Ko nommée cpu4.png. Cela fait combien de paquets (chunks) à envoyer? Simple division 811000 / 100000 = 9. Le dernier paquet fait moins de 100000 octets ou bytes.
On ouvre les devtools, et effectivement j’ai 9 envois.
Si vous essyez d’ouvrir l’image cependant, vous ne pourrez pas la visualiser. En fait l’image uploadée est différente de l’image choisie, vous n’avez qu’à comparer leurs checksum sur un outil comme celui-ci.
Mais comment est ce possible? Regardez l’image ci-dessus, le fichier php renvoit la taille écrite en guise de réponse, et l’avant dernier paquet montre 29669 octets, alors que ce devait être le dernier paquet qui doit montrer ce nombre ! Bref les paquets sont envoyés dans le désordre !! Nous allons corriger cela simplement avec async/await, qui va rendre un peu de synchronisme dans la boucle for.
Async/await à la rescousse.
Nous allons modifier le code de la façon suivante : uploadBlob sera async et on « awaitera » fetch.
async function uploadBlob(file) {
//reader = new FileReader() // le file reader va lire à partir d'une position donnée
const chunkSize = 100_000;
// const filename = file.name;
let start = 0
let i = 0
for (let start = 0; start < file.size; start += chunkSize) {
console.log("morceau ", i, "uploadé")
i++
let end = start + chunkSize
const chunk = file.slice(start, end);
const fd = new FormData();
fd.set('file', chunk,);
await fetch(url, {
method: "POST",
body: chunk
})
.then(response => response.text())
}
}
Maintenant le dernier paquet est bien le plus petit. Tous les paquets sont envoyés dans l’ordre. Nous pouvons vérifier que l’image s’ouvre bien. Vérifiez les checksum de ces deux fichiers, ils sont pareil.
Il est à noter que dans le cas des envois en désordre, la taille du fichier reçu est variable !
//script1.js
let email = document.querySelector('#email')
let password = document.querySelector('#password')
let btn = document.querySelector('#btn')
//script2.js
email.addEventListener('keyup', (event) => {
if (!isEmailValid(email.value)) {
console.log('Email is invalid')
} else {
console.log('Email is valid')
}
})
password.addEventListener('keyup', (event) => {
console.log('Password length ', password.value.length)
})
// Vérifie si l'email est correct
const isEmailValid = (email) => {
return email.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
}
//script3.js
btn.addEventListener('click', (event) => {
event.preventDefault()
if (isEmailValid(email.value) && password.value.length > 8) {
alert("Form is valid")
} else {
alert("Form is invalid")
}
})
Si votre page HTML fait appel à l’inclusion de beaucoup de fichier JS, vous aurez envie de fusionner tous ces fichiers en un seul pour le rendre plus compact. je vous propose d’utiliser Rollup, qui est simple comparé à Webpack. On va installer Rollup
Un socket est une interface de communication entre deux noeuds dans un réseau informatique. Plus concrètement, entre deux ordinateurs, et pour les développeurs web entre un serveur et un client, aka votre ordinateur et plus précisément le navigateur.
Côté serveur
On va pour simplifier utiliser Nodejs, avec un script en local on va pouvoir en quelques ligne mettre en oeuvre un socket grâce à la librairie socket.io
Côté client
Grâce à une librairie socket.io-client, on va pouvoir se connecter à un socket.
Quelle est la différence entre un socket et une requête AJAX?
Dans le monde du HTTP, une communication entre client et serveur ne dure qu’un instant, entre deux communications il n’y a rien. Vous faites une requête AJAX, le client initie une demande (Request), le serveur réponse avec un Response, et puis c’est fini.
Dans le cas d’un socket, une fois que le client initie une demande de connexion, la communication s’établie et dure tant que l’on n’a pas arrêté, elle ne s’arrête pas toute seule. Le socket sert à maintenir une connexion persistente, avec un flot continu de données de part et d’autre.
Le socket répond au besoin de recevoir contnuellement des données, une application comme une courbe de bourse en temps réel a besoin d’un socket, cela permet une mise à jour temps réel du cours de bourse. Bien sû on pourrait faire toute les secondes une requête AJAX, mais c’est très consommateur de données, de par la nature du protocole HTTP. Socket ne fait pas partie de la technologie HTTP.
Démarrer un projet avec socket.io
Créez un nouveau répertoire de projet, ouvrez le avec VSCode, ouvrez un terminal sous VSCode.
vous pouvez lancer le serveur NodeJS avec la commande:
node index.js
ça va initialiser un serveur qui écoute sur le port 3000 (par défaut pour NodeJS). Si dans votre navigateur, vous ouvrez l’url http://localhost:3000. Gardez un oeil sur le terminal de VSCode également.
Ajout d’un fichier HTML
Nous allons améliorer notre code avec une page HTML à servir
Note : Pour éviter de redémarrer à la main à chaque fois qu’il y a une mise à jour du code, je vous suggère d’installer nodemon, un démon NodeJS, qui va redémarrer le serveur automatiquement à chaque enregistrement de fichier.
npm install --save-dev nodemon // installe en tant que dépendance de développement
// redémarrer le serveur
nodemon ./index.js localhost 3000
Intégration de socket.io
Maintenant on va allez voir les choses intéressantes, l’intégration de socket !
Il nous faut agir sur deux fronts, sur le serveur et sur le client. Il y a donc deux partie à la librairie socket.io. (nous sommes toujours en architecture client/serveur)
Socket.io côté serveur
npm install socket.io
et éditons le fichier index.js
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
// début de code de gestion du socket
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
// fin de code de gestion du socket
server.listen(3000, () => {
console.log('listening on *:3000');
});
Notez qu’on crée le serveur de socket en lui passant dans le constructeur le server NodeJS. « connection » est l’événement qui se déclenche lorsque la page dans le front end se charge pour la première fois et qu’un message donc est envoyé au serveur.
Socket.io côté client
Ajoutez le snippet dans le fichier HTML avant la fermeture de body
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>
ça y est notre application web est équipée!
Maintenant ouvrez la page a port 3000, votre terminal nodeJS va afficher ‘a user connected’. Ce qui se passe c’est que lorsque la page est loadée, la librairie socket.io va envoyer un signal vers le serveur, qui intercepte le message. Si vous bne voyez rien, rafraichissez la page.
Construction de l’application de messagerie instantanée
Maintenant côté client, écrivons le code JS pour émettre les messages entrés au clavier, dans le formulaire:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var form = document.getElementById('form');
var input = document.getElementById('input');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
</script>
et côté serveur on va afficher le message, ajoutez le snippet suivant :
Tapez les messages dans le formulaire, observez les logs côté serveur.
Le broadcasting côté serveur
Intéressons nous maintenant sur comment le serveur va émettre un message pour tous les client connectés, à la manière d’une station radio, d’où le terme ‘broadcasting’. Il faut utiliser la méthode emit() de l’objet io. Modifiez le code :
Il ya plusieurs façons de débugger NodeJS avec VSCode, le plus simple (mais peut être le plus ennuyant) est d’utiliser l’auto Attach, dès qu’un process NodeJS est démarré, VSCode se met en écoute. On va utiliser cette méthode. Pour plus d’information sur les autres méthodes allez sur cette page.
Utiliser les dev tools pour visualiser les communication via le socket
Vous pouvez voir les connexion socket comme vous pouviez le faire avec les requêtes Ajax. Dans les devtools, allez dans l’onglet Réseau, cliquez sur WS dans les filtres (à côté de XHR, Images etc.
Dans l’onglet Réponse, vous pouvez voir en fait les allées et venues des messages.
Ce que tous les développeurs web connaissent, ce sont les événements du DOM, les clics de souris, les survol, les chargements de page. Soit la page HTML suivante, un formulaire avec une div cachée qui se révèle dès que le formulaire change
let email = document.querySelector('#email')
let name = document.querySelector('#name')
let formulaire = document.querySelector('#formulaire')
let popup = document.querySelector('#popup')
formulaire.addEventListener('change', (e) => {
console.log('form has changed')
popup.style.display = 'block'
})
Mais il est aussi possible de lancer des événement depuis n’importe quel objet en Javascript, et ceci va nous permettre de coder notre application avec des événements, permettant une grande souplesse et découplage.
Par exemple, imaginez un formulaire avec login et mot de passe, di les deux champs sont validés, nous voulion faire apparaitre une popup.
let email = document.querySelector('#email')
let name = document.querySelector('#name')
let formulaire = document.querySelector('#formulaire')
let popup = document.querySelector('#popup')
popup.addEventListener('formEvent', (e) => {
popup.style.display = 'block'
})
//create the CustomEvent
const formEvent = new CustomEvent('formEvent', { detail: "les détails" })
formulaire.addEventListener('change', (e) => {
console.log('form has changed')
popup.dispatchEvent(formEvent)
// popup.style.display = 'block'
})
Il faut mettre bubble à true pour que l’événement se propage
let email = document.querySelector('#email')
let name = document.querySelector('#name')
let formulaire = document.querySelector('#formulaire')
let popup = document.querySelector('#popup')
popup.addEventListener('formEvent', (e) => {
popup.style.display = 'block'
})
//create the CustomEvent
const formEvent = new CustomEvent('formEvent', { detail: "les détails",bubbles:true })
formulaire.addEventListener('change', (e) => {
console.log('form has changed')
popup.dispatchEvent(formEvent)
// popup.style.display = 'block'
})
function test() {
console.log('this in a function', this);
}
const test2 = () => {
console.log('this in arrow function', this);
}
test2() // objet window
test() // objet Window
Comme on est dnas l’espace global (Window) le mot clé this représente Window. En effet les deux fonction sont définie dans l’espace de nom global et non dans un objet.
Le mot clé this dans l’espace de nom global
dans une fonction, la valeur de this dépend de comment la fonction est appelée. Considérez this comme un paramètre caché de la fonction. this est l’objet dans lequel la fonction est appelée.
Le cas des fonctions flèches
function test() {
console.log('this in a function', this);
}
const test2 = () => {
console.log('this in arrow function', this);
}
// test2() // undefined
// test() // objet Window
const obj1 = { name: "obj1" };
const obj2 = { name: "obj2" };
obj1.test = test
obj2.test2 = test2
console.log(obj1.test()) // this est l'objet obj1
console.log('arrow ', obj2.test2()) // this est l'objet Window
Dans les fonctions flèche, le this représente toujours l’objet global Window, alors que dans les fonctions classiques this représente l’objet dans lequel la fonction est définie.
Le cas du strict-mode
"use strict";
function show() {
console.log(this);
}
show(); // undefined
This dans un event listener
Ici c’est plus courant, vous avez déjà vu avec Jquery le $this, en Javascript pur c’est pareil avec this
Non je ne parle pas de l’événement « click » ou « onclick », mais carrément de créer un événement souris. En javascript nous disposons de MouseEvent, qui ne se limite pas au seul clic gauche ou droit, mais tient compte aussi des déplacements de la souris.
var clickEvent = new MouseEvent('click', {
bubbles: true,
cancellable: true,
clientX: 32,
clientY: 32,
})
Voici le la documentation officielle de MouseEvent. Cependant je dois aussi vous dire qu’il y a un autre événement appelé PointerEvent. Cet événement est plus générique, car il peut gérer les événement touch, multi contact, alors que MouseEvent ne peut pas. Mais dans cet article nous allons nous concentrer sur MouseEvent.
function simulateClick() {
let titre = document.querySelector('h1')
let clickEvent = new MouseEvent('click', {
bubbles: true,
cancellable: true,
/* offsetX: 20,
offsetY: 20,*/
})
console.log('dispatch')
titre.dispatchEvent(clickEvent)
console.log('clicked')
}
let h2 = document.querySelector('h1').addEventListener('click', function (e) {
alert('toto')
})
setTimeout(simulateClick, 300)
La fonction simulateClick() va sélectionner l’élément H1 et la mettre dans la variable titre, construit un objet Event (MouseEvent pour être plus précis, MouseEvent héritant de Event), ensuite et c’est là qu’il faut être attentif, c’est titre qui va émettre (dispatch) l’événement clickEvent.
Dans le programme principal, h2 qui représente le même élément que titre reçoit une simulation de click et donc une alert() va faire apparaitre une popup. au bout de 300 millisecondes.
Liens en rapport avec l’article:
/////////////////////// DOM onunload //////////////////// https://stackoverflow.com/questions/11177233/javascript-event-that-runs-before-page-changes https://stackoverflow.com/questions/446892/how-to-find-event-listeners-on-a-dom-node-in-javascript-or-in-debugging https://stackoverflow.com/questions/4386300/javascript-dom-how-to-remove-all-event-listeners-of-a-dom-object/64484951#64484951
Un module est un fichier tout simplement rien de plus. On parle de module en javascript moderne, en effet ce concept n’existait pas avant ES6. Les modules existent en NodeJs également. Mais dans cet article je ne vais parler que des modules côté client.
Avant l’existence des modules, on ne pouvait pas importer un fichier Javascript dans un autre fichier Javascript comme on peut le faire en PHP.
Ici on ne va parler que des modules front end, dont la syntaxe est différente des modules dans NodeJS, dans ce dernier il existe au moins deux façon de faire, avec require() (commonJs) et import (AMD).
Comment on faisait avant pour importer un script Javascript?
On se servait de la balise script our importer les fichiers, index.js ne pouvait pas importer helper.js. La conséquence est que dans un fichier HTML on pouvait avoir facilement plus d’une dizaine d’import de fichier javascript. Cela pouvait avoir un impoact sur la rapidité du site.
Et vinrent les modules Javascript
Dès qu’il y a un import de modules, on observe au minimum deux fichiers. Soient deux fichiers, un principal.js, et un helper.js
//index.html
<script type="module">
import {sayHi} from './helper.js';
sayHi('John');
</script>
// helper.js
export function sayHi(name){
console.log('Hello ' + name)
}
On peut importer une fonction à condition qu’elle ait été exportée depuis le fichier helper.js. Dans la balise script on doit ajouter l’attribut type= »module » pour que cela marche.
Les caractéristiques de modules
Les modules utilisent le mode strict
Dans un code Javascript vous utilisez le mode strict pour forcer une plus grande rigueur dans la programmation
Par exemple dans un fichier HTML
<script>
a = 5 // autorisé pas d'erreur dans la console
</script>
Par contre
<script>
"use strict"
a = 5 // ReferenceError: a is not defined
</script>
de même dans un module
<script type="module">
a = 5 // ReferenceError: a is not defined bien qu'on n'ait pas eu à mettre use strict
</script>
Le scope des variables
Chaque module a son scope pour ses variables (je parle des variable globales du module, pas les variable locales des fonctions), les modules ne communiquenet donc pas leur variables, autrement dit, un module ne peut utiliser une variable globales d’un autre module.
// user.js
let user = "Jules"
// main.js
console.log(user) //
//index.html
<script type="module" src="user.js"></script>
<script type="module" src="hello.js"></script>
la console va afficher "SyntaxError: The requested module './lib.js' does not provide an export named 'user'"
//on modifie dans user.js
export let user = "Jules" // correct
//Pour illustrer directement dans une page HTML
<script type="module">
export let user = "Jules"
</script>
<script type="module">
console.log(user) // Erreur,et on ne peut faire import du module puisqu'il est inline, la seule façon urait été de faire window.user = "Jules"
</script>
Evaluation une seule fois du module à l’import
//index.html
<script type="module">
window.test = 0
</script>
<script type="module">
import { val } from './util.js'
</script>
<script type="module">
import { val } from './util.js'
console.log(val)
</script>
// util.js
window.test++
export let val = window.test
bien que l’on ait importé deux fois le module util.js, le code va afficher 1 et non 2
Prenons un autre exemple, qui va montrer que cet aspect est en fait pratique, permet aux différents scripts de se « partager » une variable
<script type="module">
alert(typeof button); // ce module est defer, il se télécharge immédiatement, mais il attend que le HTML finisse de se télécharger avant de s'exécuter.
</script>
<script>
alert(typeof button); // s'exécute immédiatement après le téléchargement, avant que le HTML du bouton ne vienne
</script>
<button id="button">Button</button>
Toujours préciser le chemin,
Même si le script est adjacent à votre page HTML, il faut préfixer avec un point slash
import {compteur} from 'util.js'; // dans un navigateur ceci n'est pas autorisé,TypeError: Failed to resolve module specifier "util.js". Relative references must start with either "/", "./", or "../".
import {compteur} from './util.js';
scripts externes et CORS
L’inclusion de script externe en tant que module doit se faire avec précaution dans le cas des modules. Pour les scripts externes de même origine que la page HTML, pas de soucis, mais si le script vient d’un autre nom de domaine, alors le serveur d’origine
<script type="module" src="https://site.com/script.js"></script>
il faut que site.com ait activé le CORS.
async et les modules
Le mot clé async permet de télécharger en parallèle un script javascript externe, mais ne s’utilise pas pour les script inline (à même la page HTML). dans le cas des modules, le mot clé async s’applique aussi aux Javascript inline.
<script async type="module">
import {counter} from './utils.js';
counter.count();
</script>
Ces attributs lorsqu’il sont utilisés sur une page HTML on tous un point commun : ils permettent de télécharger de façon asynchrone les fichiers liés depuis la page web, dans le but d’accélérer le téléchargement de la page et son exécution.
Async et Defer s’appliquent à la balise <script> uniquement
Comment utiliser ces deux attribut dans un script ? voici un exemple
<script src="init.js" defer></script>
ou
<script src="init.js" async></script>
Quelle est la différence entre async et defer? Regardez les deux schémas tirés de ce site,
async ne vas pas attendre la fin du téléchargement de la page HTML pour s’éxécuter, alors sur defer va attendre la fin du téléchargement de la page HTML (le parsing plus exactement).defer va être utile si vous avez besoin que tous les éléments du DOM soient mis en place.
preload permet de télécharger de façon asynchrone les ressources indiquée (css, javascript), mais il ne sont pas exécutés. Ils seront exécutés par le navigateur en cas de nécessité, ce qui est une bonne forme d’optimisation. Pour en savoir plus sur les détails techniques, cette page de la documentation officielle sur MDN.
Cet attribut a pour but de prétélécharger une page (même si on ne va pas la visiter) pour que lorsqu’on clique sur le lien, on n’ai pas à la télécharger. Il faut bien sûr s’assurer que c’est une page souvent visitée. La page prétéléchargée sera dans le cache prefetch.
L’image ci-dessus explique le fonctionnement (source : lien de la source).
lorsque vous faites des requêtes sur les éléments d’une page HTML et en faisant un affichage des éléments que vous avez obtenus, parfois vous avez des nodelist et parfois vous avez des Element, cela peut prêter à confusion parce que à première vue ce sont deux choses qui sont similaires, mais alors si elles sont similaires pourquoi elle portent deux types différents?
Node (Nœuds) dans le DOM
Dans le DOM, tout est un nœud. Un nœud peut être un élément, un attribut, un texte, un commentaire, un document, ou tout autre type d’objet DOM. Les nœuds sont organisés dans une structure arborescente, avec le nœud de document en haut et tous les autres nœuds en découlant.
Les nœuds ont des propriétés et des méthodes qui vous permettent de les manipuler ainsi que leurs nœuds enfants. Par exemple, vous pouvez utiliser la méthode appendChild() pour ajouter un nœud enfant à un nœud existant.
Éléments dans le DOM
Les éléments sont un type spécifique de nœud qui représente un élément HTML ou XML. Les éléments ont toutes les propriétés et méthodes d’un nœud, mais ils ont également des propriétés et des méthodes supplémentaires qui leur sont propres.
Par exemple, les éléments ont une propriété tagName qui spécifie le nom de l’élément, tel que « div » ou « span ». Les éléments ont également des attributs, qui peuvent être accédés en utilisant la méthode getAttribute() ou simplement en accédant à l’attribut comme une propriété de l’élément.
Il existe 12 types de noeuds
Source StackOverflow
Element est un type de noeud, il y a aussi les noeuds TEXT, COMMENT, etc. Mais le plus familier pour nous est le type Element, les éléments de la page HTML.
Quand obtient l’un ou l’autre?
Lorsque vous faite une requêtes qui peut retourner plusieurs item, vous obtenex un NodeList (ce n’est pas un tableau !)
<div class="container">
<ul id="liste">
<li id="un">Un</li>
<li id="deux">Deux</li>
<li id="trois">Trois</li>
</ul>
</div>
let el = document.getElementById('un')
console.log(el) // retourne un Element
let nodelist = document.querySelectorAll('li')
console.log(nodelist) // retourne un nodeList
Dans l’exemple ci-dessus, même si le second exemple retourne un NodeList, les items de cette nodelist sont des Elements ! Vous pouvez le vérifier avec instanceof
Mais alors pourquoi dans ce cas on ne fait pas un objet ElementList? c’est un choix des ingéieurs qui ont fait Javascript.
Par contre HTML5 définit un objet HTMLCollection, qui est un objet qui ne contient que des Element, qui exclut tous les autres types de noeuds. Vous pouvez voir HTMLCollection à l’oeuvre avec l’exemple suivant:
let el = document.getElementById('liste')
En pratique vous n’avez pas à vous soucier de ces subtiles différences.
C’est un plugin navigateur qui vous permet d’exécuter du code Javascript sur la page où vous vous trouvez. Vous connaissez les extensions de navigateur? Greasemonkey vous permet facilement de retrouver les mêmes capacités sans avoir à coder toute l’extension.
Installez Greasemonkey version 4 (sur Chrome c’est TamperMonkey).
Pour aller au plus simple, cliquez sur l’icône du singe, vérifiez qu’il est bien activé puis cliquez sur Nouveau Script
// ==UserScript==
// @name Anonyme Script 813510
// @version 1
// @grant none
// ==/UserScript==
Sion on veut de l'ajax (cross domain en plus !)
//@grant GM.xmlHttpRequest
Vous pouvez personnaliser les annotations, @name pour le nom du script, @version qui n’aura pas d’influence sur votre scripts, et surtout @grant est très important, si vous voulez par exemple faire une requêtes Ajax, par défaut vous ne pouvez pas. Je vous joins la page de documentation du plugin:
VSCode est vraiment l’éditeur de code couteau suisse, son avantage est que s’il y a un langage même confidentiel, vous trouverez un plugin. Même s’il n’est pas le meilleur dans chaque catégorie, par exemple je trouve PHPStorm mieux pour le PHP, il est un très bon outil gratuit de surcroît.
Pour NodeJs, c’est un peu plus simple que pour Xdebug en PHP, il y ap lusieur façons de déclencher le débugger de NodeJS, on va voir l’auto déclenchement du débugger dès qu’un process node est en cours d’exécution.
Je suppose que vous ayez un fichier index.js, à exécuter via la commande shell, pour le lancer vous faites node index.js. Si vous ne l’avez pas fait vous pouvez démarrer un projet NodeJS.
Et là vous devrez avoir la barre de status orange, indiquant que le débug est actif.
Configuration de VSCode pour le debug automatique (Auto attach)
Pour activer l’auto attach, faites la combinaison de touche : SHIFT + CTRL + P, une boite de recherche va apparaitre, tapez « Toggle Auto Attach » pour trouver l’option et cliquez dessus.
Maintenant chaque fois que vous démarrez un process NodeJS, VSCode se met en mode debug.
packge.json est le fichier important sans lui vous ne pouvez pas publier de package.
Le fichier index.js est le point d’entrée de votre package.
Le nom du package doit être unique dans tout le repository NPM, cela a donné à l’affaire du npmgate
Dans la barre de recherche de NPM vous pouvez chercher par nom de package.
Créez le fichier index.js
On va faire simple un seul fichier:
let uniqueArr = [];
function removeArrayDuplicates(arr) {
// Accepts an array from which the duplicates
// will be removed
if (!Array.isArray(arr)) {
arr = [];
}
let theSet = new Set(arr);
arr.filter((num) => {
if (!uniqueArr.includes(num)) {
uniqueArr.push(num)
}
})
return uniqueArr;
}
/* code de test */
let myNums = [1, 2, 3, 1, 4, 1, 2, 5, 3, 4];
let uniqueNums = removeArrayDuplicates(myNums)
console.log(uniqueNums);
Pour cela inutile d’aller sur le site web de NPM ! vous pouvez tout faire en ligne dans votre terminal. Enlevez le code inutile d’application qui vient après la fonction.
npm login
// entrez vos identifiants
npm publish
j’ai eu un problème car le nom du paquet existe déjà, donc ce qu’on peut faire c’est de changer vers un nom qui n’existe pas encore
Un événement est par exemple un click de souris, une touche de clavier enfoncée, mais ça peut être purement logiciel, par exemple lorsque la page HTML a fini de se charger, il se produit un événement de type onload.
Un événement de requête AJAX
Ce type d’événement permet de détecter lorsqu’une requête AJAX est émise.
(function() {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
console.log('request started!');
this.addEventListener('load', function() {
console.log('request completed!');
console.log(this.readyState); //will always be 4 (ajax is completed successfully)
var text = this.responseText
toto = document.querySelector('#toto')
toto.innerHTML = toto.innerHTML + text
});
origOpen.apply(this, arguments);
};
})();
Vous avez sans doute souvent vu dans un script javascript moderne ceci:
import Component from '@/components/component'
Normalement si on veut importer un module Javascript, il faut utiliser les / et les .. soit pour remonter d’un niveau soit pour descendre d’un niveau, comme on le ferait dans n’importe quel langage de programmation.
Cette notation n’est pas du Javascript natif, elle est rendue possible par l’utilisation d’un plugin de type module loader ou module bundler.
C’est un plugin comme babel plugin root import, qui permet d’avoir cette syntaxe. Mais de quel root? le root du projet javascript en question. Ainsi quelque soit l’emplacement du plugin et de la page qui demande le plugin, on utilisera l’arobase pour atteindre un plugin comme si on le demandait depuis la racine du projet.
La syntaxe JSX n’est pas forcément facile à appréhender, imaginez du HTML dans un return…
Pour faire une boucle sur un tableau afin d’afficher une liste par exemple d’élément JSX, il existe plusieurs façons, mais la plus répandue (99% semble-t-il) est d’utiliser map qui est une méthode de l’objet Array, qui retourne un tableau avec le même nombre d’éléments que le tableau d’entrée)
Cette thématique est rarement abordée, mais je trouve qu’il est intéressant d’ajouter des raccourcis clavier à votre application. Il existe des plugin qui le font déjà, mais back to basics, regardons comment on le fait avec du Javascript pur
Ecouter les touches alpha numérique de base
document.addEventListener('keydown', function (event) {
if (event.key === 'a') {
//votre code javascript ici
}
if (event.key === 'd') {
//votre code javascript ici
}
});
Il est assez simple d’ajouter un listener, mais l’astuce est de le faire sur l’élément document, comme ça, ça concernera toute votre page et on utilisera la délégation d’événement.
Ecouter les touches spéciales
document.addEventListener('keydown', function (event) {
// CTRL + D combo
if (event.ctrlKey && event.key === 'd') {
//votre code
}
// CTRL + L combo
if (event.ctrlKey && event.key === 'l') {
//votre code
}
});
Eviter les conflits avec les autres raccourcis clavier de votre navigateur, par exemple sur Windows ou Linux, CTRL + D sert à bookmarker une page, pour éviter cela on va utiliser le célèbre preventDefault !!
document.addEventListener('keydown', function (event) {
event.preventDefault()
// CTRL + D combo
if (event.ctrlKey && event.key === 'd') {
//votre code
}
// CTRL + L combo
if (event.ctrlKey && event.key === 'l') {
//votre code
}
});