This site is built with Viewi itself. It is experimental and still in development. If you see any bugs please do not hesitate and open an issue or DM me on Twitter.

Components In-Depth

Unique Id

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

abstract class BaseComponent
{
    public string $__id;
}

Useful for form components with labels:

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;
}

Contains a reference for the root DOM node of the component template:

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

Refs

Sometimes it is useful to have a reference to a specific DOM element from your template.

Syntax: #myName attribute:

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

Model and two-way data binding

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

In order to use that you just need to declare a $modelValue property in your ChildComponent. And use emitEvent to notify the parent about the change:

class ChildComponent extends BaseComponent
{
    // can be any type, let us use bool for example
    public bool $modelValue = false;

    public function onClick()
    {
        $this->modelValue = !$this->modelValue;
        $this->emitEvent('model', $this->modelValue);
    }
}

In case you have an input in your child component, you can skip creating $modelValue property and emitEvent, data binding to the parent will happen automatically from the child.

<div>
    I am a child component
    <input type="text" model="$value" />
</div>

Props

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

Contains all the props passed into component. For example:

<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 props all at once

Sometimes it is useful to pass all the props as an array:

<MyComponent id="my-id" class="my-class" title="title"></MyComponent>
... OR
<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($event)"><slot></slot></button>

Usage:
Here you will receive the event from children
<MyButton (click)="click($event)">My Button</MyButton>

Click Away

Example of 'click away' functionality using Viewi:

The code:

Dialog.php

<?php

namespace Application\Components\Views\Demo\ClickAwayDemo;

use Viewi\BaseComponent;
use Viewi\Components\Services\DomHelper;
use Viewi\DOM\Events\DOMEvent;

class Dialog extends BaseComponent
{
    public bool $modelValue = false;

    function __rendered()
    {
        // click outside
        $document = DomHelper::getDocument();
        if ($document !== null) {
            $document->addEventListener('click', $this->outsideClickHandler, true);
            $document->addEventListener('keydown', $this->onKeyDown);
        }
    }

    function __destroy()
    {
        // remove click outside
        $document = DomHelper::getDocument();
        if ($document !== null) {
            $document->removeEventListener('click', $this->outsideClickHandler, true);
            $document->addEventListener('keydown', $this->onKeyDown);
        }
    }

    // click outside
    function outsideClickHandler(DOMEvent $event)
    {
        if (
            $this->modelValue // dialog is open
            && $this->_refs['target'] // dialog exists in DOM
            && $this->_refs['target'] !== $event->target // clicked on target
            && !$this->_refs['target']->contains($event->target) // clicked on target's child
        ) {
            $this->onClickOutside();
        }
    }

    function onClickOutside()
    {
        if ($this->modelValue) {
            $this->closeDialog();
        }
    }

    function onKeyDown(DOMEvent $event)
    {
        if (
            $this->modelValue // dialog is open
            && $event->keyCode === 27 // ESC key is pressed
        ) {
            $this->closeDialog();
        }
    }

    function closeDialog()
    {
        $this->modelValue = false; // hide modal
        $this->emitEvent('model', $this->modelValue);
    }
}

Dialog.html

<div if="$modelValue" id="mui-overlay" tabindex="-1"></div>
<div if="$modelValue" #target role="dialog" class="dialog" aria-modal="true">
    <div class="dialog-content">
        <slot></slot>
    </div>
    <div class="mui--text-center">
        <button (click)="closeDialog()" class="mui-btn mui-btn--accent">Close</button>
    </div>
</div>

Usage:

DialogDemo.php

<?php

namespace Application\Components\Views\Demo\ClickAwayDemo;

use Viewi\BaseComponent;

class DialogDemo extends BaseComponent
{
    public bool $showDialog = false;
}

DialogDemo.html

<div>
    <button class="mui-btn mui-btn--accent" (click)="$showDialog = true">Show Dialog</button>
    <Dialog model="$showDialog">Dialog demo</Dialog>
</div>