I am trying to build a like button on vuejs using axios.
I have a mongoDB controller that works perfectly on postman. But i can't make it work in the frontend.
I want to add the userId in the usersLiked array and i want to add 1 in the like counter on click.
And if the user click on an already liked button, i want to take off his userID from the array and do 0 on the like counter.
thats my schema
const postSchema = mongoose.Schema({
userId: { type: String, required: true, ref: "User" },
content: { type: String, required: true, trim: true },
imageUrl: { type: String, trim: true },
likes: { type: Number, default: 0 },
usersLiked: [{ type: String, ref: "User" }],
firstname: {type: String, required: true, trim: true },
lastname: {type: String, required: true, trim: true },
created_at: { type: Date},
updated_at: { type: Date }
});
this is the axios function i tried
likePost() {
const thisPostId = localStorage.getItem("ThisPostId")
axios
.post('http://127.0.0.1:3000/api/post/like/' + thisPostId, {
headers: {
Authorization: "Bearer " + localStorage.getItem("token"),
},
})
.then((response) => {
console.log(response.data);
this.$set(this.post, 'usersLiked', this.post.usersLiked !== response?.data?._id)
})
.catch((error) => console.log(error));
}
},
the html
<div v-if="post.usersLiked == user._id">
<div class="like-setup">
<p class="likes">{{ post.likes }} like !</p>
<button v-on:click="likePost(post._id)" style="color: pink" type="submit"
title="Aimer ce post !" class="button">
<font-awesome-icon icon="fa-solid fa-thumbs-up" /> Like !
</button>
</div>
</div>
<div v-else>
<div class="like-setup">
<p class="likes">{{ post.likes }} like !</p>
<button v-on:click="likePost(post._id)" type="submit" title="Aimer ce post !"
class="button">
<font-awesome-icon icon="fa-solid fa-thumbs-up" /> Like !
</button>
</div>
</div>
and this is my data
data() {
return {
posts: [],
post: {
file: "",
content: "",
},
showModal: false,
showModifyPost: false,
user: {
firstname: "",
lastname: "",
_id: "",
},
};
},
Adding a computed function to get the current user's like status can be helpful here.
{
computed: {
myLikeStatus() {
if (!this.user) return false
if (!Array.isArray(this.posts.usersLiked)) return false
return !!this.posts.usersLiked.find(item => item === this.user._id)
}
}
}
With this computed function you should be able to set classes on the like button to show already-liked status.
<button #click="likePost" type="submit" title="Aimer ce post !"
class="button"
:class="{ liked: myLikeStatus }"
>
<font-awesome-icon :icon="`fa-solid fa-thumbs-${myLikeStatus ? 'up' : 'down'}`" /> Like !
</button>
And now in the LikePost method, just assign the response data to the reactive posts variable as:
{
methods: {
likePost() {
const thisPostId = localStorage.getItem("ThisPostId")
axios
.post('http://127.0.0.1:3000/api/post/like/' + thisPostId, {
headers: {
Authorization: "Bearer " + localStorage.getItem("token"),
},
})
.then((response) => {
console.log(response.data);
this.post = response.data
})
.catch((error) => console.log(error));
}
},
}
}
Also, your like count should be reactive the way it is now if the API is returning the correct count.
Related
Im using slick slider in Nuxt im getting the images from an API with Axios
The API look like this :
"images_selection": [
{
title: 'Global Landing',
slug: 'global-landing-1',
id: 113,
images: [
{
title: '',
description: '',
file_extension: 'jpeg',
position: 0,
url: 'https://####images',
type: 0,
link: '',
id: 3603,
video_link: '',
},
],
},
{
title: 'Home Slider 1',
slug: 'home-slider-1',
id: 331,
images: [
{
title: '',
description: '',
file_extension: 'jpeg',
position: 0,
url: 'https://###images',
type: 0,
link: '',
id: 5773,
},
],
},
]
My Axios :
async mounted() {
const response = await axios.get('/api/')
this.resultsimages = response.data.images_selection.filter(r => r.slug = home-slider-1)
},
Im trying to get the image only in "Home Slider 1" with a filter .filter(r => r.slug = home-slider-1);
But im doing something wrong what will be the best way to target only the home slider 1 ?
EDIT: here is my page where I am not able to loop on the fetched images.
<template>
<div>
<div class="home-slider">
<VueSlickCarousel
:arrows="false"
:dots="true"
:slidesToShow="3"
:rows="1"
:slidesPerRow="3"
:autoplay="true"
:fade="true"
>
<div class="slide1">
<div v-for="result in resultsimages" :key="result.id">
<div class="img1">
<img
v-for="images in result.images"
:key="images.id"
:src="images.url"
/>
</div>
</div>
</div>
</VueSlickCarousel>
</div>
</div>
</template>
<script>
export default {
data() {
return {
resultsimages: [],
data: [],
};
async mounted() {
const { data } = await axios.get("/api/", {
headers: {
"X-AUTH-TOKEN": "####",
"Content-Type": "application/json",
},
});
this.resultsimages = data.images_selection.filter((image) => (image) =>
image.slug === "home-slider-1"
);
},
};
</script>
I heavily formatted your question, especially for the axios part by using only async/await and not a mix of both async/await + .then.
This is how the block should look like
async mounted() {
const { data } = await axios.get('/api/')
this.resultsimages = data.images_selection.filter((image) => (image) => image.slug === 'home-slider-1')
},
Please format your code with more effort next time.
I am confused how to implement delete in vue-tables-2. I used this library https://www.npmjs.com/package/vue-tables-2
This is my code
HTML
<v-client-table :columns="columns" v-model="data" :options="options">
<div slot="action">
<button #click="erase" class="btn btn-danger">Delete</button>
</div>
<div slot="nomor" slot-scope>
<span v-for="t in nomer" :key="t">{{t}}</span>
</div>
<div slot="category_name" slot-scope="{row}">{{row.category_name}}</div>
</v-client-table>
Vue Js
<script>
var config = {
"PMI-API-KEY": "erpepsimprpimaiy"
};
export default {
name: "user-profile",
data() {
return {
nomer: [],
columns: ["nomor", "category_name", "action"],
data: [],
options: {
headings: {
nomor: "No",
category_name: "Category Name",
action: "Action"
},
filterByColumn: true,
sortable: ["category_name"],
filterable: ["category_name"],
templates: {
erase: function(h, row, index) {
return <delete id={row.data.category_id}></delete>;
}
}
}
};
},
methods: {
load() {
this.$http({
url: "api/v1/news_category/get_all_data",
method: "post",
headers: config
}).then(res => {
this.data = res.data.data;
});
},
del() {
this.$http({
url: "api/v1/news_category/delete",
method: "post",
headers: config
}).then(res => {
console.log("success");
});
}
},
mounted() {
this.load();
}
};
</script>
When I run the code, I got error "erase not defined". I wanna implement like the documentation which you can see at https://www.npmjs.com/package/vue-tables-2#vue-components
Your template is missing the erase function which means you have to define erase as your components method, just like that:
methods: {
erase(h, row, index) {
return <delete id={row.data.category_id}></delete>;
}
}
I am using a pagination library ( https://github.com/arnedesmedt/vue-ads-pagination ) and the VueAdsPageButton has a hidden prop on it called active that is a boolean value depending on whether or not the button is active. I am trying to set the id based on whether or not the active prop is true so I can style it accordingly. I tried:
v-bind:id="{ selected: active} but I get the warning that active is referenced in the render but doesn't exist. Not sure what I am doing wrong here.
This is my code below:
<VueAdsPagination
:total-items="totalOrdersNumber ? totalOrdersNumber : 0"
:page="page"
:loading="loading"
:items-per-page="10"
:max-visible-pages="10"
#page-change="pageChange"
#range-change="rangeChange"
>
<template
slot="buttons"
slot-scope="props"
>
<VueAdsPageButton
v-for="(button, key) in props.buttons"
v-bind:id="{ selected: active}"
:key="key"
:page="page"
v-bind="button"
#page-change="page = button.page;"
/>
</template>
</VueAdsPagination>
EDIT:
here is the component code from the library for VueAdsPageButton
<template>
<button
:class="buttonClasses"
:disabled="disabled"
:title="title"
#click="pageChange"
>
<i
v-if="loading"
class="fa fa-spinner fa-spin"
/>
<span
v-else
v-html="html"
/>
</button>
</template>
<script>
export default {
name: 'VueAdsPageButton',
props: {
page: {
type: [
Number,
String,
],
required: true,
},
active: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
html: {
type: String,
required: true,
},
title: {
type: String,
default: '',
},
loading: {
type: Boolean,
default: false,
},
disableStyling: {
type: Boolean,
default: false,
},
},
computed: {
buttonClasses () {
if (this.disableStyling) {
return {};
}
return {
'focus:vue-ads-outline-none': true,
'vue-ads-ml-1': true,
'vue-ads-leading-normal': true,
'vue-ads-w-6': true,
'vue-ads-bg-teal-500': this.active,
'vue-ads-text-white': this.active,
'vue-ads-cursor-default': this.active || this.disabled,
'vue-ads-bg-gray-200': this.disabled && this.page !== '...',
'vue-ads-text-gray': this.disabled && this.page !== '...',
'hover:vue-ads-bg-gray-100': !this.active && !this.disabled,
};
},
},
methods: {
pageChange () {
if (
this.page === '...' ||
this.disabled ||
this.active
) {
return;
}
this.$emit('page-change');
},
},
};
</script>
You can bind custom id or class on active button like this:
<vue-ads-page-button
v-for="(button, key) in props.buttons"
:key="key"
v-bind="button"
:id="button.active ? 'some-id' : null"
:class="{'some-class': button.active}"
#page-change="page = button.page"
#range-change="start = button.start; end = button.end"
/>
Here is also JSFiddle from library documentation where you can also see this - Link
I try to use vee-validate on a custom component where if nothing selected on submit should show the validation error
the template looks as it follows
<div id="validate" class="container">
<form #submit.prevent="store()" data-vv-scope="time-off" autocomplete="off">
<div class="col-lg-6">
<div class="form-group">
<select2
:options="options"
placeholder='Select...'
v-validate="'required|in:sick, vacation'"
v-model="form.category"
id="categorywidget"
class="form-control">
</select2>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary pull-right">
Send Request
</button>
</div>
</div>
</div>
</form>
</div>
and here is my vue code
Vue.component('Select2', {
props: ['options', 'value', 'id', 'placeholder', 'clear'],
template: '<select><slot></slot></select>',
mounted: function () {
var vm = this
$(this.$el)
.select2({
data: this.options,
placeholder: this.placeholder,
theme: "bootstrap",
width: '100% !important',
dropdownAutoWidth : true,
allowClear: this.clear !== '' ? this.clear : false
})
.val(this.value)
.attr("id", this.id !== null ? this.id : Date.now() + Math.random() )
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', $(this).val())
})
},
watch: {
value: function (value) {
// update value
$(this.$el).val(value).trigger('change');
},
options: function (options) {
// update options
$(this.$el).select2({data: options})
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
});
Vue.use(VeeValidate)
new Vue({
el: '#validate',
data: {
form: {
scopes: [],
category: 'null',
daterange: '',
note: ''
},
options: [
{text: 'Vacation', id: 'vacation'},
{text: 'Sick', id: 'sick'},
{text: 'Not ok', id: 'not-ok'},
]
},
methods: {
store: function() {
this.$validator
.validateAll()
.then(function(response) {
alert('is ok!')
// Validation success if response === true
})
.catch(function(e) {
// Catch errors
alert('not ok!')
})
}
}
});
here is a pen what I created
https://codepen.io/anon/pen/gXwoQX?editors=1111
on submit null the validation is passes. what is wrong with this codes
There are issues raised on this subject in github - #590, #592.
None of them lead me to a solution, so I'd suggest a check inside the promise
.then(response => {
if(this.errors.items.length === 0) {
alert('is valid')
} else {
alert('is not valid')
}
A couple of other notes:
See below, catch() is technically the wrong place to handle validation errors, that should be inside the then(response) and when invalid response === false (but not working because of bug).
The catch() is probably for 'I blew up' errors.
.catch(function(e) {
// Catch errors
// alert('not valid') // don't process these here
})
Also this
v-validate="'required|in:sick, vacation'"
should be this (remove space before vacation)
v-validate="'required|in:sick,vacation'"
I am using dropzone in this example, it accepts a file and when the file is uploaded, I provide a preview template. The preview template then needs to render a new dropzone component "import-widget" in this example from the "import widget"
var Dropzone = require('dropzone')
Dropzone.autoDiscover = false
Dropzone.autoProcessQueue = false
export default {
data(){
return{
collection: false,
}
},
props: {
id: {
type: String,
required: true
},
method: {
type: String,
required: false
},
url: {
type: String,
required: true
},
clickable: {
type: Boolean,
default: true
},
acceptedFileTypes: {
type: String
},
thumbnailHeight: {
type: Number,
default: 200
},
thumbnailWidth: {
type: Number,
default: 200
},
showRemoveLink: {
type: Boolean,
default: true
},
maxFileSizeInMB: {
type: Number,
default: 2
},
maxNumberOfFiles: {
type: Number,
default: 5
},
autoProcessQueue: {
type: Boolean,
default: true
},
useFontAwesome: {
type: Boolean,
default: false
},
useCustomDropzoneOptions: {
type: Boolean,
default: false
},
dropzoneOptions: {
type: Object
}
},
events:{
processQueue: function(data){
this.processQueue();
}
},
methods: {
removeAllFiles: function () {
this.dropzone.removeAllFiles(true)
},
processQueue: function () {
console.log(this.dropzone.getQueuedFiles());
this.dropzone.processQueue();
console.log('Dropzone widget dispatching updateUploadCollection');
this.$dispatch('updateUploadCollection',this.collection);
}
},
computed: {
cloudIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-cloud-upload"></i>'
} else {
return '<i class="material-icons"></i>'
}
},
doneIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-check"></i>'
} else {
return ' <i class="material-icons"></i>'
}
},
errorIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-close"></i>'
} else {
return ' <i class="material-icons">error</i>'
}
}
},
ready () {
console.log('Dropzone Widget Ready');
var element = document.getElementById(this.id)
if (!this.useCustomDropzoneOptions) {
this.dropzone = new Dropzone(element, {
clickable: this.clickable,
thumbnailWidth: this.thumbnailWidth,
thumbnailHeight: this.thumbnailHeight,
maxFiles: this.maxNumberOfFiles,
maxFilesize: this.maxFileSizeInMB,
dictRemoveFile: 'Remove',
addRemoveLinks: false,
acceptedFiles: this.acceptedFileTypes,
autoProcessQueue: this.autoProcessQueue,
dictDefaultMessage: this.cloudIcon +'<br>Click Here To Add Files To Upload',
previewTemplate: '<div class="row dz-preview dz-file-preview"><div class="col-xs-1" data-dz-remove>x</div><div style="word-wrap: break-word;" class="col-xs-2 dz-filename"><span data-dz-name></span></div><div class="col-xs-2 dz-image"><img width="100%" data-dz-thumbnail /></div><div class="col-xs-4"><import-widget id="import-widget2" method="post" url="test/url"></import-widget></div></div>',
previewsContainer: '#previews',
})
} else {
this.dropzone = new Dropzone(element, this.dropzoneOptions)
}
// Handle the dropzone events
var vm = this
this.dropzone.on('addedfile', function (file) {
vm.$forceUpdate();
vm.$emit('vdropzone-fileAdded', file)
})
this.dropzone.on('removedfile', function (file) {
vm.$emit('vdropzone-removedFile', file)
})
this.dropzone.on('success', function (file, response) {
console.log('dropzone success response');
console.log(response);
if (!vm.collection) {
vm.$set('collection', []);
}
vm.collection.push(response);
vm.$emit('vdropzone-success', file, response)
});
this.dropzone.on('error', function (file, error, xhr) {
vm.$emit('vdropzone-error', file, error, xhr)
})
this.dropzone.on('sending', function(file, xhr, formData){
console.log('appending: ' + Laravel.csrfToken);
formData.append("_token", Laravel.csrfToken);
vm.$emit('vdropzone-sending', file, xhr, formData)
})
},
}
<form :method="method" :action="url" class="dropzone" :id="id">
</form>
<div class="row">
<div id="previews" class="col-sm-12">
</div>
</div>
See "previewTemplate" .. in here you can see that I've inserted component. The problem is that the system treats this as html instead of a vue component. What is the best way around this?