I have the following task list app. I am trying to implement a delete function. I get I need to use splice to remove the item from the array but how do I target only the item whose button I clicked on?
https://jsfiddle.net/clintongreen/txtrage5/1/
JS
new Vue({
el: '#tasks',
data: {
message: 'Tasks',
completed: null,
newTaskName: '',
tasklist: [
{ description: 'Read', completed: true },
{ description: 'Write', completed: true },
{ description: 'Edit', completed: false },
{ description: 'Publish', completed: false }
]
},
methods: {
completeTask: function(task){
// console.log(this.tasks.description);
task.completed = true;
},
newTask: function(){
this.tasklist.push({description: this.newTaskName, completed: false});
},
removeTask: function(task){
this.tasklist.splice(this.task.index, 1);
}
}
})
HTML
<div class="container" id="tasks">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{{ message }}
</h3>
</div>
<ul class="list-group">
<li class="list-group-item clearfix" v-for="task in tasklist" >
{{ task.description }}
<!-- <button class="btn btn-primary btn-sm pull-right" v-if="!task.completed" #click="completeTask(task)">Complete</button> -->
<div class="btn-group btn-group-sm pull-right" role="group" v-if="!task.completed">
<button type="button" class="btn btn-primary" #click="completeTask(task)">Complete</button>
<button type="button" class="btn btn-info">Edit</button>
<button type="button" class="btn btn-danger" #click="removeTask(task)">Delete</button>
</div>
<button class="btn btn-default btn-sm completed text-muted pull-right disabled" v-else>Completed</button>
</li>
<li class="list-group-item clearfix">
<input v-model="newTaskName" #keyup.enter="newTask" type="text" class="pull-left">
<button class="btn btn-success btn-sm pull-right" #click="newTask">Add Task</button>
</li>
</ul>
</div>
</div>
Use the index of the task in the v-for to decide which item to splice():
v-for="(task, index) in tasklist"
Your button:
<button type="button" class="btn btn-danger" #click="removeTask(index)">Delete</button>
And then simply:
removeTask: function(index) {
this.tasklist.splice(index, 1);
}
removeTask: function(task){
this.tasklist.splice(this.tasklist.indexOf(task), 1);
}
For Vue2 use delete. See API.
removeTask: function(index){
this.$delete(this.tasklist, index)
}
You can use indexOf to get the current index from the array
remove: function (task){
this.tasklist.splice(this.tasklist.indexOf(task),1);
},
Related
I am developing a web page. there i am using v-modal with vuejs. I am making an object form new Form. it gets. in the html it inserts the id which is input by user but i want to replace that with the userID gotten from the currently logged in user. i tried getter and settter in the script but it does not change the form.id. it takes the input from user . the following is my code. sorry for the messy code.
<template>
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0">Applications Page</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><router-link to="/dashboard">Home</router-link></li>
<li class="breadcrumb-item active">About Page</li>
</ol>
</div>
</div>
</div>
</div>
<div class="content">
<div class="container-fluid">
<div class="row mt-4">
<div class="col-md-12">
<div class="card">
<div class="card-header">
<h3 class="card-title">Application</h3>
<div class="card-tools">
<button class="btn btn-success" #click="newModal">Add New
<i class="fa-regular fa-calendar-plus fa-fw"></i>
</button>
</div>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-hover text-nowrap">
<thead>
<tr>
<th>ID</th>
<th>User_ID</th>
<!-- <th>body</th> -->
<!-- <th>category_id</th> -->
<!-- <th>user_id</th> -->
<th>Job_ID</th>
<th>Status</th>
<!-- <th>image</th> -->
<th>Modify</th>
</tr>
</thead>
<tbody>
<tr v-for="application in applications.data" :key="application.id">
<td> {{ application.id }}</td>
<td>{{ application.user_id }}</td>
<td>{{ application.job_id }}</td>
<td> {{ application.status }}</td>
<td>
<a href="#" #click="editModal(application)">
<i class="fa fa-edit blue"></i>
</a> /
<a href="#" #click="deleteApplication(application.id)">
<i class="fa fa-trash red"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" v-show="!editmode" id="myModalLabel">Add New Application</h1>
<h1 class="modal-title fs-5" v-show="editmode" id="myModalLabel">Udate Application</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form #submit.prevent="editmode ? updateApplication() : createApplication() ">
<div class="modal-body">
<div class="form-group">
<input v-model="form.user_id" type="text" name="user_id" placeholder="user_id"
class="form-control" :class="{ 'is-invalid': form.errors.has('user_id') }">
<has-error :form="form" field="user_id"></has-error>
</div>
<!-- <input :value="userID" > -->
<!-- <div class="form-group"></div> -->
<!-- <select v-model="form.user_id">
<option :value=userID.id></option>
</select>
</div> -->
<!-- <input v-model="form.user_id" type="text" name="user_id" placeholder="user_id"
class="form-control" :class="{ 'is-invalid': form.errors.has('user_id') }"> <has-error :form="form" field="user_id"></has-error> -->
<div class="form-group">
<textarea v-model="form.job_id" type="text" name="job_id" placeholder="job_id"
class="form-control" :class="{ 'is-invalid': form.errors.has('job_id') }"></textarea>
<has-error :form="form" field="job_id"></has-error>
</div>
<div class="form-group">
<input v-model="form.status" type="text" name="status" placeholder="status"
class="form-control" :class="{ 'is-invalid': form.errors.has('status') }">
<has-error :form="form" field="status"></has-error>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">Close</button>
<!-- <button type="submit" class="btn btn-primary" data-bs-dismiss="modal">Create</button> -->
<button v-show="editmode" type="submit" class="btn btn-success">Update</button>
<button v-show="!editmode" type="submit" class="btn btn-primary">Create</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
import Form from 'vform';
import Swal from 'sweetalert2/dist/sweetalert2.js';
import 'sweetalert2/src/sweetalert2.scss';
export default {
data: () => ({
editmode: false,
applications: {},
// catIDs: {},
userID: {},
form: new Form({
id: '',
user_id: '',
job_id: '',
status: '',
// get newUserID (){
// return this.user_id;
// },
// set newUserID(newUser_id){
// this.user_id = newUser_id[0];
// }
})
}),
methods: {
updateApplication(id) {
this.form.put('api/application/' + this.form.id)
.then(() => {
$('#myModal').modal('hide');
Swal.fire('Application updated!');
// this.$emit('afterCreate');
this.loadApplication();
})
.catch(() => {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Something went wrong!',
})
});
},
newModal() {
this.editmode = false;
this.form.reset();
$('#myModal').modal('show');
},
editModal(application) {
this.editmode = true;
this.form.reset();
$('#myModal').modal('show');
this.form.fill(application);
},
deleteApplication(id) {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
this.form.delete('api/application/' + id).then(() => {
Swal.fire(
'Deleted!',
'Application has been deleted.',
'success'
);
this.loadApplication();
}).catch((result) => {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Something went wrong!',
})
});
}
})
},
loadApplication() {
axios.get("api/application").then(({ data }) => (this.applications = data));
},
loadUserID() {
axios.get('api/applicationGetID').then(({ data }) => (this.userID = data));
console.log(this.form.user_id);
// axios.get('api/applicationGetID').then(({ data }) => (this.userID2 = data.[id]));
},
// loadCatId() {
// axios.get('api/categoryId').then(({ data }) => (this.catIDs = data));
// },
createApplication() {
// this.form.newUserID = this.userID;
console.log(this.form.user_id)
this.form.post('api/application')
.then(() => {
$('#myModal').modal('hide');
Swal.fire('Application created!')
this.loadApplication();
})
.catch(() => {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Something went wrong!',
})
})
}
},
created() {
this.loadApplication();
this.loadUserID();
// this.loadCatId();
}
}
</script>
I am trying my first vanilla JS CRUD DOM manipulation project.
I have completed the add item function however delete function isn't working properly.
There are multiple delete buttons on HTML code, and I want it to be deleted when the button clicked. However, it is only working for the first item from the list. If I click the second or third delete button, it is just not working at all.
document.querySelector('li').addEventListener('click', deleteOrTick);
//deleteTick
function deleteOrTick(e) {
console.log("check click")
if (e.target.className == 'delete') {
console.log("delete clicked")
}
deleteTask(e);
// delete task
function deleteTask(e) {
let remove = e.target.parentNode;
let parentNode = remove.parentNode;
parentNode.removeChild(remove);
}
}
deleteOrTick();
<ul class="card-list row m-2 d-flex flex-row justify-content-center" id="card">
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="btn btn-outline-info">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">おはよう</h2>
<p class="card-text">Good morning</p>
</div>
<button type="button" class="btn btn-outline-info">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="btn btn-outline-info">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
</ul>
document.querySelector returns the first of the selected elements.
Also do not have one function inside the other
Lastly why call the delete when you load?
I think you meant to delegate
document.getElementById('card').addEventListener('click', function(e) {
const tgt = e.target;
const parent = tgt.closest('li');
if (tgt.classList.contains('delete')) parent.remove();
else if (tgt.classList.contains('update')) console.log("Update clicked for ",parent.querySelector(".card-title").textContent)
})
<ul class="card-list row m-2 d-flex flex-row justify-content-center" id="card">
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="btn btn-outline-info update">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">おはよう</h2>
<p class="card-text">Good morning</p>
</div>
<button type="button" class="btn btn-outline-info update">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
<li class="card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="btn btn-outline-info update">update</button>
<button type="button" class="btn btn-outline-danger delete">delete</button>
</li>
</ul>
Because querySelector() returns only the first match:
The Document method querySelector() returns the first Element within the document that matches the specified selector, or group of selectors. If no matches are found, null is returned.
window.getElementsByTagName() could be more appropriate for your case
I always recommend doing it like this.
Add some semantic to your HTML code. In this case add the classNames translation-card-list, translation-card, etc
Make use of event bubbling by delegating the event listening to the document element and working with the target property of the event argument. This way you have only one Listener improving performance and by delegating you can add buttons at run time without adding new EventListeners in a complicated manner
Use the closest method of the Event's target to use the semantic I added in section 1
;(function(d) {
const classNameCard = 'translations-card';
const classNameUpdateButton = 'translations-card-update-button';
const classNameDeleteButton = 'translations-card-delete-button';
const onClick = function(e) {
const target = e.target
if (!target.closest(`.${classNameCard}`)) {
return false
}
if (target.closest(`.${classNameUpdateButton}`)) {
updateAction(e);
} else if (target.closest(`.${classNameDeleteButton}`)) {
deleteAction(e);
}
}
const updateAction = function(e) {
console.log('update')
}
const deleteAction = function(e) {
const card = e.target.closest(`.${classNameCard}`);
const cardList = card.parentNode;
cardList.removeChild(card);
}
d.addEventListener('click', onClick)
}(document));
<ul class="translations-card-list card-list row m-2 d-flex flex-row justify-content-center" id="card">
<li class="translations-card card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="translations-card-update-button btn btn-outline-info">update</button>
<button type="button" class="translations-card-delete-button btn btn-outline-danger delete">delete</button>
</li>
<li class="translations-card card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">おはよう</h2>
<p class="card-text">Good morning</p>
</div>
<button type="button" class="translations-card-update-button btn btn-outline-info">update</button>
<button type="button" class="translations-card-delete-button btn btn-outline-danger delete">delete</button>
</li>
<li class="translations-card card border-success m-3" style="width: 18rem;">
<div class="card-body">
<h2 class="card-title">にほんご</h2>
<p class="card-text">Japanese</p>
</div>
<button type="button" class="translations-card-update-button btn btn-outline-info">update</button>
<button type="button" class="translations-card-delete-button btn btn-outline-danger delete">delete</button>
</li>
</ul>
I have a array of objects(comments from posts kind like facebook comments or any other social network) and I iterate it with an v-for, then I have on each comment a dropdown button with "edit" and "delete" buttons, I would like that when I press the edit button the comment would change to an input so I can edit it, so I added a property click_to_edit initialize in false to each comment object, so when I click on edit it changes the condition that the v-for is related to. But it does not change it, I guess it is that as the property is inside an object it always return false and therefore the condition never changes, but I do not know how else do this. This is the html for the v-for:
<div
class="mb-2"
v-bind:class="'comment_'+post.id"
v-for="(comment, index) in comments"
:key="index"
v-if="comment.id_post === post.id"
>
<div class="row">
<div class="col-img-user-post text-center">
<img
class="rounded-circle img-icon-profile"
:src="routes.user_files+'/'+comment.code+'/'+comment.picture"
alt
/>
</div>
<div class="col">
<div class="row">
<div class="col">
<div class="card-comment">
<div class="row">
<div v-if="!comment.clicked_to_edit" class="col">
<p class="mt-2 mb-2">{{ comment.description }}</p>
<p class="mb-0 font-smaller grey-color">{{ comment.time_ago }}</p>
</div>
<div v-if="comment.clicked_to_edit" class="col">
<input v-model="comment.description" type="text" />
<p class="mb-0 font-smaller grey-color">{{ comment.time_ago }}</p>
</div>
<div class="col-1">
<div class="row dropdown">
<div class="col">
<a
class="font-smaller"
type="button"
:id="'dropdownMenuButtonComment_'+index"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<i class="fas fa-lg fa-angle-down grey-color"></i>
</a>
<div
class="dropdown-menu dropdown-menu-right"
:aria-labelledby="'dropdownMenuButtonComment_'+index"
>
<button
class="dropdown-item"
v-if="comment.id_user===profile_info.id_user"
#click="editComment(comment.id, index)"
>
<i class="far fa-edit"></i>
Edit
</button>
<button
class="dropdown-item"
data-toggle="modal"
data-target="#modalDeleteComment"
v-if="comment.id_user===profile_info.id_user"
#click="actionComment(comment.id, 2, index)"
>
<i class="far fa-trash-alt red-color"></i>
<span class="red-color">Delete</span>
</button>
<button
class="dropdown-item"
v-if="comment.id_user!==profile_info.id_user"
#click="actionComment(comment.id, 3)"
>
<i class="far fa-flag"></i>
Report
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The object comment stated in the data:
comment: {
id: "",
id_post: "",
description: "",
clicked_to_edit: false,
id_user: this.profile_info.id_user,
code: this.profile_info.code,
name: this.profile_info.name,
surname_1: this.profile_info.surname_1,
surname_2: this.profile_info.surname_2,
nick: this.profile_info.nick,
picture: this.profile_info.picture,
role: this.profile_info.id_role,
time_ago: "0 minutes"
},
and the edit function:
editComment(id, index) {
this.comments[index].clicked_to_edit = true;
console.log(this.comments[index]);
},
I purposefully ignored your model, to show a general case. If you need help applying it to your case, let me know.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data: () => ({
comments: [
{ id: 'one', description: "test 1" },
{ id: 'two', description: "test 2" },
{ id: 'three', description: "test 3" }
].map(i => ({ ...i,
isEditing: false
}))
}),
methods: {
async toggleEditing(comment, index) {
const isOpening = !comment.isEditing;
this.comments
.filter(c => c.isEditing)
.forEach(c => c.isEditing = false);
if (isOpening) {
comment.isEditing = true;
await this.$nextTick();
this.$refs.comments[index].querySelector('input').focus()
}
},
blur: ev => ev.target.blur()
}
})
.comment {
min-height: 21px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(comment, i) in comments" ref="comments" :key="comment.id">
<div v-if="!comment.isEditing"
v-text="comment.description"
class="comment"
#click="toggleEditing(comment, i)"></div>
<div v-else>
<input v-model="comment.description"
#blur="toggleEditing(comment)"
#keyup.enter="blur"
/>
</div>
</div>
</div>
I am trying to display information from a web service but am getting the following error "ERROR TypeError: Cannot read property 'bu' of undefined". However the data that I wish to display is showing correctly. Here is a copy of the web service response and code.
{
"bu": [
{
"id": 1,
"bcluster": "R03 - Information Technology",
"bsector": "P20 - CA SA",
"bgroup": "319 - ITM as a Service (passthrough)",
"bunit": "-"
},
{
"id": 2,
"bcluster": "R03 - Information Technology",
"bsector": "Q04 - Information Management & Applications",
"bgroup": "P36 - Softworks",
"bunit": "001 - Softworks Licence & Maintenanc"
}
]
}
HTML
<ul class="list-group" *ngFor="let item of vals.bu">
<li class="list-group-item d-flex justify-content-between align-items-center">
<button type="button" class="btn btn-info">{{item.id}}</button>
<input type="text" class="input-group btn btn-light" [(ngModel)]="item.bunit">
<button type="button" class="btn btn-primary">Update</button>
</li>
</ul>
TS
ngOnInit(): void {
this.http.get<DataResponse>(this.api.getBU()).subscribe(data => {
this.vals = data;
});
}
You need to use safe navigation operator or *ngIf inorder to handle the delay of response from your asynchronous request,
<button type="button" class="btn btn-info">{{item?.id}}</button>
<input type="text" class="input-group btn btn-light" [(ngModel)]="item?.bunit">
<button type="button" class="btn btn-primary">Update</button>
</li>
EDIT
As mentioned in the comment, handle the null in your ngFor and remove it in the ngModel
<ul class="list-group" *ngFor="let item of vals?.bu">
<li class="list-group-item d-flex justify-content-between align-items-center">
<button type="button" class="btn btn-info">{{item.id}}</button>
<input type="text" class="input-group btn btn-light" [(ngModel)]="item.bunit">
<button type="button" class="btn btn-primary">Update</button>
</li>
</ul>
You should use safe navigation operator for vals?.bu. Because you are getting vals asynchronously.
<ul class="list-group" *ngFor="let item of vals?.bu">
<li class="list-group-item d-flex justify-content-between align-items-center">
<button type="button" class="btn btn-info">{{item.id}}</button>
<input type="text" class="input-group btn btn-light" [(ngModel)]="item.bunit">
<button type="button" class="btn btn-primary">Update</button>
</li>
</ul>
I want to ask a question concerning vue-validator but it is not working on JSFiddle. Can someone please help me check what the issue is so that I can go ahead and ask the main question
See JSFiddle
Html:
<div id="app">
<validator name="instanceForm">
<form v-on:submit.prevent="addInstance()">
<div class="form-group">
<input id="'instancetext" type="text" class="form-control" placeholder="Enter new Instance" v-validate:instancetext="{ required: { rule: true, message: 'What is the instance ?'}}" v-model="todo.name">
<div class="errors" v-if="showErrors">
<span v-if="$instanceForm.instancetext.required">{{$instanceForm.instancetext.required}}</span>
</div>
</div>
<div class="form-group">
<button class="btn btn-default"> <i class="fa fa-plus"> </i> Add
</button>
</div>
</form>
</validator>
<div class="">
<div v-for="todo in todoStore" class="list-group">
<div class="list-group-item">
<h4>
Main Card
</h4> {{todo.name}} {{todo.id}}
</div>
<div class="list-group-item nopadding">
<button class="btn btn-xs btn-danger margin-10" v-on:click="todoDelete(todo)">
<i class=" fa fa-trash"></i>
</button>
<form #submit="addNoteInstance">
<div class="form-group">
<input id="note-#{{note.id}}" type="text" class="form-control" name="note-#{{note.id}}" placeholder="Enter new Note Instance" v-model="name">
<button type="submit"><i class="fa fa-plus"> Add Note Instance</i></button>
</div>
</form>
</div>
<div v-for="note in todoNoteStore" class="list-group nopadding nomargin">
<div v-if="todo.id == note.id">
<div class="list-group-item">
<h4>
Note fore card {{todo.id}}
</h4> {{note.id}}{{note.name}}
</div>
<div class="list-group-item nopadding">
<button class="btn btn-xs btn-danger margin-10" v-on:click="removeNoteInstance(note)">
<i class=" fa fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<pre>{{ $data | json }}</pre>
</div>
Js:
new Vue({
el: '#app',
data: function() {
return {
showErrors: false,
todo: {
id: 1,
name: '',
completed: false,
check: ''
},
todoNote: {
id: 1,
name: '',
completed: false,
check: ''
},
todoStore: [
],
todoNoteStore: [
]
}
},
methods: {
addInstance: function() {
if (this.$instanceForm.valid) {
this.todoStore.push(this.todo);
this.todo = {};
this.showErrors = false;
} else {
this.showErrors = true;
}
},
addNoteInstance: function(e) {
e.preventDefault();
this.todoNoteStore.push({
"id": 1,
"name": this.name,
"completed": false,
"check": ''
});
this.name = '';
},
removeNoteInstance: function(note) {
this.todoNoteStore.remove(note);
}
}
});
css:
.nopadding {
padding-top: 0px;
!important;
padding-bottom: 0px;
!important;
}
.nomargin {
margin: 0px;
}
There are two problems here.
The CDN you are using for Vue-Validator (v2.1.7) seems to be broken. The links for 2.1.6 and 3.0.0-alpha1 seem to be fine. (this is specifically referring to links on cdnjs)
Vue-Validator 2.x only works with Vue 1.x. Your CDN for Vue is pointing to Vue 2.0.3.
So, either use Vue 1.x and a a working version of Vue-Validator 2.x, or use Vue 2.x and Vue-Validator 3.x (which is currently in alpha).