Introduction à React JS et Semantic UI

Date 27 mai 2016 Catégories Developpement par VulgaireDev

 

    Ces temps-ci je travaille sur un projet de R&D portant sur les données liées (une composante du projet Occiware). Avant de proposer une application exploitant l'API déjà présente afin d'illustrer les possibilités des données liées (linked data), je saute dans le bain du Front-end, afin d'améliorer le "playground", une appli qui est en fait un genre de tutoriel de l'API des données liées. Bref, voici ce que j'aurais aimé savoir avant de commencer.

Le Front, faut que ça soit beau ! Semantic UI

Eh oui c'est important d'essayer d'avoir un beau design, un truc un peu sexy qui soit clair et donne envie. Pour ça, on peut faire du CSS natif, mais il faut avoir quand même un minimum d'expérience dans le domaine pour faire quelque chose de correct. On peut aussi se tourner vers des framework CSS, qui permettent d'avoir facilement de beaux composants, et donne donc un look sympa. Le point un peu négatif des framework CSS (par exemple Bootstrap) c'est qu'ils peuvent avoir tendance à uniformiser le style.

Bootstrap c'est sympa, c'est le plus connu (utilisé pour le site que vous avez sous les yeux actuellement d'ailleurs). C'est un bon choix, on peut faire un design responsive. Mais au bout d'un moment avec Bootstrap, ça pourrait être bien de changer un peu, parce qu'on a toujours le droit aux mêmes widgets, ca serait bien d'essayer autre chose. Et ce autre chose ce sera Semantic UI :

- L'esthetique des widgets est superbe (avis subjectif mais partagé, le projet a actuellement 25000 stars sur github).

- OpenSource, avec une assez grosse communauté active.

- On compose les classes des composants de sorte à les identifier facilement (sémantique) : "ui hidden vertical menu" donnera un menu vertical caché.

- La doc est assez fournie.

- La quantité de composants disponible est importante : grid, forms, transitions, menu etc.

- On peut personnaliser sans forcement passer par le CSS !

Ce dernier point est sympathique, ils ont mis en place un systeme de "themes" qui permet de remplacer les variables de Semantic UI par les notres.

On pourra ainsi faire des trucs de ce style :

/* Checkbox */
@checkboxActiveBackground: @primaryColor;
@checkboxActiveBorderColor: @primaryColor;
@checkboxActiveCheckColor: @white;

On redéfinit les couleurs des checkbox !

Le rouge par défaut n'est pas à votre goût ?

@red: #B31225;

et tous les éléments utilisant le rouge seront d'un rouge plus foncé ! On change ainsi tout le design.

Un autre exemple, pour changer la police de caractere des headers:

@headerFont : 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;

Pour plus de details sur les themes (et dans quel fichier mettre ces instructions) : http://semantic-ui.com/usage/theming.html

En fait, Semantic UI utilise SASS (Syntactically Awesome Style Sheet), qui est un genre de wrapper du CSS, et permet notamment d'y mettre des variables. Une fois qu'on a modifié nos variables, on passe par une phase de preprocessing, qui permet de générer le CSS avec les valeurs de nos variables. On peut aussi directement chopper le CSS et le JS de Semantic UI, l'inclure dans le projet et vamos !

NB: Si vous voulez modifier les propriétés CSS de SUI (Semantic UI), il faudra mettre un !important à la fin. En effet, l'auteur explique que cette pratique est là pour pallier à un manque du CSS (ici), qui est l'absence de règles de priorités pour les règles CSS. Qu'on ne vienne pas me dire que modifier le CSS comme ça c'est pas propre, que ca viole la philosophie du framework ou je ne sais quoi. Dans un monde parfait, avec des librairies/framework parfaits on pourrait coder parfaitement selon les bonnes pratiques (si tant est qu'il y ait consensus), mais en pratique, il faut bien savoir composer avec ce qu'on a !

Notes sur les grilles

    Le systeme de grille est assez pratique, mais en expérimentant on se rend compte qu'il y a quelques trucs à savoir.

Déjà regarder la doc pour se faire une idée : http://semantic-ui.com/collections/grid.html. Mais en fait on en apprend plus sur les exemples (clic droit > examiner l'élément) : http://semantic-ui.com/examples/responsive.html

Les grilles donc, sont par défaut constituées de 16 colonnes, contrairement à Bootstrap où on en a 12. Il existe deux principales manière de repartir nos élements.

  • Créer la grid, puis définir chaque composant comme étant une colonne, chacun aura donc une part égale de la taille totale disponible:
<div class="ui grid">
    <div class="three column row">
      <div class="column ui segment"> <!-- ui segment permet de mettre en évidence la zone crée -->
      </div>
      <div class="column ui segment">
      </div>
      <div class="column ui segment">
      </div>
    </div>
</div>

 

  • Définir une grid puis spécifier la taille de chacun des composants :
<div class="ui grid">
    <div class="row">
      <div class="three wide column ui segment">
      </div>
      <div class="wide column ui segment">
      </div>
      <div class="nine wide column ui segment">
      </div>
    </div>
</div>

 

NB: Ne pas oublier de mettre "wide" devant "column !

NB important: L'ordre des classes CSS a une importance ! (et je ne me souviens pas avoir vu ça dans la doc...). Pour l'instant, ceci à l'air de fonctionner:

grid classes | ui classes | custom classes

et pour ces "grid classes":

number | [wide] | column | row

On peut rajouter la classe "stackable", pour que les éléments de la grille s'empilent lorsqu'on passe sur un smartphone.

<div class="ui stackable grid">
    <div class="two column wide ui vertical compact menu mymenu">
        <a class="item">Playground User Manual</a>
        <a class="item">Explore models and projects</a>
        <a class="item">API details</a>
        <a class="item">FAQS</a>
        <a class="item">Server configuration</a>
    </div>
    <div class="ten column wide ui segment">
    </div>
</div>

Enfin, on peut bien sur faire des "nested grid", càd des grilles imbriquées dans des grid, ce qui permet d'organiser le site comme on le désire. Il faut juste savoir que mettre une grid introduit une marge. Si on veut que tous les élements soient alignés correctement, il ne faut pas faire :

<div class="ui grid content">
    <div class="row"></div>
      
    <div class="row ui grid">
        <div class="row"></div>      
        <div class="row ui"></div>
    </div>
</div>

Mais:

<div class="ui grid content">
    <div class="row"></div>
      
    <div class="ui grid">
        <div class="row"></div>      
        <div class="row ui"></div>
    </div>
</div>

Container

La classe "container" permet de faire en sorte de donner une largeur maximale au site selon l'écran de l'utilisateur. Il y a 3 paliers, qui définissent 4 intervals (correspondant à mobile, tablette, petit écran, grand écran)(on compte l'interval "plus petit que le premier palier" et l'interval "plus grand que le dernier"). Lorsque l'utilisateur est dans un des intervals, alors le site s'affichera dans la largeur du palier du dessous : http://semantic-ui.com/elements/container.html. Par exemple, sur un écran de 1920*1080 (grand), le site s'affichera avec la largeur du palier du dessous, càd 1127px.

C'est assez avantageux de pouvoir travailler comme cela, on a plus qu'à tester le site sur ces 4 resolutions et basta, pas besoin de faire toutes les possibilités d'écran imaginables. Sur mobile on aura la largeur à 100%, on peut utiliser des classes spécifiques (genre stackable), ca marche très bien.


Le Javascript (seul), c'est catastrophique. React JS

Le javascript est un language, disons "particulier". Sam et Max en ont fait un article sympa http://sametmax.com/un-gros-troll-de-plus-sur-javacscript/. Juste un exemple (tiré du lien précédent) illustrant le taux d'alcool du language :

> {} + {} // Ceci n'est effectivement PAS un nombre
NaN
> {} + [] // logique IMPARABLE
0
> [] + {} // fuck la commutativité
"[object Object]"

Le JS est donc à manier avec précautions. Pour gagner en productivité, et avoir une belle organisation du code, on va utiliser un framework JS.

Et là c'est la jungle. Il en existe partout, et ça évolue à toute vitesse (sans parler des outils autour : bower, grunt, gulp, browserify ... Angular JS (de google) est le plus connu, mais la version 2 qui arrive casse apparemment pas mal de principes du premier, donc pas sur que ca vaille le coup de l'apprendre (le premier j'entend). Il y a Backbone, Amber, Polymer, etc. Et puis il y a React (de facebook). Celui-ci n'est pas exactement un framework, mais plus une librairie, il est présenté comme s'occupant du V de MVC, càd qu'il permet de s'occuper de la vue, de gérer les composants graphiques. Pour faire une vraie application complète, on utilisera Flux en complément (plus exactement son implémentation Redux), on verra ça dans un autre article.

On m'a conseillé React pour plusieurs raisons :

- Porté par facebook, donc très utilisé et communauté conséquente

- Open Source

- Une approche sans templates, ca change pas mal des standards actuels.

- Une approche axée sur la création de composants unitaires, pouvant s'inclure les uns dans les autre, qui encourage la réutilisabilité.

- De super performances (en théorie, j'ai pas encore testé en pratique). En gros, quand on modifie un élément, React compare l'ancien et le nouveau DOM et ne met à jour que les noeuds existants, de manière très precise.

- Consitue un outil pour construire notre application (=librairie), plutôt qu'une boîte à outils (=un gros framework). On peut ainsi combiner React avec d'autres outils pour s'adapter, c'est plus modulaire.

- Une fois qu'on connait React JS, on peut utilise React Native, qui permet de faire des applications mobiles !

Le tuto de la doc (https://facebook.github.io/react/docs/tutorial.html) est déjà très bien pour commencer. Vous clonez le git (ici), vous lancez le serveur et on est bon.

Pourquoi avoir besoin d'un serveur pour une initiation à React, qui devrait nous servir à faire une application cliente ? Le tuto est simple mais essaie de couvrir plusieurs fonctionalités, dont celle de faire des appels à une API REST. Le serveur est là pour pouvoir faire ces appels, en plus de vous fournir l'application React, tout comme ce sera le cas en production.

NB: Je ne fais pas un tuto complet, simplement une initiation pour comprendre ce qui est important pour se lancer, et pour pouvoir se faire une idée de la techno.

Keep It Simple, Stupid

    ReactJS n'utilise pas de templates comme on peut le voir ailleurs, lui son truc c'est de créer des composants, des briques, et de les ajouter pour construire votre application. Une fois le composant créé, on peut le réutiliser à souhait, et bien sur le mettre dans d'autres composants. On peut ainsi créer un composant "Menu",  contenant un composant "Titre", ainsi que des "Item Menu". On pourra tout à fait exporter ce composant pour le réutiliser dans une autre application.

Essayons :

import React from 'react';

var Menu = React.createClass({
  render: function() {
    return (
      <div className="groupementmenu">
        <div className="ui white big launch button menubutton">
          <i className="content icon"></i>
          <span className="text">Menu</span>
        </div>
      </div>
    );
  }
});

On créé donc un composant Menu, en utilisant createClass, qui doit définir un render, retournant le code correpondant à l'élément courant.

"Oula c'est moche tu génère du html, que tu intègres direct à ton code ?"

Pas exactement, vous remarquerez par exemple qu'on a pas d'attribut "class", mais un attribut "className". En fait on ne génère pas du HTML, on écrit du JSX (c'est le nom de cette syntaxe proche du XML), que React va ensuite transformer en Javascript classique (càd moche, difficile et pénible à écrire).

On peut bien sur imbriquer les composants entre eux:

import React from 'react';

var Menu = React.createClass({
  render: function() {
    return (
      <div className="four wide column center aligned groupementmenu" >
        <div className="ui white big launch button menubutton">
          <i className="content icon"></i>
          <span className="text">Menu</span>
        </div>
        <MenuDepliant/>
      </div>
    );
  }
});


var MenuDepliant = React.createClass({
  render: function(){
    return(
      <div className="ui vertical compact menu mymenu">      
      </div>
    );
  }
});

On peut aussi créer des fonctions locales aux composants, appellées par exemple lors du passage de la souris (ou de n'importe quel event, voir doc):

import React from 'react';
var Menu = React.createClass({
  enterButton: function () {
      $('.ui.mymenu').transition('fade right');{/*si on rentre la souris sur l'element, on affiche*/}
  },

  outMenu: function () {
      $('.ui.mymenu ').transition('fade right');{/*si on sort du menu, on le fait disparaitre */}
  },

  render: function() {
    return (
      <div className="groupementmenu" onMouseLeave={this.outMenu}>
        <div className="ui white big launch button menubutton" onMouseEnter={this.enterButton}>
          <i className="content icon"></i>
          <span className="text">Menu</span>
        </div>
        <ConnectedMenuDepliant/>
      </div>
    );
  }
});

var MenuDepliant = React.createClass({
  render: function(){
    return(
      <div className="ui vertical compact menu mymenu">
      </div>
    );
  }
});

Pour communiquer des données entre eux, les composants utilisent des "props". Par exemple, passons les titres des menus au MenuDepliant (ici je met le JSON dans une fonction pour simplifier mais on pourrait certainement mieux faire).

NB: Attention la syntaxe pour les commentaires en jsx est de la forme : {/* mon commentaire /*}

import React from 'react';

var Menu = React.createClass({
  getMenuItems: function() {
    return [
        "item 1",
        "item 2",
        "item 3"
    ];
  },

  enterButton: function () {
      $('.ui.mymenu').transition('fade right');
  },

  outMenu: function () {
      $('.ui.mymenu ').transition('fade right');
  },

  render: function() {
    return (
      <div className="groupementmenu" onMouseLeave={this.outMenu}>
        <div className="ui white big launch button menubutton" onMouseEnter={this.enterButton}>
          <i className="content icon"></i>
          <span className="text">Menu</span>
        </div>
        <MenuDepliant menuItems={this.getMenuItems()}/> {/*Ici on passe cette propriété au child*/}
      </div>
    );
  }
});


var MenuDepliant = React.createClass({
  render: function(){
    return(
      <div className="ui vertical compact menu mymenu">
        {this.props.menuItems.map(function(menuItem, i){ {/*on la reutilise ici avec this.props.menuitems */}
          return <a className="item">{menuItem}</a>; 
        }, this)}
      </div>
    );
  }
});

module.exports = {Menu};

NB: La fonction map permet d'appliquer un traitement à chacun des éléments d'une collection. Ici on demande de retourner un nouveau lien pour chaque menuItem.

Ce qui est bien c'est que si les props changent, les composants correspondant se metteront à jour automatiquement.

NB importante: Ici on passe l'évaluation de la fonction en tant que props (this.getMenuItems()), mais on peut tout à fait envoyer directement la fonction ! (this.getMenuItems, sans les parenthèses) Ainsi, on peut, par exemple, exposer un service du composant parent à un composant enfant. C'est très utile pour la prochaine feature : les states.

Les states permettent de donner un état à l'objet (thanks captain obvious). Ils sont parfois nécessaires car les props sont immuables depuis l'enfant.

On pourrait par exemple imaginer un état nous disant si le menu est caché ou pas (même si on peut aussi obtenir cette information avec du jQuery) :

var Menu = React.createClass({
  getInitialState: function() { {/* On définit l'état initial */}
    return {hidden: true};
  },

  getMenuItems: function() {
    return [
        "item 1",
        "item 2",
        "item 3"
    ];
  },

  enterButton: function () {
      if(this.state.hidden == true){
          $('.ui.mymenu').transition('fade right');
          this.setState({hidden: false}); {/* On change le state (setState effectue un merge, donc màj ici puisque hidden est une clé déjà existante) */}
      }
  },

  outMenu: function () {
      if(this.state.hidden == false){
          $('.ui.mymenu').transition('fade right');
          this.setState({hidden: true});
      }
  },

  render: function() {
    return (
      <div className="groupementmenu" onMouseLeave={this.outMenu}>
        <div className="ui white big launch button menubutton" onMouseEnter={this.enterButton}>
          <i className="content icon"></i>
          <span className="text">Menu</span>
        </div>
        <MenuDepliant menuItems={this.getMenuItems()}/> 
      </div>
    );
  }
});


var MenuDepliant = React.createClass({
  render: function(){
    return(
      <div className="ui vertical compact menu mymenu">
        {this.props.menuItems.map(function(menuItem, i){
          return <a className="item">{menuItem}</a>;
        }, this)}
      </div>
    );
  }
});

module.exports = {Menu};

Enfin, les composants invoquent des méthodes particulières au cours de leur vie, par exemple à chaque mise à jour du composant, avant qu'il effectue son premier render() etc. Ceci est très utile, voir la doc pour la liste des évènements (https://facebook.github.io/react/docs/component-specs.html).

Voilà pour l'intro à React !

Liens intéressants :

Les outils importants

    Avant toute chose il faut utiliser npm, pour gerer les dépendences de librairie. On commence par l'installer (http://askubuntu.com/questions/426750/how-can-i-update-my-nodejs-to-the-latest-version, attention bien mettre la dernière version sinon vous aurez des problèmes !). Npm va nous permetter d'installer les librairies de manière simple, et d'ecrire dans un fichier package.json les noms desdites librairies. Ainsi, en changeant d'environnement de production, il suffira de faire :

npm install

Et tout s'installera !

Quand on veut enregistrer une nouvelle librairie pour la production :

npm install --save <maLibrairie>

Et pour le développement :

npm install --save-dev <malibrairie>

On peut intégrer semantic UI directement dans le projet en tant que feuilles CSS et javascript, mais dans ce cas on se prive de la possibilité de pouvoir redéfinir les variables pour customiser (la police de caractère par exemple). Pour build c'est assez simple, voir la doc : http://semantic-ui.com/introduction/build-tools.html. Dans l'idéal il faudrait utiliser webpack pour faire cela, là la doc nous fait utiliser gulp (qui a une fonction proche de webpack), mais bon pour le moment on s'en contentera.

Webpack va nous permettre de faire plusieurs choses à la fois. Il va tout d'abord nous permettre de regrouper tout notre javascript en un fichier et de le minifier (le compresser) pour la production. Ensuite, il va traiter nos fichier .js pour y appliquer différentes transformation. La plus important est de les faire passer par Babel, un utilitaire qui nous permet de convertir du code ES6 vers du ES5.

NB: ES6 ou ECMAScript2015 est la dernière version de javascript, et n'est pas entièrement supportée par tous les navigateurs actuels. Par exemple en ES6 on peut faire des fonctions anonymes de la sorte :

arg => instructions

Mieux, on utiliser le "spread operator", pour recréer un objet ayant les memes attributs que le precedent:

return [...object, variable]

Le ... decompose l'objet en ses attributs, un peu comme l'opérateur splat en python. Le problème c'est qu'on ne peut pas encore faire :

return {..., "autrevar": monAutreVar } //attention !

En fait normalement on pourra faire cela en ES7, donc ce n'est pas pour tout de suite. A la place, on doit écrire ceci :

return
    Object.assign({}, state, {
        currentReading: action.currentReading
})

Enfin on va pouvoir utiliser webpack-dev-server, qui va regarder votre code, et mettre à jour automatiquement votre application dans le navigateur, plus besoin de faire F5.

NB: De plus, webpack résout le problème du cache du navigateur. Avec des include javascript classiques dans votre code, le navigateur peut garder une ancienne version dans le cache, du coup vos dernières modifications ne sont pas présentes !

Voila pour en savoir plus foncez ici : https://medium.com/@dabit3/beginner-s-guide-to-webpack-b1f1a3638460#.xu0yd1nij, c'est très bien fait.

Je laisse quand même un fichier de config assez simple qui marche bien chez moi :

module.exports = {
  entry: ["./semantic/dist/semantic.js", "./src/index.js"],
  output: {
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: [/node_modules/,/semantic/],
        query: {
          "presets": [
            require.resolve("babel-preset-es2015"),
            require.resolve("babel-preset-react"),
            require.resolve("babel-preset-stage-2")
          ],
        }
      },
    ]
  },
  resolve: {
    extensions: ['', '.js', '.json']
  }
}

Dans un prochain article, je parlerai de Flux, et plus exactement Redux, qui permet de faire des applications bien plus complètes, ainsi que de React-Router, qui permet de gerer les routes.

Commentaires

blog comments powered by Disqus