Logomotion: mise à jour introduction framework javascript

This commit is contained in:
wpetit 2018-01-09 22:52:41 +01:00
parent 3a14479bc1
commit f1d44dc9ed
14 changed files with 429 additions and 18 deletions

View File

@ -379,12 +379,12 @@ Implémenter une application minimaliste de gestion de tâches avec Riot. Cette
|Avantages|Inconvénients| |Avantages|Inconvénients|
|:-|:-| |:-|:-|
|Grande communauté, très active | Changement de license puis rétropédalage en 2017 | |Grande communauté, très active | Changement de license puis rétropédalage en 2017 |
| Maintenu et utilisé par Facebook en interne | Nécessité de mettre en place un pipeline de transpilage | | Maintenu et utilisé par Facebook | Nécessité de mettre en place un pipeline de transpilage |
| Architecture Flux | Architecture Flux | | Architecture Flux | Architecture Flux |
| Possibilité de faire des applications "isomorphiques" avec NodeJS | | Possibilité de faire des applications "isomorphiques" avec NodeJS |
| "React Native" | | Projet "React Native" |
| API stable | | API stable |
| Virtual DOM | | Virtual DOM ([voir cet article](https://auth0.com/blog/face-off-virtual-dom-vs-incremental-dom-vs-glimmer/)) |
| Moteurs "lightweight" compatibles | | Moteurs "lightweight" compatibles |
--- ---
@ -601,6 +601,8 @@ export default Clock
![center](./img/flux-simple-f8-diagram-with-client-action-1300w.png) ![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 ## Exercice
@ -641,6 +643,8 @@ Implémenter une application minimaliste de gestion de tâches avec React (sans
|Long-term support (1 an)| |Long-term support (1 an)|
|Documentation exhaustive| |Documentation exhaustive|
|Ember Data| |Ember Data|
|Ember Inspector|
|Ember CLI|
--- ---
@ -651,7 +655,6 @@ Implémenter une application minimaliste de gestion de tâches avec React (sans
### Templates ### Templates
### Modèles ### Modèles
### Contrôleurs ### Contrôleurs
### Composants
--- ---
@ -668,18 +671,53 @@ ember serve
--- ---
## Routes ## Routes (1)
### Générer une nouvelle route ### Générer une nouvelle route
```shell ```shell
ember generate route foo ember generate route posts
``` ```
--- ---
## Templates ## 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 ```handlebars
{{!-- Interpolation de variable --}} {{!-- Interpolation de variable --}}
{{ myVar }} {{ myVar }}
@ -691,16 +729,183 @@ ember generate route foo
{{#block params... }} {{#block params... }}
... ...
{{/block}} {{/block}}
{{!-- Helpers imbriqués --}}
{{sum (multiply 2 4) 2}}
``` ```
--- ---
## Modèles ## 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 ### Générer un nouveau modèle
```shell ```shell
ember generate model bar 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"
}
});
``` ```
--- ---

@ -0,0 +1 @@
Subproject commit 51eafba3eadbbce1833b76d11667461e14cb5197

View File

@ -0,0 +1 @@
{ "presets": ["react", "es2015"] }

View File

@ -0,0 +1 @@
/node_modules

View File

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import HelloWorld from './components/hello-world'
import PropsExample from './components/props-example'
import Clock from './components/clock-lifecycle'
import Counter from './components/counter'
ReactDOM.render(<HelloWorld />, document.getElementById('app'))
ReactDOM.render(<Clock />, document.getElementById('clock'))
ReactDOM.render(<Counter />, document.getElementById('counter'))

View File

@ -0,0 +1,34 @@
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

View File

@ -0,0 +1,26 @@
import React from 'react'
class Clock extends React.Component {
constructor(props) {
super(props)
this.state = {
time: new Date()
}
setInterval(this.tick.bind(this), 1000);
}
render() {
return (
<div>Time: { this.state.time.toString() }</div>
)
}
tick() {
this.setState({ time: new Date() });
}
}
export default Clock

View File

@ -0,0 +1,41 @@
// 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

View File

@ -0,0 +1,17 @@
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

View File

@ -0,0 +1,13 @@
import React from 'react'
class PropsExample extends React.Component {
render() {
return (
<span>{ this.props.text }</span>
)
}
}
export default PropsExample

View File

@ -0,0 +1,27 @@
{
"name": "react-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"css-loader": "^0.28.8",
"path": "^0.12.7",
"style-loader": "^0.19.1",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.10.0"
},
"scripts": {
"start": "webpack-dev-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>"Simple" React with Webpack</title>
</head>
<body>
<div id='app'></div>
---
<div id='props-example'></div>
---
<div id='clock'></div>
---
<div id='counter'></div>
<script src='/bundle.js'></script>
</body>
</html>

View File

@ -0,0 +1,20 @@
var webpack = require('webpack')
var path = require('path')
module.exports = {
entry: path.resolve(__dirname, 'app'),
output: {
path: __dirname + '/dist',
publicPath: '/',
filename: 'bundle.js'
},
devServer: {
contentBase: path.resolve(__dirname, 'public')
},
module: {
loaders: [
{test: /\.js$/, exclude: /node_modules/, loaders: ['babel-loader']},
{test: /(\.css)$/, loaders: ['style-loader', 'css-loader']}
]
}
}

View File

@ -22,7 +22,6 @@ class AppKernel extends Kernel
{ {
return array( return array(
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new Symfony\Bundle\TwigBundle\TwigBundle(),
); );
} }
@ -32,15 +31,15 @@ class AppKernel extends Kernel
// PHP equivalent of config.yml // PHP equivalent of config.yml
$c->loadFromExtension('framework', array( $c->loadFromExtension('framework', array(
'secret' => 'S0ME_SECRET', 'secret' => 'S0ME_SECRET',
'templating' => [ // 'templating' => [
'engine' => [ // 'engine' => [
'twig' // 'twig'
], // ],
], // ],
)); ));
$c->loadFromExtension('twig', [ // $c->loadFromExtension('twig', [
'default_path' => __DIR__.'/../templates', // 'default_path' => __DIR__.'/../templates',
]); // ]);
} }
// Configuration des routes // Configuration des routes