Blade-Components
The User Interface of the application is implemented server side with Blade. CoreUI is used to provide and appealing design. In some places Vue is used for dynamic content. Prefabricated Blade-Components are used for a consistent and productive design of the input and output views of data records.
Concepts
These components form several layers.
- The Base-Layer integrates simple HTML elements with Laravel.
id
-attribute based on thename
-attribute- automatic loading of values based on the
name
-attribute - Translate the name from Dot-Notation (
x.y
) to Array-Notation (x[y]
) - Load old entries in the event of validation errors
- in
config/view.php
configurable CSS-Class according to the validation status - in
config/view.php
configurable CSS-Class for empty fields - Presentation for both humans and machines
- The CoreUI layer builds on this and offers layout and styling.
In addition to the special input and output components in each layer, the base layer also offers a version of generic HTML elements that can be accessed if required. In general, every automatic behavior is overwritten by explicit information.
The available components can be found in the subdirectory src/Support/View/Components
.
You can also interact with them in the web browser at http://localhost/widget after starting the development environment with the command bin/ptr dev
.
Examples
Some application examples can be found below
Empty input form with Request as implicit data source
An input form is presented to a user until it is accepted by the server-side validation. In the event of resubmission due to validation errors, the errors should be displayed and the form should be pre-filled with the user’s previous entries.
Source Code
For example, this can be implemented with the components as follows.
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function create($request)
{
return (string) $this->blade(
<<< 'blade'
<x-base::form action="/store" method="post">
<label>
Gender
<x-base::select
name="person.gender"
:options="['diverse', 'male', 'female']"
placeholder=""
/>
@error('person.gender')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
First Name
<x-base::in.inline-text name="person.first_name"/>
@error('person.first_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Last Name
<x-base::in.inline-text name="person.last_name"/>
@error('person.last_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Birthdate
<x-base::in.date name="person.birthdate"/>
@error('person.birthdate')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
</x-base::form>
blade,
);
}
public function store($request)
{
$request->validate([
'person.gender' => ['required', 'in:0,1,2'],
'person.first_name' => ['required', 'string'],
'person.last_name' => ['required', 'string'],
'person.birthdate' => ['required', 'date_format:Y-m-d'],
]);
}
}
return (new Controller())->create(request());
Note that the previous entries are filled in automatically and without additional program logic.
Output
The above PHP Source Code results in the following HTML-Output.
<form enctype="multipart/form-data" method="post" action="/store">
<input type="hidden" name="_token" value=""/>
<label>
Gender
<select id="person.gender" name="person[gender]">
<option value=""></option>
<option value="0">diverse</option>
<option value="1">male</option>
<option value="2">female</option>
</select>
</label>
<br/>
<label>
First Name
<input id="person.first_name" name="person[first_name]" type="text"/>
</label>
<br/>
<label>
Last Name
<input id="person.last_name" name="person[last_name]" type="text"/>
</label>
<br/>
<label>
Birthdate
<input id="person.birthdate" name="person[birthdate]" type="date"/>
</label>
</form>
Notice the following:
- Field for CSRF-Protection
id
-attributes are derived from thename
-attributename
-attribute is converted to Array-Formatoption
-elements are created
Note
Elements with an id
-attribute which contains dots cannot be compared with an CSS-ID-Selector (for example input#person.birthdate
).
Everything after the dot would not be interpreted as part of the id
-attribute but as part of the class
-attribute.
Instead, they have to be compared with a CSS-Attribute-Selector (for example input[id="person.birthdate"]
).
Pre-filled input form with Request as implicit data source
As with an empty input form, the Request can also serve as a data source for a pre-filled one.
The previous entries made by the user are in the Session under the key _old_input
.
These can also be queried via Request::old().
The session-key _old_input
can either be set directly with Session::put() or the input of the Request can be changed and then loaded into the session.
Source Code
In the following, the input of the Requests is pre-assigned with Request::replace() and then loaded into the session with Request::flash().
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function edit($request)
{
if ($request->old() === []) {
$request->replace(['person' => [
'gender' => '0',
'first_name' => 'Dana',
'last_name' => 'Debug',
'birthdate' => '2001-02-03',
]])->flash();
}
return (string) $this->blade(
<<< 'blade'
<x-base::form action="/update" method="put">
<label>
Gender
<x-base::select
name="person.gender"
:options="['diverse', 'male', 'female']"
placeholder=""
/>
@error('person.gender')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
First Name
<x-base::in.inline-text name="person.first_name"/>
@error('person.first_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Last Name
<x-base::in.inline-text name="person.last_name"/>
@error('person.last_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Birthdate
<x-base::in.date name="person.birthdate"/>
@error('person.birthdate')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
</x-base::form>
blade,
);
}
public function update($request)
{
$request->validate([
'person.gender' => ['required', 'in:0,1,2'],
'person.first_name' => ['required', 'string'],
'person.last_name' => ['required', 'string'],
'person.birthdate' => ['required', 'date_format:Y-m-d'],
]);
}
}
return (new Controller())->edit(request());
Note that the pre-assignment only takes place if the user has not yet made any entries, as these would otherwise be overwritten.
Output
The above PHP Source Code results in the following HTML-Output.
<form enctype="multipart/form-data" method="post" action="/update">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="_token" value=""/>
<label>
Gender
<select id="person.gender" name="person[gender]">
<option value=""></option>
<option selected="" value="0">diverse</option>
<option value="1">male</option>
<option value="2">female</option>
</select>
</label>
<br/>
<label>
First Name
<input id="person.first_name" name="person[first_name]" type="text" value="Dana"/>
</label>
<br/>
<label>
Last Name
<input id="person.last_name" name="person[last_name]" type="text" value="Debug"/>
</label>
<br/>
<label>
Birthdate
<input id="person.birthdate" name="person[birthdate]" type="date" value="2001-02-03"/>
</label>
</form>
Notice the following:
- Field for Method-Spoofing
selected
-attribute of the correspondingoption
-element is setvalue
-attributes of theinput
-elements are set
Pre-filled input form with an explicit data source
Instead of using the Request as an implicit data source, the data to be used can also be specified explicitly.
Source Code
For example, this can be implemented with the components as follows.
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function edit($request)
{
return (string) $this->blade(
<<< 'blade'
<x-base::form action="/update" method="put">
<label>
Gender
<x-base::select
:data="$data"
name="person.gender"
:options="['diverse', 'male', 'female']"
placeholder=""
/>
@error('person.gender')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
First Name
<x-base::in.inline-text :data="$data" name="person.first_name"/>
@error('person.first_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Last Name
<x-base::in.inline-text :data="$data" name="person.last_name"/>
@error('person.last_name')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
<br/>
<label>
Birthdate
<x-base::in.date :data="$data" name="person.birthdate"/>
@error('person.birthdate')
<x-base::out.inline-text value="{{ $message }}"/>
@enderror
</label>
</x-base::form>
blade,
[
'data' => $request->old() ?: ['person' => [
'gender' => '0',
'first_name' => 'Dana',
'last_name' => 'Debug',
'birthdate' => '2001-02-03',
]],
],
);
}
public function update($request)
{
$request->validate([
'person.gender' => ['required', 'in:0,1,2'],
'person.first_name' => ['required', 'string'],
'person.last_name' => ['required', 'string'],
'person.birthdate' => ['required', 'date_format:Y-m-d'],
]);
}
}
return (new Controller())->edit(request());
Note that the data source must be passed to each element individually.
Output
The above PHP Source Code results in the following HTML-Output.
<form enctype="multipart/form-data" method="post" action="/update">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="_token" value=""/>
<label>
Gender
<select id="person.gender" name="person[gender]">
<option value=""></option>
<option selected="" value="0">diverse</option>
<option value="1">male</option>
<option value="2">female</option>
</select>
</label>
<br/>
<label>
First Name
<input id="person.first_name" name="person[first_name]" type="text" value="Dana"/>
</label>
<br/>
<label>
Last Name
<input id="person.last_name" name="person[last_name]" type="text" value="Debug"/>
</label>
<br/>
<label>
Birthdate
<input id="person.birthdate" name="person[birthdate]" type="date" value="2001-02-03"/>
</label>
</form>
Display
The prefabricated blade components can not only make it easier to enter values, but also to output them.
Source Code
For example, this can be implemented as follows.
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function show($request)
{
return (string) $this->blade(
<<< 'blade'
<div>
<x-base::out.url value="/edit">Edit</x-base::out.url>
<br/>
<strong>Gender</strong>
<x-base::out.inline-text name="person.gender" value="diverse"/>
<br/>
<strong>First Name</strong>
<x-base::out.inline-text name="person.first_name" value="Dana"/>
<br/>
<strong>Last Name</strong>
<x-base::out.inline-text name="person.last_name" value="Debug"/>
<br/>
<strong>Birthdate</strong>
<x-base::out.date locale="de" name="person.birthdate" :value="carbon('2001-02-03')"/>
</div>
blade,
);
}
}
return (new Controller())->show(request());
Note that the value of the date field is an object and not a simple string.
Output
The above PHP Source Code results in the following HTML-Output.
<div>
<a href="/edit">Edit</a>
<br/>
<strong>Gender</strong>
<span id="person.gender">diverse</span>
<br/>
<strong>First Name</strong>
<span id="person.first_name">Dana</span>
<br/>
<strong>Last Name</strong>
<span id="person.last_name">Debug</span>
<br/>
<strong>Birthdate</strong>
<time id="person.birthdate" datetime="2001-02-03">03.02.2001</time>
</div>
Note that the date of birth is both machine-readable and localized for humans.
Display with Request as implicit data source
As with the input, the Request can also serve as a data source for the output. The input data can be queried via Request::input(). They can be set using Request::replace().
Source Code
For example, this can be implemented as follows.
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function show($request)
{
$request->replace(['person' => [
'gender' => 'diverse',
'first_name' => 'Dana',
'last_name' => 'Debug',
'birthdate' => carbon('2001-02-03'),
]]);
return (string) $this->blade(
<<< 'blade'
<div>
<x-base::out.url value="/edit">Edit</x-base::out.url>
<br/>
<strong>Gender</strong>
<x-base::out.inline-text name="person.gender"/>
<br/>
<strong>First Name</strong>
<x-base::out.inline-text name="person.first_name"/>
<br/>
<strong>Last Name</strong>
<x-base::out.inline-text name="person.last_name"/>
<br/>
<strong>Birthdate</strong>
<x-base::out.date locale="de" name="person.birthdate"/>
</div>
blade,
);
}
}
return (new Controller())->show(request());
Output
The above PHP Source Code results in the following HTML-Output.
<div>
<a href="/edit">Edit</a>
<br/>
<strong>Gender</strong>
<span id="person.gender">diverse</span>
<br/>
<strong>First Name</strong>
<span id="person.first_name">Dana</span>
<br/>
<strong>Last Name</strong>
<span id="person.last_name">Debug</span>
<br/>
<strong>Birthdate</strong>
<time id="person.birthdate" datetime="2001-02-03">03.02.2001</time>
</div>
Display with explicit data source
Instead of using the Request as an implicit data source, the data to be used can also be specified explicitly.
Source Code
For example, this can be implemented as follows.
<?php
class Controller extends \Illuminate\Routing\Controller
{
// Used for demonstration purposes only, to render Template-Strings.
use \Illuminate\Foundation\Testing\Concerns\InteractsWithViews;
public function show($request)
{
return (string) $this->blade(
<<< 'blade'
<div>
<x-base::out.url value="/edit">Edit</x-base::out.url>
<br/>
<strong>Gender</strong>
<x-base::out.inline-text :data="$data" name="person.gender"/>
<br/>
<strong>First Name</strong>
<x-base::out.inline-text :data="$data" name="person.first_name"/>
<br/>
<strong>Last Name</strong>
<x-base::out.inline-text :data="$data" name="person.last_name"/>
<br/>
<strong>Birthdate</strong>
<x-base::out.date :data="$data" locale="de" name="person.birthdate"/>
</div>
blade,
[
'data' => ['person' => [
'gender' => 'diverse',
'first_name' => 'Dana',
'last_name' => 'Debug',
'birthdate' => carbon('2001-02-03'),
]],
],
);
}
}
return (new Controller())->show(request());
Note that the data source must be passed to each element individually.
Output
The above PHP Source Code results in the following HTML-Output.
<div>
<a href="/edit">Edit</a>
<br/>
<strong>Gender</strong>
<span id="person.gender">diverse</span>
<br/>
<strong>First Name</strong>
<span id="person.first_name">Dana</span>
<br/>
<strong>Last Name</strong>
<span id="person.last_name">Debug</span>
<br/>
<strong>Birthdate</strong>
<time id="person.birthdate" datetime="2001-02-03">03.02.2001</time>
</div>
Rendering IoElements
The following section explains the architecture of the standard in- and output elements of the admin panel. As an example we would like to render tags consisting of a name and a color:
$myTag = [
'name' => "My first pill",
'color' => "#ff0000"
];
The standard in- and output elements of the admin frontend inherit Support\View\Components\Base\IoElement
which defines their basic properties and behaviour. Based on this the constructor of every IoElement
looks like this:
public function __construct(
array|null|object $data = null,
?iterable $dataset = null,
?string $name = null,
mixed $value = null,
)
At it’s core all in and -output elements have a value. The value represents the data which we would like to render. This can be a single tag (if our component renders a single tag) or an iterable of tags (if our component renders a list of tags).
Output Elements
Rendering an Output Element by setting a dedicated value
Suppose we would like to render a single tag which is displayed using a pill by setting a dedicated value. Then this can be done like this:
new Pill(null, null, null, [
'name' => 'My first pill',
'color' => '#ff0000'
]);
Rendering a list of tags would be done like this:
new PillList(null, null, null, $myTags);
Rendering an output element by setting data and name
Let’s take a look at another option. Suppose we have a person which has tags and we would like to render it’s tags. Then we could provide the person to the element using the $data
argument and provide the name of the persons attribute which contains the tags using the $name
argument:
new PillList($myPerson, null, 'tags', null);
The IoElement
now automatically sets the persons tags
-attribute as the elements value. When rendering an output component just setting the value is sufficient most of the times.
The dataset argument
The $dataset
argument can be used to provide a set of key/value-pairs which will be assigned as data-*
-attributes on the HTML-element. Suppose we would like to assign an additional, non-standard data-index-number
-attribute to our html element then this could be done like this:
new Pill(
null,
[
'index-number' => 2342321828
],
null,
[
'name' => 'My first pill',
'color' => '#ff0000'
]
);
The output would be like this:
<span style="background-color: #ff0000;" data-index-number="2342321828">My first pill</span>
Providing configuration options
Suppose we would like to render a list of pills but only show a maximum of 3 pills. In addition we would like to make
the maximum configurable. This can be done by just adding another argument $maxPills
to our constructor (see
Laravel documentation):
class PillList
public function __construct(
array|null|object $data = null,
?iterable $dataset = null,
?string $name = null,
mixed $value = null,
int $maxPills = null,
)
Input Elements
TODO