The input field is not updating "address" in the array - javascript

I am creating a table which is looping through an array and displays the array in the table as list. I am displaying the address as an input field value :value="location.address"
The input fields are set to disabled and when I click on the edit button I updated the disabled property to false so that the input field can be edited. I have added a new property called editedAddress: null and set that to null which is updated to the current address property this.editedAddress = this.locations[index].address.
What I want is that when I click on the edit button, I want the address to be updated. I have added the following code for the update button but it does not work.
btnUpdate(index){
this.locations[index].address = this.editedAddress;
this.locations[index].disabled = !this.locations[index].disabled
}
Here is the full code
<template>
<div>
<table>
<tr>
<th>#</th>
<th>Locations</th>
<th>Actions</th>
</tr>
<tr v-for="(location, index) in locations" :key="index">
<td>{{index + 1}}</td>
<td>
<input type="text" :value="location.address" :disabled="location.disabled">
</td>
<td>
<div class="action-btns">
<button #click="btnEdit(index)">Edit</button>
<button #click="btnUpdate(index)">Update</button>
<button #click="btnDelete(index)">Delete</button>
</div>
</td>
</tr>
</table>
<input type="text" v-model="address">
<button #click="addBtn">Add</button>
</div>
</template>
<script>
export default {
data(){
return{
locations:[
{
address:'Mall of lahore',
disabled: true
},
{
address: 'The Post Office',
disabled: true
},
{
address: 'Mall of Dubai',
disabled: true
}
],
address: '',
editedAddress: null
}
},
methods:{
btnEdit(index){
this.locations[index].disabled = !this.locations[index].disabled
this.editedAddress = this.locations[index].address
},
btnUpdate(index){
this.locations[index].address = this.editedAddress;
this.locations[index].disabled = !this.locations[index].disabled
},
btnDelete(index){
this.locations.splice(index , 1)
},
addBtn(){
let newAddress = {
address: this.address,
disabled: true
}
this.locations.push(newAddress)
this.address = '';
}
}
}
</script>
Please let me know what I am doing wrong or if there is a better way to solve it

Your input field is bound to location.address.
So, you are not editing your editedAddress at all.
You can add #change="editedAddress = $event.target.value" to your input field to change editedAddress
<input type="text" :value="location.address" :disabled="location.disabled" #change="editedAddress = $event.target.value" >
Tip: use Vue Dev Tools or JSON.stringify to check the data in your vue app
JSON.stringify(editedAddress): {{JSON.stringify(editedAddress)}}
Here is the link playground with the fix

Since you don't explain what exactly do you mean by "I have added the following code for the update button but it does not work" I will assume that you UI is not being updated.
This is a caveats of reactivity in Vue reactive system https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
Probably Vue is not being able to pick that there were a change in your item inside the array locations.
To make Vue understand that there was a change you can use Vue.set:
btnUpdate(index){
Vue.set(this.locations[index], 'address', this.editedAddress);
// do the same for the other line
},
After this Vue should be able to pick that there was a change and re-render the UI for you.
Another approach is to replace the entire array (but in most cases this is a bit overkill)
btnUpdate(index){
const locations = [...this.locations];
locations[index].address = this.editedAddress;
this.locations = locations;
},

Related

Vue: Input Manual Autocomplete component

I have a vue-cli project, that has a component named 'AutoCompleteList.vue' that manually handled for searching experience and this component has some buttons that will be fill out the input.
It listens an array as its item list. so when this array has some items, it will be automatically shown; and when I empty this array, it will be automatically hidden.
I defined an oninput event method for my input, that fetches data from server, and fill the array. so the autocomplete list, will not be shown while the user doesn't try to enter something into the input.
I also like to hide the autocomplete list when the user blurs the input (onblur).
but there is a really big problem! when the user chooses one of items (buttons) on the autocomplete list, JS-engine first blurs the input (onblur runs) and then, tries to run onclick method in autocomplete list. but its too late, because the autocomplete list has hidden and there is nothing to do. so the input will not fill out...
here is my code:
src/views/LoginView.vue:
<template>
<InputGroup
label="Your School Name"
inputId="schoolName"
:onInput="schoolNameOnInput"
autoComplete="off"
:onFocus="onFocus"
:onBlur="onBlur"
:vModel="schoolName"
#update:vModel="newValue => schoolName = newValue"
/>
<AutoCompleteList
:items="autoCompleteItems"
:choose="autoCompleteOnChoose"
v-show="autoCompleteItems.length > 0"
:positionY="autoCompletePositionY"
:positionX="autoCompletePositionX"
/>
</template>
<script>
import InputGroup from '../components/InputGroup'
import AutoCompleteList from '../components/AutoCompleteList'
export default {
name: 'LoginView',
components: {
InputGroup,
AutoCompleteList
},
props: [],
data: () => ({
autoCompleteItems: [],
autoCompletePositionY: 0,
autoCompletePositionX: 0,
schoolName: ""
}),
methods: {
async schoolNameOnInput(e) {
const data = await (await fetch(`http://[::1]:8888/schools/${e.target.value}`)).json();
this.autoCompleteItems = data;
},
autoCompleteOnChoose(value, name) {
OO("#schoolName").val(name);
this.schoolName = name;
},
onFocus(e) {
const position = e.target.getBoundingClientRect();
this.autoCompletePositionX = innerWidth - position.right;
this.autoCompletePositionY = position.top + e.target.offsetHeight + 20;
},
onBlur(e) {
// this.autoCompleteItems = [];
// PROBLEM! =================================================================
}
}
}
</script>
src/components/AutoCompleteList.vue:
<template>
<div class="autocomplete-list" :style="'top: ' + this.positionY + 'px; right: ' + this.positionX + 'px;'">
<ul>
<li v-for="(item, index) in items" :key="index">
<button #click="choose(item.value, item.name)" type="button">{{ item.name }}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'AutoCompleteList',
props: {
items: Array,
positionX: Number,
positionY: Number,
choose: Function
},
data: () => ({
})
}
</script>
src/components/InputGroup.vue:
<template>
<div class="input-group mb-3">
<label class="input-group-text" :for="inputId ?? ''">{{ label }}</label>
<input
:type="type ?? 'text'"
:class="['form-control', ltr && 'ltr']"
:id="inputId ?? ''"
#input="$event => { $emit('update:vModel', $event.target.value); onInput($event); }"
:autocomplete="autoComplete ?? 'off'"
#focus="onFocus"
#blur="onBlur"
:value="vModel"
/>
</div>
</template>
<script>
export default {
name: 'input-group',
props: {
label: String,
ltr: Boolean,
type: String,
inputId: String,
groupingId: String,
onInput: Function,
autoComplete: String,
onFocus: Function,
onBlur: Function,
vModel: String
},
emits: [
'update:vModel'
],
data: () => ({
}),
methods: {
}
}
</script>
Notes on LoginView.vue:
autoCompletePositionX and autoCompletePositionY are used to find the best position to show the autocomplete list; will be changed in onFocus method of the input (inputGroup)
OO("#schoolName").val(name) is used to change the value of the input, works like jQuery (but not exactly)
the [::1]:8888 is my server that used to fetch the search results
If there was any unclear code, ask me in the comment
I need to fix this. any idea?
Thank you, #yoduh
I got the answer.
I knew there should be some differences between when the user focus out the input normally, and when he tries to click on buttons.
the key, was the FocusEvent.relatedTarget property. It should be defined in onblur method. here is its full tutorial.
I defined a property named isFocus and I change it in onBlur method, only when I sure that the focus is not on the dropdown menu, by checking the relatedTarget

How to get multiple checkboxes value

I am working on an admin panel.I have 3 checkboxes for permissions of 3 things. I am getting the value of checkbox and name of permission from apidata. I am having trouble updating the checkbox value and then sending it back to the api.
How can i update it? I have searched on the internet but i have not find any solution.
Here is the code.
const [permissions, setPermissions] = useState({
checklist:{
isView:false,
isEdit:false,
isDelete:false },
incidences:{
isView:false,
isEdit:false,
isDelete:false },
dashboardt:{
isView:false,
isEdit:false,
isDelete:false
}});
{PermissionList.map((item) => {
return (
<tr>
<td>
<div className="d-flex align-items-center">
<div className="ms-2">
<h6 className="mb-1 font-14">{item.permissionName}</h6>
</div>
</div>
</td>
<td>
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={eval(`permissions.${item.permissionName}.isView`)}
checked={permissions.isView}
onChange={(e)=>{
setPermissions({
isView:e.target.checked,
isEdit:false,
isDelete:false
})
}}
aria-label="..."
/>
</div>
</td>
<td>
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={permissions.isEdit}
checked={permissions.isEdit}
onChange={(e)=>{
setPermissions({
isView:false,
isEdit:e.target.checked,
isDelete:false
})
}}
aria-label="..."
/>
</div>
</td>
<td>
<div className="d-flex order-actions">
<div className="d-flex order-actions">
<input
className="form-check-input fs-4"
type="checkbox"
id={item.permissionId}
value={permissions.isDelete}
checked={permissions.isDelete}
onChange={(e)=>{
setPermissions({
isView:false,
isEdit:false,
isDelete:e.target.checked
})
}}
aria-label="..."
/>
</div>
</div>
</td>
</tr>
);
})}
And here is the snapshot of the component
And I want this in payload sent to api:
[
{
"groupId": 1,
"permissionId": 1,
"isView": false,
"isEdit": true,
"isDelete": true
}
]
While i get the groupId and permissionId from the state and component Id how can i get the rest of three updated values. And also how can i dynamically send it like only send that permission id whose value is updated and not send the rest of 2 or 1.
There is a variant of the setter that will pass the value to a thunk function. Use that in the onClick, spread it to return that object but with just the one checkbox value updated.
onClick={setPermissions((p)=>{...p, isDelete:e.target.checked})}
Also, it's very bad practice to use eval.
// Change this:
value={eval(`permissions.${item.permissionName}.isView`)}
// To this:
value={permissions[item.permissionName].isView}
EDIT:
just make a copy of the state passed into the setter, and update the value you want to change.
onClick={
(e) => {
setPermissions(p => {
const prev = { ...p }
prev.checklist.isView = e.target.checked
return prev
})
}
}
EDIT 2:
You can call a method onClick that will update your component's state and push up to the server. Something like this:
const onCheckboxChanged = async (whichProperty, whichCheckbox, checked) => {
// update your component's state and cause component to re-render
const updatedPermissions = {...permissions}
updatedPermissions[whichProperty][whichCheckbox] = checked
setPermissions(updatedPermissions)
// you can just update the contents of your state property
// directly and not change like the next line, but it will
// not cause the component to re-render so your checkboxes
// may not update.
permissions[whichProperty][whichCheckbox] = checked
// next you can notify server, spread the view/edit/delete
// keys from your permissions object into the object you're
// sending to the server.
await pushToServer(JSON.stringify(
[{
"groupId": 1,
"permissionId": getIdFromProperty(whichProperty),
...updatedPermissions[whichProperty]
}]
))
}
Something like that. And your onChange will just call that function:
onChange={(e)=>{onCheckboxChanged(whichProperty, "checklist", e.target.checked)}}
EDIT 3:
What I recommend is that you add a "test" button in your UI. When you press it, it will send the payload to the server. Just hard-code the payload. Get that working. Then make 1 checkbox, and update your code so you can change the checkbox and when you press 'test' it will send that value to your server. If you want to update the server ever time a checkbox changes, then add that functionality (but with just 1 checkbox). Then add the other two checkboxes, but only 1 property. And finally, update the code to support multiple properties. Take small steps. Get just 1 part working and then expand on it. And maybe - don't even work on this issue. Just spend 3-7 hours following some tutorials and when you return you'll get this working very quickly.

Array change detection for an array of complex objects in Vue JS 2

Update
Vue JS 3 will properly handle this: https://blog.cloudboost.io/reactivity-in-vue-js-2-vs-vue-js-3-dcdd0728dcdf
Problem:
I have a vue component that looks like this:
sub-comp.vue
<template>
<div>
<input type="text" class="form-control" v-model="textA">
<input type="text" class="form-control" v-model="textB">
<input type="text" class="form-control" v-model="textC">
</div>
</template>
<script>
export default {
props: {
textA: {
type: Number,
required: false
},
textB: {
type: Number,
required: false
},
textC: {
type: Number,
required: false
}
}
}
</script>
I have a parent component that looks like this:
layout-comp.vue
<template>
<div>
<button #click="addItem">Add</button>
<ul>
<li v-for="listItem in listItems"
:key="listItem.id">
<sub-comp
:textA="listItem.item.textA"
:textB="listItem.item.textB"
:textC="listItem.item.textC"
/>
</li>
</ul>
</div>
</template>
import subComp from '../sub-comp.vue'
export default {
components: {
subComp
},
data() {
return {
listItems: []
}
},
methods: {
addItem: function () {
var item = {
textA: 5,
textB: 100,
textC: 200
}
if (!item) {
return
}
this.length += 1;
this.listItems.push({
id: length++,
item: item
});
}
}
</script>
The thing is, anything I do to edit the textboxes, the array doesn't get changed, even though the reactive data shows that it changed. For example, it will always be as
{
textA: 5,
textB: 100,
textC: 200
}
Even if I changed textB: 333, the listItems array still shows textB: 100. This is because of this:
https://v2.vuejs.org/v2/guide/list.html#Caveats
Due to limitations in JavaScript, Vue cannot detect the following changes to an array
Question:
I'm wondering how do I update the array? I also want the change to occur when leaving the textbox, using the #blur event. I'd like to see what ways this can be done.
I read these materials:
https://codingexplained.com/coding/front-end/vue-js/array-change-detection
https://v2.vuejs.org/v2/guide/list.html
But it seems my example is a bit more complex, as it has indexes associated, and the arrays have complex objects.
Update 4/12/2018
Found out that in my addItem() that I had:
item = this.conditionItems[this.conditionItems.length - 1].item);
to
item = JSON.parse(JSON.stringify(this.conditionItems[this.conditionItems.length - 1].item));
I was thinking the sync modifier in the answer below was causing problems because it duplicated all items. But that's not the case. I was copying a vue object (including the observable properties), which caused it to happen. The JSON parse and JSON stringify methods only copies the properties as a normal object, without the observable properties. This was discussed here:
https://github.com/vuejs/Discussion/issues/292
The problem is that props flow in one direction, from parent to child.
Setting the value using v-model in child won't affect parent's data.
Vue has a shortcut to update parent's data more easily. It's called .sync modifier.
Here's how.
In sub-comp.vue
<template>
<div>
<input type="text" class="form-control" :value="textA" #input="$emit('update:textA', $event.target.value)" >
<input type="text" class="form-control" :value="textB" #input="$emit('update:textB', $event.target.value)">
<input type="text" class="form-control" :value="textC" #input="$emit('update:textC', $event.target.value)">
</div>
</template>
<script>
export default {
// remains the same
}
</script>
add .sync when you add the props
<sub-comp
:textA.sync="listItem.item.textA" // this will have the same effect of v-on:update:textA="listItem.item.textA = $event"
:textB.sync="listItem.item.textB"
:textC.sync="listItem.item.textC"
/>
update:
if you have reactivity problem, don't use .sync, add a custom event and use $set
<sub-comp
:textA="listItem.item.textA" v-on:update:textA="$set('listItem.item','textA', $event)"
/>

VueJS - Updating variable in another vue

I have a vue, which uses an accordion table that displays the data when a particular row is selected. There is a button "Edit" which hides the data and shows a form.
The form is in another vue (to separate them out out..) The form is showing on clicking the button, however, inside the form I have another button "Save" which calls an ajax request, then hides the form and shows the data.
The problem I'm having is that I cannot seem to figure out how I can update the variable inside the first vue from the second vue. I could use the store but this is not an option as it would update for everyone, whereas it should only update for the particular user.
Enquiries vue: (HTML)
<tr v-if="row.id in expanded">
<td :colspan="9" style="background-color: #F0FFFF;">
<div class="accordian-body">
<div v-if="editing != false">
<enquiries-view-edit></enquiries-view-edit>
</div>
<div v-else>
<div class="container">
<div class="pull-right">
<button type="button" class="btn btn-primary btn-md" #click="editing = !editing">Edit</button>
</div>
</div>
</div>
</div>
</td>
</tr>
Javascript:
export default {
components: {
Multiselect
},
data() {
return {
msg: 'This is just an estimation!',
tooltip: {
actual_price: 'Click on the price to edit it.'
},
expanded: {},
replacedCounter: 0,
theModel: [],
enquiry: [],
center: {lat: 53.068165, lng: -4.076803},
markers: [],
needsAlerting: false,
editing: false
}
},
}
Inside EnquiriesVue I have:
export default {
props: ['editing'],
computed: {
editing: function {
console.log("Computed the value");
}
}
}
I have tried to compute the value, but this is not doing anything inside the console.
EDIT:
Basically, inside enquiries-view-edit I want a button where you click on it, and it updates the variable editing inside the Enquiries vue so that the form hides and the data vue is then shown.
A child component can communicate with its parent by emitting events. Like this:
export default {
props: ['editing'],
methods: {
onClick: function {
console.log("Clicked on child!");
this.$emit('stop-editing');
}
}
}
This assumes you have something like this in your child component's template:
<button #click="onClick">Stop editing</button>
You could then "listen" to that event from the parent component as you would any other native event, by registering a handler when including the child component.
<enquiries-view-edit #stop-editing="editing = !editing"></enquiries-view-edit>
Obviously this is a very simple example. You can also pass data along with your emitted event for more complex scenarios.

Vue.js Update value of nested props sync object in component

Hi i have following problem for Vue.js v1.0.28 - I have component
Vue.component('question-editor', {
template: require('./question-editor.html'),
props: ['question'],
methods: {
addChoice() {
this.question.choicesArr.push({
id: null,
body:'zzz',
icon:'',
next:null,
});
console.log(this.question.choicesArr);
},
}
});
Where ./question-editor.html :
...
<div class="box-body">
<div class="form-group" v-for="choice of question.choicesArr">
<input v-model="choice.body" type="text" class="form-control">
</div>
</div>
<div class="box-footer">
<button #pointerdown="addChoice" type="submit" class="btn btn-primary">
Add choice
</button>
</div>
I use this component in parent component in this way:
<question-editor :question.sync="currentQuestion"></question-editor>
The problem is that when I push button "Add choice" and method addChoice() is run, i see in console that property question.choicesArr have new element - but view doesnt change (I don't see this new element on screen - so the v-for not "see" this new element and not refresh itself). What to do inside addChoice() to refresh view to see new element in question.choicesArr on screen ?
I guess vue 1.x, does not detect changes in array as it does in 2.x, you can do following to let vue know that array has been changed with the help of spread operator.
addChoice() {
this.question.choicesArr= [...this.question.choicesArr, {
id: null,
body:'zzz',
icon:'',
next:null,
}];
}
I found the solution:
addChoice() {
this.question.choicesArr.push({
id: null,
body:'zzz',
icon:'',
next:null,
});
this.question = Object.assign({}, this.question, this.question);
console.log(this.question.choicesArr);
},
The statement this.question = Object.assign({}, this.question, this.question); will set __ob__:Observer and __v-for__1:Fragment to the new objects pushed to array.
I also try this.$set(this.question, 'question.choicesArr', this.question.choicesArr); but this one set only __ob__:Observer (not __v-for__1) so v-for will not updated.
I read about it here: https://v2.vuejs.org/v2/guide/reactivity.html

Categories

Resources