Render a choice field without form - symfony

I often need to render very simple imputs in some of my templates.
I'd like to take advantage of the twig macros & form blocks to render certain HTML inputs without involving the whole Symfony forms machinery.
For example from the controller:
$templateContext = array(
'my_input' = new FormField('my_input', 'choice', array(
'choices' => array('value1', 'value2', 'my_value'),
'value' => 'my_value',
));
);
In the template:
<div>{{ form_widget(my_input) }}</div>
would render:
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
Is there a simple way to do that ?
Eventually I would also like to be able to reuse these fields elsewhere (like we can reuse form types)

There are many ways to go about this. The easiest would be to write the plain HTML into your twig template.
<form method="post">
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
</form>
And then in your controller read the values back.
$request = $this->getRequest();
if($request->getMethod() == 'POST'){
$data = $request->get('my_input')
//Do whatever you want with $data
}
If you want you re-use the html, you can build it somewhere in your PHP and pass it into Twig whenever you need it; or you can place it in a separate twig template and read that with the {include ''} command in twig.

Here is what I finally ended up with:
Class MyInput {
public static values = array(
'value1',
'value2',
'my_value',
);
}
class MyInputType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => MyInput::$value,
));
}
public function getParent() { return 'choice'; }
public function getName() { return 'my_input_type'; }
}
Field type used from the controller (after having registered it as a service)
public function MyAction(Request $request)
{
$form = $this->createForm('my_input_type');
$form->handleRequest($request);
$templateContext['myForm'] = $form->createView();
// ...
}
Input rendered into the template
<div>{{ form(myForm) }}</div>
To conclude: I've not been able to render an input without the form machinery, but the actual form remains rather simple.

I found my own solution for it since i need to create subforms from existing forms.
create totally empty twig template,
add in it only {{form(form)}}
render this template render()->getContent()
do a preg replace on the result (i do it in js) formView = formView.replace(/<(.*?)form(.*?)>/, '');
Yes yes - i know that regex is not perfect so before someone says it - i say it on my own, change it for youself as it catches "form-group" classes etc as well.
This is just a showcase of my solution

Related

Symfony render controller

want to display a form in a modal in the header. In order to make the form work I call the controller Homecontroller.
I called the controller with render controller in the branch but I got a blank page.
Thanks for your help.
header.html.Twig
<h1 class="fw-bold"></h1>
<p class="lead fw-bold"></p>
{{include ('fragments/modal_form.html.twig') }}
</main>
</div>
</div>
modal_form.html.twig
{{ render(controller(
'App\\Controller\\HomeController::index',{'form' : form.createForm()} )) }}
</div>
Controller :
* #Route("/", name="home")
*/
public function index(PostsRepository $postsRepository,TagRepository $tagRepository, Request $request ):Response
{
$listTag = $tagRepository->findAll();
$listPost = $postsRepository->findByPostPHp('php');
$posts = $postsRepository->findByExampleField($value = 6);
$partage = New Posts();
$form = $this->createForm(PartagePostType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$partage = $form->getData();
$this->entityManager->persist($partage);
$this->entityManager->flush();
$this->addFlash('success', 'Votre post a bien été partagé');
}
return $this->render('home/index.html.twig', [
'posts' => $posts,
'tag' => $listTag,
'listPost' => $listPost,
'form' => $form->createView(),
]);
}
I dont' really get how you are trying to render you form but it doesn't work that way, in your modal_form.html.twig you should use the {{ form_start() }} and {{ form_end() }} twig helpers. They take in parameters the created view of the form, i.e, the variable "form" in your case (created in your render with the createView() method).
It should look like that:
{{ form_start(form}}
{{ form_row(form.field) }}
<input type="submit" value="submit">
{{ form_end(form) }}
"field" is whatever name you defined in your FormType. Notice how I added raw HTML for the submit button, it is suggested by Symfony you add the send button that way, even though you can add it in your FormType.
You can learn more about form rendering here : How to Customize Form Rendering
And forms in general there : Forms
Last thing, if you want to use multiple forms with this modal, don't forget to change the name of the variable (also don't forget to add this variable in your controller when you render a template with a form in it, obviously)

Add to Symfony form action

I've created a page with tabs (using bootstrap), and all tabs have their own form. And they all work well. My problem is when I get a form error, I want to open the tab that the error is on.
My current controller code:
public function add(Request $request, EntityManagerInterface $em): Response {
$this->denyAccessUnlessGranted('ROLE_USER');
$entityAdd = new \App\Entity\add();
$form = $this->createForm(\App\Form\Add::class, $entityAdd);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
Function in AddFormType.php
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add(...)
->add(...)
->add(...)
->getForm()
;
}
I'm not creating the form myself, I'm using symfony form components but I'm not sure how to modify the form method to add the form hash (eg. /add#list-tab1) to the form method.
{{ form_start(AddForm) }}
{{ form_row(AddForm.name1) }}
{{ form_row(AddForm.name2) }}
{{ form_row(AddForm.save) }}
{{ form_end(AddForm) }}
You can pass the hash with the third parameter of createForm method :
$form = $this->createForm(
\App\Form\Add::class,
$entityAdd,
['action' => '#list-tab1']
);
Then, you can use the setAction() method and retrieve the option in the buildForm() method in your AddFormType.php file:
$builder
->setAction($options['action'])
->add(...)
Note: Using only the hash in the setAction() method (and not the full route), is only working if the form is submitted to the current route.
You'll have to replace #list-tab1 with the real tab id.
You can pass the action directly in the parameters of form_start(), in the Twig template:
{{ form_start(form, { action: '/add#list-tab1' }) }}
See the Symfony doc about this field option.

How to render specific form elements Drupal 8

I am using Drupal 8 and would like to customize how form elements are being displayed. Specifically, I don't like how uneditable, populated textfields are displayed as plain text. I would have it being displayed as an editable textfield (or have the text look like it is in an uneditable textfield). I have looked at various hook functions to try and achieve this but nothing seems to work.
I figure the best way to go about this is if I can render the form fields individually myself and then create a twig file that displays the individual fields as I would like them to be displayed. Here is what I would like the twig field to look like:
<div class="from">
{{ form.mail }}
</div>
<div class="message">
{{ form.message }}
</div>
<div class="actions">
{{ form.actions }}
</div>
In your .module file
function your_module_theme($existing, $type, $theme, $path) {
return [
'custom_theme' => [
'variables' => [
'form' => NULL
],
'render element' => 'form',
]
];
}
In your CustomForm.php file
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
[...your fields ...]
return $form
}
In your custom module templates directory
custom-theme.html.twig
{{ form.your_field }}

Twig: Render view with different parameters based on one Controller action

this might be a stupid question but maybe you can still help me out here.
What I want: I have a Twig template, that has tabs in it (using Bootstrap tab panels) and now I want to render the tab contents using one Action from one Controller but with different parameters.
So basically, this controller action should take some param "type" and then return different results based on this type. The goal is to not have 2 Controllers to render the twig view that do almost the same except extracting different information based on the param "type.
However, I cannot get it to work. It's either that both tabs render the same results since apparently the view is only rendered once OR that I get exceptions in the twig template at time of rendering.
What would be the right way to approach this problem?
Many thanks.
There are multiple ways to achieve this. You can use if/else statements in your Twig template, but you can also set the template in you controller. It's up to you what suits best.
Method 1: custom template
By default, Symfony uses 'foobar.html.twig' on your foobarAction() method, but you can override this in your method:
public function recentArticlesAction($max = 3)
{
// make a database call or other logic
// to get the "$max" most recent articles
$articles = ...;
return $this->render(
'article/recent_list.html.twig',
array('articles' => $articles)
);
}
(warning: example from How to Embed Controllers in a Template, but the article itself has nothing to do with your question)
You can set a variable (for example $templateName) and change it:
$templateName = 'recent_list.html.twig';
if ($admin) {
$templateName = 'another_template.html.twig';
}
//or using parameters from you Request
if ($type = $request->request->get('type')) {
$templateName = $type . '.html.twig';
}
return $this->render(
$templateName,
array('articles' => $articles)
);
Method 2: using Twig
Controller:
public function foobarAction(Request $request)
{
return [
'type' => $request->request->get('type');
];
}
You Twig template:
{% if type == 'foo' %}
<h1>Foo!</h1>
<p>Hi, welcome you the foo page!</p>
{% elseif type == 'bar' %}
<h1>Bar!</h1>
<p>Hi, you've reached the Bar page.</p>
{% else %}
<h1>Error!</h1>
<p>Type not found.</p>
{% endif %}
Try render manually each view:
if ($type) {
return $this->render('home/template.html.twig', array('data' => $data));
}
return $this->render('home/another.html.twig', array('another_data' => $another_data));
Many thanks for the answers here, but they did not really helped me. Next time I try to add more code examples ;)
My goal was to render from 1 action into 1 template but with different parameters and render those different contents as partials in the parent view.
Example: Given a template called "view.html.twig" that includes 2 times the template "myPartial.html.twig", but first time with param and second time without param. Based on this param different contents should be returned.
My question was now, why apparently only the first action is rendered in Symfony as both my partial views had the same content.
So this is what I did now:
1 Controller, 2 Actions, both call a 3rd action to fetch the data and return the values to the calling action. Then both actions call the render method with the values rendered from the 3rd action.
This is what it looks now:
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list1') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list2', {'type' : 1}) %}
</div>
But what I wanted to achieve was to do something like this (which did not work because then both tabs would show the same content):
<div class="tab-content clearfix">
<div class="tab-pane" id="1b">
{% render controller('MyBundle:MyController:list') %}
</div>
<div class="tab-pane" id="2b">
{% render controller('MyBundle:MyController:list', {'type' : 1}) %}
</div>
Which I find confusing since in both times "render" is called, so I would expect that the Controller is called in both times so that also the partial view in the controller is rendered both times. But apparently this was not the case :( The Controller itself looks something like this:
public function list1Action()
{
$twigVars = //do something to build the twig vars without param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
public function list2Action($param)
{
$twigVars = //do something to build the twig vars with param;
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}
While what I wanted was something like this:
public function listAction($param = '')
{
if ($param) {
$twigVars = //do something to return the twig vars with param;
} else {
$twigVars = //do something else to return twig vars without param;
}
return $this->render('#MyBundle/Test/partial_list.html.twig', array(
'vars' => $twigVars,
));
}

Is it good practice to put Edge Side Includes into my templates?

In our Symfony2 Application we render reusable blocks with render_esi. We have this kind of templates:
{% for products as product %}
<div class="product">
<h4>{{ product.name }}</h4>
<div class="ratings">
{{ render_esi(controller('AcmeDemoCommunityBundle:Rating:ratingStars', {
objectType: 'product',
objectId: product.id,
readOnly: true
})) }}
</div>
</div>
{% endfor %}
And of cause we use the render_esi also in the detail page of the product.
I would like to differentiate different types of blocks:
Blocks that renders other actions of the same controller.
Blocks that could be used across other parts of the application.
What is the difference?
Blocks that only renders other actions of the same controller as the parent template are most of the times there to modularize one page and make parts cacheable. This blocks are only used one times in the whole application.
Blocks that renders parts like rating stars or comments are kind of independent widgets that provide an specific functionality. The current controller dose not know anything about this widgets. This kind of blocks are mostly used multiple times in an application.
What does that mean for the software design?
It means that we may want to change the way comments and ratings work in the future. May the not get rendered by an ESI anymore in the future because we have outsourced the functionality to an third-party service and only need to include some kind of JavaScript in this place? Or we render them directly?
This is something that has to be decided by the widget and not by the part that include the widget.
So what could I do to improve my design?
You could keep using ESI (because it makes sense for your usecase), but you should change the way of how the modules are included in the Twig files. You should move the logic for this out of the template into an separate Twig Extension in the AcmeDemoCommunityBundle.
namespace Acme\DemoCommunityBundle\Twig;
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
use Symfony\Component\HttpKernel\Controller\ControllerReference;
use Acme\DemoCommunityBundle\Rating\RateableInterface;
class CommunityExtension extends \Twig_Extension
{
/**
* #var string
*/
const RATING_ACTION = 'AcmeDemoCommunityBundle:Rating:ratingStars';
/**
* #var FragmentHandler
*/
protected $handler;
public function __construct(FragmentHandler $handler)
{
$this->handler = $handler;
}
public function getFunctions()
{
return array(
'community_rating' => new \Twig_Function_Method($this, 'communityRating', array('is_safe' => array('html'))),
);
}
public function communityRating(RateableInterface $object, $readOnly = false)
{
return $this->handler->render(new ControllerReference(self::RATING_ACTION, array(
'objectType' => $object->getRatingType(),
'objectId' => $object->getId(),
'readOnly' => $readOnly
)), 'esi', $options);
}
public function getName()
{
return 'community';
}
}
services:
acme_community.twig.community:
class: Acme\DemoCommunityBundle\Twig\CommunityExtension
arguments: [ #fragment.handler ]
tags:
- { name: twig.extension }
Now your template should look like this:
{% for products as product %}
<div class="product">
<h4>{{ product.name }}</h4>
<div class="ratings">
{{ community_rating(product, true) }}
</div>
</div>
{% endfor %}
With this design it is easy to use the rating stars in our application, but we also have the flexibility to change the implementation how ratings work in the future without touching the templates where ratings are used.

Resources