Skip to content

Commit

Permalink
Merge pull request #227 from wrabit/support_colon_escaping
Browse files Browse the repository at this point in the history
Support colon escaping - alpinejs bind shortcut support
  • Loading branch information
wrabit authored Nov 29, 2024
2 parents 66d93d7 + 8d8c2c7 commit f41d300
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 11 deletions.
4 changes: 4 additions & 0 deletions django_cotton/templatetags/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def render(self, context):
value = self._strip_quotes_safely(value)
if value is True: # Boolean attribute
component_data["attrs"][key] = True
elif key.startswith("::"): # Escaping 1 colon e.g for shorthand alpine
key = key[1:]
# component_data["slots"][key] = value
component_data["attrs"][key] = value
elif key.startswith(":"):
key = key[1:]
try:
Expand Down
28 changes: 28 additions & 0 deletions django_cotton/tests/test_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,3 +483,31 @@ def test_attributes_remain_unordered(self):
self.assertTrue(
"in default slot: <strong {% if 1 == 2 %}hidden{% endif %}></strong>" in compiled
)

def test_colon_escaping(self):
self.create_template(
"cotton/colon_escaping.html",
"""
attrs: '{{ attrs }}'
""",
)

self.create_template(
"colon_escaping_view.html",
"""
<c-colon-escaping
::string="variable"
:dynamic="variable"
::complex-string="{'something': 1}"
:complex-dynamic="{'something': 1}"/>
""",
"view/",
context={"variable": "Ive been resolved!"},
)
with self.settings(ROOT_URLCONF=self.url_conf()):
response = self.client.get("/view/")

self.assertContains(
response,
"""attrs: ':string="variable" dynamic="Ive been resolved!" :complex-string="{'something': 1}" complex-dynamic="{'something': 1}"'""",
)
24 changes: 14 additions & 10 deletions docs/docs_project/docs_project/templates/alpine_js.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

<h1 id="tabs">Re-usable tabs with alpine.js</h1>

<c-note>
Be sure to checkout the notes on alpine.js support <a href="{% url 'components' %}#alpine-js-support">here</a>.
</c-note>

<p>Let's tackle together a common UI requirement - tabs.</p>

<h3 class="mt-0 pt-2 mb-5" id="goals">We'll start by defining some goals:</h3>
Expand Down Expand Up @@ -69,7 +73,7 @@ <h2 id="preparation">Preparing cotton components</h2>

<p>Now we have the design right, let's chop it up into components.</p>

<h4>Tabs component</h4>
<h3>{{ '<c-tabs />'|force_escape }} component</h3>

<c-snippet label="cotton/tabs.html">{% cotton_verbatim %}{% verbatim %}
<div class="bg-white rounded-lg overflow-hidden border shadow">
Expand All @@ -85,7 +89,7 @@ <h4>Tabs component</h4>
{% endcotton_verbatim %}{% endverbatim %}
</c-snippet>

<h4>Tab component</h4>
<h3>{{ '<c-tab />'|force_escape }} component</h3>

<c-snippet label="cotton/tab.html">{% cotton_verbatim %}{% verbatim %}
<div>
Expand Down Expand Up @@ -194,13 +198,13 @@ <h2 id="example">Usage and example</h2>

</c-snippet>

<c-navigation>
<c-slot name="prev">
<a href="{% url 'form-fields' %}">Form Inputs</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'layouts' %}">Layouts</a>
</c-slot>
</c-navigation>
<c-navigation>
<c-slot name="prev">
<a href="{% url 'form-fields' %}">Form Inputs</a>
</c-slot>
<c-slot name="next">
<a href="{% url 'layouts' %}">Layouts</a>
</c-slot>
</c-navigation>

</c-layouts.with-sidebar>
16 changes: 16 additions & 0 deletions docs/docs_project/docs_project/templates/components.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ <h1 id="components">Components</h1>
<c-index-sublink><a href="#boolean-attributes" class="no-underline">Boolean Attributes</a></c-index-sublink>
<c-index-sublink><a href="#dynamic-components" class="no-underline">Dynamic Components</a></c-index-sublink>
<c-index-sublink><a href="#context-isolation" class="no-underline">Context Isolation</a></c-index-sublink>
<c-index-sublink><a href="#alpine-js-support" class="no-underline">Alpine.js Support</a></c-index-sublink>
<c-index-sublink><a href="#summary" class="no-underline">Summary of concepts</a></c-index-sublink>
</c-slot>

Expand Down Expand Up @@ -330,6 +331,21 @@ <h2 id="context-isolation">9. Context Isolation with `only`</h2>

<c-hr />

<h2 id="alpine-js-support">10. Alpine.js support</h2>

<p>The following key features allow you to build re-usable components with alpine.js:</p>

<c-ul>
<li>
<code>x-data</code> is accessible as <code>{% verbatim %}{{ x_data }}{% endverbatim %}</code> inside the component as cotton makes available snake_case versions of all kebab-cased attributes. (If you use <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code> then the output will already be in the correct case).
</li>
<li><a href="https://alpinejs.dev/directives/bind" target="_blank">Shorthand x-bind</a> support (<code>:example</code>). Because single <code>:</code> attribute prefixing is reserved for cotton's <a href="{% url 'components' %}#dynamic-attributes">dynamic attributes</a>,
we can escape the first colon using <code>::</code>. This will ensure the attribute maintains a single <code>:</code> inside <code>{% verbatim %}{{ attrs }}{% endverbatim %}</code>
</li>
</c-ul>

<c-hr />

<h2 id="summary">Summary of Concepts</h2>
<ul>
<li><code>{% verbatim %}{{ slot }}{% endverbatim %}</code> - Default content injection.</li>
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "django-cotton"
version = "1.4.0"
version = "1.5.0"
description = "Bringing component based design to Django templates."
authors = [ "Will Abbott <willabb83@gmail.com>",]
license = "MIT"
Expand Down

0 comments on commit f41d300

Please sign in to comment.