zend 2 -using Zend Forms in partial view helper - forms

I have a form that is repeated over several different modules.
I would like to avoid repeating the same process over and over again and want to render the form once (in my application module) and then simply use a partial view script to call it wherever I wish like this;
<?php echo $this->partial('partialLayout/advertForm'); ?>
Following the advice here it would be pretty straightforward to do this if I simply created a HTML form in the partial script and then called it.
However, I want to use Zend Forms. Bearing in mind that zend form are loaded onto the page via the controller- how do I get around this.
i guess that the crux of the question is - how do you call a Zend Form in another module using a partial view script.

I did something like that with a ViewHelper. My solution is posted here, in Zend Forums.
There it is.
The code ViewHelper :
<?php
/**
* View helper to return the HTML code of the login form.
*
* #filesource LoginForm.php
* #encodage UTF-8
* #author DAFAP Informatique - Alain Pomirol
* #date 10 nov. 2015
* #version 2015-1
*/
namespace Login\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\View\Model\ViewModel;
use Login\Form\LoginForm as Formulaire;
class LoginForm extends AbstractHelper implements ServiceLocatorAwareInterface
{
protected $sm;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->sm = $serviceLocator;
return $this;
}
public function getServiceLocator()
{
return $this->sm;
}
public function __invoke()
{
$form = new Formulaire();
$layout = new ViewModel(array(
'form' => $form
));
$viewRender = $this->getServiceLocator()->getServiceLocator()->get('ViewRenderer');
$layout->setTemplate('login/index/index.phtml');
return $viewRender->render($layout);
}
}
The statement in module.config.php in Login module :
'view_helpers' => array(
'invokables' => array(
'loginForm' => 'Login\View\Helper\LoginForm'
)
),
Use in view of the application module :
'view_helpers' => array(
'invokables' => array(
'loginForm' => 'Login\View\Helper\LoginForm'
)
),

It's a common problem! Forms can really be a slow-down with ZF2.
I use Twig (recommended) but you can port this approach to plain old ZF2 templates too.
Here's one such template I use nearly everywhere:
horizontal_form.twig
{# render rows only, not the form tag #}
{% do form.prepare() %}
{% for f in form %}
{% do f.setOption( 'twb-layout', 'horizontal' ) %}
{{ formRow( f ) }}
{% endfor %}
I've got a second with a slightly different approach to support a specific IdentifiableForm in my code:
identifiable_form.twig
<form id="{{ form.getAttribute('name') }}" name="{{ form.getAttribute('name') }}" action="{{ form.getAttribute('action') }}" class="form-horizontal" role="form">
<input type="hidden" name="class" value="{{ form.getAttribute('class') }}">
{% for f in form %}
{% do f.setOption( 'twb-layout', 'horizontal' ) %}
{% do f.setOption( 'twb-form-group-size', 'form-group-sm' ) %}
{{ formRow( f ) }}
{% endfor %}
<div class="hr-line-dashed"></div>
<button type="submit" class="btn btn-primary ladda-button" data-style="expand-right">Save</button>
</form>
With those two in the pocket, my usage looks like this:
In a twig template that should use a form
...include the template from the other:
<div class="panel-body">
{% set form = general_config_form %}
{% do form.prepare() %}
{% include 'application/generic/identifiable_form' %}
</div>
The controller action that operates that template looks like:
$vm = new ViewModel();
$vm->setTerminal( true );
$vm->setTemplate( 'application/admin/config/index' );
$sm = $this->getServiceLocator();
$fm = $sm->get( 'FormElementManager' );
$country = $config_mapper->get('general.country', true );
$general_config_form = $fm->get( GeneralConfigForm::class, [ 'country' => $country ] );
$general_config_form->setAttribute('action', '/admin-config/form-save' );
$vm->setVariable( 'general_config_form', $general_config_form );
In the end it's just a game of:
instantiating/loading the form in the action
setting the form as a variable in the ViewModel
including the "generic form template" from that action's actual template
On topic, if you hate rigging forms as much as I do, I made a little tool to save time here: https://github.com/Saeven/zf2-circlical-formtool
Second, if you use Bootstrap in your app, this form helper replacement really makes things look nice: https://github.com/neilime/zf2-twb-bundle
Good luck!

Related

Create form based on EntityType with option to add quantity

I'm trying to setup a Symfony form that will allow a user to select a number of elements adding the wished quantity. I would like to be able to have a FormType which would be somewhere between an EntityType and the IntegerType. Meaning I need to have a list of elements based on a query_builder to select only part of my products, but I don't just want to be able to select the product but say how many I want of a given number of products.
I've been able to create a form based on the options I send. For each product in my array I add an IntegerType field to my form using the builder. This allows me to show a list of products and ask the client the number of elements he wants.
Now the problem I have is adding detailed data from the product in the list as I don't know the forms field names I can't interact with the label. If I could add something allowing me to say that the 'label' could show as raw html, I could concatenate the wished data in the label.
Here is my current form:
class OfferRequestStepMultipleHardwareType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// instead of haveing set fields I create them based on the $option['step']->getProducts() data
foreach ($options['step']->getProducts() as $product){
$builder->add($product->getId().'-qty', IntegerType::class, [
'mapped' => false,
'attr' => [
'value' => 0,
'class' => 'longlist',
'min' => 0
],
'row_attr' => [
'class' => 'longlist'
],
'label' => $product->getNumber() // ideally I would do some thing like '<div>'.$product->getNumber().'</div><div>'.$product->getDescription().'</div>' and then show it as raw in the form template
]);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => OfferRequest::class,
'step' => StepHardware::class,
]);
}
}
The problem is this generates code a bit like this:
<div id="offer_request_step_multiple_hardware">
<div class="longlist"><label for="offer_request_step_multiple_hardware_27-qty" class="required">Product 1</label><input type="number" id="offer_request_step_multiple_hardware_27-qty" name="offer_request_step_multiple_hardware[27-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_28-qty" class="required">Product 2</label><input type="number" id="offer_request_step_multiple_hardware_28-qty" name="offer_request_step_multiple_hardware[28-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_29-qty" class="required">Product 3</label><input type="number" id="offer_request_step_multiple_hardware_29-qty" name="offer_request_step_multiple_hardware[29-qty]" required="required" value="0" class="longlist"
min="0"></div>
<div class="longlist"><label for="offer_request_step_multiple_hardware_30-qty" class="required">Product 4</label><input type="number" id="offer_request_step_multiple_hardware_30-qty" name="offer_request_step_multiple_hardware[30-qty]" required="required" value="0" class="longlist"
min="0"></div><input type="hidden" id="offer_request_step_multiple_hardware__token" name="offer_request_step_multiple_hardware[_token]" value="PZaPfxKNSV-TjftRgjAw1K8XCUr7Dvkrp57kWTMBJ64"></div>
I've also tried to create a form theming in the template to change the way the Integer widget show with this:
{% form_theme form _self %}
{% block question %}
<h1>{{ offer.lastStep.Question }}</h1>
{% endblock %}
{% block integer_widget %}
<div class="name_row">
{{ form_label(form)|raw }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{{ form_help(form) }}
</div>
{% endblock %}
Without any success as when I concatenate and try to add the filter "raw" to it, the code is still changed autoescaped. The problem is then I've found information on how to set a specific label for a specific field of a form, but again I generate the form on the go and have no way of knowing the field names (like described here). Any suggestions on how to have this work?
Ideally I would like to be able to create a FormType based on the EntityType which would allow to add an Integer instead of selecting the Entity elements...
Any help would be nice!
Labels in Symfony forms are escaped at some point before the rendering of the template, so applying the twig raw filter won't prevent html entities from being escaped.
What you can do is create a new twig filter, to apply the php function html-entity-decode (following code untested):
Twig/HtmlDecodeExtension.php
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class HtmlDecodeExtension extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('htmldecode', [$this, 'htmlDecode']),
];
}
public function htmlDecode($value)
{
return html_entity_decode($value);
}
}
If you use the default configuration with services autowiring, you don't have to register the new extension; in the other case, do it in config/services.yaml
Now you can use the filter in your form template instead of raw, and put whatever html you like in the label:
{% form_theme form _self %}
{% block question %}
<h1>{{ offer.lastStep.Question }}</h1>
{% endblock %}
{% block integer_widget %}
<div class="name_row">
{{ form_label(form)|htmldecode }}
{{ form_errors(form) }}
{{ form_widget(form) }}
{{ form_help(form) }}
</div>
{% endblock %}

Customize data-prototype attribute in Symfony 2 forms

I am trying to follow the Symfony 2.7 docs to create Embed a Collection of Forms using a custom Collection Prototype.
Problem is, that I am not able to create a custom collection prototype as described in the docs.
As in the example there are two simple classes:
A Task class that manages the description of the task and additionally any number of tags, represented by its own Tag class
class Task {
protected $description;
protected $tags;
public function __construct() {
$this->tags = new array();
}
// Getter & Setter for description + additional addTag & removeTag methods
// ...
// Tags getter
public function getTags() {
return $this->tags;
}
}
class Tag {
protected $name;
// ... setName(...), getName()...
}
These are the custom form types:
class TaskType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('description');
$builder->add('tags', 'collection', array('type' => new TagType()));
}
public function getName() {
return 'task';
}
// ...
}
class TagType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name');
}
public function getName() {
return 'tag';
}
// ...
}
Twig file to render the form
{{ form_start(form) }}
{# render the task's only field: description #}
{{ form_row(form.description) }}
{# render tags - use table instead of ul as in example #}
<div class="table-responsive">
<table class="table">
<thead>
<th>{{ 'task.tag.headline'|trans }}</th>
</thead>
<tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{{ form_row(form.rules) }}
</tbody>
</table>
</div>
{{ form_end(form) }}
This works fine and renders the tags list inside the table. However, this code uses the default prototype, that (of course) does not create table rows for the different tags.
I tried to add the code to use a custom prototype as described in the docs. How ever the docs does not say anything about where to add this code or how to use it:
Twig code WITH custom prototype code
{{ form_start(form) }}
{# Custom Prototype Code from docs #}
{% form_theme form _self %}
{% block _tags_entry_widget %}
<tr>
<td>{{ form_widget(form.name) }}</td>
</tr>
{% endblock %}
{# render the task's only field: description #}
{{ form_row(form.description) }}
{# render tags - use table instead of ul as in example #}
<div class="table-responsive">
<table class="table">
<thead>
<th>{{ 'task.tag.headline'|trans }}</th>
</thead>
<tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{{ form_row(form.rules) }}
</tbody>
</table>
</div>
{{ form_end(form) }}
Using the custom prototype code like this results in the error:
Method "name" for object "Symfony\Component\Form\FormView" does
not exist in "MyAppBundle:Task:task.html.twig"
This sounds reasonable, since name belongs to the Tag class and not to the Task class.
Problem 1: How to use/access the Tag form inside the template?**
I removed <td>{{ form_widget(form.name) }}</td> from the prototype template and replaced it with <td>Test</td> to see if the template is used. The result: The template is NOT used and has no effect.
Problem 2: What is the correct way to set/activate the prototype template?
I found other threads dealing with prototype question/problems. The answers propose different solutions using macros, external twig files, etc. Since the Symfony docs seems to offer a solution within the same file without using hacks like macros, I would like to know implement this solution.
This is working for me - I am sorry - it is Symfony 3, but you should be able to translate.
In the buildForm method of my PersonType class, I have a CollectionType of emails
->add( 'emails', CollectionType::class, [
'label' => 'common.email',
'entry_type' => AppEmailType::class,
'by_reference' => true,
'required' => false,
'label' => false,
'empty_data' => null,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'mapped' => false,
'prototype_name' => '__email__'
] )
The template to render the PersonType includes the emails form like so
{% include 'common/emails.html.twig' with {'form': form.emails } %}
common/emails.html.twig
This template is the container for the collection
<div class="emails">
<span class="sub-form-legend">{{'common.email'|trans}}</span>
{{form_row(form)}}
{% if form.vars.allow_add %}
<div class="add-one-more-row">{{ 'common.add_one_more'|trans}}</div>
{% endif %}
</div>
In fields.html.twig, I have an entry_row template specific to the form, which uses a common email template defined in the same file.
{% block _user_person_emails_entry_row %}
{{ block('_emails_entry_row') }}
{% endblock %}
{% block _emails_entry_row %}
{% spaceless %}
<div class="form-row email">
<span class="type-select">{{ form_widget(form.type) }}</span>
<span class="input">{{ form_widget(form.email) }}</span>
<span class="comment">{{ form_widget(form.comment) }}</span>
<span class="remove-form-row" title="{{'common.remove'|trans}}" id="email-__email__"><i class="fa fa-remove"></i></span>
</div>
{% endspaceless %}
{% endblock %}
To find out the name, trace the names of your forms and prefix them with an underscore. Use {{dump(form)}} to get the unique name.
The HTML will be placed in the data-prototype attribute.

Symfony2 Create a Post calls ShowAction

I am "transplanting" my old website code into the new Symfony2.6. I have a Controller to render a form and create posts as in a blog. Then it is submited (POST method) in a second controller once it is valid. When I call the route to initialize the controller, for some reason I ignore, it also calls the ShowAction($slug), and it fails since the post it is not yet created and hence it does not have any $slug parameter. Why is it calling this showAction? Despite I type the url to just display the form.
Here is the error log at the line that starts the failure:
1. in src/Blog/BlogBundle/Services/PostManager.php at line 80
2. at PostManager ->findBySlug ('create_post') in src/Blog/BlogBundle/Controller PostController.php at line 55
3. at PostController ->ShowAction ('create_post')
4. at call_user_func_array (array(object(PostController), 'ShowAction'), array('create_post')) in app/bootstrap.php.cache at line 3020
I do not want to call the ShowAction.
Here is the controller code:
/**
* Show a Post
*
* #param string $slug
*
* #throws NotFoundHttpException
* #return array
*
* #Route("/{slug}")
* #Template()
*/
public function ShowAction($slug)
{
$post = $this->getPostManager()->findBySlug($slug);
$form_comment = $this->createForm(new CommentType());
return array(
'post' => $post,
'form_comment' => $form_comment->createView()
);
}
/**
* Displays a form to create a new Post entity.
*
* #Route("/new_post", name="_blog_backend_post_new")
* #Template()
*/
public function newAction()
{
$form = $this->createForm(new PostType(), new PostModel(new Post()));
return array(
'form' => $form->createView(),
);
}
/**
* Creates a new Post entity.
*
* #Route("/create_post", name="_blog_backend_post_create")
* #Method("POST")
* #Template("BlogBundle:Backend/Post:new.html.twig")
*/
public function createAction()
{
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(new PostType(), new PostModel(new Post()));
$formHandler = new PostHandler($form, $request, new Post(), $em);
if ($formHandler->process()) {
return $this->redirect($this->generateUrl('_blog_backend_post'));
}
return array(
'form' => $form->createView(),
);
}
I do not know if its necessary but here are the involved templates:
* #Template("BlogBundle:Backend/Post:new.html.twig"):
{% extends "::base.html.twig" %}
{% block content %}
<form class="well" action="{{ url('_blog_backend_post_create') }}" method="post" {{ form_enctype(form) }}>
{% include 'BlogBundle:Backend/Post:edit.form.html.twig' with {'form': form } %}
</form>
{% endblock %}
BlogBundle:Backend/Post:edit.form.html.twig:
{% if form_errors(form) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form) }}
</div>
{% endif %}
<p>
<label for="">Title</label>
{% if form_errors(form.title) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.title) }}
</div>
{% endif %}
{{ form_widget(form.title, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<label for="">Body</label>
{% if form_errors(form.body) %}
<div class="alert alert-block alert-error fade in form-errors">
{{ form_errors(form.body) }}
</div>
{% endif %}
{{ form_widget(form.body, { 'attr': {'class': 'w100'} }) }}
</p>
<p>
<button class="btn" type="submit">Save</button>
<a class="btn" href="{{ url('_blog_backend_post') }}">Cancel</a>
</p>
{{ form_rest(form) }}
What I am missing and how can I correct it? Thank you in advanced.
The router matches the first route possible, in this case "/{slug}" where slug="create_post".
There are at least two solutions, where the first is the easiest, the second is the one I recommend:
Cut/Paste your showAction to the bottom of your file, in that case router will match /create_post to the createAction first
You can exclude this like: #Route("/{slug}", requirements={"slug" = "^(?<!create_post).+"})
The problem is that you are posting to /create_post which in turn matches the route /{slug}. slug is being set to 'create_post'. With the Symfony 2 router the first match wins and thus your showAction method is called.
Consider setting your ShowAction path to: '/show/{slug}'.
Or you could move your ShowAction down to the bottom of your controller file. That is a bit dangerous because you might forget and add a different action later. But either way will work.

Symfony2 forms are rendered empty

I've just started trying to build forms with Symfony2.4.2 and having a VERY frustrating time. I have successfully managed to build the example found in the book chapter 12. I've also tried to build essentially the same form in another project, in another bundle and always seems to end up with an empty form:
<html>
<head></head>
<body>
<form action="" method="post" name="form"> </form>
</body>
</html>
The code to create this:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use DMV\form3Bundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->add('save', 'submit')
->getForm();
return $this->render('DMVform3Bundle:Default:new.html.twig', array('form' => $form->createView(),));
}
}
new.html.twig:
{{ form(form) }}
I've tried tried delving into the "$form" at a breakpoint at the "return $this->render..." line that is suppose to render the form and I DO see the form elements texts in the structure but it is a very large and complex structure and I'm not sure what I should see exactly.
Any suggestions would be appreciated.
You can simply render form in your new.html.twig template like:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{{ form(form) }}
But if you use Symfony version < 2.3, so try to use something that:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>

Get variable value in Symfony2 FormBuilder

I am building a form in Symfony2 and for some rows I want to pass a formatted version of each widget's value as a title attribute which I can then display in another div.
If I have not set a formatted version, I will just insert the actual, non-formatted value into the div instead.
Ideally, my twig code might look like this:
{% block form_row %}
{% spaceless %}
<div class="field-display-value">
{% if attr['title'] is defined %}
{{ attr['title'] }}
{% else %}
{{ form.vars.value }}
{% endif %}
</div>
<div class="field-widget">
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
But I don't know how I can access the value of the widget in the formBuilder. Ideally I would like something like this:
$builder->add('some_field', 'text', array(
'attr' => array('title' => someFormattingFunction( this.widget.value ),
));
Obviously, this.widget.value pseudocode doesn't work.
Don't know if even possible, but I don't want to have to resort to javascript madness.
Any ideas?
See this documentation :
http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms
You have to set a listener for the PRE_SET_DATA event, on your formBuilder.
Then you will be able to access the data passed to this form with :
$data = $event->getData();
Now you juste have to add your fields inside.
/!\ Your fields will only be added, if an object is passed to the form.
So if you still want the fields when there is no data passed, you have to add the fields outside the event lister function, and then modify the fields attribute title inside this function.
$form = $event->getForm();
$form->add('some_field', 'text', array(
'attr' => array('title' => someFormattingFunction($data.getSomething()),
));