Some blog post
To create a new component all you have to do is just to create two files with the same name: ComponentName.html for template and ComponentName.php for code logic.
Please note: both files should be in the same folder.
Inside your php file create a class derived from Viewi\BaseComponent
For example, let's create Counter component.
viewi-app/Components/Views/Counter/Counter.php
<?php namespace Components\Views\Counter; use Viewi\BaseComponent; class Counter extends BaseComponent { public int $count = 0; public function increment() { $this->count++; } }
viewi-app/Components/Views/Counter/Counter.html
<button (click)="increment()">Clicked $count times.</button>
From now on you can use it as a tag with the same name as class name: <Counter />
You can reuse components as many times as you want. Each of them will have their own scope of data and reactive flow. For example:
<Counter /> <Counter /> <Counter />
The result:
You can pass data to the child component using html attributes. It can pass literals like strings, numbers, booleans or you can pass expression. For example, let's create HelloMessage component:
HelloMessage.php
<?php namespace Application\Components\Views\Demo\SimpleComponent; use Viewi\BaseComponent; class HelloMessage extends BaseComponent { public string $name; }
HelloMessage.html
<div>Hello $name</div>
And now we can pass data to the component as a custom attribute:
<HelloMessage name="John Smith" /> <HelloMessage name="Viewi" /> <HelloMessage name="{getFullName()}" />
The result:
In case you want to define your component name dynamically at runtime you can use dynamic component syntax. For example:
class ComponentsBasics extends BaseComponent { public $currentPage = 'HelloMessage'; ... <$currentPage name="Dynamic Component" />
The result:
Quite often you will need to pass content into child component through inner HTML like this:
<Notification> <i>Karolina</i> sent you a message. </Notification>
This can be easily achieved with a special slot tag, like this:
Notification.html
<div> <strong>New notification:</strong> <slot></slot> </div>
The result:
Sometimes you will need to pass different contents into different places to render. A good example for this case is layout. Consider we want something like this:
<div class="header"> <!-- header content here --> </div> <main> <!-- main content here --> </main> <footer> <!-- footer content here --> </footer>
For that cases slot tag can be used with a name attribute to define whether each content belongs to. To specify content for each slot use slotContent tag with a nameattribute. Easy, content from slotContent goes to slot with the same name. The rest of the content (outside slotContent) goes to slot without nameattribute (slot by default). For example:
BaseLayout.html
<div class="header"> <slot name="header"></slot> </div> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer>
Using:
<BaseLayout> <slotContent name="header"> This is my header content </slotContent> <p> Some blog post </p> <slotContent name="footer"> <p>Some footer links and copyright</p> </slotContent> </BaseLayout>
The result:
Some blog post
Sometimes it's useful to set a default content in case if no content is provided. Just place fallback content inside slot tag:
<button> <slot>Submit</slot> </button>
Then use it without content:
<SubmitButton></SubmitButton>
Will generate:
Or within provided content:
<SubmitButton> Save </SubmitButton>
Will generate:
You can use foreach with components as well as if, else, else-if:
PostComponent:
class PostComponent extends BaseComponent { public string $content; } ... <p>$content</p>
Usage:
class ComponentsBasics extends BaseComponent { public array $posts = [ 'Viewi is awesome!', 'Lorem ipsum dolor sit amet' ]; ... <PostComponent foreach="$posts as $post" content="$post"></PostComponent>
Will generate:
Viewi is awesome!
Lorem ipsum dolor sit amet
And this:
<PostComponent if="true" content="Viewi is awesome!"></PostComponent>
Will generate:
Viewi is awesome!
Every Viewi component goes through a couple of steps during its lifecycle. It is possible to run custom code during these steps using lifecycle hooks. There it is a list of available lifecycle hooks at this moment:
Hook | Description |
---|---|
__init | Runs immediately a component is instantiated. Can accept dependency injected services and route parameters. |
__beforeMount | Runs after __init and before props (passed through attribute values) are set to the component. |
__mounted | Runs right after props (passed through attribute values) have been set to the component. |
__rendered | Runs every time instance is mounted and content is rendered into the page. Helpful when using custom javascript UI libraries. |
__destroy | Runs when instance is destroyed. Use it to unsubscribe from events, etc. Client side only. |
Having Viewi components is good. But what about some service or model class? In this case you can take all advantages of using it, like sharing data or logic between components without repeating yourself. Let's move our Counter implementation into a separate CounterState class.
<?php namespace Application\Components\Services\Demo; class CounterState { public int $count = 0; public function increment() { $this->count++; } public function decrement() { $this->count--; } }
And now, we can reuse it anywhere by injecting into __init function, like this:
<?php namespace Application\Components\Views\Demo\ServicesAndModels; use Application\Components\Services\Demo\CounterState; use Viewi\BaseComponent; class ServicesExample extends BaseComponent { public CounterState $counter; public function __init(CounterState $counterState) { $this->counter = $counterState; } }
<button (click)="$counter->decrement()" class="mui-btn mui-btn--accent">-</button> <span class="mui--text-dark mui--text-title">$counter->count</span> <button (click)="$counter->increment()" class="mui-btn mui-btn--accent">+</button>
The result will be next:
You can use a service as a store for sharing data between two components. It will be preserved during your application lifecycle. An example:
CounterStore.php
<?php namespace Application\Components\Views\Demo\ServicesAndModels; class CounterStore { public int $count = 0; public function increment() { $this->count++; } public function decrement() { $this->count--; } }
ComponentA.php
<?php namespace Application\Components\Views\Demo\ServicesAndModels; use Viewi\BaseComponent; class ComponentA extends BaseComponent { public CounterStore $counter; public function __init(CounterStore $counterState) { $this->counter = $counterState; } }
ComponentA.html
<div> <div>Component A</div> <button (click)="$counter->decrement()" class="mui-btn mui-btn--accent">-</button> <span class="mui--text-dark mui--text-title">{$counter->count}</span> <button (click)="$counter->increment()" class="mui-btn mui-btn--accent">+</button> </div>
ComponentB.php
<?php namespace Application\Components\Views\Demo\ServicesAndModels; use Viewi\BaseComponent; class ComponentB extends BaseComponent { public CounterStore $counter; public function __init(CounterStore $counterState) { $this->counter = $counterState; } }
ComponentB.html
<div> Component B <div>Count: {$counter->count}</div> </div>
The result: