Dynamic Heroicons in Vue.js - vue.js

I assign the heroicon to a variable from my list of menu items.
const navigation = [{ name: 'Dashboard', icon: TemplateIcon, href: '#', current: true }]
Then I try to display the icon.
<li v-for="item in navigation" class="relative px-6 py-3">
<TemplateIcon class="h-5 w-5" />
<item.icon class="h-5 w-5" />
</li>
The template icon is only shown once, but should be shown twice.
I have already tried this
<{{item.icon}} class="h-5 w-5" />
{{item.icon}}
<svg><path :d="item.icon"></path></svg>
thx for help

You can use Dynamic component approach which is useful to dynamically switch between components.
This can be achieve by using Vue's <component> element with the special :is attribute.
Demo :
new Vue({
el: '#app',
data: {
navigation: [{ icon: 'H3' }]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="container">
<div v-for="(item, index) in navigation" :key="index">
<component :is="item.icon">Hello</component>
</div>
</div>
</div>

You should use the component component in vue.
You can find the documentation for it here: https://vuejs.org/guide/essentials/component-basics.html#dynamic-components
tldr, pass your component in the is prop
<component :is="item.icon" class="h-5 w-5" />

Related

Vue bootstrap form tags allowing duplicates

I'm trying to allow duplicates in Vue bootstrap's form tags.
I've tried using :tag-validator (in the example) and #tag-state to externally modify the v-model value. However it seems like it is getting rid of the duplicate somewhere. Is this impossible?
Jsfiddle of the example: https://jsfiddle.net/yts54fpd/.
<div id="app">
<template>
<div>
<b-form-group label="Tagged input using select" label-for="tags-component-select">
<!-- Prop `add-on-change` is needed to enable adding tags vie the `change` event -->
<b-form-tags
:tag-validator="tagValidator"
id="tags-component-select"
v-model="value"
size="lg"
class="mb-2"
add-on-change
no-outer-focus
>
<template v-slot="{ tags, inputAttrs, inputHandlers, disabled, removeTag }">
<ul v-if="tags.length > 0" class="list-inline d-inline-block mb-2">
<li v-for="tag in tags" :key="tag" class="list-inline-item">
<b-form-tag
#remove="removeTag(tag)"
:title="tag"
:disabled="disabled"
variant="info"
>{{ tag }}</b-form-tag>
</li>
</ul>
<b-form-select
v-bind="inputAttrs"
v-on="inputHandlers"
:disabled="disabled"
:options="options"
>
<template #first>
<!-- This is required to prevent bugs with Safari -->
<option disabled value="">Choose a tag...</option>
</template>
</b-form-select>
</template>
</b-form-tags>
</b-form-group>
</div>
</template>
</div>
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
options: ['Apple', 'Orange', 'Banana', 'Lime', 'Peach', 'Chocolate', 'Strawberry'],
value: []
}
},
methods: {
tagValidator(tag) {
this.value.push(tag)
return true
}
}
})
}

Adding a component by clicking a button

Vue.component('component-a', {
template: '<h3>Hello world!</h3>'
})
new Vue({
el: "#app",
data: {
arr: []
},
methods: {
add(){
this.arr.push('component-a');
console.dir(this.arr)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<hr>
<button #click="add">Add a component</button>
<ul>
<li v-for="component in arr"> {{ component }} </li>
</ul>
</div>
I want to insert a component a lot of times to the page by clicking a butoon, but instead of this only a component`s name is inserted. How to add a component itself?
In your code the double curly braces do not reference the component itself but just the string you added with this.arr.push('component-a'); hence just the string being displayed.
If you would like this string to call the actual component you could use dynamic components.
Replacing {{ component }} with <component :is="component"/> would achieve the effect I think you're looking for.
However if you're only going to be adding one type of component I would consider adding the v-for to the component tag itself like so:
<component-a v-for="component in arr/>
Use the component element to render your component dynamically.
The usage is very simple: <component :is="yourComponentName"></component>
The ":is" property is required, it takes a string (or a component definition).
Vue will then take that provided string and tries to render that component. Of course the provided component needs to be registered first.
All you have to do is to add the component tag as a child element of your list tag:
<li v-for="component in arr">
<component :is="component"></component>
</li>
Vue.component('component-a', {
template: '<h3>Hello world!</h3>'
})
new Vue({
el: "#app",
data: {
arr: []
},
methods: {
add() {
this.arr.push('component-a');
console.dir(this.arr)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a></component-a>
<hr>
<button #click="add">Add a component</button>
<ul>
<li v-for="component in arr">
<component :is="component"></component>
</li>
</ul>
</div>

Custom Select Component on Vue with SVG

I'm creating a custom select component on Vue and I'm using tailwind to style it.
I would like to have an chevron-down caret svg align to the right, which will open the select options when clicked. I'm having some trouble with this.
<template>
<div class="flex flex-wrap mb-4 relative">
<select :value="value" #input="$emit('input', $event.target.value)" class="w-full h-12 pl-4 bg-white focus:bg-grey-10 focus:text-grey-5 border-2 border-grey-9 rounded-lg text-sm focus:outline-none">
<option :value="null">
Select an option
</option>
<option v-for="option in options" :value="option.slug">{{ option.name }}</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" class="absolute mt-4 mr-4 right-0 cursor-pointer">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</template>
<script>
export default {
props: [ 'value'],
data () {
return {
options: [
{ slug: 'test1', name: 'Test 1' },
{ slug: 'test2', name: 'Test 2' },
],
};
},
}
</script>
This is what it looks like, however the svg when clicked does not open the dropdown.
Any suggestions?
To the <svg> tag, you need to add the .pointer-events-none class. From the Tailwind Docs
Use .pointer-events-none to make an element ignore pointer events. The pointer events will still trigger on child elements and pass-through to elements that are "beneath" the target.
There is a helpful custom select example in the Tailwind docs.

bootstrap-vue <b-pagination> component not changing pages on click

I want to implement a pagination component b-pagination w/bootstrap-vue but it will only display page one. I am following the setup in documentation but they only show an example using a table not an unordered list. I have :
<template>
<div class="results overflow-auto" v-cloak>
<h3>Search Results</h3>
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<template v-slot:header>
<h1>NASA Image</h1>
</template>
<template v-slot:body>
<b-img class="modal-image" v-bind:src="attribute"></b-img>
</template>
</modal>
<!-- ======== Pagination Markup ============ -->
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
:items="items"
aria-controls="my-list"
></b-pagination>
<p class="mt-3">Current Page: {{ currentPage }}</p>
<!-- ==========End Pagination Markup ======== -->
<!-- Limit output to 100 items -->
<ul
class="search-results"
id="my-list"
>
<li v-for="(item, index) in propsResults.items.slice(0,100)" :key="index">
{{ item.data[0].title}}
<span>
<b-img
thumbnail
class="thumbnail"
:src="item.links[0].href"
alt="Fluid image"
id="show-modal"
v-on:click="imageModal"
></b-img>
</span>
</li>
</ul>
</div>
</template>
and my javascript is :
export default {
name: "SearchResults",
props: ["propsResults"],
data() {
return {
showModal: false,
attribute: "",
perPage: 10,
currentPage: 1,
items: this.$props.propsResults.items
};
},
computed: {
rows() {
return this.$props.propsResults.items.length;
}
}
The pagination component is displaying all 100 items of items array on one page. I should also note I do not see the items array in the b-pagination props object per Vue dev tools in FF. is this normal? Any insight appreciated..
You should still be using currentPage to choose which items to show. The b-pagination component only changes that number.
Try using this line:
<li v-for="(item, index) in items.slice(10*(currentPage-1),10*(currentPage))" :key="index">

Conditional a href in Vuejs

I am rendering a list of store titles in VueJS, some of them have a url property, some of them don't. If the title has a url, I want to add a a href property:
<div v-for="(store, index) in stores">
<span v-if="store.link"><a :href="store.link" target="_blank">{{ store.title }}</a></span>
<span v-else="store.link">{{ store.title }}</span>
</div>
This works, but the code looks duplicated. Is there anyway to simplify the code further?
you can use component tag:
var app = new Vue({
el: '#app',
data () {
return {
stores: [
{title:'product1',link:'/products/222'},
{title:'product2'},
{title:'product3',link:'/products/333'},
{title:'product4'}
]
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<div v-for="(store, index) in stores">
<component :is="store.link?'a':'span'" :href="store.link || ''" target="_blank">{{store.title}}
</component>
</div>
</div>
I'd remove the first span element, as it's not necessary. Also, the v-else does not need the conditional statement (it's not v-else-if):
new Vue({
el: '#app',
data: {
stores: [
{ link: 'foo', title: 'foo-text' },
{ title: 'bar-text' }
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="(store, index) in stores" :key="index">
<a v-if="store.link" :href="store.link" target="_blank">{{ store.title }}</a>
<span v-else>{{ store.title }}</span>
</div>
</div>
You can use dynamic arguments in vue3
https://v3.vuejs.org/guide/template-syntax.html#dynamic-arguments
<a v-bind:[attributeName]="url"> ... </a>
or binding an object of attributes
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>