so I'm creating an e-commerce website using symfony and twig. Right now The client can enter manually the quantity he wants of a specific product, but i want that quantity to not exceed the stock we have of that product. So for example if we have 5 chairs, i want him to choose between 1 and 5.
For that i created a dropdown :
<div class="field select-box">
<select name="quantity" data-placeholder="Select your quantity">
{% for i in 1..produit.stock %}
<option value="{{ i }}">{{ i }}</option>
{% endfor %}
</select>
</div>
I want to use the selected value to put it inside a form,, or maybe find another way to set the quantity asked without using the form or even just change the way the form looks because right now it only takes input. Should i generate another form ?
Hopefully i was clear.
Here's what my formType looks like :
class ContenuPanierType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('quantite');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => ContenuPanier::class,
]);
}
}
and here's a bit of code from the controller that creates a cart, add the products and all the information regarding it (quantity, user it belongs to, date)
if(is_null($updateContenuPanier)) {
$contenuPanier = new ContenuPanier();
$contenuPanier->setDate(new Datetime());
$contenuPanier->setPanier($panier);
$contenuPanier->setProduit($produit);
$contenuPanier->setQuantite($request->get("contenu_panier")["quantite"]);
}
This can be done with the ChoiceType. You just need to determine your maximum allowed value and configure the appropriate options.
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// access the entity
$entity = $builder->getData();
// your custom logic to determine the maximum allowed integer value based on $entity
$max = $entity->getStockOnHandMethodName();
$builder->add('quantite', ChoiceType::class, [
'choices' => range(1, $max),
'choice_label' => function ($choice) {
// use the value for the label text
return $choice;
},
// prevent empty placeholder option
'placeholder' => false,
]);
}
I'm almost certain you will still need to verify that the submitted quantity is still valid in your controller action which handles the form submission.
Have a look at Form Input Types here. You can specify the options. This field in particular will vary for each product (Product A = 5 in stock, Product B = 10 in stock).
I doubt a select field is the best here, maybe just use a simple <input type="number">, but that's just my thought on this.
$builder->add('quantite', ChoiceType::class, [
'choices' => [ // instead of this, do a loop to create your options
'Apple' => 1,
'Banana' => 2,
'Durian' => 3,
],
)
```
Related
I am wondering how to solve this problem:
I have form with 4 fields. I want 4th field to be dependent on user status (logged or unlogged). For logged user I will get ID from session but unlogged user should provide username manually.
I dont know which option should i use. Inherit_data, two form types (two much duplicated code) or validation groups based on the submitted data. Any ideas?
Ok, There are several ways to achive that.
Take a Look at FormEvents. In your case it would be FormEvents::PRE_SET_DATA and then read about dynamic forms
I personly prefer to do following
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder->add( ... )
//$builder->add( ... )
//$builder->add( ... )
//each-event with own method, but of cource it can be a callback like in a tutorial above
$builder->addEventListener(FormEvents::PRE_SET_DATA, array(this, 'onPreSetData');
}
and in the same class there is a method onPreSetData
public function onPreSetData ( FormEvent $formEvent )
{
$form = $formEvent->getForm();
$user = $formEvent->getData();
//pseudo-code
if( $user->isLoggedIn() )
{
$form->add('user', HiddenType::class, array(
));
}
else
{
$form->add('user', TextType::class, array(
'label' => 'Username',
'required' => true,
'constraints' => array(
new NotBlank(array('message' => 'please enter username...')),
// new YourCustomValidator(array('message' => 'not enough minerals...')),
)
));
}
}
I personally think a more elegant solution is to pass the user to the form builder from your controller. It's been covered in this answer here:
how to check the user role inside form builder in Symfony2?
you can create form by individual fields like
{{ form_row(form.username) }}
{{ form_row(form.email) }}
{{ form_row(form.phone) }}
{% if(app.user) %}
//your conditional field
{% endif%}
By this way, you have to create submit button as well as csrf token if there
I hope this will quite helpful :)
So say I have a Category and a Product entity, where Category has many Product entities. My Category form builder looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('products', CollectionType::class, array(
'entry_type' => ProductType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'prototype' => true,
'prototype_name' => '__product_prot__'))
->add('save', SubmitType::class))
;
}
And my Product form builder looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('dateAdded', DateType::class)
;
}
What I would like to know is how to set the value of dateAdded so that whenever a new prototype is added, it displays today's date?
Some people have suggested using the Entity __construct() function to create the default, but this doesn't appear to work for prototypes. I've also found the placeholder option, but I'm not sure how to use it so that it always has today's date - i.e this doesn't appear to work:
->add('addDate', DateType::class, array(
'placeholder' => new \DateTime();
))
As the error is: Attempted to call function "DateTime" from the global namespace
Additionally, I've found the prototype_data field in the CollectionType field, but again, I'm not sure how to specify to only put data in a single field, and to make it dynamic.
Can someone tell me the best method to use - and the correct syntax?
Edit:
So my __construct() looks like:
public function __construct()
{
$this->addDate = new \DateTime();
}
Which works fine for the Category entity (if I give it a date field for testing), but not the prototyped Product entity. When I load the prototype into the form, I just get a default date of 1st Jan 2011. The prototype looks like:
{% block _category_products_entry_row %}
<li>
{{ form_row(form.name) }}
{{ form_row(form.dateAdded) }}
</li>
{% endblock %}
Interestingly, I've also found if I load the new form with a Product entity created already in the Category controller, the dateAdded field that appears due to:
{% for product in form.products %}
{{ form_row(product) }}
{% endfor %}
Has today's date as it's default value. This would suggest to me that the loading of the prototypes which is done in the same fashion as the How to Embed a Collection of Forms tutorial - is causing the issue.
To set a default value to a form field, you can use "data" property and use FormEvent to handle form on update. Here is the result:
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('dateAdded', DateType::class, array(
'data' => new \DateTime(),
))
;
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$product = $event->getData();
$form = $event->getForm();
if (!$product) {
return;
}
if ($dateAdded = $product->getDateAdded()) {
$form->add('dateAdded', DateType::class, array(
'data' => $dateAdded,
));
}
});
}
On PRE_SET_DATA, you can override the default value on the dateAdded field with the data you fetched.
For more info, you can visit http://symfony.com/doc/current/reference/forms/types/date.html#data and https://symfony.com/doc/current/form/events.html
Hope it helps
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.
I have a question. So I have this form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$aColors = array();
foreach(Colors::$aNewsColors as $k=>$v){
$aColors['<div style="background-color:'.$v['textCategoryColor'].'">'.$v['name'].'</div>'] = $k;
}
$builder
->add('title', TextType::class)
->add('image',FileType::class, array('data_class' => null))
->add('document',FileType::class, array('data_class' => null))
->add('content',TextareaType::class)
->add('color_id',ChoiceType::class, array('choices' => $aColors,'choices_as_values' => true))
->add('is_top_news',CheckboxType::class)
->add('has_gallery',CheckboxType::class)
->add('save',SubmitType::class, array('label'=> 'Save'))
;
}
The problem is in this color_id, because in the form a get the select box like this :
<option value="1"><div style="background-color:rgb(174,252,202)">Green</div></option>
<option value="2"><div style="background-color:rgb(12,2,43)">Red</div></option>
So the background-color is not interpreted. Can you help me please ?
First things first. The background color is not interpreted by the browser because it's set on a <div> inside an <option>.
With that said, I agree with other people, you should use a javascript library. Things won't necessarily look pretty with a <select> tag.
But your question needs an actual answer, and it turns out that even if you use a javascript library, the approach I'll suggest will help you.
To actually answer your question, make use of 'choice_attr'
$aColors = array();
$choiceAttr = array();
foreach(Colors::$aNewsColors as $k=>$v){
$aColors[$v['name']]=$k;
$choiceAttr[$v['name']]=array(
'style'=>'background-color: ' . $v['textCategoryColor']. ';',
);
}
$builder
->add('title', TextType::class)
->add('image',FileType::class, array('data_class' => null))
->add('document',FileType::class, array('data_class' => null))
->add('content',TextareaType::class)
->add('color_id',ChoiceType::class, array(
'choices' => $aColors,
'choices_as_values' => true,
'choice_attr'=>$choiceAttr,
))
->add('is_top_news',CheckboxType::class)
->add('has_gallery',CheckboxType::class)
->add('save',SubmitType::class, array('label'=> 'Save'))
;
This should output
<option value="1" style="background-color:rgb(174,252,202);">Green</option>
<option value="2" style="background-color:rgb(12,2,43);">Red</option>
And if you do decide to go for a javascript library, you are not limited to setting a "style" in 'choice_attr'. In addition to the "style" attribute, you can use 'choice_attr' to set the "class" attribute, or any attribute you can come up with (E.g. "data-color-value").
You will need to use js.
I think you can loop the div and give a data-color attribute with the necessary information (id, color ...) and retrieve js side
When you click on the select you recovered the value (which is the id) and you course all the div element of the data attribute and find the id.
In view example.html.twig
{% for color in textCategoryColors %}
<div data-color="{color | json_encode}"></div>
{% endfor %}
I am trying to develop a form that lets students select courses from several groups. It is defined as follows:
Courses have a name/description/hours.
Courses belong to groups, which have a name/description/limits on the hours (e.g. you must choose 5 credit hours from this group, or you can only take up to 10 hours from that group)
Course Groups are organized into a Program Offering, which could have other fields
So I want the form to allow students to select courses, but the courses are organized in those groups with instructions about how many to pick. The credit hours part will be validated with custom Validation rules.
Here's an idea of the code I'm thinking of (omitting a lot of the namespaces/Doctrine mapping/etc.).
The entities are sort of like this:
class Offering
{
// has multiple CourseGroups
private $groups;
}
class CourseGroup
{
// which Offering it belongs to
private $offering;
private $name;
private $maxHours;
}
class Course
{
// which CourseGroup it belongs to
private $group;
private $name;
private $hours;
}
// User submits an application with chosen courses from multiple groups
class Application
{
// ...
private $courses;
}
// Joins the Applications to Courses (N to N)
class ApplicationCourse
{
// which Application
private $application;
// which Course
private $course;
}
But I'm trying to figure out how to put this in a form. I don't mind just binding the form to an array and sorting it out later to put it in an Application.
class ApplicationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...other fields
;
// Add the groups of courses to choose
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($offering) {
$form = $event->getForm();
// Here I would like to add 1 EntityType per Offering
// The EntityType allows the user to select multiple Courses within that group
$i = 0;
foreach ($offering->groups as $group) {
// Maybe add the $group as a static block in the twig template here
// Ideally the form should show the group name/description
// I'll probably borrow from this class https://github.com/genemu/GenemuFormBundle/blob/master/Form/Core/Type/PlainType.php
// This adds a group of checkboxes to select a course from this group
$form->add('course_'.$i, 'entity', array(
'class' => 'Acme\DemoBundle\Entity\Course',
'property' => 'name',
'multiple' => true,
'expanded' => true,
'query_builder' => function(EntityRepository $er) use ($group) {
// imagine this ia a query that selects all courses in the $group
return $er->createGroupCoursesQueryBuilder($group);
},
);
$i++;
}
}
);
}
public function getName()
{
return 'task';
}
}
In the end, I want a form that looks like this:
Science: select at least 4 credit hours
[x] SCI100 (2 hours)
[ ] SCI101 (2 hours)
[ ] SCI102 (2 hours)
Math: select at least 4 credit hours
[ ] MTH100 (4 hours)
[x] MTH101 (4 hours)
{GROUP NAME}: {GROUP DESCRIPTION}
[{selected?}] {COURSE NAME} {COURSE HOURS}
Etc.
Will this method work? Is there a better way so that I don't have to bind that form to an array and then re-assemble it after validation?
The solution was actually simple after seeing this answer Symfony 2 Forms entity Field Type grouping
I just customized the rendering of the form. The best part is, I get all the helpful parts of Symfony forms (the data binding), but I can completely customize the rendering.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('courses', 'entity', array(
'class' => 'MyMainBundle:Course',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
->add('save', 'submit')
;
}
There's a cool trick in the twig template to just rendering it myself. I did have to pass in the groups of courses.
The trick is to use {% do form.courses.setRendered %}
{{ form_start(form) }}
{{ form_errors(form) }}
{% for group in academics.courseGroups %}
<section>
<h2>
{{ group.name }}
</h2>
<p>{{ group.description }}</p>
{% for course in group.courses %}
<div class="form-group">
<div class="course">
<div class="checkbox">
<label for="my_mainbundle_application_course_courses_{{ course.id }}">
<input type="checkbox"
id="my_mainbundle_application_course_courses_{{ course.id }}"
name="my_mainbundle_application_course[courses][]"
value="{{ course.id }}"
{% if course.id in form.courses.vars.value and form.courses.vars.value[course.id] %}checked="checked"{% endif %}>
<span class="name">{{ course.name }}</span>
<span class="subject">({{ course.subject }})</span>
-
<span class="credits">{{ course.credits }} hours</span>
</label>
</div>
</div>
</div>
{% endfor %}
</section>
{% endfor %}
{% do form.courses.setRendered %}
{{ form_row(form.save) }}
{{ form_end(form) }}