Display an random Object from API Vue.js - vue.js

I need to display a random object from an array. The array comes from the API.
<template>
<div class="container">
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<div class="box">
<p class="name"> {{chosenName.title}} </p>
<p class="description"> {{chosenName.description}} </p>
<hr>
<img v-bind:src="chosenName.urlToImage" alt="">
<p class="author"> {{chosenName.author}} </p>
</div>
<button v-on:click="choose" id="choose-button">One more time</button>
</div>
</template>
#Options({
props: {
msg: String
},
data() {
return {
artworks: [],
errors: [],
chosenName: '',
}
},
created() {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
})
.catch(e => {
this.errors.push(e)
})
},
methods: {
choose() {
const chosenNumber = Math.floor(Math.random() * this.artworks.length);
this.chosenName = this.artworks[chosenNumber];
console.log(chosenNumber)
}
}
})
I managed to display the random object on click. What I would like to have is the object appearing at page load. I tried to put the function in the mounted cycle but with no good result like this ---> this.choose();

you need to call this.choose() in your mounted/created hook after the promise is fulfilled if you want to make it appear on the page load as well. e.g:
mounted () {
axios.get(url)
.then(response => {
this.artworks = response.data.names;
this.choose()
})
.catch(e => {
this.errors.push(e)
})
}

Related

Displaying posts details using Json returns undefine in vue3

After successfully fetching posts using JSON, I also want to display the details of the post on the post details.vue page but I get an undefined error.
I am sure there is something I am not doing right but I can't figure it out as well. Please someone help me with an idea on how to figure it out. Thanks.
Here is the code to fetch the posts from JSON
<div class="w-full lg:w-3/4 mb-7 p-1">
<div class="max-w-md bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl mb-6" v-for="post in posts" :key="post.id">
<div class="flex">
<div class="shrink-0">
<img class="h-28 w-full object-cover md:h-full md:w-28" src="../assets/img.png" alt="Man looking at item at a store">
</div>
<div class="pl-4 pt-2">
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">{{post.cat}}</div>
<router-link :to="{name: 'PostDetails', params:{id: post.id} }">
<h1 class="block mt-1 text-lg leading-tight font-medium text-black hover:underline font-bold">{{post.title}}</h1>
</router-link>
<!-- <p class="mt-2 text-slate-500">Getting a new business off the ground is a lot of hard work.</p> -->
</div>
</div>
</div>
</div>
JS
<script>
export defualt {
data(){
return{
posts:[]
}
},
mounted(){
fetch('http://localhost:3000/posts')
.then(res => res.json())
.then(data => this.posts = data)
.catch(err => console.log(error.message))
}
}
</script>
Code to display posts details
<template>
<h1>Current Post You Are reading</h1>
<div v-if="post">
<p>The Post Id is {{id}}</p>
<h1>{{post.title}}</h1>
</div>
Js
<script>
export default {
props:['id'],
data(){
return{
post: null
}
},
mounted(){
fetch('http://localhost:3000/posts/' + this.id)
.then(res => res.json())
.then(data => this.post = data)
.catch(err => console.log(error.message))
}
}
The Outcome
I think my sample will help you,
PostList.vue
<template>
<div>
<div v-for="item in posts" :key="item.id">
<router-link :to="'/postdetails/'+item.id">{{item.first_name}}</router-link>
<h1>{{item.first_name}}</h1>
<h2>{{item.last_name}}</h2>
<div>{{item.email}}</div>
<div>
<img v-bind:src="item.avatar" alt="">
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PostList',
data() {
return {
posts: []
}
},
created() {
fetch('https://reqres.in/api/users')
.then(res => res.json())
.then(data => {
this.posts = data.data
console.log("this.posts", this.posts)
})
.catch(err => console.log(err))
}
}
</script>
PostDetails.vue
<template>
<div>
<div v-for="item in posts" :key="item.id">
<h1>{{ item.first_name }}</h1>
<h2>{{ item.last_name }}</h2>
<div>{{ item.email }}</div>
<div>
<img v-bind:src="item.avatar" alt="" />
</div>
</div>
</div>
</template>
<script>
export default {
name: "PostDetails",
data() {
return {
msg: "welcome to PostList page",
posts: [],
id: this.$route.params && this.$route.params.id,
};
},
created() {
fetch("https://reqres.in/api/users/" + this.id)
.then((res) => res.json())
.then((data) => {
this.posts = data;
})
.catch((err) => console.log(err));
},
};
</script>
And in router index.js
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '#/components/HelloWorld'
import PostList from '#/components/PostList'
import PostDetails from '#/components/PostDetails'
Vue.use(Router)
export default new Router(
{ routes: [
{ path: '/', redirect: { name:'PostList' }},
{ path: '/postlist', name: 'PostList', component: PostList },
{ path: '/postdetails/:id', name: 'PostDetails', component: PostDetails }
]}
)

how to create autocomplete component in vue js?

I am facing an issue with my autocomplete component. whenever i type anything into the input field the input is reset.I mean it does not let me type anything.It just keeps getting reset before i could fully type anything.
main.js
Vue.component('g-autocomplete', {
props: ['list','value','title'],
data() {
return {
input: '',
}
},
template: `<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="input" #input="handleInput"/>
<ul v-if="input" >
<li v-for="(item, i) in list" :key="i" #click="setInput(item)" >
<!-- {{ autocompleteData }} -->
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{item.id}}
</p>
<p>
<b>Description:</b>
{{item.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{item.id}}
</p>
<p>
<b>First Name:</b>
{{item.firstName}}
</p>
<p>
<b>Last Name:</b>
{{item.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>`,
methods: {
handleInput(e) {
console.log('inside handleInput')
this.$emit('input', e.target.value)
},
setInput(value) {
console.log('inside setInput')
this.input = value
this.$emit('click', value)
},
},
watch: {
$props: {
immediate: true,
deep: true,
handler(newValue, oldValue) {
console.log('new value is'+newValue)
console.log('old value is'+oldValue)
console.log('value inside handler'+this.value)
console.log('list inside handler'+this.list)
console.log('title inside handler'+this.title)
this.input=this.value
}
}
// msg(newVal) {
// this.msgCopy = newVal;
// }
}
})
i reuse the above component from diffrent vue pages's like this-
<b-field label="Custom Business Unit">
<g-autocomplete v-on:input="getAsyncDataBusinessUnit" v-on:click="(option) => {updateValue(option.id,'businessUnit')}" :value="this.objectData.businessUnit" :list="dataBusinessUnit" title='businessUnit' >
</g-autocomplete>
</b-field>
my debounce function that is called when something is typed into the input field.
getAsyncDataBusinessUnit: debounce(function(name) {
if (!name.length) {
this.dataBusinessUnit = [];
return;
}
this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/?filter={id} LIKE '%25${name}%25' OR {description} LIKE '%25${name}%25'`)
.then(response => {
this.dataBusinessUnit = [];
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
this.isFetching = false;
});
}, 500),
what could be the issue here ? Also i noticed that the issue doesn't happen if i comment out the body of the debounce function.So therefore i feel there is something in the debounce function that is causing this.I will try to isolate the problem but i want to understand what exactly is causing this issue. Plz help?

Run a Vue function to fetch array data inside a v-for when a Bootstrap collapse is clicked

Good day forks. Please help. I want to Run a Vue function to fetch array data inside a v-for when a Bootstrap collapse is clicked. So I have an array of items with (id, title, description etc) attributes. Then for each of the item, it has an array of sub-items. So I want to fetch the sub-items when I click the item and shows in a bootstrap collapse div as follows:
<div v-for="item in items" :key="item.id">
<p>
<a :href="'#' + item.id" data-toggle="collapse">{{
item.item_name
}}</a>
</p>
<div class="collapse" :id="item.id">
<div v-html="getSubItems(item.id)">
<p v-for="sub_item in sub_items" :key="sub_item.id">
{{ sub_item.sub_item_name }}
</p>
</div>
<p>
<span class="glyphicon glyphicon-time"></span> 5:44 Status
<span class="label label-success pull-right">{{
item.item_status ? "Done" : "Pending"
}}</span>
</p>
</div>
<hr />
</div>
And the JavaScript is as follows:
export default {
props: {},
data() {
return {
id: 1,
items: [],
sub_items: []
};
},
created() {
axios
.get("http://ip/api/v1/items")
.then(response => {
console.log(response.data);
this.topics = response.data.data;
})
.catch(error => {
console.log(error);
});
},
methods: {
getSubItems: function(item_id) {
return axios
.get("http://ip/api/v1/sub-items/" + item_id)
.then(response => {
console.log(response.data);
this.sub_items = response.data.data;
})
.catch(error => {
console.log(error);
});
}
}
};
If I was you, I would respond to a click event on the anchor tag to get the sub_items.
v-html is used to render raw HTML which is probably why your code doesn't work.
I've created a snippet below (without the axios) to show one way you could get it working.
new Vue({
el: "#app",
data() {
return {
id: 1,
items: [],
sub_items: []
};
},
created() {
this.getItems();
},
methods: {
getItems: function() {
this.items = [{
id: 1,
item_name: "Test Item"
}];
},
getSubItems: function(item_id) {
this.sub_items = [{
id: 1,
sub_item_name: "Test Sub Item"
}];
}
}
});
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div id="app">
<div v-for="item in items" :key="item.id">
<p>
<a :href="'#panel-' + item.id" data-toggle="collapse" #click="getSubItems(item.id)" role="button" aria-expanded="false" aria-controls="collapseExample">{{
item.item_name
}}</a>
</p>
<div class="collapse" :id="'panel-' + item.id">
<div>
<p v-for="sub_item in sub_items" :key="sub_item.id">
{{ sub_item.sub_item_name }}
</p>
</div>
<p>
<span class="glyphicon glyphicon-time"></span> 5:44 Status
<span class="label label-success pull-right">{{
item.item_status ? "Done" : "Pending"
}}</span>
</p>
</div>
<hr />
</div>
</div>
Try this:
<a :href="'#' + item.id" data-toggle="collapse" #click="getSubItems(item)">{{
item.item_name
}}</a>
//
getSubItems: function(item) {
if (!item.subItems){
return axios
.get("http://ip/api/v1/sub-items/" + item_id)
.then(response => {
console.log(response.data);
this.sub_items = response.data.data;
})
.catch(error => {
console.log(error);
});
}
}
}
only fetch subitems if you don't already have. So you should make a check in the function if you have them.
The simplest way I think would be to save the results on the item itself as item.subItems then once you open you don't need to open again

How to make pagination?

How to make pagination. I tried many times already, but I can’t do it. I have to paginate without Laravel. The problem is that I can not make a cycle that will display the number of pages, each page should have 10 posts, and there are 98 posts in total. I made the property to be calculated, thanks to which you can find out how many pages there will be. I made a page switch that works. But for some reason, the cycle with which I will display the number of pages does not work for me, I cannot understand what the problem is?
Screenshot
My Vue js:
import axios from 'axios';
export default {
name: 'app',
data () {
return{
counter: 1,
zero: 0,
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
watch: {
counter: function(newValue, oldValue) {
this.getData()
}
},
created(){
this.getData()
},
computed: {
evenPosts: function(posts){
return Math.ceil(this.posts.length/10);
}
},
methods: {
getData() {
axios.get(`http://jsonplaceholder.typicode.com/posts?_start=${this.counter}+${this.zero}&_limit=10`).then(response => {
this.posts = response.data
})
},
// even: function(posts) {
// return Math.ceil(this.posts.length/10)
// },
deleteData(index, id) {
axios.delete('http://jsonplaceholder.typicode.com/posts/' + id)
.then(response => {
console.log('delete')
this.posts.splice(index, 1);
})
.catch(function(error) {
console.log(error)
})
},
addPost() {
axios.post('http://jsonplaceholder.typicode.com/posts/', {
title: this.createTitle,
body: this.createBody
}).then((response) => {
this.posts.unshift(response.data)
})
},
changePost(id, title, body) {
axios.put('http://jsonplaceholder.typicode.com/posts/' + id, {
title: title,
body: body
})
},
},
}
My html:
<div id="app">
<div class="smallfon">
<div class="blocktwitter"><img src="src/assets/twitter.png" class="twitter"/></div>
<div class="addTextPost">Add a post</div>
<input type="text" v-model="createTitle" class="created"/>
<input type="text" v-model="createBody" class="created"/>
<div><button #click="addPost()" class="addPost">AddPost</button></div>
<div class="post1">
<div class="yourPosts">Your Posts</div>
<ul>
<li v-for="(post, index) of posts" class="post">
<p><span class="boldText">Title:</span> {{ post.title }}</p>
<p><span class="boldText">Content:</span> {{ post.body }}</p>
<button #click="deleteData(index, post.id)" class="buttonDelete">Delete</button>
<button #click="visiblePostID = post.id" class="buttonChange">Change</button>
<div v-if="visiblePostID === post.id" class="modalWindow">
<div><input v-model="post.title" class="changePost"><input v-model="post.body" class="changePost"></div>
<button type="button" #click="changePost(post.id, post.title, post.body)" class="apply">To apply</button>
</div>
</li>
</ul>
<button type="button" #click="counter -=1" class="prev">Предыдущая</button>
<!-- <div class="counter">{{ counter }}</div> --> <span v-for="n in evenPosts" :key="n.id">{{ n }} </span>
<button type="button" #click="counter +=1" class="next">Следущая</button>
<!-- <span v-for="n in evenPosts" :key="n.id">{{ n }} </span> -->
</div>
</div>
</div>
If you bind a limit to your fetching request (axios.get(...&_limit=10)), you can't return a paginate count because your computed evenPost property will always return 1 i.e Math.ceil(10/10) == 1
To fix your pagination, remove the parameters query to get the data:
getData() {
axios.get('https://jsonplaceholder.typicode.com/posts').then(response => {
this.posts = response.data
})
}
Then change the default counter page to 0 and add a computed property to return 10 posts based on it:
data () {
return {
counter: 0,
//...
}
},
computed: {
paginatedPosts() {
const start = this.counter * 10;
const end = start + 10;
return this.posts.slice(start, end);
}
}
Now you can iterate on this property:
<ul>
<li v-for="(post, index) of paginatedPosts" class="post">
...
</li>
</ul>
Basic live example

Is it possible to sync Vuejs components displayed multiple times on the same page?

I have a web page that displays items. For each items there is a button (vuejs component) which allow user to toggle (add/remove) this item to his collection.
Here is the component:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': dAdded, 'btn-primary': !dAdded}">{{ dText }}</button>
</template>
<script>
export default {
props: {
added: Boolean,
text: String,
id: Number,
},
data() {
return {
dAdded: this.added,
dText: this.text,
dId: this.id
}
},
watch: {
added: function(newVal, oldVal) { // watch it
this.dAdded = this.added
},
text: function(newVal, oldVal) { // watch it
this.dText = this.text
},
id: function(newVal, oldVal) { // watch it
this.dId = this.id
}
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.dId
}))
.then(response => {
this.dText = response.data.message
let success = response.data.success
this.dText = response.data.new_text
if (success) {
this.dAdded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.dId);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
}
}
</script>
For each item, the user can also open a modal, loaded by a click on this link:
<a href="#" data-toggle="modal" data-target="#popModal" #click="id = {{$pop->id}}">
<figure>
<img class="card-img-top" src="{{ URL::asset($pop->img_path) }}" alt="Card image cap">
</figure>
</a>
The modal is also a Vuejs component:
<template>
<section id="pop" class="h-100">
<div class="card">
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-1 flex-column others d-none d-xl-block">
<div class="row flex-column h-100">
<div v-for="other_pop in pop.other_pops" class="col">
<a :href="route('frontend.pop.collection.detail', {collection: pop.collection.slug, pop: other_pop.slug})">
<img :src="other_pop.img_path" :alt="'{{ other_pop.name }}'" class="img-fluid">
</a>
</div>
<div class="col active order-3">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
</div>
</div>
<div class="col-12 col-lg-6 content text-center">
<div class="row">
<div class="col-12">
<img :src="pop.img_path" :alt="pop.name" class="img-fluid">
</div>
<div class="col-6 text-right">
<toggle-pop :id="pop.id" :added="pop.in_user_collection" :text="pop.in_user_collection ? 'Supprimer' : 'Ajouter'"></toggle-pop>
</div>
<div class="col-6 text-left">
<!-- <btnaddpopwhishlist :pop_id="propid" :added="pop.in_user_whishlist" :text="pop.in_user_whishlist ? 'Supprimer' : 'Ajouter'"></btnaddpopwhishlist> -->
</div>
</div>
</div>
<div class="col-12 col-lg-5 infos">
<div class="header">
<h1 class="h-100">{{ pop.name }}</h1>
</div>
<div class="card yellow">
<div class="card p-0">
<div class="container-fluid">
<div class="row">
<div class="col-3 py-2">
</div>
<div class="col-6 py-2 bg-lightgray">
<h4>Collection:</h4>
<h3>{{ pop.collection ? pop.collection.name : '' }}</h3>
</div>
<div class="col-3 py-2 bg-lightgray text-center">
<a :href="route('frontend.index') + 'collections/' + pop.collection.slug" class="btn-round right white"></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
export default {
props: {
id: Number
},
data() {
return {
pop: {
collection: {
}
}
}
},
ready: function() {
if (this.propid != -1)
this.fetchData()
},
watch: {
id: function(newVal, oldVal) { // watch it
// console.log('Prop changed: ', newVal, ' | was: ', oldVal)
this.fetchData()
}
},
computed: {
imgSrc: function() {
if (this.pop.img_path)
return 'storage/images/pops/' + this.pop.img_path
else
return ''
}
},
methods: {
fetchData() {
axios.get(route('frontend.api.v1.pops.show', this.id))
.then(response => {
// JSON responses are automatically parsed.
// console.log(response.data.data.collection)
this.pop = response.data.data
})
.catch(e => {
this.errors.push(e)
})
// console.log('fetchData')
}
}
}
</script>
Here is my app.js script :
window.Vue = require('vue');
Vue.component('pop-modal', require('./components/PopModal.vue'));
Vue.component('toggle-pop', require('./components/TogglePop.vue'));
const app = new Vue({
el: '#app',
props: {
id: Number
}
});
I would like to sync the states of the component named toggle-pop, how can I achieve this ? One is rendered by Blade template (laravel) and the other one by pop-modal component. But they are just the same, displayed at different places.
Thanks.
You could pass a state object as a property to the toggle-pop components. They could use this property to store/modify their state. In this way you can have multiple sets of components sharing state.
Your component could become:
<template lang="html">
<button type="button" #click="toggle" name="button" class="btn" :class="{'btn-danger': sstate.added, 'btn-primary': !sstate.added}">{{ sstate.text }}</button>
</template>
<script>
export default {
props: {
sstate: {
type: Object,
default: function() {
return { added: false, text: "", id: -1 };
}
}
},
data() {
return {};
},
methods: {
toggle: function(event) {
axios.post(route('frontend.user.profile.pop.toggle', {
pop_id: this.sstate.id
}))
.then(response => {
this.sstate.text = response.data.message
let success = response.data.success
this.sstate.text = response.data.new_text
if (success) {
this.sstate.ddded = success.attached.length
let cardPop = document.getElementById('card-pop-'+this.sstate.id);
if(cardPop)
cardPop.classList.toggle('owned')
}
})
.catch(e => {
console.log(e)
})
}
};
</script>
Live demo
https://codesandbox.io/s/vq8r33o1w7
If you are 100% sure that all toggle-pop components should always have the same state, you can choose to not define data as a function. Just declare it as an object.
data: {
dAdded: this.added,
dText: this.text,
dId: this.id
}
In https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function, it mentions
a component’s data option must be a function, so that each instance
can maintain an independent copy of the returned data object
If Vue didn’t have this rule, clicking on one button would affect the
data of all other instances
Since you want to sync the data of all toggle-pop component instances, you don't have to follow the data option must be a function rule.