I have post.title and post.body in value, and I need that after changing the values in the text inputs, it is saved in data so that I can use these new values later for the user to write (PUT request) on the API. How can I achieve this?
Here's my code -
<template>
<div id="app">
<input type="text" v-model="createTitle" />
<input type="text" v-model="createBody" />
<button #click="addPost()">AddPost</button>
<ul>
<li v-for="(post, index) of posts">
<p>{{ post.title }}</p>
<p>{{ post.body }}</p>
<button #click="deleteData(index, post.id)">Delete</button>
<button #click="visiblePostID = post.id">
Изменить
</button>
<transition v-if="visiblePostID === post.id">
<p><input :value="post.title"><br><input :value="post.body">
<button type="button" #click="changePost(post.id, post.title, post.body)">Применить</button></p>
</transition>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'app',
data() {
return {
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
changePost(id, title, body) {
axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
title: title,
body: body
})
}
}
</script>
For two-way data binding you should use v-model. Read here.
<transition v-if="visiblePostID === post.id">
<p>
<input v-model="post.title">
<br>
<input v-model="post.body">
<button type="button" #click="changePost(post.id, post.title, post.body)">Применить</button>
</p>
</transition>
To add to #Riddhi's answer, you could use v-model on those inputs with temporary variables so that the model is not modified until the PUT-request is confirmed successful:
Add temporary data properties to hold the <input> values from the template:
// template
<transition v-if="visiblePostID === post.id">
<input v-model="tmpTitle" />
<input v-model="tmpBody" />
</transition>
// script
data() {
return {
tmpTitle: '',
tmpBody: ''
}
}
Replace the edit-button's handler with a method (named editPost()), and pass to the method the current post's ID, title, and body, which will be stored in the temporary data properties declared above:
// template
<button #click="editPost(post.id, post.title, post.body)">
Изменить
</button>
// script
methods: {
editPost(id, title, body) {
this.tmpTitle = title;
this.tmpBody = body;
this.visiblePostID = id;
}
}
Update changePost() to also take the current post, which will be updated with the temporary data properties once the PUT request is successful.
// template
<button type="button" #click="changePost(post, post.id, tmpTitle, tmpBody)">
Применить
</button>
// script
methods: {
async changePost(post, id, title, body) {
const { status } = await axios.put("https://jsonplaceholder.typicode.com/posts/" + id, { title: title, body: body });
if (status === 200 /* HTTP OK */) {
post.title = title;
post.body = body;
}
}
}
demo
Related
I am trying to create a simple CRUD app with Vuejs 3.
I have a homepage with a form (as a child component) and a table with created items (as another child component). I submit data via the form to API/database and the table updates. So far so good.
Then, for the update phase, I would like to have a detail page for each item where I also would have the form (the same component reused). But the idea is that form fields would be pre-populated with data from API/Database.
The table on the homepage has a route-link to a detail page and I am passing the id of the item as params. The detail page makes request to API based on id, receives item data and passes them as props into the form component.
If I try to render data directly into template like this, it works fine:
<p v-if="submitType === 'update' && item.id">{{ item.id }}</p>
Now, form fields are bound by v-model to data (form.id for example). But when I try to repopulate it as below, I always get undefined values.
data() {
return {
form: {
id: this.submitType === 'update' ? this.item.id : 0,
}
}
},
I suspect that problem is that the parent call to API is asynchronous and the passing of props is delayed. Because when I pass as props some hardcoded value, it appears as a value in the form field with no problem. Also if the form is shown only when props are received (with the v-if directive), the data.form.id is still undefined.
So is there any way how to pre-populate bound form fields with received props and still have the form component reused for insert and update actions? The rest of the relevant code is below. Thank you very much in advance
// Detail Page
<template>
<Form :item="item" submit-type="update"></Form>
</template>
<script>
export default {
data() {
return {
item: {}
}
},
created() {
callAPI(id).then( response => this.item = response.data )
}
}
</script>
// Form Component
<template>
<p v-if="submitType === 'update' && item.id">{{ item.id }}</p>
<div v-if="submitType === 'insert' || (submitType === 'update' && item.id )">
<section>
<form #submit.prevent="onSubmit">
<div>
<label for="id">ID</label>
<input id="id" name="id" v-model="form.id" type="number" placeholder="ID">
</div>
<input type="submit" value="Save">
</form>
</section>
</div>
</template>
<script>
export default {
name: 'Form',
props: {
item: {
type: Object
},
submitType: {
type: String
}
},
data() {
return {
form: {
id: this.submitType === 'update' ? this.item.id : 0,
}
}
},
}
</script>
You can try with watchers, take a look at following snippet:
const app = Vue.createApp({
data() {
return {
item: {},
type: 'update'
}
},
methods: {
change() {
this.type === 'update' ? this.type = 'insert' : this.type = 'update'
}
},
created() {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => this.item = json)
//callAPI(id).then( response => this.item = response.data )
}
})
app.component('myForm', {
template: `
<p v-if="submitType === 'update' && item.id">{{ item.id }}</p>
<div v-if="submitType === 'insert' || (submitType === 'update' && item.id )">
<section>
<form #submit.prevent="onSubmit">
<div>
<label for="id">ID</label>
<input id="id" name="id" v-model="form.id" type="number" placeholder="ID">
</div>
<input type="submit" value="Save">
</form>
</section>
</div>
`,
props: {
item: {
type: Object
},
submitType: {
type: String
}
},
data() {
return {
form: {}
}
},
methods: {
fillData() {
this.submitType === 'update' ? this.form = {...this.item} : this.form = {id: 0}
}
},
watch: {
item() {
this.fillData()
},
submitType() {
this.fillData()
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<button #click="change">switch type</button>
{{type}}
<my-form :item="item" :submit-type="type"></my-form>
</div>
I am creating commenting system using vue.js and laravel5.8.
I have done with models and seeding, so I have now 10 comments to one post (id is 51).
But I got this error,
Property or method "comment" is not defined on the instance but
referenced during render
and
Cannot read property 'user' of undefined
I have problems with fetching data.
I created a new endpoint for a comment function.
web.php
Route::get('results/{post}', 'ResultsController#show')->name('posts.show');
Route::get('results/{post}/comments', 'CommentsController#index');
I want to show comments in show.blade.php.
ResultsController.php
public function show(Post $post)
{
$recommended_posts = Post::latest()
->whereDate('date','>',date('Y-m-d'))
->where('category_id','=',$post->category_id)
->where('id','!=',$post->id)
->limit(7)
->get();
$posts['particular_post'] = $post;
$posts['recommended_posts'] = $recommended_posts;
$post->comments()->with('user')->get();
return view('posts.show',compact('posts'));
}
show.blade.php
<comments-component :post="{{ $posts['particular_post']->comments }}"></comments-component>
comments.vue
<div class="reply-comment" :v-for="comment in comments">
<div class="user-comment" >
<div class="user">
<!--<img src="" alt="" >-->
<avatar :username="comment.user.name" :size="30" ></avatar>
</div>
<div class="user-name">
<span class="comment-name">{{ comment.user.name }}</span>
<p> {{ comment.body }} </p>
</div>
</div>
<div class="reply">
<div class="seemorecomments">
see more
</div>
<button class="reply-button">
<i class="fas fa-reply"></i>
</button>
</div>
</div>
<script>
import Avatar from 'vue-avatar'
export default {
props: ['post'],
components: {
Avatar
},
mounted() {
this.fetchComments()
},
data: () => ({
comments: {
data: []
}
}),
methods: {
fetchComments() {
axios.get(`/results/${this.post.id}/comments`).then(({data}) => {
this.comments = data
})
}
}
}
CommentsController.php
public function index(Post $post)
{
return $post->comments()->paginate(5);
$post->comments()->with('user')->get();
}
comment.php
protected $with = ['user'];
I cannot get data object here.
Within axios, you may need to access data from the response that is returned (see console.log examples here), try the following within your comments component:
methods: {
fetchComments() {
axios.get(`/results/${this.post.id}/comments`).then((response) => {
this.comments = response.data.data
})
}
}
Note response.data.data is used.
I assume returning the ->paginate() will put the results within a data key in the returned array. If not, then just use response.data.
Also, in the controller getting the comments change to the following:
public function index(Post $post)
{
return $post->comments()->with('user')->paginate(5);
}
This will eager load the users with the queried comments.
I am trying to have custom filter search functionality . When i try to filter the records based on keywords in filter search, it does nothing but displays all posts. This always has this.search as ''. What is the potential fix in this case?
<form>
<fieldset class="w-100 left">
<input type="text" placeholder="Filter posts by keyword" v-model="search"/>
</fieldset>
</form>
<div>
<div id="mainSec">
<div v-for="post in filteredPosts">
<div v-for="result in results" v-if="post.userId == result.id" class="left ml-3 mt-3" >
<p >{{result.name}}</p>
<p>{{result.email}} | {{result.address.city}}</p>
</div>
<p style="float:left;width:100%"><strong>{{post.title.toUpperCase()}}</strong></p>
<p style="float:left;width:100%">{{post.body}}</p>
</div>
</div>
</div>
</html>
<script>
var vm = new Vue({
el: '#mainSec',
data() {
return {
search : '',
results : [],
posts : []
};
},
async created() {
try{
axios.get(urlUsers).then(response => {
this.results = response.data
});
axios.get(urlPosts).then(response => {
this.posts = response.data
});
}
catch(error){
console.log("Error is " + error);
}
},
computed : {
filteredPosts (){
if(this.search){
return this.posts.filter((item)=>{
return item.title.match(this.search);
})
}else{
return this.posts;
}
}
})
</script>
It is likely because you're use el: '#mainSec', to target a portion of your html code. That means that the v-model="search" does nothing, and thus the variable never gets updated and the computed never fires.
The easiest way to fix this is to wrap your <form> AND your <div id="mainSec"> with a div that you give the mainSec id to. (or give it a separate id and then reference that in your Vue el attribute.
Good afternoon, I have two child components Header and Pagination. In Header, I have an input search engine and two inputs (title and body) in order to be able to add a post to Pagination. I managed to transfer the search value to the Pagination component, but I don’t know how to transfer the value from two inputs (title, body). I use to transfer the event bus. Help me please pass the value of the two inputs (title, body) into the Pagination component when you click the AddPost button.
My code on GitHub
Screenshot of app
My code of component Header:
<template>
<div class="header">
<input type="text" v-model="search" class="header_input_search" placeholder="Search" #input="saveMessage" />
<img src="src/assets/milk.png">
<div class="header_div_inputs">
<input type="text" v-model="createTitle" class="created"/>
<p><input type="text" v-model="createBody" class="createBody"/></p>
</div>
<button #click="addPost()" class="addPost">AddPost</button>
</div>
</template>
<script>
import axios from 'axios';
import {eventEmitter} from './main'
export default {
name: 'Header',
data () {
return {
search: '',
createTitle: '',
createBody: '',
}
},
methods:{
saveMessage(){
eventEmitter.$emit('messageSave', this.search)
},
}
}
</script>
My code of component Pagination:
<template>
<div class = "app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {eventEmitter} from './main'
export default {
name: 'app',
data () {
return {
current: null,
page: 0,
visiblePostID: '',
pSearch: ''
}
},
mounted(){
this.$store.dispatch('loadPosts')
},
computed: {
...mapState([
'posts'
]),
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.filteredPosts.slice(start, end);
},
filteredPosts() {
return this.posts.filter((post) => {
return post.title.match(this.pSearch);
});
},
},
created(){
eventEmitter.$on('messageSave', (string) => {
this.pSearch = string
})
}
}
</script>
You can wrap title and body in an object
addPost() {
const post = {
title: this.createTitle,
body: this.createBody
}
eventEmitter.$emit('postAdd', post)
}
and then listen as normal
created(){
eventEmitter.$on('postAdd', (post) => {
console.log(post)
// do whatever you want
})
}
I have not worked on vue js but agreed with #ittus answer. You can make an object consisting of your required data which you want to share across the component and pass it as an event data.
I Created my API with PHP and here is the link: https://monstajams.co/streaming/rest/api/album/read.php
But anytime i put it in my Vue.js (Home.vue) file using axios No data is displayed on the front-end.
Here is my code below:
<ul class="ta-track-list" v-if="faqs && faqs.length">
<li class="ta-track-card column col-2 flex-column" v-for="faq of faqs">
<div class="inner">
<div class="artwork" role="link">
<span role="link" style="background-image: url(http://localhost/mymusic/assets/images/artwork/Wizkid-Soco.jpg);">
</span>
<div class="hover flex align-center justify-center">
<button id="webutton" class="ta-secondary play" onclick='playSong()'>
<i class="material-icons">play_arrow</i>
</button>
</div>
</div>
<div class="info">
<div class="title white-primary-hover" role="lin">{{ faqs }}</div>
<div class="username light-white-hover" role="link">{{ faq.artist }}</div>
<div class="released">{{ faq.duration }}</div>
</div>
</div>
</li>
</ul>
<script>
import axios from 'axios';
export default {
name: 'home',
data: () =>({
faqs: [],
errors: []
}),
created() {
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(response => {
this.faqs = response.data;
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
The problem is your code incorrectly assumes axios.get() resolves to the raw response, but it actually resolves to a response wrapper, where the raw response is contained in a data subproperty of the wrapper, which coincidentally has the same name as the target property within the response.
You can either change your Axios response handler to get the inner data field:
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(response => {
// this.faqs = response.data; // response.data is the raw response, but you need the array within the response (also named "data")
this.faqs = response.data.data;
})
demo
Or leave your frontend alone, and update your PHP backend to send only the array in the response:
// FROM THIS RESPONSE:
{
data: [/* PLAYLIST DATA */]
}
// TO THIS RESPONSE:
[/* PLAYLIST DATA */]
You are not updating your data accordingly to Vue docs.
For reactive changes see this document.
In the example below i update my list before Vue is mounted so rendering can occur accordingly.
let vm = new Vue({
el: "#app",
data: {
todos: []
},
methods: {
updateList() {
axios.get('https://monstajams.co/streaming/rest/api/album/read')
.then(res => {
res.data.data.forEach(item => {
Vue.set(vm.todos, vm.todos.length, {
text: item.title,
done: true
})
})
})
}
},
beforeMount() {
this.updateList()
},
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<div id="app">
<h2>Todos:</h2>
<ol>
<li v-for="todo in todos">
<label>
<span>
{{ todo.text }}
</span>
</label>
</li>
</ol>
</div>