Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a controller #19

Open
broskees opened this issue Dec 11, 2022 · 5 comments
Open

Adding a controller #19

broskees opened this issue Dec 11, 2022 · 5 comments

Comments

@broskees
Copy link

broskees commented Dec 11, 2022

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 ?: '&nbsp;' }}</{{ 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?

@majermi4
Copy link

I use this library to achieve the prop types validation https://github.com/guym4c/twig-prop-types

@giorgiopogliani
Copy link
Owner

@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:

/**
* Set namespace to autoload class components.
*
* @param string $namespace
* @return Configuration
*/
public function setComponentsNamespace(string $namespace): self
{
$this->namespace = $namespace;
return $this;
}
public function getComponentsNamespace(): ?string
{
return $this->componentsNamespace;
}

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.

@broskees
Copy link
Author

broskees commented Dec 12, 2022

@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?

@giorgiopogliani
Copy link
Owner

@broskees this could be a start, although there are some limitations at the moment.

@giorgiopogliani
Copy link
Owner

giorgiopogliani commented Dec 20, 2022

@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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants