I cannot wrap my head around this. I've looked at countless other issues, answers and I don't even understand the issue or what's causing it. I have a projects.js file with an array in it. When I import {projects} from "#/projects.js" I get the error in the title. When I add setup to <script> it works, but then I cannot export a variable that's needed for another component. What do I do? I've spent hours reading the docs & issues/answers. If someone could explain this problem to me like I'm 5 then that'd be very helpful also.
projects.js
const getImg = (name) => {
const path = `/src/assets/images/${name}`; //your imgfile
const modules = import.meta.globEager("/src/assets/images/*");
return modules[path].default;
}
export const projects = [
{
id: 5,
date: "Aprill 6, 2022",
title: "Muusika poe veebileht",
image: getImg("music_instrument_store.jpg"),
},
{
id: 4,
date: "Märts 25, 2022",
title: "Andres Kõljalg E-Portfoolio",
image: getImg("andres_koljalg_portfolio.png"),
},
{
id: 3,
date: "Märts 4, 2022",
title: "Andmemudelite & projekti planeerimine",
image: getImg("datamodels_project_planning.webp"),
},
{
id: 2,
date: "Detsember 15, 2021",
title: "Virtuaalne Instrument",
image: getImg("arduino_chello.jpg"),
},
{
id: 1,
date: "5 November, 2021",
title: "Ryan Reynolds E-Portfoolio",
image: getImg("ryan_reynolds.jpg"),
}
]
ProjectsList.vue
<template>
<div class="bg-black p-12 flex flex-col gap-20">
<router-link to="/ProjectView.vue" #click="pId = project.id" class="z-10 hover:scale-[1.02] ease-in-out duration-300 hover-trigger" v-for="(project, index) in projects" :key="index"><div class="h-44 overflow-hidden flex items-center justify-center">
<div class="absolute z-[1] p-3 px-64 flex flex-col items-center justify-center gap-2 project-card-txt-shdw h-44">
<p class="text-5xl">{{project.title}}</p>
<p class="text-lg">{{project.date}}</p>
</div>
<img :src="project.image" class="w-full ease-in-out duration-300 hover-target">
</div></router-link>
</div>
</template>
<style>
...blabla
</style>
<script>
import {projects} from "#/projects.js"
export default {
Setup(){
let pId = 0;
}
}
</script>
The projects variable should be returned from the setup hook in order to be accessed by template and the setup hook name should be in lowercase format :
<script>
import {projects} from "#/projects.js"
export default {
setup(){
let pId = 0;
return{ projects }
}
}
</script>
Related
I am trying to create an interface for a support ticket system in my Vue 3 / Quasar / TypeScript app.
It consists of tickets and message threads.
When viewing a single ticket I want to display each thread of the ticket as a q-expansion-item. The expansion item shows a preview of the thread, and when clicked the preview disappears and the full message thread is shown.
I have a working component for this. The problem is that all thread previews disappear when opening a q-expansion-item. But I only want the thread preview of the q-expansion-item that was clicked to disappear.
Any idea how I can fix this?
This is what it looks like before opening:
This is what it looks like after opening. Note that ALL thread previews have gone, but I only want the preview for the top thread that was clicked to disappear:
I tried to create a minimal reproduction in codepen.io and codesandbox.io but I couldn't get it working with Vue 3 / Quasar / TypeScript.
So instead I have pasted the code below.
<template>
<q-card flat class="full-width" style="max-width: 1200px">
<q-card-section>
<q-list bordered class="rounded-borders">
<div v-for="(thread, index) in threads" :key="thread.threadId">
<q-expansion-item
clickable
:content-inset-level="1"
#update:model-value="handleOpenClose"
>
<template #header>
<q-item-section top>
<q-item-label lines="1">
<span class="text-weight-medium q-mr-sm">
{{ thread.author.name }}
</span>
<span class="text-grey-8 text-sm">
{{
date.formatDate(
new Date(thread.createdAt.seconds * 1000),
'Do MMMM hh:mmA'
)
}}
</span>
</q-item-label>
<q-item-label v-if="!isOpen" caption lines="1">
{{ stripHTML(thread.content) }}
</q-item-label>
</q-item-section>
</template>
<template #default>
<q-card>
<!-- eslint-disable-next-line vue/no-v-html -->
<q-card-section v-html="thread.content" />
</q-card>
</template>
</q-expansion-item>
<q-separator v-if="threads && index != threads?.length - 1" />
</div>
</q-list>
</q-card-section>
</q-card>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { date } from 'quasar';
// Fake Data
const threads = ref([
{
threadId: '6bXb0tfCZWoNVfFEztIS',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: '__vue_devtool_undefined__',
type: 'END_USER',
},
createdAt: { seconds: 1656573148, nanoseconds: 38000000 },
content: 'The first thread',
},
{
threadId: 'MTM4qvzcRKQ2eZ2crpQB',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: '__vue_devtool_undefined__',
type: 'END_USER',
},
createdAt: { seconds: 1656573666, nanoseconds: 250000000 },
content: 'The second thread',
},
{
threadId: 'Q9xf9PFmTzs6X4LYSXXh',
ticketId: '9rgz013ahc2Aqx9C2UxG',
author: {
name: 'Ben Bob',
firstName: 'Ben',
lastName: 'Bob',
photoURL: null,
type: 'END_USER',
},
createdAt: { seconds: 1656573990, nanoseconds: 262000000 },
content: 'The third thread',
},
]);
// Normal vue script code
const isOpen = ref(false);
const handleOpenClose = (isShowing: boolean) => {
isOpen.value = isShowing;
};
const stripHTML = (html: string) => {
let doc = new DOMParser().parseFromString(html, 'text/html');
doc.body
.querySelectorAll('br, li, div') // Get all <br>, <li>, and <div> elements
.forEach((br) => br.after(doc.createTextNode(' '))); // And add spaces after them
return doc.body.textContent || '';
};
</script>
I'm not too familiar with Quasar, but it looks like your issue is that you're using <q-item-label v-if="!isOpen" caption lines="1">, which will always resolve true when something is open. You want to check that isOpen.value exists and equals the value of the model (which I think would be the threadId).
EDIT - added suggested answer (just a guess!):
Swap
#update:model-value="handleOpenClose"
with
#update:model-value="handleOpenClose(thread.threadId)"
and then swap
const handleOpenClose = (isShowing: boolean) => {
isOpen.value = isShowing;
};
with
const handleOpenClose = (threadId) => {
isOpen = threadId;
};
then swap
<q-item-label v-if="!isOpen" caption lines="1">
with
<q-item-label v-if="isOpen !== thread.threadId" caption lines="1">
I'm confused as to what I've missed in my code, when I try and use it, DevTools console says "Property or method "item" is not defined on the instance but referenced during render". Please could someone tell me what I've forgotten here, thanks!
<template>
<div>
<b-navbar toggleable="lg" type="dark" class='lx-navbar'>
<b-navbar-brand href="#">
<img src='../assets/images/navbar.png' alt="Logo" height='35px;'>
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav class="ml-auto">
<b-nav-item :v-for="item in items" :key="item.id" :to='item.to'>{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</div>
</template>
<script>
export default {
name: 'Header',
data() {
return {
items: [
{ id: 1, name: 'Home', to: '/' },
{ id: 2, name: 'GitHub', to: '/GitHub' },
{ id: 3, name: 'Socials', to: '/Socials' },
{ id: 4, name: 'Nova', to: '/Nova' }
]
}
}
}
</script>
<style>
.lx-navbar {
background: rgb(35, 45, 61);
}
</style>
You shouldn't binding v-for
:v-for is wrong
My slider works just great, but when pictures are changing from one to another its jumping. It's like one pic appears under another. And then when first pic disappears, the second block jumps back to needed position. I think it would be solved with mode="in-out", but group transition doesn't have this option. It's only for single transition. Or should I try redo it with regular transition?
<template>
<div class="inner__slider">
<transition-group name="fade" tag="p">
<the-slide v-for="(item, key) in imgs"
:key="key"
:item_name="item.name"
v-show="item.id === curImg"
/>
</transition-group>
<button #click="ft_cnt(1)">Foward</button>
<button #click="ft_cnt(-1)">Downward</button>
</div>
</template>
<script>
import TheSlide from '#/components/TheSlide.vue'
export default {
name: "TheSlider",
components: {
TheSlide
},
data() {
return {
curImg: 0,
imgs:[
{
id: 0,
name: 'DiamPhoto.png'
},
{
id: 1,
name: 'Dima.png'
},
{
id: 2,
name: 'TrumpBuilding.png'
}]
}
},
methods: {
ft_cnt(i) {
this.curImg += 1;
if (this.curImg < 0)
this.curImg = Object.keys(this.imgs).length - 1;
if (this.curImg > Object.keys(this.imgs).length - 1)
this.curImg = 0;
}
}
}
</script>
And here is TheSlider component:
<template>
<div class="img">
<img :src="require('#/assets/img/' + item_name)" alt="">
</div>
</template>
<script>
export default {
name: "TheSlide",
props: {
item_name: String
}
}
</script>
I am currently doing a website builder, where user can drag and drop to add element.
The drag and drop works well, but what i want is, how can i disable/hide the drop placeholder in the target container ?
As show in the image, whenever I hover on a container, it will show a copy of my dragging element by default, which I don't want.
Here is my code :
<template>
<div style="display : flex;">
<div id="dragArea">
<draggable
class="dragArea list-group"
:list="list1"
:group="{ name: 'item', pull: 'clone', put: false }"
:clone="cloneItem"
#change="log"
>
<div class="list-group-item" v-for="element in list1" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
<div id="dropArea">
<draggable class="dragArea list-group" :list="list2" group="item" #change="log">
<div class="list-group-item" v-for="element in list2" :key="element.id">{{ element.name }}</div>
</draggable>
</div>
</div>
</template>
Script :
<script>
import draggable from "vuedraggable";
let idGlobal = 8;
export default {
name: "custom-clone",
display: "Custom Clone",
order: 3,
components: {
draggable,
},
data() {
return {
hover : false,
list1: [
{ name: "cloned 1", id: 1 },
{ name: "cloned 2", id: 2 },
],
list2: [
]
};
},
methods: {
log: function(evt) {
window.console.log(evt);
},
cloneItem({ name, id }) {
return {
id: idGlobal++,
name: name
};
},
},
};
</script>
On each of your <draggable> components within your <template>, you can set the ghost-class prop to a CSS class that hides the drop placeholder (ie. "ghost", or "dragging element" as you called it) using display: none; or visibility: hidden;.
For example:
In your <template>:
<draggable ghost-class="hidden-ghost">
and in the <style> section of your Vue Single File Component, or in the corresponding stylesheet:
.hidden-ghost {
display: none;
}
Working Fiddle
The ghost-class prop internally sets the SortableJS ghostClass option (see all the options here). The ability to modify these SortableJS options as Vue.Draggable props is available as of Vue.Draggable v2.19.1.
I have three component icons <DiscoverIcon>, <FeedIcon>, <ProfileIcon> and in tab loop I want to be able to use a different Icon for each respective title.
I tried a list element like
{ key: 1, icon: <div class='iconbgd'><DiscoverIcon /></div>, text: 'Discover', route: '/discover'}
and calling {{ link.icon }} and also
{ key: 1, text: 'Discover', route: '/discover'}
and calling <div class='iconbgd'><{{link.text}}Icon /></div>
<template>
<v-tabs fixed-tabs>
<v-tab
v-for="link in links"
:key="link.key"
>
<div class='iconbgd'><{{link.text}}Icon /></div><h4>{{ link.text }}</h4>
</v-tab>
</v-tabs>
</template>
<script>
import DiscoverIcon from '../components/icons/DiscoverIcon'
import FeedIcon from '../components/icons/FeedIcon'
import ProfileIcon from '../components/icons/ProfileIcon'
export default {
components: {
DiscoverIcon,
FeedIcon,
ProfileIcon
},
name: 'App',
data () {
return {
links: [
{ key: 1, icon: <div class='iconbgd'><DiscoverIcon /></div>, text: 'Discover', route: '/discover'},
{ key: 2, icon: <div class='iconbgd'><FeedIcon /></div>, text: 'Feed', route: '/feed'},
{ key: 3, icon: <div class='iconbgd'><ProfileIcon /></div>, text: 'Profile', route: '/profile'}
]
}
}
}
</script>
<style>
.iconbgd svg{
fill:url(#grad1);
width: 30px;
height: auto;
padding-right: 5px;
}
</style>
This is the Vuetify tabs component for this use case but getting it working isn't connected with using tabs but my expected result is to be able to loop through and in each tab use a different correlated component rather than just create three separate buttons which I currently have.
First of all {{link.icon}} is not is meant to be displayed as HTML. You should use a different approach.
<div class='iconbgd'><{{link.text}}Icon /> is equal to <div class='iconbgd' v-text="link.text"><Icon />. Therefore Vue has a v-html directive for HTML, you can read here more about the varieties of directives.
Still try to avoid v-html, when possible and since the different {{link.icon}} are very similar you could make it easily work without v-html.
This looks like you are trying to bind components <{{link.text}}Icon />. Dynamic components are what you looking for and they are very powerful.
I quickly looked into the Vuetify documentation for v-tabs and changed it a bit, however I have never used it before and this is not tested. It should be what you are trying to accomplish:
<template>
<v-tabs fixed-tabs>
<v-tab v-for="link in links" :key="link.key">
<div class="iconbgd">{{link.label}}</div>
</v-tab>
<v-tab-item v-for="link in links" :key="link.key">
<h4>{{link.label}}</h4>
<component :is="link.label + 'Icon'" :key="link.key"/>
</v-tab-item>
</v-tabs>
</template>
<script>
import DiscoverIcon from '../components/icons/DiscoverIcon';
import FeedIcon from '../components/icons/FeedIcon';
import ProfileIcon from '../components/icons/ProfileIcon';
export default {
data() {
return {
links: [
{
key: 1,
label: 'Discover',
route: '/discover'
},
{
key: 2,
label: 'Feed',
route: '/feed'
},
{
key: 3,
label: 'Profile',
route: '/profile'
}
]
};
},
name: 'App',
components: {
DiscoverIcon,
FeedIcon,
ProfileIcon
}
};
</script>
<style>
.iconbgd svg {
fill: url(#grad1);
width: 30px;
height: auto;
padding-right: 5px;
}
</style>
Thank you so much! A few tweaks to that and it's working.
<v-tabs fixed-tabs color='transparent' slider-color='#1341B2'>
<v-tab v-for="link in links" :key="link.key" :to="link.route">
<div class="iconbgd">
<component :is="link.label + 'Icon'" :key="link.key"/>
</div>
<h4>{{link.label}}</h4>
</v-tab>
</v-tabs>