Aller au contenu

Composants et événements

Très rapidement, vous allez vouloir créer vos propres éléments : pages spécifiques, contrôles…

Reprenons l’exemple le plus simple et étoffons-le.

// import des éléments nécessaires
import { Application, Button, VBox } from 'x4js';
// création de l'application
const app = new Application( );
// création de la vue principale
const frame = new VBox( {
cls: "fit", // la classe fit remplit le conteneur parent
content: [
new Button( { label: "Hello world" } ),
]
});
// on la déclare comme vue principale de l'application
app.setMainView( frame );

Ici, la vue principale est un objet de type VBox : les enfants sont placés les uns sous les autres. Dans la majorité des cas, ce type de construction ne suffit pas : on a besoin de répondre à des événements, et le contenu lui-même gagne à être porté par l’objet. Voici comment procéder :

import { Application, BoxProps, Button, VBox } from 'x4js';
// cette fois, on déclare une classe qui étend VBox
class MainView extends VBox {
constructor( props: BoxProps ) {
super( props );
this.addClass( "fit" );
this.setContent( [
new Button( { label: "Hello, I'm a custom class" } ),
]);
}
}
const app = new Application( );
app.setMainView( new MainView( { } ) );

Nous avons créé une classe MainView qui dérive de VBox et qui implémente son propre constructeur.

Nous allons maintenant capter le clic sur le bouton. Pour cela, on dit au bouton qui appeler :

class MainView extends VBox {
constructor( props: BoxProps ) {
super( props );
this.addClass( "fit" );
this.setContent( [
new Button( {
label: "Hello, I'm a custom class",
click: ( ) => this.onClick( ), // appelle ma méthode onClick
}),
]);
}
// appelée par le bouton
private onClick( ) {
alert( "I have been clicked" );
}
}

En suivant ce principe, on crée des « briques » logicielles : connexion, fiche utilisateur, listes, dialogues…

Passer le gestionnaire dans les propriétés est la forme courte. On peut aussi s’abonner plus tard, avec on :

const save = new Button( { label: "Save" } );
const sub = save.on( "click", ( ) => console.log( "saved" ) );
// pour se désabonner
sub.off( );

Un composant d’application déclare trois choses : ses propriétés, ses événements, et la forme de chaque événement.

import { Button, ComponentEvent, ComponentEvents, ComponentProps, EventCallback, HBox, Label } from 'x4js';
// ce que transporte l'événement
interface EvRename extends ComponentEvent {
name: string;
}
// les événements du composant
interface TitleBarEvents extends ComponentEvents {
rename: EvRename;
}
// ses propriétés, dont le gestionnaire en forme courte
interface TitleBarProps extends ComponentProps {
title: string;
rename?: EventCallback<EvRename>;
}
class TitleBar extends HBox<TitleBarProps,TitleBarEvents> {
declare protected refs: { title: Label };
constructor( props: TitleBarProps ) {
super( props );
// branche les gestionnaires reçus dans les propriétés
this.mapPropEvents( props, "rename" );
this.setContent( [
this.refs.title = new Label( { flex: 1, text: props.title } ),
new Button( { label: "Rename", click: ( ) => this.rename( "New title" ) } ),
]);
}
private rename( name: string ) {
this.refs.title.setText( name );
this.fire( "rename", { name } );
}
}

Il s’utilise alors comme les composants fournis, et l’éditeur connaît ses propriétés et ses événements :

const bar = new TitleBar( {
title: "Untitled",
rename: ( ev ) => console.log( ev.name ),
});

Dans l’exemple, le libellé est rangé dans this.refs.title au moment où on construit le contenu. refs est un répertoire typé des enfants auxquels on doit accéder plus tard. On déclare sa forme dans la classe, et l’affectation s’écrit directement dans le tableau de contenu.

Un enfant auquel on n’a pas besoin de revenir n’a pas à y figurer.