Showing posts with label form. Show all posts
Showing posts with label form. Show all posts

Sunday, February 3, 2013

Symfony2 - Customize output of expanded form choices

I'm updating one of my projects to the current master of the symfony2 framework. One of the backwards incompatible changes I noticed are changes in the form framework. In one of my templates I need to customize the output of a list of checkboxes as shown in the screenshot of my data table.
The data for the checkboxes is taken from the database, so I'm using the 'entity' type in my form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
         ->add('cities', 'entity', array(
            'class' => 'MyCityBundle:City',
            'query_builder' => $this->queryBuilder,
            'required' => false,
            'expanded' => true,
            'multiple' => true,
        ))
    ;
} 
Using twig you could render all of the checkboxes without labels or anything else very easily: But obviously you don't want to render all checkboxes all at once, but in a loop. Each loop renders a table row and the checkbox is the content of one of the columns. You can loop over each of the choices with: That's fine. But what about the other data? The other data is part of the choice list which is saved in the form element and automatically retrieved from the database. The checkbox label and value can be retrieved via {{ child.vars.value }} and {{ child.vars.label }}. But there doesn't seem to be any possibility to retrieve all of the entity data in the twig template. What we need to do is saving the entity data in a separate variable and access it in the twig template. You could use the query builder, which was passed to the form element during creation to retrieve your entity data, but that would mean, that the whole data has to be retrieved again from the database, although it's already available somewhere in the form element. The way I extracted the entity data from the form element before Symfony 2.1 was: ...which doesn't work anymore. The new way (Symfony 2.1) to do it is: The only thing left is to connect the entity to the currently drawn checkbox. This can be done via the checkbox value, as this is the entity's ID attribute: This way you have full flexibility when rendering a complex expanded entity choice type.

Thursday, October 25, 2012

sf2 input type date - disable native datepicker

Newer versions of the Google Chrome browser (and perhaps Firefox will follow) implement their own datepicker on input fields with type="date". This seems to be some new fancy HTML5 feature, but correct me if I'm wrong. If you are using jQuery datepicker throughout your whole project for example, you suddenly have two datepickers on that input field.

There is no possibility to turn off the native datepicker with additional attributes. You are left with three solutions to your problem:

  1. Use modernizr to detect this feature and only enable jQuery datepickers, if not present:
  2. Prevent standard event handling on this kind of input types: which I don't think is a good solution!
  3. Render all form fields of type date with type="text" in symfony2. To enable this globally, register a global form theme in your config.yml as described in the Symfony2 documentation. My form theme consists of the following Twig code: This overwrites form_widget_simple and checks for the registered field type before setting it for the input field. If it finds 'date' it is overwritten with 'text'. Very nice and quick solution in my opinion, hope that helps you.

Saturday, November 12, 2011

Search form using symfony2 and doctrine2

This post is about creating a search form and using the doctrine2 query builder to dynamic built the query. You need an object you can bind the form values to. I used the symfony2 entity generator to build my heavy search form data holder really quickly. Let it create an empty repository class for you, which will hold the search function later. Use the symfony2 form generator to create a form based on the entity. As this entity won't be saved in the database the doctrine annotations are not needed. I kept them for later use and just removed the "Entity" annotation.

Next thing you do is writing a template for your form and to specify some validation rules. My controller action which receives the search POST looks like this:



We need to implement the findBySearchCriteria function in the empty repository class of the user entity. This function returns a query builder object which we can do everything we want with.

The following examples show what is possible with the doctrine2 query builder in the search function.

Many-to-one search

The user has a languages attribute which is of type Language. The entity Language has three attributes:
  1. a language name (e.g. english, french, etc)
  2. points that define how good the language is spoken by the user (10 very good, 1 very bad)
  3. a back reference to the user
We want to find users that marked their english knowledge at least with 5 points and their french with at least 7 points. To accomplish this, we need to check, if the user has an language entry which fulfills the given criteria. We check, if such a language entry exists.

As "exists" in the query builder expects a dql subquery, we give it one.

"u" is the alias for the User class of the main query builder. The same method is used for the french language points.

The resulting DQL is:
SELECT u, l FROM Acme\UserBundle\Entity\User u WHERE (EXISTS( SELECT langE FROM AcmeUserBundle:Language langE WHERE langE.user = u AND langE.language = :langName AND langE.points BETWEEN 10 AND :points))

Nested AND / OR queries

Next requirement is that the zip code of the user entity has to lie in a given range of zip codes. The user who defines the search criteria is able to provide a list of zip code ranges. The format for a range is "xxxxx-xxxxx", so a zip code consists of five digits.

The zip code range search query needs to be built using OR. The user's zip code can lie in one range OR another. Native SQL could look like this:
WHERE (zipCode BETWEEN :zipCodeFrom1 AND :zipCodeTo1) OR (zipCode BETWEEN :zipCodeFrom2 AND :zipCodeTo2)

To combine this with the language query, the OR part has to be nested within an AND part:
WHERE ((zipCode BETWEEN :zipCodeFrom1 AND :zipCodeTo1) OR (zipCode BETWEEN :zipCodeFrom2 AND :zipCodeTo2)) AND (EXISTS( SELECT .... 
To accomplish the OR nesting you can use add() on the orx expression. The following example shows how to do that:


This results in a really nice nested query like this:
SELECT u FROM Acme\UserBundle\Entity\User u WHERE ((u.zipCode BETWEEN :zipFrom1 AND :zipTo1) OR (u.zipCode BETWEEN :zipFrom2 AND :zipTo2)) AND (EXISTS( SELECT langE FROM AcmeUserBundle:Language langE WHERE langE.user = u AND langE.language = :langName AND langE.points BETWEEN 10 AND :points))

Building nested queries is really easy. It took me some time to find out that I can use add() on the orx expression to dynamically add parts to it.