Dynamic import of SVG icon components - vue.js

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>

Related

CKEditorError: ckeditor-duplicated-modules while adding plugins to ckeditor

I'm trying to add text-alignment plugin to my ckeditor build in vue js.
Following is my code for the same::
<template>
<div>
<nav-bar />
<div id="app">
<ckeditor :editor="editor" v-model="editorData" :config="editorConfig" class="full-width"></ckeditor>
</div>
<router-view class="fixed-center"></router-view>
</div>
</template>
<script>
import NavBar from "components/NavBar.vue";
import ClassicEditor from "#ckeditor/ckeditor5-build-classic"
import Alignment from "#ckeditor/ckeditor5-alignment"
export default {
components: { NavBar },
name: "app",
data() {
return {
editor:ClassicEditor,
editorData:'<p>Content of the editor.</p>',
editorConfig:{
plugins:[Alignment],
toolbar:['Heading','bold', 'italic','alignment']
}
};
},
methods: {},
};
</script>
I'm getting following error ::
CKEditorError: ckeditor-duplicated-modules

Can't get the vue router to work properly

I have struggling to get the vue router to work properly, despite the fact that I have tried multiples different ways and read through the documentation, there seems to be something that I don't understand
I know app component is a bit messy, with router view covering router link and there is the component. its because I have tried it all, included the router-view first then it showed everything from my firstUser component, then tried the link, it worked, but it did not go to that page seperately,
I need to make it so that when clicked it should show the firstUser page only, Please help, much appreciated
//This is my app component
<template>
<div id="app">
<h1>Welcome</h1>
<router-view>
<router-link to="firstUser"> <first-user/> Register </router-link>
</router-view>
<div>
<h2>Returning user</h2>
<returning-user />
</div>
<div>
<h2>Registered User</h2>
<registered-user />
</div>
</div>
</template>
<script>
import firstUser from "#/components/firstUser.vue";
import returningUser from "#/components/returningUser.vue";
import registeredUser from "#/components/registeredUser.vue";
export default {
components: { returningUser, registeredUser, firstUser },
data() {
return {};
},
};
</script>
<style>
#app {
padding: 20px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
input:focus {
outline: none;
}
</style>
//this is my fisrtUser component
<template>
<div>
<form class="form" #submit.prevent="registerUser">
<h4 class="">Register</h4>
<my-input
v-model="this.firstName"
type="text"
class="input"
placeholder="First Name"
/>
<my-input
v-model="this.lastName"
type="text"
class="input"
placeholder="Last Name"
/>
<myButton #click="registerUser"> Create </myButton>
</form>
</div>
</template>
<script>
import myInput from "./UI/myInput.vue";
import myButton from "./UI/myButton.vue";
export default {
name: "firstUser",
components: { myInput, myButton },
data() {
return {
user: [
{
firstName: "",
lastName: "",
},
],
users: [],
};
},
methods: {
registerUser() {
const newUser = {
firstName: this.firstName,
lastName: this.lastName,
};
this.users.push(newUser);
console.log(newUser);
},
},
};
</script>
<style>
</style>
///then this is the index.js from router
import { createRouter, createWebHistory } from 'vue-router';
import firstUser from '#/views/firstUser';
const routes = [{
path: '/firstUser',
name: 'firstUser',
component: firstUser
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
and
It is not entirely clear what the firstUsers are. I will assume what you did is intentional and there are two firstUsers (one view and one component).
Specify the to prop of the router-link component as you would the href attribute of a link/anchor <a> element. That is, the route needs to begin with a slash.
<router-link to="/firstUser">
Alternatively, you might want to reduce the coupling by declaring the link by route name instead. This way, you can change the route to whatever you want without having to update your link. This can be done by using v-bind to tell Vue to parse the prop contents, and by using different syntax.
<router-link :to="{ name: 'firstUser' }">
See the Vue Router documentation.

click event not firing in antd menu item

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

Vue.js 3 - Trying to build a system with 2 layouts

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)
}
},

Vue-cli change object value globally

I have this code in file app.vue :
<template>
<div id="app">
<button v-on:click="component = 'login'">aa</button>
<component v-bind:is="component"></component>
</div>
</template>
<script>
import acceuil from './components/acceuil.vue'
import login from './components/login.vue'
export default {
name: 'app',
components: {
acceuil,
login
},
data(){
return {
component: 'acceuil'
}
}
}
</script>
How can I toggle between acceuil/login in component from a different vue file ?
You need to pass the imported dependency (the object or the name of the component as a string) to v-bind:is. You can do this by returning it in a computed function and pass it to a computed property, which you then can use in the template.
<template>
<div id="app">
<button v-on:click="isLogin = true">Show Login</button>
<component v-bind:is="currentComponent"></component>
</div>
</template>
<script>
import acceuil from './components/acceuil.vue';
import login from './components/login.vue';
export default {
name: 'app',
data () {
return {
isLogin: false
};
},
computed: {
currentComponent () {
return this.isLogin ? login : acceuil;
}
},
};
</script>
See also the documentation of dynamic components in the official docs.