#click within v-for triggers all #click events - javascript

When a single #click is triggered from within the v-for the display property in all of the objects within the array is updated, rather than just by index. I only want that element that is click to receive the event and update the display property, not all of them.
<script>
import TransitionExpand from '#/transitions/transition-expand'
import ResourceCard from '#/components/resource-card'
export default {
components: {
TransitionExpand,
ResourceCard
},
data () {
return {
}
},
methods: {
toggle (evt, i) {
this.status[i].display = !this.status[i].display
}
},
async asyncData ({ app }) {
const { data } = await app.$axios.get('training_modules')
const status = Array(data.trainingModules.length).fill({ display: false })
return {
modules: data.trainingModules,
status
}
}
}
</script>
<template>
<div>
<div class="container px-4 mx-auto mt-16 lg:mt-32">
<div class="flex flex-wrap mb-20">
<h1 class="w-full lg:w-1/2 mb-6">Training Modules</h1>
<p class="w-full lg:w-1/2 leading-7 text-abbey"></p>
</div>
<div
v-for="(item, i) in modules"
:key="item.id"
class="mb-12"
>
<div class="flex items-center">
<h2>{{ item.title }}</h2>
<img #click="toggle($event, i)" class="ml-auto cursor-pointer" src="#/assets/images/icons/plus.svg" alt="open">
</div>
<TransitionExpand v-show="status[i].display">
<div class="">
<p class="mt-6 mb-12">{{ item.description }}</p>
<div class="flex flex-wrap -m-3">
<ResourceCard
v-for="resource in item.resources"
:key="resource.id"
:resource="resource"
/>
</div>
</div>
</TransitionExpand>
</div>
</div>
<BaseFooter />
</div>
</template>

Problem is in const status = Array(data.trainingModules.length).fill({ display: false }) code which is filing all array items with the same object which is observable hence change in display property will be applied to all elements in the array as status[0] === status[1].
Use map instead and it will work as expected:
const status = data.trainingModules.map(() => ({ display: false }));

Related

Having trouble using database data in vue app?

Hi I've been trying to get multiple profiles from a database and use v-for to try and display them all but whenever I try it doesn't work on and I can't figure out how to get it to use the data which should be getting imported.
Below is what the javascript file looks like.
import { projectFirestore } from "../Firebase/Config";
import { ref } from "vue"
const getPremium = () => {
const Premium = ref([])
const error = ref(null)
const load = async () => {
try{
const res = await projectFirestore.collection('Premium').get()
Premium.value = res.docs.map(doc => {
console.log(doc.data())
return {...doc.data(), id: doc.id}
})
}
catch (err){
error.value = err.message
console.log(error.value)
}
}
return { Premium, error, load}
}
export default getPremium
And heres the vue where I am trying to use v-for to create the profiles.
<script>
import getPremium from "../Composables/getPremium.js";
const {Premium, error, load} = getPremium();
load();
</script>
<template>
<div v-for =" Premium in getPremiums" :key="Premium.id" >
<div class= "hover:scale-105 transition ease-in-out duration-300 bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-900 text-neutral-400 font-bold rounded-xl">
<br>
<p>{{ Premium.Name }}</p>
<img src="../assets/Sample-pic.png" class="object-contain ml-6 w-60 h-80 transition ease-in-out duration-300">
<div class="grid grid-cols-2 grid-rows-fit text-left ml-6">
<p>Location:</p>
<p>{{ Premium.Location }}</p>
<p>Rates:</p>
<p>{{ Premium.Rates }} /hr</p>
<p>Phone:</p>
<p>{{ Premium.Phone }}</p>
</div><br>
</div>
</div>
</template>
I'm not sure what I need to do from here to get it to work properly, I'm new to using databases and any help would be greatly appreciated, Thankyou
Besides, you are trying to iterate through function, you iterate it using in operator. In operator iterates object properties, no arrays. And created variables call using first small character not big one like you have in code "Premium".
<script>
import getPremium from "../Composables/getPremium.js";
const { premiumList, error, load } = getPremiumList(); // Name variables, functions with first small letter not big one. Interfaces, Clases with first big letter. Add "List" or "s" in end of name so everyone knows it is array.
load();
</script>
<template>
<main>
<div v-for="item of premiumList" :key="item.id">
<div
class="hover:scale-105 transition ease-in-out duration-300 bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-900 text-neutral-400 font-bold rounded-xl"
>
<br />
<p>{{ item.Name }}</p>
<img
src="../assets/Sample-pic.png"
class="object-contain ml-6 w-60 h-80 transition ease-in-out duration-300"
/>
<div class="grid grid-cols-2 grid-rows-fit text-left ml-6">
<p>Location:</p>
<p>{{ item.Location }}</p>
<p>Rates:</p>
<p>{{ item.Rates }} /hr</p>
<p>Phone:</p>
<p>{{ item.Phone }}</p>
</div>
<br />
</div>
</div>
</main>
</template>
You are destructuring Premium from getPremium(), so that is what you should use in your v-for
<script>
import getPremium from "../Composables/getPremium.js";
const { Premium, error, load } = getPremium();
load();
</script>
<template>
<main>
<div v-for="item in Premium" :key="item.id">
<div
class="hover:scale-105 transition ease-in-out duration-300 bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-900 text-neutral-400 font-bold rounded-xl"
>
<br />
<p>{{ item.Name }}</p>
<img
src="../assets/Sample-pic.png"
class="object-contain ml-6 w-60 h-80 transition ease-in-out duration-300"
/>
<div class="grid grid-cols-2 grid-rows-fit text-left ml-6">
<p>Location:</p>
<p>{{ item.Location }}</p>
<p>Rates:</p>
<p>{{ item.Rates }} /hr</p>
<p>Phone:</p>
<p>{{ item.Phone }}</p>
</div>
<br />
</div>
</div>
</main>
</template>

Blur event not working on custom multi-select Vue component

Blur event is not working properly. It works if I click anywhere in the component except when clicking in the input field. If I click in the input field then outside the component, it won't trigger the blur event which closes the options list. How can I make the blur event on the outer div work after clicking on the input field and then clicking outside the component (* blur event should that be triggered if I click on the components list since it is still within the component, therefore I can't just place a blur event on the input field)
<template>
<div class="flex flex-col relative w-full">
<span v-if="label" class="font-jost-medium mb-2">{{ label }}</span>
<div>
<div #blur="showOptions = false" :tabindex="tabIndex">
<div
class="border border-[#EAEAEA] bg-white rounded-md flex flex-col w-full"
>
<div
v-if="selectedOptions.length"
class="flex flex-wrap px-4 py-2 border-b gap-2"
>
<div
v-for="option in selectedOptions"
class="border bg-secondary rounded-full py-1 px-2 flex items-center"
>
<span>{{ option.text }}</span>
<vue-feather
type="x"
class="h-3 w-3 ml-1.5 cursor-pointer"
#click="onDeleteOption(option)"
/>
</div>
</div>
<div
class="flex flex-row justify-end items-center px-4 cursor-pointer"
:class="selectedOptions.length ? 'py-2' : 'p-4'"
#click="showOptions = !showOptions"
>
<MagnifyingGlassIcon class="h-5 w-5 mr-2" />
<input
class="focus:outline-0 w-full"
type="text"
v-model="searchInput"
/>
<vue-feather type="chevron-down" class="h-5 w-5" />
</div>
</div>
<div v-if="showOptions && optionsMap.length" class="options-list">
<ul role="listbox" class="w-full overflow-auto">
<li
class="hover:bg-primary-light px-4 py-2 rounded-md cursor-pointer"
role="option"
v-for="option in optionsMap"
#mousedown="onOptionClick(option)"
>
{{ option.text }}
</li>
</ul>
</div>
<div
id="not-found"
class="absolute w-full italic text-center text-inactive-grey"
v-else-if="!optionsMap.length"
>
No records found
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch } from "vue";
import { IconNameTypes } from "#/types/enums/IconNameTypes";
import { AppIcon } from "#/components/base/index";
import { MagnifyingGlassIcon } from "#heroicons/vue/24/outline";
export default defineComponent({
name: "AppAutocomplete",
components: {
AppIcon,
MagnifyingGlassIcon,
},
props: {
modelValue: {
type: String,
},
label: {
type: String,
default: "",
},
tabIndex: {
type: Number,
default: 0,
},
options: {
type: Array as PropType<{ text: string; value: string }[]>,
required: true,
},
},
setup(props, { emit }) {
const showOptions = ref(false);
const optionsMap = ref(props.options);
const selectedOptions = ref<{ text: string; value: string }[]>([]);
const searchInput = ref("");
watch(searchInput, () => {
optionsMap.value = props.options.filter((option1) => {
return (
!selectedOptions.value.some((option2) => {
return option1.text === option2.text;
}) &&
option1.text.toLowerCase().includes(searchInput.value.toLowerCase())
);
});
sortOptionsMapList();
});
const onOptionClick = (option: { text: string; value: string }) => {
searchInput.value = "";
selectedOptions.value.push(option);
optionsMap.value = optionsMap.value.filter((option1) => {
return !selectedOptions.value.some((option2) => {
return option1.text === option2.text;
});
});
sortOptionsMapList();
emit("update:modelValue", option.value);
};
const onDeleteOption = (option: { text: string; value: string }) => {
selectedOptions.value = selectedOptions.value.filter((option2) => {
return option2.text !== option.text;
});
optionsMap.value.push(option);
sortOptionsMapList();
};
const sortOptionsMapList = () => {
optionsMap.value.sort(function (a, b) {
return a.text.localeCompare(b.text);
});
};
sortOptionsMapList();
document.addEventListener("click", () => {
console.log(document.activeElement);
});
return {
showOptions,
optionsMap,
searchInput,
selectedOptions,
IconNameTypes,
onOptionClick,
onDeleteOption,
};
},
});
</script>
<style scoped lang="scss">
.options-list,
#not-found {
box-shadow: 0 0 50px 0 rgb(19 19 28 / 12%);
#apply border border-[#EAEAEA] rounded-md p-4 mt-1 absolute bg-white z-10 w-full;
}
ul {
#apply max-h-52 #{!important};
}
</style>
blur is not an event that 'bubbles up' to outer elements, so it never reaches the parent div. What you want is focusout
<div #focusout="showOptions = false" :tabindex="tabIndex">

Vue.js iterate props array and manage additional state for that array

I have the prop that is array, iterate it in template and try to manage hover state with displaying additional element when hover is happened:
<template>
<div class="d-flex justify-center" style="height: 100%">
<div v-for="(item, i) in data" :key="i" class="flex-grow-1 d-flex flex-column" style="height: 100%">
<div class="px-2" style="height: 100%;">
<div class="d-flex flex-column-reverse justify-start align-end" style="height: 100%;">
<div class="bar"
:class="{'bar-active' : item.isActive}"
#mouseover="item.isHover = true"
#mouseleave="item.isHover = false"
:style="`height: ${getBarHeight(item.y)}%`"/>
<div class="container">
<div v-if="item.isHover || item.isActive" class="label fw-600 fs-12 tc-text-primary">{{getIncomeView(item)}}</div>
</div>
</div>
</div>
<div class="container">
<div class="label content-accent" style="white-space: nowrap;">{{item.x}}</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import BarChartModel from "./bar-chart-model";
#Component({
components: {
}
})
export default class OverviewChart extends Vue {
#Prop({default: () => []})
data!: BarChartModel[];
hovers: boolean[] = [];
...
}
</script>
The problem is coming when I try to pass the data as prop. Data ceases to be updated with item.isHover = true and hover feature doesn't work. I tried to keep the hover state in separate data array (out of the prop in hovers: boolean[] = []), but it also doesn't work (state array is changed, but if hasn't react on the v-if="hovers[i]". What I can do to track the hover state and display the additional element during hover and pass the data as prop?

Method "getChampionName" has type "undefined" in the component definition. Did you reference the function correctly?

I'm trying to use this function (src/api/)
function getChampionName(champId) {
axios.get('http://ddragon.leagueoflegends.com/cdn/12.5.1/data/en_US/champion.json')
.then(({ data }) => {
let list = data
let championList = list.data
for (var i in championList) {
if (championList[i].key == champId) {
return championList[i].id
}
}
})
.catch((err) => console.log(err))
}
export {getChampionName}
In this component (src/components/)
<template>
<div class="w-72">
<header class="rounded-tl-lg rounded-tr-lg bg-slate-400 p-0.5">
<p>Champion's Mastery</p>
</header>
<ul class="grid gap-1 rounded-bl-lg rounded-br-lg bg-slate-50 p-0.5">
<li v-for="champ in masteryData.slice(0,10)" :key="champ.championId">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<img src="https://ddragon.leagueoflegends.com/cdn/12.5.1/img/champion/Karma.png" alt="" class="rounded-lg h-14 w-14">
<div>
<p class="font-medium text-center">{{ getChampionName(champ.championId) }}</p>
<p>Level {{ champ.championLevel }}</p>
</div>
</div>
<p class="text-2xl font-medium">{{ champ.championPoints }} Pts</p>
</div>
</li>
</ul>
</div>
</template>
<script>
import getChampionName from '#/api/search'
export default{
name: 'MasteryInfo',
props: [
'masteryData'
],
methods: {
getChampionName
}
}
</script>
But I'm getting this error Method "getChampionName" has type "undefined" in the component definition. and don't know what does it mean.
It seems you didn't import the method properly.
Change the import into:
import { getChampionName } from '#/api/search';
You can read more about import export in javascript here:
https://javascript.info/import-export
If you think you almost have the same question as this question, you can also refer to this:
Method "showDate" has type "undefined" in the component definition

Scroll down when clicked with Vue.js

What I want to do is that when the user clicks on an article, it scrolls down to a sibling section. Code looks something like this.
<template>
<article #click="handleCardClick" class="text-center mr-8 mb-12 cursor-pointer hover:opacity-50 w-1/5">
<picture class="flex justify-center items-center mb-4 w-full" style="height: 320px">
<img :src="source" :alt="imgAlt" class="shadow-md" style="" />
</picture>
<h4 class="font-bold mb-1">{{ title }}</h4>
<h6 class="text-sm text-gray-600">{{ tags.length > 0 ? tags[0].name : '' }}</h6>
</article>
</template>
<script>
import { mapActions, mapState } from 'vuex';
export default {
props: {
title: {
type: String,
required: true,
},
},
computed: {
...mapState({
previewIndex: state => state.templates.hasTemplate
}),
},
methods: {
...mapActions({
setActiveTemplate: 'templates/setActive',
setPreview: 'templates/setPreview',
removePreview: 'templates/removePreview',
}),
handleCardClick () {
this.setActiveTemplate(this.template);
this.selectTemplate(this.pos);
},
}
}
</script>
And the other file looks like this
<template>
<section v-if="template" class="flex justify-between w-full pt-10 pl-10 pr-5 pb-12 relative border-b border-t border-black my-4" style="height: 75vh">
<article class="flex flex-col justify-between" style="width: 25%">
<button #click="changeSection('invite')" class="h-1/3 pb-4">
<picture class="h-full w-full flex justify-center items-center bg-gray-100">
<img :src="template.url || ''" class="bg-gray-200 shadow-lg" style="min-height: 20px; min-width: 20px; height:80%" alt="Preview de la invitacion">
</picture>
</button>
</article>
</section>
</template>
I'm a bit new to Vue, so maybe it's really simple and I just can't find how to do it :) Thanks in advance!
You only need to assign a reference ref to each article and then build a method to go to any of your referenced articles:
<article #click="goto('art1')">Go to article 1</article>
For earch sibiling declare it's reference so you can call them on the goto method
<article ref="art1">
Article 1
</article>
Declare the goto method, it has a parameter, the reference of where you want to go.
methods: {
goto(refName) {
var element = this.$refs[refName];
var top = element.offsetTop;
window.scrollTo(0, top);
}
},
And this is it.
If you have the click action inside a child component then you'll have to use $emit to perform the click action on the parent, here is an example following the above:
Parent
<template>
<Article #scrollto="goto"></Article>
<Section ref="art1">
...
</Section>
</template>
<script>
import Article from "./article";
import Section from "./section";
export default {
methods: {
goto(refName) {
var element = this.$refs[refName];
var top = element.offsetTop;
window.scrollTo(0, top);
}
}
}
</script>
Article.vue
<template>
<div>
<button #click="$emit("scrollto", 'art1')">
Go to the Article!
</button>
</div>
</template>
Documentation about vue ref function
Documentation about window.scrollTo function

Categories

Resources