Sometimes we need to use specific HTML form in multiple parts of single page. For example, the search form might be placed in the desktop menu, while on mobile it would appear only in the popup. Or we might have carousel with for
loop listing through matrix field containing company employees, where each person has his/her own contact form.
Following DRY principle, we should create only a single form Twig component and include it in multiple places. This can, unfortunately, lead to problems, such as:
- Multiple occurrences of inputs with same
id
attribute and labels with samefor
attribute might cause conflicts. When the user clicks on the label, the browser focuses cursor on input withid
matching labelsfor
. If there are multiple inputs with the sameid
, first occurring input will get focused. Which might not be input from our form. Not to mention that elements with duplicateid
will cause w3c validation errors. - Sometimes we might want to fill the form with data it sent upon submitting. This is commonly used in search forms, where we want to have the ability to modify search text after submitting the form. If we, however, have multiple forms using the same component, all forms would be filled with submitted data, which might not be the desired result.
The solution to these problems is to namespace the form. By doing that, we ensure that each instance of the form component has unique values of potentially problematic attributes.
Manually namespacing form #
First, let's do this the hard way. Here's a very simple form. It has getQueryParam method inside input value
attribute. getQueryParam
returns query string with a specific key. name
attribute content is same as the parameter of getQueryParam
- this means that when the form is submitted with specific text, it will retain this text after page reloads.
<form action="">
<label for="someId">Our label</label>
<input type="text" id="someId" name="someName" value="{{craft.app.request.getQueryParam('someName')}}">
</form>
Now, lets append {{namespace}}
variable to for
, id
and name
attributes, and also to getQueryParam
parameter.
<form action="">
<label for="{{namespace ?? null}}someId"></label>
<input type="text" id="{{namespace ?? null}}someId" name="{{namespace ?? null}}someName" value="{{craft.app.request.getQueryParam((namespace ?? null)~'someName')}}">
</form>
Each time we include this form component, we can provide it with a unique namespace
variable. Thanks to using the null coalescing operator (??
) which will replace variable with null
if it is missing, we might also use it without namespace variable.
{# first form #}
{% include 'our_form' with {namespace: 'first-form'} only %}
{# second form #}
{% include 'our_form' with {namespace: 'second-form'} only %}
{# not namespaced #}
{% include 'our_form' %}
This method works, but appending variables everywhere makes form more complicated and its code harder to read. Fortunately, Craft allows us to namespace forms more efficiently.
Namespace Twig tag #
Enter the namespace Twig tag. It will automatically append namespace to id
, for
and name
HTML attributes.
{% namespace 'something' %}
<form action="">
<label for="someId"></label>
<input type="text" id="someId" name="someName" value="{{craft.app.request.getQueryParam('someName')}}">
</form>
{% endnamespace %}
This will result with form rendered like this:
<form action="">
<label for="something-someId"></label>
<input type="text" id="something-someId" name="something[someName]" value="">
</form>
As you can see, id
and for
values changed into something-someId
. name
content changed into something[somename]
, which means that query string sent by this form will be nested.
But what about getQueryParam
function? Its argument needs to be namespaced too. We could potentially use namespaceInputName filter for its argument - like this:
{{craft.app.request.getQueryParam('someName'|namespaceInputName)}}
This would change someName
into something[someName]
. Note that we don't need to provide a parameter to namespaceInputName
filter. If we use it within namespace
tag, it will use namespace provided to this tag by default.
There is hovewer one problem - for nested query strings, getQueryParam
accepts syntax like something.someName
instead of something[someName]
. We can use simple macro to walk around that problem (thanks to Brandon Kelly for pointing this out on github):
{% macro namespaceParam(param) %}
{{- param|namespaceInputName|replace('/[\\[\\]]+/', '.')|trim('.') -}}
{% endmacro %}
{{ craft.app.request.getQueryParam(_self.namespaceParam('someName')) }}
This macro will replace [
and ]
characters with .
and strip last .
from end of string. So, something[someName]
will change into something.someName
, which can be used in getQueryParam()
method.
Our final, properly namespaced form might look like that:
{% macro namespaceParam(param) %}
{{- param|namespaceInputName|replace('/[\\[\\]]+/', '.')|trim('.') -}}
{% endmacro %}
<form action="">
<label for="someId"></label>
<input type="text" id="someId" name="someName" value="{{ craft.app.request.getQueryParam(_self.namespaceParam('someName')) }}">
</form>
Well, except a bit of tinkering with craft.app.request.getQueryParam
, it looks just like regular form. That's because we don't actually need to use namespace
tag inside form component - we can use it when we include it using {% include %}
.
{# first form #}
{% namespace 'first-form' %}
{% include 'our_form'%}
{% endnamespace %}
{# second form #}
{% namespace 'second-form' %}
{% include 'our_form'%}
{% endnamespace %}
Namespecing CSS styles #
In Craft 3.5 (which is currently yet to be released), namespace
tag will gain additional functionality. It will be able to namespace CSS styles.
Let's say that HTML component that you want to include in your page has its own <style>
tag. Normally, such styles would pose a danger of messing with other styles of our site, especially if they use some generic class names or even style HTML tag directly.
namespace
tag will allow us to prevent such problems. If used with keyword withClasses
, it will be able to namespace class
and id
attributes of HTML elements, as well as style declarations. Thanks to that, these styles will be contained to the specific component.
So, such code:
{% namespace 'foo' withClasses %}
<style>
.title { font-weight: bold; }
</style>
<div class="title"></div>
{% endnamespace %}
Will be rendered as:
<style>
.foo-title { font-weight: bold; }
</style>
<div class="foo-title"></div>