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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
<template>
<div id
=
"app"
>
<img src
=
"./assets/logo.png"
>
<div>
<label for
=
"vat"
>
VAT in %</label>
<input id
=
"vat"
type
=
"number"
v-model
=
"vatRate"
>
</div>
<ExpenseForm :
currencies
=
"currencies"
:
vatRate
=
"vatRate"
:
expenses
=
"expenses"
/>
<div class
=
"divider"
></div>
<ExpenseList :
currencies
=
"currencies"
:
expenses
=
"expenses"
/>
</div>
</template>
<script>
import ExpenseForm from './components/ExpenseForm.vue'
import ExpenseList from './components/ExpenseList.vue'
export default {
name
:
'app'
,
components
:
{
ExpenseForm,
ExpenseList
},
data
: (
) => ({
vatRate
:
20
,
expenses
:
[],
currencies
:
[{
name
:
"EUR"
,
symbol
:
"?"
},
{
name
:
"USD"
,
symbol
:
"$"
},
{
name
:
"GBP"
,
symbol
:
"£"
}],
}
),
}
</script>
<style>
#app
{
font-family:
'Avenir'
,
Helvetica,
Arial,
sans-serif
;
-webkit-font-smoothing:
antialiased;
-moz-osx-font-smoothing:
grayscale;
text-align:
center
;
color:
#2c3e50
;
margin-top:
60
px;
}
.divider
{
height:
30
px;
}
</style>
Version GWT
AppComponent.java
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
@Component
(
components =
{
ExpenseFormComponent.class
, ExpenseListComponent.class
}
)
public
class
AppComponent extends
VueComponent {
@JsProperty
double
vatRate =
20
;
@JsProperty
List<
Expense>
expenses =
new
ArrayList<>(
);
@JsProperty
List<
Currency>
currencies =
Arrays.asList
(
new
Currency
(
"EUR"
, "€"
),
new
Currency
(
"USD"
, "$"
),
new
Currency
(
"GBP"
, "£"
));
}
AppComponent.html
2.
3.
4.
5.
6.
7.
8.
9.
10.
<div id
=
"app"
>
<img style
=
"width: 10%"
src
=
"./assets/logo.png"
>
<div>
<label for
=
"vat"
>
VAT in %</label>
<input id
=
"vat"
type
=
"number"
v-model
=
"vatRate"
>
</div>
<expense-form :
currencies
=
"currencies"
:
vatRate
=
"vatRate"
:
expenses
=
"expenses"
></expense-form>
<div class
=
"divider"
></div>
<expense-list :
currencies
=
"currencies"
:
expenses
=
"expenses"
:
vatRate
=
"vatRate"
></expense-list>
</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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
<template>
<div class
=
"expense-form"
>
<label for
=
"amount"
>
Amount: </label>
<input type
=
"number"
id
=
"amount"
v-model
=
"amount"
/>
<label for
=
"amountVAT"
>
VAT: </label>
<input type
=
"number"
id
=
"amountVAT"
v-model
=
"amountVAT"
disabled/>
<label for
=
"currency"
>
Currency: </label>
<select id
=
"currency"
v-model
=
"currency"
>
<option v-for
=
"currency in currencies"
>
{{ currency.name }} </option>
</select>
<label for
=
"date"
>Date: </label>
<input type
=
"date"
id
=
"date"
value
=
"2018/03/06"
v-model
=
"date"
/>
<label for
=
"reason"
>
Reason: </label>
<textarea id
=
"reason"
v-model
=
"reason"
/>
<button id
=
"append"
v-on
:
click
=
"submitExpense"
>
Add Expense </button>
</div>
</template>
<script>
export default {
name
:
'ExpenseForm'
,
methods
:
{
submitExpense
:
function(
event
) {
this.
expenses.push
({
"amount"
:
this.
amount,
"date"
:
this.
date,
"reason"
:
this.
reason,
"vatRate"
:
this.
vatRate,
"vat"
:
this.
amountVAT,
"currency"
:
this.
currency}
);
}
},
data
: (
) =>
{
return {
amount
:
0
,
date
:
''
,
reason
:
''
,
currency
:
''
}
},
props
:
{
currencies
:
Array,
expenses
:
Array,
vatRate
:
Number,
},
computed
:
{
amountVAT
:
function(
) {
return parseFloat
(
this.
vatRate /
100
*
this.
amount).toFixed
(
2
)}
}
}
</script>
//..styles etc,..
Version GWT
ExpenseFormComponent.java
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
@Component
public
class
ExpenseFormComponent extends
VueComponent {
@JsProperty
double
amount =
0
;
@JsProperty
String date =
""
;
@JsProperty
String reason=
""
;
@JsProperty
String currency=
""
;
@Prop
@JsProperty
double
vatRate;
@Prop
@JsProperty
List<
Expense>
expenses;
@Prop
@JsProperty
List<
Currency>
currencies;
@JsMethod
public
void
submitExpense
(
) {
expenses.add
(
new
Expense
(
amount, date, reason, getAmountVAT
(
), vatRate, currency));
}
@Computed
public
double
getAmountVAT
(
) {
return
vatRate /
100
*
amount;
}
}
ExpenseForm.html
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<vue-gwt:
import class
=
"com.gwidgets.client.dto.Currency"
/>
<div class
=
"expense-form"
>
<label for
=
"amount"
>
Amount: </label>
<input type
=
"number"
id
=
"amount"
v-model
=
"amount"
/>
<label for
=
"amountVAT"
>
VAT: </label>
<input type
=
"number"
id
=
"amountVAT"
v-model
=
"amountVAT"
disabled/>
<label for
=
"currency"
>
Currency: </label>
<select id
=
"currency"
v-model
=
"currency"
>
<option v-for
=
"Currency currency in currencies"
>
{{ currency.getName() }} </option>
</select>
<label for
=
"date"
>Date: </label>
<input type
=
"date"
id
=
"date"
value
=
"2018/03/06"
v-model
=
"date"
/>
<label for
=
"reason"
>
Reason: </label>
<textarea id
=
"reason"
v-model
=
"reason"
/>
<button id
=
"append"
v-on
:
click
=
"submitExpense"
>
Add Expense </button>
</div>
La principale différence entre les deux versions est le renforcement du typage :
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
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
<template>
<div class
=
"expense-list"
>
<table class
=
"expense-table"
>
<thead>
<th>Amount</th>
<th>Date</th>
<th>VAT rate %</th>
<th>VAT</th>
<th>Reason</th>
</thead>
<tbody>
<tr v-for
=
"expense in expenses"
>
<td>{{ expense.amount + getCurrencySymbol(expense.currency, currencies)}}</td>
<td>{{ expense.date }}</td>
<td>{{ expense.vatRate }}</td>
<td>{{ expense.vat + getCurrencySymbol(expense.currency, currencies)}}</td>
<td>{{ expense.reason }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name
:
'ExpenseList'
,
props
:
{
currencies
:
Array,
expenses
:
Array,
},
methods
:
{
getCurrencySymbol
: (
currencyName,
currencies) =>
{
for (
let i =
0
;
i <
currencies.
length;
i++
) {
if (
currencyName ===
currencies[
i].
name
)
return currencies[
i].
symbol;
}
return "$"
}
}
}
</script>
//..styles etc
Version GWT
ExpenseListComponent.java
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
@Component
public
class
ExpenseListComponent extends
VueComponent {
@Prop
@JsProperty
double
vatRate;
@Prop
@JsProperty
List<
Expense>
expenses;
@Prop
@JsProperty
List<
Currency>
currencies;
@JsMethod
public
String getCurrencySymbol
(
String currencyName) {
return
currencies.stream
(
)
.filter
(
currency ->
currency.getName
(
).equals
(
currencyName))
.findFirst
(
)
.map
(
Currency::getSymbol)
.orElse
(
"$"
);
}
}
EExpenseListComponent.html
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<vue-gwt:
import class
=
"com.gwidgets.client.dto.Expense"
/>
<div class
=
"expense-list"
>
<table class
=
"expense-table"
>
<thead>
<th>Amount</th>
<th>Date</th>
<th>VAT rate %</th>
<th>VAT</th>
<th>Reason</th>
</thead>
<tbody>
<tr v-for
=
"Expense expense in expenses"
>
<td>{{ expense.amount + getCurrencySymbol(expense.currency)}}</td>
<td>{{ expense.date }}</td>
<td>{{ expense.vatRate }}</td>
<td>{{ expense.amountVAT + getCurrencySymbol(expense.currency)}}</td>
<td>{{ expense.reason }}</td>
</tr>
</tbody>
</table>
</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 :
[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 :
2.
3.
4.
5.
6.
7.
8.
@JsMethod
public
String getCurrencySymbol
(
String currencyName) {
return
currencies.stream
(
)
.filter
(
currency ->
currency.getName
(
).equals
(
currencyName))
.findFirst
(
)
.map
(
Currency::getSymbol)
.orElse
(
"$"
);
}
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.