formations/developpement/intro_javascript/presentation/slides.md

927 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
pre { font-size: 0.5em !important; }
table { font-size: 0.6em !important; }
</style>
# Frameworks Javascript - Tour d'horizon
## William Petit - S.C.O.P. Cadoles
---
<!-- page_number: true -->
## Les frameworks
## RiotJS
## ReactJS
## EmberJS
---
## RiotJS
### Présentation générale
### Concepts généraux
### Exercice
---
## Présentation générale (1)
- Publication: Septembre 2013
- Mainteneurs principaux: [@GianlucaGuarini](https://github.com/GianlucaGuarini) et [@tipiirai](https://github.com/tipiirai) (projet communautaire)
- License: MIT
---
## Présentation générale (2)
|Avantages|Inconvénients|
|:-|:-|
|Une librairie et pas un framework| Une librairie et pas un framework|
| Projet communautaire | Projet communautaire |
| Orientée "composant" | |
| API minimaliste |
| Prêt à l'emploi (pas de transpilage nécessaire en développement) |
| Routeur "frontend" disponible |
---
## Concepts généraux
### Composants ("tags")
### Observable
---
## Composants
### Structure et utilisation d'un composant
### Templating
### Styles de composant
### Évènements du DOM
### Cycle de vie du composant
---
## Structure d'un composant
```html
<!-- components/my-tag.tag -->
<!-- Racine du Tag -->
<my-tag>
<!-- Définition du DOM -->
<span>{ opts.foo }</span>
<!-- Style du composant -->
<style>
:scope {
display: block
}
</style>
<!-- Script du composant -->
<script>
this.on('mount', () => {
this.opts.foo = "bar";
});
</script>
</my-tag>
```
---
## Utilisation du composant
### Compilation dans le navigateur
```html
<html>
<body>
<!-- Utilisation du composant dans notre page HTML -->
<my-tag></my-tag>
<!-- Inclusion de notre "tag" -->
<script type="riot/tag" src="components/my-tag.tag"></script>
<!-- Inclusion de la librairie Riot + compilateur -->
<script type="text/javascript" src="js/riot+compiler.js"></script>
<!-- Montage des composants Riot -->
<script type="text/javascript">
var opts = {}; // Options passées à tous les tags
riot.mount('*', opts)
</script>
</body>
</html>
```
---
## Templating
```html
<my-tag>
<!-- Interpolation simple -->
<span>{ foo }</span> <!-- <span>bar</span> -->
<!-- Boucles -->
<ul>
<li each={ item in myItems }>{ item.text }</li>
</ul>
<!-- Condition avec appel de méthode -->
<span if={ isFooBar() }>foo est égal à "bar" !</span>
<script>
this.foo = "bar"
this.myItems = [
{ text: "Hello World" },
{ text: "Foo Bar" }
];
isFooBar() {
return this.foo == "bar";
}
</script>
</my-tag>
```
---
## Styles de composant
### Feuille de style
### Styles et classes dynamiques
---
## Feuille de style
```html
<my-tag>
<!-- Définition du DOM -->
<span id="foo">Foo</span>
<div class="bar">Bar</div>
<!-- Style du composant -->
<style>
:scope {
display: block
}
#foo {
color: red;
}
.bar {
background: blue;
}
</style>
</my-tag>
```
---
## Styles et classes dynamiques
```html
<my-tag>
<!-- Définition du DOM -->
<span style={ fooStyles }>Foo</span>
<div class={ barClasses }>Bar</div>
<script>
this.fooStyles = {
color: "red"
}
this.barClasses = {
"bar": true, // Classes conditionnelles
"foo": false
}
</script>
</my-tag>
```
---
## Évènements du DOM
```html
<my-tag>
<button onclick={ onButtonClick }>Click me !</button>
<script>
onButtonClick(evt) {
// Si l'élément à l'origine se trouve dans une boucle each={ ...},
// evt.item pointera vers l'élément courant
console.log(evt);
}
</script>
</my-tag>
```
---
## Cycle de vie du composant
```html
<my-tag>
<script>
this.on('before-mount', () => console.log('before-mount'));
this.on('mount', () => console.log('mount'));
this.on('update', () => console.log('update'));
this.on('updated', () => console.log('updated'));
this.on('before-unmount', () => console.log('before-unmount'));
this.on('unmount', () => console.log('unmount'));
</script>
</my-tag>
```
---
## Observable (1)
### Déclaration d'un "service"
```javascript
// services/auth.js
class MyAuthService {
constructor() {
riot.observable(this)
this.user = null;
}
login(user, password) {
var form = new FormData();
form.append('login', login);
form.append('password', password);
return fetch('/login', { method: 'POST', form: form })
.then(res => res.json())
.then(user => {
this.user = user;
this.trigger('loggedIn', user);
})
;
}
isLoggedIn() {
return this.user != null;
}
}
const myAuth = new MyAuthService();
```
---
## Observable (2)
### Utilisation du service
```html
<my-app></my-app>
<!-- Tag "inline" -->
<script type="riot/tag">
<my-app>
<button if={ !user }
onclick={ login }>
Login
</button>
<span if={ user }>User: { user.name }</span>
</my-app>
<script>
this.user = null;
this.on('mount', () => {
this.opts.auth.on('loggedIn', user => {
this.user = user;
this.update();
});
});
login() {
this.opts.auth.login();
}
</script>
</script>
<script type="text/javascript" src="services/auth.js"></script>
<script type="text/javascript">riot.mount('*', { auth: myAuth })</script>
```
---
## Exercice
Implémenter une application minimaliste de gestion de tâches avec Riot. Cette application doit comprendre les fonctionnalités suivantes:
- Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
- Marquer une tâche comme "terminée".
- Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
- Les tâches doivent être persistées dans le "Local Storage" du navigateur.
---
## ReactJS
### Présentation générale
### Concepts généraux
### Prise en main
---
## Présentation générale (1)
- Publication: Mars 2013
- Mainteneur principal: Facebook
- License: MIT
---
## Présentation générale (2)
|Avantages|Inconvénients|
|:-|:-|
|Grande communauté, très active | Changement de license puis rétropédalage en 2017 |
| Maintenu et utilisé par Facebook | Nécessité de mettre en place un pipeline de transpilage |
| Architecture Flux | Architecture Flux |
| Possibilité de faire des applications "isomorphiques" avec NodeJS |
| Projet "React Native" |
| API stable |
| Virtual DOM ([voir cet article](https://auth0.com/blog/face-off-virtual-dom-vs-incremental-dom-vs-glimmer/)) |
| Moteurs "lightweight" compatibles |
---
## Concepts généraux
### Composants
### L'architecture Flux
---
## Composants
### Structure d'un composant
### Props
### State
### Évènements du DOM
### Cycle de vie d'un composant
---
## Structure d'un composant
```jsx
// components/hello-world.js
import React from 'react'
class HelloWorld extends React.Component {
render() {
return (
<div className='hello-world'>
<h1>Hello World</h1>
<p>Welcome to React</p>
</div>
)
}
}
export default HelloWorld
```
---
## Props
```jsx
// components/props-example.js
import React from 'react'
class PropsExample extends React.Component {
render() {
return (
<span>{ this.props.text }</span>
)
}
}
export default PropsExample
// app.js
import React from 'react'
import ReactDOM from 'react-dom'
import PropsExample from './components/props-example'
const mountpoint = document.getElementById('props-example');
ReactDOM.render(<PropsExample text="foo bar" />, mountpoint)
```
---
## State
```jsx
// components/clock.js
import React from 'react'
class Clock extends React.Component {
constructor(props) {
// On fait appel au constructeur de la classe
// parente
super(props)
// Initialisation du "state" du composant
this.state = {
time: new Date()
}
// On appelle la méthode tick() du composant
// toutes les secondes
setInterval(this.tick.bind(this), 1000);
}
// Méthode de rendu du composant
render() {
return (
<div>Time: { this.state.time.toString() }</div>
)
}
// La méthode tick() met à jour le state du composant avec
// la date courante
tick() {
this.setState({ time: new Date() });
}
}
export default Clock
```
---
### Évènements du DOM
```jsx
// components/counter.js
import React from 'react'
class Counter extends React.Component {
constructor(props) {
// On fait appel au constructeur de la classe
// parente
super(props)
// Initialisation du "state" du composant
this.state = {
count: 0
}
}
// Méthode de rendu du composant
render() {
return (
<div>
Count: <span>{ this.state.count }</span>&nbsp;
<button onClick={ () => this.increment() }>+1</button>
<button onClick={ () => this.decrement() }>-1</button>
</div>
)
}
// La méthode increment() incrémente la valeur du compteur de 1
increment() {
this.setState(prevState => ({ count: prevState.count+1 }))
}
// La méthode decrement() incrémente la valeur du compteur de 1
decrement() {
this.setState(prevState => ({ count: prevState.count-1 }))
}
}
export default Counter
```
---
## Cycle de vie d'un composant
```
// components/clock-lifecycle.js
import React from 'react'
class Clock extends React.Component {
constructor(props) {
super(props)
this.state = {
time: new Date()
}
}
componentDidMount() {
console.log('mount')
this.intervalId = setInterval(this.tick.bind(this), 1000);
}
componentWillUnmount() {
console.log('unmount')
clearInterval(this.intervalId);
}
render() {
return (
<div>Time: { this.state.time.toString() }</div>
)
}
tick() {
this.setState({ time: new Date() });
}
}
export default Clock
```
---
## L'architecture Flux
![center](./img/flux-simple-f8-diagram-with-client-action-1300w.png)
[Voir une description des concepts](https://github.com/facebook/flux/tree/master/examples/flux-concepts)
---
## Exercice
Implémenter une application minimaliste de gestion de tâches avec React (sans l'architecture Flux). Cette application doit comprendre les fonctionnalités suivantes:
- Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
- Marquer une tâche comme "terminée".
- Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
- Les tâches doivent être persistées dans le "Local Storage" du navigateur.
---
## EmberJS
### Présentation générale
### Concepts généraux
### Initialisation d'un projet
### Exercice
---
## Présentation générale (1)
- Publication: Décembre 2011
- Mainteneur principal: [Ember Core Team](https://emberjs.com/team/) (projet communautaire)
- License: MIT
---
## Présentation générale (2)
|Avantages|Inconvénients|
|:-|:-|
|"Clés en main"|"Opiniatre"|
|API stable|Structure MVC classique|
|Long-term support (1 an)|
|Documentation exhaustive|
|Ember Data|
|Ember Inspector|
|Ember CLI|
---
## Concepts généraux
### Ember CLI
### Routes
### Templates
### Modèles
### Contrôleurs
---
## Ember CLI
### Générer une nouvelle application
```shell
npm install ember-cli -g
ember new my-app
cd my-app
ember serve
```
---
## Routes (1)
### Générer une nouvelle route
```shell
ember generate route posts
```
---
## Routes (2)
### Attacher un/plusieurs modèles à une route
```javascript
// app/routes/posts.js
import Route from '@ember/routing/route';
export default Route.extend({
model() {
return this.get('store').findAll('post');
}
});
```
---
## Routes (3)
### Effectuer une redirection
```javascript
// app/routes/index.js
import Route from '@ember/routing/route';
export default Route.extend({
beforeModel() {
this.transitionTo('posts');
}
});
```
---
## Templates (1)
### Syntaxe générale
```handlebars
{{!-- Interpolation de variable --}}
{{ myVar }}
{{!-- Utilisation d'un "helper" --}}
{{helper params... }}
{{!-- Définition d'un "block" --}}
{{#block params... }}
...
{{/block}}
{{!-- Helpers imbriqués --}}
{{sum (multiply 2 4) 2}}
```
---
## Templates (2)
### "Blocks" de base
```handlebars
{{!-- Condition --}}
{{if myVar}}
Maybe...
{{else}}
Or not.
{{/if}}
{{!-- Boucle --}}
{{#each people as |person|}}
<li>Hello, {{person.name}}!</li>
{{/each}}
```
---
## Templates (3)
### "Helpers" spécifiques
```handlebars
{{!-- Affiche myVar[key] (accès dynamique aux variables exposées dans le controleur) --}}
{{get myVar "key"}}
{{!-- Affiche le contenu de la (sous) route --}}
{{outlet}}
{{!-- Modèle exposé par la route --}}
{{model}}
{{!-- Affiche un lien vers la route donnée --}}
{{link-to routeName}}
{{!-- Attacher une action à un élément (onclick) --}}
<button {{action "myAction"}}>Do it !</button>
```
---
## Contrôleurs (1)
### Générer un contrôleur
```shell
ember generate controller posts
```
---
## Contrôleurs (2)
### Exposer des variables pour le template
```shell
import Controller from '@ember/controller';
export default Controller.extend({
foo: "bar" // {{ foo }} dans le template associé
});
```
---
## Contrôleurs (3)
### Actions
```shell
import Controller from '@ember/controller';
export default Controller.extend({
foo: "bar"
actions: {
myAction: function() {
// On modifie la valeur de "foo"
// Si "foo" est utilisé dans le template, il sera automatiquement mis à jour
this.set("foo", "hello world");
}
}
});
```
---
## Modèles (1)
### Générer un nouveau modèle
```shell
ember generate model post
```
---
## Modèles (2)
### Définition d'un modèle
```javascript
// app/models/comment.js
import DS from 'ember-data';
export default DS.Model.extend({
// Simple attributs
text: DS.attr('string'),
// Relations
post: DS.belongsTo('post')
});
// app/models/post.js
import DS from 'ember-data';
export default DS.Model.extend({
// Simples attributs
title: DS.attr('string'),
content: DS.attr('string'),
// Relations
comments: DS.hasMany('comment')
});
```
---
## Modèles (3)
### Manipuler une instance de modèle
```javascript
// app/controllers/posts.js
// Créer une nouvelle instance
var post = store.createRecord('post', {
title: 'Mon article',
content: 'Lorem ipsum'
});
// Modifier l'instance
post.set('title', 'Foo bar');
// Sauvegarde de l'instance
post.save().then(() => console.log("Post saved !"));
// Supprimer l'instance
post.destroyRecord().then(() => console.log("Post deleted !"));
```
---
## Modèles (4)
### Effectuer des requêtes sur le "store"
```javascript
// app/controllers/posts.js
// Récupérer toutes les instances de "post"
var posts = this.get('store').findAll('post');
// Requête avec filtre
var posts = this.get('store').query('post', {
filter: {
title: "Foo Bar"
}
});
```
---
## Exercice
Implémenter une application minimaliste de gestion de tâches avec EmberJS. Cette application doit comprendre les fonctionnalités suivantes:
- Ajouter une nouvelle tâche (champs texte) avec une priorité parmi les suivantes: "haute", "moyenne", "basse" et un statut: "en cours" ou "terminé"
- Marquer une tâche comme "terminée".
- Afficher la liste des tâches en attente, triées par priorité décroissante + tâches terminées en dernier.
- Les tâches doivent être persistées dans le "Local Storage" du navigateur.
---
# Licence
## CC BY-NC-SA 3.0 FR
[Creative Commons - Attribution - Pas dUtilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France](https://creativecommons.org/licenses/by-nc-sa/3.0/fr/)