Twig: How to get the first character in a string - symfony

I am implementing an alphabetical search.
We display a table of Names. I want to highlight only those alphabets, which have names that begin with the corresponding alphabet.
I am stumped with a simple problem.
How to read the first character in the string user.name within twig.
I have tried several strategies, including the [0] operation but it throws an exception.
Here is the code
{% for i in ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0-9'] %}
{% set has_user_starting_with_alphabet = false %}
{% for user in pagination %}
{% if user.name[0]|lower == i %}
{% set has_user_starting_with_alphabet = true %}
{% endif %}
{% endfor %}
{% if has_user_starting_with_alphabet %}
<li><span>{{ i }}</span></li>
{% endif %}
{% endfor %}
Is there some function like "starts_with" in twig?

Since twig 1.12.2 you can use first:
{% if user.name|first|lower == i %}
For older version you can use slice:
{% if user.name|slice(0, 1)|lower == i %}

Note: You may also use this notation:
{% if user.name[:1]|lower == i %}

Related

Twig - Get corresponding alphabet letter in loop

I want to use the loop.index variable in twig to get the corresponding alphabet letter (1 = A, 2 = B, etc).
{% for item in form.items %}
{% set nom_item = 'Item'~loop.index %}
{% endfor %}
How could I do to get alphabet letter in loop ? I can't find a twig function for that.
Simplest solution
{{ range('A','Z')[loop.index0] }}
try with this!
{% set foo = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] %}
{% for index,item in form.items %}
{% set nom_item = 'Item'~foo[index] %}
{% endfor %}

Looping through values in twig and replacing empty values

The title is a bit ambiguous I know, but let me explain what I'm trying to achieve.
I am attempting to generate a CSV based on data pulled from a doctrine query in my Symfony2 CRM. The data retrieved is based on OpenCart Product and Attribute data, as well as some bespoke information which is irrelevant for this issue.
Each product can have up to 5 different attribute values, named A, B, D, L1 and L2. However, some products do not have all of them, only A, B and L1. The CSV requires each attribute value to be in a separate cell - so the headers are as follows:
ATTRIBUTE: A | ATTRIBUTE: B | ATTRIBUTE: D | ATTRIBUTE: L1 |
ATTRIBUTE: L2
And then I loop through in my Twig file as follows:
{% for attribute in row.product.attributes %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endfor %}
If the product has all 5 attributes, the structure of the CSV is fine. However, if the product only has 3 attributes, it means that all of the subsequent values are pulled back a cell, meaning that the other data is under the wrong headings. I tried checking for values first:
{% for attribute in row.product.attributes %}
{% if attribute.attributeName.name == "A" %}
{% if attribute.text is not null %}
{{ attribute.text }},
{% else %}na,{% endif %}
{% endif %}
{% endfor %}
And I did this for each possible attribute name, but unfortuantely this does not work since if the name does not exist, it just skips it anyway. I'm having trouble trying to think of a way to loop through these attributes and entering a n/a if it's non existent - I'm sure there is a way but I don't know what it is.
For reference, here is the controller code that's generating the data for the CSV:
public function adminCsvAction($filter) {
$repository = $this->getDoctrine()->getRepository('AppBundle:Project');
$stages_repository = $this->getDoctrine()->getRepository('AppBundle:Stage');
$users_repository = $this->getDoctrine()->getRepository('AppBundle:User');
$results = $repository->getSearchResults($filter);
$users = $users_repository->findAll();
$stages = $stages_repository->findBy(array('deleted' => 0), array('sortOrder' => 'ASC'));
$filename = "export_".date("Y_m_d_His").".csv";
$response = $this->render('AppBundle:pages:csv.html.twig', array('data' => $results,'users' => $users, 'stages' => $stages));
$response->headers->set('Content-Type', 'text/csv');
$response->headers->set('Content-Disposition', 'attachment; filename='.$filename);
return $response;
}
The Project Entity has various mappings, one of which links to the Product table in OpenCart which means all attributes and linked values are accessible via this.
Any help in this is much appreciated.
I also agree with Cerad from comment section - that is not the job for Twig. In case your really need to do it, I would try roughly something like this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% for attribute in allAttr %}
{% if row.product.attributes[attribute] is defined %}
{{ row.product.attributes[attribute].text }}
{% endif %}
{% if not loop.last %},{% endif %}
{% endfor %}
I guess the is defined is critical here...
OK I figured it out. Using what Jovan Perovic suggested, I came up with this:
{% set allAttr = ["A","B","D","L1","L2"] %}
{% set prodAtts = [] %}
{% for row in data %}
{% set existingAtts = [] %}
{% for att in allAttr %}
{% if att not in prodAtts %}
{% set prodAtts = prodAtts|merge([att]) %}
{% endif %}
{% endfor %}
{% for rowAtt in row.product.attributes %}
{% set existingAtts = existingAtts|merge({(rowAtt.attributeName.name|trim):(rowAtt.attributeName.name|trim~'_'~rowAtt.text|trim)}) %}
{% endfor %}
{% for prodAtt in prodAtts %}
{% if prodAtt not in existingAtts|keys %}
{% set existingAtts = existingAtts|merge({(prodAtt):(prodAtt~'_na')}) %}
{% endif %}
{% endfor %}
{% set orderedAtts = existingAtts|sort %}
....
Then the loop for each row thereafter. I used the attribute name with an underscore in order to be able to sort it correct (as it only sorts by value not key) then used preg_replace to remove it along with any instance of the name so I just ended up with the value.
Bit of a lengthy - and probably over thought - solution but it does work!

Twig variable from one loop in another doesn't work

I have two loops in Twig. One is to check the stock level of a product, the other is to display some product options. I'm trying to create a variable from one loop which I can use inside the other to add a class name to a list(s).
I can't get that to work. Any help is more then welcome...
What I have is this:
{% set stockLevel = '' %}
{% for variant in product.variants %}
{% set stockLevel = variant.stock.level %}
{{ stockLevel }} // gives 1 0 1 1 (So all sizes except the second one are available)
{% endfor %}
{% for option in product.options %}
<ul class="optionslist">
{% if option.values %}
{% for value in option.values %}
<li class=" {% if stockLevel == 0 %}not_on_stock {% endif %}" >
<a class="item">etc...</a>
</li>
{% endfor %}
{% endif %}
</ul>
{% endfor %}
I believe there is something wrong the way you are handling your data. Right now, event if your variable was accessible in your second for loop, the value of it would be the one set lastly and your script would've failed anyway.
I can suggest a bit of a hacky way, feel free to improvise or use the idea behind it.
Say, we assign an empty array:
{% set variantsArray = [] %}
Then, we will use the filter merge to fill it with some dummy data(that's where your first loop comes into play)
{% for number in 1..5 %}
{% set variantsArray = variantsArray | merge([number]) %}
{% endfor %}
Then to be sure, we can access all the values there, we will just dump it out(that's where your second loop comes)
{% for index in 1..3 %}
{{ dump(variantsArray) }}
{% endfor %}
The following dump generated:
array:5 [▼
0 => 1
1 => 2
2 => 3
3 => 4
4 => 5
]
Hope you got the idea. Any suggestions are kindly welcome.
I am not sure what you are trying to do, how do you get your stock level from the variants? i think it is better to have a service function which help you get your stock level by product, the code would be cleaner :
controller action
$stock_helper = $this->container->get('stockhelper_service');
$stocklevel = $stock_helper->getStockLevelByProduct($product); //getstocklevelbyproduct handle your logic and return the stock level integer.
pass this variable to the twig.
One variable in your twig, no loops there, clean view, understandable code in your view and action.
Ok, I found the solution. The trick is to "connect" both values. Like so:
{% for option in product.options %}
{% if option.values %}
<ul id="product_configure_option2_{{ option.id }}" data-id="{{ option.id }}">
{% for value in option.values %}
{% set stock = null %}
{% for variant in product.variants **if variant.title == (option.title ~ ': ' ~ value.title)** %}
{% set stock = variant.stock %}
{% endfor %}
<li value="{{ value.id }}"{% if not stock.available %} class="not_on_stock"{% endif %}>{{ value.title }}</li>
{% endfor %}
</ul>
{% endif %}
{% endfor %}

Twig compare two values in different arrays

First of all I'm learning Twig.
I was wondering if it is possible wit Twig to compare two different values from different arrays/lists?!
I have two list of items I call them like so:
{% if page.cart %}
{% for product in page.cart.products %}
{{ product.id }}
{% endfor %}
{% endif %}
And:
{% if products %}
{% for product in products %}
{{ product.id }}
{% endfor %}
{% endif %}
I want to compare both product.id's so I can create a new statement. Is there any way to compare both values? The idea is to check if an id is present in page.cart.products and if so then do something.
I want to create a new statement to display some info. Something like so:
{% if page.cart %}
{% for product in page.cart.products %}
{% set cartId %}{{ product.id }}{% endset %}
{% endfor %}
{% endif %}
{% if products %}
{% for product in products %}
{% set listId %}{{ product.id }}{% endset %}
{% endfor %}
{% endif %}
{% if cartId == listId %}
.... do this ....
{% endif %}
Any help greatly appreciated!
You can loop over one array and check if the id is present in the second one. If it's there, you can do something.
{# In case you want to store them, you can do so in an array #}
{% set repeatedIds = [] %}
{% for productCart in page.cart.products if page.cart %}
{% for product in products if products %}
{% if productCart.id == product.id %}
<p>This id -> {{ product.id }} is already in page.cart.products</p>
{% set repeatedIds = repeatedIds|merge([product.id]) %}
{% endif %}
{% endfor %}
{% endfor %}
{{ dump(repeatedIds) }}
It's a very basic search algorithm and the cost is quadratic. Obviously, there are more efficient ways to look for an element in an array (though more complicated to implement).
If the amount of products you have to deal with is not very big, you could use this solution. However, if you have, let's say, more than one hundred products in each array (or you feel that the algorithm is slowing down your loading time), you could do this process in the controller using more sophisticated methods and PHP and just pass the result to the template.
Hope it helps.

How to pass a variable to form_theme?

I want to theme my form so that the field's label show the current locale, something like
Name (en) :
So I would like to rewrite block generic_label like that :
{# form_theme.html.twig #}
{% block generic_label %}
{% spaceless %}
{% if required %}
{% set attr = attr|merge({'class': attr.class|default('') ~ ' required'}) %}
{% endif %}
<label{% for attrname,attrvalue in attr %} {{attrname}}="{{attrvalue}}"{% endfor %}>{{ label|trans }} (app.session.locale)</label>
{% endspaceless %}
{% endblock %}
and import it in my template :
{% form_theme options 'myBundle:Object:form_theme.html.twig' %}
but the app variable is not accessible in the form template.
How can I pass a variable to a form theme ?
In current version of twig (as for 2016) it is possible.
In your template use the following like this:
{{ form_row(form.content, {'testvar' : 'this is test variable'}) }}
Then, in your theme file, just use:
{{testvar}}
of course instead of form.content you will use the field name you need.
Cheers,
Chris
You need to create a form extension in order to get it done. Take a look at
http://toni.uebernickel.info/2011/11/25/how-to-extend-form-fields-in-symfony2.html
to learn how to create the extension.
To have access to session locale, make sure to inject the container. After that you'll be able to get any var value you want.
If the app variable is not available in the form theme it may be a bug. I suggest you create a ticket.
In the meantime you can use the current template as a theme. Something like...
{% form_theme form _self %}
{% block field_label %}
{% set attr = attr|merge({ 'for': id }) %}
{% if required %}
{% set attr = attr|merge({ 'class': attr.class|default('') ~ ' required' }) %}
{% endif %}
<label{% for attrname, attrvalue in attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans }} ({{ app.session.locale }})</label>
{% endblock %}
If you are using Symfony master (2.1) replace app.session.locale with app.request.locale.

Resources