Routing and Composites
F jusqu'à présent, ce tutoriel n'était qu'une application d'une seule page. Cette étape change cela. Vous déplacerez l'interface utilisateur que vous avez créée dans Travailler avec des données vers sa propre page et créerez une autre page pour ajouter de nouveaux clients. Ensuite, vous connecter ces pages afin que votre application soit capable de naviguer entre elles en appliquant ces concepts :
- Routage
- Composer des composants
- Le composant
ColumnsLayout
Compléter cette étape crée une version de 3-routing-and-composites.
Exécution de l'application
En développant votre application, vous pouvez utiliser 3-routing-and-composites comme comparaison. Pour voir l'application en action :
-
Naviguez jusqu'au répertoire de niveau supérieur contenant le fichier
pom.xml; c'est3-routing-and-compositessi vous suivez la version sur GitHub. -
Utilisez la commande Maven suivante pour exécuter l'application Spring Boot localement :
mvn
L'exécution de l'application ouvre automatiquement un nouveau navigateur à http://localhost:8080.
Applications routables
Auparavant, votre application avait une seule fonction : afficher un tableau de données clients existantes. Dans cette étape, votre application sera également capable de 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 ajouterez cette fonctionnalité en tant que page séparée. Vous allez rendre votre application routable afin que webforJ puisse accéder et charger les deux interfaces utilisateur individuellement.
Une application routable rend l'interface utilisateur en fonction de l'URL. Annoter la classe qui étend la classe App avec @Routify active le routage, et l'élément packages indique à webforJ quels packages contiennent des composants d'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 :
@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);
}
// Suppression de la méthode App.run() remplacée
}
Gardez l'annotation @BundleEntry dans Application pour ajouter le fichier CSS au bundle frontend au niveau de l'application, afin que les styles restent disponibles dans toutes les vues routées.
Création de routes
L'ajout de l'annotation @Routify rend votre application routable. Une fois qu'elle est routable, votre application cherchera dans le package com.webforj.tutorial.views des routes.
Vous devrez créer les routes pour vos interfaces utilisateur et spécifier également 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 de type View.
Le deuxième type de route est Layout, qui contient l'interface utilisateur qui apparaît sur plusieurs pages, comme un en-tête ou une barre latérale. Les routes de mise en page englobent é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 comme un 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 uniques de type View : une pour le tableau et une pour le formulaire client. Dans /src/main/java/com/webforj/tutorial/views, créez deux classes avec un suffixe View :
MainView: Cette vue contiendra leTableprécédemment dans la classeApplication.FormView: Cette vue contiendra un formulaire pour ajouter de nouveaux clients.
Mapping des URLs aux composants
Votre application est routable et sait qu'elle doit rechercher deux routes de type 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 ce faire, ajoutez une annotation @Route qui mappe MainView à l'URL racine de votre application :
@Route("/")
public class MainView {
public MainView() {
}
}
Pour le FormView, mappez la vue afin qu'elle se charge lorsqu'un utilisateur accède à http://localhost:8080/customer :
@Route("customer")
public class FormView {
public FormView() {
}
}
Si vous n'assignez pas explicitement une valeur à l'annotation @Route, le segment d'URL est le nom de la classe converti en minuscules, avec le suffixe View supprimé.
MainViewserait mappé à/mainFormViewserait 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 traits partagés, comme l'utilisation de composants Composite, sont fondamentaux 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 à page unique, vous avez stocké les composants à l'intérieur d'un Frame. À l'avenir, avec une application à plusieurs vues, vous devrez envelopper ces composants d'interface utilisateur dans des 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 fondation de 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, comme le FlexLayout ou le AppLayout. En utilisant la méthode getBoundComponent(), vous pouvez référencer le composant lié et avoir accès à 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érencez ce composant lié afin que vous puissiez intégrer les interfaces utilisateur plus tard. Les deux vues devraient avoir une structure similaire à la 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 pour le composant
private Button submit = new Button("Submit");
public MainView() {
// Ajouter le composant d'interface utilisateur au composant lié
self.add(submit);
}
}
Définir le titre de la fenêtre
Lorsqu'un 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 en utilisant l'annotation @FrameTitle :
- MainView
- FormView
@Route("/")
@FrameTitle("Table des clients")
public class MainView extends Composite<Div> {
private Div self = getBoundComponent();
public MainView(CustomerService customerService) {
}
}
@Route("customer")
@FrameTitle("Formulaire client")
public class FormView extends Composite<Div> {
private Div self = getBoundComponent();
public FormView(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 UI identiques.
Ajoutez le nom de classe CSS card au composant lié dans chaque vue :
- MainView
- FormView
@Route("/")
@FrameTitle("Table des clients")
public class MainView extends Composite<Div> {
private Div self = getBoundComponent();
public MainView() {
self.addClassName("card");
}
}
@Route("customer")
@FrameTitle("Formulaire client")
public class FormView extends Composite<Div> {
private Div self = getBoundComponent();
public FormView() {
self.addClassName("card");
}
}
Utilisation de CustomerService
Le dernier trait partagé pour les vues est l'utilisation de la classe CustomerService.
Le Table dans MainView affiche chaque client, tandis que FormView ajoute de nouveaux clients. Comme les deux vues interagissent avec les données clients, elles ont besoin d'accès à la logique métier de l'application.
Les vues obtiennent l'accès par le service Spring créé dans Travailler avec des données, CustomerService. Pour utiliser le service Spring dans chaque vue, faites de CustomerService un paramètre du constructeur :
- MainView
- FormView
@Route("/")
@FrameTitle("Table des clients")
public class MainView extends Composite<Div> {
private Div self = getBoundComponent();
public MainView(CustomerService customerService) {
this.customerService = customerService;
self.addClassName("card");
}
}
@Route("customer")
@FrameTitle("Formulaire client")
public class FormView extends Composite<Div> {
private Div self = getBoundComponent();
public FormView(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 du Table
Lorsque vous déplacez les composants de Application à MainView, c'est une bonne idée de commencer à sectionner les parties de votre application, de sorte qu'une méthode personnalisée unique puisse apporter des modifications au Table à la fois. Sectionner votre code maintenant en rend la gestion plus facile à mesure que l'application devient plus complexe.
Maintenant, votre constructeur MainView ne devrait appeler qu'une seule méthode buildTable() qui ajoute les colonnes, définit la taille et référence le dépôt :
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");
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());
}
Navigation vers FormView
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 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 vue différente, 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 de l'utilisateur.
Pour permettre aux utilisateurs d'ajouter un nouveau client, vous pouvez soit modifier, soit remplacer le bouton d'information du 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 terminé
Avec la navigation vers FormView et des méthodes de tableau regroupées, voici à quoi devrait ressembler MainView avant de passer à la création de FormView :
Création de FormView
FormView affichera un formulaire pour ajouter de nouveaux clients. Pour chaque propriété client, FormView aura un composant éditable pour que les utilisateurs puissent interagir. De plus, il aura un bouton pour que les utilisateurs soumettent les données et un bouton annulé pour les rejeter.
Création d'une instance Customer
Lorsqu'un utilisateur modifie des données pour un nouveau client, les modifications ne doivent être appliquées au dépôt que lorsqu'il est prêt à soumettre le formulaire. Utiliser une instance de l'objet Customer est un moyen pratique d'éditer et de maintenir les nouvelles données sans modifier directement le dépôt. Créez un nouveau Customer à l'intérieur de FormView à utiliser pour le formulaire :
private Customer customer = new Customer();
Pour rendre l'instance Customer modifiable, chaque propriété, à l'exception de l'id, doit être associée à un composant éditable. Les changements qu'un utilisateur apporte à l'interface utilisateur doivent être reflétés dans l'instance Customer.
Ajout de composants TextField
Les trois premières propriétés modifiables dans Customer (firstName, lastName, et company) sont toutes des valeurs String, et doivent être représentées par un éditeur de texte à une seule ligne. Les composants TextField sont un excellent choix pour représenter ces propriétés.
Avec le composant TextField, vous pouvez ajouter une étiquette et un écouteur d'événements qui se déclenche à chaque fois que la valeur du champ change. Chaque écouteur d'événements doit mettre à jour l'instance Customer pour la propriété correspondante.
Ajoutez trois composants TextField qui mettent à jour l'instance Customer :
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", 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");
}
}
Nommer les composants de la même façon 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 énumérées : UNKNOWN, GERMANY, ENGLAND, ITALY, et USA.
Un meilleur composant pour sélectionner dans une liste prédéfinie d'options 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.
new ListItem(isbn, bookTitle);
Cependant, cette application traite d'une liste de noms de pays, et non de livres. Pour chaque ListItem, vous souhaitez que l'Object soit l'énumération Customer.Country, tandis que le texte peut être sa représentation String.
Pour ajouter toutes les options 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 une ArrayList<ListItem>. Ensuite, vous pouvez insérer cette ArrayList<ListItem> dans un composant ChoiceBox :
//Créer le composant ChoiceBox
private ChoiceBox country = new ChoiceBox("Pays");
//Créer une 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 remplie dans le ChoiceBox
country.insert(listCountries);
//Fait le premier `ListItem` 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 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 l'ArrayList<ListItem> et l'ajoute au ChoiceBox devrait être 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 :
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", 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 formulaire de nouveau client, les utilisateurs devraient pouvoir sauvegarder ou annuler leurs changements.
Créez deux composants Button pour implémenter cette fonctionnalité :
private Button submit = new Button("Soumettre");
private Button cancel = new Button("Annuler");
Les boutons de soumission et d'annulation devraient ramener l'utilisateur à MainView.
Cela permet à l'utilisateur de voir immédiatement les résultats de son action, qu'il s'agisse de voir un nouveau client dans le tableau ou de constater qu'il reste inchangé.
Puisque plusieurs entrées dans FormView renvoient les utilisateurs à MainView, la navigation doit être mise dans une méthode rappelable :
private void navigateToMain(){
Router.getCurrent().navigate(MainView.class);
}
Bouton d'annulation
Annuler les changements dans le formulaire ne nécessite pas de code supplémentaire pour l'événement au-delà de revenir à MainView. Cependant, comme annuler n'est pas une action principale, définir le thème du bouton pour qu'il soit outline donne plus de proéminence au bouton de soumission.
La section Thèmes de la page du composant Button liste tous les thèmes disponibles.
private Button cancel = new Button("Annuler", ButtonTheme.OUTLINED_PRIMARY,
e -> navigateToMain());
Bouton de soumission
Lorsqu'un utilisateur appuie sur le bouton de soumission, les valeurs de l'instance Customer devraient être utilisées pour créer une nouvelle entrée dans le dépôt.
En utilisant le 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 dépôt, 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
Avec l'ajout des 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 l'un des composants interactifs.
Pour créer un ColumnsLayout, spécifiez chaque composant qui devrait ê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 spécifié de colonnes. En utilisant le ColumnsLayout, vous pouvez faire un formulaire avec deux colonnes, mais uniquement si l'écran est suffisamment large pour afficher deux colonnes. Sur des écrans plus petits, les composants sont affichés dans une seule colonne.
La section Breakpoints dans l'article ColumnsLayout explique plus en détail les breakpoints.
Pour garder le code maintenable, définissez les breakpoints 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 avec la méthode setSpacing().
private void setColumnsLayout() {
//Avoir deux colonnes dans le ColumnsLayout s'il est plus large que 600px
List<Breakpoint> breakpoints = List.of(
new Breakpoint(600, 2));
//Ajouter la liste de breakpoints
layout.setBreakpoints(breakpoints);
//Définir l'espacement entre les composants en utilisant une variable CSS DWC
layout.setSpacing("var(--dwc-space-l)")
}
Enfin, vous pouvez ajouter le ColumnsLayout nouvellement créé au composant lié de FormView, tout en définissant également la largeur maximale, et en ajoutant le nom de classe d'avant :
self.setMaxWidth(600)
.addClassName("card")
.add(layout);
FormView terminé
Après avoir ajouté une instance Customer, les composants interactifs, et le ColumnsLayout, votre FormView devrait avoir l'apparence suivante :
Prochaine étape
Maintenant que les utilisateurs peuvent ajouter des clients, votre application doit ê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 modifier les propriétés.