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 serve

problè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