bind Kendo vue dropdownlist to array of objects - javascript

SAMPLE https://stackblitz.com/edit/usjgwp?file=index.html
I want to show a number of kendo dropdownlist(s) on a page. The exact number depends on an API call. This API call will give me an array of stakeholder objects. Stakeholder objects have the following properties: Id, name, type, role and isSelected.
The number of dropdownlist that has to be shown on this page should be equal to the number of unique type values in the API response array. i.e,
numberOfDropdowns = stakeholders.map(a => a.type).distinct().count().
Now, each dropdown will have a datasource based on the type property. i.e, For a dropdown for type = 1, dataSource will be stakeholders.filter(s => s.type == 1).
Also the default values in the dropdowns will be based on the isSelected property. For every type, only one object will have isSelected = true.
I have achieved these things by using the following code:
<template>
<div
v-if="selectedStakeholders.length > 0"
v-for="(stakeholderLabel, index) in stakeholderLabels"
:key="stakeholderLabel.Key"
>
<label>{{ stakeholderLabel.Value }}:</label>
<kendo-dropdownlist
v-model="selectedStakeholders[index].Id"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
></kendo-dropdownlist>
<button #click="updateStakeholders">Update form</button>
</div>
</template>
<script>
import STAKEHOLDER_SERVICE from "somePath";
export default {
name: "someName",
props: {
value1: String,
value2: String,
},
data() {
return {
payload: {
value1: this.value1,
value2: this.value2
},
stakeholders: [],
selectedStakeholders: [],
stakeholderLabels: [] // [{Key: 1, Value: "Stakeholder1"}, {Key: 2, Value: "Stakeholder2"}, ... ]
};
},
mounted: async function() {
await this.setStakeholderLabels();
await this.setStakeholderDataSource();
this.setSelectedStakeholdersArray();
},
methods: {
async setStakeholderLabels() {
let kvPairs = await STAKEHOLDER_SERVICE.getStakeholderLabels();
kvPairs = kvPairs.sort((kv1, kv2) => (kv1.Key > kv2.Key ? 1 : -1));
kvPairs.forEach(kvPair => this.stakeholderLabels.push(kvPair));
},
async setStakeholderDataSource() {
this.stakeholders = await STAKEHOLDER_SERVICE.getStakeholders(
this.payload
);
}
setSelectedStakeholdersArray() {
const selectedStakeholders = this.stakeholders
.filter(s => s.isSelected === true)
.sort((s1, s2) => (s1.type > s2.type ? 1 : -1));
selectedStakeholders.forEach(selectedStakeholder =>
this.selectedStakeholders.push(selectedStakeholder)
);
},
async updateStakeholders() {
console.log(this.selectedStakeholders);
}
}
};
</script>
The problem is that I am not able to change the selection in the dropdownlist the selection always remains the same as the default selected values. Even when I choose a different option in any dropdownlist, the selection does not actually change.
I've also tried binding like this:
<kendo-dropdownlist
v-model="selectedStakeholders[index]"
value-primitive="false"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
></kendo-dropdownlist>
If I bind like this, I am able to change selection but then the default selection does not happen, the first option is always the selection option i.e, default selection is not based on the isSelected property.
My requirement is that I have to show the dropdown with some default selections, allow the user to choose different options in all the different dropdowns and then retrieve all the selection then the update button is clicked.
UPDATE:
When I use the first method for binding, The Id property of objects in the selectedStakeholders array is actually changing, but it does not reflect on the UI, i.e, on the UI, the selected option is always the default option even when user changes selection.
Also when I subscribe to the change and select events, I see that only select event is being triggered, change event never triggers.

So it turns out that it was a Vue.js limitation (or a JS limitation which vue inherited),
Link
I had to explicitly change the values in selectedStakeholders array like this:
<template>
<div
v-if="selectedStakeholders.length > 0"
v-for="(stakeholderLabel, index) in stakeholderLabels"
:key="stakeholderLabel.Key"
>
<label>{{ stakeholderLabel.Value }}:</label>
<kendo-dropdownlist
v-model="selectedStakeholders[index].Id"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
#select="selected"
></kendo-dropdownlist>
<button #click="updateStakeholders">Update form</button>
</div>
</template>
And in methods:
selected(e) {
const stakeholderTypeId = e.dataItem.type;
const selectedStakeholderIndexForTypeId = this.selectedStakeholders.findIndex(
s => s.type == stakeholderTypeId
);
this.$set(
this.selectedStakeholders,
selectedStakeholderIndexForTypeId,
e.dataItem
);
}

Related

How to update a row with contenteditable in Vue?

I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>

Vue allow only one element in array be true

I'm creating multiple input fields with checkboxes in vue and I want that only one can be true. So if the user clicks on one the others should be false so that only the last clicked checkbox is true.
My code is like that:
new Vue({
el: "#app",
data: {
selected: null,
options: [
{"id": 1, "title": "One", "value": false},
{"id": 2, "title": "Two", "value": false },
{"id": 3, "title": "Three", "value": false},
]
},
watch: {
selected(selected) {
this.options.forEach((option, index) => {
option.id == selected ? option.value = true : option.value = false;
});
}
}
Unfortunately my watcher isn't working properly. I would be really glad if somebody can show me how to correct it. I want that always the last true element is the only true element and the watches sets all other elements in options to false.
If i understood your requirements correctly you can still do it with radio buttons. You can specify the value to be used inside the selected variable, as described here: https://v2.vuejs.org/v2/guide/forms.html#Radio-1. This means that you can set up a watcher and then mutate the options list accordingly:
selected: function (newVal) {
this.options.forEach(option => {
if (option.id === newVal) option.value = true
else option.value = false
})
console.log(this.options)
}
Here is a sandbox to see it in action:
https://codesandbox.io/s/heuristic-goldberg-lilsw
Update: Just saw that you want to use the </b-switch> from buefy. You can still do something similar by calling a function from the input event which then mutates the options list according to the just changed element. Something like this:
<div v-for="(option,index) in options" :key="index">
<div class="box">
<div class="field">
<b-switch v-model="option.value" #input="(modelValue) => onSwitchChanged(modelValue, option.id)">
{{ option.title }}
</b-switch>
<label :for="index">{{ option.title }}</label>
</div>
</div>
</div>
function onSwitchChanged(modelValue, id) {
if (!modelValue) return
this.options.forEach(option => {
if (option.id === id) option.value = true
else option.value = false
})
}
If you want that Only One will be selected then you have to use radio button. Checkbox has options to select all But One by One.
Without watch you can use methods. Pass index to the method.
<input
type="checkbox"
:id="index"
:value="option.id"
#click="selectAnOption(index)"
>
Method:
methods: {
selectAnOption(index) {
this.options[index].value = true
}
}
Full Code here: https://jsfiddle.net/8ktdp9ew/

Angular 7: How to reset few checkboxes (generated dynamically using *ngFor) to it's previous state only click of a button

I have a child component which consists of 3 checkboxes (generated dynamically using ngFor) and an Apply and Cancel button.
Selector tag for the child is added in the parent's template. Parent component accesses this child component using #ViewChild and calls the present() method exposed by the child component with an object as argument as below which consists of checked state of the checkboxes.
Every time when modal is displayed, present() method is getting called. For the first time, UI/checkboxes is getting updated/checked as the values sent by parent. But, in the subsequent calls to present(), even though options.checked value is getting updated as expected in the ts file, this is not getting reflected in the UI. Every time the modal is displayed, I want checkbox to be checked or unchecked based on the value sent by the parent in present() method. Need help. Thanks in advance
parent.component.ts:
#ViewChild(childModalComponent) childModalComponent: ChildModalComponent;
onBtnClick() {
this.childModalComponent.present({
checkbox1: true,
checkbox2: false,
checkbox3: false
});
}
parent.component.html:
<feature-child-modal></feature-child-modal>
child.component.ts:
#ViewChild('childModal') childModal: ElementRef;
ngOnInit() {
this.options = [
{
label: 'label1',
value: 'value1',
checked: false,
},
{
label: 'label2',
value: 'value2',
checked: false,
},
{
label: 'label3',
value: 'value3',
checked: false,
},
];
}
present(optionsState: CompressTransactionType) {
this.options.forEach(item => {
if(item.value == "value1"){
item.checked = optionsState.checkbox1;
}
if(item.value == "value2"){
item.checked = optionsState.checkbox2;
}
if(item.value == "value3"){
item.checked = optionsState.checkbox3;
}
});
this.childModal.nativeElement.present();
}
dismiss() {
this.childModal.nativeElement.dismiss();
}
child.component.html:
<div *ngFor="let option of options">
<input
type="checkbox"
[value]="option.value"
(change)="onOptionsSelectChanged($event)"
[checked]="option.checked" />
</div>
try passing the array object here option instead of $event. Have a look in this url..
https://stackblitz.com/edit/angular-ivy-wifdeg

How to access the key's value of Ant select

I need to get the item ID of the selected item, In my case the user types in the input and gets results from an API in form of array that iterates the <Option> as below.
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select Invoices"
defaultValue={[]}
onChange={handle_select_invoices}
optionLabelProp="label"
onSearch={search_invoice_by_number}
>
{
invoices.map((el,index) => {
return <Option key={el.invoice_id} value={el.invoice_number}></Option>
})
}
</Select>
When user select an option, the handle_select_invoices is fired. It takes two params value and key.
const handle_select_invoices =(value,key) => {
console.log(' ************** IDS ****************')
console.log(key)
}
function search_invoice_by_number(value) {
var data={'invoice_number':value};
axios.post('http://localhost:4000/get_invoice_by_number',data).then(
response => {
if(response.data.length > 0){
set_invoices(response.data);
}else{
set_invoices([]);
}
},error =>{
Swal.fire({
title: 'Error!',
text: 'Please Contact your software developer',
icon: 'error',
confirmButtonText: 'OK'
})
}
)
}
The problem
When user selects multiple items, the console.log shows an empty Json elements and only the last element in the array is filled.
What is wrong in the code that leads to this result?
Alright, I think I understand what you mean. Here is how I suggest you do it. Use a variable in state that keeps track of selectedValues. In select onChange just set them in state like handleChange = values => setSelectedValues(values). In search, after you get the new data from the API, filter the selectedValues like so:
set_invoices(response.data);
const values = selectedValues.filter(value =>
data.map(i => i.invoice_number).includes(value)
);
setSelectedValues(values); // filter out the values that do not exist in the new data
and your select would contain an additional prop value={selectedValues}.
Here is a working example with some dummy data: https://codesandbox.io/s/pedantic-carson-xwydd?file=/src/App.js:699-805

Vue JS : Getting Unique ID and Value of an array of input at the same time

I am new in Vue and still learning using it. I am learning to use the Element UI for Vue UI. Specifically, I'm trying the Input Number Component, for an array of data. Let say I have my data like this:
dataList = [{
id: 1,
productName: "ABC",
qty: 1
}, {
id: 2,
productName: "DEF",
qty: 2
}];
And the element goes like this:
<div v-for="(item, index) in dataList" v-bind:key="item.id">
<el-input-number v-model="item.qty" #change="handleChange"></el-input-number>
</div>
And for the script goes like this:
<script type="text/javascript">
handleChange = function (value) {
console.log(value); /* I need the item.id also not just the qty value */
};
</script>
from the function handleChange() I only can get the value of input number, but not the item id that I've assign in the element. How can I get both of that (item.id and value)?
I was expecting a js function like this could work, but it didnt:
handleChange = function(item, value) { /* */ }
I've been trying to google for some answer, but the answer always showing only 1 parameter that i can acquire from change event.
Any help would be appreciated, thank you.
You could pass your value ($event) as first parameter and the other parameter as the second one :
<div v-for="(item, index) in dataList" v-bind:key="item.id">
<el-input-number v-model="item.qty" #change="handleChange($event,item.id)"></el-input-number>
</div>
Script :
<script type="text/javascript">
handleChange = function (value,id) {
console.log(value,id);
};
</script>

Categories

Resources