Skip to content

Components and events

Very quickly you will want to create your own elements: specific pages, controls…

Let us take the simplest example and flesh it out.

// import the required elements
import { Application, Button, VBox } from 'x4js';
// create the application
const app = new Application( );
// create the main view
const frame = new VBox( {
cls: "fit", // the fit class fills the parent container
content: [
new Button( { label: "Hello world" } ),
]
});
// declare it as the main view of the application
app.setMainView( frame );

Here the main view is a VBox object: children are placed one under the other. In most cases this kind of construction is not enough: you need to respond to events, and the content itself is better carried by the object. Here is how:

import { Application, BoxProps, Button, VBox } from 'x4js';
// this time we declare a class that extends 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( { } ) );

We created a MainView class that derives from VBox and implements its own constructor.

We are now going to catch the click on the button. To do so, we tell the button whom to call:

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( ), // call my onClick method
}),
]);
}
// called by the button
private onClick( ) {
alert( "I have been clicked" );
}
}

Following this principle, you create software “bricks”: login, user form, lists, dialogs…

Passing the handler in the properties is the short form. You can also subscribe later, with on:

const save = new Button( { label: "Save" } );
const sub = save.on( "click", ( ) => console.log( "saved" ) );
// to unsubscribe
sub.off( );

An application component declares three things: its properties, its events, and the shape of each event.

import { Button, ComponentEvent, ComponentEvents, ComponentProps, EventCallback, HBox, Label } from 'x4js';
// what the event carries
interface EvRename extends ComponentEvent {
name: string;
}
// the events of the component
interface TitleBarEvents extends ComponentEvents {
rename: EvRename;
}
// its properties, including the handler in short form
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 );
// connect the handlers received in the properties
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 } );
}
}

It is then used like the components provided, and the editor knows its properties and its events:

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

In the example, the label is stored in this.refs.title while the content is being built. refs is a typed directory of the children you need to reach again later. Declare its shape in the class, and write the assignment directly in the content array.

A child you never need to come back to does not have to be in it.