Vue3 dynamic import from js - vuejs3

hello i just joined you,
I want to use heroicons dynamically with vue 3 js,
What I want to do is on the Home page you see below; atrribute I want that icon to be called according to the icon name I wrote.
For example in Home.vue
There are 2 examples available.
Please I would be happy if you take into account that I am new here and have little experience with vue js while writing an answer. Thank you from now.
This is what I want to do, it doesn't have to be in a .vue template, it can be given as an example as .js.
heroicons
<my-icon svgIcon="heroicons_outline:menu"></my-icon>
<my-icon svgIcon="heroicons_solid:menu"></my-icon>
<template>
<my-icon svgIcon="heroicons_outline:menu"></my-icon>
<my-icon svgIcon="heroicons_solid:menu"></my-icon>
</template>
<script>
import MyIcon from "./MyIcon";
export default {
name: "Home",
components: {MyIcon},
};
</script>
MyIcon.vue
<template>
<label class="block font-bold text-md text-gray-700">
<svgIcon></svgIcon>
</label>
</template>
<script>
import {svgIcon} from '#heroicons/vue/outline'
import {defineComponent} from 'vue';
export default defineComponent({
props: ['svgIcon']
})
</script>

Related

Composition API + script setup Dynamic component

I want to implement simple tabs using script setup and composition API
<script setup>
import Timeline from './Timeline.vue';
import Profile from './Profile.vue';
import Groups from './Groups.vue';
const currentTab = ref('Timeline')
const tabs = ref(['Timeline', 'Profile', 'Groups'])
</script>
<template>
<div class="tabs">
<div v-for="tab in tabs"
:key="tab"
#click="currentTab = tab" v-text="tab"
<component :is="currentTab"></component>
</div>
</template>
But this code will only result <timeline></timeline> instead of the actual content of the Timeline component.
As mentioned here when using the <script setup>-method you need to pass the actual object of the component to the :is instead of the string.
See this Example here
Here is also the code itself:
<script setup>
import Timeline from './Timeline.vue';
import Profile from './Profile.vue';
import Groups from './Groups.vue';
import { shallowRef } from 'vue';
const currentTab = shallowRef(Timeline);
const tabs = [Timeline, Profile, Groups];
</script>
<template>
<div class="tabs">
<div v-for="tab in tabs"
:key="tab"
#click="currentTab = tab" v-text="tab.__name">
</div>
</div>
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
</template>

When I use unplugin-icons, how do I render icon components dynamically in Vue

When I use element-plus to create an icon component, I use unplugin-icons and unplugin-auto-import to automatically import icons, for example I use <el-icon><i-ep-monitor />< /el-icon> to import an icon component. Now I have a requirement, I have an array which stores some strings like ['i-ep-avatar', 'i-ep-cellphone', 'i-ep-apple'], now what can I pass The method converts the strings in this array into components and dynamically adds them to the vue template. I hope to get your help.
<template>
<el-icon>
<i-ep-monitor />
</el-icon>
</template>
When I write like this, it can be imported normally
<template>
<el-icon>
{How should I dynamically generate a component like <i-ep-monitor /> from the string content in the array?}
</el-icon>
</template>
You can use <component> for that
details: https://vuejs.org/api/built-in-special-elements.html#component
example:
<template>
<el-icon>
<component :is="myIcon"/>
</el-icon>
</template>
where myIcon would return a string like i-ep-monitor

Vuetify v-text-field label on right

The label of the v-input-text is on the right. I would like to put it on the left without using css, which should actually be the default. From the documentation I can see that the left side is the default one. I have already tried several examples of the Vuetify documentation and all of them give me the same result.
The code is copied/pasted from the docs. It seems to me that something so simple should not be an issue. I have struggled with this for awhile now. I have already tried to run several examples in different browsers: Opera, Microsoft Edge, Firefox.
Here is the code that you can find here:
<template>
<v-form>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="message"
outlined
clearable
label="Message"
type="text"
>
<template v-slot:prepend>
<v-tooltip
bottom
>
<template v-slot:activator="{ on }">
<v-icon v-on="on">
mdi-help-circle-outline
</v-icon>
</template>
I'm a tooltip
</v-tooltip>
</template>
<template v-slot:append>
<v-fade-transition leave-absolute>
<v-progress-circular
v-if="loading"
size="24"
color="info"
indeterminate
></v-progress-circular>
<img
v-else
width="24"
height="24"
src="https://cdn.vuetifyjs.com/images/logos/v-alt.svg"
alt=""
>
</v-fade-transition>
</template>
<template v-slot:append-outer>
<v-menu
style="top: -12px"
offset-y
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
v-on="on"
>
<v-icon left>
mdi-menu
</v-icon>
Menu
</v-btn>
</template>
<v-card>
<v-card-text class="pa-6">
<v-btn
large
flat
color="primary"
#click="clickMe"
>
<v-icon left>
mdi-target
</v-icon>Click me
</v-btn>
</v-card-text>
</v-card>
</v-menu>
</template>
</v-text-field>
</v-col>
</v-row>
</v-container>
</v-form>
</template>
Result:
I had the same issue.
In comparing my code to the code pen example on https://codepen.io/pen/?&editors=101 I noticed that they construct the vuetify object slightly differently.
I had:
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
const opts = {}
export default new Vuetify(opts)
document.addEventListener('DOMContentLoaded', () => {
Vue.use(Vuetify);
login_vue = new Vue({
el: '#login-vue',
data: {
message: "Hello World",
},
computed: {},
methods: {}
});
});
Their vue code:
new Vue({
el: '#app',
vuetify: new Vuetify(),
})
This led me to think that their code was expecting a variable named 'vuetify'.
After checking the installation guide again. https://vuetifyjs.com/en/getting-started/installation/#webpack-install. I saw that they have two ways to load vuetify depending on if you are loading from a loader or from javascript. I was using the Loader example in javascript.
Once I changed my code to create the variable they were expecting everything worked as expected.
Final Code:
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
const opts = {}
export default new Vuetify(opts)
document.addEventListener('DOMContentLoaded', () => {
Vue.use(Vuetify);
vuetify: new Vuetify(),
login_vue = new Vue({
el: '#login-vue',
data: {
message: "Hello World",
},
computed: {},
methods: {}
});
});
Seems odd that I would need Vue.use(Vuetify); and vuetify: new Vuetify()
This solution is working, but maybe someome with more VUE experience can give a better configuration.
I am using Vue2. The issues seems to be lying in the fact that my application has been made outside the v-app DOM. In order for everything to function correctly, it should be a descendant of . Hence, the issue. Once I put everything inside v-app, things started working like they should. However, refactoring the code could be days of work, depending upon size and complexity.
CSS Workaround
I have found a CSS workaround to solve the issue. I added the following styles to my App.vue and it seemed to fix the issue:
.v-input .v-label--active{
transform: translateX(-12%) translateX(-7.5px) translateY(-28px) scale(0.75) !important;
}
.v-input--dense .v-label--active{
transform: translateX(-12%) translateX(-7.5px) translateY(-21px) scale(0.75) !important;
}
My Problem
For me, the active labels were shifted slightly to the right and slightly to the bottom:
Suggestion
Since your issue is more extreme, try making the values in translateX more negative.
Caution
Remember - keep the ratio of the translateX values constant while doing so (12:7.5). This will ensure that there is no disparity due to the length of the labels.

Passing props that is an array to 2 different components

This is my 3rd day working with Vue CLI, and I want to pass props that is an array to 2 different components. The components are called Products and Modal. The Modal component is dependent on the Products component. When I pass props to both components inside my App.vue, I don't want both components to render in my App.vue. Like I said, my Modal component is dependent on my Products component.
My overall goal is to display the product title, photo, and description in my modal.
The issue I have is I make a get request in my App.vue, which i fill an empty array and then pass that to Products component. If i pass the array as props to both Products and Modal, I will get an extra modal to render at app level which does make sense, but I don't want that.
Im still getting used to how Vue works and tips or help would be much appreciated.
This could be a very obvious answer that i'm just overlooking, but i'm learning, so please understand that.
App.Vue (I make my get request here and fill an empty array to then have that array passed down as props to the two components):
<template>
<div id="app">
<all-products v-bind:newProductsArray="newProductsArray"></all-products>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import AllProducts from './components/AllProducts.vue'
import Modal from './components/Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
//export components to be rendered by main.js then by the DOM
export default {
components: {
'all-products': AllProducts
},
data() {
return {
//empty products array to be filled after get request
products: [],
//declare new array to be set to products array, then passed as props. I do this because without another array,
//all products component will receiev an empty array before it can be filled by the http request.
newProductsArray: []
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
//set products array to the new array to be passed down as props to all products component.
this.newProductsArray = this.products
})
}
}
</script>
Product component (This component receives props from app.vue):
<template>
<div class="products">
<h1 class="all-products">All Products</h1>
<div v-for="product in newProductsArray" :key="product.id" class="single-product">
<div class="product-container">
<div class="row">
<div class="col-md-8 center-block">
<div class="title">{{product.title}}</div>
<img class="images" :src="product.photo_url">
<div class="price">${{product.price}}</div>
<modal/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import bootstrap to be used to style buttons and modal.
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
export default {
//receive new products array as props from app
props: ['newProductsArray'],
components: {
'modal': Modal
},
data() {
return {
//set modal show to false by default
modalShow: false
}
},
methods: {
showModal() {
//toggle modal to show
this.modalShow = true
},
closeModal() {
//toggle modal to close
this.modalShow = false
}
},
}
</script>
Modal component (I want to receive the same props that products received)
<template>
<div>
<b-button #click="modalShow = !modalShow">Show Product Info</b-button>
<b-modal v-model="modalShow" title="title">
<div class="modal-body">
<img class="img-responsive" style="margin:0 auto;" src="http://placehold.it/300x340" alt="">
</div>
<p class="product-description">Description</p>
<div slot="modal-footer" class="w-100">
<b-button size="md" class="float-right" variant="warning" #click="modalShow=false">Close</b-button>
<b-button size="md" class="float-right" variant="primary" #click="modalShow=false">Like</b-button>
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: ['newProductsArray'],
data() {
return {
modalShow: false
}
}
}
</script>

CSS Modules and React components, I feel like I am not using CSS modules correctly and my styles are clashing

So I have a search box component that is to be used in the Navigation bar on all pages of the site. I also want to use this component/html in other pages of the site hence I put it inside a component shown below
LocationSearchBox.js
import React, {PropTypes} from 'react'
import {Button,FormGroup, FormControl} from 'react-bootstrap'
import styles from '../scss/components/LocationSearchBox.scss'
export default function LocationSearchBox(props) {
return (
<FormGroup>
<FormControl type="text" placeholder="Search" />
<Button bsStyle="success" type="submit" className={styles.navbarSubmitButton}>Get Weather</Button>
</FormGroup>
)
}
I am using css modules with web pack to convert my scss into css and than generate random styles to use in classnames for the components.
LocationSearchBox.scss
.navbarSubmitButton {
margin-left: 20px;
}
This used inside the component just adds some space between the input and submit button.
This is the NavBar component again with the help of react-bootstrap.
MainNavBar.js
import React from 'react';
import {Navbar, NavbarHeader, NavbarBrand, NavbarCollapse} from 'react-bootstrap';
import {default as Search} from './LocationSearchBox'
import styles from '../scss/components/MainNavbar.scss'
export default function MainNavbar() {
return(
<Navbar fixedTop className={styles.navbarColour} >
<NavbarBrand pullLeft >
<a href='#' className={styles.Brand}>Weather-app</a>
</NavbarBrand>
<Navbar.Collapse>
<Navbar.Form pullRight>
<Search/>
</Navbar.Form>
</Navbar.Collapse>
</Navbar>
)
}
Now I have created a homepage component and I want to use the LocationSearchBox component inside it.
Home.js
import React from 'react'
import {default as Search} from '../components/LocationSearchBox'
import styles from '../scss/components/Home.scss'
export default function Home() {
return (
<div className={styles.center}>
<h2>Enter a city and state</h2>
<Search />
</div>
)
}
The search component inside Home.js, the button has the same margin-left property was the navigation bar so it is moved to the right a bit. I don't want that to happen. I want it only to be applied to the search box used inside the navigation bar but I am unsure of how to do that with CSS modules and React components without creating a separate search box for the navigation bar but I see that as pointless when it will have the exact same code.
I feel like I am not using CSS modules correctly at all, I am not using its philosophy and the point of CSS modules correctly.
It's hard to say what the best approach would be without understanding why it needs to be visually different in those locations but generally I would update <Search /> to accept a new prop to conditionally apply the extra margin.
It could be theme if those two styles are likely to be used many times:
<FormControl type="text" placeholder="Search" />
<Button bsStyle="success" type="submit" className={props.theme === 'foo' ? styles.navbarSubmitButton : null}>Get Weather</Button>
Or if it's more of an override you could provides a buttonClassName prop to add exceptional styles at specific call sites:
<FormControl type="text" placeholder="Search" />
<Button bsStyle="success" type="submit" className={`${styles.navbarSubmitButton} ${props.buttonClassName}`}>Get Weather</Button>

Resources