i'm trying to add a field type url in the list view of an entity, this is the link at the documentation -> https://symfony.com/doc/master/bundles/SonataAdminBundle/reference/field_types.html#url .
This is my code, i've simply copied the documentation:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name')
->add('url', 'url', [
'url' => 'http://example.com'
]);
}
This seems to work but the column "Url" is always empty.
I found the template of Sonata that is responsible to render this field -> #SonataAdmin/CRUD/list_url.html.twig .
Here is the code
{% extends get_admin_template('base_list_field', admin.code) %}
{% block field %}
{% spaceless %}
{% if value is empty %}
{% else %}
{% if field_description.options.url is defined %}
...
The problem is that value is always empty, i don't know what is this variable; and the documentation is not talking about any field named value.
So you can achieve this by creating a template which simply contains a button with the URL you'd like to link to. See below:
First we define a field on the list view which references a template, the type is null:
->add('foo', null, [
'template' => 'example/foobar.html.twig',
])
Inside our template we've just referenced, we can do the following:
{% extends '#SonataAdmin/CRUD/base_list_field.html.twig' %}
{% block field %}
<a class="btn btn-success" href="http://google.co.uk/">My Link</a>
{% endblock %}
and now you should see the button display as a column on the list view.
It would be nice if the documented suggestion worked as intended, this solution is a work around.
I am a beginner in Symfony, so this question might be simple for those who are more experienced in this framework.
I am building forms and I have several possible types of items in forms. At this moment these are:
text
html
image
However, in the future there will be many more items. Previously, the project generated all items into their place (text, html and image) and hidden those which are not needed for the specific form item (maximum one is needed). However, I intend to avoid adding items I do not need. Since I do not know at the point buildForm of an arbitrary item is running whether it is text, html or image, so at this point all of them are added (I know this is counter-intuitive, but this is a code I try to refactor):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('layoutTypeInput', TextType::class);
$builder->add('blockTypeOutput', EntityType::class, array(
'class' => 'MyPageBundle:BlockTypeOutput',
'choice_label' => 'titleHu',
'required' => false,
'placeholder' => 'Válassz blokk kimenetet!',
'empty_data' => null,
));
$builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));
$builder->add('html', TextareaType::class, $this->getBlockTypeOptions('html'));
$builder->add('image', ImageSelectType::class, $this->getBlockTypeOptions('image'));
$builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPostSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
Now, I have a function which removes the unnecessary elements of a form:
private function removeUnnecessaryItems(\Symfony\Component\Form\FormInterface $form, $key)
{
$keys = ['text', 'html', 'image'];
foreach ($keys as $k) {
if ($key !== $k) $form->remove($k);
}
}
And inside onPostSetData I call it like this:
$this->removeUnnecessaryItems($form, $inputObject->getLayoutTypeIdText());
and finally, in the twig I determine what should be generated into the form:
{% for ioLayoutBlock in form.ioLayoutBlocks %}
<div class="row">
<div class="col-xs-12 col-md-3">
{{ form_errors(ioLayoutBlock.layoutTypeInput) }}
{{ioLayoutBlock.layoutTypeInput.vars.label}}
</div>
{{ form_widget(ioLayoutBlock.layoutTypeInput, {'attr' : {'class':'hidden'}}) }}
<div class="col-xs-12 col-sm-6 col-md-5">
{{ form_errors(ioLayoutBlock.blockTypeOutput) }}
{{ form_widget(ioLayoutBlock.blockTypeOutput, {'attr' : {'class':'blockTypeOutput'}}) }}
</div>
<div class="col-xs-12 col-sm-6 col-md-4">
{% if ioLayoutBlock.text is defined %}
{{ form_errors(ioLayoutBlock.text) }}
{{ form_widget(ioLayoutBlock.text, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_text' }}) }}
{% elseif ioLayoutBlock.html is defined %}
{{ form_errors(ioLayoutBlock.html) }}
{% if layout.layoutType.name == 'userHTML' %}
<div class="input-group ioLayoutBlock_html hidden">
<a class="input-group-addon myAdminForm" target="_blank" data-my-href="page/{{ page.id }}/wysiwyg/{{ layout.id }}"><span class="glyphicon glyphicon-pencil"></span></a>
{{ form_widget(ioLayoutBlock.html, {'attr':{'class':'uniqueInput wysiwyg' }}) }}
</div>
{% else %}
{{ form_widget(ioLayoutBlock.html, {'attr':{'class':'hidden uniqueInput wysiwyg ioLayoutBlock_html' }}) }}
{% endif %}
{% elseif ioLayoutBlock.image is defined %}
{{ form_errors(ioLayoutBlock.image) }}
{{ form_widget(ioLayoutBlock.image, {'attr':{'class':'hidden uniqueInput ioLayoutBlock_image' }}) }}
{% endif %}
</div>
</div>
{% endfor %}
and if I load the page, everything is shown correctly, but unfortunately, when I try to submit the form, it gives the error of
This form should not contain extra fields.
as many times as many form items I have. If I comment out the call on removeUnnecessaryItems inside onPostSetData and subsequently remove the conditionals from the twig, like:
{% if ioLayoutBlock.text is defined %}
then everything works, but that's how it worked before the refactor. Ideally I would like to avoid adding so many unnecessary things at buildForm, but I do not know how can I load up any meaningful data there to determine the type of the item. Alternatively I would like to ensure that the items are successfully removed at the events where I do know their type, without the form errors on submit I described above. So, my question is: How can I avoid generating all kinds of unnecessary stuff into my form without being blocked by submit errors?
My approach would be to give a parameter to your form
$form = $this->createForm(DynamicType::class, $user, [
'layout_type_id' => $layoutTypeIdText,
]),
Then adds the fields depending of the param
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->layout_type = $options['LayoutTypeId'];
// [...]
if ($this->layout_type !== 'text' )
$builder->add('text', TextType::class, $this->getBlockTypeOptions('text'));
// [...]
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// [...]
'layout_type_id' => null,
]);
}
With this approch, the good part is you don't to duplicate your logic in twig, the form get the fields it needs, so you can just render the form using
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
For your specific case you need to avoid adding the items at buildForm, to handle the default empty values at onPreSubmit (as the transformers will not be called if the items are not added at buildForm) and to add the effective items at onPreSubmit.
You were correct using form listeners, they are the way to go for your use case.
I see 2 sub scenarii:
the form data is coming from the Model, you fill it inside the controller
a new item is added to the form, client-side (via the prototype).
I'm getting from your example code that users can't add new items dynamically. If you want to do so, the code is just slightly different.
Remains the first scenario. The trick is not to remove, but to add forms inside the PRE_SET_DATA listener:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('layoutTypeInput', TextType::class);
$builder->add('blockTypeOutput', EntityType::class, array(
'class' => 'MyPageBundle:BlockTypeOutput',
'choice_label' => 'titleHu',
'required' => false,
'placeholder' => 'Válassz blokk kimenetet!',
'empty_data' => null,
));
$builder->addEventListener(FormEvents:: PRE_SET_DATA, array($this, 'onPreSetData'));
// ...
}
public function onPreSetData(FormEvent $event)
{
$data = $event->getData(); // This contains model data (ie., from controller)
$form = $event->getForm();
$type = 'image'; // Read type from your model
$formType = $this->getFormTypeForType($type);
$builder->add($type, formType, $this->getBlockTypeOptions($type));
}
private function getFormTypeForType($type)
{
switch ($type) {
case 'image':
return ImageSelectType::class;
// ...
default:
// Up to you, you can decide on setting a default type or enforcing that the type is correct
throw new \RuntimeException('Unsupported type');
}
}
With that code, you can keep the same Twig.
I'm not sure on what you are trying to do with layoutTypeInput and blockTypeOutput. Maybe we are answering just partially here, don't hesitate on posting the full use case.
I have a form that contains a collection of forms (a Vote with many VoteChoice). The VoteChoiceType is as follows
class VoteChoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('answer', null, array('disabled' => true))
->add('priority', null);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PollBundle\Entity\VoteChoice',
));
}
}
Now in my Controller I create and populate many VoteChoices, setting the answer according to the available choices for the current poll (derived from the URL)
$vote = new Vote();
$vote->setPoll($poll);
foreach ($vote->getPoll()->getPollOptions() as $op) {
$vc = New VoteChoice();
$vote->addVoteChoice($vc->setAnswer($op));
}
So when the Form loads, I want all the options to display only - not to be an actual choice, and then the user can set the priority they want. However, the answer is of every single answer I have in my poll_options table (each Poll has many PollOption, similar to how each Vote has many VoteChoice)
Current twig template
<ul class="voteChoices" data-prototype="{{ form_widget(form.voteChoices.vars.prototype)|e('html_attr') }}">
{% for voteChoice in form.voteChoices %}
<li>{{ form_row(voteChoice.answer) }} {{ form_row(voteChoice.priority) }}</li>
{% endfor %}
</ul>
</div>
<p><button type="submit" class="btn btn-success">Go!</button></p>
{{ form_end(form) }}
I want the voteChoice.answer as a plain text (so it's not part of a dropdown - I know I can disable it in the FormBuilder, but I don't want it to appear as part of a drop-down menu, I just want it as plain text)
If I use voteChoice.answer I get the following symfony error
An exception has been thrown during the rendering of a template ("Catchable Fatal Error: Object of class Symfony\Component\Form\FormView could not be converted to string") in poll\vote.html.twig at line 9.
I have a __toString function in my VoteChoice class.
I want the voteChoice.answer as a plain text (so it's not part of a dropdown - I know I can disable it in the FormBuilder, but I don't want it to appear as part of a drop-down menu, I just want it as plain text)
You can access the current data of your form via form.vars.value (Reference):
{{ voteChoice.vars.value.answer }}
This means that voteChoice.vars.value is an instance of PollBundle\Entity\VoteChoice so you can remove the answer field from your form safely if this is not required by edit.
How could I achieve a form for each list item using csrf and validation in symfony way?
I have a Task entity, which has comments property with a relation OneToMany. So I want to list all tasks and include a hidden comment form for every task. Usually I pass generated forms from controller to template, but how to create them dinamically in template?
{% for task in tasks %}
<taskinfo>
<task comment form>
{% endfor %}
Solved using this way:
In controller:
$forms = array();
foreach($tasks as $task) {
$entity = new TaskComment();
$forms[$task -> getId()] = $this ->createTaskCommentForm($entity, $task -> getId());
}
return $this->render('Bundle:blah:index.html.twig', array(
....
'forms' => $forms
));
An then comment box near every Task box in view:
...task info...
{% for task in tasks %}
<div class="comment-box">
{{ form(forms[task.id]) }}
</div>
{% endfor %}
P.S. I'm using collapsible panels to show/hide each task.
Maybe you need to embed a collection of forms? If so, here and here you can read more.
SETUP:
Twig 1.13.1
PHP 5.4.3
PROBLEM:
I am needing help setting up a custom tag that calls a function that i have already built...
Current Code:
Template Code
{% set stories = get_latest_stories(2, sports) %}
{% for story in stories %}
{{ story.headline }} <br>
{% endfor %}
Controller
$function = new Twig_SimpleFunction('getViewStories', function (section, limit) {
return news_stories::getStories(section,limit);
});
$twig->addFunction($function);
$twig->render("storyList.html");
GOAL:
No with that said I would like to use a custom tag like
{% get_latest_stories 2 sports %}
to call the same function as above. The new way looks nicer and is easier to follow
Why not fetch your stories in the controller instead of the template? This does not seem like a job for the view layer...
So, something like this:
$twig->render("storyList.html", array(
'stories' => news_stories::getStories($section, $limit)
));
Then, you'll have a stories variable available in your template.
here is simple example how to write twig extension
Following code is taken from my unfinished project
function file_import($value){
//some code here
return $value;
}
$app['twig']->addFunction('file_import', new Twig_Function_Function('file_import'));
usage
{{ file_import('value') }}