I'm creating a dynamic menu using vue3 and antdv
MenuItem.vue
<template>
<slot name="menu-item" :items="[data]" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: ['data']
})
</script>
MenuGroup.vue
<template>
<a-sub-menu>
<template #title>
<component v-if="data.icon" :is="data.icon"></component>
<span>{{ data.label }}</span>
</template>
<slot name="menu-item" :items="data.items" />
</a-sub-menu>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: ['data']
})
</script>
Layout.vue
<a-menu theme="dark" mode="inline" v-model:selectedKeys="selectedKeys">
<component v-for="block in menu" :key="block.key" :is="block.component" :data="block">
<template v-slot:menu-item="{ items }">
<a-menu-item v-for="item in items" :key="item.key" #click="handleMenuClick">
<component v-if="item.icon" :is="item.icon"></component>
<span>{{ item.label }}</span>
</a-menu-item>
</template>
</component>
</a-menu>
edit:
putting a normal menu item inside the menu will trigger the click event
<a-menu-item #click="handleMenuClick">
<span>title</span>
</a-menu-item>
my items array:
[
{
key: 'keyA',
label: 'labelA',
icon: HomeOutlined,
component: MenuItem,
route: '/someroute'
},
{
key: 'keyB',
label: 'labelB',
icon: HomeOutlined,
component: MenuGroup,
items: [
{
key: 'keyc',
label: 'labelc',
icon: ToolOutlined,
route: '/route-sample'
}
]
}
The code looks like is working, but when I click on the submenu keyc the function handleMenuClick is called, by clicking on keya nothing happen....what I'm doing wrong?
sandbox
Looks like ant design implements own click handler that will override whatever you have setup when it is available.
You're likely better off using the onClick in the <a-menu> or onTitleClick for <a-sub-menu> items
Related
I'm trying to import SVG icons for each item in a v-for loop, with the filename changing depending on the item's id. The icons are loading, but I get the following error for each icon imported.
Is there a better way to approach this?
Uncaught (in promise) TypeError: Failed to resolve module specifier '~/assets/img/flags/ar.svg'
<template>
<NavigationItem v-for="item in topCountries">
<template #icon>
<component :is="getIcon(item.id)" />
</template>
<NavigationItem />
</template>
<script setup>
const getIcon = (id) => defineAsyncComponent(() =>
import(`~/assets/img/flags/${id}.svg`));
</script>
You can have a look at https://nuxt.com/modules/nuxt-svgo module.
This module allows to import SVG.
npm i --save nuxt-svgo
Add it as a module dependency in your nuxt.config file
// nuxt.config.ts
import { defineNuxtConfig } from 'nuxt'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
modules: ['nuxt-svgo']
})
Import SVG icons as follow:
<script setup lang="ts">
const getIcon = (id: string) => defineAsyncComponent(() => import(`#/assets/svg/${id}.svg`));
</script>
<template>
<div v-for="item in ['icon1', 'icon2']">
<component :is="getIcon(item)" />
</div>
</template>
Note that if you use Typescript, you will have to create a custom.d.ts file to fix import error
// custom.d.ts
declare module '*.svg' {
import type { DefineComponent } from 'vue'
const component: DefineComponent
export default component
}
calls each icon from the data. uses font awesome icons. you can also add svgs between the i tags
<template>
<ul>
<!-- list rendering -->
<li v-for="item in items">
<span class="icon">
<i :class="[faClass(item.icon)]"
aria-hidden="true"></i>
</span>
</li>
</ul>
</template>
<script>
export default {
name: "navbarMobile",
data() {
return {
//listItems
items: [
{
icon: 'home',
},
{
icon: 'wrench',
},
{
icon: 'project-diagram',
},
{
icon: 'cogs',
},
{
icon: 'phone',
}
]
}
},
methods: {
faClass(icon) {
return `fa fa-${icon}`;
}
}
}
</script>
Us the component name instead of the component path. Also, don't forget to import SVG components and add ?inline at the end of the name.
<template>
<NavigationItem v-for="item in topCountries">
<template #icon>
<component :is="item.icon" />
</template>
<NavigationItem />
</template>
<script setup>
import Eye from '~/assets/img/flags/Eye.svg?inline';
import Balls from '~/assets/img/flags/Balls.svg?inline';
const topCountries = [
{ icon: 'Eye' },
{ icon: 'Balls' }
]
</script>
I'm struggling on Vue.JS with a component that has as children/slots some "complex" components with canvas data (e.g. Maps).
I want to avoid that when the parent component re-renders, their inner slots re-render. Because of how this components work (or any other scenario), every time it re-renders it needs to do all of it's "loading" steps. Even when saving their real-time state.
For example:
Component.vue
<template>
<div>
<span>{{value}}</span>
<slot></slot>
</div>
</template>
View
<Component v-model="value">
<Map :latitude="0" :longitude="0"/>
</Component>
<script>
this.value = "Hello";
setTimeout(()=>{
this.value="Hello world!";
},1000);
</script>
Is there any way to prevent from slots from re-rendering when their parent re-renders?
Thanks in advance.
Hello use props for a child component, and this child is not will rerender
App
<template>
<div id="app">
<HelloWorld msg="Hello Vue in CodeSandbox!" :somedata="key">
slot information key: {{ key }}
</HelloWorld>
<button #click="key++">key++</button>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld,
},
data() {
return {
key: 0,
};
},
};
</script>
Child
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h3>somedata from props: {{ somedata }}</h3>
<hr />
<slot></slot>
<hr />
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: {
type: String,
default: "",
},
somedata: {
type: Number,
default: 999,
},
},
created() {
console.log("renred");
},
};
</script>
How you can see I used console.log("renred"); in the child component, you can check the console, the information will be shown only one time.
https://codesandbox.io/s/naughty-platform-ib68g?file=/src/components/HelloWorld.vue
I am a beginner with vue.js (3)
I try to build a system with 2 layouts :
1 for a connected user
1 for a not connected user
In my router/index.js, I add a meta for each route :
const routes = [
{
path: '/',
name: 'Home',
meta: { layout: 'layout-connected' },
component: Home
},
{
path: '/myspace',
name: 'MySpace',
meta: { auth: true },
component: MySpace
}
]
In my App.vue, I decide of which layout to use like that (see the ":is="layout"):
<template>
<div id="app">
<component :is="layout">
<router-view/>
</component>
</div>
</template>
<script>
const defaultLayout = 'layout-not-connected'
export default {
computed: {
layout () {
console.log(this.$route.meta.layout)
return (this.$route.meta.layout || defaultLayout)
}
},
And at least , in my layout I have :
<template>
<div class="container-fluid">
<div class="row essai">
<h1>layout non connected</h1>
<slot />
</div>
</div>
</template>
When I console.log which route to apply, it works fine : I have the correct layout in the console.
BUT I never see the layout (for example the tag). Only the component.
Do I have understood fine the concept ? What can be my errors ?
Thanks
The layouts are components which should be registered globally in main.js using :
app.component('layout-name',theLayoutComponent)
or locally in components option :
<template>
<div id="app">
<component :is="layout">
<router-view/>
</component>
</div>
</template>
<script>
const defaultLayout = 'layout-not-connected'
import LayoutConnected from 'path/to/LayoutConnectedComponent'
import DefaultLayout from 'path/to/DefaultLayout Component'
export default {
components:{
DefaultLayout,LayoutConnected
},
computed: {
layout () {
console.log(this.$route.meta.layout)
return (this.$route.meta.layout || defaultLayout)
}
},
I am getting the following - Cannot read property 'free' of undefined.
I will be adding this button component on multiple pages and I have data object which will allow me to add text based on whatever page I want displayed on a page. For example if its on the homepage I would like to use <buttons :text="buttonText.free" /> and on about us page I would like to use <buttons :text="buttonText.spend" />
Template file
<template>
<main class="container">
<buttons :text="buttonText.free" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
components: {
Buttons
}
}
</script>
Component file
<template>
<div>
<button class="button"">
<span>{{ buttonText }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
},
data () {
return {
buttonText: {
free: 'free',
spend: 'spend',
now: 'now',
nowFree: 'now free'
}
}
}
}
</script>
Could you tell me what I am doing wrong?
You should define your data in your parent component's data property. All the variables that is used inside the template tag will be fetched from data, computed or props of the component. You are passing an undefined buttonText data to your buttons component.
<template>
<main class="container">
<buttons :text="buttonText.free" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
data() {
return {
buttonText: {
free: 'free',
spend: 'spend',
now: 'now',
nowFree: 'now free'
}
}
},
components: {
Buttons
}
}
</script>
and in your buttons component, just accept the props passed by the parent component. In this case, you are using text as the props of the buttons component.
<template>
<div>
<button class="button"">
<span>{{ text }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
}
}
</script>
template.vue
<template>
<main class="container">
<buttons :text="your customized text" />
</main>
</template>
<script>
import Buttons from '~/components/atoms/buttons.vue'
export default {
components: {
Buttons
}
}
</script>
buttons.vue
<template>
<div>
<button class="button">
<span>{{ text }}</span>
</button>
</div>
</template>
<script>
export default {
props: {
text: String
}
}
</script>
here is a simple solution to solve your problem
but you need to learn more fundamentals on vue components
vue component doc
This code
index.js
{
path: '/test',
name: 'test',
component: Test,
children: [{
path: 'p1',
name: 'p1',
component: P1
}]
}
in Component Test
test.vue
<template>
<div>
<h1>Test</h1>
<button v-on:click="navigateTo('/test/p1')">p1</button>
</div>
</template>
<script>
export default {
methods: {
navigateTo(route) {
this.$router.push(route)
}
}
}
</script>
in Component P1
<template>
<div>
<h1>P1</h1>
</div>
</template>
When i click button p1 i has path in browser but not load component p1. why??
I'm new in vue.js
I think you missed the <router-view></router-view> in your parent template (in test.vue). Router view is where the child component gets rendered. i.e.
<template>
<div>
<h1>Test</h1>
<button v-on:click="navigateTo('/test/p1')">p1</button>
<router-view></router-view>
</div>
</template>