Passer au contenu principal

Routing and Composites

Ouvrir dans ChatGPT

Jusqu'à présent, ce tutoriel n'était qu'une application à une seule page. Cette étape change cela.
Vous allez déplacer l'interface utilisateur que vous avez créée dans Travailler avec les données vers sa propre page et créer une autre page pour ajouter de nouveaux clients.
Ensuite, vous connecterez ces pages afin que votre application soit capable de naviguer entre elles en appliquant ces concepts :

En complétant cette étape, vous créez une version de 3-routing-and-composites.

Exécution de l'application

Au fur et à mesure que vous développez votre application, vous pouvez utiliser 3-routing-and-composites comme référence. Pour voir l'application en action :

  1. Naviguez vers le répertoire de niveau supérieur contenant le fichier pom.xml ; c'est 3-routing-and-composites si vous suivez la version sur GitHub.

  2. Utilisez la commande Maven suivante pour exécuter l'application Spring Boot localement :

    mvn

Exécuter l'application ouvre automatiquement un nouveau navigateur à http://localhost:8080.

Applications routables

Auparavant, votre application avait une fonction unique : afficher un tableau de données clients existantes.
Dans cette étape, votre application pourra également modifier les données clients en ajoutant de nouveaux clients.
Séparer les interfaces utilisateur pour l'affichage et la modification est bénéfique pour la maintenance et les tests à long terme, donc vous allez ajouter cette fonctionnalité sous forme de page séparée.
Vous rendrez votre application routable afin que webforJ puisse accéder et charger les deux interfaces utilisateur individuellement.

Une application routable affiche l'interface utilisateur en fonction de l'URL. Annoter la classe qui étend la classe App avec @Routify permet le routage, et l'élément packages indique à webforJ quels packages contiennent des composants de l'interface utilisateur.

Lorsque vous ajoutez l'annotation @Routify à Application, supprimez la méthode run(). Vous déplacerez les composants de cette méthode vers une classe que vous créerez dans le package com.webforj.tutorial.views. Votre fichier Application.java mis à jour devrait ressembler à ceci :

Application.java
@SpringBootApplication
@BundleEntry("css/card.css")
@AppTheme("system")

//Ajout de l'annotation @Routify
@Routify(packages = "com.webforj.tutorial.views")

@AppProfile(name = "CustomerApplication", shortName = "CustomerApplication")
public class Application extends App {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

// Méthode App.run() remplacée supprimée

}
CSS global

Conserver l'annotation @BundleEntry dans Application ajoute le fichier CSS au bundle frontend au niveau de l'application, donc les styles restent disponibles dans toutes les vues routées.

Création de routes

Ajouter l'annotation @Routify rend votre application routable. Une fois qu'elle est routable, votre application cherchera dans le package com.webforj.tutorial.views pour les routes.
Vous devrez créer les routes pour vos interfaces utilisateur et également spécifier leurs Types de Route. Le type de route détermine comment mapper le contenu de l'interface utilisateur à l'URL.

Le premier type de route est View. Ces types de routes se mappent directement à un segment d'URL spécifique dans votre application. Les interfaces utilisateur pour le tableau et le formulaire de nouveau client seront toutes deux des routes View.

Le deuxième type de route est Layout, qui contient une interface utilisateur apparaissant sur plusieurs pages, comme un en-tête ou une barre latérale. Les routes de mise en page enveloppent également les vues enfants sans contribuer à l'URL.

Pour spécifier le type de route d'une classe, ajoutez le type de route à la fin du nom de la classe en tant que suffixe. Par exemple, MainView est un type de route View.

Pour garder les deux fonctions de l'application séparées, votre application doit mapper les interfaces utilisateur à deux routes View uniques : une pour le tableau et une pour le formulaire client. Dans /src/main/java/com/webforj/tutorial/views, créez deux classes avec le suffixe View :

  • MainView : Cette vue aura le Table précédemment dans la classe Application.
  • FormView : Cette vue aura un formulaire pour ajouter de nouveaux clients.

Mapping des URL aux composants

Votre application est routable et sait chercher deux routes View, MainView et FormView, mais elle n'a pas d'URL spécifique pour les charger. En utilisant l'annotation @Route sur une classe de vue, vous pouvez indiquer à webforJ où la charger en fonction d'un segment d'URL donné. Par exemple, en utilisant @Route("about") dans une vue, vous mappez localement la classe à http://localhost:8080/about.

Comme son nom l'indique, MainView est la classe que vous souhaitez charger initialement lorsque l'application s'exécute. Pour y parvenir, ajoutez une annotation @Route qui mappe MainView à l'URL racine de votre application :

MainView.java
@Route("/")
public class MainView {

public MainView() {
}

}

Pour FormView, mappez la vue pour qu'elle se charge lorsque l'utilisateur se rend à http://localhost:8080/customer :

FormView.java
@Route("customer")
public class FormView {

public FormView() {
}

}
Comportement par défaut

Si vous n'attribuez pas explicitement une valeur pour l'annotation @Route, le segment d'URL est le nom de la classe converti en minuscules, avec le suffixe View supprimé.

  • MainView serait mappé à /main
  • FormView serait mappé à /form

Caractéristiques partagées

En plus d'être toutes deux des routes de vue, MainView et FormView partagent des caractéristiques supplémentaires. Certaines de ces caractéristiques partagées, comme l'utilisation de composants Composite, sont fondamentales pour l'utilisation des applications webforJ, tandis que d'autres facilitent simplement la gestion de votre application.

Utilisation des composants Composite

Lorsque l'application était monopage, vous stockiez les composants à l'intérieur d'un Frame. À partir de maintenant, avec une application à plusieurs vues, vous devrez envelopper ces composants d'interface utilisateur à l'intérieur de composants Composite.

Les composants Composite sont des wrappers qui facilitent la création de composants réutilisables. Pour créer un composant Composite, étendez la classe Composite avec un composant lié spécifié qui sert de base à la classe, par exemple, Composite<FlexLayout>.

Ce tutoriel utilise des éléments Div comme composants liés, mais ils peuvent être n'importe quel composant, tel que FlexLayout ou AppLayout. À l'aide de la méthode getBoundComponent(), vous pouvez référencer le composant lié et accéder à ses méthodes. Cela vous permet de définir la taille, d'ajouter un nom de classe CSS, d'ajouter des composants que vous souhaitez afficher dans le composant Composite et d'accéder à des méthodes spécifiques au composant.

Pour MainView et FormView, étendez Composite avec Div comme composant lié. Ensuite, référez-vous à ce composant lié afin de pouvoir ajouter les interfaces utilisateur plus tard. Les deux vues devraient ressembler à la structure suivante :

// Étendre Composite avec un composant lié
public class MainView extends Composite<Div> {

// Accéder au composant lié
private Div self = getBoundComponent();

// Créer une interface utilisateur de composant
private Button submit = new Button("Soumettre");

public MainView() {

// Ajouter le composant d'interface utilisateur au composant lié
self.add(submit);
}
}

Définir le titre de la fenêtre

Lorsque l'utilisateur a plusieurs onglets dans son navigateur, un titre de fenêtre unique l'aide à identifier rapidement quelle partie de l'application il a ouverte.

L'annotation @FrameTitle définit ce qui apparaît dans le titre du navigateur ou l'onglet de la page. Pour les deux vues, ajoutez un titre de fenêtre à l'aide de l'annotation @FrameTitle :

MainView.java
@Route("/")
@FrameTitle("Tableau des clients")
public class MainView extends Composite<Div> {

private Div self = getBoundComponent();

public MainView(CustomerService customerService) {
}
}

CSS partagé

Avec un composant lié que vous pouvez référencer dans MainView et FormView, vous pouvez le styliser avec CSS.
Vous pouvez utiliser le CSS de la première étape, Créer une application de base, pour donner aux deux vues des styles de conteneur d'interface utilisateur identiques.
Ajoutez le nom de classe CSS card au composant lié dans chaque vue :

MainView.java
@Route("/")
@FrameTitle("Tableau des clients")
public class MainView extends Composite<Div> {

private Div self = getBoundComponent();

public MainView() {

self.addClassName("card");
}
}

Utilisation de CustomerService

La dernière caractéristique partagée pour les vues est l'utilisation de la classe CustomerService.
Le Table dans MainView affiche chaque client, tandis que FormView ajoute de nouveaux clients. Étant donné que les deux vues interagissent avec les données clients, elles ont besoin d'accéder à la logique métier de l'application.

Les vues obtiennent l'accès via le service Spring créé dans Travailler avec les données, CustomerService. Pour utiliser le service Spring dans chaque vue, faites de CustomerService un paramètre de constructeur :

MainView.java
@Route("/")
@FrameTitle("Tableau des clients")
public class MainView extends Composite<Div> {

private Div self = getBoundComponent();

public MainView(CustomerService customerService) {
this.customerService = customerService;
self.addClassName("card");
}
}

Création de MainView

Après avoir rendu votre application routable, donné aux vues des wrappers de composant Composite, et inclus le CustomerService, vous êtes prêt à construire les interfaces utilisateur uniques à chaque vue. Comme mentionné précédemment, MainView contient les composants d'interface utilisateur initialement dans Application. Cette classe a également besoin d'un moyen de naviguer vers FormView.

Regroupement des méthodes Table

Alors que vous déplacez les composants de Application vers MainView, il est bon de commencer à sectionner des parties de votre application, de sorte qu'une méthode personnalisée puisse apporter des modifications au Table à la fois. La sectionnement de votre code maintenant le rend plus gérable à mesure que l'application devient plus complexe.

Maintenant, votre constructeur MainView devrait n'appeler qu'une seule méthode buildTable() qui ajoute les colonnes, définit la taille et référence le référentiel :

private void buildTable() {
table.setSize("1000px", "294px");
table.setMaxWidth("90vw");
table.addColumn("firstName", Customer::getFirstName).setLabel("Prénom");
table.addColumn("lastName", Customer::getLastName).setLabel("Nom de famille");
table.addColumn("company", Customer::getCompany).setLabel("Société");
table.addColumn("country", Customer::getCountry).setLabel("Pays");
table.setColumnsToAutoFit();
table.getColumns().forEach(column -> column.setSortable(true));
table.setRepository(customerService.getRepositoryAdapter());
}

Les utilisateurs ont besoin d'un moyen de naviguer de MainView à FormView en utilisant l'interface utilisateur.

Dans webforJ, vous pouvez naviguer directement vers une nouvelle vue en utilisant la classe de la vue. Le routage via une classe au lieu d'un segment d'URL garantit que webforJ prendra le bon chemin pour charger la vue.

Pour naviguer vers une autre vue, utilisez la classe Router pour obtenir l'emplacement actuel avec getCurrent(), puis utilisez la méthode navigate() avec la classe de la vue comme paramètre :

Router.getCurrent().navigate(FormView.class);

Ce code enverra programmatique les utilisateurs vers le formulaire de nouveau client, mais la navigation doit être connectée à une action utilisateur.
Pour permettre aux utilisateurs d'ajouter un nouveau client, vous pouvez modifier ou remplacer le bouton d'information de Application. Au lieu d'ouvrir une boîte de dialogue de message, le bouton peut naviguer vers la classe FormView :

private Button addCustomer = new Button("Ajouter un client", ButtonTheme.PRIMARY,
e -> Router.getCurrent().navigate(FormView.class));

MainView complété

Avec la navigation vers FormView et le regroupement des méthodes de tableau, voici à quoi devrait ressembler MainView avant de passer à la création de FormView :

MainView.java
@Route("/")
@FrameTitle("Tableau des clients")
public class MainView extends Composite<Div> {
private final CustomerService customerService;
private Div self = getBoundComponent();
private Table<Customer> table = new Table<>();
private Button addCustomer = new Button("Ajouter un client", ButtonTheme.PRIMARY,
e -> Router.getCurrent().navigate(FormView.class));

public MainView(CustomerService customerService) {
this.customerService = customerService;
addCustomer.setWidth(200);
buildTable();
self.setWidth("fit-content")
.addClassName("card")

Création de FormView

FormView affichera un formulaire pour ajouter de nouveaux clients. Pour chaque propriété client, FormView aura un composant éditable avec lequel les utilisateurs peuvent interagir. De plus, il aura un bouton pour que les utilisateurs soumettent les données et un bouton annuler pour les abandonner.

Création d'une instance de Customer

Lorsqu'un utilisateur modifie les données pour un nouveau client, les changements ne doivent être appliqués au référentiel que lorsqu'il est prêt à soumettre le formulaire. Utiliser une instance de l'objet Customer est un moyen pratique d'éditer et de conserver les nouvelles données sans modifier directement le référentiel. Créez un nouveau Customer à l'intérieur de FormView à utiliser pour le formulaire :

private Customer customer = new Customer();

Pour rendre l'instance Customer éditable, chaque propriété, à l'exception de l'id, doit être associée à un composant éditable. Les changements qu'un utilisateur effectue dans l'interface utilisateur doivent se refléter dans l'instance Customer.

Ajout de composants TextField

Les trois premières propriétés éditables dans Customer (firstName, lastName et company) sont toutes des valeurs String et doivent être représentées par un éditeur de texte sur une seule ligne. Les composants TextField sont un excellent choix pour représenter ces propriétés.

Avec le composant TextField, vous pouvez ajouter un label et un auditeur d'événements qui se déclenche à chaque fois que la valeur du champ change. Chaque auditeur d'événements doit mettre à jour l'instance Customer pour la propriété correspondante.

Ajoutez trois composants TextField qui mettent à jour l'instance Customer :

FormView.java
public class FormView extends Composite<Div> {
private final CustomerService customerService;
private Customer customer = new Customer();
private Div self = getBoundComponent();

private TextField firstName = new TextField("Prénom", e -> customer.setFirstName(e.getValue()));
private TextField lastName = new TextField("Nom de famille", e -> customer.setLastName(e.getValue()));
private TextField company = new TextField("Société", e -> customer.setCompany(e.getValue()));

public FormView(CustomerService customerService) {
this.customerService = customerService;
self.addClassName("card");
}
}
Convention de nommage partagée

Nommer les composants de la même manière que les propriétés qu'ils représentent pour l'entité Customer facilite la liaison des données dans une étape future, Validation et liaison des données.

Ajout d'un composant ChoiceBox

Utiliser un TextField pour la propriété country ne serait pas idéal, car la propriété ne peut être qu'une des cinq valeurs d'énumération : UNKNOWN, GERMANY, ENGLAND, ITALY et USA.

Un meilleur composant pour sélectionner parmi une liste d'options prédéfinies est le composant ChoiceBox.

Chaque option pour un composant ChoiceBox est représentée comme un ListItem. Chaque ListItem a deux valeurs, une clé Object et un texte String à afficher dans l'interface utilisateur. Avoir deux valeurs pour chaque option vous permet de gérer l'Object en interne tout en présentant simultanément une option plus lisible pour les utilisateurs dans l'interface utilisateur.

Par exemple, la clé Object pourrait être un numéro international normalisé du livre (ISBN), tandis que le texte String est le titre du livre, ce qui est plus compréhensible.

new ListItem(isbn, bookTitle);

Cependant, cette application traite d'une liste de noms de pays, pas de livres. Pour chaque ListItem, vous voulez que l'Object soit l'énumération Customer.Country, tandis que le texte peut être sa représentation String.

Pour ajouter toutes les options de country dans un ChoiceBox, vous pouvez utiliser un itérateur pour créer un ListItem pour chaque énumération Customer.Country, et les mettre dans un ArrayList<ListItem>. Ensuite, vous pouvez insérer cet ArrayList<ListItem> dans un composant ChoiceBox :

//Créer le composant ChoiceBox
private ChoiceBox country = new ChoiceBox("Pays");

//Créer un ArrayList d'objets ListItem
ArrayList<ListItem> listCountries = new ArrayList<>();

//Ajouter un itérateur qui crée un ListItem pour chaque option Customer.Country
for (Country countryItem : Customer.Country.values()) {
listCountries.add(new ListItem(countryItem, countryItem.toString()));
}

//Insérer l'ArrayList rempli dans le ChoiceBox
country.insert(listCountries);

//Fait du premier `ListItem` le par défaut lorsque le formulaire se charge
country.selectIndex(0);

Ensuite, lorsque l'utilisateur sélectionne une option dans le ChoiceBox, l'instance Customer doit se mettre à jour avec la clé de l'élément sélectionné, qui est une valeur de Customer.Country.

private ChoiceBox country = new ChoiceBox("Pays",
e -> customer.setCountry((Customer.Country) e.getSelectedItem().getKey()));

Pour garder le code propre, l'itérateur qui crée le ArrayList<ListItem> et l'ajoute au ChoiceBox doit se trouver dans une méthode séparée.
Après avoir ajouté un ChoiceBox qui permet à l'utilisateur de choisir la propriété country, FormView devrait ressembler à ceci :

FormView.java
public class FormView extends Composite<Div> {
private final CustomerService customerService;
private Customer customer = new Customer();
private Div self = getBoundComponent();
private TextField firstName = new TextField("Prénom", e -> customer.setFirstName(e.getValue()));
private TextField lastName = new TextField("Nom de famille", e -> customer.setLastName(e.getValue()));
private TextField company = new TextField("Société", e -> customer.setCompany(e.getValue()));

private ChoiceBox country = new ChoiceBox("Pays",
e -> customer.setCountry((Customer.Country) e.getSelectedItem().getKey()));

public FormView(CustomerService customerService) {
this.customerService = customerService;
self.addClassName("card");
fillCountries();
}

private void fillCountries() {
ArrayList<ListItem> listCountries = new ArrayList<>();
for (Country countryItem : Customer.Country.values()) {
listCountries.add(new ListItem(countryItem, countryItem.toString()));
}
country.insert(listCountries);
country.selectIndex(0);
}

}

Ajout de composants Button

Lorsqu'ils utilisent le nouveau formulaire client, les utilisateurs devraient pouvoir soit sauvegarder soit abandonner leurs modifications.
Créez deux composants Button pour mettre en œuvre cette fonctionnalité :

private Button submit = new Button("Soumettre");
private Button cancel = new Button("Annuler");

Les boutons de soumission et d'annulation doivent renvoyer l'utilisateur vers MainView.
Cela permet à l'utilisateur de voir immédiatement les résultats de son action, qu'il voit un nouveau client dans le tableau ou que celui-ci reste inchangé. Étant donné que plusieurs entrées dans FormView amènent les utilisateurs à MainView, la navigation doit être mise dans une méthode rappelable :

private void navigateToMain(){
Router.getCurrent().navigate(MainView.class);
}

Bouton Annuler

Abandonner les modifications sur le formulaire ne nécessite pas de code supplémentaire pour l'événement, au-delà du retour vers MainView. Cependant, comme l'annulation n'est pas une action principale, donner au bouton un thème outline donne plus de préséance au bouton de soumission.
La section Thèmes de la page du composant Button répertorie tous les thèmes disponibles.

private Button cancel = new Button("Annuler", ButtonTheme.OUTLINED_PRIMARY,
e -> navigateToMain());

Bouton Soumettre

Lorsqu'un utilisateur appuie sur le bouton de soumission, les valeurs de l'instance Customer doivent être utilisées pour créer une nouvelle entrée dans le référentiel.

À l'aide de CustomerService, vous pouvez prendre l'instance Customer pour mettre à jour la base de données H2. Lorsque cela se produit, un nouvel et unique id est attribué à ce Customer. Après avoir mis à jour le référentiel, vous pouvez rediriger les utilisateurs vers MainView, où ils peuvent voir le nouveau client dans le tableau.

private Button submit = new Button("Soumettre", ButtonTheme.PRIMARY,
e -> submitCustomer());

//...

private void submitCustomer() {
customerService.createCustomer(customer);
navigateToMain();
}

Utilisation d'un ColumnsLayout

Après avoir ajouté les composants TextField, ChoiceBox et Button, vous avez maintenant toutes les parties interactives du formulaire. La dernière amélioration à FormView dans cette étape est d'organiser visuellement les six composants.

Ce formulaire peut utiliser un ColumnsLayout pour séparer les composants en deux colonnes sans avoir à définir la largeur de composants interactifs.
Pour créer un ColumnsLayout, spécifiez chaque composant qui doit être à l'intérieur de la mise en page :

private ColumnsLayout layout = new ColumnsLayout(
firstName, lastName,
company, country,
submit, cancel);

Pour définir le nombre de colonnes pour un ColumnsLayout, utilisez une List d'objets Breakpoint. Chaque Breakpoint indique au ColumnsLayout la largeur minimale qu'il doit avoir pour appliquer un nombre de colonnes spécifié. En utilisant le ColumnsLayout, vous pouvez créer un formulaire avec deux colonnes, mais uniquement si l'écran est suffisamment large pour afficher deux colonnes. Sur les écrans plus petits, les composants sont affichés en une seule colonne.

La section Breakpoints de l'article sur ColumnsLayout explique plus en détail les points d'arrêt.

Pour garder le code maintenable, définissez les points d'arrêt dans une méthode séparée. Dans cette méthode, vous pouvez également contrôler l'espacement horizontal et vertical entre les composants à l'intérieur du ColumnsLayout à l'aide de la méthode setSpacing().

private void setColumnsLayout() {

//Avoir deux colonnes dans le ColumnsLayout si c'est plus large que 600px
List<Breakpoint> breakpoints = List.of(
new Breakpoint(600, 2));

//Ajouter la liste des points d'arrêt
layout.setBreakpoints(breakpoints);

//Définir l'espacement entre les composants à l'aide d'une variable CSS DWC
layout.setSpacing("var(--dwc-space-l)")
}

Enfin, vous pouvez ajouter le nouveau ColumnsLayout créé au composant lié de FormView, tout en définissant la largeur maximale et en ajoutant le nom de classe d'entrée :

self.setMaxWidth(600)
.addClassName("card")
.add(layout);

FormView complété

Après avoir ajouté une instance de Customer, les composants interactifs et le ColumnsLayout, votre FormView devrait ressembler à ce qui suit :

FormView.java
@Route("customer")
@FrameTitle("Formulaire client")
public class FormView extends Composite<Div> {
private final CustomerService customerService;
private Customer customer = new Customer();
private Div self = getBoundComponent();
private TextField firstName = new TextField("Prénom", e -> customer.setFirstName(e.getValue()));
private TextField lastName = new TextField("Nom de famille", e -> customer.setLastName(e.getValue()));
private TextField company = new TextField("Société", e -> customer.setCompany(e.getValue()));
private ChoiceBox country = new ChoiceBox("Pays",
e -> customer.setCountry((Customer.Country) e.getSelectedItem().getKey()));
private Button submit = new Button("Soumettre", ButtonTheme.PRIMARY, e -> submitCustomer());
private Button cancel = new Button("Annuler", ButtonTheme.OUTLINED_PRIMARY, e -> navigateToMain());
private ColumnsLayout layout = new ColumnsLayout(
firstName, lastName,

Prochaine étape

Puisque les utilisateurs peuvent maintenant ajouter des clients, votre application devrait être capable de modifier les clients existants en utilisant le même formulaire. Dans la prochaine étape, Observateurs et paramètres de route, vous permettrez à l'id du client d'être un paramètre initial pour FormView, afin qu'il puisse remplir le formulaire avec les données de ce client et permettre aux utilisateurs de changer les propriétés.