Angular5 data prepopulation one way binding not happening - angular5

I am using a parent component travelerInput that creates travelerListForm using model in my component and iterate it extracting travelerForm which is then passed to the nested child components using #Input:
for (let i = 1; i <= numberOfTravelers; i++) {
const tId = `${ptc}_0${i}`;
const Id = `${TravelerInput.pIndex++}`;
const traveler = new Traveler({passengerTypeCode: ptc, id: Id, tid: tId, names: [new Identity({
firstName: "",
middleName: "",
lastName: "",
title: "",
nameType: "native",
isDisplayed: false
})],
dateOfBirth: undefined ,
gender: "unknown",
accompanyingTravelerId: "",
accompanyingTravelerTid: ""
});
travelerList.push(traveler);
}
HTML
<div class="alpi-section col-xs-12 col-sm-12 col-md-12 col-lg-12"
*ngFor="let travelerForm of travelerListForm.controls; let tIndex = index;">
<o3r-simple-traveler-input
[config]="config.simpleTravelerInput"
[travelerForm]="travelerForm"
[index]="tIndex">
</o3r-simple-traveler-input>
Now we have a drop down in parent component with a list of travelers. The selected passenger in the drop down will have its information prepopulated in the form fields which are nested child components. I am using travelerForm which is iterated over travelerListForm in child components as #Input. On change of drop down I am binding the value of the passenger information to the corresponding index of travelerListForm which is also getting updated but on UI there is no update.
pickSelectedADTPassenger(adult:any, index: number){
this.selectedADTId= this.ADTTravelerId[adult];
this.travelerListForm.controls[index].value.names[0].firstName = this.selectedADTId.IDENTITY_INFORMATION.FIRST_NAME; //ASSISGNMENT
}
Have also tried using ngModel in the child component input field where I want the value to be prepopulated but it did not work:
<input type="text"
[(ngModel)]="travelerForm.controls.firstName.value"
class="form-control"
placeholder="FIRST NAME"
maxlength="52"
formControlName="firstName">
</div>
Please suggest.

Related

prop.sync is not working with :value (vuejs)

I have a made a custom "Select" component as well as an "Input" component. For my use case, it a certain object with "Preset" value is selected then I want the "Input" component to dynamically set the value to this Preset value. The parent component (below) has the child component "MegaSelect" (the custom select component) and "MegaCell" (custom input component)
<MegaSelect
:row="rowNum"
:col="0"
:options="frontTypes"
:displayValue="true"
:data.sync="data.frontType"
placeholder="Item type"
#change="typeChange"
></MegaSelect>
Here the "Select" component called "MegaSelect" is triggering typeChange (below)
typeChange() {
var presets = productData.getFrontType(this.data.frontType).presets
this.wPresets = presets ? presets.w : null
this.hPresets = presets ? presets.h : null
this.qtyPresets = presets ? presets.qty : null
}
typeChange will check if my selected object has a hardcoded "Preset" value. if it does, it will update the data of wPresets, hPresets and qtyPresets.
<MegaCell
:value="qtyPresets ? qtyPresets[0] : null"
:data.sync="data.qty"
dataType="Number"
:row="rowNum"
:col="4"
></MegaCell>
it will then dynamically update the :value of another custom "input" component called "MegaCell" which has a :prop.sync updating the "data" prop.
the MegaCell component has the following template
<template>
<div
class="mega-cell"
:class="{
selected: selected,
focussed: focussed,
outOfBounds: outOfBounds,
warn: warn
}"
#mousedown="mousedown"
>
<input ref="field" v-model="convertedDbValue" #focus="setFocus" #dblclick="setFocus" :placeholder="placeholder" />
</div>
</template>
I believe my mistake has something to do with the v-model and my computed: convertedDbValue's setter (below)
convertedDbValue: {
get: function() {
if (!this.data) return null
var fixedData = this.$utils.toFixedNumber(this.data / 25.4, 3)
var adjustedData = fixedData % 1 == 0 ? parseInt(fixedData) : fixedData
return this.dataType == 'Number' && this.unit == 'inch' ? adjustedData : this.data
},
set: function(value) {
if (this.dataType !== 'String') {
var val = this.unit == 'inch' ? this.$utils.toFixedNumber(value * 25.4, 3) : parseInt(value)
this.$emit('update:data', val)
} else {
this.$emit('update:data', value)
}
}
}
it's updating the data prop with a few adjustments to the inputted (number) value. how do i get the :value to dynamically change from the parent component to the child component and update the input value accordingly?

How to validate multiple user inputs within just one popup using Vue-SweetAlert2

As a coding training, right now I'm making a web page where you can click a "Create" button, which triggers a popup, where you are supposed to fill in 6 data inputs, whose input style varies like text, select etc. (See the code and the attached image below)
<template>
<v-btn class="create-button" color="yellow" #click="alertDisplay">Create</v-btn>
<br/>
<p>Test result of createCustomer: {{ createdCustomer }}</p>
</div>
</template>
<script>
export default {
data() {
return {
createdCustomer: null
}
},
methods: {
alertDisplay() {
const {value: formValues} = await this.$swal.fire({
title: 'Create private customer',
html: '<input id="swal-input1" class="swal2-input" placeholder="Customer Number">' +
'<select id="swal-input2" class="swal2-input"> <option value="fi_FI">fi_FI</option> <option value="sv_SE">sv_SE</option> </select>'
+
'<input id="swal-input3" class="swal2-input" placeholder="regNo">' +
'<input id="swal-input4" class="swal2-input" placeholder="Address">' +
'<input id="swal-input5" class="swal2-input" placeholder="First Name">' +
'<input id="swal-input6" class="swal2-input" placeholder="Last Name">'
,
focusConfirm: false,
preConfirm: () => {
return [
document.getElementById('swal-input1').value,
document.getElementById('swal-input2').value,
document.getElementById('swal-input3').value,
document.getElementById('swal-input4').value,
document.getElementById('swal-input5').value,
document.getElementById('swal-input6').value
]
}
})
if (formValues) {
this.createdCustomer = this.$swal.fire(JSON.stringify(formValues));
console.log(this.createdCustomer);
}
}
}
}
</script>
Technically, it's working. The popup shows up when the "create" button is clicked, and you can fill in all the 6 blanks and click the "OK" button as well. But I want to add some functionalities that check if the user inputs are valid, I mean things like
address should be within 50 characters
firstName should be within 20 characters
customerNumber should include both alphabets and numbers
and so on.
If it were C or Java, I could probably do something like
if(length <= 50){
// proceed
} else {
// warn the user that the string is too long
}
, but when it comes to validating multiple inputs within a single popup using Vue-SweetAlert2, I'm not sure how to do it, and I haven't been able to find any page that explains detailed enough.
If it were just a single input, maybe you could use inputValidor like this
const {value: ipAddress} = await Swal.fire({
title: 'Enter an IP address',
input: 'text',
inputValue: inputValue,
showCancelButton: true,
inputValidator: (value) => {
if (!value) {
return 'You need to write something!'
}
}
})
if (ipAddress) {
Swal.fire(`Your IP address is ${ipAddress}`)
}
, but this example only involves "one input". Plus, what this checks is just "whether an IP address has been given or not" (, which means whether there is a value or not, and it doesn't really check if the length of the IP address is correct and / or whether the input string consists of numbers / alphabets).
On the other hand, what I'm trying to do is to "restrict multiple input values (such as the length of the string etc)" "within a single popup". Any idea how I am supposed to do this?
Unfortunately the HTML tags to restrict inputs (e.g. required, pattern, etc.) do not work (see this issues),
so I find two work around.
Using preConfirm as in the linked issues
You could use preConfirm and if/else statement with Regex to check your requirement, if they are not satisfied you could use Swal.showValidationMessage(error).
const{value:formValue} = await Swal.fire({
title: "Some multiple input",
html:
<input id="swal-input1" class="swal-input1" placeholder="name"> +
<input id="swal-input2" class="swal-input2" placeholder="phone">,
preConfirm: () => {
if($("#swal-input1").val() !== "Joe"){
Swal.showValidationMessage("your name must be Joe");
} else if (!('[0-9]+'.test($("#swal-input2").val())){
Swal.showValidationMessage("your phone must contain some numbers");
}
}
});
Using Bootstrap
In this way Bootstrap does the check at the validation, you need to include class="form-control" in your input class and change a little your html code.
If some conditions fails, the browser shows a validation message for each fields, in the order they are in the html code.
const {value: formValue} = await Swal.fire({
title: 'Some multiple inputs',
html:
'<form id="multiple-inputs">' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input1" id="swal-input1" min=2 max=4 required>' +
'</div>' +
'<div class="form-group">' +
'<input type="text" class="form-control swal-input2" id="swal-input2" placeholder="Name" pattern="[A-Za-z]" required>' +
'</div>' +
'</form>',
});
I have tried both the solution, actually only with Bootstrap3 but it should work also with the latest release.

why doesn't change the value of my input?

I have an input where passing the value of my star-rating module. When i use my function with v-model, the value of my input change (for example '4'), but the value it's not read with my function #input.
My code in template:
<div v-if="question.element_type=='notation'">
<star-rating
inactive-color="#4c514c"
v-bind:star-size="30"
v-model="selectedNote"
#rating-selected ="next"
:rounded-corners="true"
:show-rating="false">
</star-rating>
<textarea
v-model="selectedNote"
class="myTextarea"
maxlength="1"
:name="'q'+question.id"
#input="next">
</textarea>
</div>
my two functions :
setRating: function(rating) {
this.selectedNote = rating
},
next(evt){
this.selectedNote = evt.target.value
if(evt.target.value.length)
this.affichageSuivant = true
else
this.affichageSuivant = false
},
and my state of seletedNote
selectedNote: 0,

Dynamically Tally Child Elements by Classname in Vue

I have a page that allows a user to drag/drop images into pre-defined DIVs, then I tally up the total value of the images based on their class name. What I am trying to do is get vue to read the values from each outer div.answer and get the class names of the child images.
My source code is:
<div
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint"
></div>
<script>
Vue.component('box-answers', {
props: ['level','hint'],
template: '<div class="droppable answer :id="level" :title="hint"></div>'
});
new Vue({
el: '#mainapp',
data: {
boxes: [
{ id: 1, level: 'baselevel-1', hint: 'x 1' },
{ id: 2, level: 'baselevel-2', hint: 'x 20' },
{ id: 3, level: 'baselevel-3', hint: 'x 400' },
{ id: 4, level: 'baselevel-4', hint: 'x 8,000' },
{ id: 5, level: 'baselevel-5', hint: 'x 160,000' }
]
}
</script>
This converts to the follow HTML (the nested DIVs and SPANs are user-possible entries by dragging):
<div id="baselevel-5" class="droppable answer" title="x 160,000">
<div><img src="images/line.gif" alt="Five" class="imgfive"></div>
<span><img src="images/dot.gif" alt="One" class="imgone"></span>
</div>
...
<div id="baselevel-1" class="droppable answer" title="x 1">
<span><img src="images/line.gif" alt="One" class="imgone"></span>
</div>
Currently, I have jQuery/JavaScript calculating the point values using the following:
$(function(j) {
var arAnswers = Array(1);
count = 0; //
j("div.answer").each(function( idx ) {
currentId = j(this).attr('id');
ones = 0;
fives = 0;
if ( j("#" + currentId).children().length > 0 ) {
ones = j("#" + currentId).children().find("img.imgone").length * 1;
fives = j("#" + currentId).children().find("img.imgfive").length * 5;
arAnswers[count] = ones + fives; //Tally box value
count++;
}
});
});
I would like Vue to perform similar iteration and addition to return total value of ones and fives found based on the image classname.
Currently, you are approaching this problem as a pure-play DOM operation. If that is what you need then you can simply use $refs:
<!-- NOTICE ref -->
<div ref="boxAnswers"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Inside your high-level component, you will have a function like:
function calculate() {
// NOTICE $refs
const arAnswers = this.$refs.boxAnswers.map((x) => {
// $el is the DOM element
const once = x.$el.querySelectorAll('img.imgone').length * 1;
const fives = x.$el.querySelectorAll('img.imgfive').length * 5;
return once + fives
});
return arAnswers;
}
But this is not the correct Vue way of doing things. You have to think in terms of events and data model (MVVM - don't touch DOM. DOM is just a representation of your data model). Since, you have a drag-n-drop based application, you have to listen for drag, dragstart, dragend and other drag events. For example:
<!-- NOTICE drop event -->
<div #drop="onDropEnd(box, $event)"
is="box-answers"
v-for="box in boxes.slice().reverse()"
v-bind:key="box.id"
v-bind:level="box.level"
v-bind:hint="box.hint">
</div>
Your onDropEnd event handler will look like:
function onDrop(box, $event) {
// box - on which box drop is happening
// $event.data - which image is being dropped
// Verify $event.data is actually the image you are intending
if ($event.data === 'some-type-image') {
// Do the counting manipulations here
// ... remaining code
}
}
This is not a complete code as I don't know other components. But it should help you with the required direction.

Angular Material Table Dynamic Columns without model

I need to use angular material table without model, because I don't know what will come from service.
So I am initializing my MatTableDataSource and displayedColumns dynamically in component like that :
TableComponent :
ngOnInit() {
this.vzfPuanTablo = [] //TABLE DATASOURCE
//GET SOMETHING FROM SERVICE
this.listecidenKisi = this.listeciServis.listecidenKisi;
this.listecidenVazife = this.listeciServis.listecidenVazife;
//FILL TABLE DATASOURCE
var obj = {};
for (let i in this.listecidenKisi ){
for( let v of this.listecidenVazife[i].vazifeSonuclar){
obj[v.name] = v.value;
}
this.vzfPuanTablo.push(obj);
obj={};
}
//CREATE DISPLAYED COLUMNS DYNAMICALLY
this.displayedColumns = [];
for( let v in this.vzfPuanTablo[0]){
this.displayedColumns.push(v);
}
//INITIALIZE MatTableDataSource
this.dataSource = new MatTableDataSource(this.vzfPuanTablo);
}
The most important part of code is here :
for( let v in this.vzfPuanTablo[0]) {
this.displayedColumns.push(v);
}
I am creating displayedColumns here dynamically, it means; even I don't know what will come from service, I can show it in table.
For example displayedColumns can be like that:
["one", "two" , "three" , "four" , "five" ]
or
["stack","overflow","help","me]
But it is not problem because I can handle it.
But when I want to show it in HTML, I can't show properly because of
matCellDef thing:
TableHtml :
<mat-table #table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let disCol of displayedColumns; let colIndex = index" matColumnDef="{{disCol}}">
<mat-header-cell *matHeaderCellDef>{{disCol}}</mat-header-cell>
<mat-cell *matCellDef="let element "> {{element.disCol}}
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
My problem is here:
<mat-cell *matCellDef="let element "> {{element.disCol}} < / mat-cell>
In fact, I want to display element."disCol's value" in the cell, but I don't know how can I do that.
Otherwise, everything is ok except this element."disCol's value" thing.
When I use {{element.disCol}} to display value of element that has disCols's value , all cells are empty like that:
Other example that using {{element}} only:
Also as you can see:
Table datasource is changing dynamically. It means I can't use {{element.ColumnName}} easily, because I don't know even what is it.
First Example's displayedColumns = ['Vazife', 'AdSoyad', 'Kirmizi', 'Mavi', 'Yesil', 'Sari'];
Second Example's displayedColumns = ['Muhasebe', 'Ders', 'Egitim', 'Harici'];
matHeaderCellDef is correct , because it is using {{disCol}} directly.
But I need to read disCol's value, and display element.(disCol's value) in the cell.
How can I do that ?
I found solution :)
It is very very easy but i could't see at first :)
only like that :
<mat-cell *matCellDef="let element "> {{element[disCol]}}
</mat-cell>
I must use {{element[disCol]}} only in HTML.
Now , everything is ok:)
For a full working example based on #mevaka's
Where jobDetails$ is the array of items.
columns$ is equvilent to Object.keys(jobDetails$[0]) so is just a string[]
<table mat-table [dataSource]="jobDetails$ | async">
<ng-container *ngFor="let disCol of (columns$ | async); let colIndex = index" matColumnDef="{{disCol}}">
<th mat-header-cell *matHeaderCellDef>{{disCol}}</th>
<td mat-cell *matCellDef="let element">{{element[disCol]}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="(columns$ | async)"></tr>
<tr mat-row *matRowDef="let row; columns: (columns$ | async)"></tr>
</table>
I've tried my best to boil a dynamic table down to the minimum. This example will display any columns given an array of flat objects with any keys. Note how the first object has an extra "foo" property that causes an entire column to be created. The DATA const could be some data you get from a service. Also, you could add a "column ID -> label" mapping into this if you know some common property names you'll be getting the JSON. See the stachblitz here.
import {Component, ViewChild, OnInit} from '#angular/core';
const DATA: any[] = [
{position: 1, name: 'sdd', weight: 1.0079, symbol: 'H', foo: 'bar'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}
];
#Component({
selector: 'dynamic-table-example',
styleUrls: ['dynamic-table-example.css'],
templateUrl: 'dynamic-table-example.html',
})
export class DynamicTableExample implements OnInit {
columns:Array<any>
displayedColumns:Array<any>
dataSource:any
ngOnInit() {
// Get list of columns by gathering unique keys of objects found in DATA.
const columns = DATA
.reduce((columns, row) => {
return [...columns, ...Object.keys(row)]
}, [])
.reduce((columns, column) => {
return columns.includes(column)
? columns
: [...columns, column]
}, [])
// Describe the columns for <mat-table>.
this.columns = columns.map(column => {
return {
columnDef: column,
header: column,
cell: (element: any) => `${element[column] ? element[column] : ``}`
}
})
this.displayedColumns = this.columns.map(c => c.columnDef);
// Set the dataSource for <mat-table>.
this.dataSource = DATA
}
}
<mat-table #table [dataSource]="dataSource">
<ng-container *ngFor="let column of columns" [cdkColumnDef]="column.columnDef">
<mat-header-cell *cdkHeaderCellDef>{{ column.header }}</mat-header-cell>
<mat-cell *cdkCellDef="let row">{{ column.cell(row) }}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>