twig-components
twig-components copied to clipboard
Adding a controller
I'd really like to place a controller between the component and the template, for example:
{# /components/templates/table.twig #}
{% spaceless %}
<table {{ attributes.merge({}) }}>
{% if caption is not empty %}
<caption {{ caption.attributes }}>
{{ caption }}
</caption>
{% endif %}
{% for row in rows %}
<tr {{ row.attributes.merge({}) }}>
{% for cell in column %}
<{{ cell.type }} {{ cell.attributes.merge({}) }}>{{ cell.label ?: ' ' }}</{{ cell.type }}>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endspaceless %}
<?php
/**
* /components/controllers/table.php
*/
class table
{
public function __construct($attributes)
{
if (!isset($attributes['caption']) {
throw new Error("You must define a caption to keep this accessible");
}
}
}
Is there any way to implement something like this with this library?
I use this library to achieve the prop types validation https://github.com/guym4c/twig-prop-types
@majermi4 I don't use that library, do we need to add some integration or it just works?
@broskees it could be possible to add this, I still didn't find an implementation that I'm ok with. I rerely need this feature so I didn't put anymore time into this, but I see that can be useful.
The basic idea in place is to configure a namespace where all components class are. Some methods are already in the code: https://github.com/giorgiopogliani/twig-components/blob/8f9c806ec8630c27863f663809bf61bfc11b282a/src/Configuration.php#L85-L101
The parser should try to find the component class and if no class is found try with a file only component. Also, we would will need a convention or some way to know the class from the name of the component.
An easy implementation could be to just pass a variable into the template but in this way I feel that the file and the class become two different things. Although, maybe with some magic it could be possible to make things look nice.
Another way I know, could be to wrap everything in a component class and render from there but I am not sure how to move things the way they are now, the component class kinda already exists as twig generate a template class from the component file. Laravel is doing something like this and for components without thier class is using some AnonymousComponent class.
@giorgiopogliani Thanks for getting back to me quickly. A few thoughts:
The parser should try to find the component class and if no class is found try with a file only component.
When you say the parser tries to find the component class... this means, I am assuming, that after you set the namespace, it will then look within that namespace for a matching component class?
So for instance this would be loaded from my above example?
<?php
/**
* /components/controllers/table.php
*/
namespace TwigComponents;
class table
{
public function __construct($attributes)
{
if (!isset($attributes['caption']) {
throw new Error("You must define a caption to keep this accessible");
}
}
}
<?php
/**
* twig initializing class
*/
// ...
Configuration::make($twig)
->setTemplatesPath('/relative/directory/to/components')
->setTemplatesExtension('twig')
->useCustomTags()
->setComponentsNamespace('TwigComponents')
->setup();
// ...
If this is the case, do we have a general structure for the class? If not, it leads me to my next comment.
Another way I know, could be to wrap everything in a component class and render from there but I am not sure how to move things the way they are now, the component class kinda already exists as twig generate a template class from the component file. Laravel is doing something like this and for components without thier class is using some AnonymousComponent class.
I mean we can play around with different abstract classes or interfaces as options for that class so it's more usable. This is Laravel's version of the Component base class.
Which would make a component controller itself look something like this in Laravel:
class Alert extends Component
{
/**
* The alert type.
*
* @var string
*/
public $type;
/**
* The alert message.
*
* @var string
*/
public $message;
/**
* The alert types.
*
* @var array
*/
public $types = [
'default' => 'text-indigo-50 bg-indigo-400',
'success' => 'text-green-50 bg-green-400',
'caution' => 'text-yellow-50 bg-yellow-400',
'warning' => 'text-red-50 bg-red-400',
];
/**
* Create the component instance.
*
* @param string $type
* @param string $message
* @return void
*/
public function __construct($type = 'default', $message = null)
{
$this->type = $this->types[$type] ?? $this->types['default'];
$this->message = $message;
}
/**
* Get the view / contents that represent the component.
*
* @return \Illuminate\View\View|string
*/
public function render()
{
return $this->view('components.alert');
}
}
Is there anything stopping us from doing something similar?
@broskees this could be a start, although there are some limitations at the moment.
@broskees pushed an update to the feature/class-components
branch. Here how it works:
- Set namespace for components in configuration
- Create a component class extending
Performing\TwigComponents\View\Component
- The abstract
template
method needs to be implemented returing the relative path of the component twig template (e.g'components/simple_alert.twig'
) - Attributes that match names in the constructor will be used to create the component instance
- All public properties are available, actually accessing the value on the component instance
- The variable
this
is available and it's the component instance, it is possible to call functions, etc...
Limitations:
- Components in folders in the components namespace will probably not work (not tested)
- Component constructor needs default values in order to be created internally without arguments
- Components from packages?
- Others?
- Props vs Attributes: the attributes variables contains all the passed props even though some are component props
Hope you can have a look and test this! it would be really cool to add this feature!