Javascript

Démarrer une application Node/Express Typescript

Bootstrapping du projet Node Express

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 .

On va installer Express

npm install express

On va code le fichier index.js

//index.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`);
});

Passons à Typescript !

Installation de Typescript

npm install typescript

Ensuite renommons le fichier index.js en index.ts !

En survolant les variable maintenant vous allez voir que pas mal de variable sont de type any, ça prouve qu’on est passé nen mode Typescript.

Installaton des fichier de type pour nodeJS et Express

npm install @types/express @types/node

modification des façons d’importer

Dans Typescript on n’utilise pas require mais import

import express, { Express, Request, Response } from 'express';
const app: Express = express();
const port = process.env.PORT || 3000;

app.get('/', (req: Request, res: Response) => {
    res.send('Hello World! in Typescript Now'); // modifié
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`);
});

A ce stade en survolant la varaible express, on voit que c’est typé.

Illustration, vous ne pouvez pas mettre n’importe quoi avec la notation pointée, si l’objet ne possède pas la méthode, vous aurez une erreur.

app.toto  << erreur souligné en rouge dans VSCode

Pour aller plus loin dans le typage et aider Typescript à savoir de quel type est une varaible on postfix avec les deux point

app.get('/', (req : Request, res : Response) => {
    res.send('Hello World!');
});

démarrage du serveur sour Typescript

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)

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, commetn 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

npm install concurrently --save-dev
    "dev": "concurrently \"tsc -w\" \"nodemon dist/index.js\""

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.

Ce tuto est inspiré de cette vidéo Youtube

Uploader en ajax un fichier avec barre de progression

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).

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BLOB upload</title>
</head>

<body>
    <form action="#">
        <input type="file" value="" id="file" name="file"><br>
        <input type="submit" id="uploadBtn" value="UPLOAD">
    </form>
</body>
<script>
...
</script>
</html>

Grâce à l’attribut name on peut faire référence au fichier. Dans la balise script on a le code suivant :

<script>
    const uploadBtn = document.querySelector('#uploadBtn')
    const url = 'http://localhost.test/blobupload.php'

    uploadBtn.addEventListener('click', function (e) {
        e.preventDefault()
        const file = document.querySelector('#file').files[0]
        uploadBlob(file)

    })


</script>

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.

<script>
    const uploadBtn = document.querySelector('#uploadBtn')
    const url = 'http://localhost.test/blobupload.php'

    uploadBtn.addEventListener('click', function (e) {
        e.preventDefault()
        const file = document.querySelector('#file').files[0]
        uploadBlob(file)

    })
  
  function uploadBlob(file) {
        
        const chunkSize = 100_000;
      
        let start = 0

        for (let start = 0; start < file.size; start += chunkSize) {

            const chunk = file.slice(start, start + chunkSize);
            const fd = new FormData();
            fd.set('file', chunk,);


            fetch(url, {
                method: "POST",
                body: chunk
            })
                .then(response => alert('Blob Uploaded'))
                .catch(err => alert(err));
        }
    }
</script>

Note boucle for va saucissonner le fichier en portions égales (à l’exception du dernier tronçon) à chaque itération. Ensuite ce morceau sera uploadé.

Côté backend pour la réception des fichiers en PHP

<?php
//  blobupload.php
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
$result = file_get_contents('php://input');
$written = file_put_contents('cpu.png', $result, FILE_APPEND);
echo $written;

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.

Image que nous allons envoyer.

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 !

Utiliser Rollup pour fusionner vos fichiers javascript

Application d’origine sans bundle JS

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bundler with Parcel</title>
</head>

<body>
    <form action="#">
        <label for="email">Email</label><br>
        <input type="text" value="" id="email"><br>
        <label for="password">Mot de passe</label><br>
        <input type="text" value="" id="password"><br>
        <input type="submit" id="btn" valu="Soumettre">
    </form>
</body>
<script src="./script1.js"></script>
<script src="./script2.js"></script>
<script src="./script3.js"></script>

</html>

Les différents scripts JS

//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

npm install rollup @rollup/plugin-node-resolve --save-dev

Ensuite créer un fichier de configuration

import resolve from '@rollup/plugin-node-resolve';

export default {
  input: 'src/main.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife'
  },
  plugins: [resolve()]
};

Dans le fichier main.js vous importez les fichier Javascript

import './script1.js';
import './script2.js';
import './script3.js';

// etc

Si vous avez un message du genre

Ajoutez une propriété dans package.json

{
    "scripts": {
        "build": "rollup -c"
    },
    "devDependencies": {
        "@rollup/plugin-node-resolve": "^15.2.3",
        "rollup": "^4.18.1"
    },
    "type": "module"
}

Désormais dans le fichier HTML l’inclusion du Js ressemble à ça

//index.html
...
</body>
<script src="./dist/bundle.js"></script>
</html>

A quoi ressemble le bundle.js

(function () {
    'use strict';

    document.querySelector('#email');
    document.querySelector('#password');
    document.querySelector('#btn');

    email.addEventListener('keyup', (event) => {
        if (!isEmailValid$1(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$1 = (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,}))$/
            );
    };

    btn.addEventListener('click', (event) => {
        event.preventDefault();
        if (isEmailValid(email.value) && password.value.length > 8) {
            alert("Form is valid");
        } else {
            alert("Form is invalid");

        }
    });

})();

Comprendre les sockets avec Socket.io

Qu’est qu’un socket?

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.

Créer un fichier package.json comme ci-dessous:

{
  "name": "app-socket",
  "version": "0.1",
  "description": "mon socket.io app",
  "dependencies": {}
}

Installez Express

Express est un framework pour NodeJS en MVC.

npm install express@4

Créez le fichier index.js

const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);

app.get('/', (req, res) => {
  res.send('<h1>Hello world</h1>');
});

server.listen(3000, () => {
  console.log('listening on *:3000');
});

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

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

      #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
      #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
      #input:focus { outline: none; }
      #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages > li { padding: 0.5rem 1rem; }
      #messages > li:nth-child(odd) { background: #efefef; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form id="form" action="">
      <input id="input" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>

Changez le code d’index.js

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

Relancez votre serveur avec node index.js

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 :

io.on('connection', (socket) => {
...
  socket.on('chat message', (msg) => {
    console.log('message: ' + msg);
  });
});

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 :

io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('disconnect', () => {
        console.log('user disconnected');
    });

    socket.on('chat message', (msg) => {
        console.log('message du client: ' + msg);
        io.emit('retour', { propriete1: 'valeur 1', propriete2: 'valeur2' })
    });
});

Configurer VSCode pour débugger

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.

Programmation orientée événéments en Javascript

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

<body>
    <style>
        #popup {
            border: black 1px solid;
            width: 50%;
            height: 10vh;
            border-radius: 5px;
            margin: 10px;
            display: none;
        }
    </style>
    <form action="#" id="formulaire">
        <label for="email">Email</label>
        <input type="text" id="email"><br>
        <label for="name">Name</label>
        <input type="text" id="name"><br>
        <input type="submit" value="Send">
    </form>
    <div id="popup">
        <h1>Form filled !</h1>
    </div>
</body>
    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'
    })

Déjouer les piège du mot clé this en Javascript

Le mot clé this en javascript sert dans 3 contextes différents, et son comportement diffère selon les contexte.

le mot clé this dans une classe

C’est sans doute le plus facile à appréhender, car c’est comme dans les autres langages de programmation comme Java ou PHP.

class Rectangle {
  constructor(hauteur, largeur) {
    this.hauteur = hauteur;
    this.largeur = largeur;
  }
}

Le mot clé this dans une fonction

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ètres 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

<span id="btn">Click Me</span>
<script>
const btn = document.querySelector("#btn")
btn.addEventListener('click',function(event){

console.log(this)

}

</script>

Modifier la valeur de this avec bind,call et apply

const person1 = {
    name: 'Paul',
    surname: 'Auchan',
    sayName: function () {
        return this.name + " " + this.surname;
    }
}

const person2 = {
    name: 'Justin',
    surname: 'News'
}

console.log(person1.sayName.call(person2)); // le this sera le this de person2
//appel avec arguments

const person2 = {
    name: 'Justin',
    surname: 'News'
}

const person3 = {
    name: 'Paul',
    surname: 'Auchan',
    sayName: function (ville) {
        return this.name + " " + this.surname + ', ' + ville;
    }
}
console.log(person3.sayName.call(person2, "Paris")); // le this sera le this de person2

Cas de bind

Autant call et apply se ressemblent, bind n’exécute pas la fonction , il faut l’invoquer manuellement. bind ne fait que binder une variable

//bind
const person4 = {
    name: 'Jules',
    surname: 'Sanchez',
    sayName: function() {
        return this.name + " " + this.surname
    }
}

const person5 = {
    name: 'Rudy',
    surname: 'Hess'
}

const sayPerson2Name = person1.sayName.bind(person2)

console.log(sayPerson2Name())

Simuler le clic d’une souris avec Javascript

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.

Description de MouseEvent

Les 3 coordonnées de la souris dans une page web

Le référentiel d’un élément HTML

Le référentiel de la fenêtre

Le référentiel du document

Mise en apllication de MouseEvent

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MouseEvent</title>
    <script src="MouseEvent.js" defer></script>
    <link rel="stylesheet" href="style.css">
</head>

<body>

    <div class="container">
        <div class="header"></div>
        <div class="main">
            <h1>Titre</h1>
            <div class="card">
                <p>Ceci est un exemple pour illustrer MouseEvent</p>
            </div>
        </div>
    </div>
</body>


</html>

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

Comprendre les modules en javascript

Qu’est ce qu’un module en Javascript?

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?

#index.html
<html>
...
<body>
..
<script src="index.js"></script>
<script src="helper.js"></script>
...

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

// config.js
export let config = {};

// 1.js
import { config } from './config.js'
config.user = "user1"

//2.js
import { config } from './config.js'
config.url = "http://local.test/user1"

// index.html
<script type="module" src="1.js"></script>
<script type="module" src="2.js"></script>
<script type="module">
    import { config } from './config.js'
    console.log(config)
</script>

L’exemple ci-dessus montre que les fichier 1.js et 2.js apportent des nouvelles propriétés

La propriété meta de import

// en reprenant l'exemple précédent

// index.html
<script type="module" src="1.js"></script>
<script type="module" src="2.js"></script>
<script type="module">
    import { config } from './config.js'
    console.log(import.meta)
</script>

On obtient quelques informations sur le module courant.

Le mot clé this dans un module

Dans un module le mot clé this n’existe pas, il est undefined

<script>
  alert(this); // window
</script>

<script type="module">
  alert(this); // undefined
</script>

Les script module sont deferred dans un client

Ce qui suit n’est valable que dans un navigateur

Vous connaissez l’attribut defer dans la balise script.

<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.

async Javascript import
<script async type="module">
  import {counter} from './utils.js';

  counter.count();
</script>

Async, defer, preload, prefetch : tout savoir sur ces attributs

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 et Prefetch sont pour les balises <link>

<link rel="preload" href="style/style.css">
<link rel="preload" href="main.js">

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.

Prefetch pour pré-télécharger des pages web

<link rel="prefetch" href="/articles/" as="document">

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).

Quelle différence entre le Node et les Elements dans le DOM?

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

console.log(nodelist[0] instanceof Element)  // true
console.log(nodelist[0] instanceof Node)  // true
console.log(el instanceof Node)  // true

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.

Quand utiliser Redux

Quand ne pas utiliser Redux

  • budget peu élevé
  • application de taille petite à moyenne
  • UI simple, flux de données simple
  • données statiques

Pours et contres de Redux:

Pour :

  • débug facilité
  • test
  • faire et défaire
  • persistence de state
  • préserve le state d’une page

Contre:

  • complexité
  • verbosité

Débuter avec Greasemonkey

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:

Débugger sous NodeJS avec VSCode

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.

Comment déployer votre propre package sur NPM?

Nous allons voir comment créer et déployer soi-même son package NPM, plutôt cool comme idée non? Voici les étapes :

  • Créer un compte sur le site de NPM
  • écrire notre package
  • tester localement
  • publier notre package sur NPM
  • test grandeur nature de notre package

Créer un compte sur le registre NPM

Voici un fichier par default de package.json, personnalisez le :

{
  "name": "npmpackage",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

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);

Mettez à jour le package.json

{
  "name": "remove-dup-arr",
  "version": "0.1.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "remove",
    "array",
    "duplicate"
  ],
  "author": "refschool <[referencementschool@gmail.com](mailto:referencementschool@gmail.com)>",
  "license": "ISC"
}

Publication de votre package NPM

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

{
  "name": "openweather-wrapper",
  "version": "0.1.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "openweather",
    "meteo",
    "demo"
  ],
  "author": "refschool <[referencementschool@gmail.com](mailto:referencementschool@gmail.com)>",
  "license": "ISC"
}

PS E:\npmpackage> npm publish 
Debugger attached.
npm notice 
npm notice �📦  openweather-wrapper@0.1.0
npm notice === Tarball Contents ===
npm notice 521B index.js    
npm notice 359B package.json
npm notice === Tarball Details ===
npm notice name:          openweather-wrapper
npm notice version:       0.1.0
npm notice filename:      openweather-wrapper-0.1.0.tgz
npm notice package size:  586 B
npm notice unpacked size: 880 B
npm notice shasum:        ca698456c2b3c8691534f370f7539d0c42662724
npm notice integrity:     sha512-oCm4uzyLUsJC3[...]b1V0cR834Cchg==
npm notice total files:   2
npm notice
npm notice Publishing to https://registry.npmjs.org/
+ openweather-wrapper@0.1.0
Waiting for the debugger to disconnect...

Vérifions sur le site NPM la présence de notre package qui est visible pour le monde entier :

Test grandeur nature de notre package NPM

Maintenant que notre package est déployé je me suis rendu compte que quelque chose n’allait pas, il n’est pas utilisable car pas de directive export !

Remédions à cela à la fin du fichier index.js

module.exports = removeArrayDuplicates;

et republions.

Maintenant créons un autre répertoire (qui n’a rien à voir avec notre précédent répertoire) dans lequel nous allons tester notre package

npm install openweather-wrapper

Un répertoire node_modules va être créé.

Créez un fichier test.js avec le code suivant :

const tab = [8, 4, 2, 65, 2, 33, 33, 33, 22, 8, 8];
const removeArrayDup = require('openweather-wrapper');
let unik = removeArrayDup(tab)

console.log(unik)

Félicitation vous avez réussi !

javascript

Mémento de Fetch

Faire une requête GET avec fetch

L’expression la plus simple pour une requête GET avec fetch est :

fetch("https://google.fr")
.then(function(response){
    return response.json
})
.then()

Pour une requête GET pas la peine d’indiquer à la fonction car c’est celui par défaut

Faire une requête POST avec fetch

Le body

let apiUrl = "http://localhost/api/"
            fetch(apiUrl, {
                method: "POST",
                body: {
                    name : 'toto'
                   }
            })

La requête ci-dessus va envoyer un objet JSON contenat l’attribut « name », via la méthode POST (comme si c’était un formulaire)

javascript

Ecouter un événement de réponse AJAX

C’est quoi un événement en Javascript?

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);
    };
})();
javascript

Javascript import et symbole @

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.

Cela permet d’éviter que la syntaxe suivante :

import Component from 'components/component'

Dans ce cas ça va chercher dans /node_modules.

Si on cherche un module dans le projet

import Component from './components/component'
javascript

L’Event Loop dans Javascript

setTimeout n’appartient pas à V8

Javascript est single threaded

Blocking

Async callback and call stack @ 11:25

console.log('hi'); // A

setTimeout(function(){  // B
    console.log('there');
},5000);

console.log('Hello');  // C

Ordre d’appel dans le callstack A, B, C mais le ‘there’ est affiché 5 secondes après. Javascript ne fait qu’une seule chose à la fois

Event Loop

L’event loop regarde s’il reste encore des opérations à faire dans le stack, si le stack est vide, l’Event loop va regarder dans le task queue.

Liens :

JS Async

Latentflip Loupe

Javascript engine talk

javascript

Test de nullité en Javascrip : Cas où l’ordre a une importance

    const handleAccountCreation = async () => {
        const exist = await userExistCheck(result.email)
        console.warn('social login', exist)

        if (exist.exist !== null) {
            socialLogin(result.email)
            console.warn('social login')
        }
        else {
            createAccount(result.email)
            console.warn('social create account')
        }
    }

La condition dans le if échoue si exist est null, car exist.exist reverra une erreur undefined.

Il faut mettre une condition autrement

    const handleAccountCreation = async () => {
        const exist = await userExistCheck(result.email)
        console.warn('social login', exist)

        if (exist == null) {
             createAccount(result.email)
             console.warn('social login')
        }
        else {
            socialLogin(result.email)
            console.warn('social create account')
        }
    }

javascript

Opérateur point d’interrogation ?. en Javascript (Null propagation operator)

A ne pas confondre avec le null coalesce operator en Javascript ?? . Cet opérateur est encore appelé optional chaining operator.

        if (data.error?.message) {
          Alert.alert('Erreur carte',
            `Votre transaction ne s\'est pas terminée correctement Message de Stripe.com : ${data.error.message}`)
        } else {
          return data.id
        }

Si error n’existe pas alors data.error?.message évaluera en undefined

https://ponyfoo.com/articles/null-propagation-operator

react native

Boucler dans un JSX

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)

Façon ES6, la plus compacte avec fonction flèche

<div>
    {montableau.map((item, i) => <ObjectRow obj={item} key={i} />)}
</div>

Méthode avec le mot clé function

<div>
    {montableau.map(function(object, i){
        return <ObjectRow obj={item} key={i} />;
    })}
</div>

Avec la boucle for

Cette méthode est rarement vue mais elle marche

<div>
    for (var i=0; i < objects.length; i++) {
        <ObjectRow obj={objects[i]} key={i}>
    } 
</div>

https://stackoverflow.com/questions/22876978/loop-inside-react-jsx

javascript

Ecouter les événements du clavier avec Javascript

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
  }
});

Les autres touches péciales Alt, Shift etc

alt :  event.altKey
shift : event.shiftKey

Les modules en Javascript (import/export)

Tout d’abord sachez que les modules existent aussi bien ôté serveur que client. Les modules on été inventé pour permettre d’inclure ds fichier de script javascript comme on purrait le faire en PHP avec include.

Il existait plusieur type de module

  • AMD (Asynchronous Module Definition) un des plus ancien cf la librairie require.js
  • CommonJS, le systyme de mdule de NodeJS
  • UMD (universal Module Definition) Universal comme rassemblant AMD et CommonJS.

C’est quoi un module?

Un module est un fichier JS. Pour charger les uns des autres, il y a les mots clé import et export.

Exemple :

// sayHi.js
export function saluer(user) {
  alert(`Bonjour, ${user}!`);
}

Dans un autre fichier qui veut utiliser cette fonction

// main.js
import {sayHi} from './sayHi.js';
saluer('Jean'); // Bonjour Jean

ES2015 et ses apports

Grâce à ES2015, nous avons nativement les instructions import et export côté client. Si nous voulons importer un module dans une page HTML, nous devons utiliser le type module. Pour accéder au fichier HTML ci-dessous, vous devez avoir un serveur local, car ouvrir le fichier directement dans le navigateur avec le protocole file:// ne va pas marcher.

<!doctype html>
<script type="module">
  import { saluer} from './sayHi.js;
 document.body.innerHTML = sayHi('John');
</script>

Les particularités des modules

  • utiliser use strict
  • chaque module a son scope, les varaibles de deux modules sont invisible mutuellement
  • un module est exécutéune seule fois même s’il est importé de multiples fois

Export et import

Nous avons vu que à la base du système de module, ce sont ces deux mot clé qui sont incontournables.

Nous pouvons exporte une constante, un tableau , un objet, ou un fonction, ou encore une classe.

export const Pi = 3.14
export let nom = "Dupont"
expot class User{
   constructor(name){
      this.name = name
   }
} // pas de ; à la fin

function sayHi(user) {
  alert(`Hello, ${user}!`);
}

function sayBye(user) {
  alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // a list of exported variables

Export default

On rencontre très couramment cette syntaxe. Un seul export défault par fichier JS. Et l’import se fait sans accolade.

export default class User { // just add "default"
  constructor(name) {
    this.name = name;
  }
}
// main.js
import User from './user.js'; // et non {User}

donc import utiliser les accolades pour les exports nommés, et pas d’accolade pour les export defaults.

https://javascript.info/modules-intro

https://javascript.info/import-export

Réaliser une application web avec Firebase, Firestore

Firebase d’avoir une base de données NoSQL principalement dans le cloud. Mais ça ne se limite pas à ça, il y a aussi du stockage d fichier, de code, de l’analytics, mais le principal usage à ma connaissance est la base temps réel NoSQL, son avantage est de permettre de mettre sur pied rapidement une application, pour peu qu’on soit familier avec lui.

Création d’un compte Firebase

Pour créer un compte Firebase, vous devez disposer d’un compte Google, et ensuite activer votre compte Firebase.

Contexte du tutoriel

Le contexte de l’application est une application dans une page html, donc pas besoin d’avoir NodeJs, nous verrons qu’il est possible de créer un projet de cette façon et de faire appel aux librairies, mais pour des raisons de simplicité, nous allons au plus simple.

Ensuite vous devez créer un projet et remplir des informations sur votre projet. Vous disposez d’niformation de configuration pour votre application.

Inclusion des scripts

Il y a plusieurs scripts à inclure en fonction de ce que vous voulez faire (analytics ou non) mais le principal fichier à inclure est le firebase-app.js

    <!-- The core Firebase JS SDK is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/7.15.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-auth.js"></script>

    <script src="https://www.gstatic.com/firebasejs/7.15.0/firebase-firestore.js"></script>
    <!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
    <script src="https://www.gstatic.com/firebasejs/7.15.0/firebase-analytics.js"></script>
#exemple de configuration Firebase
        // Your web app's Firebase configuration
        var firebaseConfig = {
            apiKey: "DCZ28p7gilP15WoymVHn-A",
            authDomain: "myapp-bull3503.firebaseapp.com",
            databaseURL: "https://myapp-bull3503.firebaseio.com",
            projectId: "myapp-bull3503",
            storageBucket: "myapp-bull3503.appspot.com",
            messagingSenderId: "2017024",
            appId: "1:2017024:web:e282485405d09",
            measurementId: "G-J4XXXQPHT9"
        };

// initialisation de l'app
firebase.initializeApp(firebaseConfig);

Créer une base de données orientée documents Cloud Firestore

Cette base est en accès public pour une durée de 30 jours, vous pourrez vous y connecter sans mot de passe, passé ce délai, vous recevrez une alerte comme quoi la base ne sera plus accessible en public. Dans la cnsole, vous verrez Realtime Database et Cloud Firestore, quelle est la différence? C’est expliqué sur cet article.

#paramétrage de la base de données
à l'origine le paramétrage est permissif pour vous permettre de tester la base

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.time  < timestampe.date(2020,7,6);
    }
  }
}

Mais au bout de 30 jours vous recevez un mail pour modifier l'accès à la base avec des règles plus strictes.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

Mais au bout de 30 jours vous recevez un mail pour modifier l'accès à la base avec des règles plus strictes.

Créer un utilisateur

Pour créer un utilisateur, chargez d’abord la librairie auth, qui vous permet de créer un utilisateur et de gérer les connexion.

#création d'un utilisateur via un formulaire
        firebase.auth().createUserWithEmailAndPassword(email, password).catch(function (error) {
            // Handle Errors here.
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode, ':', errorMessage)
        });

Si la création d’un utilisateur s’est faite avec succès, vous êtes automatiquement loggé. Si vous n’arrivez pas à créer un utilisateur de cette façon (probablement parce que vous avez oublié d charger la librairie auth) et que vous désirez rapidement en créer un, vous pouvez aller directement sur la console, dans la sidebar de gauche Authentification puis Ajouterun utilisateur

Connexion d’un utilisateur

        firebase.auth().signInWithEmailAndPassword(email, password).catch(function (error) {
            // Handle Errors here.
            var errorCode = error.code;
            var errorMessage = error.message;
            console.log(errorCode, ':', errorMessage)
        });

Pour maintenir la connexion de l’utilisateur entre différentes pages, il vous faudra utiliser le localStorage.

Lire une table

Je suppose que vous incluiez à chaque page les librairie correspondantes.

        var firebaseConfig = {
            ...
        };
        // Initialize Firebase
        firebase.initializeApp(firebaseConfig);
        firebase.auth().signInWithEmailAndPassword(email, password).catch(function (error) {
          ...
        });
#instanciation de la connexion à Firestore
var db = firebase.firestore();

Découvrez comment administrer la base de données depuis le site de Firebase.

Passage de paramètre à une fonction callback

vous connaissez tous la requête Ajax avec JQuery

$.ajax({
url:url,
method:'GET',
success:function(data){
  console.log(data)
}
})

En passant en callback une fonction anonyme, on définit la fonction à la volée, donc pas de problème pour passer des paramètres.

Mais qu’en est-il si on utilise une fonction nommée à la place?

function callback(data) {
console.log(data)
}

On serait tenté d’écrire :

$.ajax({
url:url,
method:'GET',
success:callback(data)
})

ça ne marche pas en effet, vous devez passer la fonction par référence, autrement dit vous devez passer le handler, en ajoutant des parenthèses vous exécutez la fonction. Donc on doit avoir la syntaxe suivante, mais qui amène à poser la question : mais comment on passe le paramètre data?

$.ajax({
url:url,
method:'GET',
success:callback
})

Il existe en Javascript la variable argument, qui fait référence aux paramètres qui sont passés, c’est un tableau commençant à l’index 0 pour le premier paramètre (donc data dans notre cas)

function callback() {
console.log(argument[0])
}

#marche quelquesoit le nombre de paramètres
Retour en haut