Angular2 model-based parent/children form - forms

I'm a newbie with Angular2 (beta1) and I'd like to implement a sort of simple editable grid, built of 2 components. Here I use two fake-data components to keep things simple. They are (see this Plunker: http://plnkr.co/edit/5cZfLTIlhLc82wWV4PQI):
the parent component, named contact. Say it represents a contact with a name.
the child component, named entry. Say it represents an entry for a contact, where each contact can include 0 or more entries. Each entry has an address and a zip code.
I'd like to create a form where the user can edit the contact's properties, and also its children entries: he could add a new entry, delete an existing entry, or edit an existing entry.
To this end, the views for both these components provide a form-based template.
I can think of this data flow:
contact: the user edits the form and then clicks a submit button to save
the whole thing. Thus, I can just have some code handling the submit button
and emitting an event as the component output. The contact has an entries
array property: I can thus use an ngFor directive in its template to render
an entry component for each of them.
entry: the entry has properties addressCtl and zipCtl which represent
the control directives included in the ControlGroup representing the whole
form. Also, I need a couple of properties to be bound as the input of the
component (address and zip), so that in the parent template I can do something like:
<tr *ngFor="#e of entries">
<td><my-entry [address]="e.address" [zip]="e.zip"></my-entry></td>
</tr>
Now, it's not clear to me how to shape the relation between the "model" properties representing the control's input, and the "form" directives properties. I should be able to get the address and zip values from the parent component through the [...] binding, and pass the updated values up through an event fired by the child component (e.g. blur?). Does this make sense in the NG2 world? Anyway, I'm missing a piece here: how can I connect the form controls values to the model properties values? Could anyone make this clearer or point to some good docs?

In fact, using the [...] binding only corresponds to a one-way binding. When the parent property is updated in the parent component, the value is also updated in the child component.
But if you want to update parent attributes from the child, you need to leverage events and #Ouput attribute.
Here is a sample with a labels component:
export class LabelsComponent implements OnInit {
#Input()
labels:string[];
#Output()
labelsChange: EventEmitter;
(...)
removeLabel(label:string) {
var index = this.labels.indexOf(label, 0);
if (index != undefined) {
this.labels.splice(index, 1);
this.labelsChange.emit(this.labels);
}
}
addLabel(label:string) {
this.labels.push(this.labelToAdd);
this.labelsChange.emit(this.labels);
this.labelToAdd = '';
this.addAreaDisplayed = false;
}
}
This way you can leverage two way binding on this component:
<labels [(labels)]="company.labels"></labels>
Hope it answers your question,
Thierry

Just moved the comment to answer...
You can pass the object e, instead of passing string.
i.e
<my-entry [entry] = "e"></my-entry>
then in your my-entry component, use ng-model for each input. so you automatically gets 2 way bindings.

Related

Thymeleaf form with multiple objects of the same class

Simple problem but can't find a solution: I have a Thymeleaf form used to add a new object, say of a Book class. It works perfectly well and I only need that particular form for adding new objects, not editing the existing ones. The question is: how can I put several objects of the Book class in the same single form? So, purely for convenience, instead of filling form for a single book and clicking Send you can fill form for several books at once and only then click Send, have them all inserted into the database (in whatever order) and also have the option to fill the form partially (e.g. the form has room for 5 books but it will also accept 1, 2, 3 or 4 and you can leave the rest blank).
Edit: I've tried passing a list of object to the Thymeleaf template with the form bound to the whole list and iteration inside, but Thymeleaf throws BingingResultError upon rendering it.
You need to use a wrapper object to realize what you want.
Something like:
public class BooksCreationDto {
private List<Book> books;
// default and parameterized constructor
public void addBook(Book book) {
this.books.add(book);
}
// getter and setter
}
Then you need to pass this object as a model attribute in your controller:
BooksCreationDto booksForm = new BooksCreationDto();
model.addAttribute("form", booksForm);
bind fields using index property
th:field="*{books[__${itemStat.index}__].title}"
and get back the result with
#ModelAttribute BooksCreationDto form
in your controller.
For a complete and detailled explaination visit: https://www.baeldung.com/thymeleaf-list

How do I properly update parent component from child form using props

I have a parent and child components
ShowreelApp.vue and Showreel.vue. I am passing properties over to the child when I create it.
<showreel-component
v-for="showreel in showreels"
v-bind="showreel"
:key="showreel.id"
#update="update"
#delete="del"
></showreel-component>
I then use the props to populate an update form in each child showreel.
<div class="showreel-input">
<label>Heading</label>
<input type="text" :value="myHeading" >
</div>
In the child form I am emiting an update event to the parent component to update the specific showreels data.
update(event) {
this.$emit('update', this.id, this.heading, this.subheading, this.youtubeid);
}
When the user clicks on the update button on the child Showreels components form I would like to send the data to the parent.
However I cannot bind the data to the child's input field as that causes the warning about mutating the property as I should not be updating the value from the child form.
One option I thought of was creating duplicates of each value in the child ... Updating them to the passed in props when its created , binding them to the form inputs... and then passing these values over to my parent using emit when updating.
But then these seems a bit convoluted to me and now I am thinking I have gone about this the wrong way. Any suggestions would be appreciated.
Mutating props from a child is usually not a very good practice but if you need it, as of version 2.3+ there is a .sync modifier. For more reference see Sync modifier
Another solution is to use event emitting or VueX. I would personally recommend Vuex but if you have relatively small application go with either sync or event emitting as you have described.
I should have mentioned that each showreel component has its own form fields and update button.
The solution I have come up with is to create a "sync" event in the parent and emit to it from the child
sync(id, property,value){
var currentShowreel = this.showreels.find(showreel => showreel.id === id);
currentShowreel[property] = value
},
The sync event calls a method in the parent which takes in and ID proprty name and property value.
This generic method is called from the child element inputs with a method
#keyup="sync('heading', $event.target.value)"
This calls a method in the child which in turn updates the parents data
},sync(property, value){
this.$emit('sync', this.id, property, value);
}
Then when the user updates I simply emit the update event to the parent. The parent grabs the particular showreel items data and updates based on the latest values.
It's working but not sure this is the right way of doing this.

How to add cross-field validation in Angular 2 with a complex model

Cross-Field Validation in Template Based Forms
Question: Is it possible to create some sort of validation context that spans multiple fields but does not modify the underlying model? If not, is there a better way to do what I'm doing?
Given a model like this and assuming that changing the model is not possible:
export interface IEvent {
location?: {
address: string;
city: string;
country: string;
};
onlineUrl?: string;
}
What is the best way to build a template-based form that will require either all of the location object (address, city, country) to be populated OR the onlineUrl to be populated?
Here is a working Plunk where I have the custom validation working but there are some challenges:
In order to match the model, we need an ngModelGroup around the location fields, but onlineUrl should not be inside that ngModelGroup since it is not part of the location object. Since onlineUrl is not inside the ngModelGroup, it's difficult to come up with a cohesive validation approach. Notice in this example, that we've added a custom validator on the ngModelGroup, that seems to break some basic validation premises - for example, notice that when the fields are invalid that the red highlighting only shows up next to the location field because the custom validator is on the ngModelGroup and onlineUrl is not inside that group.
Typing in the onlineUrl field does not automatically trigger the re-validation since this field is not in the ngModelGroup that has the custom validator on it. In order to make this work I needed to add this binding: (change)="locationGroup.control.controls.address.updateValueAndValidity()" to the onlineUrl field in order to trigger the validation on a field inside the ngModelGroup. This doesn't seem ideal.
Restating the Question: Is it possible to create some sort of validation context that spans multiple fields but does not modify the underlying model?
You can place 'validateLocation' directive up in the top 'form' tag:
<form #myForm="ngForm" (ngSubmit)="save(myForm)" autocomplete="off" novalidate validateLocation>
<fieldset ngModelGroup="location">
Then validate() method gets access to both location and onlineUrl via 'FormGroup.value' and will look like:
if (control && control.value && control.value.location) {
let g = control.value;
if (g.location.address && g.location.city && g.location.country)
return null;
if (g.onlineUrl)
return null;
}
return {validateLocation: false}
Try this plunker. Form.valid would become true when either location or url is captured.

redux-form Wizard form with linked fields

I am building a multi-step application form with React. Having first built it with pure internal state I am now in the process of refactoring to Redux using redux-form.
Having used the example here as a basis: http://redux-form.com/5.2.5/#/examples/wizard?_k=oftw7a we have come a good way.
However the problem appears when i have two forms which are supposed to have the same value. During one of the pages i have a name field, that is supposed to be duplicated on the name field of the next page. The opposite should happen if you go back from the last page. Any tips to how this could be achieved?
Using the wizard, you are basically working with the exact same form that's split into multiple pieces. Ultimately it's the same form, because redux-form tracks them by name. It is how the library identifies the pieces of the same form - using the name.
form: 'wizard',
Here you can see that the exact same instance of the form will be shared throughout the pieces. fields work in a similar manner. Each field is defined as part of a form.
As long as you use the same field constants inside the fields object that you pass into the reduxForm function and as long as the value for form is the same, so that they use the same underlying form object, it should work for you just fine.
On one page you should pass in
export default reduxForm({
form: 'wizard',
fields : {
'fieldIWantOnBothPartsOfTheForm',
'someOtherFieldThatShouldOnlyBeHere',
},
...
And then on the other page:
export default reduxForm({
form: 'wizard',
fields : {
'fieldIWantOnBothPartsOfTheForm',
'thirdFieldHere',
},
...
Also, make sure you keep destroyOnUnmount equal to false if you want to navigate back-and-forth.
Hope that helps.

ASP.NET MVC 2 - Control Advice

I'm new to ASP.NET MVC 2, and I need some advice on the best 'Control' to use for this situation. (I'm know ASP.NET MVC doesn't really use server controls, but there are a number of add-ons such as MVC Controls ToolKit).
Here's what I need to do. I have a table in a database which contains a list of tests. I need to be able to display these in a View, and allow the user to select them in some way (via checkboxes or whatever).
Then I need to be able to determine which items are selected.
Can someone tell me the best way to achieve this?
Any help/comments are appreciated.
TIA.
If you do it with client side functionality, it will end up consisting mainly of two parts:
The visual HTML
The functional Javascript
How would I'd do it
I'd create a partial view that displays the table. If you need to reuse this, put the partial in Views/Shared folder
Each TR of the table would have serialized JSON of the object that is displayed in that particular row. Serialization can be done by writing a custom object extension method, so you can call ToJson() on any object afterwards
<tr data='<%= this.Model[0].ToJson()'>
<td class="actions"> Select ... </td>
<td>...</td>
...
</tr>
Mind the extra column with actions that you need to provide.
also add a Javascript that would provide the client side functionality (important: this script uses jQuery)
$(function(){
var selection = {};
$(".actions a.action-select").click(function(evt){
evt.preventDefault();
var context = $(this);
var rowObj = $.parseJSON(context.closest("tr[data]").toggleClass("selected").attr("data"));
if (selection[rowObj.Id])
{
// deselect
delete selection[rowObj.Id];
}
else
{
// select
selection[rowObj.Id] = rowObj;
}
});
This way your rows will have additional selected class when they're selected and your selection object will always have selected rows (or better said their objects) taht you can use however you please.
Additional note
Why did I set selection to be an object rather than an array? Because Javascript objects are kind of associative arrays so searching for a particular element is faster than enumerating over its elements it it was a normal array. This way I can immediately see whether row is selected or not and also remove an element from it directly.
Outcome
This way you'll have a reusable table that you can put on various pages (hence similarities with user controls). but in case you'd need to add several of these tables to your pages you'd have to tweak them a little so that client-side functionality won't mix data between different tables.