(Symfony 4) Adding the filepond class wipes out my file widget and nothing shows up on the back end - symfony

I want to use filepond to make my input forms fancier, but it replaces my file widget with a series of elements of which have no name attribute, so nothing shows up on the back end.
Here is my Symfony form class:
class UserProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', TextType::class)
->add('lastName', TextType::class)
etc...
// This is the widget I am concerned about for this question
->add('idFile', FileType::class, [
'label' => 'File or Files',
'required' => false,
'mapped' => false,
'multiple' => false
])
etc...
->add('save', SubmitType::class, [
'attr' => ['class' => 'save'],
]);
}
}
Here is what it looks like when I create the file widget in twig:
<div class="col-12">
<label for="file-upload" class="mb-2">Photo I.D.</label>
<!-- so right here where I add the filepond class -->
{{ form_widget(form.idFile, { 'id': 'file-upload', 'attr': {'class': 'form-control filepond'} } ) }}
</div>
Here are the filepond libraries I used, included at the bottom of my twig file:
<!-- Load FilePond library -->
<script src="https://unpkg.com/filepond/dist/filepond.min.js"></script>
<!-- <script src="https://unpkg.com/jquery-filepond/filepond.jquery.js">
</script> -->
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
So when the twig file loads, filepond takes over and converts my widget to a series of elements with all kinds of properties to them.
That's fine, I understand that filepond does this to fancy up your forms. But the problem is that on the back-end, nothing shows up.
One more thing to add - none of the elements which filepond creates have a "name" attribute to them, I don't understand why.
So when I look at the files suplerglobal on the back end, my idFile is not there.
Just to re-affirm, when I take out the filepond class, my idFile shows back up in the files suplerglobal at the back-end just fine.
Here is the twig-generated input element:
<div class="col-12">
<label for="file-upload" class="mb-2">Photo I.D.</label>
<input type="file" id="file-upload" name="user_profile[idFile]" class="form-control filepond" />
</div>
If I do an inspect element, among the filepond-generated divs there is a < input type="hidden" name="user_profile[idFile]" value="">
Here is the controller code at the back end:
public function saveProfileCompleteAction(Request $request)
{
$user = $this->getUser();
$form = $this->createForm(UserProfileType::class, $user);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$idFile = $form['idFile']->getData();
If I do a dump($idFile); then it just displays null, and I have checked and made sure that I have selected a file to upload.
How can I use the filepond library and get my file input widget to show up on the back-end?

Related

Symfony 6: embedded form collection, add only new elements and omit existing elements

I have a FormType which contains an embedded collection of another FormType, all is working properly and done according to How to Embed a Collection of Forms.
My embedded FormType is an attachment list, which has title and file upload.
AttachmentType:
builder
->add('title', null, ['label' => 'Title', 'required' => true])
->add('attachmentFile', FileType::class, [
'label' => 'File',
'mapped' => false,
'required' => true,
'constraints' => [
new File([
'maxSize' => '1024k',
])
],
])
The problem is, when editing the parent object, I want to allow new attachments but I don't want to edit previous attachments, as it would be forcing me to upload the same files every time I edit the parent object.
All elements and javascript are done according to the docs linked, I don't find any property for CollectionType that would allow add but wouldn't allow edit or something similar. The javascript seems too dependent on external stuff to allow some modification.
Javascript
// assets/controllers/form-collection_controller.js
import { Controller } from '#hotwired/stimulus';
export default class extends Controller {
static targets = ["collectionContainer"]
static values = {
index : Number,
prototype: String,
}
addCollectionElement(event)
{
const item = document.createElement('li');
item.innerHTML = this.prototypeValue.replace(/__name__/g, this.indexValue);
this.collectionContainerTarget.appendChild(item);
this.indexValue++;
}
}
Twig template
<div {{ stimulus_controller('form-collection') }}
data-form-collection-index-value="{{ form.attachments|length > 0 ? form.attachments|last.vars.name + 1 : 0 }}"
data-form-collection-prototype-value="{{ form_widget(form.attachments.vars.prototype)|e('html_attr') }}"
>
<ul {{ stimulus_target('form-collection', 'collectionContainer') }}></ul>
<button type="button" class="btn btn-secondary" {{ stimulus_action('form-collection', 'addCollectionElement') }}>AƱadir adjunto</button>
</div>

How to render specific form elements Drupal 8

I am using Drupal 8 and would like to customize how form elements are being displayed. Specifically, I don't like how uneditable, populated textfields are displayed as plain text. I would have it being displayed as an editable textfield (or have the text look like it is in an uneditable textfield). I have looked at various hook functions to try and achieve this but nothing seems to work.
I figure the best way to go about this is if I can render the form fields individually myself and then create a twig file that displays the individual fields as I would like them to be displayed. Here is what I would like the twig field to look like:
<div class="from">
{{ form.mail }}
</div>
<div class="message">
{{ form.message }}
</div>
<div class="actions">
{{ form.actions }}
</div>
In your .module file
function your_module_theme($existing, $type, $theme, $path) {
return [
'custom_theme' => [
'variables' => [
'form' => NULL
],
'render element' => 'form',
]
];
}
In your CustomForm.php file
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
[...your fields ...]
return $form
}
In your custom module templates directory
custom-theme.html.twig
{{ form.your_field }}

Render a choice field without form

I often need to render very simple imputs in some of my templates.
I'd like to take advantage of the twig macros & form blocks to render certain HTML inputs without involving the whole Symfony forms machinery.
For example from the controller:
$templateContext = array(
'my_input' = new FormField('my_input', 'choice', array(
'choices' => array('value1', 'value2', 'my_value'),
'value' => 'my_value',
));
);
In the template:
<div>{{ form_widget(my_input) }}</div>
would render:
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
Is there a simple way to do that ?
Eventually I would also like to be able to reuse these fields elsewhere (like we can reuse form types)
There are many ways to go about this. The easiest would be to write the plain HTML into your twig template.
<form method="post">
<div>
<select name="my_input">
<option>value1</option>
<option>value2</option>
<option selected="selected">my_value</option>
</select>
</div>
</form>
And then in your controller read the values back.
$request = $this->getRequest();
if($request->getMethod() == 'POST'){
$data = $request->get('my_input')
//Do whatever you want with $data
}
If you want you re-use the html, you can build it somewhere in your PHP and pass it into Twig whenever you need it; or you can place it in a separate twig template and read that with the {include ''} command in twig.
Here is what I finally ended up with:
Class MyInput {
public static values = array(
'value1',
'value2',
'my_value',
);
}
class MyInputType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choices' => MyInput::$value,
));
}
public function getParent() { return 'choice'; }
public function getName() { return 'my_input_type'; }
}
Field type used from the controller (after having registered it as a service)
public function MyAction(Request $request)
{
$form = $this->createForm('my_input_type');
$form->handleRequest($request);
$templateContext['myForm'] = $form->createView();
// ...
}
Input rendered into the template
<div>{{ form(myForm) }}</div>
To conclude: I've not been able to render an input without the form machinery, but the actual form remains rather simple.
I found my own solution for it since i need to create subforms from existing forms.
create totally empty twig template,
add in it only {{form(form)}}
render this template render()->getContent()
do a preg replace on the result (i do it in js) formView = formView.replace(/<(.*?)form(.*?)>/, '');
Yes yes - i know that regex is not perfect so before someone says it - i say it on my own, change it for youself as it catches "form-group" classes etc as well.
This is just a showcase of my solution

Handle two forms in one Controller with Symfony2

Hey I am saving every page in two different languages on my website. I want to manage my pages with an admin area that I am developing with symfony2 at the moment.
Following controller code is able to display two forms on the same page containing the right data from the database. One form to manage the DE language and another for EN:
View:
<form action="{{ path('admin_about') }}" method="post" {{ form_enctype(formEN) }}>
{{ form_widget(formEN) }}
<button type="submit" class="btn btn btn-warning" naem="EN">Save</button>
</form>
<form action="{{ path('admin_about') }}" method="post" {{ form_enctype(formDE) }}>
{{ form_widget(formDE) }}
<button type="submit" class="btn btn btn-warning" name="DE">Save</button>
</form>
Controller:
public function aboutAction(Request $request)
{
$pageEN = $this->getDoctrine()
->getRepository('MySitePublicBundle:Page')
->findOneBy(array('idName' => 'about', 'lang' => 'EN'));
$pageDE = $this->getDoctrine()
->getRepository('MySitePublicBundle:Page')
->findOneBy(array('idName' => 'about', 'lang' => 'DE'));
if (!$pageDE) {
throw $this->createNotFoundException('About page (DE) not found.');
}
if (!$pageEN) {
throw $this->createNotFoundException('About page (EN) not found.');
}
$formDE = $this->createFormBuilder($pageDE)
->add('title', 'text')
->add('content', 'text')
->getForm();
$formEN = $this->createFormBuilder($pageEN)
->add('title', 'text')
->add('content', 'text')
->getForm();
//Save Form here
return $this->render('MySitePublicBundle:Admin:about.html.twig', array(
'aboutPageDE' => $pageDE, 'aboutPageEN' => $pageEN, 'formDE' => $formDE->createView(), 'formEN' => $formEN->createView(),
));
}
My Question is: How to save the form that has been used out of one controller?
Based on the Forms and Doctrine section of the Symfony2 Docs (or in your case, since you're not using a Form class) --
So where you have //save form here assuming you've set up MySitePublicBundle:Page to save the Title and Content (and has the normal getters/setters).
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
// data is an array with "title" and "content" keys
$data = $form->getData();
// You'll need to have some switch depending on which language you're dealing
// with... (unless its both, then just repeat for $pageDE)
$pageEn->setTitle($data['title']);
$pageEn->setContent($data['content']);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($pageEn);
$em->flush();
}
In your controller you could test if the request contains the forms, such as:
if($this->getRequest()->get('form1')) {
//
} elseif($this->getRequest()->get('form2')) {
//
}

collection Field Type not creating form elements

I'm trying to create a form which will add a new text box every time the 'Add new box' link got clicked.
I read through the following example.
http://symfony.com/doc/current/reference/forms/types/collection.html
Basically I was following the example from the book. But when the page is rendered and I click on the link nothing happens.
Any thoughts?
Thanks.
This is my controller.
public function createAction() {
$formBuilder = $this->createFormBuilder();
$formBuilder->add('emails', 'collection', array(
// each item in the array will be an "email" field
'type' => 'email',
'prototype' => true,
'allow_add' => true,
// these options are passed to each "email" type
'options' => array(
'required' => false,
'attr' => array('class' => 'email-box')
),
));
$form = $formBuilder->getForm();
return $this->render('AcmeRecordBundle:Form:create.html.twig', array(
'form' => $form->createView(),
));
}
This is the view.
<form action="..." method="POST" {{ form_enctype(form) }}>
{# store the prototype on the data-prototype attribute #}
<ul id="email-fields-list" data-prototype="{{ form_widget(form.emails.get('prototype')) | e }}">
{% for emailField in form.emails %}
<li>
{{ form_errors(emailField) }}
{{ form_widget(emailField) }}
</li>
{% endfor %}
</ul>
Add another email
</form>
<script type="text/javascript">
// keep track of how many email fields have been rendered
var emailCount = '{{ form.emails | length }}';
jQuery(document).ready(function() {
jQuery('#add-another-email').click(function() {
var emailList = jQuery('#email-fields-list');
// grab the prototype template
var newWidget = emailList.attr('data-prototype');
// replace the "$$name$$" used in the id and name of the prototype
// with a number that's unique to our emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/\$\$name\$\$/g, emailCount);
emailCount++;
// create a new list element and add it to our list
var newLi = jQuery('<li></li>').html(newWidget);
newLi.appendTo(jQuery('#email-fields-list'));
return false;
});
})
</script>
This problem can be solved by referring to the following link.
https://github.com/beberlei/AcmePizzaBundle
Here you will find the same functionality being implemented.
I've been through this too.
Answer and examples given to this question and the other question I found did not answer my problem either.
Here is how I did it, in some generic manner.
In generic, I mean, Any collection that I add to the form just need to follow the Form template loop (in a macro, for example) and that's all!
Using which convention
HTML is from Twitter Bootstrap 2.0.x
Javascript code is already in a $(document).ready();
Following Symfony 2.0.x tutorial
Using MopaBootstrapBundle
Form Type class
class OrderForm extends AbstractType
{
// ...
public function buildForm(FormBuilder $builder, array $options)
{
// ...
$builder
->add('sharingusers', 'collection', array(
'type' => new UserForm(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required'=> false
));
// ...
}
}
JavaScript
/* In the functions section out of document ready */
/**
* Add a new row in a form Collection
*
* Difference from source is that I use Bootstrap convention
* to get the part we are interrested in, the input tag itself and not
* create a new .collection-field block inside the original.
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
function addTagForm(collectionHolder, newBtn) {
var prototype = collectionHolder.attr('data-prototype');
var p = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);
var newFormFromPrototype = $(p);
var buildup = newFormFromPrototype.find(".controls input");
var collectionField = $('<div class="collection-field"></div>').append(buildup);
newBtn.before(collectionField);
}
/* ********** */
$(document).ready(function(){
/* other initializations */
/**
* Form collection behavior
*
* Inspired, but refactored to be re-usable from Source defined below
*
* Source: http://symfony.com/doc/current/cookbook/form/form_collections.html
*/
var formCollectionObj = $('form .behavior-collection');
if(formCollectionObj.length >= 1){
console.log('run.js: document ready "form .behavior-collection" applied on '+formCollectionObj.length+' elements');
var addTagLink = $('<i class="icon-plus-sign"></i> Add');
var newBtn = $('<div class="collection-add"></div>').append(addTagLink);
formCollectionObj.append(newBtn);
addTagLink.on('click', function(e) {
e.preventDefault();
addTagForm(formCollectionObj, newBtn);
});
}
/* other initializations */
});
The form template
Trick here is that I would have had used the original {{ form_widget(form }} but I needed to add some specific to the view form and I could not make it shorter.
And I tried to edit only the targeted field and found out it was a bit complex
Here is how I did it:
{# All form elements prior to the targeted field #}
<div class="control-collection control-group">
<label class="control-label">{{ form_label(form.sharingusers) }}</label>
<div class="controls behavior-collection" data-prototype="{{ form_widget(form.sharingusers.get('prototype'))|escape }}">
{% for user in form.sharingusers %}
{{ form_row(user) }}
{% endfor %}
</div>
</div>
{{ form_rest(form) }}

Resources