Components In-Depth

Unique Id

Each component has a unique id, which is assigned on component creation:

abstract class BaseComponent
{
    public string $__id;
}

Useful for form components with labels:

class MyTextInput extends BaseComponent
{
    public ?string $id = null;

    function getId(): string
    {
        return $this->id ?? "input-{$this->__id}";
    }
}
<label for="{getId()}">First Name</label>
<input id="{getId()}" type="text" />

DOM element reference

abstract class BaseComponent
{
    public ?HtmlNode $_element = null;
}

_element contains a reference for the root DOM node of the component template:

class MyButton extends BaseComponent
{
    function onClick(DOMEvent $event)
    {
        // _element has a reference to the button DOM node
        $this->_element->blur();
    }
}
<button (click)="onClick"><slot></slot></button>

References (Refs)

Sometimes it is useful to have references to specific DOM elements in your template.

Syntax: #myName attribute.

All references will be stored in the _refs (associative array) property and you can access it with the name of your reference: $this->_refs['myTarget'].

<div (click)="onClickOutside"><span #myTarget>Inside Content</span></div>
class NavigationDrawer extends BaseComponent
{
    // click outside
    function onClickOutside(DOMEvent $event)
    {
        if (
            $this->_refs['myTarget'] !== $event->target 
            && !$this->_refs['myTarget']->contains($event->target)
        ) {
            // click is outside
        }
    }
}

Additionally you can create a property with your reference name and it will be assigned automatically once element is rendered:

class NavigationDrawer extends BaseComponent
{    
    public ?HtmlNode $myTarget = null;

    // click outside
    function onClickOutside(DOMEvent $event)
    {
        if (
            $myTarget !== $event->target 
            && !$myTarget->contains($event->target)
        ) {
            // click is outside
        }
    }
}

References for child components

Same works with child component reference. For example, there is a component

class ValidationMessage extends BaseComponent
{
    public string $error = '';
}

And we want to access it from its parent. Just use #myName syntax

Order details:
...
Errors:
<ValidationMessage #validationMessages />

And now use it

class OrderEdit extends BaseComponent
{
    public ?ValidationMessage $validationMessages = null;

    public function validate()
    {
        // ...
        $this->validationMessages->error = 'Something has happened';

Model and two-way data binding on components

<ChildComponent model="$value"></ChildComponent>

In order to use that you just need to declare a $model property in your ChildComponent. And use emitEvent('model', $event) to notify the parent about the change:

class TestInput extends BaseComponent
{
    public ?string $model = null;

    public function onInput(DomEvent $event)
    {
        $this->emitEvent('model', $event->target->value);
    }
}
<input (input)="onInput" model="$model" placeholder="Test Input" />

Input properties (Props)

abstract class BaseComponent
{
    public array $_props = [];
}

$_props property (associative array) contains all input properties that had been passed to the component from parent:

<MyComponent id="my-id" class="my-class" title="title" (click)="onClick"></MyComponent>
class MyComponent extends BaseComponent
{
    function clickable(): bool
    {
        return isset($this->_props['(click)']);
    }

    function getClasses()
    {
        return $this->_props['class'] ?? '';
    }
}

Passing properties all at once

Sometimes it is useful to pass all the properties as an array.

<MyComponent id="my-id" class="my-class" title="title"></MyComponent>
<!-- OR pass as an array -->
<MyComponent _props="$childProperties"></MyComponent>
<!-- OR pass all parent props -->
<MyComponent _props="{$_props}"></MyComponent>

Emit Event

Once you have your component, you may want to pass some events to the parent component. You can do it with emitEvent method:

abstract class BaseComponent
{
    function emitEvent(string $eventName, $event = null);
}

For example:

class MyButton extends BaseComponent
{
    function onClick(DOMEvent $event)
    {
        // emit it to the parent
        $this->emitEvent('click', $event);
    }
}
<button (click)="onClick"><slot></slot></button>

Here you will receive the event from children

<MyButton (click)="onClick">My Button</MyButton>