Formation Focus
Le 18/08/2015
Pierre Besson

JavaScript
Points d'attention
//Egalité
1 == '1' //=> true
1 === '1' // => false
//Toujours privilégier la triple égalité
// Une fonction JS a un contexte qui lui est propre. Ce contexte se retrouve dans le this.
// Un problème fréquent en JS est le problème de la closure qui correspond à une perte de contexte.
// Ce qu'il faut retenir: quand vous avez un this qui n'est pas correct dans une fonction, il s'agit d'un problème de closure.
const a = ["E1", "E3","E4","E5"];
for(var i=0, aLength = a.length; i< aLength ; i++){
window.setTimeout(
function(){console.log('noClosure', i);}
,1000);
//Closure
window.setTimeout(
(function(p){
return console.log('closure', p);
})(i)
, 1000);
}Promise
// Une Promise permet d'effectuer un traitement asynchrone de manière très lisible
let asyncTreatement = require('../mySuperSvc');
asyncTreatement.then(function success(d){
treatSuccess(d);
}, function error(e){
treatError(e);
});
Text
promise.then(successCb,errorCb).then(otherSuccess, otherCb)Promise
Cas pratique
//Service de chargement
function loadGodsSvc(){
return Promise.resolve([
{nom: 'Zeus', ctcId: 1},
{nom: 'Hades', ctcId: 2},
{nom: 'Poseidon', ctcId: 3},
]);
}
//Ce qui nous intéresse c'est de transformer l'information pour avoir un id plutôt qu'un ctcId.
function traitement(){
loadGodsSvc().then((data)=>{
return data.map((god)=>{
let {nom, ctcId} = god;
return {
name: nom,
id: ctcId
};
});
});
}
//On récupère dans une promesse l'objet suivant
// [{id: 1, name: 'Zeus'} ,...]CommonJS
Nous utilisons CommonJS dans le projet
Chaque fichier est un module qui est un singleton.
Afin de rendre public un contenu il suffit d'utiliser le mot clef:
module.exports = {mySuperObjectToExportOrFunction}
Exercice: https://gist.github.com/pierr/3a2f5a7e3f505b25c961
ES6 / ES2015
https://github.com/lukehoban/es6features#arrows
//On a un objet de la forme
var obj = {
prenom: 'Pierre',
nom: 'Besson'
age: 27,
adresse: "12 rue des fleurs 75000 Paris FRANCE"
};
//On s'intéresse à prénom et age uniquement
//ES5
var nom = obj.nom;
var prenom = obj.prenom;
//ES2015
let {nom, prenom} = obj;Destructing
String interploation
//ES5
var bjr = 'Bonjour';
var name = 'focus';
var str = bjr + ' '+ name+ ' !'
// Bonjour focus !
//ES6
let bjr = 'Bonjour';
let name = 'focus';
let str = `${bjr} ${name} !'
// Bonjour focus !
Fonction declaration
//ES5
var mySuperMixin = {
displayName: 'Pierre',
displayBonjour: function displaybonjour(){
console.log('super fonction');
}
};
//ES6
var mySuperMixin = {
displayName: 'Pierre',
displayBonjour(){
console.log('super fonction');
}
};Fat arrow
// Expression bodies
let odds = evens.map(v => v + 1);
let nums = evens.map((v, i) => v + i);
let pairs = evens.map(v => ({even: v, odd: v + 1}));
// Statement bodies
nums.forEach(v => {
if (v % 5 === 0)
fives.push(v);
});
// Lexical this
let bob = {
_name: "Bob",
_friends: [],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
}babel
libraries
tools
http://facebook.github.io/react/
http://facebook.github.io/react/
https://facebook.github.io/react/docs/component-specs.html
https://facebook.github.io/react/jsx-compiler.html
https://facebook.github.io/react/docs/glossary.html

https://facebook.github.io/flux/

http://facebook.github.io/react/
Focusjs
Archi
Flux dans focus
Flux dans focus
Domains
Une application focus est composée de plusieurs dossiers.
- Le dossier app qui contient l'ensemble du code applicatif.
- Le dossier vendor qui contient semble du code provenant des librairies externes.
- Le fichier package.json contenant l'ensemble des dépendances
- Un fichier brunch-config.js
Structure applicative
package.json
- assets
- config
- i18n
- components
- router
- stores
- actions
- services
- views
- initializer
- index.js : le point d'entrée de l'application
- application.js: l'initialisation de l'application
app
Assets contient l'ensemble des ressources statiques de l'application:
- images
- polices
- fichiers
- ...
assets
Contient toute la configuration relative à l'application.
- Les domaines
- Les associations champs / domaines + la notion de requis ou non.(générés à partir du modèle de données)
- Les ’urls’ des web services à appeler
A priori il n'y a pas de raison de toucher régulièrement à ce dossier
( à part lorsque vous ajoutez un nouveau web service. )
config
Contient l'ensemble des traductions de l'application.
- Un dossier par culture fr-FR par exemple
- Certaines traductions sont générés
- Une traduction est identifiée par une clef
i18n
//Traductions
module.exports = {
contactInformations: {
firstName: 'Prénom',
lastName: 'Nom',
contacts: 'Nb de contact: __nb__'
}
}
//Clef associée
contactInformations.firstName
//Pour traduire
i18n.t('key', {options})
//ou
this.i18n('key) //Dans un composant
L'application dispose de différents initializers:
Domaines :
initializers
Focus.definition.domain.container.setAll(require('../config/domain'));Définitions des entités
Focus.definition.entity.container.setEntityConfiguration(require('../config/entity-definition'));Layout de l'application
let render = Focus.application.render;
let Layout = Focus.components.application.layout.component;
let MenuLeft = require('../views/menu');
render(Layout, 'body', {
props: {MenuLeft}
});
components
Contiendra l'ensemble des composants spécifiques au projet.
Par exemple un composant graphique pour effectuer une notation ou un montant de facture.
Le composant exporté doit être défini de la même manière que dans focus-components de manière à pourvoir être intégré à ce dernier.
Contient l'ensemble des routeurs de l'application.
- Une application peut avoir plusieurs routeurs
- Une route ne peut être définie qu'une seule fois
- Une route peut avoir des paramètres, optionnels ou non
- A une route est associé une fonction de callback appellée avec les parmètres de la route.
module.exports = Focus.router.extend(
routes:{
'contact/:id(/:node)': 'contactRouteHandler'
},
contactRouteHandler(id, node){
let ContactView = require('views/contact');
//Rendu de la vue avec les paramètres dans le contenu de la page.
this._pageContent(ContactView, {props: {id: id}});
}
);stores
Ce sont des singletons, responsables des données de l'application.
Ils réagissent aux données transitant au sein du dispatcher via les actions. Ils notifient les composants en écoute des changements sur eux même.
let {CoreStore} = Focus.store;
module.exports = new CoreStore({
defintition: {
node1: 'node1',
node2; 'node2',
//Un noeud peut être n'importe quoi. Un object , une map, un tableau, ...
//Un noeud est l'unité minimale de donnée pour lequel un évènement de type 'modification' peut être lancé.
}
});actions
Les actions sont déclanchées par un composant (loading, ...) ou par une action utilisateur (click sur un bouton, ...)
Elles ont pour but de faire transiter des données au sein du dispatcher en vue d'être traitée par les stores.
//Action 'brute"
function myAction(crit){
myService(crit).then((data)=>{
Focus.dispatcher.handleViewAction(
data: {
node: data
},
type: 'update'
});
});
}
//Action utilisant action-builder
//Retourne une fonction
module.exports = actionBuilder({
service: myService,
node: 'movie',
status: 'loaded'
});
//Fonctionne pour le cas très fréquent des blocs en chargement / édition.services
Les services sont responsables du chargement des données. Dans 90% des cas il s'agit d'appeler un web-service, il peut également s'agir d'appeler une base de donnée locale au navigateur. Par essence un service est asynchrone et retourne une Promise.
//L'url est lue depuis la configuration.
let URL = require('../../config/server');
//permet d'appeller un webservice en spécifiant une url, un verbe en spécifiant des données
// soit en paramètre soit en données postée.
//Réalise une xmlHttpRequest
let fetch = Focus.network.fetch;
module.exports = {
getMovieById(id) {
return fetch(URL.movie.get({urlData: {id: id}}))
}
}views
Les vues vont représenter les pages de l'application.
On peut en réaliser de plusieurs type:
- Une page de recherche (une par application normalement), s'appuie sur un moteur de recherche et des vues préfaites au sein de focus voir FocusComponents.page.search
- Une page de liste (avec ou sans critère)(complètement autonome) (liste administrable ou ancien écran critères / résultat). voir FocusComponents.page.list
- Une page de détail contenant plusieurs blocs en édition autonome et en chargement autonome. FocusComponents.page.detail et FocusComponents.common.form.mixin
Code pour le détail (block)
// Mixins
let formMixin = Focus.components.common.form.mixin;
// Stores
let movieStore = require('stores/movie');
// Actions
let movieActions = require('action/movie').movie;
// Components
let Block = Focus.components.common.block.component;
module.exports = React.createClass({
definitionPath: 'movie',
displayName: 'MovieInformations',
mixins: [formMixin],
stores: [{store: movieStore, properties: ['informations']}],
action: movieActions,
renderContent: function renderMovieView() {
return (
<Block title='movie.detail.identity.title' actions={this._renderActions}>
{this.fieldFor('title')}
{this.fieldFor('released')}
{this.fieldFor('runtime')}
{this.fieldFor('countryIds')}
{this.fieldFor('languageIds')}
{this.fieldFor('genreIds')}
</Block>
);
}
});Exercices
Objectif
Créer une page qui affiche ce qu'est un contact.
- Repasser l'ensemble des initializers
- Un bloc d'information
- Une page composite
- Un bloc d'adresse
- Un bloc d'amis
- Ouvrir la liste d'amis dans un panneau glissant
- Ajout du cartouche en haut de la page
- Créer un composant Map pour afficher une carte représentant le lieu d'habitation
- Créer un composant customisé pour le domaine d'un champ
- Un bloc de préférences
- Faire un wizard de création pour créer un contact
Initialiser l'application
# Ouvrir gitbash
# Se rendre dans el dossier où vous souhaitez récupérer les exercices
git clone https://github.com/pierr/focus-formation.git
cd focus-formation
npm install
npm run serveproblème de proxy: ça se passe ici
Parcours de la structure
Prenez le temps de regarder la structure de l'application.
Comme décrit au paragraphe précédent.
initialize.js, les initializers.
Etape 1
Etape 2
- Se rendre dans le routeur et créer une ou plusieurs route.
- D'abord la route doit faire un console.log
- La route doit afficher un composant basique React type hello world
- La route doit afficher un composant affichant la liste des paramêtres
Etape 3
- Création d'un bloc de détail
- A priori, la couche de service est fournie.
- Récupérer le mixin correspondant dans focus
- Créer le store
- Créer l'action de chargement
- Initialiser les propiétés nécessaires (definitionPath, storeConf , ...)
- Afficher la page
- Créer l'action de sauvegarde
Etape 4
- Créer la page composite (contenant adresse et informations)
- Faire un bloc adresse
- mixins,
- Ajouter la config store, definitionPath
- Afficher la page
- Créer un composant Map affichant une carte google map utilisant la lattitude / longitude (cf doc)
Etape 5
- Créer la page de liste d'amis
- Faire un bouton pour ouvrir une popin dans la page de contact afin d'afficher plus d'amis
- Si vous êtes en avance, éventuellement transformer l'écran de gestion des amis en une liste avec critères / résultat
Etape 6
- Ajouter le header de la page de détail
- Créer un composant spécifique pour le champ note du contact qui est une note entre 0 et 10 (soit une progress bar, soit un indicateur coloré, ....)
- Utiliser ce composant dans la page de détail
- Configurer ce champ dans le domaine associé à la note
- Afficher ce champ dans la page en utilisant fieldFor
Etape 7
- Si tout se passe bien, une mise à jour de focus doit être disponible sur le projet, c'est le moment de s'entrainer à mettre à jour la version(npm install --save focus@latest) (normalement tout se passe bien)
- Vérifier que vous n'avez pas d'erreurs eslint dans votre éditeur
- Si vous avez terminé, nous allons pouvoir attaquer la création d'un contact en plusieurs étapes (wizard, à la lumière de la formation, comment feriez vous ? )
- Faites une proposition de composant sur papier
- Comparer avec l'implémentation proposé dans un exemple.
Formation focus
By Pierre Besson
Formation focus
- 352