|
M2 Miage NTDP et MBDS : Server Side JS, MongoDB, frameworks JS
De $1
Introduction
Cette année on va voir NodeJS/MongoDB/REST WS/Clients JS.
Séance 1 - présentation de NodeJS et premiers exercices simples
Outils à installer
- Un bon éditeur de code prêt pour le dev front-end: sublime text 3, visual studio code, atom, etc. Pensez à installer les packages emmet, babel, etc.
- Si vous êtes sous windows, un bon terminal, acceptant le copier/coller, la colorisation, le multi-onglet ou multi-fenêtre, par exemple http://cmder.net/, powershell (de microsoft, déjà installé sous win10), hyperterminal (payant), console2, powercmd, colorconsole, mobaextrem, terminal wings, conemu,
Casablanca : exemples JsBin sur les objets JavaScript, fait en cours: http://jsbin.com/nemaxonani/edit?js,console
TP - routage avec le module ExpressJS, Web Services RESTFUL
Supports de cours: transparents de présentation de Express
- TP:
- Récupérez cette archive, dezippez là quelque part, avec la ligne de commande, faites cd dans le répertoire TP_ROUTAGE_EXPRESS_CRUD_AJAX créé, et exécutez la commande "npm install".
Ceci devrait créer un sous directory node_modules contenant les modules nécessaires (dans notre cas: express et multer). - Suivez les consignes du prof et les explications au tableau.
-
Les bonnes pratiques pour créer une API REST: https://blog.octo.com/designer-une-api-rest/ - FACULTATIF: Avec Multer on peut aussi uploader des fichiers Testez cet exemple qui fait de l'upload de fichiers. Pensez à installer les modules express et multer dans le répertoire avant d'exécuter.
TP - MongoDB
- Vous allez, dans cette séance apprendre les bases de l'utilisation de MongoDB depuis une application écrite en JavaScript et exécutée avec NodeJS. Dans un second temps, vous ré-utiliserez le code qui fait le CRUD sur une base d'exemples de MongoDB, en le déplaçant dans des méthodes app.get, app.post etc du TP de la séance 3, pour faire une application utilisable depuis des URLs.
1 - Installer MongoDB sur votre machine
Suivez les installations propres à votre système d'exploitation situées sur cette page du site officiel. Ne choisissez pas l'installation manuelle.
Une fois installé lancez la commande "mongod" en ligne de commande. Si le daemon mongoDB ne se lance pas, regardez les erreurs.
Sur mon Mac j'ai fait:
brew update brew install mongodb mkdir -p /data/db chmod 777 /data/db (pour autoriser un user lambda à écrire dedans) mongod (pour exécuter MongoDB)
2 - Créer et peupler une collection par défaut (avec un dataset issu du tutorial MongoDB officiel)
- Suivre les instructions situées sur cette page. Vous allez devoir récupérer le fichier primer-dataset.json (assez gros, attention, ne pas faire copier/coller), et exécuter la commande suivante, qui créer une bd "test" et ajoute dedans la collection "restaurants" :
- Une fois l'importation faite, garder l'onglet ouvert sur cette page, car on va continuer le tutorial à l'étape 5.
3 - Installer un outil d'administration sympa
Je vous recommande fortement MongoChef, un produit commercial mais gratuit pour utilisation non commerciale. Le télécharger sur: https://studio3t.com/
4 - Ouvrir 3T STudio et voir la collection que l'on vient d'importer
- S'assurer que MongoDB tourne (mongod doit être lancé, ou bien vous devez avoir installé MongoDB en service)
- Lancer l'application 3T Studio, connectez-vous sur MongoDB. Les valeurs par défaut doivent être Ok, normalement.
- Ouvrir la collection "restaurants" dans la base "test"
Quelques screenshots:
5 - Installer le driver MongoDB pour NodeJS
- Allez dans le répertoire du TP précédent (routage avec express etc)
Exécutez npm install -save mongodb - Cette dernière commande ne fait qu'installer le driver mongodb pour nodeJS dans le répertoire node_modules.
6 - Tester des requêtes CRUD avec NodeJS et express
- Récupérez cette archive qui est une version modifiée de l'exercice précédent: TPRoutageExpressWithMongo3.zip
- Dezipper, allez dans le répertoire créé (
TPRoutageExpressWithMongo3 ) et faites "npm install " - Exécutez
node serverCrudWithMongo (ou nodemon) - Ouvrez localhost:8080
- Etudiez le code, regardez en particulier comment, quand on clique sur le premier bouton GET AJAX un tableau est construit dynamiquement.
- Etudiez ce que le prof a fait en live coding: la suppression et la modification d'une donnée.
A FAIRE : essayer d'ajouter un findByName et un count à l'application précédente.
Corrections pour le count et pour le findByName:
Voir la doc de MongoDB pour les requêtes comprenant des expressions régulières: https://docs.mongodb.com/manual/refe...r/query/regex/
Dans serverCrudWithMongo.js:
// ICI c'est autorisé par la norme REST car
// "count" est un mot réservé, on ne risque pas de
// le prendre pour une TABLE ou une collection
// cf la partie "reserved words" de
// https://blog.octo.com/designer-une-api-rest/
app.get('/api/restaurants/count', function(req, res) {
// on renvoie le nombre de restaurants
mongoDBModule.countRestaurants(function(data) {
var objdData = {
msg:"Count effectué avec succès",
data: data
}
res.send(JSON.stringify(objdData));
});
});
// On va récupérer des restaurants par un GET (standard REST)
// cette fonction d'API peut accepter des paramètres
// pagesize = nombre de restaurants par page
// page = no de la page
// Oui, on va faire de la pagination, pour afficher
// par exemple les restaurants 10 par 10
app.get('/api/restaurants', function(req, res) {
// Si présent on prend la valeur du param, sinon 1
let page = parseInt(req.query.page || 0);
// idem si present on prend la valeur, sinon 10
let pagesize = parseInt(req.query.pagesize || 10);
let nom = req.query.nom;
if(nom) {
// find by name
mongoDBModule.findRestaurantsByName(nom, page, pagesize, function(data) {
var objdData = {
msg:"restaurant recherchés par nom avec succès",
data: data
}
res.send(JSON.stringify(objdData));
});
} else {
// find normal
mongoDBModule.findRestaurants(page, pagesize, function(data) {
var objdData = {
msg:"restaurant recherchés avec succès",
data: data
}
res.send(JSON.stringify(objdData));
});
}
});
Dans app_modules/crud-mongo.js:
exports.countRestaurants = function(callback) {
console.log("DANS COUNT")
MongoClient.connect(url, function(err, client) {
var db = client.db(dbName);
db.collection('restaurants')
.count(function(err, res) {
console.log("COUNT = " + res)
callback(res);
});
});
};
exports.findRestaurantsByName = function(nom,page, pagesize, callback) {
MongoClient.connect(url, function(err, client) {
var db = client.db(dbName);
console.log("pagesize = " + pagesize);
console.log("page = " + page);
console.log("FIND BY NAME nom=" + nom);
// syntaxe recommandée
// Cf doc mongodb: https://docs.mongodb.com/manual/reference/operator/query/regex/
// The $regex value needs to be either the string
// pattern to match or a regular expression object.
// When passing a string pattern, you don't include
// the / delimitters
// VERSION avec $regexp et $options
let myquery = {
"name": {
$regex: ".*" + nom + ".*",
$options:"i"
}
}
// VERSION avec objet RegExp
//let myquery = {'name' : new RegExp('^.*'+nom+'.*$',"i")};
// ou, si on veut être "case sensitive"
//let myquery = {'name' : new RegExp('^.*'+nom+'.*$')};
if(!err){
db.collection('restaurants')
.find(myquery)
.skip(page*pagesize)
.limit(pagesize)
.toArray()
.then(arr => callback(arr));
}
else{
callback(-1);
}
});
};
Séance 3: rappels JavaScript objet et asyncrhone, introduction aux frameworks Les classes ou pseudo classes en JavaScript - A FAIRE : vous inscrire au MOOC JavaScript Intro, les objets sont détaillés tout au long du MOOC, mais pour gagner du temps, le plus intéressant est dans le module 4 et le module 5. Ce cours d'intro ne parle pas de l'héritage ou de design patterns. C'est pourquoi je vous donne des exemples ci-dessous que nous détaillerons en classe.
Ressources pour l'héritage : - Le plus simple pour commencer est de tester des exemples simples que je vous ai préparé sur JsBin:
- ES5 :
- Exemple 1 qui couvre les cas "classiques" : classes et sous-classes, héritage de propriété et de méthodes. Différentes syntaxe d'appel des constructeurs hérités (avec et sans utilisation de la méthode
call ) - Exemple 2 qui montre comment redéfinir une méthode héritée sans changer son nom, et en gardant la possibilité d'appeler la méthode de la classe dont on hérite (équivalent du
super.methode ...) - Exemple 3 avec le Black Box model (qui n'est pas de l'héritage mais de la composition, on peut quand même simuler l'héritage et la redéfinition, vous verrez, mais l'opérateur instanceof ne donnera plus l'appartenance des instances à plusieurs types en même temps).
- Pour en savoir plus sur la l'héritage et la définition de pseudo-classes avec la syntaxe JavaScript classique, voir ce cours en français sur le Mozilla Developer Network
Introduction aux frameworks Une todolist en différentes versions JS/jQuery/VueJS/VueJS + fetch API: Introduction à ReactJS A faire avant le TP: - Vous devez avoir nodeJS correctement installé (node, npm, etc, tout ceci doit fonctionner...)
- Installer l'extension Google Chrome "React Developer Tools" (https://chrome.google.com/webstore/d...jfkapdkoienihi), elle existe aussi pour Firefox
- Installer une bonne fenêtre terminal pour Windows, si vous êtes sous windows, comme Hyperterminal ou http://cmder.net/
- Installer les package "babel" et "emmet" pour Sublime Text 3 ou pour Atom, selon l'éditeur de code que vous utilisez
- Récupérez les sources du cours React (cd dans le répertoire du TP -creez-le!- et exécutez git clone https://github.com/wesbos/React-For-...-Starter-Files)
- Ouvrir la ligne de commande et exécuter la commande "npm install -g create-react-app",
- Pour vérifier que ça fonctionne, créez un autre répertoire nommé "helloWorld" et cd dedans,
- executer "create-react-app hello-world" (ça prend du temps, quelques minutes)...
- Allez dans le répertoire créé (cd hello-world), et faites "npm install" (là aussi, quelques minutes)
- Exécutez le progralle en tapant "npm start", ça doit ouvrir automatiquement le browser sur le port 3000 et afficher une page avec le logo React qui tourne.
- Editez le code du fichier src/App.js, changez le texte et sauvez -> la page Web de l'application doit se rafraichir automatiquement.
A faire pour la séance de vendredi : Introduction à Angular (4/5) TP Partie 1 : création d'une application "squelette" -
Installer angular-cli : dans une fenêtre de terminal, tapez la commande "npm install -g @angular/cli " (éventuellement avec "sudo" avant si vous êtes sous Linux ou Mac OS) - Créez un répertoire "exo1Angular" et faites cd dans ce répertoire,
- Générez une application "squelette" angular en tapant la commande "
ng new angular-framework-intro " - Faites
cd angular-framework-intro et ouvrez visual studio code sur ce répertoire (commande "code . " sur Mac) - Faites
npm install pour installer les modules nécessaires (pour l'application et pour l'environnement de développement) - Exécutez le projet en lançant la commande
ng serve --open dans le répertoire - Ok, maintenant faites "view source" sur la page, que remarquez-vous ? Comparez avec React et VueJS... Qu'en déduisez-vous ? A propos, est-ce qu'on a installé une extension dans le browser ?
- Etudions maintenant la structure du projet....
TP Partie 2 : affichage d'une liste d'éléments - Regardez le fichier
index.html , remarquez l'élément <app-root></app-root> , est-ce du HTML standard ? - Regardez les fichiers
src/app.component.ts , src/app.component.html et src/app.component.css - Modifiez le fichier de templates
app.components.html et faites ctrl-s, - Regardez le fichier app.module.ts, Angular va vous obliger à mettre à jour ce fichier très régulièrement.
- Maintenant on va effacer le contenu du fichier de template, et on va essayer d'afficher une liste d'éléments : CA SE PASSE EN LIVE CODING (une correction sera postée ici à la fin), utilisation de *ngFor (
<li *ngFor="let el of elements">...</li> si elements est la propriété du composant -un tableau de chaines de caractères-), un peu l'équivalent du v-for de VueJS. - Ajout d'un bouton pour ajouter un nouvel élément, utilisation de
(click)="expression" comme attribut, - Regardons comment afficher une ligne verte, une ligne rouge, etc. Utilisation de
[ngStyle]="{propriété CSS : expression}" , écrire une fonction getColor(element) comme dans les exemples qu'on avait fait avec VueJS et React, et l'appeler dans l'expression). TP partie 3 : création d'un nouveau composant -
On va créer "manuellement" un nouveau composant intitulé "username", qui sera juste un<li> qui affichera la valeur de la propriété username. Pour cela, créer le fichier "app/username.component.ts, et il doit ressembler à cela: import { Component, Input } from "@angular/core";
@Component ({
selector: 'app-username',
template:`
<p>{{username}}</p>
`
})
export class UsernameComponent {
@Input() username :string;
} Remarquez qu'on n'a pas utilisé ici de template html séparé, à la place on a mis le template dorectement sous forme de chaine de caractère entre les caractères `....` Le @Input sert à définir une propriété qu'on pourra passer via des attributs HTML, c'est l'équivalent des "props" de VueJS et de React. Maintenant, ajoutez des instances de ce composant dans le template app.components.html: <app-username [username]="'Michel'"></app-username>
<app-username [username]="'John'"></app-username>
Regardez ce que cela donne.... rien, ouvrez la console de debug... C'est normal, il faut déclarer le composant dans le fichier app.module.ts (c'est pénible... mais vous verrez qu'on peut automatiser cela en créant les composants avec la ligne de commande)... Ajoutez le composant dans app.module.ts et cela devrait fonctionner. Maintenant on veut pouvoir cliquer sur un nom et afficher un message dans le template du composant principal. On va faire comme en VueJS : envoyer un événement quand on clique sur le nom. TP Partie 4 : définition d'un événement qui sera envoyé du composant "fils" vers le composant "père" - Dans le composant (app/username/username.component.ts), définir un événement comme ceci:
export class UsernameComponent {
@Input() username :string;
@Output() userClicked = new EventEmitter<string>(); // définition d'un événement
onUsernameClicked() {
this.userClicked.emit(this.username); // émission de l'événément
}
} Et dans le template (app/username/username.component.ts ) : template:`
<p (click)="onUsernameClicked()">{{username}}</p>
`
Ainsi, dans le template du composant (app.component.html ) qui instancie ce composant, on pourra écouter l'événement "userClicked" : <app-username [username]="'Michel1'" (userClicked)="onUserWasClicked($event)"></app-username>
<app-username [username]="'John'" (userClicked)="onUserWasClicked($event)"></app-username>
Et maintenant on peut écrire la méthode onUserWasClicked(username) dans app.component.ts : onUserWasClicked(username: string) {
alert(username);
} Testez l'application : en cliquant sur un nom, ça doit afficher une alerte avec la valeur associée. TP Partie 5 : binding bi-directionnel avec [(ngModel)]="nom" - Définissez un champs de saisie dans le template pour une propriété "nom" que vous ajouterez dans le fichier .ts de app.component.ts
- Dans le template, affichez la valeur de cette propriété en-dessous du champs de saisie.
- Tapez quelque chose dans le champ de saisie
- Plus simple qu'en React non ? Ca ressemble au v-model de VueJS non ?
TP Partie 6 : refaire l'exemple des hobbies complet, mais en angular En vous aidant de l'exemple précédent : - Dans le composant
app.component (.ts/html/) , définissez un tableau de hobbies et affichez-les dans une liste (fournissez quelques hobbies par défaut) - Ajoutez un bouton 'Nouveau Hobby' + un champ <input>. Quand on clique le bouton on ajoutera le hobby et la vue se mettra à jour
- Rendez chaque hobby clickable. Si on clique un hobby on le supprime
- Faites apparaitre un message <p>Hobby supprimé! dans un </p> qui ne sera visible que lorsque au moins un hobby a été supprimé (soyez créatifs! Il y a plusieurs manières de faire)
- Ajouter un compteur de hobbies (<p>Nombre de Hobbies: ...</p>) au-dessus de la liste des hobbies
- Changez le style CSS du compteur, selon sa valeur: vert si moins de 3 hobbies, rouge sinon
- Transformez les éléments <li> en un composant réutilisable <app-hobby>
- On veut remplit les hobbies au démarrage de l'application en récupérant les hobbies au format JSON sur l'URL :
https://my-json-server.typicode.com/...ffa/hobbies/db (au passage, j'ai utilisé les services de jsonplaceholder.typicode.com pour servir un fichier json qui se trouve sur un de mes repository github). LIVE CODING AVEC LE PROF pour voir comment créer un service TP Partie 7 : exercice de routage avec Router et Route, <router-outlet></router-outlet> et <a routerLink="..."></a> LIVE CODING !!! Correction du TP - Récupérez cette archive : hobbies_angular.zip et desarchivez-la quelque part,
- Faites cd dans le répertoire créé,
- Faites "npm install",
- Faites "ng serve --open"
Mini projet à rendre avant le 31 Janvier (MBDS) Vous devrez réaliser, avec un des trois frameworks vus en cours (VueJS, React, Angular 4/5), une application "galerie vidéo" présentant des vidéos YouTube faites par les élèves du MBDS, 30 secondes maximum, présentant le MBDS en entier ou juste une de ses facettes. Cahier des charges: - Description générale
- L'application est une galerie de vidéos. On suppose que les vidéos ont été postées sur YouTube, et qu'elles sont accessibles via leur URL. Je suggère que la promo fasse une chaine MBDS dans laquelle l'ensemble des vidéos sera posté.
- L'application que vous allez développer permettra d'ajouter/modifier/supprimer/afficher des vidéos sous la forme d'une galerie.
- Visualisation de la gallerie / lecture des vidéos
- Une fois arrivé sur la page de départ, on voit une liste de vidéos (par défaut elles ne sont pas en lecture, on ne voit que le lecteur vidéo en mode "statique", on peut imaginer aussi afficher une image par vidéo, si on clique sur l'image ça lance la vidéo), sous la vidéo on voit sa légende (une ligne, par exemple le nom de la personne sur la vidéo ou le titre de la vidéo).
- Si on clique sur une vidéo, ça joue la vidéo, et on peut voir sa description. On utilisera simplement dans le template d'un composant que vous crééerez pour jouer la vidéo, le code HTML (une iframe) proposé par YouTube pour insérer une vidéo dans une page Web.
- A priori on n'a pas besoin de pagination, on affichera toutes les imagettes des vidéos sur une simple page.
- Facultatif : faire de la pagination et permettre à l'application de gérer un plus grand nombre de vidéos.
- Facultatif : Possibilité de noter la vidéo avec des étoiles (1 à 5 étoiles), dans un premier temps on ne vérifiera pas qu'un utilisateur puisse voter plusieurs fois. On essaiera de réfléchir à un système essayant d'interdire les votes multiples, sans que l'utilisateur ait besoin de s'authentifier.
- Ajout d'une vidéo
- Un bouton ou une entrée de menu permettra d'ajouter une nouvelle vidéo. On demandera l'URL de la vidéo YouTube, une description de quelques lignes, et une légende pour afficher sous la vidéo. On vérifiera que la vidéo n'a pas déjà été ajoutée. On vérifiera aussi que les champs description et légende ne sont pas vides avant de publier la vidéo.
- Facultatif : utiliser l'API de YouTube pour récupérer la description de la vidéo et la légende (le titre de la vidéo) directement sur YouTube. On supposera dans un premier temps que la clé d'API est codée "en dur", vous pourrez, si vous avez le temps et si vous êtes à l'aise, prévoir un menu "paramètres" dans lequel vous pourrez coller la clé d'API qui sera utilisée par l'application.
- Modification d'une vidéo
- On pourra modifier la description ou la légende d'une vidéo après publication. Ce ne sera pas modifié sur YouTube, ou bien (facultatif) optionellement (case à cocher ?)
- Suppression d'une vidéo
- On pourra ajouter un bouton ou une croix pour supprimer une vidéo. La vidéo ne sera supprimée que de votre base de données, pas sur YouTube.
BACK END : - Vous utiliserez soit MongoDB et NodeJS/Express comme dans les TPs, soit FireBase.
- Attention, si vous faites un projet "serveur" avec node + express + mongo, et un autre projet "front end", assurez-vous que la configuration du serveur node accepte bien les requêtes cross-domain.
Exemple de code de configuration pour le cross domain: app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
Modalités de rendu : - Projet à faire en binome, à rendre avant le 31 Janvier.
- Repository github obligatoire, je veux la doc sur le README.md, je clonerai le répertoire, je suivrai vos instructions pour lancer le projet. Si ça ne marche pas -> des points en moins.
Par exemple : 1) faire npm install 2) lancer "npm start" Autre exemple : 1) creez une base mongo comme ceci 2) vérifier que la commande "mongod" est lancée 3) faites "npm install" 4) lancez "node server.js" 5) ouvrez "localhost:8081", si le port ne vous convient pas vous pouvez le changer dans le fichier server.js OPTIONS POSSIBLES : Faites-vous plaisir, si vous voulez ajouter des options n'hésitez pas, si vous voulez faire une app mobile avec React Native, ok aussi. CE QUI EST INTERDIT: Ce qui est interdit c'est d'utiliser une autre technologie que celles vues en cours. Ou de me refourguer un truc piqué sur le Web.
|