Wrap contextual html around a specific twig variable {{ product.name }} - twig

I want to automatically wrap some html, lets say <span data-id=".."> when I call {{ product.name }} in my twig template.
So when in a twig template, I use {{ product.name }}, I want the output to be: <span data-type="product" data-id="8" data-prop="name">My product name</span>. I cannot use twig filters or macros, since I really need the template syntax to be {{ product.name }}, so the end-user (template designer), does not have to care about it.
The reason I need this is because I am building an on-page editting tool for twig templates, so I need to know the contexts of those variables from within HTML.
I have tried to override the Compiler that the Twig_Environment uses, but I cannot seem to alter the output of the twig variable node.
How can I do this?
EDIT
I wanted to mention that I need this to use the {{ product.name }} syntax, since other designers will work with those templates outside of Symfony 2. I want to make almost all twig variables editable in the front-end, so a solution with filters or macros can indeed work, but it kills the usability and readability of the platform I am writing. There is no public API currently in twig that can achieve what I want, that is why I am fiddling with the twig compiler. I do not have the required knowledge of the Twig internals to achieve this. If someone could point me into a direction that would be great!
UPDATE 2
I have found a place where I can achieve what I want. Every GetAttr node is compiled to $this->getAttribute($someContext, "property"). So if I can change the subclass of compiled twig template, I can achieve what I want. By default all twig templates extend from Twig_Template. I want to extend this method.
How can I change the subclass of all compiled twig templates?
UPDATE 3
I've found a way to use my own base class for all compiled twig templates. I can simply set it as an option on the twig environment, see this link. I hope to get it working tomorrow and I will post a proper answer on how I solved it all together. Not sure how I will handle escaping, since that will happen after the $this->getAttribute() is called.

I think macros are the best candidates for those kind of wrappings.
For example:
main.twig
{% import "macros.twig" as macros %}
{{ macros.display_product(product) }}
macros.twig
{% macro display_product(product) %}
<span data-id="{{ product.id }}" data-prop="name">{{ product.name }}</span>
{% endmacro %}
Context
product:
id: 8
name: My Georgeous Product
Result
<span data-id="8" data-prop="name">My Georgeous Product</span>
See fiddle

I've solved it by wrapping the PrintNode that is created when parsing a VAR_START token (inside the twig parser) with my own EditablePrintNode. In that node I traverse the expression that is compiled by the print node and get the necessary property path and pass that as an argument to a wrapper function around the default twig escape function that is compiled by the PrintNode.

I suggest to write a custom filter. You can find the doc here about how to write and configure in your environment
// an anonymous function
$filter = new Twig_SimpleFilter('my_custom_product_filter', function ($product) {
return '<span data-id="'.$product->getId().'" data-prop="name">'.$product->getName().'</span>';
});
You need to register as described in the doc
then you can use as follow:
{{ myProduct|my_custom_product_filter}}
Hope this help

Related

Flask | JINJA 2: render_template_string() with macro imported in context

I am looking to make macros available inside of the articles for my flask blog. When the body of my blog changes I run the following code to render the body into HTML:
target.body_html = bleach.linkify(bleach.clean(render_template_string(value),tags=allowed_tags, attributes=allowed_attr, strip=True))
render_template_string(value) is the part I'm concerned about. To use a macro inside of the template string (value), every single string has to include the following in order for me to use the macros from this file in the article body:
{% import "includes/macros.html.j2" as macros %}
This would not be reasonable to ensure that writers have access to the macros that I write in all of their articles. Is there any way to pass that argument via the render_template_string() function so that it doesn't have to be defined in every string? Or to otherwise create a template that the string is rendered inside of? Something like the following:
render_template_string(string,macros=function_to_import_macros("includes/macros.html.j2"))
While there does not appear to be a solution to pass an entire macros template as context, get_template_attribute does appear to allow you to pass individual macros one at a time. Example:
includes/macros.html.j2:
{% macro hello() %}Hello!{% endmacro %}
{% macro hello_with_name(name) %}Hello, {{ name }}!{% endmacro %}
Template rendering code:
hello = get_template_attribute("includes/macros.html.j2","hello")
hello_with_name = get_template_attribute("includes/macros.html.j2","hello_with_name")
return render_template("index.html", hello=hello,hello_with_name=hello_with_name)
index.html:
{{ hello() }}
{{ hello_with_name("Bob") }}
This solution will allow for adding individual macros to the context while rendering a template or template_string and can even be added to the global context via this method. If anyone knows how to add an entire template full of macros, that'd rock, but this allows the basic needed functionality.

Drupal 8 Twig User ID?

I'm new to Drupal & Twig and all I need is in my custom theme a twig expression to output the current user's ID. I can't find anything in the template comments, only if a user is logged in true / false.
Is there a simple way to get the ID of the current user? I'm not sure how to implement custom methods in a theme.
thanks!
Hello bobomoreno,
I would suggest you use the module Bamboo Twig.
The Bamboo Twig module provides some Twig extensions with some useful functions and filters aimed to improve the development experience.
You could then enable the sub-module Bamboo Twig - Loaders:
drush en bamboo_twig_loader -y
Finally, you will be able to use the Twig function bamboo_load_currentuser:
<!-- Get Current User -->
{% set user = bamboo_load_currentuser() %}
<div>{{ user.name.value }}</div>
<div>{{ user.uid.value }}</div>
You can find the complete official documentation there.
In your theme find file yourthemename.theme and add following code:
function yourthemename_preprocess(&$vars, $hook)
{
$vars['uid'] = \Drupal::currentUser()->id();
}
now if you edit your twig template for html, page, region, block, field, form element... you can use 'uid' token in your twig. It works for all hooks
If you only need the ID in user.html.twig, it's {{ user.id }}
Here's how D8 now works, in two lines of executable code:
<?php
// This code returns the current user ID.
$account = \Drupal::currentUser();
return $account->id();
The display name is not a field you can configure in {{ content }}. You can get it directly from the user entity:
{{ user.displayname }}
Reference for the php method: AccountInterface::getDisplayName
The Twig Tweak module is very small and yet very powerful. You can get the current user id with drupal_token('current-user:uid') I am using it to pass the current user id to a view like this:
{{ drupal_view('view_name', 'embed_1', drupal_token('current-user:uid')) }}

Automatically declare variables used in Twig template

I am refactoring some old twig templates, where many variables are passed to the templates but not declared in doc comments (which also work in PhpStorm for type hinting), like this:
{# #var user AppBundle\Entity\User #}
{# #var message string #}
<p>Hello {{ user.fullName }}!</p>
<p>{{ message }}</p>
Is there a tool that can pre-generate these doc comments from variables used in a template, ideally usable as PhpStorm plugin?
It will be good enough if it would extract just variable names without types (which are hard to guess from Twig syntax), just for me to make sure that I have not overlooked some variables.
Also, it will be great if it would not declare variables created in the template, like this:
{# #var users AppBundle\Entity\User[] #}
{# "user" variable is not documented as it is not passed into the template from outside #}
{% for user in users %}
<p>Hello {{ user.fullName }}!</p>
{% endfor %}
Such comments are helpful not only for type hinting, but also for the developer to see what should be passed into the template when refactoring/reusing the template.
Thanks in advance.
As far as I can see there's unfortunately nothing that IDE or available plugins could suggest for your use case.

what exactly is haswidgets and widgets in bolt (or twig rather )?

I was just going through the Default theme of bolt CMS, and i came actoss the following lines of code:
{% if haswidgets('aside_top') %}
{{ widgets('aside_top') }}
{% else %}
I googled twig haswidgets and also twig widgets , but i could't find anything.
can somebody explain what these two methods are ? and what exactly do they do ?
They are a feature of Bolt. Extensions can push content to specific places in the backend and also to some places in the frontend, as long as the theme supports that. They are called widgets. the haswidgets() and widgets() twig functions are for checking and displaying them.
You can find more info here https://docs.bolt.cm/3.1/templating/widgets and here https://docs.bolt.cm/3.1/extensions/intermediate/widgets

Yii 2 registerJs in twig templates

In the Yii 2.0 guide it says you can register Javascript code in a twig template like this:
{registerJs key='show' position='POS_LOAD'}
$("span.show").replaceWith('<div class="show">');
{/registerJs}
Tried this but it will simply output the whole snippet {registerJs ... as text on the page without adding it to the page's Javascript Code.
Similar commands like registerJsFile or registerCss aren't working either.
Am I missing something? Thanks!
EDIT:
As Mihai P. noted below, {registerJs} is syntax for Smarty templates. So, the question is: is there a similar way to register inline JS in Twig templates? The documentation only mentions registering assets.
You can use this in your Twig template, which is the current view, to call registerJs.
{% set script %}
var app = new Vue({
el: '#vue-widget',
data: {
message: 'Hello Vue!'
}
})
{% endset %}
{{ this.registerJs(script,4,'vue-widget') }}
The question is 3 years old, I put it here for someone runs into a similar problem.
You can use this in the Twig template with View constant like below:
{{ this.registerJs('$("span.show").replaceWith(\'<div class="show">\');', constant('\\yii\\web\\View::POS_HEAD')) }}
And, you can also call registerJs inside the Yii2 controller, which I think it makes the code cleaner
$this->getView()->registerJs('$("span.show").replaceWith(\'<div class="show">\');', \yii\web\View::POS_HEAD);
Your example is copied from the smarty section not the twig. There are some examples in the twig section that you might want to try.

Resources