I'm using Vue Js and Firebase to build a voting web app, the app contains units, and each unit has an "upvote" and "downvote" buttons.
my problem is that whenever a button is clicked the whole list of units refreshes, instead of changing the votes count only as it should. am I missing something with my Vue js code?
Html code:
<div id="unitWrapper">
<div v-for="request in requests" id="unit" >
<iframe :src="request.text" id="spotifyEmbed" width="80" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>
<h2 id="title">{{ request.songN }}</h2>
<div id="votes">
<span id="up" #click="upV(request.id)">
<i class="fas fa-arrow-up fa-2x" v-if="request.isUpvoted" style="color: rgb(1, 187, 1);"></i>
<i class="fas fa-arrow-up fa-2x" v-else style="color: #888;"></i>
<p class="votesNumber" style="color: rgb(1, 187, 1);">{{ request.upVotes }}</p>
</span>
<span id="down" #click="downV(request.id)">
<i class="fas fa-arrow-down fa-2x" v-if="request.isDownvoted" style="color: red;"></i>
<i class="fas fa-arrow-down fa-2x" v-else style="color: #888;"></i>
<p class="votesNumber" style="color: red;">{{ request.downVotes }}</p>
</span>
</div>
</div>
</div>
Js/Vue code:
var app = new Vue({
el: "#unitWrapper",
data: {
requests: [],
},
methods: {
upV(id) {
const upvote = firebase.functions().httpsCallable("upvote");
upvote({ id: id }).catch((err) => {
showNotification();
});
},
downV(id) {
const downvote = firebase.functions().httpsCallable("downvote");
downvote({ id: id }).catch((err) => {
showNotification();
});
},
},
created() {
const ref = firebase
.firestore()
.collection("requests")
.orderBy("upVotes", "desc");
ref.onSnapshot((snapshot) => {
let requests = [];
snapshot.forEach((doc) => {
var obj = {};
obj = {
...doc.data(),
id: doc.id,
};
var user = firebase.auth().currentUser;
if (user != null) {
firebase
.firestore()
.collection("users")
.doc(user.uid)
.get()
.then((val) => {
if (val.exists) {
var res = val.data().upVotedOn.includes(doc.id)
var res2 = val.data().downVotedOn.includes(doc.id)
console.log(`${res2} - downvoted`);
obj.isUpvoted = res;
obj.isDownvoted = res2;
} else {
obj.isUpvoted = false;
obj.isDownvoted = false;
}
requests.push(obj);
})
.catch((err) => console.log(err));
} else {
obj.isUpvoted = false;
obj.isDownvoted = false;
requests.push(obj);
}
});
// console.log(requests);
this.requests = requests;
});
},
});
any help is appreciated
Related
I have a js code that works, but the problem is that when I use it on the main HTML page, for some reason it only works on the first picture, although the LIKE icon is on all the pictures.
I do not understand why it is used only on the first value of the icon and not on the others.
How to make it work on all LIKE icons?
const likeIcon = document.getElementById('like-icon');
const likeCount = document.getElementById('like-count');
likeIcon.onclick = () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'applicatin/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if(data.liked) {
likeIcon.classList.remove('empty-heart');
}
else {
likeIcon.classList.add('empty-heart');
}
likeCount.innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
})
}
HTML CODE
<section class="trend">
<div class="container">
<div class="flex align-center justify-between trend-header">
<h2 class="subtitle">
<i class="ri-fire-line subtitle-icon"></i>
В тренде
</h2>
<button class="btn btn-outline">Посмотреть все</button>
</div>
<div class="trend-content">
{% for new in news|slice:"2" %}
<div class="trend-card">
<img src="{{new.banner.url}}" alt="newsPhoto" class="trend-background" />
<div class="card-header">{{new.category.title}}</div>
<div class="card-bottom">
<h3 class="card-title">
{{new.title}}
</h3>
<div class="card-btn">
<div class="count" id="like-count">{{new.likes.count}}</div>
{% if liked_by %}
<button class="btn-up" class="fa fa-heart">
<li><i id="like-icon" data-news="{{new.id}}" class="fa fa-heart"></i></li>
</button>
{% else %}
<button class="btn-up" class="fa fa-heart empty-heart">
<li><i id="like-icon" data-news="{{new.id}}" class="fa fa-heart empty-heart"></i></li>
</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>
Tried to use https://developer.mozilla.org/en/docs/Web/API/Document/querySelectorAll but couldn't do it
const likeIcon = document.querySelectorAll('#like-icon');
const likeCount = document.getElementById('like-count');
likeIcon.forEach(like-icon => {
like-icon.addEventListener("click", () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'applicatin/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if(data.liked) {
likeIcon.classList.remove('empty-heart');
}
else {
likeIcon.classList.add('empty-heart');
}
likeCount.innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
})
});
}
}
And i expected it to work for all pictures that I had on main HTML page
It looks like you're on the right track using querySelectorAll to get all the like icons. However, there is a small mistake in your code when you use likeIcon instead of like-icon in your forEach loop.
Try replacing your JS code with the following, which should work for all like icons:
const likeIcons = document.querySelectorAll('#like-icon');
const likeCounts = document.querySelectorAll('#like-count');
likeIcons.forEach((likeIcon, index) => {
likeIcon.addEventListener('click', () => {
const newId = likeIcon.getAttribute('data-news');
const url = `/like_news/${parseInt(newId)}/`;
fetch(url, {
method: 'GET',
headers: {
'Content-type': 'application/json'
}
})
.then(response => {
return response.json();
})
.then(data => {
if (data.liked) {
likeIcons[index].classList.remove('empty-heart');
} else {
likeIcons[index].classList.add('empty-heart');
}
likeCounts[index].innerHTML = data.like_count;
})
.catch(error => {
console.log(error);
});
});
});
This code should loop through all the like icons and add a click event listener to each of them. When a user clicks on a like icon, the corresponding like count and icon should update based on the server response.
Note that we are using querySelectorAll to get all the like icons and like counts, and then we are looping through them using forEach. We are also using the index of each like icon to update the correct like count and icon.
You need to use class selector instead of ID selector.
While multiple Dom have the same ID, but only the first Dom can be query by document.querySelectorAll or document.querySelector.
Selects an element based on the value of its id attribute. There should be only one element with a given ID in a document.
https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors
<i id="like-icon" data-news="{{new.id}}" class="fa fa-heart like-icon"></i>
const likeIcon = document.querySelectorAll('.like-icon');
// like-icon is not a right variable definition.
likeIcon.forEach(likeIcon => {
likeIcon.addEventListener("click", () => {
...
I got problem with 'live' like unlike button in Django and JavaScript DOM
after button is clicked I got an error
POST http://127.0.0.1:8000/like/24 404 (Not Found)
likePost # javascripts.js:24
(anonymous) # javascripts.js:40
javascripts.js:7
I don't know if the problem is in the 'await fetch' function or maybe I used the wrong class or id somewhere.
Where to start?
javascript.js
const reloadPostHTML = async (postId) => {
const homePageResponse = await fetch(window.location.href);
const newHTML = await homePageResponse.text();
const newDocument = new DOMParser().parseFromString(newHTML, "text/html");
console.log(newDocument)
const newPostElem = newDocument
.querySelector(`[data-post-id='${postId}']`)
.closest(".post");
const oldPostElem = document
.querySelector(`[data-post-id='${postId}']`)
.closest(".post");
oldPostElem.innerHTML = newPostElem.innerHTML;
makeLikeButton(oldPostElem.querySelector(".like-button-wrapper"));
};
const likePost = async (postId, csrfToken) => {
await fetch(`/like/${postId}`, {
method: 'POST',
credentials: 'include',
headers: {
"X-CSRFToken": csrfToken
}
});
reloadPostHTML(postId);
};
const makeLikeButton = (elem) => {
elem.querySelector('button').addEventListener("click", (event) => {
event.preventDefault();
const postId = elem.dataset.postId;
const csrfToken = elem.dataset.csrfToken;
likePost(postId, csrfToken);
});
};
const makeLikeButtons = () => {
for (let elem of document.getElementsByClassName("like-button-wrapper")) {
makeLikeButton(elem);
}
};
makeLikeButtons();
urls.py
path(
'article_detail/<int:pk>/',
login_required(
ArticleDetail.as_view(template_name = "web/article_detail_view.html")
),
name='article_detail'
),
path('like/<int:pk>', views.like, name='like'),
In the views should I also use "if request.method == "POST":" ?
views.py
def like(request, pk):
article = get_object_or_404(Article, id=request.POST.get("article_id"))
if article.likes.filter(id=request.user.id).exists():
article.likes.remove(request.user)
liked = False
else:
article.likes.add(request.user)
liked = True
return HttpResponseRedirect(reverse("article_detail", args=[int(pk)]))
and detail_view.py
class .post is in thats why i used .closest(".post") in javascript.js
<div class="card post"> ........
<div class="like-button-wrapper"
data-post-id='{{ article.pk }}'
data-csrf-token='{{ csrf_token }}'>
{% if liked %}
<button class="btn btn-danger position-relative" type="submit" id="like" name="article_id"
value="{{article.id}}">
<i class="bi bi-hand-thumbs-down">
</i>
</button>
{% else %}
<button class="btn btn-primary position-relative" type="submit" id="like" name="article_id"
value="{{article.id}}">
<i class="bi bi-hand-thumbs-up">
</i>
<span
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ likes }}
</span>
</button>
{% endif %}
</div>
Ok I got it it was a error 404 came from views.py get_or_404
Now I just rebuild the like function in views and its working as should
def like(request, pk):
if request.method == "POST":
#article = get_object_or_404(Article, id=request.POST.get("article_id"))
article = Article.objects.get(id=pk)
if article.likes.filter(id=request.user.id).exists():
article.likes.remove(request.user)
liked = False
else:
article.likes.add(request.user)
liked = True
return HttpResponseRedirect(reverse("article_detail", args=[int(pk)]))
i am using a v-for to display list of product from an api request, the product card contains three buttons, one of the Adds item to cart,with a shopping-cart icon.
i want it so that when a user clicks the add to cart button, the shopping-cart icon changes to a spinner icon
I try declaring a "loading" in the data object, default set to false, so in my add to cart function, before the function is called, loading is set to true,
And in my template i use a v-show="loading" which set the visibility of the fa-spin to true if loading is true
//template
<template>
<div class="row">
<div v-for="product in products" v-bind:key="product_slug"
class="col-md-auto mx-auto card text-center card-product">
<div class="card-product__img">
<img class="card-img" src="img/product/product1.png" alt="">
<ul class="card-product__imgOverlay">
<li>
<button><i class="ti-search"></i></button>
</li>
<li>
<button #click="addToCart(product.id, product.slug, product.price)"><i
class="ti-shopping-cart"></i> <i v-show="loading" class="fa fa-spinner fa-spin"></i>
</button>
</li>
<li>
<button><i class="ti-heart"></i></button>
</li>
</ul>
</div>
<div class="card-body">
<p>Accessories</p>
<h4 class="card-product__title">{{ product.slug }}</h4>
<p class="card-product__price">₦ {{ product.price}}</p>
</div>
</div>
//script
<script>
export default {
data() {
return {
loading: false,
products: [],
product: {
"id": '',
"slug": '',
"product_image_1": '',
"product_image_2": '',
"product_image_3": '',
"product_image_4": '',
"price": '',
"qty": '',
"stock_status": '',
"sku": '',
"short_description": '',
"description": '',
},
product_slug: '',
pagination: {},
}
},
created() {
this.fetchProduct();
},
methods: {
fetchProduct(page_url) {
//assign variable to this
let vm = this;
// check if page url exist, = page url else = /api/shop
page_url = page_url || '/api/shop';
fetch(page_url)
.then(res => res.json())
.then(res => {
this.products = res.data;
vm.makePagination(res.links, res.meta);
})
.catch(err => console.log(err));
},
makePagination(links, meta) {
//Make an object made up of meta, page details from the api response
let pagination = {
current_page: meta.current_page,
last_page: meta.last_page,
next_page_url: links.next,
prev_page_url: links.prev,
};
// Set the object to the pagination value
this.pagination = pagination;
},
addToCart(id, slug, price) {
this.loading = true;
axios.post('/api/cart', {
id: id,
name: slug,
price: price,
})
.then(function (response) {
this.loading = false;
console.log(response.data);
})
.catch(function (err) {
this.loading = false;
this.addToCart = err;
});
}
}
}
</script>
The problems are
1) Once the add to cart button is clicked, the spinner shows in all of the product's card.
2) fa-cart icon is not hiding, shows side-by-side with the shopping-cart icon
3) fa-spin continues, even after success of api request
You need to maintain a dictionary of the loading state. In addToCart function, you need to set true for particular product id. Try this code.
addToCart(id, slug, price) {
this.loading[id] = true;
axios.post('/api/cart', {
id: id,
name: slug,
price: price,
})
.then(function (response) {
this.loading[id] = false;
console.log(response.data);
})
.catch(function (err) {
this.loading[id] = false;
this.addToCart = err;
});
}
In Fetch product function made some changes.
fetchProduct(page_url) {
//assign variable to this
let vm = this;
// check if page url exist, = page url else = /api/shop
page_url = page_url || '/api/shop';
fetch(page_url)
.then(res => res.json())
.then(res => {
this.products = res.data;
this.products.filter(function (item) {
vm.loading[item.id]=false;
return item;
})
vm.makePagination(res.links, res.meta);
})
.catch(err => console.log(err));
},
html changes.
<button #click="addToCart(product.id, product.slug, product.price)"><i
class="ti-shopping-cart"></i> <i v-show="loading[product.id]" class="fa fa-spinner fa-spin"></i>
</button>
I am working on this Laravel Social Network Script and it uses Vue 1.0.26.
My objetive is to bind the class connected or disconnected according to the user status which is true or false. I created a Laravel API to get the user status:
Controller (Laravel):
// $id = User id - https://www.mysite.uy/api/v1/status/{userid}
public function getUserStatus($id)
{
$active = false;
$open_session = \App\OpenSession::where('user_id', $id)->first();
if($open_session) {
if(Carbon::parse($open_session->created_at)->diffInSeconds(Carbon::parse($open_session->expires)) < 86400 && $open_session->active > 0) {
$active = true;
}
}
return response()->json($active);
}
This works fine from the URL, it returns true or false, but then here is some view.blade.php:
<li class="list-group-item" v-for="conversation in conversations.data" v-if="conversation.user"> <!-- Conversaciones -->
<a href="#" #click.prevent="showChatBox(conversation)">
<div class="media">
<div class="media-left">
<img v-bind:src="conversation.user.avatar" alt="images">
</div>
<div class="media-body">
<h4 class="media-heading">
#{{ conversation.user.name }}
<i v-bind:class="getUserStatus(conversation.user.id)"></i> <!-- <-- look at here -->
</h4>
<span class="pull-right active-ago" v-if="message">
<time class="microtime" datetime="#{{ message.created_at }}" title="#{{ message.created_at }}">
#{{ message.created_at }}
</time>
</span>
</div>
</div>
</a>
</li>
This block is being displayed as <i></i> on the HTML when using the getUserStatus() method on computed:
<h4 class="media-heading">
#{{ conversation.user.name }}
<i v-bind:class="getUserStatus(conversation.user.id)"></i> <!-- <-- look at here -->
</h4>
And this displays <i class="status disconnected"></i> even when the response.data is true and I check it with console.log(response.data == true):
<h4 class="media-heading">
#{{ conversation.user.name }}
<i v-bind:class="['status', getUserStatus(conversation.user.id) ? 'connected' : 'disconnected' ]"></i>
</h4>
And here is the Vue stuff:
data: {
status: {
on: 'connected',
off: 'disconnected'
},
},
created: {
...
},
methods: {
getUserStatus: function(userid)
{
this.$http.post(base_url + 'api/v1/status/' + userid).then(function(response) {
if(response.data == 'true') {
return true;
} else {
return false;
}
});
},
},
computed: {
/*getUserStatus: function(userid)
{
this.$http.post(base_url + 'api/v1/status/' + userid).then(function(response) {
return {
status: true,
connected: response.data == 'true',
disconnected: response.data == 'false'
}
});
}*/
}
I only have one of the methods "working", but I switch them. I am reading this guide but I get this is for Vue 2.
Ok, I made it work by adding a default variable in data and changing it later:
data: {
...
userStatus: {},
}
And in the method. As you can see I made some irrelevant changes on the Controller side.
methods: {
getUserStatus: function(userid)
{
var userStatus = this.$http.post(base_url + 'api/v1/status/' + userid).then(function(response) {
this.userStatus = JSON.parse(response.data);
});
return this.userStatus.status;
},
...
}
And in the HTML side I use:
<h4 class="media-heading">
#{{ conversation.user.name }}
<i class="status connected" v-bind:class="[ 'status', getUserStatus(conversation.user.id) ? 'connected' : 'disconnected' ]"></i>
</h4>
Now I have a different issue: The function seems to run repeteadly so the status are constantly changing. All the other functions runs once.
I have an array of comments on a picture called 'comments'
I also have a property called "newcmt" that i'd like to push into comments
I have a function addComment that tries and fails to make these changes.
I have tried a few different ways, but the put doesn't seem to update the api despite returning the proper changes in the response.
Am I just updating the reference?
Is asynchronous nature causing errors?
Is my mongo scheme wrong?
Would it be easier to make comments its own endpoint?
JS
var refresh = function() {
$http.get('/api/things').success(function(awesomeThings) {
$scope.awesomeThings = awesomeThings;
});
};
$scope.addThing = function() {
if($scope.newThing === '') {
return;
}
$http.post('/api/things/', { name: $scope.newThing });
refresh();
};
$scope.addComment = function(thing) {
if(thing.newcmt == '') {
return;
}
$scope.newcmt = thing.comment;
$scope.newcmt.push(thing.newcmt);
console.log($scope.newcmt)
$http.put('/api/things/' + thing._id, {comment : $scope.newcmt }, {safe: true, upsert: true, new : true}).success(function(response) {
console.log(response);
refresh();
})
};
$scope.deleteThing = function(thing) {
$http.delete('/api/things/' + thing._id);
refresh();
};
HTML
<div class="container">
<img class="img-responsive" src="{{thing.url}}" alt="">
</div>
<div class="col-md-12" ng-repeat="comment in thing.comment">
<p>{{comment}}</p>
</div>
<div class="row">
<div class="col-xs-12">
<i class="fa fa-comment-o fa-3x"></i>
<input type="text" placeholder='...' ng-model="thing.newcmt" >
<button class="btn btn-default" ng-click="addComment(thing)">+</button>
<i class="fa fa-arrow-down fa-2x pull-right"></i>
<i class="fa fa-arrow-up fa-2x pull-right"></i>
</div>
</div>
MODEL
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var ThingSchema = new Schema({
name: String,
url: String,
credit: String,
active: Boolean,
comment: Array,
newcmt: String
});
module.exports = mongoose.model('Thing', ThingSchema);
PUT
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.status(404).send('Not Found'); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.status(200).json(thing);
});
});
};
full github:
https://github.com/jneljneljnel/meangen