Dynamically adding form elements with jQuery and Zend_Form - zend-framework

I have a form in which people shall be able to add the same portion of elements with a plus-button, so that something like this is produced:
<div id="person-1" class="person">
<input type="text" name="name-1" id="name-1" />
<input type="text" name="age-1" id="age-1" />
</div>
<!-- as of here, it's JS created -->
<div id="person-2" class="person">
<input type="text" name="name-2" id="name-2" />
<input type="text" name="age-2" id="age-2" />
</div>
<div id="person-3" class="person">
<input type="text" name="name-3" id="name-3" />
<input type="text" name="age-3" id="age-3" />
</div>
I already managed to write jquery-code that allows me to add the same elements once again with a new id (name-1, age-1, name-2, age-2, name-3, age-3, …).
Of course, Zend_Form does not know about name-2 and name-3, so it just drops them when the form contains an error and is displayed again. Neither can I access the value of name-2 with $form->getValue('name-2'). I have to go over raw $this->getRequest()->getPost().
Is there a better method I can use to combine Zend_Form and javascript-based added form elements (of same type like an hardcoded element).
Caveat: In the real problem, it’s select and not input. Found out this could make a difference (with ->setIsArray(true)), but using select would blow up the example code.

What you could do is create a subform container inside your main form and add an X amount of subforms to that container.
For example:
class My_Form extends Zend_Form
{
private $_numPersons = 1;
public function setNumPersons($numPersons)
{
$this->_numPersons = (int) $numPersons;
}
public function init()
{
$container = new Zend_Form_SubForm();
$this->addSubForm($container, 'persons');
for($index = 0; $index < $this->_numPersons; $index++) {
$personForm = new My_PersonForm();
$container->addSubForm($personForm, $index+1);
}
}
}
When rendered, the input fields will have names like persons[1][name]. Note the $index+1, Zend_Form does not allow a form to be named '0'.
Ofcourse, you should only use this method if the amount of person subforms is limited.
Another strategy would be to override the isValid method and use a single My_PersonForm form to validate all the person data.
Sidenote; the above code will only work when you define the numPersons as part of the options set, when creating the form instance. E.g.;
$form = new My_Form(array('numPersons' => 10));

Related

How to edit an item which is created using a single form on Angular2?

I am new to angular2 & I have a form which can add more item to a page (item with name & decription). This works really well, I can keep on adding new item to my list.
However, each of this item has its own edit & delete. How can I edit and delete each of the item using that only 1 form?
<form #formExperiencesRef="ngForm">
<label for="name">name</label>
<input id="name" type="text" name="fruit [(ngModel)]="formData.name">
<label for="description">description</label>
<input id="description" type="text" name="fruit [(ngModel)]="formData.description">
<button (click)="onSubmit(formExperiencesRef.value)"></button>
</form>
This single form is what I use to keep on adding new item. And now I find it hard to edit the item that I created using this. Can someone help me?
Often I would advise to go with a reactive form for all it's benefits, but if your form is this simple a template driven approach can be sufficient.
First of all I see problem in your form. Your name attributes are the same for both fields, this will mean that they are evaluated as one and the same. I would actually name them as for how your formData object looks like, and then just push the form value as is to the array. I'll just use one way binding here for the sake of the editing of item. Also pass the form object in submit.
How we can edit can be done numerous ways. Here we'll utilize the index of your list (assumingly it's an array).
<form #formExperiencesRef="ngForm" (ngSubmit)="onSubmit(formExperiencesRef.value)">
<input name="name" [ngModel]="formData.name">
<input name="description" [ngModel]="formData.description">
<button type="submit">Submit</button>
</form>
Your list:
<div *ngFor="let item of items; let i = index">
{{item.name}} <button (click)="edit(item, i)">Edit</button>
</div>
And in the TS, we can use #ViewChild to reference our form, which I am using to reset the form:
#ViewChild('formExperiencesRef') formExperiencesRef: NgForm;
and your methods for editing and saving a new item:
formData = {};
items = [];
index = null; // used to store current index value of item (if exists)
edit(item, i) {
this.index = i;
this.formData = item;
}
onSubmit(val) {
// check if index exists, if not it's a new item
if(this.index == null) {
this.items.push(val)
} else {
this.items[this.index] = val;
}
// reset index & form
this.index = null;
this.formExperiencesRef.reset();
}
DEMO: http://plnkr.co/edit/ksHp10WwaDg4AQjwDf2d?p=preview
For the future, I really suggest you check out reactive forms, you have tighter control over your form, handle validations easier and a big,big advantage to me is especially if you are dealing with nested components. Reactive forms can be confusing in the beginning, but it's worth it! :)

Submitting list of items with removed first one deletes all items

i have a model that has a list of sub items in it , something like this :
class MyObj {
public string Name {get;set;}
public List<mySubObject> Items {get;set;}
}
and
class mySubObject {
public string Name {get;set;}
public int Order {get;set;}
}
Now, when i render the list with a for loop and editorFor, the html i get is somethign like this :
<input type="text" name="Items[0].Name">
<input type="Number" name="Items[0].Order">
<input type="text" name="Items[1].Name">
<input type="number" name="Items[1].Order">
...
<input type="text" name="Items[9].Name">
<input type="number" name="Items[9].Order">
Now imagine remove the first element from the HTML via jQuery because i no longer want it in my list, and then save the list. The data that goes back is without the first [0] element
and all the elements from 1 to 9 go to the server but the model binding fails and it displays (on the server) that the list of items is null
What am i doing wrong ?
Is this a bug of the default model binder ?
What am i doing wrong ?
Leaving holes in the indices.
Is this a bug of the default model binder ?
No, it's by design. You shouldn't leave any holes in the indices.
One possibility is to recalculate the indices when you remove a row but you will have to write a ton of crap javascript for that.
I would recommend you using a different approach. Instead of using sequential indices use Guids. The approach is detailed in the following blog post by Phil Haack: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx (Non-Sequential indices section).
And Steven Sanderson illustrated a nice little helper in this post that would allow you to very easily achieve this: http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
That you could use like this:
#using(Html.BeginCollectionItem("Items"))
{
for (var i = 0; i < Model.Items.Count; i++)
{
#Html.EditorFor(x => x.Name)
#Html.EditorFor(x => x.Order)
}
}
and it will generate the following:
<input type="hidden" name="Items.Index" value="F769E33C-4D07-4586-8D9C-63D386C641FA" />
<input type="text" name="Items[F769E33C-4D07-4586-8D9C-63D386C641FA].Name">
<input type="Number" name="Items[F769E33C-4D07-4586-8D9C-63D386C641FA].Order">
<input type="hidden" name="Items.Index" value="F4A4A9BB-4427-497E-BFD5-3CDE1F46095B" />
<input type="text" name="Items[F4A4A9BB-4427-497E-BFD5-3CDE1F46095B].Name">
<input type="number" name="Items[F4A4A9BB-4427-497E-BFD5-3CDE1F46095B].Order">
...
Now feel free to add/remove rows as much as you like on the client with javascript without fearing that it would break the model binding of your collection.

Submit the value of a <p> element when an html form is submitted

I have this code: <p class = "foo">Text</p>
And I also have a form: <form action = "XXX.php" method = post></form>
However, how can I get the value of the <p> when I submit it, as the <p> element can be changed.
So what I mean is to be able to post the value of the <p> when the user submits the form, and to be able to access it from that php file with: $_POST['foo'];
Thanks, I have tried to be as clear as possible.
You have to use Javascript for that
A jQuery function that will work
$("form").submit(function(){
var value = $("p").html();
// If foo already exists
if( $("[name=foo]").length > 0 )
{
$("[name=foo]").val(value);
}
else
{
var input = $("<input />", { name : "foo",
value : value ,
type : "hidden" });
$(this).append(input);
}
});
Use
<input type="hidden" value="something" name="something" id="something" />
and when you change inner html of <p> change the value of hidden input.
I think your best bet is to make it an input with readonly enabled, and style to to look like a <p>. It's better then trying to add it to the POST parameters with JavaScript.
Here's a quick example. I bet it could still be improved with a few extra CSS quirks, experiment a bit.
The easiest thing to do is set the value of a hidden form field when you change the contents of your <p>.
Alternatively, you can get its contents and post with JavaScript.
For text you need to use input field:
<input type="text"/>
Form fields should must have an id:
<input type="text" id="pewpew" class="foo"/>
I would go with:
<input type="text" id="pewpew" class="foo" value="default text goes here"/>
OR
Go with different workarounds, like setting form's hidden elements on the fly, etc.
You can create hidden field on the fly and set its value on form submit. Like this:
<form id="form" action="/somewhere" method="post">
<p>Some text</p>
<input type="submit" value="Submit"/>
</form>
<script type="text/javascript">
var form = document.getElementById('form');
form.onsubmit = function()
{
var p = this.getElementsByTagName('p')[0];
if (!document.getElementById('pval'))
{
var pinput = document.createElement('input');
pinput.setAttribute('type', 'hidden');
pinput.setAttribute('id', 'pval');
pinput.setAttribute('name', 'p');
this.appendChild(pinput);
}
document.getElementById('pval').value = p.innerHTML;
return true;
}
</script>
Works, i've tested.

Salesforce - Process variable number of form fields

I've been able to do this in PHP but it's not translating to Salesforce very well.
I have a form to input an Opportunity. It has an Account, a Contact, and a variable number of fields that will be used to create custom objects (For my purposes an Opportunity is a trip, and the custom objects are legs of that trip). The Salesforce controller needs to create a new Opportunity with the Account and Contact (that's the easy part) but then it needs to create a new custom object (Leg__c) for each leg of the trip.
My form looks like this:
<input type="text" name="Account" />
<input type="text" name="Contact" />
<div id="leg0">
<input type="text" name="dep[0]" />
<input type="text" name="arr[0]" />
</div>
<div id="leg1">
<input type="text" name="dep[1]" />
<input type="text" name="arr[1]" />
</div>
...
I'm not even sure where to begin on this one...
Assuming you know how many legs you need, you can simply create a list of them in your visualforce controller:
public list<Leg__c> liLegs {get; set;};
// upon oppty creation:
liLegs = new list<Leg__c>();
for (integer i = 0; i < iNumLegs; i++)
{
liLegs.add(new Leg__c());
}
Then you can just loop over these in your page like so:
<apex:repeat var="v" value="{!liLegs}">
<apex:inputField value="{!v.Dep__c}"/>
<apex:inputField value="{!v.Arr__c}"/>
</apex:repeat>
The input fields will correspond to the fields in each entry in the list, so then in your Save action or whatever you're using you can just insert the list insert liLegs;.
Hope this is of help and I haven't missed the mark, let me know if so! PS. I've just written this code directly in here so it may not be 100% syntactically correct ;)

Symfony dynamic forms

I started with a form, which is made by hand because of it's complexity (it's a javascript modified form, with sortable parts, etc). The problem is that now I need to do the validation, and it's a total mess to do it from scratch in the action using the sfValidator* classes.
So, I am thinking to do it using sfForm so that my form validation and error handling can be done more easier and so I can reuse this form for the Edit and Create pages.
The form is something like this:
<form>
<input name="form[year]"/>
<textarea name="form[description]"></textarea>
<div class="sortable">
<div class="item">
<input name="form[items][0][name]"/>
<input name="form[items][0][age]"/>
</div>
<div class="item">
<input name="form[items][1][name]"/>
<input name="form[items][1][age]"/>
</div>
</div>
</form>
The thing is that the sortable part of the form can be expanded from 2 to N elements on the client side. So that it has variable items quantity which can be reordered.
How can I approach this problem?
Any ideas are welcome,
thank you. :)
I'm doing something similar using embedded forms for the repeating fields.
In your form class you could do something like:
$form_data = $this->getObject();
if (isset($form_data['items']) && is_array($form_data['items']))
{
$items_form = new BaseForm();
foreach ($form_data['items'] as $count => $values)
{
$form = new BaseForm();
$form->widgetSchema['name'] = new sfWidgetFormInputText();
$form->widgetSchema['age'] = new sfWidgetFormInputText();
$items_form->embedForm($count, $form);
}
$this->embedForm('items', $items_form);
$this->validatorSchema['items'] = new sfValidatorPass(array('required' => false));
}