Tutoriel pour découvrir Vue-GWT, le wrapper GWT de Vue.js

Zakaria Amine

Traducteur

Fabrice Bouyé

Ce tutoriel se propose de présenter le wrapper GWT vue-gwt du framework web Vue.js.

Pour réagir à ce support de cours, un espace de dialogue vous est proposé sur le forum Commentez .

- Titre : Tutoriel pour découvrir Vue-GWT, le wrapper GWT de Vue.js

- Auteur : Zakaria Amine

- Traducteur : Fabrice Bouyé

- Parution : 21 mai 2018

- Mise à jour : 21 mai 2018

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2018 Zakaria Amine. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Vue.js rencontre GWT

- III. Que gagne-t-on à utiliser GWT par-dessus Vue.js

- IV. Conclusion

- V. Remerciements

I - Introduction

Vue.js est, avec Angular et React, un des frameworks JavaScript les plus populaires. Il permet de tirer parti des techniques récentes pour la construction d'applications front-end, et peut se révéler utile pour maintenir des applications qui nécessitent un contenu conséquent de JavaScript et de CSS. Vue.js dispose de nombreux avantages et fonctionnalités, qui sont décrits dans sa documentation.

Dans ce billet, nous voulons nous focaliser sur le wrapper GWT vue-gwt qui est apparu récemment suite à la popularité croissante de Vue.js. Il s'agit là d'un des projets les plus populaires de l'écosystème GWT. Aux fonctionnalités sympathiques de Vue.js, vue-gwt apporte la robustesse de Java et introduit des vérifications lors de la compilation dans plusieurs aspects tels que l'initialisation et le typage des variables. Un des principaux aspects qui a retenu toute notre attention lors des tests de Vue.js est sa simplicité et sa facilité d'apprentissage. Le développeur peut aborder rapidement le cadriciel avec un bagage de connaissances minimales en JavaScript. Nous recommandons que le développeur se familiarise avec Vue.js avant de tenter de développer avec sa variante GWT. Dans la suite du billet, nous allons comparer l'application Vue.js suivante : https://github.com/zak905/vuejs-demo avec sa version GWT : https://github.com/zak905/vuejs-gwt-demo.

II - Vue.js rencontre GWT

L'application de démo est une application de saisie de dépenses en deux composants : un formulaire pour saisir la dépense, et une table pour afficher toutes les dépenses. L'application ressemble à ceci :

Version JavaScript

1. <template>

2. <div id="app">

3. <img src="./assets/logo.png">

4. <div>

5. <label for="vat"> VAT in %</label>

6. <input id="vat" type="number" v-model="vatRate">

7. </div>

8. <ExpenseForm :currencies="currencies" :vatRate="vatRate" :expenses="expenses" />

9. <div class="divider"></div>

10. <ExpenseList :currencies="currencies" :expenses="expenses" />

11. </div>

12. </template>

14. <script>

15. import ExpenseForm from './components/ExpenseForm.vue'

16. import ExpenseList from './components/ExpenseList.vue'

18. export default {

19. name: 'app',

20. components: {

21. ExpenseForm,

22. ExpenseList

23. },

24. data: () => ({

25. vatRate: 20,

26. expenses: [],

27. currencies: [{name: "EUR", symbol: "?"}, {name: "USD", symbol: "$"}, {name: "GBP", symbol: "£"}],

28. }),

29. }

30. </script>

32. <style>

33. #app {

34. font-family: 'Avenir', Helvetica, Arial, sans-serif;

35. -webkit-font-smoothing: antialiased;

36. -moz-osx-font-smoothing: grayscale;

37. text-align: center;

38. color: #2c3e50;

39. margin-top: 60px;

40. }

41. .divider {

42. height: 30px;

43. }

44. </style>

Version GWT

AppComponent.java

1. @Component(components = {ExpenseFormComponent.class, ExpenseListComponent.class})

2. public class AppComponent extends VueComponent  {

3. @JsProperty

4. double vatRate = 20;

6. @JsProperty

7. List<Expense> expenses = new ArrayList<>();

9. @JsProperty

10. List<Currency> currencies = Arrays.asList(new Currency("EUR", "€"),

11. new Currency("USD", "$"),

12. new Currency("GBP", "£"));

13. }

AppComponent.html

1. <div id="app">

2. <img style="width: 10%" src="./assets/logo.png">

3. <div>

4. <label for="vat"> VAT in %</label>

5. <input id="vat" type="number" v-model="vatRate">

6. </div>

7. <expense-form :currencies="currencies" :vatRate="vatRate" :expenses="expenses" ></expense-form>

8. <div class="divider"></div>

9. <expense-list :currencies="currencies" :expenses="expenses" :vatRate="vatRate"></expense-list>

10. </div>

Étant donné que la balise style n'est pas encore supportée dans le fichier .html (surveiller ce ticket pour les mises à jour), tous les styles doivent être inclus dans des fichiers .css séparés pour la version GWT. Pour la balise du nom des composants, Vue.js supporte l'utilisation du nom complet ainsi que sa transformation, tandis que vue-gwt utilise la convention qui dit que chaque composant doit voir son nom se terminer par Component pour chaque fichier .java et .html et, si le composant est inclus en tant qu'enfant dans un autre, il doit être nommé en suivant la notation kebab en retirant le mot Component : le composant ExpenseFormComponent est inclus en tant qu'enfant de AppComponent sous le nom expense-form.

Le composant ExpenseForm ressemble alors à :

Version JavaScript

1. <template>

2. <div class="expense-form">

3. <label for="amount">Amount: </label>

4. <input type="number" id="amount" v-model="amount"/>

5. <label for="amountVAT">VAT: </label>

6. <input type="number" id="amountVAT" v-model="amountVAT" disabled/>

7. <label for="currency">Currency: </label>

8. <select id="currency" v-model="currency">

9. <option v-for="currency in currencies"> {{ currency.name }} </option>

10. </select>

11. <label for="date"  >Date: </label>

12. <input type="date" id="date" value="2018/03/06" v-model="date"/>

13. <label for="reason">Reason: </label>

14. <textarea id="reason" v-model="reason" />

15. <button id="append" v-on:click="submitExpense"> Add Expense </button>

16. </div>

17. </template>

19. <script>

20. export default {

21. name: 'ExpenseForm',

22. methods: {

23. submitExpense: function(event) {

24. this.expenses.push({"amount": this.amount, "date": this.date, "reason": this.reason, "vatRate": this.vatRate, "vat": this.amountVAT, "currency": this.currency});

25. }

26. },

27. data: () => {

28. return {

29. amount: 0,

30. date: '',

31. reason: '',

32. currency: ''

33. }

34. },

35. props: {

36. currencies: Array,

37. expenses: Array,

38. vatRate: Number,

39. },

40. computed: {

41. amountVAT: function() {return parseFloat(this.vatRate / 100 * this.amount).toFixed(2)}

42. }

43. }

44. </script>

46. //..styles etc,..

Version GWT

ExpenseFormComponent.java

1. @Component

2. public class ExpenseFormComponent extends VueComponent {

3. @JsProperty

4. double amount =  0;

5. @JsProperty

6. String date = "";

7. @JsProperty

8. String reason="";

9. @JsProperty

10. String currency="";

12. @Prop

13. @JsProperty

14. double vatRate;

16. @Prop

17. @JsProperty

18. List<Expense> expenses;

20. @Prop

21. @JsProperty

22. List<Currency> currencies;

24. @JsMethod

25. public void submitExpense() {

26. expenses.add(new Expense(amount, date, reason, getAmountVAT(), vatRate, currency));

27. }

29. @Computed

30. public double getAmountVAT() {

31. return vatRate / 100 * amount;

32. }

33. }

ExpenseForm.html

1. <vue-gwt:import class="com.gwidgets.client.dto.Currency"/>

2. <div class="expense-form">

3. <label for="amount">Amount: </label>

4. <input type="number" id="amount" v-model="amount"/>

5. <label for="amountVAT">VAT: </label>

6. <input type="number" id="amountVAT" v-model="amountVAT" disabled/>

7. <label for="currency">Currency: </label>

8. <select id="currency" v-model="currency">

9. <option v-for="Currency currency in currencies"> {{ currency.getName() }} </option>

10. </select>

11. <label for="date"  >Date: </label>

12. <input type="date" id="date" value="2018/03/06" v-model="date"/>

13. <label for="reason">Reason: </label>

14. <textarea id="reason" v-model="reason" />

15. <button id="append" v-on:click="submitExpense"> Add Expense </button>

16. </div>

La principale différence entre les deux versions est le renforcement du typage :

1. <select id="currency" v-model="currency">

2. <option v-for="Currency currency in currencies"> {{ currency.getName() }} </option>

3. </select>

Cela nécessite une balise d'importation spéciale : <vue-gwt:import class="com.gwidgets.client.dto.Currency"/>

La liste des dépenses ressemble à ceci :

Version JavaScript

1. <template>

2. <div class="expense-list">

3. <table class="expense-table">

4. <thead>

5. <th>Amount</th>

6. <th>Date</th>

7. <th>VAT rate %</th>

8. <th>VAT</th>

9. <th>Reason</th>

10. </thead>

11. <tbody>

12. <tr v-for="expense in expenses">

13. <td>{{ expense.amount + getCurrencySymbol(expense.currency, currencies)}}</td>

14. <td>{{ expense.date }}</td>

15. <td>{{ expense.vatRate }}</td>

16. <td>{{ expense.vat + getCurrencySymbol(expense.currency, currencies)}}</td>

17. <td>{{ expense.reason }}</td>

18. </tr>

20. </tbody>

21. </table>

22. </div>

23. </template>

25. <script>

26. export default {

27. name: 'ExpenseList',

28. props: {

29. currencies: Array,

30. expenses: Array,

31. },

32. methods: {

33. getCurrencySymbol: (currencyName, currencies) => {

34. for (let i = 0; i < currencies.length; i++ ) {

35. if (currencyName === currencies[i].name)

36. return currencies[i].symbol;

37. }

38. return "$"

39. }

40. }

41. }

42. </script>

44. //..styles etc

Version GWT

ExpenseListComponent.java

1. @Component

2. public class ExpenseListComponent extends VueComponent {

3. @Prop

4. @JsProperty

5. double vatRate;

7. @Prop

8. @JsProperty

9. List<Expense> expenses;

11. @Prop

12. @JsProperty

13. List<Currency> currencies;

15. @JsMethod

16. public String getCurrencySymbol(String currencyName) {

17. return currencies.stream()

18. .filter(currency -> currency.getName().equals(currencyName))

19. .findFirst()

20. .map(Currency::getSymbol)

21. .orElse("$");

22. }

23. }

EExpenseListComponent.html

1. <vue-gwt:import class="com.gwidgets.client.dto.Expense"/>

2. <div class="expense-list">

3. <table class="expense-table">

4. <thead>

5. <th>Amount</th>

6. <th>Date</th>

7. <th>VAT rate %</th>

8. <th>VAT</th>

9. <th>Reason</th>

10. </thead>

11. <tbody>

12. <tr v-for="Expense expense in expenses">

13. <td>{{ expense.amount + getCurrencySymbol(expense.currency)}}</td>

14. <td>{{ expense.date }}</td>

15. <td>{{ expense.vatRate }}</td>

16. <td>{{ expense.amountVAT + getCurrencySymbol(expense.currency)}}</td>

17. <td>{{ expense.reason }}</td>

18. </tr>

19. </tbody>

20. </table>

21. </div>

Une fois de plus, GWT requiert que le type Expense soit explicité.

III - Que gagne-t-on à utiliser GWT par-dessus Vue.js

Au premier coup d'œil, il semble que vue-gwt ait ajouté pas mal de vérifications qui renforcent l'intégrité des données et évite de déployer en production une application bancale. Entre autres, lors de la compilation, vue-gwt va vérifier l'initialisation des variables. Supposons que nous affichons des listes ou des tables, et que nous oublions la variable dans le v-for. Par exemple, supprimons le champ currencies de AppComponent.java. Avec vue-gwt, vous obtiendrez une erreur de compilation et vous ne pourrez pas construire votre application :

1. [17,8] In AppComponent.html at line 7: Couldn't find variable/method "currencies". Make sure you didn't forget the @JsProperty/@JsMethod annotation.

Dans la version JavaScript, l'application peut être construite et déployée malgré l'absence de currencies dans les données. Un message sera bien affiché dans la console en mode développeur, mais bien sûr c'est trop tard lorsque l'application est déjà déployée. Sur de petites applications comme celle-ci, les effets sont minimes, mais les répercussions sont bien plus déplaisantes sur de grosses applications.

Un autre avantage à utiliser vue-gwt est de tirer parti des API Java telles que : Collections, Optional, Stream (les flux), les tables de hachage, etc. À partir de sa version 2.8.0, GWT supporte les flux et les expressions lambda, et cela peut vous simplifier la mise lorsque vous travaillez sur des transformations et des traitements de données complexes. Par exemple, dans le composant ExpenselistComponent, nous avons utilisé les API de flux de données pour filtrer les objets en fonction de la monnaie :

1. @JsMethod

2. public String getCurrencySymbol(String currencyName) {

3. return currencies.stream()

4. .filter(currency -> currency.getName().equals(currencyName))

5. .findFirst()

6. .map(Currency::getSymbol)

7. .orElse("$");

8. }

Pour être tout à fait franc, il existe aussi une API de flux de données en JavaScript ; cependant, tous les navigateurs ne la supportent pas encore.

Enfin, vue-gwt renforce la vérification du typage dans les modèles comme nous l'avons mentionné plus tôt : <tr v-for="Expense expense in expenses">, ce qui permet de rendre les applications encore plus robustes et résilientes aux changements apportés aux objets de données.

IV - Conclusion

Vue.js est un framework JavaScript proéminent, et, accompagné de vue-gwt, les développeurs pourront en tirer le maximum. vue-gwt introduit une nouvelle manière de développer des applications GWT en utilisant des modèles .html et des classes .java correspondantes, et le résultat final sera une application Vue.js robuste basée sur du Java. GWT a rencontré Vue.js et il semble qu'il en soit tombé sous le charme.

V - Remerciements

Cet article a été publié avec l'aimable autorisation de Zakaria Amine. L'article original est disponible à cette adresse : http://www.g-widgets.com/2018/03/24/a-walk-through-the-gwt-wrapper-for-vue-js-vue-gwt/.

Nous tenons à remercier f-leb pour la relecture orthographique attentive de cet article, Fabrice Bouyé pour la traduction en langue française et Mickael Baron pour la mise au gabarit.