Is it possible to call a sidebar in node.html.twig in Drupal 8?
Something like that :
<article{{ attributes }}>
{{ title_prefix }}
{% if not page %}
<h2{{ title_attributes }}>
{{ label }}
</h2>
{% endif %}
{{ title_suffix }}
{% if display_submitted %}
<footer>
{{ author_picture }}
<div{{ author_attributes }}>
{% trans %}Submitted by {{ author_name }} on {{ date }}{% endtrans %}
{{ metadata }}
</div>
</footer>
{% endif %}
<div{{ content_attributes }}>
{{ content }}
</div>
<div{{ content_attributes }}>
{{ sidebar}}
</div>
I would like to display a different sidebar for each node.
You can try this solution found here http://atendesigngroup.com/blog/making-region-content-available-node-templates-drupal-8:
/**
* Implements hook_preprocess_node() for NODE document templates.
*/
function THEME_preprocess_node(&$variables) {
// Allowed view modes
$view_mode = $variables['view_mode']; // Retrieve view mode
$allowed_view_modes = ['full']; // Array of allowed view modes (for performance so as to not execute on unneeded nodes)
// If view mode is in allowed view modes list, pass to THEME_add_regions_to_node()
if(in_array($view_mode, $allowed_view_modes)) {
// Allowed regions (for performance so as to not execute for unneeded region)
$allowed_regions = ['primary_content'];
THEME_add_regions_to_node($allowed_regions, $variables);
}
}
/**
* THEME_add_regions_to_node
*/
function THEME_add_regions_to_node($allowed_regions, &$variables) {
// Retrieve active theme
$theme = \Drupal::theme()->getActiveTheme()->getName();
// Retrieve theme regions
$available_regions = system_region_list($theme, 'REGIONS_ALL');
// Validate allowed regions with available regions
$regions = array_intersect(array_keys($available_regions), $allowed_regions);
// For each region
foreach ($regions as $key => $region) {
// Load region blocks
$blocks = entity_load_multiple_by_properties('block', array('theme' => $theme, 'region' => $region));
// Sort ‘em
uasort($blocks, 'Drupal\block\Entity\Block::sort');
// Capture viewable blocks and their settings to $build
$build = array();
foreach ($blocks as $key => $block) {
if ($block->access('view')) {
$build[$key] = entity_view($block, 'block');
}
}
// Add build to region
$variables[$region] = $build;
}
}
I used this hook to override node. You can pass {{ primary_content }} in your node.html.twig
Related
I added an extra checkout step to the Sylius checkout procedure and I am trying to add 1 text field per instance of an ordered item. Thus having put 3x itemA and 1x itemB in cart should spawn 4 text fields.
I have this code so far in the form builder (based on code from here: https://sf.khepin.com/2011/08/basic-usage-of-the-symfony2-collectiontype-form-field/)
$builder->add('myCollection', CollectionType::class, $formOptions);
$totalCounter = 0;
/** #var OrderItem $item */
foreach($order->getItems() as $item) {
for($itemCounter = 0 ; $itemCounter < $item->getQuantity(); $itemCounter++ ) {
$totalCounter++;
$builder->get('myCollection')->add('item_' . $totalCounter, TextType::class, $formOptions);
}
}
First Question: Is this the right approach for the form builder for my particular scenario, and secondly: If yes, how do I read from this in the twig template?
form.children.myCollection does exist but does not seem to contain these children, so that I could use them with the form_row function. What I would have expected is something like this:
{% set totalCounter = 0 %}
{% for item in order.items %}
{% for itemCounter in 1..item.quantity %}
{% set totalCounter = totalCounter + 1 %}
{{ form_row(form.children.myCollection['item_' ~ totalCounter ]) }}
{% endfor %}
{% endfor %}
Any idea how to do this? Thanks for any hint in advance!
Just saw my exact case is described in the beginning of the documentation of CollectionType https://symfony.com/doc/current/reference/forms/types/collection.html
The form builder part..
$builder->add('emails', CollectionType::class, [
// each entry in the array will be an "email" field
'entry_type' => EmailType::class,
// these options are passed to each "email" type
'entry_options' => [
'attr' => ['class' => 'email-box'],
],
]);
..and the twig part:
{{ form_label(form.emails) }}
{{ form_errors(form.emails) }}
<ul>
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
I am getting Twig_Error_Runtime
I am trying to find the mistake I've made, but I can't see any... maybe I am looking at the wrong file, but I've tried looking everywhere I've done changes before this error appeared.
This is my twig code:
{% extends 'base.html.twig' %}
{% block body %}
<div class="container">
{% form_theme form 'bootstrap_4_layout.html.twig' %}
{{ form_start(form) }}
<br>
Name {{ form_widget(form.name) }}
Price {{ form_widget(form.price) }}
Available {{ form_widget(form.available) }}
Date {{ form_widget(form.date) }}
<div class="row js-ticket-time-wrapper"
data-prototype="{{ form_widget(form.times.vars.prototype)|e('html_attr') }}"
data-index="{{ form.times|length }}">
{% for time in form.times %}
<div class="col-xs-4 js-ticket-time-item">
<a href="#" class="js-remove-time pull-right">
<span class="fa fa-close"></span>
</a>
{{ form_row(time.name) }}
</div>
{% endfor %}
<a href="#" class="js-ticket-time-add">
<span class="fa fa-plus-circle"></span>
Add another Time
</a>
</div>
<button type="submit" class="btn btn-primary" formnovalidate>Save</button>
{{ form_end(form) }}
</div>
{% endblock %}
{% block js %}
{{ parent() }}
<script>
jQuery(document).ready(function() {
var $wrapper = $('.js-ticket-time-wrapper');
$wrapper.on('click', '.js-remove-time', function(e) {
e.preventDefault();
$(this).closest('.js-ticket-time-item')
.fadeOut()
.remove();
});
$wrapper.on('click', '.js-ticket-time-add', function(e) {
e.preventDefault();
// Get the data-prototype explained earlier
var prototype = $wrapper.data('prototype');
// get the new index
var index = $wrapper.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
$wrapper.data('index', index + 1);
// Display the form in the page before the "new" link
$(this).before(newForm);
});
});
</script>
{% endblock %}
I've also made changes in Entities Time & Ticket, but I don't think so this is connected somehow.
Here's my TicketType Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name',TextType::class)
->add('price',MoneyType::class)
->add('available',IntegerType::class)
->add('date',DateType::class)
->add('times', CollectionType::class, array(
'entry_type' => \App\Form\TimeType::class,
'allow_delete' => true,
'by_reference' => false,
'allow_add' => true,
));
}
And this is TimeType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class);
}
Because the classname of your formtype TimeType is the same as the Symfony TimeType (https://symfony.com/doc/current/reference/forms/types/time.html) the formtheme layout tries to render the symfony type instead of yours. You can see the symfony TimeType has an option called widget so the formtype is expecting this type.
So try to rename your TimeType to something else like TicketTimeType.
Or you can rename your block prefix like this in your TimeType:
public function getBlockPrefix()
{
return "ticket_time_type";
}
I am building a project in Symfony 2.3 using Twig. I want to add a class to the form row block. I am using a form theme file which contains:
{% block form_row %}
<div class="form-row">
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endblock %}
Now some of my form rows I want to add an extra class form-row-split. I can't figure out how to do this properly. The way I have it almost-working is:
{% block form_row %}
{% set attr = attr|merge({'class': 'form-row' ~ (attr.class is defined ? ' ' ~ attr.class : '') ~ (errors|length > 0 ? ' error' : '')} ) %}
<div {{ block('widget_container_attributes') }}>
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endblock %}
(Note, I've left the error class logic in there too as that needs to stay).
Then in the form builder:
$builder
->add('first_name', 'text', array(
'attr' => array(
'class' => 'form-row-split'
)
));
This almost works but it adds this class everywhere and also adds the widget id to the row!
<div id="myform_first_name" class="form-row form-row-split">
<label for="myform_first_name">First name</label>
<input id="myform_first_name" class="form-row-split" type="text" name="myform[first_name]">
</div>
I can think of a few potential solutions but none of them are pretty or straight forward. Surely there must be a simple way of doing this?
There is a fairly simple solution to this problem actually. I just needed a form type extension to extend the base form type to allow an extra available option: http://symfony.com/doc/2.3/cookbook/form/create_form_type_extension.html
Following through the example in the docs, I created a new form type extension:
// src/Acme/FrontendBundle/Form/Extension/FormTypeExtension.php
namespace Acme\FrontendBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Class FormTypeExtension
* #package Acme\FrontendBundle\Form\Extension
*/
class FormTypeExtension extends AbstractTypeExtension
{
/**
* Extends the form type which all other types extend
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return 'form';
}
/**
* Add the extra row_attr option
*
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'row_attr' => array()
));
}
/**
* Pass the set row_attr options to the view
*
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['row_attr'] = $options['row_attr'];
}
}
Then I registered the service in my bundle...
<!-- Form row attributes form extension -->
<service id="acme.form_type_extension" class="Acme\FrontendBundle\Form\Extension\FormTypeExtension">
<tag name="form.type_extension" alias="form" />
</service>
Since every widget extends the base form type this then allows me to pass this new row_attr option through on any field, eg:
$builder
->add('first_name', 'text', array(
'row_attr' => array(
'class' => 'form-row-split'
)
));
Then the twig overrides to make use of the new row_attr option:
{% block form_row %}
<div {{ block('form_row_attributes') }}>
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endblock form_row %}
{% block form_row_attributes %}
{% spaceless %}
{% for attrname, attrvalue in row_attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
{% endspaceless %}
{% endblock form_row_attributes %}
And it's done!
(For completeness, my full twig override still merges in the form-row and error classes in like so:
{% set row_attr = row_attr|merge({'class': 'form-row' ~ (row_attr.class is defined ? ' ' ~ row_attr.class : '') ~ (errors|length > 0 ? ' error' : '')} ) %}
.. but thats not really necessary for answering my own question :P )
Docs say: you always able to pass attr to rendered element:
{{ form_start(form, {'attr': {'class': 'your-class'}} ) }}
{{ form_label(form, {'attr': {'class': 'your-class'}}) }}
{{ form_widget(form, {'attr': {'class': 'your-class'}}) }}
{{ form_errors(form, {'attr': {'class': 'your-class'}}) }}
{{ form_end(form) }}
Below is a clone of answer by #lopsided but with changes reflecting latest Symfony structure changes (v. 2.7+):
There is a fairly simple solution to this problem actually. I just needed a form type extension to extend the base form type to allow an extra available option: http://symfony.com/doc/master/form/create_form_type_extension.html
Following through the example in the docs, I created a new form type extension:
// src/Acme/FrontendBundle/Form/Extension/FormTypeExtension.php
namespace Acme\FrontendBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class FormTypeExtension
* #package Acme\FrontendBundle\Form\Extension
*/
class FormTypeExtension extends AbstractTypeExtension
{
/**
* Extends the form type which all other types extend
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return FormType::class;
}
/**
* Add the extra row_attr option
*
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'row_attr' => []
));
}
/**
* Pass the set row_attr options to the view
*
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['row_attr'] = $options['row_attr'];
}
}
Then I registered the service in my bundle...
<!-- Form row attributes form extension -->
<service id="acme.form_type_extension" class="Acme\FrontendBundle\Form\Extension\FormTypeExtension">
<tag name="form.type_extension" alias="form" extended_type="Symfony\Component\Form\Extension\Core\Type\FormType" />
</service>
Since every widget extends the base form type this then allows me to pass this new row_attr option through on any field, eg:
$builder
->add('first_name', TextType:class, [
'row_attr' => [
'class' => 'form-row-split'
]
]);
Then the twig overrides to make use of the new row_attr option:
{% block form_row %}
<div {{ block('form_row_attributes') }}>
{{ form_label(form) }}
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% endblock form_row %}
{% block form_row_attributes %}
{% spaceless %}
{% for attrname, attrvalue in row_attr %}{{ attrname }}="{{ attrvalue }}" {% endfor %}
{% endspaceless %}
{% endblock form_row_attributes %}
And it's done!
(For completeness, my full twig override still merges in the form-row and error classes in like so:
{% set row_attr = row_attr|merge({'class': 'form-row' ~ (row_attr.class is defined ? ' ' ~ row_attr.class : '') ~ (errors|length > 0 ? ' error' : '')} ) %}
.. but thats not really necessary for answering my own question :P )
What I did was simpler (though maybe a bit less clean?).
Pass the class for the form row via a field's "data" attribute :
// template.html.twig
{{ form_start(form) }}
{{ form_row(form.field, {'attr': {'data-row-class': 'my-row-class'} }) }}
{{ form_end(form) }}
And then handle it in the form theme template this way :
// form-theme.html.twig
{% block form_row -%}
{% set row_class = attr['data-row-class'] | default('') %}
<div class="{{ row_class }}">
{{- form_label(form) -}}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
</div>
{%- endblock form_row %}
Which gives this :
<form name="formName" method="post">
<div class="my-row-class">
<label for="formName_field">Field label</label>
<input type="text" id="formName_field" name="formName[field]" data-row-class="my-row-class">
</div>
</form>
Lets say i have a one to many relation:
A company can own several pictures
I have company entity and image entity
Now, I want to display all pictures of a company within a template. And i also want to make theese pictures directly editable. I thought of adding a form to each Entity of the DoctrineArrayCollection and pass them to the template. In Template if somebody clicks on a picture the corresponding should be fade in, the should be able to edit the pictures description and pass it through ajax to a controller.
In my entity I added a field without annotations:
private $form;
public function setForm(MyPictureForm $form)
{
$this->form = $form;
}
public function getForm()
{
return $this->form;
}
Now in my controller I add a form instance to every picture of the company:
//office images with forms
$officeImages = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$image->setForm($form->createView());
array_push($officeImages, $image);
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages
));
}
And in my template i render it like this way:
{% for image in officeImages %}
<a href="#" title="{% if image.description %}{{ company.description }}{% else %}CLICK HERE FOR EDIT{% endif %}">
{% if image.image %}
<img src="{{ vich_uploader_asset(image, 'image') | imagine_filter('company_office_image_thumb') }}"
alt="{% if image.description %}{{ company.description }}{% endif %}"/>
{% else %}
{% image '#UserBundle/Resources/public/img/nopic_logo.jpg' output='/images/nopic_logo.jpg' %}
<img src="{{ asset_url }}" alt="Joblogo"/>
{% endimage %}
{% endif %}
</a>
{{ form(image.form) }}
{% else %}
<p>Es sind noch keine Images vorhanden</p>
{% endfor %}
At the end there is a lot if javascript stuff which handels the fade in / fade out of the form and their submitting.
Is this the correct way to handle my case? I think not cause passing a form for every picture seems like overhead?
The reason why I am working with forms instead of simply passing data out of a manual added input field is csrf protection and the smart usage of the form component.
As you said keeping form object in entity isn't a good idea. It should represents model data.
I've got two solutions for this:
I. Pass array of forms with image id as a key
$officeImages = array();
$imageForms = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$imageForms[$image->getId()] = $form->createView();
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages,
'imageForms' => imageForms
));
and in show.html.twig
{% for image in officeImages %}
{# your image display code #}
{{ form(imageForms[image.id]) }}
{% endfor %}
II. render partial for single image
in controller
public function showAndEditImageAction(Image $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
return $this->render(
'CompanyBundle:Company:Show/showAndEditImage.html.twig', array(
'image' => $image,
'imageForm' => $form->createView()
));
}
in twigs
{# CompanyBundle:Company:Show/showAndEditImage.html.twig #}
{# your image display code #}
{{ form(imageform) }}
{# CompanyBundle:Company:Show/show.html.twig #}
{% for image in officeImages %}
{{ render(controller('CompanyBundle:Company:showAndEditImage',
{ 'image': image })) }}
{% endfor %}
When I've created a form:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button')->getForm();
and render it:
<div><button type="button" id="form_add" name="form[add]">Add</button></div>
the attributes type, id and name are created.
I want to erase this attributes but I don't know how to do it. I've tried to do:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button', array( 'attr' => array() ) )->getForm();
without any success.
How could I do it?
Greetings and thanks
I messed around with this for a while and the closest I could get was to have them render with empty attributes. However, that's because the project I tested with is Symfony 2.0, and in that version it's impossible to completely remove the attributes since Symfony\Component\Form\FormView::$vars is private.
However, in Symfony 2.1 and later, that same property is public so you should be able to modify (or delete) the attributes/vars directly without being constrained by the FormView api.
First, create your own type to represent this "naked button"
src/Your/Bundle/Form/NakedButtonType.php
<?php
namespace Your\Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class NakedButtonType extends AbstractType
{
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.FormTypeInterface::getName()
*/
public function getName()
{
return "naked_button";
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::getParent()
*/
public function getParent(array $options)
{
return 'button';
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::buildViewBottomUp()
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
// Symfony 2.0
// This will still render the attributes, but they will have no value
$view->set('id', null);
$view->setAttribute('type', null);
// Symfomy >= 2.1
// This *should* remove them completely
unset( $view->vars['id'] );
unset( $view->vars['attr']['type'] );
}
}
Now, tell the service container how to build your type
app/config/config.yml
services:
form.type.naked_button:
class: Your\Bundle\Form\NakedButtonType
tags:
- {name: form.type, alias: naked_button}
Then update your parent form to use your new type instead of the ootb "button" type.
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'naked_button')->getForm();
All of that being said...
If you want these buttons w/o any attributes, why not just put them like that directly into your view?
<form>
{{ form_errors(form) }}
{{ form_rest(form) }}
<div>
<button>Add</button>
</div>
</form>
All of this custom type nonsense seems like alot of overhead to render something you clearly don't need Symfony to manage for you.
While not a good idea, I will help you load the gun and shoot yourself in the foot :-)
Create a new Resources/views/Form/fields.html.twig file, and put the following in it:
{% block widget_attributes %}
{% spaceless %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname, attrvalue in attr %}{% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}" {% else %}{{ attrname }}="{{ attrvalue }}" {% endif %}{% endfor %}
{% endspaceless %}
{% endblock widget_attributes %}
If you really, really want to remove all the attributes on there, you can remove the {% for attrname ... %} line. That will remove the attributes from all form fields. If you add some logic, you can have it apply to just specific fields.
Next step, you need to register your fields helpers. In your app/config/config.yml file, add the following line:
twig:
form:
resources:
- 'SomeBundle:Form:fields.html.twig'