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 Basics

Base Example

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 />

Reusing Components

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:

Passing Data through Properties

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:

Hello John Smith
Hello Viewi
Hello Paul White

Dynamic Components

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:

Hello Dynamic Component

Slots

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:

New notification: Karolina sent you a message.

Named Slots

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:

This is my header content

Some blog post

Some footer links and copyright

Fallback Content

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:

Using with If, Foreach

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!

Lifecycle

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.

Services and Models

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:

0

Sharing a state

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:

Component A
0
Component B
Count: 0