Take the following example structure:
{% set paths = {
...
'js': {
...
'jquery': {
'version': '1.7.2',
'cdn': 'https://ajax.googleapis.com/ajax/libs/jquery/{{paths.js.jquery.version}}/jquery.min.js',
'fallback': ....
}
}
}
%}
To access, I would normally use something like:
<script src="{{paths.js.jquery.cdn}}"></script>
The problem:
The interpolated variable isn't recognized, you will get something like ...libs/jquery/%7B%7B%paths.js/jquery.version7D%7D/jquery.min.js....
I've tried:
'a': 'text{{b}}text',
'a': {{ ('text' ~ b ~ 'text') }},
'a': "{{ ('text' ~ b ~ 'text') }}",
'a': "{{ (['text', b, 'text'] | join }}",
'a': "{{ (['text', {{b}}, 'text'] | join }}"
And more I've forgotten
I'm aware of attribute()
There's not a lot of documentation on it, but from what I see, it would have to be something like this:
attribute(paths, attribute(js, attribute(jquery, cdn)))
It would be fine for one level, but not an arbitrary level of depth. Please correct me if I'm misunderstanding attribute().
'a': ('text' ~ b ~ 'text') is actually correct, but there cannot be curly braces around the expression (because we're already inside an expression, a variable definition).
The correct way:
{% set paths = {
...
'js': {
...
'jquery': {
'version': '1.7.2',
'cdn': ('https://ajax.googleapis.com/ajax/libs/jquery/' ~ paths.js.jquery.version ~ '/jquery.min.js'),
'fallback': ....
}
}
}
%}
But there is simple design mistake here; when the renderer/parser comes to the cdn attribute definition, paths isn't set yet. One possible way to get around this is to declare another variable first;
{% set params= {
'jqueryversion': '1.7.2'
}
%}
{% set paths = {
...
'js': {
...
'jquery': {
'cdn': ('https://ajax.googleapis.com/ajax/libs/jquery/' ~ params.jqueryversion ~ '/jquery.min.js'),
'fallback': ....
}
}
}
%}
Obviously if paths.js.jquery.cdn is only going to be used once then just hardcode the parent variable's value where you need it and interpolate the child variable:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/{{params.jqueryversion}}/jquery.min.js"></script>
Related
In Liquid, one can remove a character X from a liquid string like so,
{{ 'random_string' | remove:'X' }}
E.g. the { character can be removed in this way.
However, removing the } character this way gives the following error,
Liquid syntax error (line 15): Variable '{{ 'random_string' | remove:"}' was not properly terminated with regexp: /\}\}/
Which probably has to do with the usage of the } character in the Liquid language.
Question
How do I remove the } character from the string?
One solution would be using the url_encode filter, then removing the encoded string:
{% capture my_variable %}hello}}worl}d{% endcapture %}
{{ my_variable | url_encode | remove: "%7D"}}
First, create your own tag.
<project>/<app>/templatetags/clean_string.py
Insert this code into it.
import re
from django import template
register = template.Library()
#register.filter
def clean_string(value):
regex = '[^-_.,!?#%\w\d]+'
return re.sub(regex, '', value)
After that, register it in the settings...
<project>/<projectName>/settings.py
'OPTIONS': {
'context_processors': [
...
],
'libraries': {
'clean_string': '<project>.templatetags.clean_string'
}
},
At the beginning of your .html template Add tag loading
{% load clean_string %}
This filter can be applied to any string.
For example:
{{ '[tes{t}!'|clean_string }}
The filter can be adjusted in a variable ...
regex = '[^-_.,!?#%\w\d]+'
Output:
test!
In twig template I have an object with multiple levels
I need to add a new object into the multiple object as the sub object
{%
set data = {
'first': 'First',
'data': {
'val_1': 'val_1'
}
}
%}
this should be added to data val_2: val_2
expected result:
{%
set data = {
'first': 'First',
'data': {
'val_1': 'val_1',
'val_2': 'val_2'
}
}
%}
First, in Twig terminology, that's not an object or a key-value array but a hash (see Twig's documentation of literals).
You can't add an item to a hash e.g. by doing {% set data.second = 'Second' %}. Instead you need to use the merge filter:
{%
set data = data|merge({
second: 'Second',
})
%}
{{ dump(data) }}
{# Prints this:
array(3) {
["first"]=>
string(5) "First"
["data"]=>
array(1) {
["val_1"]=>
string(5) "val_1"
}
["second"]=>
string(6) "Second"
}
#}
So, to add an item to a hash inside a hash, you need to use the merge filter twice:
{%
set data = data|merge({
data: data.data|merge({
val_2: 'val_2',
}),
})
%}
{# Prints this:
array(3) {
["first"]=>
string(5) "First"
["data"]=>
array(1) {
["val_1"]=>
string(5) "val_1"
["val_2"]=>
string(5) "val_2"
}
["second"]=>
string(6) "Second"
}
#}
If you do lots of this kind of variable manipulation in Twig, it might be a sign that some of that code might be better put in a controller or model or whatever.
Lets say I have an associative array like so:
{% set settings = { 'foo':'bar', 'cat':'mouse', 'apple':'banana' } %}
To use this data I would do the following:
{{ settings.foo }}
{{ settings.cat }}
{{ settings.apple }}
However, I wondered if there is a way to extract the keys to variables, and the values to values? Essentially the same as the PHP Extract function. So I can just do this instead:
{{ foo }}
{{ cat }}
{{ apple }}
My very amateurish attempt to do this started out like this:
{% for key,val in settings %}
{% set key = val %}
{% endfor %}
But obviously that doesn't work (or I wouldn't be here). Is there another approach I could take?
Thanks,
Mark
As most things in Twig this can be done by extending Twig
ProjectTwigExtension.php
class ProjectTwigExtension extends Twig_Extension {
public function getFunctions() {
return array(
new Twig_SimpleFunction('extract', array($this, 'extract'), ['needs_context' => true, ]),
);
}
public function extract(&$context, $value) {
foreach($value as $k => $v) $context[$k] = $v;
}
public function getName() {
return 'ProjectTwigExtension';
}
}
Register class in Twig
$twig = new Twig_Environment($loader);
$twig->addExtension(new ProjectTwigExtension());
template.twig
{{ extract({'foo': 'bar', }) }}
{{ foo }} {# output : bar #}
(sidenote) Seems you can't do this by using a closure (see example below) because the compiler of Twig passes the variables in an array, thus creating a copy
With closure
$twig->addFunction(new Twig_SimpleFunction('extract', function (&$context, $value) {
foreach($value as $k => $v) $context[$k] = $v;
}, ['needs_context' => true, ]));
Compiled result
echo twig_escape_filter($this->env, call_user_func_array($this->env->getFunction('extract')->getCallable(), array($context, array("foo" => "bar", "foobar" => "foo"))), "html", null, true);
I've encountered this problem. I wanted to merge these two hashes:
{% set additional_context = {
attributes:{
'class': 'post-link',
'data-confirm-text': params.confirm
}
} %}
{% set ajax_context = {
attributes: {
'class': '',
'data-href': target,
}
} %}
Doing this:
{% additional_context = additional_context|merge(ajax_context) %}
would override additiona_context.attributes with ajax_context.attributes.
I'd like to have attributes hashes merged as well, not overriden. I cannot change the name of the sub-hash - it has to be attributes.
I didn't find a neat way to do it. Ideas much appreciated. Thanks
I've already a solution, but just for JavaScript. Unfortunately while-loops do not exist in Twig.
My Twig-target in JavaScript:
var x = 10; // this is an unknown number
var result = x;
while (100 % result !== 0) {
result++;
}
console.log(result);
Any ideas how I do this in Twig?
What's my target: (not important if you already understood)
I want to get the first number after my unknown number, that satisfy the following condition:
100 divided by (the first number) equals a whole number as result.
EDIT: I have no access to PHP nor Twig-core.
You can make a Twig extension like:
namespace Acme\DemoBundle\Twig\Extension;
class NumberExtension extends \Twig_Extension
{
public function nextNumber($x)
{
$result = $x;
while (100 % $result !== 0) {
$result++;
}
return $result;
}
public function getFunctions()
{
return array(
'nextNumber' => new \Twig_Function_Method($this, 'nextNumber'),
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'demo_number';
}
}
And define it in the service.xml of the bundle:
<service id="twig.extension.acme.demo" class="Acme\DemoBundle\Twig\Extension\NumberExtension" >
<tag name="twig.extension" />
</service>
Then use it in the template:
{{ nextNumber(10) }}
UPDATE
A (not so great) approach but that possibly satisfy your needed is to do something like this:
{% set number = 10 %}
{% set max = number+10000 %} {# if you can define a limit #}
{% set result = -1 %}
{% for i in number..max %}
{% if 100 % i == 0 and result < 0 %} {# the exit condition #}
{% set result = i %}
{% endif %}
{% endfor %}
<h1>{{ result }}</h1>
Hope this help
In my case - I had to output an object with similar subobjects - including a templete with predefined values and setting up a normal if-condition worked.
See http://twig.sensiolabs.org/doc/tags/include.html for more infomration.