I don't know whether this is a symphony or a sonata admin bundle issue.
I have my main MultimediaAdmin class which has multiple embedded FileAdmin entries.
class MultimediaAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Files')
->add('files','sonata_type_collection',
array('label' => 'Multimedia Files',
'btn_add' => 'Add File',
'by_reference' => 'false',
'type_options' => array('delete' => false)
), array(
'edit' => 'inline',
'template' => 'MyMultimediaBundle:Multimedia:horizontal.fields.html.twig'
)
)
->end()
->with('Tags')
->add('tags')
->end()
;
}
}
I have a custom template styling the appearance of the embedded FileAdmin form which among few fields has one that shows preview of the uploaded media when editing.
/* horizontal.fields.html.twig */
<fieldset>
<div class="sonata-ba-collapsed-fields">
{% for nested_group_field_name, nested_group_field in form.children %}
{% for field_name, nested_field in nested_group_field.children %}
<div class="control-group">
<label class="control-label" for="nested_field.vars['sonata_admin'].admin.trans(nested_field.vars.label" {{ nested_field.vars['required'] ? 'class="required"' : '' }}>{{ nested_field.vars['sonata_admin'].admin.trans(nested_field.vars.label) }}</label>
<div class="controls">
{% if sonata_admin.field_description.associationadmin.formfielddescriptions[field_name] is defined %}
{{ form_widget(nested_field, {
'inline': 'natural',
'edit' : 'inline'
}) }}
{% set dummy = nested_group_field.setrendered %}
{% else %}
{{ form_widget(nested_field) }}
{% endif %}
{% if sonata_admin.field_description.help %}
<span class="help-block sonata-ba-field-help">{{ sonata_admin.admin.trans(sonata_admin.field_description.help, {}, sonata_admin.field_description.translationDomain)|raw }}</span>
{% endif %}
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</fieldset>
Here's FileAdmin which has image preview added to it when editing to show the thumbnail
class FileAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$media = $this->getSubject();
// use $fileFieldOptions so we can add other options to the field
$fileFieldOptions = array('required' => false,'label' => 'Files', 'attr' => array("multiple" => "multiple"), 'by_reference' => false);
if ($media && ($webPath = $media->getWebPath())) {
$fileFieldOptions['help'] = '<img src="'.$webPath.'" class="admin-preview" />';
}
$formMapper
->add('title','text',array('label'=>'Title'))
->add('abstract','textarea',array('label'=>'Abstract'))
->add('language')
->add('format')
->add('file', 'file', $fileFieldOptions)
->add('quality')
;
}
}
The custom form works perfectly in terms of styling but the only problem I have is, it doesn't show the image preview when editing embedded files in the multimedia form. When I go to edit file directly, not under MultimediaAdmin, the image preview works perfectly. Where could I be going wrong?
Related
I try to follow this link to install and create a form with CraueFormFlowBundle
CraueFormFlowBundle tutorial
i create the file
/src/AppBundle/Form/InterventoFlow.php
namespace AppBundle/Form;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowInterface;
class InterventoFlow extends FormFlow {
protected function loadStepsConfig() {
return array(
array(
'label' => 'wheels',
'form_type' => 'AppBundle\Form\InterventoForm',
),
array(
'label' => 'engine',
'form_type' => 'AppBundle\Form\InterventoForm',
'skip' => function($estimatedCurrentStepNumber, FormFlowInterface $flow) {
return $estimatedCurrentStepNumber > 1 && !$flow->getFormData()->canHaveEngine();
},
),
array(
'label' => 'confirmation',
),
);
}
}
after i create
/src/AppBundle/Form/InterventoForm.php
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace AppBundle/Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
class InterventoForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
switch ($options['flow_step']) {
case 1:
$validValues = array(2, 4);
$builder->add('numberOfWheels', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', array(
'choices' => array_combine($validValues, $validValues),
'placeholder' => '',
));
break;
case 2:
// This form type is not defined in the example.
$builder->add('engine', 'TextType', array(
'placeholder' => 'prova',
));
break;
}
}
public function getBlockPrefix() {
return 'createIntervento';
}
}
after i create the service
services:
app.form.InterventoFlow:
class: AppBundle\Form\InterventoFlow
parent: craue.form.flow
and the controller
public function interventoAction(Request $request)
{
$formData = new Validate(); // Your form data class. Has to be an object, won't work properly with an array.
$flow = $this->get('AppBundle.form.InterventoFlow'); // must match the flow's service id
$flow->bind($formData);
// form of the current step
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
// flow finished
$em = $this->getDoctrine()->getManager();
$em->persist($formData);
$em->flush();
$flow->reset(); // remove step data from the session
return $this->redirect($this->generateUrl('home')); // redirect when done
}
}
return $this->render('interventi/intervento.html.twig', array(
'form' => $form->createView(),
'flow' => $flow,
));
}
and my twig file
{% extends 'base.html.twig' %}
{% block body %}
<div>
Steps:
{% include '#CraueFormFlow/FormFlow/stepList.html.twig' %}
</div>
{{ form_start(form) }}
{{ form_errors(form) }}
{% if flow.getCurrentStepNumber() == 1 %}
<div>
When selecting four wheels you have to choose the engine in the next step.<br />
{{ form_row(form.numberOfWheels) }}
</div>
{% endif %}
{{ form_rest(form) }}
{% include '#CraueFormFlow/FormFlow/buttons.html.twig' %}
{{ form_end(form) }}
{% endblock %}
{% stylesheets '#CraueFormFlowBundle/Resources/assets/css/buttons.css' %}
<link type="text/css" rel="stylesheet" href="{{ asset_url }}" />
{% endstylesheets %}
always give me error
Attribute "autowire" on service "app.form.intervento" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly in C:\xampp\htdocs\Myprject\app/config\services.yml (which is being imported from "C:\xampp\htdocs\Myprject\app/config\config.yml").
and if i comment
parent: craue.form.flow
You have requested a non-existent service "app.form.interventoFlow".
Try to dalete _defaults section from your service.yml file.
I am making the form which allow a user to select a img.
Symfony2.8 with sonataMediaBundle/
$form = $this->createFormBuilder($form)
->add('media',EntityType::class,array(
'expanded' => true,
'class' => "Application\Sonata\MediaBundle\Entity\Media"
}))
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
in twig
{{form_widget(form.media)}}
However it shows the radio buttons with only the name of img.
○AAA.jpg ○BBB.jpg ○CCC.jpg ○DDD.jpg ('○' is radio button)
It is not good design for users.
I want to show the thumbnail of imgs here.
Is there good way to do this?
The easiest way i can imagine is to actually check the radio button with javascript.
Your Controller :
//src/AppBundle/Controller/YourController.php
public function yourAction()
{
$em = $this->getDoctrine()->getManager();
$medias = $em->getRepository('AppBundle:Media')->findAll();
$entity = new YourEntity();
$form = $this->createForm(YourEntityType::class, $entity);
if ($form->isSubmitted() && $form->isValid()) {
//... your logic
}
return $this->render('AppBundle::template.html.twig', array(
'form' => $form->createView(),
'medias' => $medias
));
}
Then in your twig file
{% for media in medias %}
<img class="to-select" src="{{ media.pathToThumbnail }}" data-id="{{ media.id }}" />
{% endfor %}
{{ form_start(form) }}
<!-- assuming you are using bootstrap -->
<div class="hidden">{{ form_widget(form.media) }}</div>
{{ form_widget(form.submit) }}
{{ form_end(form) }}
{% block javascripts %}
<script>
//assuming you use jquery
$('.to-select').click(function () {
var id = '#form_name_media_' + $(this).data('id');
var media = $(id);
if (media.is(':checked')) {
media.prop('checked', false);
} else {
media.prop('checked', true);
}
});
</script>
{% endblock javascripts %}
I would want a form for my entity « X ». This entity own a relationship OneToMany with many entities of type « X ». It's a relationship parent <-> children.
When I create my form simply, it works.
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"by_reference" => false));
}
}
Then, I would like add easily new entity in my collection with the option « allow_add », and used the prototype to add in javascript. This is my form with the « allow_add » option.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"allow_add" => true,
"by_reference" => false));
}
When I execute with or without called the prototype, I have an webserver error. It's XDebug which kick my request because the recursive call is too big. There are cascade call.
What's the best solution to resolve my problem ?
I can't really say where your problem is, but should be somewhere around javascript and the collection prototype. Also, when you allow items to be added, you usually allow to allow_delete too.
Take a look at my example:
$builder->add('book', 'collection', array(
'type' => new BookType(),
'allow_add' => true,
'allow_delete' => true,
'prototype_name' => '__prototype__',
'by_reference' => false,
'error_bubbling' => false
);
To the template where you render form, include this:
{% block javascript %}
{{ parent() }}
{% javascripts
'#ProjectSomeBundle/Resources/public/js/form/collection.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
And the collection.js file holds this:
$(function($) {
$addButton = $('button.add');
$collection = $addButton.parent().children().first();
$addButton.click(function () {
var prototype = $($collection.data('prototype').replace(/__prototype__/g, function() { return (new Date()).getTime(); }));
prototype.appendTo($collection.children('ul'));
});
$('body').on('click', 'button.remove', function () {
$(this).parent().remove();
});
});
Also, you should be using those customised collection widgets, which you pass to twig. Nothing fancy, it will be rendered as unordered list with items, to make items easily accessible.
config.yml:
twig:
form:
resources:
- 'ProjectSomeBundle:Form:fields.html.twig'
fields.html.twig:
{% extends 'form_div_layout.html.twig' %}
{% block collection_widget %}
{% import "ProjectSomeBundle:Macro:macro.html.twig" as macro %}
{% spaceless %}
<div class="collection">
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': block('prototype_widget') }) %}
{% endif %}
<div {{ block('widget_container_attributes') }}>
<ul>
{% for row in form %}
{{ macro.collection_item_widget(row) }}
{% endfor %}
</ul>
{{ form_rest(form) }}
</div>
<div class="clear"></div>
<button type="button" class="add">{% trans %}Add{% endtrans %}</button>
</div>
<div class="clear"></div>
{% endspaceless %}
{% endblock collection_widget %}
{% block prototype_widget %}
{% spaceless %}
{{ macro.collection_item_widget(prototype) }}
{% endspaceless %}
{% endblock prototype_widget %}
You can notice it uses macro, so here it is:
macro.html.twig
{% macro collection_item_widget(fields) %}
<li>
{% set fieldNum = 1 %}
{% for field in fields %}
<div class="field_{{ fieldNum }}">
{{ form_label(field) }}
{{ form_errors(field) }}
{{ form_widget(field) }}
</div>
{% set fieldNum = fieldNum + 1 %}
{% endfor %}
<button type="button" class="remove">{% trans %}Delete{% endtrans %}</button>
<div class="clear"></div>
</li>
{% endmacro %}
This is a full example, I hope you find it useful and works for you.
I ran into the same issue (my web server crashes, as well, due to too many recursive calls). My quick workaround was to simply create a dummy (cloned) type that doesn't contain the recursive field (this works for me since I was interested only into the 1st level children).
So, if this scenario is applicable to you, as well, you could change your code as follows:
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XTypeWithoutChildren(),
"by_reference" => false));
}
}
class XTypeWithoutChildren extends XType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"));
}
}
Is there a way to render a custom attribute in the KNP Menu Bundle, something like this:
$menu = $factory->createItem(Role::ROLE_PROGRAM_EVENT_PLANNER, array(
'route' => 'show_form_events',
'attributes' => array('class' => 'menu pe_planner'),
'extra' => array(
'content' => 'my custom content'
)
));
I have overriden the linkElement by adding an extra div after the a-tag. In that div I would like to render extra content
{% block linkElement %}
{% import _self as knp_menu %}
<a href="{{ item.uri }}"{{ knp_menu.attributes(item.linkAttributes) }}>{{ block('label') }}</a>
{% if item.hasChildren == false %}
<div class="custom">{{ item.getExtra('content') }}</div>
{% endif %}
{% endblock %}
Actually i had to do quite the same for today ;)
MenuBuilder
$menu->addChild(
'Dashboard',
array(
'route' => 'dashboard',
'attributes' => array(
'class' => 'navigation-entry'
),
'extras' => array(
'icon' => '6'
)
)
);
menuTemplate
{% block linkElement %}
{% import "knp_menu.html.twig" as macros %}
<a href="{{ item.uri }}"{{ macros.attributes(item.linkAttributes) }}>
<span class="icon">{{ item.getExtra('icon') }}</span>
<span class="entry">{{ block('label') }}</span>
</a>
{% endblock %}
Dont be confused be the icon content because i use an icon font.
I have a collection widget in my form. That's displayed like:
Teams 0 player1 inputfield
1 player2 inputfield
I would like to not display the word 'teams' and the '0' and the '1'.
I've got this block in my fields.html.twig template, but not really sure how to edit this.
{% block collection_widget %}
{% spaceless %}
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': form_row(prototype) }) %}
{% endif %}
{{ block('form_widget') }}
{% endspaceless %}
{% endblock collection_widget %}
{% block form_label %}
{% spaceless %}
<div class="hidden">
{{ block('generic_label') }}
</div>
{% endspaceless %}
{% endblock form_label %}
ChallengeType form:
class ChallengeType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('teams', 'collection', array(
'type' => new TeamType(),
'allow_add' => true
))
->add('place')
->add('date');
}
public function getName()
{
return 'challenge';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Tennisconnect\DashboardBundle\Entity\Challenge');
}
}
Thx.
Those lables are created in form_label block. I usually wrap them in a div and set them hidden when needed.
Edit:
There is a better solution :).
Change collection section of the ChallengeType.php with following
->add('teams', 'collection', array(
'type' => new TeamType(),
//label for Teams text
'attr' => array('class' => 'team-collection'),
//label for each team form type
'options' => array(
'attr' => array('class' => 'team-collection')
),
'allow_add' => true
))
Now those unwanted labels will have team-collection class. In your css file you can set display:none for label.team-collection. No need to change form theme block definition.