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

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! :)

Related

Remove an object from list of child objects in Model by checkbox unset in Fluid

I have two Extbase models with 1:n relation. Parent relates to Children via ObjectStorage.
What I want to achieve: editAction($parent) which shows a list of all Children as entries with a checkbox (checked by default). User is allowed to unset any checkbox, submit a form and corresponding Children should be removed from Parent relation.
What I've done so far.
In a fluid I iterate over objects and output the checkboxes like this:
<f:for each="{parent.children}" as="child" iteration="iteration">
<f:form.checkbox property="children.{iteration.index}" value="{child}" />
<label>{child.title}</label>
</f:for>
This generates following HTML, which seems okay for me:
<input type="hidden" name="tx_myext_plugin[parent][children][0][__identity]" value="">
<input type="checkbox" name="tx_myext_plugin[parent][children][0][__identity]" value="135" checked="checked">
<label>child0-title</label>
<input type="hidden" name="tx_myext_plugin[parent][children][1][__identity]" value="">
<input type="checkbox" name="tx_myext_plugin[parent][children][1][__identity]" value="136" checked="checked">
<label>child1-title</label>
...
But when I unset the 2nd checkbox (uid=136) and submit a form, I get the following Exception
#1297759968: Exception while property mapping at property path "children.1": The identity property "" is no UID.
Which also seems logical, because there is that hidden input, that submits an empty value.
I think, I can hook somewhere in MVC-process and just filter out the entries with empty __identity, but is there a more elegant (e.g. best practice) way?
TYPO3 7.6.11
In your controller, you can make an initialize*Action() function. There you can filter out your empty values so that only values with an identity exsist.
public function initializeSaveAction()
{
if ($this->request->hasArgument('parent')) {
$parent = $this->request->getArgument('parent');
foreach ($parent['children'] as $key => $child) {
if (!$child['__identity']) unset($parent['children'][$key]);
}
$this->request->setArgument('parent', $parent);
}
}
Now your saveAction is called after the initializeSaveAction and has only the selected children keept.

Using same form for insert and edit

How can I use the same form for inserting and editing a document in Meteor.
For insert I use empty form without variable:
<template name="frmOrganization">
// no doc variable
<form class="form-horizontal" role="form">
...
<input type="text" class="form-control" id="Name">
And for update I use form with variable:
<template name="frmOrganization">
{{#with organization}} // doc variable
<form class="form-horizontal" role="form">
...
<input type="text" class="form-control" id="Name" value="{{Name}}">
(Using Meteor 0.9.3 + Iron-Router)
I had the same issue and the workaround I used was as follows (seems to be more of a hack but I get to avoid having multiple forms)
<template name="frmOrganization">
// no doc variable
<form class="form-horizontal" role="form">
...
<input type="text" class="form-control" id="Name" value={{name}}>
{{#if name}}
<input type="submit" value="Edit"/>
<input type="submit" value="Delete"/>
{{else}}
<input type="submit" value="Add"/>
{{/if}}
...
In my router.js file
this.route('frmOrganization', {
path: '/organisation/:_id/edit',
data: function() { return Organisation.findOne(this.params._id); }
});
All seems pretty standard, but then for my Add link, I use:
Add
Which will create he following link:
/organisation/null/edit
The null page gives you your Add page and an ID would give you your edit page.
-- Meteor Noob
A simple way would be to use an upsert with the _id of the organization document as the first argument. If nothing is found in the collection when you try to upsert, then it will insert the data into a new document (second argument).
However, there are some packages which can make this task much easier:
autoform, simple-schema and collection2
(I would link to the others but lack the reputation)
Firstly define a schema and apply it to the collection as follows:
Pages = new Meteor.Collection('pages');
PagesSchema = new SimpleSchema({
title: {
type: String,
label: 'Title',
max: 100
},
body: {
type: String,
label: 'Body',
max: 1000
}
});
Pages.attachSchema(PagesSchema);
Then it's possible to simply switch these two templates for an insert or update:
{{> quickForm collection="Pages" id="page-form" type="insert"}}
{{> quickForm collection="Pages" doc=page id="page-form" type="update"}}
(With 'page' being the document itself available in the template)
These handlebars helpers will produce the entire form, and you can even validate with them.
Say that you have a create form at path /my-form. This is fairly straightforward. It creates a new document in your database with an :_id of, say, XYZ123.
Now you want an edit form.
You create a link that goes to /my-form/XYZ123
You've created a path with the document :_id as a parameter.
You then set your route to read the :_id parameter of your path as data. (Look at the Discover Meteor book for an example)
Now add code to your existing form that will take that :_id from the path, do a lookup for the document, and pre-populate the values in the form with the values it finds from the database based off of that :_id.
And change the submit button to do an update instead of insert.
As for delete, that's simple. No extra form needed. On the edit form just create a big red delete button that will delete the document with that :_id.
Write your JS so that if there is a parameter in the path, make the delete button visible and turn the submit button from insert into update.

Trigger validation of all fields in Angular Form submit

I'm using this method: http://plnkr.co/edit/A6gvyoXbBd2kfToPmiiA?p=preview to only validate fields on blur. This works fine, but I would also like to validate them (and thus show the errors for those fields if any) when the user clicks the 'submit' button (not a real submit but a data-ng-click call to a function)
Is there some way to trigger validation on all the fields again when clicking that button?
What worked for me was using the $setSubmitted function, which first shows up in the angular docs in version 1.3.20.
In the click event where I wanted to trigger the validation, I did the following:
vm.triggerSubmit = function() {
vm.homeForm.$setSubmitted();
...
}
That was all it took for me. According to the docs it "Sets the form to its submitted state." It's mentioned here.
I know, it's a tad bit too late to answer, but all you need to do is, force all forms dirty. Take a look at the following snippet:
angular.forEach($scope.myForm.$error.required, function(field) {
field.$setDirty();
});
and then you can check if your form is valid using:
if($scope.myForm.$valid) {
//Do something
}
and finally, I guess, you would want to change your route if everything looks good:
$location.path('/somePath');
Edit: form won't register itself on the scope until submit event is trigger. Just use ng-submit directive to call a function, and wrap the above in that function, and it should work.
In case someone comes back to this later... None of the above worked for me. So I dug down into the guts of angular form validation and found the function they call to execute validators on a given field. This property is conveniently called $validate.
If you have a named form myForm, you can programmatically call myForm.my_field.$validate() to execute field validation. For example:
<div ng-form name="myForm">
<input required name="my_field" type="text" ng-blur="myForm.my_field.$validate()">
</div>
Note that calling $validate has implications for your model. From the angular docs for ngModelCtrl.$validate:
Runs each of the registered validators (first synchronous validators and then asynchronous validators). If the validity changes to invalid, the model will be set to undefined, unless ngModelOptions.allowInvalid is true. If the validity changes to valid, it will set the model to the last available valid $modelValue, i.e. either the last parsed value or the last value set from the scope.
So if you're planning on doing something with the invalid model value (like popping a message telling them so), then you need to make sure allowInvalid is set to true for your model.
You can use Angular-Validator to do what you want. It's stupid simple to use.
It will:
Only validate the fields on $dirty or on submit
Prevent the form from being submitted if it is invalid
Show custom error message after the field is $dirty or the form is submitted
See the demo
Example
<form angular-validator
angular-validator-submit="myFunction(myBeautifulForm)"
name="myBeautifulForm">
<!-- form fields here -->
<button type="submit">Submit</button>
</form>
If the field does not pass the validator then the user will not be able to submit the form.
Check out angular-validator use cases and examples for more information.
Disclaimer: I am the author of Angular-Validator
Well, the angular way would be to let it handle validation, - since it does at every model change - and only show the result to the user, when you want.
In this case you decide when to show the errors, you just have to set a flag:
http://plnkr.co/edit/0NNCpQKhbLTYMZaxMQ9l?p=preview
As far as I know there is a issue filed to angular to let us have more advanced form control. Since it is not solved i would use this instead of reinventing all the existing validation methods.
edit: But if you insist on your way, here is your modified fiddle with validation before submit. http://plnkr.co/edit/Xfr7X6JXPhY9lFL3hnOw?p=preview
The controller broadcast an event when the button is clicked, and the directive does the validation magic.
One approach is to force all attributes to be dirty. You can do that in each controller, but it gets very messy. It would be better to have a general solution.
The easiest way I could think of was to use a directive
it will handle the form submit attribute
it iterates through all form fields and marks pristine fields dirty
it checks if the form is valid before calling the submit function
Here is the directive
myModule.directive('submit', function() {
return {
restrict: 'A',
link: function(scope, formElement, attrs) {
var form;
form = scope[attrs.name];
return formElement.bind('submit', function() {
angular.forEach(form, function(field, name) {
if (typeof name === 'string' && !name.match('^[\$]')) {
if (field.$pristine) {
return field.$setViewValue(field.$value);
}
}
});
if (form.$valid) {
return scope.$apply(attrs.submit);
}
});
}
};
});
And update your form html, for example:
<form ng-submit='justDoIt()'>
becomes:
<form name='myForm' novalidate submit='justDoIt()'>
See a full example here: http://plunker.co/edit/QVbisEK2WEbORTAWL7Gu?p=preview
Here is my global function for showing the form error messages.
function show_validation_erros(form_error_object) {
angular.forEach(form_error_object, function (objArrayFields, errorName) {
angular.forEach(objArrayFields, function (objArrayField, key) {
objArrayField.$setDirty();
});
});
};
And in my any controllers,
if ($scope.form_add_sale.$invalid) {
$scope.global.show_validation_erros($scope.form_add_sale.$error);
}
Based on Thilak's answer I was able to come up with this solution...
Since my form fields only show validation messages if a field is invalid, and has been touched by the user I was able to use this code triggered by a button to show my invalid fields:
// Show/trigger any validation errors for this step
angular.forEach(vm.rfiForm.stepTwo.$error, function(error) {
angular.forEach(error, function(field) {
field.$setTouched();
});
});
// Prevent user from going to next step if current step is invalid
if (!vm.rfiForm.stepTwo.$valid) {
isValid = false;
}
<!-- form field -->
<div class="form-group" ng-class="{ 'has-error': rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched && rfi.rfiForm.stepTwo.Parent_Suffix__c.$invalid }">
<!-- field label -->
<label class="control-label">Suffix</label>
<!-- end field label -->
<!-- field input -->
<select name="Parent_Suffix__c" class="form-control"
ng-options="item.value as item.label for item in rfi.contact.Parent_Suffixes"
ng-model="rfi.contact.Parent_Suffix__c" />
<!-- end field input -->
<!-- field help -->
<span class="help-block" ng-messages="rfi.rfiForm.stepTwo.Parent_Suffix__c.$error" ng-show="rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched">
<span ng-message="required">this field is required</span>
</span>
<!-- end field help -->
</div>
<!-- end form field -->
Note: I know this is a hack, but it was useful for Angular 1.2 and earlier that didn't provide a simple mechanism.
The validation kicks in on the change event, so some things like changing the values programmatically won't trigger it. But triggering the change event will trigger the validation. For example, with jQuery:
$('#formField1, #formField2').trigger('change');
I like the this approach in handling validation on button click.
There is no need to invoke anything from controller,
it's all handled with a directive.
on github
You can try this:
// The controller
$scope.submitForm = function(form){
//Force the field validation
angular.forEach(form, function(obj){
if(angular.isObject(obj) && angular.isDefined(obj.$setDirty))
{
obj.$setDirty();
}
})
if (form.$valid){
$scope.myResource.$save(function(data){
//....
});
}
}
<!-- FORM -->
<form name="myForm" role="form" novalidate="novalidate">
<!-- FORM GROUP to field 1 -->
<div class="form-group" ng-class="{ 'has-error' : myForm.field1.$invalid && myForm.field1.$dirty }">
<label for="field1">My field 1</label>
<span class="nullable">
<select name="field1" ng-model="myresource.field1" ng-options="list.id as list.name for list in listofall"
class="form-control input-sm" required>
<option value="">Select One</option>
</select>
</span>
<div ng-if="myForm.field1.$dirty" ng-messages="myForm.field1.$error" ng-messages-include="mymessages"></div>
</div>
<!-- FORM GROUP to field 2 -->
<div class="form-group" ng-class="{ 'has-error' : myForm.field2.$invalid && myForm.field2.$dirty }">
<label class="control-label labelsmall" for="field2">field2</label>
<input name="field2" min="1" placeholder="" ng-model="myresource.field2" type="number"
class="form-control input-sm" required>
<div ng-if="myForm.field2.$dirty" ng-messages="myForm.field2.$error" ng-messages-include="mymessages"></div>
</div>
</form>
<!-- ... -->
<button type="submit" ng-click="submitForm(myForm)">Send</button>
I done something following to make it work.
<form name="form" name="plantRegistrationForm">
<div ng-class="{ 'has-error': (form.$submitted || form.headerName.$touched) && form.headerName.$invalid }">
<div class="col-md-3">
<div class="label-color">HEADER NAME
<span class="red"><strong>*</strong></span></div>
</div>
<div class="col-md-9">
<input type="text" name="headerName" id="headerName"
ng-model="header.headerName"
maxlength="100"
class="form-control" required>
<div ng-show="form.$submitted || form.headerName.$touched">
<span ng-show="form.headerName.$invalid"
class="label-color validation-message">Header Name is required</span>
</div>
</div>
</div>
<button ng-click="addHeader(form, header)"
type="button"
class="btn btn-default pull-right">Add Header
</button>
</form>
In your controller you can do;
addHeader(form, header){
let self = this;
form.$submitted = true;
...
}
You need some css as well;
.label-color {
color: $gray-color;
}
.has-error {
.label-color {
color: rgb(221, 25, 29);
}
.select2-choice.ui-select-match.select2-default {
border-color: #e84e40;
}
}
.validation-message {
font-size: 0.875em;
}
.max-width {
width: 100%;
min-width: 100%;
}
To validate all fields of my form when I want, I do a validation on each field of $$controls like this :
angular.forEach($scope.myform.$$controls, function (field) {
field.$validate();
});

Dynamically adding form elements with jQuery and Zend_Form

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));

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));
}