Vue.js #click not execute function when a:href link is exist - javascript

I'm trying to create notfication, when the notification clicked and linked to his path will decrease the notification length.
<li class="header">You have {{ notificationsCount()}} notif</li> <li>
<ul class="menu">
<li v-for="(notification, index) in notif" :notification="notification" :key="notification.id">
<a :href="notification.data.path"
#click="markAsRead(notification.id)"
#mouseup.right="markAsRead(notification.id)"
class="text-aqua">{{ notification.data.message }}
</a>
</li>
</ul>
methods: {
notificationsCount() {
return this.notif.length
},
markAsRead(index, id) {
if (this.notif.length){
axios.delete(`/notifications/${id}`)
.then(() => {
this.notif.splice(index, 1);
this.notificationsCount();
});
}
}
}
The problem is when a :href link is exist, notificationcount not decrease, but when a :href filled with(#) or i used #click.prevent its work to execute the function and notificationcount decreased. how to solve it?
inside tag i have two trigger #click & #mouseup.right for handle when open new tab. and when i click right its work well and notification decreased because execute through #mouseup.right but when execute through #click its not work. i have to reload once again for decrease notification count

This works as expected. You can see the counter change before it leaves. When you leave the page, the program is erased. When you come back to the page, it is restarted. If you want data values to persist, you will need to use some kind of persistent storage like localStorage or a database. You have axios, but I can't use that in my example here.
Following a link to another page is going to unload the current page, so you don't have much chance to see the change happen. Also, you're not correctly finding the item to splice out.
new Vue({
el: '#app',
data: {
notif: [{
id: 1,
data: {
path: 'http://www.google.com',
message: 'Go to google'
}
}]
},
computed: {
notificationsCount() {
return this.notif.length;
}
},
methods: {
markAsRead(item) {
const index = this.notif.indexOf(item);
this.notif.splice(index, 1);
}
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
Count: {{notificationsCount}}
<ul class="menu">
<li v-for="(notification, index) in notif" :key="notification.id">
<a :href="notification.data.path" #click="markAsRead" class="text-aqua">{{ notification.data.message }}
</a>
</li>
</ul>
</div>

Your problem is the axios request is not finish yet and page is already redirected to the path. You can refactor the redirect function after axios request is finish.
...
<a
#click="markAsRead(notification)"
#mouseup.right="markAsRead(notification)"
class="text-aqua">{{ notification.data.message }}
</a>
...
markAsRead(index, notification) {
if (this.notif.length){
axios.delete(`/notifications/${id}`)
.then(() => {
this.notif.splice(index, 1);
this.notificationsCount();
window.location = notification.data.path
});
}
}
You don't need bind click and mouseup together, choose either click or mouseup.

Related

Vuejs How to have many <a> tags and hashtags within <router-link> or #click function

enter image description here
i want to have a post card like twitter post card, if clicked on any part of the post card goes to post page, but when clicked on hashtags or links goes to the hashtag or link
example below
1.
<div #click="gotoPostPage" class="text-weight-light text-justify">
{{ feed.content }}
check google if you need more information then follow #benny(a tag) or follow these hashtags
#google #finda, #checks, now when i click within this post take me to post page
</div>
now this hits the gotoPostPage function even if link or a tag is clicked
using this also
2.
<router-link :to="`/post/${ feed.id }`">
{{ feed.content }}
check google if you need more information then follow #benny(a tag) or follow
these hashtags
#google #finda, #checks, now when i click within this post take me to post page
</router-link>
goes to even when check google is clicked
Please how can i resolve this, your help is highly appreciated,
Thanks
Add #click.stop to your links. E.g:
<a href="https://google.com" #click.stop>check google</a>
This will stop propagation of click events to the parent DOM elements.
Note: #click.stop is shorthand for #click="e => e.stopPropagation()".
Docs here.
Update, based on data shown in comment:
I would avoid storing HTML id database. That's a really bad pattern. You're supposed to detach the data from the UI layer. You have to allow the UI layer to change, based on device and medium. I'd try to store that data as strings holding a hashtag name and, where the name is different than the hastag, as on object, containing both.
Something like this:
const { createApp, reactive, computed, onMounted, toRefs } = Vue;
createApp({
setup() {
const state = reactive({
items: [],
links: computed(() => state.items.map(
item => ({
name: item.name || `#${item}`,
url: item.url || `/hashtag/${item}`
})
))
});
onMounted(async() => {
/* replace this promise with an axios call to server which returns
an array, similar to the one i'm passing here:
(see axios call example below)
*/
const data = await new Promise(resolve => {
setTimeout(() => {
resolve([
'one',
'two',
'three',
'печаль',
'грусть',
{
name: '#mens',
url: '/hashtag/fıstıklıbaklava'
},
'чайная',
'джаз'
]);
});
})
// example axios call:
/*
const data = await axios
.get('http://endpoint-returning-data')
.then(({data}) => data);
*/
state.items = data;
console.log('See difference betwen items (received from server) \r\n\and links (same data, mapped for template):\r\n', {...state}, '\r\nMakes sense?');
})
return {
...toRefs(state),
log: () => console.log('You clicked on app!')
}
}
}).mount('#app')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<div v-for="link in links" :key="link.url" #click="log">
<a :href="link.url"
v-text="link.name"
#click.stop></a>
</div>
</div>
As you can see, it produces the HTML from the data. The template also applies the stopPropagation(), in the v-for.

Commit initial array to store when leaving page, but if pressing back button loads last commited array

I have an array of articles that can be filtered based on their category. If you click the filter for "ideas" it will filter the array into a new array, let's say "ideasFiltered" and then i commit it to my store "filteredArticles". If i leave the page and press back it is saving that info. Yay! Great! BUT, let's say i go to a new page in my website and then i click my news page again, it has still saved that last commit "ideasFiltered". Is there a way to only save that data if you are pressing "back" in the browser or pressing "back to articles" out of one of my articles (i have a button that let's you go back to the list of articles on each article page), but if you click to a different link in my site it will reset to my full article list?
here is my store:
const store = new Vuex.Store({
state: {
filteredArticles: this.articles
},
mutations: {
setFilteredList (state, value) {
state.filteredArticles = value
}
},
plugins: [
createPersistedState()
],
})
my computed:
computed: {
filteredArticles () {
return store.state.filteredArticles
}
}
so if you click one of my filtered is runs a script, here is an example of the ideas script
ideas: function ideas() {
this.$store.commit('setFilteredList', articles) **// resetting to the full list before filtering**
var ideasFiltered = this.filteredArticles.filter(function(post) {
return post.category === 'Ideas';
});
this.filteredCategory = 'ideas'; **// this is used to add the category to my url**
this.$store.commit('setFilteredList', ideasFiltered) **// committing to the store**
}
here is my html, not sure if it is really necessary though
<div class="news-article-list">
<ul>
<li v-for="(article, index) in filteredArticles" :key="index">
<a :href="article.url">
<img :src="article.featureImg" v-if="article.featureImg" alt="Article Feature Image" />
<h2 class="news-title">{{ article.title }}</h2>
<p class="news-date">{{ article.date }}</p>
<p class="short-desc">{{ article.shortDesc }}...</p>
</a>
<router-link class="read-more" :to="article.url">Read More</router-link>
</li>
</ul>
</div>
Let me know if i need to explain any further. I need it to commit ALL articles if going to a different page on my site and clicking news to get back to the news page, but saving the FILTERED article list if you go to an article and click back.
thanks in advance!
I wouldn't worry about detecting the back button. What you want to know where from did the user come and where to the user wants to go. to and from are your routes. ideasFiltered is changed permanently until you clear it manually. To handle clearing it yourself, create an action in vue-router that resets the data in ideasFiltered and then dispatch that action depending on what route the user is on. You can add a watch in your Vue component to watch the global vue-router $router. In Vue.js 3 its like this:
import {useRoute} from 'vue-router';
import {useStore} from 'vuex';
....
setup() {
const $route = useRoute();
const $store = useStore();
watch(() => $route.name, async () => {
// If the user is not on News or Articles route, then tell store to reset filtered list
if ($route.name !=== "News" || $route.name !=== "Articles") {
$store.dispatch('clearFilteredList', null) // You need to create this action in your store
},
{
deep: true,
immediate: true
}
)
}
The mutation and action in $store to be committed would be something like:
mutations: {
setFilteredList (state, value) { // value passed should be null
state.filteredArticles = value
}
actions: {
clearFilteredList({commit}, payload) { // This is null from Vue component remember!
commit('setFilteredList', payload) // sets it to null
}
I can't see your full store code so the above answer is a best guess but should give you an idea how to go about it. You don't have to use null, you could use make it an empty array or object e.g. []

Vue.js 2.5 list rendering

I have an array in JS/Vue that I would like to display in <ul>/<li> tags, and keep updated as the array gets new elements.
HTML:
<ul id="ulist">
<li v-for="user in users">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>
JS:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
this.$nextTick(function() {
socket.on('test-action', function(data) {
this.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>
The array is being properly populated (as I can see via the console.log statement), but the <li v-for="user in users">... part doesn't seem to be working as none of the <li>...</li> elements get created. What am I doing wrong here?
Just to clarify: if I add hard coded values to the users array, those values show up in <li> elements fine, but no additional values that are added to the array (in the mounted function) show up in <li>...</li> elements.
Edit: version is 2.5.13, if it matters
Can you try this ?
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
var _self = this;
this.$nextTick(function() {
socket.on('test-action', function(data) {
self.users.push(data.username);
console.log(data.username);
}.bind(this));
});
}
});
</script>
The problem is with the scope of your this variable. In your code this line:
this.users.push(data.username);
is scoped to the function call within the ajax request, if you use () => it will keep the current scope in context within your method. Also, you shouldn't need nextTick within the mounted call so try this:
<script>
var socket = io('localhost:3000');
new Vue({
el: "#ulist",
data: {
users: []
},
mounted: function() {
socket.on('test-action', data => {
this.users.push(data.username);
console.log(data.username);
});
}
});
</script>
Although you were using bind(this) you were using this within nextTick which was causing scope issues.
Another thing worth noting, lists require a key in vue v?? (I can't remember which) so it's best to add a key when using v-for:
<ul id="ulist">
<li v-for="(user, index) in users" :key="index">
#{{ user }} <!-- "#" needed since this is in a laravel project with blade templates -->
</li>
</ul>

Vue Router Setup work when visited directly via URL

I'm looking for advice on how to go about this routing scenario:
I have the following HTML that loops category and items in the category. The <router-view> is inside the category so that when an item is clicked it will open only in the category that related to that item.
<ul>
<li v-for="cat in categories">
<ul>
<li v-for="business in filteredByCat">{{business.name}}</li>
</ul>
<router-view v-if="..."></router-view>
</li>
</ul>
My routes are as follows:
{
path: '/businesses',
name: 'Directory',
component: Directory,
children: [{
path: ':listing',
name: 'Listing',
component: Listing
}]
}
Visualization:
How do I get the data of the item clicked to pass to the router-view? I assume I'd use props but that wouldn't work if the user visited details directly by URL?
I tried getting the item like so:
methods: {
finalItem ($route) {
var match = this.businesses.filter((business) => {
return business.link === $route.params.listing
})
return match[0]
}
}
This doesn't work, even if it did, this feels wrong. Is there a way to pass the data in a way that would preserve even when visited directly? This is my primary concern. (I understand the repeated <router-view> is bad code but am not sure how to get around doing that with my layout. Open to suggestions on that too though.)
The way you're using router-view, you might as well just drop a component in. As far as using multiple router-views goes, it's very common, so idk what #varbrad is talking about there. Child routes are fine.
The not-so-common part is using multiple router-view's in one component. The UI you're aiming for is nearly identical to Netflix. If you check out what they're doing, you'll see that they pass a movie ID (business id/shortname) as "jbv" and a row number (category name) as "jbr" in the route query.
Let's mimic this in your component:
I'm not sure what filteredByCat looks like, but the way you have it set up, it would list the same businesses for every category. Try something like this:
computed:{
businessesByCategory(){
let dictionary = {};
this.businesses.forEach(business=>{
business.categories.forEach(category=>{ // assuming each business has an array of categories
dictionary[category] = dictionary[category] || [];
dictionary[category].push(business)
})
})
return dictionary;
}
},
data(){
return {
activeBusiness:null
}
},
methods:{
setActiveBusiness(business){
this.activeBusiness = business;
},
setUrlQuery(listing, category){
this.$route.query.listing = listing;
this.$route.query.category = category;
},
openListing(business, category){
this.setActiveBusiness(business);
this.setUrlQuery(business.link, category);
}
}
-
<ul>
<li v-for="cat in categories">
<ul>
<li
v-for="business in businessesByCategory[cat]"
#click="openListing(business, cat)"
>
{{business.name}}
</li>
</ul>
<Listing :business="activeBusiness" v-if="$route.query.category == cat"></Listing>
</li>
</ul>

Show Action via iron:router, template isn't passed data from a collection.findOne method

I can't get a the 'show' page of an instance of a model to display its data.
Here's the template that won't show its data:
<template name="priority">
<h1>Priority: {{title}}</h1>
</template>
It's very simple in and of itself, yet I can't get title to display. Iron:router does the job of directing us to this page with the following code:
Router.route('/priority/:_id', function(){
var priority = this.params._id;
this.render('priority', {
data: function(priority){
Meteor.call('showPriority', priority, function(error){
if (error) {
console.log("An error has occured: " + error);
}
})
}
})
}, {
name: 'priority.show'
});
The Meteor.method is very simple, it just queries for the variable priority:
'showPriority': function(priority) {
return Priorities.findOne({_id: priority});
}
The view which carries the href is here:
<template name="priorityList">
<ul class="table-view">
{{#each this}}
<li class="table-view-cell">
{{title}}
<span class="pull-right icon icon-edit"></span>
</li>
{{/each}}
</ul>
</template>
Note that this view shows a list of all priorities. When I inspect the href, all the paths are being dynamically generated with an _id:
<a href="/priority/yYihyZmZ2xkAso7i5">...
Oh, and I should mention, that I also tried to use the waitOn method, since I thought I might be loading the template before the data, but that didn't help either...
Router.configure({
...
waitOn: function(){
return Meteor.subscribe('priorities');
}
});
So much code, just to show what's going on!
What's the deal here? Why won't my "show" template give me any data?
Change you route to this.
Router.map(function () {
this.route('priority', {
path: '/priority/:_id',
waitOn: function(){
return Meteor.subscribe('priorities',this.params._id);
},
data: function(){
if(this.ready()){
return Priorities.findOne({_id: this.params._id});
}else{
this.render('loading') //if data not ready we render some loading template
}
}
});
});
You don't need to make a Meteor.call, for the find(); instead to everything on the data:function()
The above is just an example so you can get the idea, but it should work since you are expecting _id:priority and _id:this.params._id its the same.
Just be sure you have the autopublish package removed.
and have the subscriptions/publish in order.
Meteor.publish('Menu', function(){
return Priorities.find();
});

Categories

Resources