Onload method when the page is loading VUE - javascript

I want to call the method when the page is loading. So far I call the method when I click, but when I try to use mounted to automatically call the method, it keeps failing, do you have some suggestion?
Below is the code:
<template>
<div>
<AppHeader></AppHeader>
<div style="display: flex">
<ul>
<li
style="cursor: pointer"
v-for="exchange of profileExchanges"
:key="exchange.id"
#click="getImagesByExchange(exchange.id)"
>
<div style="padding: 20px; border: solid 2px red">
<h3>Brand: {{exchange.brand}}</h3>
<p>Description: {{exchange.description}}</p>
<p>Category: {{exchange.category}}</p>
</div>
</li>
</ul>
<div>
<span style="margin: 10px" v-for="url of exchangeImageUrls" :key="url">
<img width="250px" :src="url" />
</span>
</div>
</div>
<br />
</div>
</template>
<script>
import AppHeader from '#/components/Header5'
export default {
components: {
AppHeader
},
created() {
this.$store.dispatch('exchange/getExchangesByProfile', this.$store.state.auth.user.profile.user)
},
data() {
return {
selectedExchangeId: '',
exchanges: []
}
},
computed: {
user() {
return this.$store.state.auth.user
},
profile() {
return this.user.profile || {}
},
profileExchanges() {
return this.$store.getters['exchange/profileExchanges']
},
exchangeImageUrls() {
return this.$store.getters['exchange/imageUrls']
}
},
methods: {
getImagesByExchange(exchangeId) {
this.$store.dispatch('exchange/getImagesByExchange', exchangeId)
},
getListings() {
},
updateProfile(profile, closeModal) {
this.$store.dispatch('auth/updateProfile', profile)
.then(_ => {
closeModal()
})
}
}
}
</script>
I try to put mounted like this
mounted: function() {
this.getImagesByExchange() // Calls the method before page loads
},
But it keeps failing. I guess the problem is how to access the key, but not sure.

If the selectedExchangeId is already not set, you could get the first element of the profileExchanges array and pass the id to the getImagesByExchange function:
mounted() {
this.getImagesByExchange(this.profileExchanges[0].id) // Calls the method before page loads
},
EDIT
Looking again at your code one could suppose that, at the moment the component is mounted the profileExchanges property might not yet be set. One way to handle this is to call both of them inside the created like this:
async created() {
await this.$store.dispatch(
'exchange/getExchangesByProfile',
this.$store.state.auth.user.profile.user
);
if (this.profileExchanges && this.profileExchanges.length) {
this.getImagesByExchange(this.profileExchanges[0].id);
}
}

Related

Display dropdowns dynamically in one component

I want to have multiple dropdowns in one component using one variable to display or not and also clicking away from their div to close them:
<div class="dropdown">
<button #click.prevent="isOpen = !isOpen"></button>
<div v-show="isOpen">Content</div>
</div>
// second dropdown in same component
<div class="dropdown">
<button #click.prevent="isOpen = !isOpen"></button>
<div v-show="isOpen">Content</div>
</div>
data() {
return {
isOpen: false
}
},
watch: {
isOpen(isOpen) {
if(isOpen) {
document.addEventListener('click', this.closeIfClickedOutside)
}
}
},
methods: {
closeIfClickedOutside(event){
if(! event.target.closest('.dropdown')){
this.isOpen = false;
}
}
}
But now when I click one dropdown menu it displays both of them. I am kinda new to vue and cant find way to solve this
To use just one variable for this, the variable needs to identify which dropdown is open, so it can't be a Boolean. I suggest storing the index (e.g., a number) in the variable, and conditionally render the selected dropdown by the index:
Declare a data property to store the selected index:
export default {
data() {
return {
selectedIndex: null
}
}
}
Update closeIfClickedOutside() to clear the selected index, thereby closing the dropdowns:
export default {
methods: {
closeIfClickedOutside() {
this.selectedIndex = null
}
}
}
In the template, update the click-handlers to set the selected index:
<button #click.stop="selectedIndex = 1">Open 1</button>
<button #click.stop="selectedIndex = 2">Open 2</button>
Also, update the v-show condition to render based on the index:
<div v-show="selectedIndex === 1">Content 1</div>
<div v-show="selectedIndex === 2">Content 2</div>
Also, don't use a watcher to install a click-handler on the document because we want to know about the outside-clicks when this component is rendered. It would be more appropriate to add the handler in the mounted hook, and then remove in the beforeDestroy hook:
export default {
mounted() {
document.addEventListener('click', this.closeIfClickedOutside)
},
beforeDestroy() {
document.removeEventListener('click', this.closeIfClickedOutside)
},
}
demo
Make an array and loop through it, much easier that way.
<template>
<div id="app">
<div class="dropdown" v-for="(drop, index) in dropData" :key="index">
<button #click="openDropdown(index);">{{ drop.title }}</button>
<div v-show="isOpen === index">{{ drop.content }}</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isOpen: null,
dropData: [
{
title: "Hey",
content: "Hey it's content 1"
},
{
title: "Hey 2",
content: "Hey it's content 2"
},
{
title: "Hey 3",
content: "Hey it's content 3"
},
]
};
},
methods: {
openDropdown(idx){
if (this.isOpen === idx) {
this.isOpen = null;
} else {
this.isOpen = idx;
}
}
}
};
</script>

Default click on a button when component loads in vue js

I have a button in vue component within template as follow:
<a href="#" #click="openTab" class="border-red px-8" id="activeSlide" data-target-quote="#yvoefrance">
<img :src="inactive_logo[0]" class="logo" alt="yvoefrance logo" />
</a>
I want it to be clicked by default when components loads after refreshing the page, how can I achieve this? I tried following but didn't work for me.
I thought the right place is created. Can anyone help? Thank you in advance.
export default {
name: "component.showcase",
components: {
// ...
},
data() {
return {
// data here....
};
},
created() {
document.querySelector("#activeSlide").click();
},
mounted() {},
beforeDestroy() {},
computed: {},
methods: {
openTab: function(e) {
e.preventDefault();
const target_tab = e.target.parentElement.dataset.targetQuote;
document.querySelector(target_tab).classList.add("active");
e.target.src = require(`#/assets/img/testimonials/${target_img}_active.png`);
}
}
};
The button should call a method when clicked:
<button #click="someMethod">Show Content</button>
Then you can just call that method programmatically from a lifecycle hook instead of trying to manually trigger a click on the button:
methods: {
someMethod() {
console.log('someMethod called');
}
},
created() {
this.someMethod(); // Run the button's method when created
}
EDIT to match your edit:
You are using DOM manipulation but should manipulate data instead and let Vue handle the DOM. Here is a basic example of how you can do what you want:
new Vue({
el: "#app",
data() {
return {
logos: [
{
urlInactive: 'https://via.placeholder.com/150/000000/FFFFFF',
urlActive: 'https://via.placeholder.com/150/FFFFFF/000000',
isActive: false
},
{
urlInactive: 'https://via.placeholder.com/150/666666/FFFFFF',
urlActive: 'https://via.placeholder.com/150/999999/000000',
isActive: false
}
]
}
},
methods: {
toggleActive(logo) {
logo.isActive = !logo.isActive;
}
},
});
<div id="app">
<a v-for="logo in logos" #click="toggleActive(logo)">
<img :src="logo.isActive ? logo.urlActive : logo.urlInactive" />
</a>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

Passing vue.js store data to a event click handler

I am using regular Vue.js in my project. I store data in a store made from scratch and use it in a template:
<template>
<div>
<div class="row">
<div v-for="(picture, index) in storeState.pictures"
:key="index"
class="col-md-2 my-auto">
<div >
<img class="img-thumbnail img-responsive"
:src="picture.url"
#click="deleteMe">
</div>
</div>
</div>
</div>
</template>
<script>
import { store } from "../common/store.js"
export default {
name:"PictureUpload",
data() {
return {
storeState: store.state,
};
},
methods: {
deleteMe() {
let apiUrl = this.picture.url
console.log(apiUrl)
}
}
}
</script>
My pictures are rendering well but now I want to add a delete() function to the picture #click and whenever I click on the button I get:
TypeError: Cannot read property 'url' of undefined
So how can I access my picture data inside my method?
You should pass picture as parameter in the click handler :
#click="deleteMe(picture)">
and refer to it in the method like :
methods: {
deleteMe(picture) {
let apiUrl = picture.url //omit this
console.log(apiUrl)
}
}
the storeState should be a computed property :
export default {
name:"PictureUpload",
data() {
return {
};
},
computed:{
storeState(){
return store.state;
}
},
methods: {
deleteMe(picture) {
let apiUrl = picture.url
console.log(apiUrl)
}
}
}

pattern to add to array item from child component

I have two components, a parent and child one, the
parent data attribute I've set up it like this...
data () {
return {
users: [],
}
}
the users array is populated by a button click, i share this array with the child component.
The child component is trying to add a user to this list which works(adding value to passed in props), but because the users array is declared under data the parent component refreshes and i lose my users...
is there a pattern to keep the users array values and add to them via a child...
sorry if this is obvious but as i said i'm just starting...
edit : adding code (parent component)
<template>
<div id="app">
<div>
<button v-on:click="display()">Display users</button>
<button v-on:click="displaySingleUserInput =
!displaySingleUserInput">Add user</button>
<div>
</div>
<ul v-if="errors && errors.length">
<li v-for="error of errors">
{{error.message}}
</li>
</ul>
</div>
<add-user v-on:submit_user="addUser" v-show="displaySingleUserInput"></add-user>
<user-list v-bind:users="users"></user-list>
</div>
</template>
<script>
import axios from 'axios';
import UserList from './components/UserList';
import AddUser from './components/AddUser';
export default {
components: {
'user-list': UserList,
'add-user': AddUser
},
name: 'app',
data () {
return {
users: [],
errors: [],
displaySingleUserInput: false
}
},
methods: {
display: function(string)
{
axios.get(`users.json`)
.then(response => {
// JSON responses are automatically parsed.
this.users = response.data.users
})
.catch(e => {
this.errors.push(e)
})
},
addUser: function(id) {
this.users.push({firstname: "john", lastName: 'jones'})
},
}
}
</script>
child component
<template>
<div id="singleUserAdd">
<form id=addUser aria-label="single user add">
<button v-on:click="submit()">Submit</button>
<button>Cancel</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
submit: function() {
this.$emit('submit_user', 1)
}
}
}
</script>
I assume that you have a method called addUser in your child component :
addUser(){
this.$emit("addusr",this.newuser);
}
In the parent one :
<child-comp #addusr="addNewUser" />
...
addNewUser(newuser){
this.users.push(newuser);
}
looks like i was missing
<form v-on:submit.prevent="onSubmit"
to stop my page from refreshing

Update array items asynchronously - watches not firing

I have created a component that displays blog article previews. This component has pagination and upon selecting a new page I refresh the array of article previews. The list of articles is fetched from a JSON api from server1. The response contains information to fetch each article from server 2. Then I fire x asynchronous fetches to server 2, as many as items in the first response. In those responses I update the items in the array.
I am new to vue but after some struggling I got this to work. Now I'm trying to add a spinner in the article previews while the separate articles are loading. My idea was to watch in the previewcomponent for an article update and show the spinner depending on that. Unfortunately it doesn't work and now I'm starting to doubt my implementation. I notice that the watch in the preview is not called for every previewcomponent but still every preview is updated and shown correctly. I assume this is because of the messaging system but I don't manage to fix it.
My question is twofold:
Is my implementation a correct way of handling this problem? To get this to work I nicely I need to 'erase' the array because otherwise new articles were 'overwriting' old ones and this was visible.
How can I handle the spinners. Why are the watches not triggered and how can I fix this? In the code below I have some console writes. I see 10 times 'async' and each time a different amount of 'watch', never 10.
The complete code is on github here: Home and ArticlePreview. These are the most relevant parts:
Home:
<template>
<div class="container article">
<div class="row" v-for="(article, index) in articles" :key="index">
<ArticlePreview v-bind:blogEntry="article"></ArticlePreview>
</div>
<b-pagination-nav :use-router="true" :link-gen="generateLink" align="center" :number-of-pages="nofPages" v-model="pageIndex" />
</div>
</template>
data: function ()
{
return {
articles: <BlogEntry[]> [],
nofPages: 1
}
},
loadContent()
{
fetch("./api/v1/articles.php?page=" + this.pageIndex)
.then(response => response.json())
.then((data) =>
{
this.nofPages = Math.ceil(data.nofItems/10);
this.articles.splice(0);
this.articles.splice(data.data.length);
let index :number;
for(index = 0; index < data.data.length; index++)
{
createArticleAsync(data.data[index].name, data.data[index].permlink).then(function(this: any, index: number, article: BlogEntry)
{
console.log('async');
Vue.set(this.articles, index, article);
}.bind(this, index));
}
})
},
ArticlePreview:
<template>
<div class="container-fluid">
<div class="row" v-if="blogEntry">
<template v-if="blogEntry">
<div class="imageframe col-md-3">
<div class="blog-image">
<img :src="blogEntry.previewImage" style="border-radius: 5px;">
</div>
</div>
<div class="col-md-9">
<h5 class="font-weight-bold" style="margin-top:5px;"><router-link :to="{ name: 'Article', params: {author: blogEntry.author, permlink: blogEntry.permlink } }">{{blogEntry.title}}</router-link></h5>
<div class="multiline-ellipsis">
<p>{{blogEntry.previewBody}}</p>
</div>
<span class="metadata"><i>by <a :href="AuthorBlogLink">{{blogEntry.author}}</a> on {{blogEntry.created | formatDate}}</i></span>
</div>
</template>
<template v-else>
<p>Loading</p>
</template>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueRouter from 'vue-router';
import {formatDate} from "../utils/utils";
export default Vue.extend({
props: [
'blogEntry'
],
data: function ()
{
return {
loading: true
}
},
watch:
{
blogEntry(newValue)
{
console.log('watch');
if(newValue)
this.loading = false;
else
this.loading = true;
}
}
});
</script>
I think the method of getting the detailed data of the article should be encapsulated inside the component, and the loading state is also maintained internally.just like the code below:(It doesn't work properly because Mockjs cannot execute correctly in snippet)
Mock.setup({timeout: 2000})
const URL_ARTICLE_LIST = '/api/v1/articles.php'
const URL_ARTICLE_DETAIL = '/api/v1/article_detail.php'
Mock.mock(/\/api\/v1\/articles\.php.*/,function(options){
return {
nofItems: 33,
data: Mock.mock({
'list|10': [{
'title': '#title',
'url': URL_ARTICLE_DETAIL
}]
}).list
}
})
Mock.mock(URL_ARTICLE_DETAIL,function(options){
return Mock.mock({
content: '#paragraph'
})
})
Vue.component('article-card',{
template: `
<div>
<template v-if="!loading">
<div class="article-title">{{articleTitle}}</div>
<div class="article-content">{{article.content}}</div>
</template>
<template v-else>
<div>loading...</div>
</template>
</div>`,
data () {
return {
loading: false,
article: {}
}
},
props: {
articleTitle: {
required: true,
type: String
},
articleUrl: {
required: true,
type: String
}
},
watch: {
articleUrl (url,oldUrl) {
if(url && url!=oldUrl){
this.loadContent()
}
}
},
methods: {
loadContent () {
this.loading = true;
//or your own async functions
axios.get(this.articleUrl).then(res=>{
this.article = res.data
this.loading = false;
})
}
},
created () {
this.loadContent()
}
});
var app = new Vue({
el: '#app',
data () {
return {
articles: [],
nofPages: 1,
page: 1 //you should get page param from vue-router just like this.$route.query.page
}
},
created () {
//you can also use fetch here
axios.get(URL_ARTICLE_LIST+'?page='+this.page).then(res=>{
console.log(res.data)
this.nofPages = Math.ceil(res.data.nofItems/10);
this.articles = res.data.data
})
}
})
ul,li{
list-style: none;
}
.article_list{
display: flex;
flex-wrap: wrap;
}
.article_list>li{
width: 300px;
background: skyblue;
color: white;
margin: 10px;
}
.article-content{
text-indent: 2em;
}
.pagination-wrapper>li{
display:inline-block;
padding: 5px;
border: 1px solid skyblue;
margin: 3px;
}
.pagination-wrapper>li.active{
background: skyblue;
color: #fff;
}
<script src="https://cdn.bootcss.com/Mock.js/1.0.1-beta3/mock-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<ul class="article_list">
<li v-for="article of articles">
<article-card :article-title="article.title" :article-url="article.url"></article-card>
</li>
</ul>
<ul class="pagination-wrapper">
<li v-for="x in nofPages" :class="{active: page==x}">{{x}}</li>
</ul>
</div>

Categories

Resources