Symfony Forms - dynamically add/remove fields depending on choices - javascript

I'm trying to create a form that maps to an entity of the type "Participant". A participant is in a one-to-one relationship with a 'person'. Adding a participant, I want to first give the option to choose a person already in the database and if the right one doesn't exist, create that person with the participant form.
This works if I do it with two pages/forms. The first one trying to choose an existing person, otherwise open a new page with the different form.
First page:
$form->add('person', AjaxEntityType, [ // EntityType but with select2 ajax
'class' => Person::class,
'remote_route' => 'person_ajax_list'
]);
Second page:
$participant->setPerson(new Person());
$form->add('person', PersonType::class);
// adds PersonType fields to the Participant form
Well, that works, but it's terribly slow and unecessary. What I'd rather want is having BOTH of those shown, where the PersonType form fields (first name, last name, title, company, address, etc.) are automatically populated with the persons data, if one is selected. Otherwise, if no Person is selected and the form is submitted with data entered, a new Person should be created and persisted in the database.
It's sadly not possible to render the 'person' twice, once as a dropdown and once as a PersonType form. So how would I go about achieving what I want, without surreal amounts of JavaScript?
My current solution would be to manually create all the required fields with JavaScript and populate them with the person data I'd get with another Ajax request on a onchange event on the person dropdown, then in the PRE_SUBMIT event of the form, remove the 'person' field and add it again as a PersonType field, check if the entered data corresponds to an existing person or a new one and then act accordingly. There has to be a better solution, right?
Form events have sadly otherwise proven majorly pointless, as it's not possible to attach an event listener to a 'change' event on one of the fields.
Thanks.

Ended up solving it with an unmapped person choice field and javascript to automatically update the data (using ajax).
participant/add.twig:
{% block javascripts %}
<script type="text/javascript">
$(document).ready(function () {
function onTrainerChange() {
let trainerId = $('#participant_person_choose').val();
$.get(Routing.generate('person_data_ajax', { id: trainerId }), function (data) {
$('#participant_person_gender').val(data.gender);
$('#participant_person_title').val(data.title);
$('#participant_person_firstName').val(data.firstName);
$('#participant_person_lastName').val(data.lastName);
$('#participant_person_email').val(data.email);
$('#participant_person_telephone').val(data.telephone);
if (data.company) {
let company = $('#participant_person_company');
company.empty();
company.append(new Option(data.company.text, data.company.id));
company.val(data.company.id);
company.trigger('change');
// manipulate dom directly because of .select('data') bug with select2 >=4.0
}
});
};
let trainer = $('#participant_person_choose');
trainer.change(onTrainerChange);
});
</script>
{% endblock %}
ParticipantController add:
$participant = new Participant($seminar);
$person = $participant->getPerson() ?? new Person();
$participant->setPerson($person);
$form = $this->createParticipantForm($participant)
->add('person_choose', AjaxEntityType::class, [
'mapped' => false,
'class' => Person::class,
'remote_route' => 'person_select_ajax',
'placeholder' => 'form.personCreate',
'label' => 'form.person'
])
->add('person', PersonType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($form->get('reservation')->getData()) {
$participant->setInterested();
}
$personEntered = $form->get('person')->getData();
$personChosen = $form->get('person_choose')->getData();
if ($personChosen) {
$person = $personChosen;
$person->setGender($personEntered->getGender());
$person->setTitle($personEntered->getTitle());
$person->setFirstName($personEntered->getFirstName());
$person->setFirstName($personEntered->getLastName());
$person->setCompany($personEntered->getCompany());
$person->setEmail($personEntered->getEmail());
$person->setTelephone($personEntered->getTelephone());
$participant->setPerson($person);
}
$this->getDoctrine()->getManager()->persist($person);
$this->getDoctrine()->getManager()->persist($participant);
}
PersonController Ajax:
/**
* #Route("/{id}/data", name="person_data_ajax", methods={"GET"}, options={"expose": true})
*/
public function dataAjax(Person $person, PhoneNumberHelper $phonenumberHelper)
{
$arr = [
'id' => $person->id,
'gender' => $person->getGender(),
'title' => $person->getTitle(),
'firstName' => $person->getFirstName(),
'lastName' => $person->getLastName(),
'email' => $person->getEMail(),
'telephone' => $person->getTelephone() ? $phonenumberHelper->format($person->getTelephone(), PhoneNumberFormat::NATIONAL) : null,
'company' => $person->getCompany() ? [
'id' => $person->getCompany()->id,
'text' => $person->getCompany()->__toString()
] : null
];
return new JsonResponse($arr);
}
Hope this can help someone else. Really disappointed with how limited Symfonys Forms are.

Related

Symfony Forms EntityType Select All using JS

On my webpage, I am using a Symfony Form with an EntityType class. I want to create an extra button that selects all items from this dropdown on click. Is this possible using JavaScript / JQuery? Simply changing the automatic generated HTML does not work.
FormType.php:
->add('item', EntityType::class, [
'class' => Item::class,
'choice_label' => function(Item $item) {
return sprintf('%s', $item->getName());
},
'label' => 'Staff',
'multiple' => true,
I think something like that would do the trick :
<!-- Somewhere in your Twig file : -->
<button id="check-all-options">Check all !</button>
document.querySelector('#check-all-options').addEventListener('click', () => {
// Select all the options of your <select> tag
const options = document.querySelectorAll('#form_item option');
// Loop on all the options and pass them to selected ✅
options.forEach(option => option.selected = true);
});
Let me know if it helps :)

laravel getting error on empty request

I get error below while trying to add item to my cart:
Darryldecode \ Cart \ Exceptions \ InvalidItemException
validation.numeric
The error comes from this part of my code:
$customAttributes = [];
if(!empty($request->attr)){
foreach($request->attr as $sub) {
// find the suboption
$sub = Suboption::find($sub);
if (!empty($sub->id)) {
$itemCondition1 = new \Darryldecode\Cart\CartCondition(array(
'name' => $sub->title,
'value' => $sub->price,
'type' => 'additional',
'target' => 'item',
));
array_push($customAttributes, $itemCondition1);
}
}
}
and it take place in here:
Cart::add(array(
'id' => $product->id,
'name' => $product->title,
'price' => $price,
'quantity' => $request->input('quantity'),
'attributes' => $weightArray,
'conditions' => $customAttributes, //here
));
The $customAttributes code supposed to get data IF product does have those information And user chose any of it and suppose to ignore if product doesn't have any of those info or user didn't chose any of it.
Issue is
The code expect data no matter what, product does have that info or not, user selected any or not, even if user select that data still i get error above.
Demo
https://imgur.com/a/KHJqp
any idea why is that?
UPDATE
I figured my issue comes from 'price' => $price, in my add method where i get my $price like:
$price = $product->discounts;
if($price->count() > 0 ) {
foreach($discounts as $disc){
if($disc->value_to >= $mytime) {
$price = $product->price - $disc->amount;
}
}
}else{
$price = $product->price;
}
this part supposed to get product price if there is no discount, and get discounted price if there is.
How I get to this line of code? here is it
SOLVED
I used hidden field and got my data from front-end instead of controller method.

Kendo UI Drop Down loading data from model

I have a standard Order form with a product selection. I am creating a dropdown list as per below where ProductName and ID are properties of the Product reference data model. ProductID is a property of the Order model.
The dropdown is loaded and works correctly when submitting the form.
My problem is when the user opens this form again to view his order. I load the Order model from the database and I can see that the ProductID is correctly loaded back. However, the dropdown selection remains blank. Is this standard behaviour? Perhaps I need to carry out some additional tasks. Doesn't kendo ui automatically translate the Product ID to show the Product Name in the dropdown?
#model int
#(Html.Kendo().DropDownList()
.Name("ProductID")
.OptionLabel(" ")
.DataTextField("ProductName")
.DataValueField("ID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("RefDataClientSelection_Read", "RefDataClient").Type(HttpVerbs.Post); //Set the Action and Controller name
})
.ServerFiltering(true); //If true the DataSource will not filter the data on the client.
})
)
Try using DropDownListFor() as below:
#model int
#(Html.Kendo().DropDownListFor(m => m) // or m => m.ProductId if you have a more complex model
.OptionLabel(" ")
.DataTextField("ProductName")
.DataValueField("ID")
.DataSource(source =>
{
source.Read(read =>
{
read.Action("RefDataClientSelection_Read", "RefDataClient").Type(HttpVerbs.Post); //Set the Action and Controller name
})
.ServerFiltering(true); //If true the DataSource will not filter the data on the client.
})
)

How to save Django "forms.ModelForm" in database after editing it content and passing a javascript variable to it's new entered field

I have a model look like this:
class filled(stateobject):
first = models.IPAddressField(verbose_name=_("First address"))
second = models.CharField(max_length=39, verbose_name=_("Second address")
Embedding these model into Django-forms by following code :
class hostCreateForm(forms.ModelForm):
class Meta:
model = filled
widgets = {
'user': forms.HiddenInput()
}
In views.py:
def address_create(request):
if required.method=='POST':
form = HostCreateForm(request.POST)
if form.is_valid():
host = form.save()
return redirect_to(request, url=host.get_absolute_url())
extra_context = {
'form': HostCreateForm(initial={'user': request.user.pk})
}
return direct_to_template(request, 'networks/Address_form.html', extra_context)
In :Address_form.html
{{ form.as_p }}
When i run above code's it shows me a form containing two fields i.e. first and second. I want to edit this form and add a new char field by named GMT, So that i am able to pass a following javascript variable i.e gmtHours in this new created field
var d = new Date()
var gmtHours = -d.getTimezoneOffset()/60;
Finally when user's click on sumbit button at template it save a form content i.e.
1. first
2. second
3. newly created gmtHours
I am newbie in Django-model. Want your help :)
Add a hidden field to your ModelForm that will hold the date:
class hostCreateForm(forms.ModelForm):
class Meta:
model = filled
widgets = {
'user': forms.HiddenInput()
}
# additional hidden field
datetime = forms.DatetimeField(widget=forms.HiddenInput)
You can then assign it a value in the javascript.

jquery autocomplete in variable length list

Trying to figure out how to do this, using Sanderson begincollectionitems method, and would like to use autocomplete with a field in each row.
I think I see how to add a row with an autocomplete, just not sure the approach for existing rows rendered with guid.
Each row has an of field that the user can optionally point to a record in another table. Each autocomplete would need to work on the html element idfield_guid.
I'm imagining using jquery to enumerate the elements and add the autocomplete to each one with the target being the unique of field for that row. Another thought is a regex that maybe let you enumerate the fields and add autocomplete for each in a loop where the unique field id is handled automatically.
Does that sound reasonable or can you suggest the right way? Also is there a reasonable limit to how many autocomplete on a page? Thanks for any suggestions!
Edit, here's what I have after the help. data-jsonurl is apparently not being picked up by jquery as it is doing the html request to the url of the main page.
$(document).ready(function () {
var options = {
source: function(request, response) {
$.getJSON($(this).data("jsonurl"), request, function (return_data) {
response(return_data.itemList);
});
},
minLength: 2
};
$('.ac').autocomplete(options);
});
<%= Html.TextBoxFor(
x => x.AssetId,
new {
#class = "ac",
data_jsonurl = Url.Action("AssetSerialSearch", "WoTran", new { q = Model.AssetId })
})
%>
And the emitted html look okay to me:
<input class="ac" data-jsonurl="/WoTran/AssetSerialSearch?q=2657" id="WoTransViewModel_f32dedbb-c75d-4029-a49b-253845df8541__AssetId" name="WoTransViewModel[f32dedbb-c75d-4029-a49b-253845df8541].AssetId" type="text" value="2657" />
The controller is not a factor yet, in firebug I get a request like this:
http://localhost:58182/WoReceipt/Details/undefined?term=266&_=1312892089948
What seems to be happening is that the $(this) is not returning the html element but instead the jquery autocomplete widget object. If I drill into the properties in firebug under the 'element' I eventually do see the data-jsonurl but it is not a property of $(this). Here is console.log($this):
You could use the jQuery UI Autocomplete plugin. Simply apply some know class to all fields that require an autocomplete functionality as well as an additional HTML5 data-url attribute to indicate the foreign key:
<%= Html.TextBoxFor(
x => x.Name,
new {
#class = "ac",
data_url = Url.Action("autocomplete", new { fk = Model.FK })
})
%>
and then attach the plugin:
var options = {
source: function(request, response) {
$.getJSON($(this).data('url'), request, function(return_data) {
response(return_data.suggestions);
});
},
minLength: 2
});
$('.ac').autocomplete(options);
and finally we could have a controller action taking two arguments (term and fk) which will return a JSON array of suggestions for the given term and foreign key.
public ActionResult AutoComplete(string term, string fk)
{
// TODO: based on the search term and the foreign key generate an array of suggestions
var suggestions = new[]
{
new { label = "suggestion 1", value = "suggestion 1" },
new { label = "suggestion 2", value = "suggestion 2" },
new { label = "suggestion 3", value = "suggestion 3" },
};
return Json(suggestions, JsonRequestBehavior.AllowGet);
}
You should also attach the autocomplete plugin for newly added rows.

Categories

Resources