Table of contents
Introduction
Reactables is a plugin for Glowie Framework intended to create and work with dynamic and reactive view components.
Alongside with Skeltch, Reactables allows you to create amazing single-page applications and interactions, but with all the logic being handled with PHP. Think about it like some sort of Vue.js in the backend. Amazing, right?
Quick example
Here is a quick example to understand better what Reactables does:
Let's assume you have a Posts
model which represents a list of blog posts from your database table. You want to show this post list in a page, but also want to create a "search" bar where the user can filter for the post title.
You'd probably have to create a form and submit it to your controller through a route, then apply the filter in an application controller using the request query string, then reloading the page with the fetched results. That sucks.
With Reactables you can make this filter without creating forms or even reloading the page. Check out:
Component controller (app/controllers/Components/SearchPosts.php)
<?php
namespace Glowie\Controllers\Components;
use Glowie\Plugins\Reactables\Controllers\BaseComponent;
class SearchPosts extends BaseComponent{
public function create(){
$this->component->search = '';
}
public function make(){
$postsModel = new \Glowie\Models\Posts();
$postsModel->where('title', 'like', '%' . $this->component->search . '%');
$this->render('search-posts', [
'posts' => $postsModel->fetchAll()
]);
}
}
?>
Component view (app/views/components/search-posts.phtml)
<div>
<input type="text" r-model="search" placeholder="Type your search...">
{ foreach($this->posts as $post) }
<h3>{{ $post->title }}</h3>
{ /foreach }
</div>
Now, after adding the component to one of your application views, when the user types in the input element, the post list is updated in real-time with the new filter applied. This is a reactive component!
Installation
In your Glowie application folder, open a terminal and run:
composer require glowieframework/glowie-reactables
Now, in your app/config/Config.php
, search for the plugins
setting, and add the following line to the array:
'plugins' => [
// ...
\Glowie\Plugins\Reactables\Reactables::class
]
Creating components
To start creating your first Reactables component, open the terminal and run:
php firefly create-component --name=MyComponent
This will create two files: the component controller inside app/controllers/Components
and the component view inside app/views/components
.
Rendering components
To render a component in your application view, you must first include the Javascript assets that Reactables uses. To do that, in the end of your app view, before the closing </body>
tag, include the Skeltch directive:
<body>
<!-- ... -->
{ reactablesAssets }
</body>
Even if your view has more than one component, you only need to include this directive once per page.
Now, wherever you want to render your component, just use the directive:
{ component('mycomponent') }
If you want to share your view parameters with the component, pass an associative array as the second parameter of this directive.
{ component('mycomponent', ['name' => $this->name]) }
Understanding the lifecycle
When a component is rendered inside one of your application views, it's content will be initially compiled (as any regular Glowie view file), but a Javascript watcher will be bound to each interactable DOM elements inside the component. Whenever an input value changes or an action is triggered (like clicking a button), an AJAX request is sent to your component controller with the new data, the component view is recompiled and returned through the AJAX response. Then, the current view is intelligently morphed with the new data in the page.
The component controller
The component controller file is designed to handle how your component works in its lifecycle. It will be responsible for handling interactions and properties of the component, besides rendering the component view itself.
Component controllers must be stored inside app/controllers/Components
folder, and extend Glowie\Plugins\Reactables\Controllers\BaseComponent
class.
The make()
method
The heart of the component controller is the make()
method. This is where you will set what your component does whenever its created or updated. This is also where you will render your component view.
public function make(){
// ...
$this->render('mycomponent');
}
You can also pass parameters to the component view by passing an associative array as the second parameter of the render()
method.
public function make(){
// ...
$this->render('mycomponent', [
'name' => 'John'
]);
}
The create()
method
The create()
method is called when your component is initially rendered in the page. It will not be called on subsequent request, this means, when the component is refreshed or its data changes. This is useful to initialize properties of the component.
public function create(){
// ...
$this->component->name = '';
}
The update()
method
The update()
method, unlike create()
, is called whenever your component is updated. This is useful to manipulate data or sanitize something before the final render.
public function update(){
// ...
$this->component->name = strolower(trim($this->component->name));
}
Manipulating the component data
From the component controller, you can manipulate its data through the $this->component
object. It is a Glowie Element instance with your component data.
// Setting a property
$this->component->name = 'John'; # or
$this->component->set('name', 'John');
// Retrieving a property
$name = $this->component->name; # or
$name = $this->component->get('name');
// Checking if a property exists
$hasName = $this->component->has('name'); # or
$hasName = isset($this->component->name);
// Removing a property
$this->component->remove('name'); # or
unset($this->component->name);
The component view
The component view works as any regular Glowie view, and you can also use any Skeltch directives here. Component views must be a .phtml file stored inside app/views/components
.
The only requirements is that the component view MUST have a single root element. It can be any HTML element, but everything must be wrapped inside of it. This is required by our DOM parser.
<div>
<!-- My component view goes inside a single root element -->
</div>
Basic two-way data binding
Any input element inside your component can be two-way data binded to a model, which represents a property of the component. This way, whenever the input value changes, the property in the controller will be also changed, and the component view will react to it acordingly. On the other hand, when the property value in the controller changes, the input value also changes.
To bind an input to a model, use the r-model
attribute, passing the property name you want to bind.
<input type="text" r-model="name">
Hello, {{ $this->name }}!
From the component controller, you can retrieve the property through $this->component
object (as seen above).
$name = $this->component->name;
Debouncing input updates
If you want a model to update only after the user stops typing, instead of real-time, you can add a r-debounce
attribute to the element. The value of this attribute is the time, in miliseconds to wait before the component update is triggered.
This prevents the application of sending too many requests and end up loading your server.
<input type="text" r-model="name" r-debounce="500">
Deferring input updates
If you don't want a model to instantly update the component, but rather wait until another element is updated or an action (see below) is triggered, you can add a r-lazy
attribute to the element.
<input type="text" r-model="name" r-lazy>
Checkboxes
Checkboxes can have a custom value (rather than true/false) to set to their models when checked. To do that, use a value
attribute.
<input type="checkbox" r-model="accept" value="Yes">
If you want to accept multiple options in the same checkbox, bind the element to an array model.
<input type="checkbox" r-model="services[]" value="Service 1">
<input type="checkbox" r-model="services[]" value="Service 2">
<input type="checkbox" r-model="services[]" value="Service 3">
In the controller, the component property will be an array filled with the selected checkboxes custom values.
$services = $this->component->services; // ['Service 1', 'Service 2']
Radio buttons
Groups of radio buttons should be bound to the same model, with a value
attribute for each one of them.
<input type="radio" r-model="rating" value="1">
<input type="radio" r-model="rating" value="2">
<input type="radio" r-model="rating" value="3">
Selects
Select options can also have a custom value
attribute. If no value is specified, the option text will be used as the value.
<select r-model="age">
<option value="18">18 years old</option>
<option value="30">30 years old</option>
<option value="60">60 years old</option>
</select>
Multiple selects
Selects with a multiple
attribute should be bind to an array model. The value
attribute to each option is optional.
<select r-model="vehicles[]" multiple>
<option value="B">Bike</option>
<option value="C">Car</option>
<option value="A">Airplane</option>
</select>
In the controller, the component property will be an array filled with the selected options values.
$vehicles = $this->component->vehicles; // ['B', 'C']
This documentation is currently a work in progress!