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" />
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>
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(); } } }
<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>
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'] ?? ''; } ...
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>
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>
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>