I am having a little problem that it's making headaches.
I have a modal where I show some info with checkboxes, the information, comes from an array, and I set the checkbox states from that array, and example of the array:
this.array = [
{scope: "acc", code: "1", alias: "aaa", selected: true, editable: true},
{scope: "acc", code: "2", alias: "bbb", selected: true, editable: true}
]
The thing that I want to do is to play as normal with the checks, but when i click a discardChanges button, the checkboxes, return the state that they were previously.
<div *ngFor="let account of allAccountsList; let i = index;" class="">
<div class="row">
<input (click)="saveCheck(account.code, account.scope)" [(checked)]="account.selected"
type="checkbox" name="genres" value="adventure" id="{{i}}">
<label for="{{i}}" style="font-family: 'SExtralight'; font-size:14px;"></label>
</div>
</div>
Thank you all.
This code:
<div *ngFor="let account of array1; let i = index;">
<input [checked]="account.selected"
type="checkbox"
name="genres"
id="{{i}}">
<label for="{{i}}"
style="font-size:14px;">{{ account.alias }}
</label>
</div>
Does not update the selected property of the underlying array.
This code:
<div *ngFor="let account of array2; let i = index;">
<input [(ngModel)]="account.selected"
type="checkbox"
name="genres"
id="{{i}}">
<label for="{{i}}"
style="font-size:14px;">{{ account.alias }}
</label>
</div>
<div>
Does update the selected property of the underlying array.
Use the first set of code if you want to ensure that the underlying array data is not changed.
But if you do need to track the changes as the user clicks and allow for a discard changes option, use the second set of code. Then in the component, copy the array to keep the original values:
ngOnInit() {
// Save a copy of the original values
this.array2Copy = this.array2.map(e => ({...e}));
console.log(JSON.stringify(this.array2Copy));
}
discardChanges() {
// Copy the original values over the array
this.array2 = this.array2Copy.map(e => ({...e}));
console.log(JSON.stringify(this.array2Copy));
}
I have a stackblitz of this code here:https://stackblitz.com/edit/angular-arraycopy-deborahk
Related
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.
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;
},
I have a list of checkboxes that are rendered with a v-for on a component. When checked, the checkboxes fill up an array with currently selected checkboxes. The problem comes when one or more of the checked checkboxes is removed - the v-model still counts the removed checkbox. How do I update the v-model as my array updates? I tried force re-rendering the whole component which solves the problem but it's not the solution I need.
<div v-for="player in players" :key="player.id">
<input
v-model="selectedPlayers"
:value="player.id"
type="checkbox"
:id="player.id"
/>
<label :for="player.id">
{{ player.name }}
</label>
</div>
Sandbox
Problem
Desired outcome
V-model won't remove data when the component is no longer rendered, you need to do that explicitly.
You could filter selectedPlayers from the #click handler so that it only includes ids that are in the new variable.
this.selectedPlayers = this.selectedPlayers.filter(
id => players2.find(
player => player.id === id
)
)
So from what I understand, you have this player2 array, that you need to compare against. Whatever is there in the players2 array needs to be there in the selectedPlayers array. To do this just use the use map array function to iterate over the players2 array to return only the ids of the players and then store them in the selected players array which being a reactive property will automatically patch the DOM. There's absolutely no need to re-render the component.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: "#app",
data() {
return {
selectedPlayers: [],
players: [
{name: "Arnold",id: "1"},
{name: "Rambo",id: 2},
{name: "Terminator",id: 3},
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
players2: [
{name: "Titan",id: 4},
{name: "Odin",id: 5},
],
};
},
methods: {
clicked() {
this.players = this.players2;
this.selectedPlayers = this.players2.map(p => p.id);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
If you check all checkboxes and press the button, the array still contains all elements
<div v-for="player in players" :key="player.id">
<input v-model="selectedPlayers" :key="players.length" :value="player.id" type="checkbox" :id="player.id" />
<label :for="player.id">
{{ player.name }}
</label>
</div>
{{ selectedPlayers }}
<button #click="clicked">Press me</button>
</div>
I have a view with an input field, which can be multiplicated by a given button. The problem is that after any entry of a char, the focus of the input field is lost. You have to click again to enter another char.
Do someone have a clue what could be the problem?
My model:
'model': [
...,
'filter': [
...,
'something': [
'string'
]
]
]
My code:
<div v-for="(something, index) in model.filter.something" v-bind:key="something">
<input type="text" v-model.trim="model.filter.something[index]"/>
</div>
The problem is that you are using a changing value as key. Vue expects key to indicate a unique identifier for the item. When you change it, it becomes a new item and must be re-rendered.
In the snippet below, I have two loops, both using the same data source. The first is keyed the way you have it set up. The second uses index instead (that may not be what you need, but the point is to use something other than what you're editing; in this example, key isn't needed anyway). The first exhibits the loss-of-focus you describe, the second works as expected.
new Vue({
el: '#app',
data: {
'model': {
'filter': {
'something': [
'string'
]
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<div v-for="(something, index) in model.filter.something" v-bind:key="something">
<input type="text" v-model.trim="model.filter.something[index]" />
{{something}}
</div>
<div v-for="(something, index) in model.filter.something">
<input type="text" v-model.trim="model.filter.something[index]" :key="index" />
{{something}}
</div>
</div>
I'm a little wired with VueJS states.
This is my simple app :
new Vue({
el: '#media-app',
data: {
activeTypes: [],
activeCategories: [],
medias: []
},
methods: {
getFilteredData: function () {
// some computing needed
// refresh vue
Vue.set(me, "medias", []);
},
filterMedia: function () {
console.debug(this.activeCategories);
console.debug(this.activeTypes);
this.getFilteredData();
}
}
});
And some HTML stuff:
<input type="checkbox" id="i1" value="i1" class="filter categories" v-model="activeCategories" v-on:click="filterMedia()">
<label for='i1'>My cat 1</label>
</div>
#{{ activeCategories }}
When I check the checkbox, the template displays #{{ activeCategories }} correctly with "i1". But the console.debug(this.activeCategories) displays an empty array. I have to put that debug into an updated method to get the correct value. But if I do that, I cannot call a method which change the data or I'll get into an infinity loop…
So, where should I call my filterMedia function to be able to access updated values from activeCategories ?
Thanks for your help.
Try the onchange event:
<input type="checkbox" id="i1" value="i1" class="filter categories"
v-model="activeCategories" v-on:change="filterMedia()">