add comment using v-model inside v-for loop in posts - vue.js

I'm getting a posts array from a vuex getter and looping through them with v-for to display each post and it's comment then i added an input field and binded it with v-model to get the input value and dispatch an action to send the value to the API
<div class="post-content" v-for="(post, index) in posts">
<div class="post-comment">
<input type="text" class="form-control" placeholder="Add a comment" v-model="comment" #keyup.enter="addComment(post.id)">
</div>
</div>
<script>
export default {
data() {
return {
postContent: '',
comment: ''
}
},
methods: {
addPost() {
this.$store.dispatch('addPost', {
content: this.postContent
})
this.postContent = ''
},
addComment(postID, index) {
this.$store.dispatch('addComment', {
body: this.comment,
post_id: postID
})
}
},
created(){
this.$store.dispatch( 'loadFeed' )
},
computed: {
postsLoadStatus(){
return this.$store.getters.getPostsLoadStatus
},
posts(){
return this.$store.getters.getFeed
}
},
}
</script>
but when i set the v-model to a data property and try to type something in the input it's assigned on all posts so what's the right way to grab the comment data

Create a getter that accepts a function:
getters () {
getCommentByPostId(state) => (post_id) => {
return state.posts.find((post) => post.id === post_id).comment
}
}
Then use that getter on that :value and not v-model:
<input type="text" class="form-control" placeholder="Add a comment" :value="$store.getters['getCommentByPostId'](post.id)" #keyup.enter="addComment(post.id)">
Make sure to handle scenarios where the comment doesn't exist and return an empty string, too.

Related

vue js filtering with search bar

I have created an app that requests from an API the data and creates flexboxes. Now I added a search box and I would like to filter the articles by their contact and/or title.
I've also created a computed property to filter the returned list of items but when I replace in line 11 the paginated('items') with paginated('filteredArticles') that returns nothing.
What did I do wrong?
<template>
<div id="app">
<div class="search-wrapper">
<input type="text"
class="search-bar"
v-model="search"
placeholder="Search in the articles"/>
</div>
<paginate ref="paginator" class="flex-container" name="items" :list="items">
<li v-for="(item, index) in paginated('items')" :key="index" class="flex-item">
<div id="image"><img :src="item.image && item.image.file" /></div>
<div id="date">{{ item.pub_date }}</div>
<div id="title"> {{ item.title }}</div>
<div class="article">{{item.details_en}}</div>
</li>
</paginate>
<paginate-links for="items" :limit="2" :show-step-links="true"></paginate-links>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
items: [],
paginate: ["items"],
search:'',
};
},
created() {
this.loadPressRelease();
},
methods: {
loadPressRelease() {
axios
.get(`https://zbeta2.mykuwaitnet.net/backend/en/api/v2/media-center/press-release/?page_size=61&type=5`)
.then((response) => {
this.items = response.data.results;
});
},
},
computed:{
filteredArticles() {
return this.items.filter(item=>item.includes(this.search))
}
}
};
</script>
You need fields you want to search and connvert search string and fields with toLowerCase() or toUpperCase():
computed : {
filteredArticles() {
if (!this.search) return this.items
return this.items.filter(item => {
return (item.title.toLowerCase().includes(this.search.toLowerCase()) || item.contact.toLowerCase().includes(this.search.toLowerCase()));
})
}
}
Your computed doesn't seem correct. Since items is an array of objects, you'd need to do this:
filteredArticles() {
if (!this.search) {
return this.items;
}
return this.items.filter(item => {
return item.title.includes(this.search);
})
}
Note that this will only search the title field, and it's case sensitive.

Update child component value on axios response using v-model

Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.

VueJS handling dynamic input

I have an vs-input with :id=info.tag. How can I change the input value based on the :id tag? Data is dynamic
<template>
<vs-row vs-type="flex" vs-justify="space-around">
<div v-for="info in information" :key="info.id">
<vs-col vs-type="flex" vs-justify="center" vs-align="center" vs-w="3">
<vx-card :title="info.user_1" :subtitle="info.desc">
<div slot="footer">
<vs-row vs-justify="center">
<vx-input-group class="mb-base">
<vs-input :id="info.tag" placeholder="Data Output" readonly />
<br>
<div class="text-center">
<vs-button color="primary" #click="grabData($event, generator.tag)">Grab data</vs-button>
</div>
</vx-input-group>
</vs-row>
</div>
</vx-card>
</vs-col>
</div>
</vs-row>
</template>
<script>
export default {
data() {
return {
information: null
}
},
created () {
this.$store.dispatch('user/getInformation').then(this.$$nextTick).then(() => {
this.information = this.$store.state.user.information
})
},
methods: {
grabData(data, tag) {
this.$store.dispatch('user/grabData', {tag})
.then(res => {
//Nice! Set input value based on the tag
})
.catch(error => {
//hmm
})
},
}
}
</script>
I'll assume that each item in information has a field named value
Then you can just change vs-input to this
If you don't have a field name value you can add it
created () {
this.$store.dispatch('user/getInformation').then(this.$$nextTick).then(() => {
this.information = this.$store.state.user.information.map(item => Object.assign({value:''}, item))
})
}
So given vs-input I assume you are using VueSax.
It has v-model property which will line up to the value.
add v-model to vs-input
<vs-input :id="info.tag" v-model="infoInput" placeholder="Data Output" readonly/>
make sure to include property into data
data() {
return {
information: null,
infoInput: '',
}
},
then when you have an event just change the value.
this.infoInput = "new value"
This will update the value in vs-input
More info can be found v-model api

Send value from API request from a component to another Vue.JS

I have a component which allow to retrieve the datas from a rest API...
My template allow user to enter an input (id) and to find the user associated with the user. I also have a component which is called dynamically.
<template>
<div>
<!-- form -->
<form>
<input type="text" v-model="userId" id="userId">
<button type="submit" class="btn btn-primary" #click="getUser($event); !isExistingUser">Get User</button>
</form>
<!-- result -->
<div v-if="!showComponent">
{{ user["id"] }} {{ user["username"] }} {{ user["email"] }}
<button #click="showComponent = !showComponent">Editer</button>
</div>
<!-- Edit the user -->
<div v-if="showComponent">
<edit-user :toUpdate="updateUser"></edit-user>
</div>
</div>
</template>
In the script part I have datas and methods :
The objective is to send the user that i collect and to send it to the update user. For this I created a data binding.
I also try to set the value of the object in the getUser method. And i can display the value.
<script>
import axios from "axios";
import EditUserForUpdate from "./EditUserForUpdate";
export default {
name: "FindUser",
components: {
"edit-user": EditUserForUpdate
},
data() {
return {
toUpdate: Object,
user: null,
isExistingUser: false,
userId: "",
userEmail:"",
userUsername: "",
showComponent: false
};
},
methods: {
getUser(event) {
axios
.get("http://localhost:4000/api/users/" + this.userId)
.then(response => {
console.log(response);
this.user = response.data.data;
var toUpdate = {};
toUpdate = { upUserName: this.user.username, upUserEmail: this.user.email, upId: this.user.id};
console.log(toUpdate);
});
}
}
};
</script>
Finally in the child component :
<script>
export default {
name: "EditUserForUpdate",
data: function () {
return {
updateUser: ''
}
},
props: {
updateUser: Object
},
methods: {
beforeMount () {
var updateUser = this.updateUser // save props data to itself's data and deal with it
console.log("userToUpdate : " + updateUser);
}
}
}
</script>
My issue is that I don't retrieve the data in the child module for an unknown reason.
The property is named toUpdate and not updateUser.
Update your prop accordingly in the EditUserForUpdate component:
props: {
toUpdate: Object
}
And of course, localize that object for manipulation:
beforeMount() {
this.updateUser = this.toUpdate
}

Retrieve data attribute value of clicked element with v-for

I've made a datalist which is filled dynamically and it works correctly.
Now, I need listen the click event on the options to retrieve the data-id value and put it as value in the input hidden.
I already tried with v-on:click.native and #click but there is no response in the console.
Any idea? I'm just starting at Vue, hope you can help me.
Edit:
Looks like it doesn't even fire the function. I've tried v-on:click="console.log('Clicked')" but nothing happens.
<input type="hidden" name="id_discipline" id="id_discipline">
<input list="disciplines" id="disciplines-list">
<datalist id="disciplines">
<option
v-for="discipline in disciplines"
:key="discipline.id_discipline"
:data-id="discipline.id_discipline"
v-on:click="updateDisciplineId($event)"
>{{discipline.name}}</option>
</datalist>
methods: {
updateDisciplineId(event) {
console.log('clicked!);
}
},
Using datalist is not suited for what you want to acheive, however there's a workaround with a limitation.
Template:
<template>
<div>
<input
type="text"
name="id_discipline"
v-model="selectedID"
placeholder="Data id value of clicked"
/>
<input
#input="onChange"
list="disciplines"
id="disciplines-list"
class="form-control"
placeholder="Seleccionar disciplina"
/>
<datalist id="disciplines">
<option
v-for="discipline in disciplines"
:key="discipline.id_discipline"
:data-value="discipline.id_discipline"
>{{ discipline.name }}</option
>
</datalist>
</div>
</template>
Script Part:
<script>
export default {
data() {
return {
selectedID: "",
id_discipline: "",
disciplines: [
{
id_discipline: 1,
name: "Yoga"
},
{
id_discipline: 2,
name: "Functional"
}
]
};
},
methods: {
onChange(e) {
this.getID(e.target.value).then(
resposnse => (this.selectedID = resposnse)
);
},
async getID(value) {
let promise = new Promise((resolve, reject) => {
this.disciplines.forEach(item => {
if (item.name === value) resolve(item.id_discipline);
});
});
return await promise;
}
}
};
</script>
Here's a working Sandbox demo.
**Limitation: Discipline name (Yoga, functional) should be unique.