# Frameworks Javascript - Tour d'horizon ## William Petit - S.C.O.P. Cadoles --- ## 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 { opts.foo } ``` --- ## Utilisation du composant ### Compilation dans le navigateur ```html ``` --- ## Templating ```html { foo } foo est égal à "bar" ! ``` --- ## Styles de composant ### Feuille de style ### Styles et classes dynamiques --- ## Feuille de style ```html Foo
Bar
``` --- ## Styles et classes dynamiques ```html Foo
Bar
``` --- ## Évènements du DOM ```html ``` --- ## Cycle de vie du composant ```html ``` --- ## 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 ``` --- ## 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 (

Hello World

Welcome to React

) } } export default HelloWorld ``` --- ## Props ```jsx // components/props-example.js import React from 'react' class PropsExample extends React.Component { render() { return ( { this.props.text } ) } } 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(, 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 (
Time: { this.state.time.toString() }
) } // 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 (
Count: { this.state.count } 
) } // 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 (
Time: { this.state.time.toString() }
) } 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|}}
  • Hello, {{person.name}}!
  • {{/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) --}} ``` --- ## 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 d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 France](https://creativecommons.org/licenses/by-nc-sa/3.0/fr/)