wrabit / django-cotton

Bringing component based design to Django templates
https://django-cotton.com
MIT License
382 stars 12 forks source link

Enhance documentation about attrs priority and key access #98

Closed tomblancdev closed 2 weeks ago

tomblancdev commented 2 weeks ago

I was testing today how attrs works into components.

I didn't find anything in the documentation so I apologize if it already exists.

My test case :

I need my component able to add some class to my html component without overriding the component base classes.

So my first try was to add {{ attrs.class }} into the class value of my component :

Component :

<{{ as }} 
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 {{ attrs.class }}"
>
    {{ slot }}
</{{ as }}>

Usage:

<c-components.test as="a" href="/" class="hover:bg-red-500">Home</c-components.test>

Result :

<a class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 ">
    Home
</a>

Nothing happened so I tested many combinations.

My discoveries

{{ attrs.key }} does not work, and this is logic

It seems that using a dot to access the value of a specific key of the attribute that we want to use does not works like into a simple disctionary. Too much logic when you think about it : we can't make the difference between two same html keys πŸ˜….

Accessing {{ attrs }} specific value has two ways of working

So, to access a specific key of the passed attribute you may simply use {{ key }}. Yes it works as documented πŸ˜….

It seems that they are two cases depending on the placement of the {{ attrs }} into the html component.

1. If you place {{ attrs }} before any attributes it override it :

Component :

<{{ as }} 
{{ attrs }} 
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 {{ class }}"
>
    {{ slot }}
</{{ as }}>

Usage :

<c-components.test as="a" href="/" class="hover:bg-red-500">Home</c-components.test>

Result :

<a as="a" class="hover:bg-red-500" href="/">
    Home
</a>

2. If you place it after the html tag it doesn't override it and so you can use it as you want.

Component :

<{{ as }} 
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 {{ class }}"
{{ attrs }} 
>
    {{ slot }}
</{{ as }}>

Usage :

<c-components.test as="a" href="/" class="hover:bg-red-500">Home</c-components.test>

Result :

<a class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 hover:bg-red-500" as="a" href="/">
    Home
</a>

You can notice that the hover:bg-red-500 was added at the end of the class attribut of our component.


Conclusion :

wrabit commented 2 weeks ago

I was curious and had to test a couple of things, here's my response and a couple of things I found:

So my first try was to add {{ attrs.class }} into the class value of my component :

Yeah so I have considered to add this behaviour but I couldn't find a valid reason for it, as in why would we need {{ attrs.key }} when we can just do {{ key }}.

{{ attrs.key }} does not work, and this is logic, we can't make the difference between two same html keys πŸ˜….

I don't think this a reason why it won't be a good idea, because attributes are not designed to be used more than once anyway, and I can't imagine any cases this would be tried.

  1. If you place it after the html tag it doesn't override it and so you can use it as you want.

I think some wires might be getting crossed here. If you're inspecting the source code by a browser console, you are seeing the html how the browser has parsed it, not how the source code is. For example, in chrome, using dev tools Elements tab (inspect) I'm seeing:

<a as="a" href="/" class="hover:bg-red-500"></a>

But inspecting the Source code then you see that it's actually:

<a as="a" href="/" class="hover:bg-red-500" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-all ease-in-out duration-500 hover:bg-red-500"></a>

This is probably because the browser attempts to fix broken html somewhat to ensure optimal functionality.

So there is no specific attribute merging. Interestingly enough I do have an undocumented property attrs_dict and filter merge. It allows you to merge classes from parent with classes in the component like:

<!-- component: link.html -->
<a href="#" {{ attrs_dict|merge:"class:text-red-500" }}>
<!-- view -->
<c-link class="font-bold">...</c-link>
<!-- output -->
<a href="#" class="text-red-500 font-bold">...</a>

It's undocumented because I don't think it's not yet fully thought out and tested. But now you mention {{ attrs.key }} effectively turning {{ attrs }} into a dict, this will effectively replace {{ attrs_dict }} which will cleanup the api slightly.

tomblancdev commented 2 weeks ago

This is probably because the browser attempts to fix broken html somewhat to ensure optimal functionality.

Oh yess, I didn't thought about it. I will think about it next time. Thanks πŸ‘.

So there is no specific attribute merging. Interestingly enough I do have an undocumented property attrs_dict and filter merge. It allows you to merge classes from parent with classes in the component.

I'm gonna implement it today to stay compliant with current way of working.

It's undocumented because I don't think it's not yet fully thought out and tested. But now you mention {{ attrs.key }} effectively turning {{ attrs }} into a dict, this will effectively replace {{ attrs_dict }} which will cleanup the api slightly.

OK, I'ill follow it.

I don't really have time to learn how django-cotton works for now, but when I learn it, I will be able to helps you on upgrades and features if you want.