Problem
I'm using yii2 with the twig templating engine. It seems to be working however I can't seem to use any of the methods of the Html helpers (yii/helpers/Html).
My view is extending a base layout using twig extends. {% extends "#layouts/_base.twig" %}
I am including 'yii/helpers/Html' in the _base.twig file. {{ use('yii/helpers/Html') }}
I'm using {{ html.encode(this.title) }} to render the page title in the header and
{{ html.submitButton('Send Message', {
'class': 'button button--cta button--expand',
}) | raw }}
to try and render a button in my view but neither seems to work, I don't get any errors, just nothing rendering.
Question
Have I set this up correctly? What should I have to do to render a button in yii2 twig? Very new to using twig.
Code
index.twig
{% extends "#layouts/_base.twig" %}
{% block layout %}
...
{% set form = active_form_begin({
'id' : 'contact-us-form'
}) %}
...
<div class="row">
<div class="medium-8 medium-offset-2 columns">
{{ form.field(contact_us, 'full_name').textArea([{
'rows' : 6,
'placeholder' : 'Let us know if you have any questions...'
}]).label('Message', {
'class' : 'label--inline'
}) | raw }}
</div>
</div>
<div class="row">
<div class="medium-3 medium-offset-2 columns">
{{ html.submitButton('Send Message', {
'class': 'button button--cta button--expand',
}) | raw }}
</div>
</div>
{{ active_form_end() }}
</section>
{% endblock %}
_base.twig
{{ use('yii/helpers/Html') }}
{{ use('yii/widgets/ActiveForm') }}
{{ use('yii/web/JqueryAsset') }}
{{ this.beginPage() }}
<!DOCTYPE html>
<html lang="{{app.language}}">
<head>
{{ this.head() }}
<meta charset="{{app.charset}}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link href='https://fonts.googleapis.com/css?family=Lato:300,400,700,900' rel='stylesheet' type='text/css'>
{{ html.csrfMetaTags() | raw }}
{{ register_asset_bundle('www/assets/AppAsset') }}
<title>{{ html.encode(this.title) }}</title>
</head>
<body>
{{ this.beginBody() }}
{% block header %}
{% include "#layouts/components/header.twig" %}
{% endblock %}
{% block layout %}
{% endblock %}
{% block footer %}
{% include "#layouts/components/footer.twig" %}
{% endblock %}
{{ this.endBody() }}
</body>
</html>
{{ this.endPage() }}
To use \yii/helpers\Html helper in twig templates you need to configure twig extension accordingly. This is covered in documentation, Additional Configuration page.
You need to add 'globals' option inside 'twig' section of configuration (see 'THIS LINE' mark):
For yiisoft/yii2-twig version 2.1.0:
[
'components' => [
'view' => [
'class' => 'yii\web\View',
'renderers' => [
'twig' => [
'class' => 'yii\twig\ViewRenderer',
'cachePath' => '#runtime/Twig/cache',
// Array of twig options:
'options' => [
'auto_reload' => true,
],
/* *THIS LINE* */ 'globals' => ['html' => ['class'=>'\yii\helpers\Html']],
'uses' => ['yii\bootstrap'],
],
// ...
],
],
],
]
Then just use it in template as 'html' variable, example:
{{ html.csrfMetaTags() | raw }}
For yiisoft/yii2-twig version 2.0.6 and earlier there was old syntax:
'globals' => ['html' => '\yii\helpers\Html'], // *THIS LINE*
According actual documentation, the correct form must be:
[
'components' => [
'view' => [
'class' => 'yii\web\View',
'renderers' => [
'twig' => [
'class' => 'yii\twig\ViewRenderer',
'cachePath' => '#runtime/Twig/cache',
// Array of twig options:
'options' => [
'auto_reload' => true,
],
'globals' => ['html' => ['class'=>'\yii\helpers\Html']], // *THIS LINE*
'uses' => ['yii\bootstrap'],
],
// ...
],
],
],
]
I don't know why, the former "html"=>'\yii\helpers\html' not works in one of my recents projects.
Related
Is it possible to configure injected files places in public/index.html?
I want to generate a django template instead of simple html file provided, it could look like this:
{% extends 'base.html' %}
{% block header_extra %}
<!-- app.css -->
{% endblock %}
{% block js_extra %}
<!-- app.js, chunk-vendors.js, etc -->
{% endblock %}
{% block main_content %}
<div id="app"></div>
{% endblock %}
I've found how to do it, first it's required to disable files injecting in vue.config.js:
configureWebpack: config => {
config.plugins.forEach((plugin) => {
if(plugin.constructor.name === 'HtmlWebpackPlugin') {
plugin.options.inject = false;
plugin.options.minify.collapseWhitespace = false;
}
});
}
For the next it's needed to insert js / css files using underscore templating.
Here I'm prepending vue before file names, cause I collect static with django.
{% extends 'base.html' %}
{% load static %}
{% block header_extra %}
<% htmlWebpackPlugin.files.css.forEach((fileName) => { %>
<link rel="stylesheet" href="{% static 'vue<%= fileName %>' %}">
<% }) %>
{% endblock %}
{% block js_extra %}
<% htmlWebpackPlugin.files.js.forEach((fileName) => { %>
<script src="{% static 'vue<%= fileName %>' %}"></script>
<% }) %>
{% endblock %}
{% block main_content %}
<div id="app"></div>
<!--
<pre id="config">
<%= JSON.stringify(htmlWebpackPlugin, null, 2) %>
</pre>
-->
{% endblock %}
Then, cause I have to collect static to static/vue directory, in settings.py I added a new dir:
STATICFILES_DIRS = [
# vue is an additional subdirectory for vue static
('vue', os.path.join(BASE_DIR, '../frontend/dist')),
]
Also it's needed to have an access to the vue generated template from django, so in settings.py:
TEMPLATES = [
# ...
# 'frontend' is the vue project directory name,
# so you should be able to specify template like this:
# template_name='dist/index.html'
{'DIRS': ['frontend']}
# ...
]
Thanks
If someone prefers the webpack chain way:
chainWebpack: (config) => {
config.plugins.values().forEach( p => {
if (p.get('plugin').name === 'HtmlWebpackPlugin') {
// console.log(p.get('args'))
p.tap(args => [ merge(args[0], {
inject: false,
})]);
// console.log(p.get('args'))
}
})
}
I've got this homepage and when I execute my template, the JSON-LD script is duplicated inside my page (1 in the HEAD and 1 in the middle of the page).
I just don't understand why this script is running another time in the middle of the page...
So here is my code: The header, where I introduced the JSON-LD script and the the bodyContent block where you can find the page content.
{% block header %}
{% block head_javascript %}
<script type="application/ld+json">
{
"#context": "http://schema.org",
"#type": "WebSite",
"url": "",
"description": "",
"name": ""
}
</script>
{% endblock%}
{% endblock %}
{% block bodyContent %}
{# TAG | SELECTION #}
<div>
<h2> {{ 'title' }} </h2>
<p> {{ 'desc' }} </p>
</div>
<div>
<div>
<h1>{{ 'create.title' }}</h1>
<p>
{{ 'create.desc' }}
</p>
<div>
<div>
{% set texte %}{{ 'create.step.one' }}{% endset %}
{% set svg %}svg:cursor-mkp.svg.twig{% endset %}
{% set number %}1{% endset %}
{{ include(':step.html.twig') }}
</div>
<div>
{% set texte %}{{ 'create.step.two' }}{% endset %}
{% set svg %}svg:mkp.svg.twig{% endset %}
{% set number %}2{% endset %}
{{ include('idea-step.html.twig') }}
</div>
</div>
<a href="{{path('path.mkp')}}">
<button>{{ 'create.button' }}</button>
</a>
</div>
</div>
{% endblock %}
I've found this error with the Google Structured Data Testing Tool. Indeed, I've seen that there was the same structured data twice.
May this help someone :
From my controller, my header block is generating more code than I coulded see and I was not able to check this before I just move my jsonld script out of the header block.
Now it's working well and I do not have 2 times the same snippet.
{% block header %}
{% endblock %}
{% block head_javascript %}
<script type="application/ld+json">
{
"#context": "http://schema.org",
"#type": "WebSite",
"url": "",
"description": "",
"name": ""
}
</script>
{% endblock%}
To generate a simple menu, I can do:
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
and then:
{% embed '...' with { items: ['Home', 'Articles'] %}
But how do I have to write the TWIG code, if I want to create endless deep menus like:
<ul>
<li>Alpha</li>
<li>Bravo</li>
<ul>
<li>Charlie</li>
<li>Delta</li>
<li>Echo</li>
<ul>
<li>Foxtrott</li>
</ul>
<li>Golf</ul>
</ul>
<li>Hotel</li>
<li>India</li>
</ul>
Thx for help!
To perform recursion in twig you can make use of macro's
{% import _self as macro %}
{{ macro.multilevel(foo) }}
{% macro multilevel(array) %}
{% import _self as macro %}
<ul>
{% for item in array %}
<li>
{% if item is iterable %}
{{ macro.multilevel(item) }}
{% else %}
{{ item }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
twigfiddle
EDIT With a simple array it's not possible to nest children in the same <li> as the parent. Herefor you would need to change your array arround,
Reformed array
$data = [
'links' => [
[
'title' => 'alpha',
'href' => 'http://www.alpha.com',
'children' => [],
],
[
'title' => 'beta',
'href' => 'http://www.beta.com',
'children' => [
[
'title' => 'charlie',
'href' => 'http://www.charlie.com',
'children' => [],
],
[
'title' => 'delta',
'href' => 'http://www.delta.com',
'children' => [],
],
[
'title' => 'echo',
'href' => 'http://www.echo.com',
'children' => [
[
'title' => 'foxtrot',
'href' => 'http://www.foxtrot.com',
'children' => [],
],
],
],
],
],
]
];
tiwg
{% macro menu(links) %}
{% import _self as macro %}
<ul>
{% for link in links %}
<li>
{{ link['title'] }}
{% if not link['children']|default([]) is empty %}
{{ macro.menu(link['children']) }}
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
twigfiddle
I have a symfony3/twig skeleton template
page1/skeleton.twig
{# set default values #}
{% block content %}
{% set test = {
sec1: {
title: "null",
content: 'null'
},
}
%}
{% endblock %}
<ul>
19 {% for sec in test[0:] %}
<li>
<p>{{ sec.title }}</p>
<div>
<p>{{ sec.content }}</p>
</div>
</li>
{% endfor %}
</ul>
I then create a layout template that extends the skeleton with 'real' data
page1/layout.html.twig
{% extends 'page1/skeleton.html.twig' %}
{% block content %}
{% set test = {
sec1: {
title: "title1",
content: 'content2'
},
sec2: {
title: "title2",
content: 'content2'
}
%}
{% endblock %}
But when I generate/publish the page, Symfony fires an error
Variable "test" does not exist in :page1:skeleton.html.twig at line 19
500 Internal Server Error - Twig_Error_Runtime
complaining about the skeleton itself.
That 'test' array is defined in the skeleton. Afaict from reading the docs on 'block', 'extends' & 'set', and can't figure out what exactly the problem is.
What do I need to change to eliminate this error?
blocks in twig have their own variable scope.Variables created inside a block can't be accessed outside of it.
Imo you should only test if the variable exist and otherwise create the default value :
skeleton.twig
{% if not test is defined %}
{%
set test = {
sec1: {
title: "null",
content: 'null'
},
}
%}
{% endif %}
<ul>
{% for sec in test[0:] %}
<li>
<p{{ sec.title }}</p>
<div>
<p>{{ sec.content }}</p>
</div>
</li>
{% endfor %}
</ul>
controller.php
<?php
echo $twig->render('page/page.twig', array(
'foo' => [
'title' => 'title1',
'content' => content1',
],
);
Change this in the file page1/skeleton.twig:
{% for sec in test %}
Then it will work.
I tried it. Make sure you understand why!
Edit: see the solution I've found at the end of my question ;-)
I'm looking since hours for a solution but I do not really find what I'like. I'm sure it is easy to do.
I'm building a form with Symfony 3.1. My form does not send anything in a database. Everything seems okay. But when rendering my twig template all my checkbox are surrounded by a <div> html tag.
I just would like twig renders me a form without that <div> tag.
edit: this is what Twig render me: this is ok for me but I would like to remove div tags
<form name="form" method="post">
<div id="form"><div><label for="form_1">1</label><input type="checkbox" id="form_1" name="form[1]" class="ballsCheckBox" value="1" /></div>
</form>
Here is my twig template:
{% extends "::base.html.twig" %}
{% block title %}SimulotoBundle:Lotto:lotto{% endblock %}
{% block body %}
<h1>Welcome to the Lotto:result page</h1>
{{form(form)}}
{% endblock %}
I build the form directly in a controller. See it:
public function LottoSimulationFormAction()
{
$lt = new LottoSimulation();
$data = [];
$formBuilder = $this->createFormBuilder($data);
/** building Lotto grid with checkboxes * */
for ($i = 1; $i <= $lt->getMaxNb(); $i++)
{
$formBuilder->add($i, CheckboxType::class, [
'label' => $i,
'required' => false,
'attr' => [
'class' => 'ballsCheckBox'
]
]);
}
/** adding submit button **/
$formBuilder->add('Envoyer', SubmitType::class, [
'attr' => [
'class' => 'save'
]
]);
$form = $formBuilder->getForm();
return $this->render("SimulotoBundle:Lotto:lotto.html.twig", [
"form" => $form->createview()
]);
}
}
Here is the soluton to solve this problem.
I need to Customize the form rendering as it is explained in the Synfony cookBook on this page http://symfony.com/doc/current/form/form_customization.html
Go to vendor\Symfony\Bridge\Twig\Ressources\views\Form\form_div_layout.html and modify the block:
{%- block form_row -%}
<div>
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
</div>
{%- endblock form_row -%}
You can remove it but a better way is to copy/past that block to your teplate and overide it. Like this:
{% extends "::base.html.twig" %}
{% form_theme form _self %} //don't forget this line of code
{%- block form_row -%}
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
{%- endblock form_row -%}