Dynamically remove Options from symfony2 choice (entity) form - symfony

I have a Entity Type form where I list all the friends of the current user. This is e.g. used for creating a new Group. Now I want to use the same form for adding people to the same group, so the Choice field should show all the friends of the current user that are not yet in the group. I thought I just use a form event to remove the options(users) that are already in the group.
My listener looks like this:
class FriendSelectListener implements EventSubscriberInterface {
public static function getSubscribedEvents() {
// Tells the dispatcher that we want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event) {
$betRound = $event->getData();
$form = $event->getForm();
$groupForm = $form->get('userGroup');
$usersForm = $groupForm->get('users');
foreach($betRound->getUserGroup()->getUsers() as $user){
if($usersForm->has($user->getId())){
$usersForm->remove($user->getId());
}
}
}
}
But I can't render it, because in my test I removed the user with the id 2 and then I get the following error message while rendering:
Key "2" in object (with ArrayAccess) of type
"Symfony\Component\Form\FormView" does not exist in
/var/lib/stickshift/1ed1210eec124c179c45aac4ad484afd/app-root/data/269891/src/Strego/UserBundle/Resources/views/Form/selectFriends.html.twig
at line 5
Update:
This might be related to my view:
{% for id, choice in choices %}
{% set user = choice.data %}
<label>
<div class="people-list people-choose unselected" style="width:270px;">
<img src="{{ user.profilePic | imagine_filter('profile_thumb') }}" class="img-polaroid" alt="{{ user.nickname }}">
{#<img src="{{ asset('bundles/stregoapp/img/profile-thumb-90x90.png') }}" alt="Profilbild" class="img-polaroid">#}
<p>{{ form_widget(form[id]) }} <span class="label">einladen</span></p>
<h3>{{ user.nickname }}</h3>
<h3><small>{{ user.firstName }} {{ user.lastName }}</small></h3>
</div>
</label>
{% endfor %}
For me it seems like I only removed the form element but not the choice.

I found the problem, it was indeed my view. As soon as I stopped iterating over the options but used the child elemts of my form element it was working:
{% for child in form %}
{% set user = choices[child.vars.value].data %}
<label>
<div class="people-list people-choose unselected" style="width:270px;">
<img src="{{ user.profilePic | imagine_filter('profile_thumb') }}" class="img-polaroid" alt="{{ user.nickname }}">
<p>{{ form_widget(child) }} <span class="label">einladen</span></p>
<h3>{{ user.nickname }}</h3>
<h3><small>{{ user.firstName }} {{ user.lastName }}</small></h3>
</div>
</label>
{% endfor %}

Related

Issue with Post Object Timber/Twig WordPress

I have an ACF field, It's a Repeater, and inside the Repeater is a Post Object.
Controller
/* Template Name: Strategic Partners */
use Timber\Timber;
use Timber\PostQuery;
use Flynt\Utils\Options;
use const Flynt\Archives\POST_TYPES;
$context = Timber::get_context();
$context['slider'] = get_field('slider');
$context['featured'] = get_field('featured');
if (isset($_GET['contentOnly'])) {
$context['contentOnly'] = true;
}
Timber::render('templates/StrategicPartners/index.twig', $context);
Here's the snippet from the View
{% for partner in Post(featured) %}
{{ partner.link }}
{% endfor %}
I'm var_dumping the context, and it's getting the Timber Post... but {{ partner.title }} does not grab the Post Title, it's blank.
{{ partner.link }} comes out with the direct link.
Output
http://***/strategic-partners/
http://***/strategic-partners/
What am I doing wrong here? I'm using {{ Post() }}} within a seperate repeater, within for foreach itself and that works on another page, but it does not work here either, so I tried Post() included in the foreach and it still is not working.
No one answered this for me. I answered it myself.
There isn't much documentation on this within the formal Timber documentation so I hope this helps somebody.
Do not call Post with your foreach
Do this for a Post Object within a Repeater (ACF)
{% for item in seq %}
{{ Post(item.post_object_name).field }}
{% endfor %}
seq is your ACF Field
post_object_name is the Post Object within the field.
field is the field within the Post Object type.
Hope this helps someone.
For more Context:
Controller
<?php
/* Template Name: Strategic Partners */
use Timber\Timber;
use Timber\PostQuery;
use Flynt\Utils\Options;
use const Flynt\Archives\POST_TYPES;
$context = Timber::get_context();
$context['slider'] = get_field('slider');
$context['featured_sps'] = get_field('featured_strategic_partners');
$context['rebate'] = get_field('rebate_information');
if (isset($_GET['contentOnly'])) {
$context['contentOnly'] = true;
}
Timber::render('templates/StrategicPartners/index.twig', $context);
Twig View
{% for partner in featured_sps %}
{{ Post(partner.featured).title }}
{% endfor %}
Images are more complex
{% for partner in featured_sps %}
<div class="slide">
<a href="{{ Post(partner.featured).link }}">
<img src="{{ Image(Post(partner.featured).logo).src }}" alt="{{ Post(partner.featured.title) }} Logo">
</a>
</div>
{% endfor %}

How to show Type name in Twig

I have 2 tables: Product and Type. Relationship is One Type has Many Product
Type: id, name
Product: id, name, type_id
I dont know how to show type name in Twig, I'm new in Symfony and here is my code:
<div>
{% for pro in product %}
<div>Product name: {{ pro.name }}</div>
<div>
Type name:
</div>
<div>Price: {{ pro.price }}</div>
<div>
{% for img in pro.images %}
<img src="{{ "/uploads/product/" ~ img.path }}" style="height: 100px;">
{% endfor %}
</div>
{% endfor %}
</div>
You can access the properties of an object in Twig. You should not look at your database, but the way your entities are made. Your Product has a $id, $name and $type. Type has $id and $name.
In Twig you can do {{product.name}}
To get the type, you want {{product.type}}
To get the name of the type, you want {{product.type.name}}
So, in your code, that would be:
<div>
Type name: {{ pro.type.name }}
</div>
Internally, the Twig parser will get $product and check if it has the method getName() when you do {{product.name}}. That same logic applies to {{product.type.name}}. It checks if $product has getType(), then if that has getName(). The PHP equivalant is $product->getType()->getName().
You almost never should type an actual method in your code. If you have to, you might want to take a step back and re-evaluate, because it often is a code smell.
You can just add {{ pro.type.name }} to your code:
<div>
{% for pro in product %}
<div>Product name: {{ pro.name }}</div>
<div>
Type name: {{ pro.type.name }}
</div>
<div>Price: {{ pro.price }}</div>
<div>
{% for img in pro.images %}
<img src="{{ "/uploads/product/" ~ img.path }}" style="height: 100px;">
{% endfor %}
</div>
{% endfor %}
</div>
And be sure that type attribute refer to Type entity. Your product entity should have type attribute like below:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Type", inversedBy="products")
* #ORM\JoinColumn(nullable=false)
*/
private $type;
// getter
public function getType()
{
return $this->type;
}
And your Type entity should have products attribute like below:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Type", mappedBy="type")
* #ORM\JoinColumn(nullable=true)
*/
private $products;
public function getProducts()
{
return $this->products;
}

Symfony: trying to customize a collection form prototype

I have this form:
class BillType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user')
->add('numberPlate')
->add('servicesPerformed', CollectionType::class, array(
'label' => false,
'entry_type' => ServicePerformedType::class,
'allow_add' => true,
))
->add('Save', SubmitType::class)
;
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'DefaultBundle\Entity\Bill'
));
}
being ServicePerformedType class this:
class ServicePerformedType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', TextareaType::class, array('label' => false))
->add('price', TextType::class, array('label' => false))
->add('quantity', TextType::class, array('label' => false));
}
}
And this template to render the form:
{{ form(form) }}
Add service
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script type="text/javascript">
var index = 0;
$('a').on('click', function() {
var prototype = $('#bill_servicesPerformed').data('prototype');
prototype = prototype.replace(/_name_/g, index.toString());
$('#bill_servicesPerformed').html(prototype);
index++;
})
</script>
As it is said in the docs, to get a custom prototype I should add the lines below at the top of my template:
{% form_theme form _self %}
{% block _servicesPerformed_entry_widget %}
I WILL WRITE MY CUSTOM PROTOTYPE HERE
{% endblock %}
But when I press Add service I dont get the text I WILL WRITE MY CUSTOME PROTOTYPE HERE, but the description, fields and quantity related to the ServicePerformedType class as before..
NOTE: maybe there are other ways to custom a form prototype, but I'm interested on this, so will be very thankful to someone who give a solution related to this way to custom form prototypes, thanks.
I must warn you that customizing the prototype could be a bit tricky. If you change your FormType fields you'll need to go through the template and make the same changes as well or your form will fail to render.
What I like to do is to create a custom template for that specific field and then customize it appropriately. So, looking at your code I would do something like this:
Create a page template - the one you'll use to render the entire page, including the form.
{# MyBundle/Resources/views/myPage.html.twig #}
{% extends "::base.html.twig" %}
{# This will tell the form_theme to apply the
`MyBundle:form/fields/servicePerformed.html.twig` template to your "form" #}
{% form_theme form with [
'MyBundle:form/fields/servicePerformed.html.twig'
] %}
{% block body %}
<div>
{{ form_start(form) }}
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}
Now you'll need to create the template MyBundle/Resources/views/form/fields/servicePerformed.html.twig. It will be used to customize only the servicePerformed field. The template should look something like this
{% macro service_template(fields) %}
<tr>
<td>I WILL WRITE MY CUSTOM PROTOTYPE HERE</td>
</tr>
{% endmacro %}
{#
The block name must match your field name!
You can find it by going into the debug toolbar -> Forms ->
click on your form field and then search for "unique_block_prefix".
Copy that and add "_widget" at the end.
#}
{% block _service_performed_widget %}
<table data-prototype="{{ _self.service_template(form.vars.prototype)|e }}">
{% for fields in form.children %}
{{ _self.service_template(fields) }}
{% endfor %}
</table>
{% endblock %}
I want to note that in the field template I'm passing the original prototype _self.service_template(form.vars.prototype). By doing this, you can use the original fields and render them in your customized prototype. For example this code:
{% macro service_template(fields) %}
<tr>
<td>{{ form_widget(fields.description) }}</td>
</tr>
{% endmacro %}
Will result in something like the following rendered prorotype:
<tr>
<td>
<input type="text" id="service_performed___name___description" name="service[__name__][description]"/>
</td>
</tr>
You can then manipulate it using your javascript.
I hope this helps you.
Actually form theme blocks that start with an underscore _ relate to a field with a specific name.
What I mean is that, if your master form BillType is called my_form, you will need to do this:
{% form_theme form _self %}
{% block _my_form_servicesPerformed_entry_widget %}
I WILL WRITE MY CUSTOM PROTOTYPE HERE
{% endblock %}
The problem with this approach is that it concerns a specific iteration of BillType. If you use this form type elsewhere and provide it with a different name my_form_2, you would have to add an identical block named _my_form_2_servicesPerformed_entry_widget.
You can use macro, have a look at below example it's working fine for me even in Symfony3. Using this example you will be able to format your collection prototype as well.
View
{% macro widget_prototype(widget, remove_text) %}
{% if widget.vars.prototype %}
{% set form = widget.vars.prototype %}
{% set name = widget.vars.name %}
{% else %}
{% set form = widget %}
{% set name = widget.vars.full_name %}
{% endif %}
<div data-content="{{ name }}" class="panel panel-default">
<div class="section row">
<div class="col-md-12">
<label class="field-label">Skill <span class="text-danger">*</span></label>
<label class="field select">
{{ form_widget(form.skill) }}
<i class="arrow double"></i>
</label>
</div>
</div>
<div data-content="{{ name }}">
<a class="btn-remove" data-related="{{ name }}">{{ remove_text }}</a>
{{ form_widget(form) }}
</div>
</div>
{% endmacro %}
Your template to render the form has some problems. The first is this line:
prototype = prototype.replace(/_name_/g, index.toString());
The regex should be __name__.
Next, you are retrieving the prototype, but then immediately overwriting it and replacing the HTML of the prototype. There is nothing there I can see that actually appends the new form to your existing form anywhere. Plus since you just have a string of text, that replace isn't going to find any text __name__ to replace.
You should post the full extent of your Twig/Javascript so we can actually see the #bill_servicesPerformed as well as everything else you are trying to do. Before you write a custom prototype you should get the form working with the standard prototype just to make sure you don't have any bugs there first.
As an example, this is the way I keep going it. I do not know if there are some reasons not to, so be careful.
Form to include prototype:
<div class="modal-body" id="contactMehtods" data-prototype="
{% filter escape %}
{{ include(':organization/partials:prototype_contact_method.html.twig', { 'contact_method': contact_form.contactMethods.vars.prototype }) }}
{% endfilter %}">
<div class="form-group">
{{ form_label(contact_form.name, null, { 'label_attr': { 'class': 'control-label' }}) }}
{{ form_widget(contact_form.name, {'attr': {'class': 'form-control'}}) }}
</div>
</div>
Prototype template:
<div class="form-group">
{{ form_label(contact_method.name, null, { 'label_attr': { 'class': 'col-sm-3 control-label' }}) }}
<div class="col-sm-9">
{{ form_widget(contact_method.name, {'attr': {'class': 'form-control'}}) }}
</div>
</div>
<div class="form-group">
{{ form_label(contact_method.value, null, { 'label_attr': { 'class': 'col-sm-3 control-label' }}) }}
<div class="col-sm-9">
{{ form_widget(contact_method.value, {'attr': {'class': 'form-control'}}) }}
</div>
</div>
But a note to consider, the javascript needs to accommodate for these changes I guess.

Symfony: How to avoid custom form-types getting wrapped in a div automatically?

UserType Form:
class UserType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', 'email', ['label' => 'EMail']);
// various other fields....
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('registration'),
'data_class' => 'Vendor\Model\Entity\User',
));
}
public function getName()
{
return 'form_user';
}
}
TutorType Form:
class TutorType extends Translate
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('user', new UserType(), ['label' => false]);
$builder->add('school', 'entity', [
'class' => 'Model:School',
'property' => 'name',
'label' => 'Label'
]);
// Various other fields
$builder->add('save', 'Submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
//'validation_groups' => array('registration'),
'data_class' => 'Vendor\Model\Entity\Tutor',
'cascade_validation' => true,
));
}
public function getName()
{
return 'form_tutor';
}
}
When rendering, the UserType is rendered inside a div, i cant find a way to overcome this.
The Form is rendered as
<form name="form_tutor"
method="post"
action=""
novalidate="novalidate"
class="form-horizontal form-horizontal"
id="form_tutor">
<div id="form_tutor"
novalidate="novalidate"
class="form-horizontal">
<div class="form-group">
<div class="col-lg-10">
<div id="form_tutor_user">
<div class="form-group">
<label class="col-lg-2 control-label aaaa required"
for="form_tutor_user_email">EMail</label>
<div class="col-lg-10">
<input type="email"
id="form_tutor_user_email"
name="form_tutor[user][email]"
required="required"
class="form-control" />
</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label aaaa required"
for="form_tutor_tutorType">Type</label>
<div class="col-lg-10">
<select id="form_tutor_tutorType"
name="form_tutor[tutorType]"
class="form-control">
</select>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-2 col-lg-10">
<button type="submit"
id="form_tutor_save"
name="form_tutor[save]"
class="btn btn-default">Speichern</button>
</div>
</div><input type="hidden"
id="form_tutor__token"
name="form_tutor[_token]"
class="form-control"
value="s6i6zPxJs7KU5CiEe8i6Ahg_ca8rc2t5CnSk5yAsUhk" />
</div>
</form>
The form_tutor_user is wrapped in a own form-group div.
I tried to overwrite the form_tutor_user_widget but this is one level to deep. (And only a quick fix, it should be globally applied to all form type - Classes)
How can i change the theme so all custom types are not wrapped with the default form_row template?
Or how do i know in twig when a "subform" is rendered?
so i can decide to print the <div class="form-group"> when the child-node is not a subform, or skip it, if this is the case.
TIA
By default, in the base form theme:
{% block form_row %}
{% spaceless %}
<div>
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</div>
{% endspaceless %}
{% endblock form_row %}
And, for custom compound forms:
{% block form_widget_compound %}
{% spaceless %}
<div {{ block('widget_container_attributes') }}>
{% if form.parent is empty %}
{{ form_errors(form) }}
{% endif %}
{{ block('form_rows') }}
{{ form_rest(form) }}
</div>
{% endspaceless %}
{% endblock form_widget_compound %}
Unless you changed something here, the DIV you see should come from either one or the other bit of template.
However, in your specfic example, if form_tutor_user_row is defined, the first bit is never used, and if form_tutor_user_widget is defined, the last bit is never used.
Back to your question. Your question is :
"How can i change the theme so all custom types are not wrapped with the default form_row template?"
Here is the problem the way I see it: you want that your TOP forms (the form in which all sub-forms are included) all have a common way of rendering, in sections. Each section will be included in a DIV with class="form-group". You may want to throw in some additional rendering operations but I will limit myself to this to keep things simple.
What you need to do then is to create a specfic form type and make all your TOP forms inherit from this new form type. For instance:
class TopType extends AbstractType
{
public function getName()
{
return 'top_form';
}
}
... and an inherited form:
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
}
public function getName()
{
return 'my_form';
}
public function getParent()
{
return 'top_form';
}
}
As you can see, there is no need to make PHP inheritance for form theming inheritance to work.
Template-theming-wise (can I even say that?), if no specific form theming is set for my_form, Symfony will understand that the default form theme to use here is the form theme of top_form, that you can define as such:
{% block top_form_widget %}
{% spaceless %}
{% for child in form %}
<div class="form-group">
{{ form_widget(child) }}
</div>
{% endfor %}
{{ form_rest(form) }}
{% endspaceless %}
{% endblock top_form_widget %}
I should add that this is a problem I already encountered and solved. Tell me how that works for you.
Edit:
To sum it up, what you have to do is:
Create the TopType form type,
Add the top_form_widget block in your form theme,
For all your root forms (i.e. top-level forms, forms that have no parent), add a getParent() method that will return the name of your TopType form ("top_form")
In theory, if you override the form_widget_compound block in a global form theme this way, it should work as you want:
// app/Resources/views/form.html.twig
{% block form_widget_compound %}
{% if form.parent is empty %}
<div {{ block('widget_container_attributes') }}>
{{ form_errors(form) }}
{% endif %}
{{ block('form_rows') }}
{{ form_rest(form) }}
{% if form.parent is empty %}
</div>
{% endif %}
{% endblock %}
And register your form theme:
// app/config/config.yml
twig:
form:
resources:
- "::form.html.twig"
I usually solve this problem by rendering the nested form's fields manually:
{{ form_row(form.tutor.school) }}
{{ form_row(form.tutor.user.email) }}
Probably that's not the most elegant solution, but it works for me and I haven't looked for an elegant one yet.
Bootstrap 3.3.4 I ended up doing this.
They key part of this:
<div class="{% if form.parent.parent is empty %}form-group{% endif %}{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
Full template.
{% block form_row -%}
<div class="{% if form.parent.parent is empty %}form-group{% endif %}{% if (not compound or force_error|default(false)) and not valid %} has-error{% endif %}">
{% if form.parent.parent is null and label is not empty %}
{{- form_label(form) -}}
{% elseif label is empty %}
{{- form_label(form) -}}
{% endif %}
{% if compound is empty %}<div class="{{ block('form_group_class') }}">{% endif %}
{{- form_widget(form) -}}
{{- form_errors(form) -}}
{% if compound is empty %}</div>{% endif %}
</div>
{%- endblock form_row %}
Maybe it is not an elegant solution, however works for me, because I was also trying to find the solution.
As an example:
{% for custom_field in form.custom_fields %}
<div class="edit_custom">
{{ form_row(custom_field.name) }}
{{ form_row(custom_field.value) }}
</div>
{% endfor %}
<script>
$('.edit_custom').find('input').unwrap();
</script>
Try using form_themes.
First, in you parent template define the form theme:
{% form_theme form with ['BundleName:ControlerName:somesubform_form.html.twig'] %}
btw replace BundleName, ControllerName and somesubform with the proper names.
then render it with:
{{ form_row(form.somesubform) }}

Symfony 2 - Access mapped Object property form twig

I hava a entity with the following flied:
/**
* #ORM\ManyToOne(targetEntity="Document", inversedBy="posts")
* #ORM\JoinColumn(name="document_id", referencedColumnName="id")
*/
protected $image;
which I get with the following method:
public function indexAction() {
$posts = $this->getDoctrine()
->getRepository('airpaprcms2Bundle:Post')
->findByPageType(1);
if (!$posts) {
throw $this->createNotFoundException('No posts in Database!');
}
return $this->render('airpaprcms2Bundle:Default:index.html.twig', array('posts' => $posts));
}
How can I access a property of the mapped object form within twig? I tried post.image.file (the mapped entity Document has a property file)
{% for post in posts %}
<div>
<h1>
{{ post.title }}
</h1>
<p>{{ post.text }}</p>
<img src="{{ post.image.name }}" alt="{{ post.title }}" />
</div>
{% endfor %}
and get the following error message:
Item "file" for "" does not exist in airpaprcms2Bundle:Default:index.html.twig at line 11
What is the right syntax to access the mapped document property?
You can access the property of your linked entity with the following twig syntax:
{% for post in posts %}
{{ post.entityName.propertyName }}
{% endfor %}
In your case that would be:
{% for post in posts %}
{{ post.image.propertyName }}
{% endfor %}
Be sure to check that all post entities are linked to an image object. If one post entity has no link to an image, you will get a property not found error when trying to render your page.

Resources