How to mark notification as Read using Vue js? - vue.js

I'm trying to mark a notification as read when a user clicks on it. Right now, when a user clicks on one of the notifications, it marks all of the user's notifications as read, instead of just the one.. I created a "click" function on the <a>.
AppHeader.vue:
<li class="dropdown-item preview-item dropdown-item-notifications" v-for="(unreadNotification, index) in unreadNotifications" :key="index">
<a class="dropdown-item" type="button" #click="markAsRead()">
<div class="preview-item-content flex-grow py-2">
<div v-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestCandidateReply' : ''">
<p class="font-weight-light small-text"> <InterviewRequestCandidateReplyMessage /> </p>
</div>
<div v-else-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestReplyConfirmed' : ''">
<p class="font-weight-light small-text"> <InterviewRequestReplyConfirmedMessage /> </p>
</div>
</div>
</a>
</li>
methods: {
markAsRead: async function() {
try {
const response = await employerService.markNotificationAsRead();
this.loadNotificationsData();
console.log(this.notifications);
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
},
employerService.js from code above:
export function markNotificationAsRead(id) {
return http().post(`employer/notifications/${id}`);
}
In my #click="markAsRead() function I think I need to get the id so maybe something like this #click="markAsRead(unreadNotification.id). Now the tricky part and where I'm stuck is, how can I pass this id into the markNotificationAsRead() function below?
const response = await employerService.markNotificationAsRead();
I'm not sure how to do this. I'm using Laravel for my backend.
--------------------- UPDATE: ---------------------
Something strange is happening. I know that the answers provided should work, but for some reason it's still marking all records as read.
EmployerNotificationsController.php:
public function markAsRead($id)
{
$notifications = Auth::user()->notifications->where('id', $id)->first()->markAsRead();
return response()->json($notifications, 200);
}
api.php:
Route::post('/employer/notifications/{id}', 'EmployerNotificationsController#markAsRead')
->name('employer.notifications.mark-as-read');
Any ideas why?

You're absolutely right about passing the ID to the click handler: #click="markAsRead(unreadNotification.id)
Your markAsRead method will receive the ID as an argument that you can then pass to your service method:
markAsRead: async function(id) {
try {
const response = await employerService.markNotificationAsRead(id);
//...
},

<li class="dropdown-item preview-item dropdown-item-notifications" v-for="(unreadNotification, index) in unreadNotifications" :key="index">
<a class="dropdown-item" type="button" #click="markAsRead(unreadNotification.id)">
<div class="preview-item-content flex-grow py-2">
<div v-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestCandidateReply' : ''">
<p class="font-weight-light small-text"> <InterviewRequestCandidateReplyMessage /> </p>
</div>
<div v-else-if="unreadNotification ? unreadNotification.type === 'App\\Notifications\\InterviewRequestReplyConfirmed' : ''">
<p class="font-weight-light small-text"> <InterviewRequestReplyConfirmedMessage /> </p>
</div>
</div>
</a>
</li>
methods: {
markAsRead: async function(id) {
try {
const response = await employerService.markNotificationAsRead(id);
this.loadNotificationsData();
console.log(this.notifications);
} catch (error) {
this.$toast.error("Some error occurred, please refresh!");
}
},
},
just like this ...

Related

Vue 3 : Event, emit and props

I have a problem with event, emit and props (and probably some logic too)
I have a component A in which I have a loop with a component B.
In this loop, I have a method to open the modal (which is a component C) but this method is not part of component B.
Like this :
<a>
<!-- MODAL-->
<div v-if="showModal">
<modal-cat #cat="catId = getCatId($event)" #addTx="addTx($event)"></modal-cat>
</div>
<div v-if="transactions.length != 0" class="mx-auto">
<div v-for="tx in transactions" :key="tx">
<div class="mb-2 border border-gray-600 rounded-lg bg-white pt-2 pb-4">
<div class="flex justify-end">
<span
class="inline-flex items-center justify-center h-6 w-6 rounded-full text-lg bg-blue-800 text-white"
#click="showModal = true, txToAdd = tx">
<i class='bx bx-plus'></i>
</span>
</div>
<transaction-data :transaction="tx" :address="walletAddress"></transaction-data>
</div>
</div>
</div>
</a>
In this modal, I fetch some data (in fact, a array of categories) that I also display in a loop.
Like this :
<div class="modal-container">
<div v-for="(categorie) in categories" :key="categorie">
<p #click="$emit('cat', categorie.id)">{{ categorie.name}}</p>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="modal-default-button" #click="$emit('addTx', 'ok')">
OK
</button>
</slot>
</div>
</div>
I need some data from my modal in my component A but I also need some data from my component B in my component A (to add a transactions to a category)
I managed to get the data I wanted like this (And I can get it):
const showModal = ref(false);
const txToAdd = ref({});
const catId = ref(0);
function getCatId(event) {
return event
}
const addTx = (value) => {
if (value === "ok") {
//console.log(txToAdd.value); <= the value are well displayed in the console.
let data = {
tx: txToAdd.value,
catId: catId.value
}
store.dispatch("categories/addTxToCategories", data);
}
}
But in my store, when I try to get the payload, I can't access to the data and I only get the payload object.
Is there something wrong with my logic ? What am I doing wrong ?
EDIT
I just need to wrap the result in spread operator, like this :
const addTx = (value) => {
if (value === "ok") {
//console.log(txToAdd.value);
let data = {
tx: {...txToAdd },
catId: catId.value
}
store.dispatch("categories/addTxToCategories", {...data });
}
}
And in my store, the payload MUST be the second argument :
async addTxToCategories({ commit }, payload) {}

how can I update a vue child-component from the vuex-store

My application shows a window where player can enter name and passcode to enter
When the player exists and has a card, I want to show the card as well.
When the player exists, I make the card visible. Then in 'created'I call fetchSpelerCards. This is successful but shows in the VUE console as pending...
I hope some experienced vue user reads this and can help me with a hint, reference or explanation.
For that I have in the following code:
<h2>Meld je aan</h2>
<form #submit.prevent="register" class="mb-3">
<div class="form-group">
<input type="text" class="form-control m-2" placeholder="naam" v-model="name">
</div>
<div class="form-group">
<input type="text" class="form-control m-2" placeholder="kies inlogcode" v-model="pass_code">
</div>
<button type="submit" #click="checkSpeler()" class="btn btn-primary btn-block" style="color:white">Save</button>
</form>
<p class="alert alert-danger" v-if="errorMessage !== ''"> {{errorMessage}} </p>
<p class="alert alert-success" v-if="successMessage !== ''"> {{successMessage}} </p>
<CardsSpeler v-if="spelerCorrect"></CardsSpeler>
</div>
</template>
The component looks as follows:
<h2>Cards 1</h2>
<form #submit.prevent="addCard" class="mb-3">
<div class="form-group">
<input type="text" class="form-control" placeholder="title" v-model="card.title">
</div>
<div class="form-group">
<textarea class="form-control" placeholder="description" v-model="card.description">
</textarea>
</div>
<div>
<input type="file" v-on:change="onFileChange" ref="fileUpload" id="file_picture_input">
</div>
<button type="submit" class="btn btn-primary btn-block" style="color:white">Save</button>
</form>
<div class="card card-body mb-2" v-for="card in cards" v-bind:key="card.id">
<h3> {{currentSpelerCard.title}} </h3>
<p> {{currentSpelerCard.description}}</p>
<img class="img-circle" style="width:150px" v-bind:src="currentSpelerCard.picture" alt="Card Image">
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
mounted(){
console.log('component mounted');
},
computed: {
...mapState([
'currentSpeler' ,'currentSpelerCard'
]),
},
data() {
return{
cardExists:false,
successMessage:'',
errorMessage:'',
}
},
created(){
this.fetchSpelerCards();
},
methods: {
...mapActions([ 'getGames', 'addGame', 'fetchSpelerCards' ]),
fetchSpelerCards(){
this.$store.dispatch('fetchSpelerCards', this.currentSpeler.speler.id )
.then(res => {
this.cardExists = true;
this.successMessage = res;
console.log(res);
})
.catch(err => {
this.errorMessage = err;
this.cardExists = false;
});
},
The corresponding action, in actions.js is:
export const fetchSpelerCards = ({commit}, speler_id) => {
return new Promise((resolve, reject) => {
let status = '';
let data ={};
fetch(`api/cardsBySpeler/${speler_id}`)
.then(res => {
status = res.status;
data = res.json();
})
.then(res=>{
if ( status === 200) {
commit('SET_PLAYER_CARD', data);
resolve('Kaart gevonden');
}
else {
reject('Er is geen kaart beschikbaar')
}
});
})
}
In the vuex-store I see (viewed with VUE add-on of chrome browser):
currentSpelerCard: Promise
Yet, the response of the fetch command was successful, and the card was pulled in, as I see also in the console: status 200, I can see name, title, image address etc..
I was under the assumption that, when the promise eventually resolves, the store is updated and the card becomes available because of the:
computed: { ...mapState([ 'currentSpeler' ,'currentSpelerCard' ]),
Can anyone help me and explain what I am doing wrong?
fetchSpelerCards in Vuex commits SET_PLAYER_CARD with data, this will be a pending promise. You need to await the promise.
You can solve this in a few different ways.
Making the function async and await res.json() would be the easiest.
...
fetch(`api/cardsBySpeler/${speler_id}`)
.then(async res => {
status = res.status;
data = await res.json();
})
...

V-model inside v-for is filling all the inputs

In few words I have multiple Todolists, once I try to post a new Todo inside the Todolist all the inputs of other Todolists gets fill in with the same words. How can I solve this?
Edit: I can post a new todo without problems and it will appear on the relative todolist (see addTodo method). The ONLY problem is that the v-model is for all the todolists on the field, not only for the one I'm writing in, so as soon as I start to write inside an input I got all the inputs filled.
<div class="todolist" v-for="todolist in todolists" :key="todolist.id">
<div class="td-title">
<h5 class="text-center m-0 py-2 text-light">
{{ todolist.title }}
</h5>
</div>
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
// this is the v-model
<input v-model="todo.title" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>
<div>
<div v-for="todo in todolist.todos" :key="todo.id" class="todo">
{{ todo.title }}
</div>
<div>
<i class="fas fa-times" #click="cancel(todolist.id)"></i>
</div>
</div>
</div>
data
data() {
return {
todolists: [],
loading: true,
todo: {
title: "",
todolist_id: "",
user_id: ""
},
};
},
Methods
getTodoLists() {
axios
.get("http://127.0.0.1:8000/api/todolists")
.then((res) => {
this.todolists = res.data;
this.loading = false;
})
.catch((err) => {
console.log(err);
});
},
cancel(id) {
axios
.delete(`http://127.0.0.1:8000/api/todolists/${id}`)
.then(() => {
this.getTodoLists();
})
.catch((err) => {
console.log(err);
});
},
addTodo(todolist) {
axios
.post(`http://127.0.0.1:8000/api/${todolist}/todos`, this.todo)
.then(() => {
this.todo = {}
this.getTodoLists();
})
.catch((err) => {
console.log(err);
});
},
Like down here: I'm writing on the left input and it shows even on the right input.
SOLVED:
With this computed property (with setter and getter) I solved my problem, now just the input in which I'm typing in is filled, and the todo can be post without a problem.
computed: {
getTitle: {
get: function () {
return "";
},
set: function (value) {
this.todo.title = value;
},
},
},
And this is the v-model with the new computed property instead of todo.title
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
<input v-model="getTitle" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>
You have a variable in data called "todo" and then you are reusing that variable name inside your inner v-for:
<div v-for="todo in todolist.todos" :key="todo.id" class="todo">
{{ todo.title }}
</div>
I think todo is referring to the todo from your data, not from the v-for. Thus, all todo items refer to the same variable. Try to rename one of them and see if it solves the problem.
You are binding your input to your todo declared data object.
Instead, you should bind your input to your todolist object in this way:
<div class="todolist" v-for="todolist in todolists" :key="todolist.id">
....
<input v-model="todolist.title" class="px-2" type="text" />
....
</div>
SOLVED:
With this computed property (with setter and getter) I solved my problem, now just the input in which I'm typing in is filled, and the todo can be post without a problem.
computed: {
getTitle: {
get: function () {
return "";
},
set: function (value) {
this.todo.title = value;
},
},
},
And this is the v-model with the new computed property instead of todo.title
<form
#submit.prevent="addTodo(todolist.id)"
class="td-inputs d-flex align-items-center justify-content-center"
>
<input v-model="getTitle" class="px-2" type="text" />
<button type="submit">
<i class="fas fa-plus fa-2x"></i>
</button>
</form>

How to unshift to append under spicific div in Vue

I have comments and this comment containe likes, My propblem is when I try to push replay to the comment it is append to the wrong comment, So I used this.$el; to get the target comment bu the propblem is unshift/push just use with list and it shows error push is not a function
My qustion is how I can use unshift/push to append under spicific div not list
async addReplay(comment, e){
const element = this.$el; //this to get the target div
}
here the post replay function
async getaddReplay(id, setReplayPlace){
await axios.get('/account/api/auth/user/')
.then(response => {
this.currentUserImage = response.data.profile_image
})
const replayData = {
content: this.commentReplayContent,
video: this.$route.params.video_id,
parent: id
}
await axios.post(`/video/api/video/comment/${id}/replay/create/`, replayData)
.then(response => {
console.log(this.setReplayPlace)
// here I want append the item to spicific div
this.setReplayPlace.unshift({ content: this.commentReplayContent, author: this.$store.state.user.username, id:response.data.id, author_image:this.currentUserImage, video:this.$route.params.video_id ,likes:0, total_parents:0, check_like: false, publish:'now'})
this.commentReplayContent = ''
})
.catch(error => {
console.log(error)
})
}
Edit
Here there are comments with replies component and I pass the data to repliy component using props
<ul v-for="(comment, index) in comments" :key="index">
<li class="comment-object">
<div class="image-container">
<img class="profile-pic" :src="comment.author_image" v-on:change="currentUserImage" alt="profile picture" id="user_video_comment_profile_image" refs="user_video_comment_profile_image" />
</div>
<div class="comment-text">
<h2 class="username" style="color: #C2C3C4">{{comment.author}} <span class="muted">· {{comment.publish}}</span>
<DeleteComment :comment="comment" v-if="comments || index" :comments="comments" :index="index" />
</h2>
<p class="comment">{{comment.content}} </p>
<RepliesJustAdded #justAddedReplies="addRepliesToParent" :replaiesJustAdded="replaiesJustAdded" :setReplayPlace="setReplayPlace" :allReplies="allReplies" :replaiesData="replaiesData" v-if="replaiesJustAdded || setReplayPlace || allReplies || replaiesData" />
</div>
</li>
</ul>
The Replay component
<ul v-for="replay in replies" :key="replay.id" id="video_comments_replies" >
<li class="comment-object">
<div class="image-container">
<img class="profile-pic" :src="replay.author_image" alt="profile picture" id="user_video_comment_profile_image" refs="user_video_comment_profile_image" />
</div>
<div class="comment-text">
<h2 class="username" style="color: #C2C3C4">{{replay.author}} <span class="muted">· {{replay.publish}}</span></h2>
<p class="comment">{{replay.content}} </p>
<RepliesActionButtons :replay="replay" />
</div>
</li>
</ul>
I didn't find the line where you are trying to call push but I thing I know what's wrong: your are trying to call push but array-like collections don't have such method. You should convert your collection to array before calling push. Try something like that:
// create a `NodeList` object
const divs = document.querySelectorAll('div');
// convert `NodeList` to an array
const divsArr = Array.from(divs);
After that all methods of Array.prototype will be available.

Why still adding even with validation form?

When I click the button on my modal with an empty field on my input its give me an undefined value on my console. And when I put a value on my input and click the button it is adding to my database. The problem is even the empty field or the undefined value are also adding to my database and the sweetalert is not working. I want to prevent the empty field adding to my database and prevent the undefined. Can somebody help me?
//start of method
checkForm: function(e) {
if (this.category_description) {
return true;
}
this.errors = [];
if (!this.category_description) {
this.errors.push('Category required.');
}
e.preventDefault();
},
addCategory : function() {
axios({
method : "POST",
url : this.urlRoot + "category/add_category.php",
data : {
description : this.category_description
}
}).then(function (response){
vm.checkForm(); //for FORM validation
vm.retrieveCategory();
console.log(response);
swal("Congrats!", " New category added!", "success");
vm.clearData();
}).catch(error => {
console.log(error.response);
});
},
//end of method
<form id="vue-app" #submit="checkForm">
<div class="modal" id="myModal" > <!-- start add modal -->
<div class="modal-dialog">
<div class="modal-content " style="height:auto">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title"> Add Category </h4>
<button #click="clearData" type="button" class="close" data-dismiss="modal"><i class="fas fa-times"></i></button>
</div>
<!-- Modal body -->
<div class="modal-body">
<div class="form-group">
<div class="col-lg-12">
<input type="text" class="form-control" id="category_description" name="category_description" v-model="category_description" placeholder="Enter Description">
<p v-if="errors.length">
<span v-for="error in errors"> {{ error }} </span>
</p>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit"#click="category_description !== undefined ? addCategory : ''" class="btn btn-primary"> Add Category </button>
</div>
</div>
</div>
</div>
</form>
The easiest way to stop this is adding data check. And condition check at top of your method.category_description !== undefined. Btw, move your e.preventDefault() to top too.
First of all do this:
- <button type="submit" #click="category_description !== undefined ? addCategory : ''" class="btn btn-primary"> Add Category </button>
+ <button type="submit" #click.prevent="addCategory" class="btn btn-primary"> Add Category </button>
and then in addCategory:
addCategory() {
if (!this.category_description) {
return;
} else {
// do your stuff here
}
}
When you are clicking on Add Category button, it is triggering the addCategory along with your validation method.
The return value of validation method has no impact on triggering of addCategory.
This issue can be handled in following ways.
Call addCategory only when there is some valid data
<button type="submit" #click="category_description != undefined ? addCategory() : ''" class="btn btn-primary"> Add Category </button>
Call the validation method inside addCategory and then proceed.