Vue2 dynamic v-model for a group - javascript

I am working on a housing form and I am stuck somewhere and I am hoping someone give me a solution to my problem.
So, when you select in a form that your house have 2 floors, then you get to write each floor how many rooms, toilets, balcony etc has..
My issue is to "group" these answers somehow that I could send it to my backend and "read" those answers.
so this is what I did so far:
data() {
return {
flooroptions: [
{
name: 'Rooms',
id: '1',
},
{
name: 'Balcony',
id: '2',
},
{
name: 'Toilets',
id: '3',
},
],
floors: '',
floor1: [],
},
}
and if I loop through like this:
<div class="" v-for="(opt, index) in flooroptions">
<input type="number" name="" value="" v-model="floor1[index]">
</div>
I can see my data using floor1:
{{ floor1 }}
But if in the form someone selects that 1st floor has 2 rooms and does not add any other input than I get to floor1 only 2
How would be better approach for this type of problem ??
PS. Everyfloor will have this flooroptions loop and they can input number of each option for each floor, and I want to be able to read that number correctly..

if I understand correctly, I would...
data() {
return {
flooroptions: [
{
name: 'Rooms',
id: '1',
},
{
name: 'Balcony',
id: '2',
},
{
name: 'Toilets',
id: '3',
},
],
numFloors: 2,
floorData: [
{'Rooms':0, 'Balcony':0, 'Toilets':2},
{'Rooms':2, 'Balcony':1, 'Toilets':0}
],
},
}
and loop through like this:
<div v-for="f in numFloors">
<div class="" v-for="opt in flooroptions">
<input type="number" name="" value="" v-model="floorData[f][opt.name]">
</div>
</div>
or... if you want to pass less data and the id's are know on the backen
data() {
return {
flooroptions: {
1: {
name: 'Rooms'
},
2: {
name: 'Balcony'
},
3: {
name: 'Toilets'
},
},
numFloors: 2,
floorData: [
{1:0, 2:0, 3:2},
{1:2, 2:1, 3:0}
],
},
}
and loop through like this:
<div v-for="f in numFloors">
<div class="" v-for="(opt, i) in flooroptions">
<input type="number" name="" value="" v-model="floorData[f][i]">
</div>
</div>

Related

disable current selection until some value is inputed in the previous selection

I'm working with BootstrapVue.
I have following problem. I have a b-form-input where I'm searching for my number over my b-form-select. So I'm selecting 3 values and get a number in my input field and other way around - so it's an Autofill.
This works fine - and because of that I think I don't need to show you the code.
The problem is that I want to disable all selects (instead of the first) till the field before was selected.
I have problem that if I have multiple elements all will be updated. So if I input something in Input1 in the first element the second Input of all other elements will be !disabled
Additional Info: IDParent is a prop!
If you need any additional code, I can update my question!
<template>
<div>
<div class="mt-2" v-for="(IDChild, indexChild) in inputs" :key="indexChild">
<div>
<div class="mt-2">Number</div>
<b-form-input type="number" v-model="IDChild.Number" :value="IDChild.Number"></b-form-input>
</div>
<div>
<div class="mt-2">Input 1</div>
<b-form-select v-model="IDChild.Input1" :value="IDChild.Input1" :options="optionsInput1" #input="searchNumber(IDChild, IDParent, indexChild)"></b-form-select>
</div>
<div>
<div class="mt-2">Input 2</div>
<b-form-select :disabled="disabledInput2" v-model="IDChild.Input2" :value="IDChild.Input2" :options="optionsInput2" #input="searchNumber(IDChild, IDParent, indexChild)"></b-form-select>
</div>
<div>
<div class="mt-2">Input 3</div>
<b-form-select :disabled="disabledInput3" v-model="IDChild.Input3" :value="IDChild.Input3" :options="optionsInput3" #input="searchNumber(IDChild, IDParent, indexChild)"></b-form-select>
</div>
</div>
<!-- add new element when button was clicked -->
<div class="mt-4 mb-5 ml-3 mr-3">
<b-button #click="addElement"> Add Element </b-button>
</div>
</div>
</template>
my script:
<script>
import json from "./json/json.json";
export default {
name: "Test",
methods: {
addElement() {
this.inputs.push({});
},
searchNumber(input, IDParent, indexChild) {
input.Number = "";
this.json.forEach((element) => {
if (
element.Input1 == input.Input1 &&
element.Input2 == input.Input2 &&
element.Input3 == input.Input3
) {
for (const key of Object.keys(element)) {
input[key] = element[key];
}
}
});
if(input.Input1) {
this.disabledInput2 = false;
}
if(input.Input2) {
this.disabledInput3 = false;
}
},
},
props: [
"IDParent",
],
data() {
return {
inputs: [{}],
json: json,
disabledInput2: true,
disabledInput3: true,
};
},
};
</script>
What you are missing is a multi-layer model for your data which you can call in your searchNumber function and call for your :disabled attribute.
In your v-for="(IDChild, indexChild) in inputs" you could use IDChild or indexChild for that. For example you would call :disabled="disabledInput2[indexChild]". That way you would refer to disabledInput2 with the specific indexChild.
You also need to handle this in your function, for example this.disabledInput2[indexChild] = false;.
Basically it´s the same as storing multi-layer data in the same object with v-model.
EDIT: Generally Example
I´ve created the following properties for this example. We have myNumberInput as an object to handle multiple input fields for numbers. mySelectionData provides a simple collection of two objects with 3 selection arrays each. myDisableData is the object that will handle multiple disabled attributes for this selections:
myNumberInput: {},
mySelectionData: {
1: {
1: [
{ name: "1A", value: 1 }, { name: "2A", value: 2 }, { name: "3A", value: 3 }
],
2: [
{ name: "4A", value: 4 }, { name: "5A", value: 5 }, { name: "6A", value: 6 }
],
3: [
{ name: "7A", value: 7 }, { name: "8A", value: 8 }, { name: "9A", value: 9 }
]
},
2: {
1: [
{ name: "1B", value: 11 }, { name: "2B", value: 21 }, { name: "3B", value: 31 }
],
2: [
{ name: "4B", value: 41 }, { name: "5B", value: 51 }, { name: "6B", value: 61 }
],
3: [
{ name: "7B", value: 71 }, { name: "8B", value: 81 }, { name: "9B", value: 91 }
]
}
},
myDisableData: {}
From the mySelectionData object, we will build our myDisableData with this function:
setupMyDisableData() {
Object.keys(this.mySelectionData).forEach(parent_key => {
Object.assign(this.myDisableData, {[parent_key]: {}})
Object.keys(this.mySelectionData[parent_key]).forEach(child_key => {
Object.assign(this.myDisableData[parent_key], {[child_key]: true})
});
});
}
This will loop trough our "parents", assign their index to myDisableData and also loop trough the "childs" and assign their index to myDisableData with the "parent"-index as a pointer. After that we have a multi-layer object which is able to provide diabled= true or false for each selection.
The html for this example looks like this:
<div v-for="(item,index) in mySelectionData" :key="index">
<input type="number" v-model="myNumberInput[index]" #input="enableMySelection(index, myNumberInput[index])">
<div v-for="(child_item, child_index) in item" :key="child_index">
<select :disabled="myDisableData[index][child_index]">
<option v-for="(child_option, child_option_index) in child_item" :key="child_option_index" :value="child_option.value">{{child_option.name}}</option>
</select>
</div>
</div>
As I don´t use BootstrapVue, my html looks different, but I guess you will get the trick. You simply now refer to the three object by the index of "parent" and "child". In this example the function enableMySelection will enable a selection depending on the number entered in the input. The function looks like this:
enableMySelection(parent_index, input_number) {
Object.keys(this.myDisableData[parent_index]).forEach(child_key => {
this.myDisableData[parent_index][child_key] = true;
});
this.myDisableData[parent_index][input_number] = false;
}
So if you enter "2" in your first input, it will enable the second selection of the first block. If you enter 1 in the second input, it will enable the first selection in the second block.
As I said, this is just a generally example but this should be enough to help you define the structure to handle your multiple inputs and selections.

VueJs - Checbokex & Textfield based on v-model and value based on selected option

Hello I'm trying to set a value when one of the options is selected. The text input will only show when an option is selected and the value from the array user.roles should populate on input text but for some reason I can't figure out why isn't working. Any help will be greatly appreciated.
new Vue({
el: '#app',
data: {
user: {
email: 'test#test.com',
roles: [{id: 1, name:'Client', value:'this is just a value'}]
},
roles: [
{
id: 1,
name: 'Client',
value:'',
},
{
id: 2,
name: 'Admin',
value:'',
},
{
id: 3,
name: 'Guest',
value:'',
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template>
<label>Email</label>
<input type="text" v-model="user.email" />
</div>
<div v-for="(role,index) in roles" :key="role.id">
<label>{{role.name}}</label>
<input type="checkbox" v-model="user.roles" :value="role"/>
<p v-if="user.roles.filter(e => e.id === role.id).length > 0">
<input type="text" value="The value from user.roles should go here.... instead of the value of role.value">
</p>
</div>
<p>User's selected roels</p>
{{user.roles}}
</template>
</div>
I have a Vue 2 CLI 'sandbox' app that I use for testing, so I created a single file component based on your template and Vue instance in your question.
Not sure if this will solve your problem, but I did discover a problem with initially checking the role checkboxes that the user already has.
In your example your user is already assigned role 1, but the associated checkbox was not initially being rendered as checked in my test app. I looked at the documentation for binding multiple checkboxes to an array, as in your code, but the docs don't address the issue of initializing checkboxes bound to an array.
After searching around in vain, I noticed that in your initial 'user.roles' array, the 'Client' role object does not exactly match the 'Client' role object in data 'roles' because the property 'role.value' do not match. When I modified your code to set the pre-assigned 'user.roles' 'Client' object 'value' property to an empty string, the checkbox for 'Client' was now correctly initially rendering as checked.
data() {
return {
user: {
email: 'test#test.com',
roles: [{ id: 1, name: 'Client', value: '' }]
},
roles: [
{
id: 1,
name: 'Client',
value: '',
},
{
id: 2,
name: 'Admin',
value: '',
},
{
id: 3,
name: 'Guest',
value: '',
}
]
}
},
new Vue({
el: '#app',
data: {
rolename: [],
user: {
email: 'test#test.com',
roles: [{id: 1, name:'Client', value:'this is just a value'}]
},
roles: [
{
id: 1,
name: 'Client',
value:'',
},
{
id: 2,
name: 'Admin',
value:'',
},
{
id: 3,
name: 'Guest',
value:'',
}
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template>
<div>
<label>Email</label>
<input type="text" v-model="user.email" />
</div>
<div v-for="role in roles" :key="role.id">
<label>{{ role.name }}</label>
<input type="checkbox" v-model="rolename" :value="role.name" />
</div>
<p>
<input type="input" v-model="rolename" />
</p>
<p>User's selected roels</p>
{{ user.roles }}
</template>
</div>

Nested Vue components with counts of direct children and nested children

I am trying to implement nested comments in vue.js and nuxt.js.
Each comment can have one or more children comments.
Each child comment, can again, have one or more children comments.
Unlimited levels of nested comments is possible.
As you can see in the diagram I have attached, I would like each comment to "know" (for the sake of simplicity, to display) the following information:
The depth of the comment (I have this working already). Example, all of the "top-level" comments are at depth=0, all their children are at depth=1, and so on.
The number of direct children
the number of children (including nested children, unlimited levels deep)
I came across this question on StackOverflow but it doesn't quite do the trick. Or maybe I am doing something wrong.
In case you want to take a look at my (very messy) code, here it is. However, I'm willing to start over, so appreciate any pointers on how to pass the data up / down the chain of nested comments (vue components). Some sample code would be great.
components/PostComment.vue:
<template>
<div>
<div class="tw-flex tw-flex-wrap tw-justify-end">
<div :class="indent" class="tw-w-full tw-flex">
<div class="tw-font-bold tw-p-4 tw-border-gray-400 tw-border tw-rounded tw-text-right">
<div class="kb-card-section">
<div class="kb-card-section-content tw-flex tw-flex-wrap tw-items-center tw-text-left">
<div class="tw-flex tw-w-full">
<div class="tw-hidden md:tw-block md:tw-w-2/12 tw-text-right tw-my-auto">
<div class="tw-flex">
<p class="tw-w-full tw-text-xs tw-text-gray-600 tw-text-right">children: {{ numNestedChildComments }}, depth: {{depth}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tw-w-full" v-if="commentData.nested_comments" v-for="nestedComment in commentData.nested_comments">
<post-comment
:commentData="nestedComment"
:depth="depth + 1"
:numChildCommentsOfParent=numNestedChildComments
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'post-comment', // necessary for recursive components / nested comments to work
props: {
depth: {
type: Number,
required: true
},
postAuthorData: {
type: Object,
required: true
},
commentAuthorData: {
type: Object,
required: true
},
commentData: {
type: Object,
required: true
},
numChildCommentsOfParent: {
type: Number,
required: true
},
},
data() {
return {
numNestedChildComments: this.numChildCommentsOfParent,
}
},
mounted() {
this.incrementNumParentComments();
},
methods: {
incrementNumParentComments() {
this.numNestedChildComments++;
this.$emit('incrementNumParentComments');
},
},
computed: {
indent() {
switch (this.depth) {
case 0:
return "tw-ml-0 tw-mt-1";
case 1:
return "tw-ml-4 tw-mt-1";
case 2:
return "tw-ml-8 tw-mt-1";
case 3:
default:
return "tw-ml-12 tw-mt-1";
}
},
},
}
</script>
Figured it out with some help from Rodrigo Pedra from the Laracasts community.
Here as a parent component calling the tree roots:
<template>
<div>
<MyTree v-for="item in records" :key="item.id" :item="item" />
</div>
</template>
<script>
import MyTree from './MyTree';
const FIXTURE = [
{
id: 1,
children: [
{
id: 2,
children: [{id: 3}, {id: 4}, {id: 5}],
},
{
id: 6,
children: [
{id: 7},
{id: 8, children: [{id: 9}, {id: 10}]},
],
},
],
},
{
id: 11,
children: [
{id: 12, children: [{id: 13}, {id: 14}, {id: 15}]},
{id: 16, children: [{id: 17}]},
{id: 18},
],
},
];
export default {
components: {MyTree},
data() {
return {
records: FIXTURE,
};
},
};
</script>
And here is the tree component:
<template>
<div>
<div style="border: 1px solid black; padding: 5px;" :style="offset">
id: {{ item.id }}
// depth: {{ depth }}
// direct: {{ direct }}
// children: {{ childrenCount }}
</div>
<template v-if="item.children">
<MyTree
v-for="record in item.children"
:key="record.id"
:item="record"
:depth="depth + 1"
#born="handleBorn()" />
</template>
</div>
</template>
<script>
const COLORS = [
'white',
'lightgray',
'lightblue',
'lightcyan',
'lightskyblue',
'lightpink',
];
export default {
// MUST give a name in recursive components
// https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components
name: 'MyTree',
props: {
item: {type: Object, required: true},
depth: {type: Number, default: 0},
},
data() {
return {
childrenCount: 0,
};
},
computed: {
direct() {
if (Array.isArray(this.item.children)) {
return this.item.children.length;
}
return 0;
},
offset() {
return {
'margin-left': (this.depth * 20) + 'px',
'background-color': COLORS[this.depth % COLORS.length],
};
},
},
mounted() {
this.$emit('born');
},
methods: {
handleBorn() {
this.childrenCount++;
this.$emit('born');
},
},
};
</script>
Screenshot:

how to fetch array of input values from html to JS

I have posted the same question on salesforce.stackexchange.com below is the link
https://salesforce.stackexchange.com/questions/283596/how-to-fetch-input-field-value-in-js-in-lwc?noredirect=1#comment426016_283596
But in the comments people suggested this is not much related to salesforce it is more of a Javascript thing so I am posting the same here, please take a look and suggest from html and js point of view.
HTML syntax seem different as it is lightning web component of salesoforce not angular, and also please help me to understand the same behavior in angular and I will do it in salesforce accordingly
in the below html code I am iterating 2 lists linItemData(11 records) and studyData(20 records)
<table class="slds-table slds-table_cell-buffer slds-table_bordered">
<thead>
<tr class="slds-line-height_reset">
<template if:true={studyData}>
<template for:each={studyData} for:item="sData">
<th key={sData}>
<div class="tablename slds-p-bottom_medium"></div>{sData.Visit_Name__c}
</th>
</template>
</template>
</tr>
</thead>
<tbody>
<template if:true={lineItemData}>
<template for:each={lineItemData} for:item="sLineItem">
<tr key={sLineItem}>
<template for:each={studyData} for:item="sData">
<td key={sData.Id}>
<lightning-input variant="label-hidden" class="fieldSize" type="number" step="0.01"
label="visitValue" data-key={sData.Id} onchange={visitValueChange} placeholder="00.00">
</lightning-input>
</td>
</template>
</tr>
</template>
</template>
</tbody>
</table>
and I am getting an expected result with all the required input fields on the UI like below:
now I need to fetch the input value for all v20s for example for v1 (first column name)should have array of 11 input records.
JS is as below:
visitValueChange(event) {
this.studyData
.find(item => item.Id === event.currentTarget.dataset.key)
.visitValue = event.target.value;
}
I have tried everything I could but the visitValue can hold only the 1 recent value for example if I add 1 on first input field, two on 2nd, three on 3rd and so on after that I hit a button which is calling a JS function to see the studyData array but it has only the recent value which is 3 not all 1,2 and 3 values entered and the requirement is to have an array of all the input values added on all the 11 input fileds for all the columns.
Please help me to suggest any workaround for the same.
You have two issues:
The way your data is designed
Linking the input with the correct record
Data model
Depending on your context, you could say that:
Each line is a record with columns being attributes of record (col1, col2, ...)
Each column is a record with lines being attributes of record (row1, row2, ...)
Each cell is a record identified with an id like x_y (1_1, 1_2, ...)
I would probably recommend option 1 as it's the most common representation.
This means you have to prepare / create records like this:
const myRecords = [
{
id: 1,
col1: 0,
col2: 0,
...
}, {
id: 2,
col1: 0,
col2: 0,
...
}
]
Linking input to correct record
Use LWC datatabel to create your table. You can then get what you need using custom data types. This would allow to use properly designed records (one by line and each columns is an attribute) without any transformation.
Display using the provided HTML
Unfortunatly there are some limits in Salesforce (with the HTML you provided) so you need to transform your records into this.
const myRecords = [
{
id: 1,
columns: [
{ id: 1, value: 0 },
{ id: 2, value: 0 }
...
]
}, {
id: 2,
columns: [
{ id: 1, value: 0 },
{ id: 2, value: 0 }
...
]
}
]
This will allow this to work
import {
LightningElement,
track
} from 'lwc';
export default class App extends LightningElement {
columns = [{
id: '1',
name: 'v1'
},
{
id: '2',
name: 'v2'
},
{
id: '3',
name: 'v3'
},
{
id: '4',
name: 'v4'
},
{
id: '5',
name: 'v5'
},
]
records = [{
id: '1',
columns: [{
id: '1',
value: 0
},
{
id: '2',
value: 0
},
{
id: '3',
value: 0
},
{
id: '4',
value: 0
},
{
id: '5',
value: 0
},
]
},
{
id: '2',
columns: [{
id: '1',
value: 0
},
{
id: '2',
value: 0
},
{
id: '3',
value: 0
},
{
id: '4',
value: 0
},
{
id: '5',
value: 0
},
]
}
]
visitValueChange(event) {
const recordId = event.target.dataset.recordId
const columnId = event.target.dataset.columnId
const value = event.target.value
const record = this.records.find(record => record.id === recordId)
const column = record.columns.find(column => column.id === columnId)
column.value = value
}
}
<template>
<table class="slds-table slds-table_cell-buffer slds-table_bordered">
<thead>
<tr class="slds-line-height_reset">
<template for:each={columns} for:item="column">
<th key={column.id}>{column.name}</th>
</template>
</tr>
</thead>
<tbody>
<template for:each={records} for:item="record">
<tr key={record.id}>
<template for:each={record.columns} for:item="column">
<td key={column.id}>
<lightning-input
variant="label-hidden"
class="fieldSize"
type="number"
step="0.01"
label="visitValue"
value={column.value}
data-record-id={record.id}
data-column-id={column.id}
onchange={visitValueChange}
placeholder="00.00"
></lightning-input>
</td>
</template>
</tr>
</template>
</tbody>
</table>
</template>

ng-model returns name of value in place of real value

I have html page using angular.
I have a table with ng-repeat on array:
<tr ng-repeat="oblig in Obligations">
oblig object contains property of chargeOptionId connected to another array ChargeOptions.
Inside the table there is a select element that I want to show details of charge option of oblig.
html code:
<select class="form-control full-width" ng-model="oblig.ChargeOptionId">
<option ng-selected="ch.Id == oblig.ChargeOptionId" value="ch.Id" ng-repeat="ch in ChargeOptions">{{ch.Account}}</option>
</select>
the select element display the charge option details as I want, but when I try saving oblig, oblig.ChargeOptionId="ch.Id" string in place of value of ch.Id.
I tried:
1) using ng-value, but the select did not display the data correctly.
2) ng-options, but still data was not displayed correctly.
how can I fix that problem?
Change
value="ch.Id"
to
value="{{ch.Id}}"
or
ng-value="ch.Id"
The attribute "value" is not an angular directive (like ng-model and ng-value are) so it doesn't know you intend "ch.Id" as a variable.
jsbin
I'm not sure what was failing before, but this seems to work: I used ng-options instead of ng-repeat.
This seemed to do the trick:
angular.module('app', []).controller('Ctrl', function($scope) {
$scope.Obligations = [
{ ChargeOptionId: 1 },
{ ChargeOptionId: 2 },
{ ChargeOptionId: 3 },
{ ChargeOptionId: 4 },
{ ChargeOptionId: 5 },
{ ChargeOptionId: 6 },
{ ChargeOptionId: 7 }
];
$scope.ChargeOptions = [
{ Id: 1, Account: 'Account 1' },
{ Id: 2, Account: 'Account 2' },
{ Id: 3, Account: 'Account 3' },
{ Id: 4, Account: 'Account 4' },
{ Id: 5, Account: 'Account 5' },
{ Id: 6, Account: 'Account 6' },
{ Id: 7, Account: 'Account 7' }
];
$scope.alertSelected = function(idx) {
alert($scope.Obligations[idx].ChargeOptionId);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div ng-app="app" ng-controller="Ctrl">
<div ng-repeat="oblig in Obligations">
<div>
ChargeOption.Id = {{ChargeOptions[$index].Id}} /
Obligation.ChargeOptionId = {{oblig.ChargeOptionId}}
</div>
<select class="form-control full-width"
ng-model="oblig.ChargeOptionId"
ng-options="ch.Id as ch.Account for ch in ChargeOptions">
</select>
<button ng-click="alertSelected($index)">Selected value?</button>
<br /><br />
</div>
</div>

Categories

Resources