bootstrap-vue : how to recognize hover event over b-tab - vue.js

I'm trying to change the active tab of a bootstrap-vue b-tabs when the tab title is hovered over, not only when clicked on. I'm having trouble isolating this event.
In the following Codepen example, I can isolate the event when the image is being hovered over, however I want to isolate the event when the title ('Tubes and Vials' for example) is being hovered over.
I'm fairly new to Vue so I apologize if this is a simple answer, but I haven't struggled with this for a while now and haven't been able to figure this out. Thanks!
Component File
<template>
<b-container class="px-3" fluid>
<div>
<h3>Range Of Glass Products We Inspect</h3>
<p>Anything formed from tubular glass</p>
</div>
<div>
<b-tabs content-class="mt-3" align="left" class="vial-types" vertical>
<b-tab
v-for="glassItem in productRange"
v-bind:key="glassItem.type"
v-bind:ref="glassItem"
v-bind:title="glassItem.type"
#mouseover.native="greet()"
#mouseleave.native="greet()"
>
<b-img
v-bind:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>
</div>
</b-container>
</template>
<script>
export default {
name: "ProductRange",
data() {
return {
productRange: [
{type: "Screw & Flanged Head", image:"https://picsum.photos/600/400/", hover: false},
{type: "Tubes and Vials", image:"https://picsum.photos/600/400/", hover: false},
{type: "Pipettes, Syringes, Filling Needles", image:"https://picsum.photos/400/400/",hover: false},
{type: "Ampoules", image:"https://picsum.photos/600/400/", hover: false},
{type: "Custom Geometries Per Customer Specification", image:"https://picsum.photos/600/400/", hover: false}
]
}
},
methods: {
greet: function () {
console.log("Hovering");
}
}
}
</script>
<style lang="sass">
</style>

You could also use the b-tab's title slot, and then add a hover/unhover listener in there:
<b-tabs content-class="mt-3" align="left" class="vial-types" vertical>
<b-tab
v-for="glassItem in productRange"
v-bind:key="glassItem.type"
v-bind:ref="glassItem"
>
<template v-slot:title>
<div
#mouseover="hovered"
#mouseleave="unHovered"
>
{{ glassItem.type }}
</div>
</template>
<b-img
v-bind:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>

Sadly I don't think there's a built-in way to easily do this.
However, you can still solve this by hiding the standard tabs and instead reconstruct the structure yourself using b-nav and binding to the b-tabs v-model.
You can then add your events the b-nav-item as they'll be working as your tabs.
new Vue({
el: "#app",
data: {
selectedTab: 0,
productRange: [
{
type: "Screw & Flanged Head",
image: "https://picsum.photos/600/400/"
},
{
type: "Tubes and Vials",
mage: "https://picsum.photos/640/400/"
},
{
type: "Pipettes, Syringes, Filling Needles",
image: "https://picsum.photos/400/400/"
},
{
type: "Ampoules",
image: "https://picsum.photos/600/400/"
},
{
type: "Custom Geometries Per Customer Specification",
image: "https://picsum.photos/700/400/"
}
]
},
methods: {
greet: function() {
console.log("hovering");
},
onTabHover(glassItem) {
console.log("Tab hovered", glassItem)
}
}
});
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.4.2/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.4.2/dist/bootstrap-vue.min.js"></script>
<b-container id="app" class="px-3"fluid>
<div>
<h3>Range Of Glass Products We Inspect</h3>
<p>Anything formed from tubular glass</p>
</div>
<div>
<b-row>
<b-col cols="auto">
<b-nav pills vertical>
<b-nav-item v-for="(glassItem, index) in productRange"
:active="selectedTab === index"
#click="selectedTab = index"
#mouseenter="onTabHover(glassItem)">
{{ glassItem.type }}
</b-nav-item>
</b-nav>
</b-col>
<b-col>
<b-tabs v-model="selectedTab"
content-class="mt-3"
class="vial-types"
nav-class="d-none">
<b-tab
v-for="glassItem in productRange"
:key="glassItem.type"
:ref="glassItem"
:title="glassItem.type"
#mouseover.native="greet()"
#mouseleave.native="greet()"
>
<b-img
:src="glassItem.image"
alt="Factory Image">
</b-img>
</b-tab>
</b-tabs>
</b-col>
</b-row>
</div>
</b-container>

Related

PrimeVue Toast displaying html tags

How can I implement displaying a link in a primevue toast message? I cannot use v-html directive and triple brackets do not work. Does anybody has another idea how to solve it?
A hacky way is to extends Toast component:
Here a codesandbox : https://codesandbox.io/s/extend-primevue-toast-o5o1c?file=/src/CustomToastMessage.vue
1. On your component
Import your custom toast component where you need to call this.$toast:
<template>
<div>
<CustomToast />
<CustomToast position="top-left" group="tl" />
<CustomToast position="bottom-left" group="bl" />
<CustomToast position="bottom-right" group="br" />
<div class="card">
<Button #click="test" label="test" />
</div>
</div>
</template>
<script>
import CustomToast from "./CustomToast.vue";
export default {
components: {
CustomToast,
},
data() {
return {
messages: [],
};
},
methods: {
test() {
this.$toast.add({
severity: "success",
summary: "Test",
detail: "<b>Test Bold</b>",
});
},
},
};
</script>
2. CustomToast.vue (extend primevue toast)
<template>
<Teleport to="body">
<div ref="container" :class="containerClass" v-bind="$attrs">
<transition-group name="p-toast-message" tag="div" #enter="onEnter">
<CustomToastMessage
v-for="msg of messages"
:key="msg.id"
:message="msg"
#close="remove($event)"
/>
</transition-group>
</div>
</Teleport>
</template>
<script>
import Toast from "primevue/toast/Toast.vue";
import CustomToastMessage from "./CustomToastMessage.vue";
export default {
extends: Toast,
components: {
CustomToastMessage,
},
};
</script>
3. CustomToastMessage (extend primevue toastmessage)
Add v-html where you want to have html
<template>
<div
:class="containerClass"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div class="p-toast-message-content">
<span :class="iconClass"></span>
<div class="p-toast-message-text">
<span class="p-toast-summary">{{ message.summary }}</span>
<div class="p-toast-detail" v-html="message.detail"></div>
</div>
<button
class="p-toast-icon-close p-link"
#click="onCloseClick"
v-if="message.closable !== false"
type="button"
v-ripple
>
<span class="p-toast-icon-close-icon pi pi-times"></span>
</button>
</div>
</div>
</template>
<script>
import ToastMessage from "primevue/toast/ToastMessage.vue";
export default {
extends: ToastMessage,
};
</script>
There is an easiest solution.
Just implement your own template.
Example:
<Toast :position="toastPosition">
<template #message="slotProps">
<span :class="iconClass"></span>
<div class="p-toast-message-text">
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
<div class="p-toast-detail" v-html="slotProps.message.detail" />
</div>
</template>
</Toast>

How can I write emit even to tell parent is icon is clicked in Vuejs

I am write input element with icon to show/hide password, how can I write event emit in v-icon to tell parent component that icon is click
Here is my child component BaseInputPassword
<div class="label">
{{ labelName }} <span v-if="required" class="required">※</span>
</div>
<div class="input_password">
<input
:type="type ? 'password' : 'text'"
:placeholder="placeholder"
/>
<v-icon #click="type =! type">{{ type ? "mdi-eye-off" : "mdi-eye" }}</v-icon>
</div>
</div>
</template>
<script>
export default {
props: ["labelName", "placeholder", "required", "type" ],
data() {
return {
};
},
methods: {
updateValue($event) {
this.$emit("input", $event);
},
}
}
</script>
Here is my parent component, im using three child component
<div class="input_area">
<BlockInputPassword labelName="現在のパスワード" required="true" :type="show1" v-model="password" ></BlockInputPassword>
<BlockInputPassword labelName="現在のパスワード" required="true" :type="show2" v-model="password" ></BlockInputPassword>
<BlockInputPassword labelName="新しいパスワードの確認" required="true" :type="show3" v-model="password"></BlockInputPassword>
</div>
</div>
<div class="footer">
<router-link to=""
><div class="btn_login btn_simple">
キャンセル
</div></router-link>
<router-link to=""
><div class="btn_login btn_simple status_color_4">
変更
</div></router-link>
</div>
</div>
</div>
</template>
<script>
import BlockInputPassword from "../components/common/BlockInputPassword"
export default {
components: {
BlockInputPassword,
},
name: "PasswordChange",
data() {
return {
password: '',
show1: false,
show2: false,
show3: false,
};
},
have you child component v-icon emit a custom event
<v-icon #click="$emit('icon-clicked')">
then from the parent component
<div class="input_area">
<BlockInputPassword #icon-click="doSomething" ...></BlockInputPassword>
...
</div>

Template slot in buefy carousel showing broken image

Hi i am newbie in vuejs and buefy. I wanted to do a carousel. However its already printing in the b-carousel-item but in template slot="indicators" it showing broken image. Can anyone help me i want to show the image also in the template slot
this is the code:
https://codesandbox.io/s/wonderful-gagarin-5wc8d?file=/src/App.vue
App.vue
<template>
<b-carousel :indicator-inside="false">
<b-carousel-item v-for="(item, i) in imgurl" :key="i">
<span class="image">
<img :src="getImgUrl(item)" />
</span>
</b-carousel-item>
<template slot="indicators" slot-scope="props">
<span class="al image">
<img :src="getImgUrl(props.item)" :title="props.item" />
</span>
</template>
</b-carousel>
</template>
<script>
export default {
data() {
return {
thumbs: null,
imgurl: [
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTE7e4ENLA4IRiYClFOOyc418WmdNTuWAAX_A&usqp=CAU",
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQrMMLeNArJ-NmM-sRGGGr0ya8y0NSF4HZ8Aw&usqp=CAU",
],
};
},
methods: {
getImgUrl(value) {
this.thumbs = value;
return `${value}`;
},
},
};
</script>

vue b-table customize pagination to show all rows

I am using bootstrap-vue. For pagination of a b-table, I used b-pagination component with the following code for the template:
<div class="description perpage">Per page</div>
<b-form-select class="numPerPage" v-model="perPage" :options="pageOptions"></b-form-select>
<b-col sm="7" md="6" class="pagination">
<b-pagination
:total-rows="totalRows"
v-model="currentPage"
:per-page="perPage"
align="fill"
class="my-0"
aria-controls="my-table"
last-number
></b-pagination>
</b-col>
<div class="description found">Found: {{this.totalRows}}</div>
</div>
<b-table
id="my-table"
show-empty
striped
hover
sticky-header="true"
:items="filteredItems"
:fields="fields"
:per-page="perPage"
:current-page="currentPage"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc">
and the following for the script part:
data() {
return {
totalRows: 1,
perPage: 10,
currentPage: 1,
sortBy: "name",
sortDesc: false,
pageOptions: [5, 10, 20, 50, "show all"],
fields: [..myfields]
};
}
And if I use "show all" in the options field it will show all rows, but it will not properly set the pagination to only one available page.
current display
I want to achieve showing the correct pagination option (only one page) or be able to hide the whole pagination when the "show all" option was made.
How can I get this done?
The easiest way would be to set your show all option to a REALLY high number. To do this you could use the constant Number.MAX_SAFE_INTEGER which contains the number of 9007199254740991.
Which i would safely guess you wont reach in rows.
If you want to hide the pagination completely when the show all option is selected, you could instead set the value to 0. This will show all rows too.
Then you add a v-if to your pagination <b-pagination v-if="perPage !== 0">, which will hide it when that option is selected.
new Vue({
el: '#app',
created() {
for (let i = 0; i < 1000; i++) {
this.items.push({
id: i + 1
});
}
},
computed: {
totalRows() {
return this.items.length
}
},
data() {
return {
perPage: 10,
currentPage: 1,
sortBy: "name",
sortDesc: false,
/* Number.MAX_SAFE_INTEGER = 9007199254740991 */
pageOptions: [5, 10, 20, 50, {
value: Number.MAX_SAFE_INTEGER,
text: "show all"
}],
items: []
}
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.13.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.13.0/dist/bootstrap-vue.js"></script>
<div id="app">
<div class="description perpage">Per page</div>
<b-form-select class="numPerPage" v-model="perPage" :options="pageOptions"></b-form-select>
<b-pagination
:total-rows="totalRows"
v-model="currentPage"
:per-page="perPage"
align="fill"
class="my-0"
aria-controls="my-table"
></b-pagination>
<div class="description found">Found: {{ this.totalRows }}</div>
<b-table
id="my-table"
show-empty
striped
hover
sticky-header="true"
:items="items"
:per-page="perPage"
:current-page="currentPage"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc">
</b-table>
</div>

Transition on class change property

I currently have a vue component that I use to select drinks to be ordered.
You can increment or decrement the amount of drinks per type.
There are 2 buttons (Increment and decrement), the decrement button needs to be hidden when the amount of ordered drinks for that type is equal to 0. This can easily be accomplished by using :class="drink.amount > 0 ? 'visible':'invisible'" using the tailwind classes which sets visibility:hidden css property. When using this method it looks like:
Now I tried to implement css animations (sliding the button underneath the drink block to hide it and later hide from dom). I want to implement this using the vue transition component since this is heavily used throughout the application.
Now I have the problem that the vue transition component only works with a limited amount of vue functions among which v-if and v-show. v-if removes the html from the dom. v-show sets the property display:none both of these functions have the effect of shifting the buttons:
I would like to known how I can use the vue transition component and get the requested aligned buttons as I got without the animation.
<div v-for="drink in drinks" class="w-full flex">
<div class="mx-auto flex">
<transition name="transition-slide-right">
<div class="bg-white w-16 h-16 flex justify-center items-center mt-12"
v-show="drink.amount">
<p>-</p>
</div>
</transition>
<div class="bg-brand w-32 h-24 flex justify-center items-center my-8 z-30">
<p>{{drink.name}} ({{drink.amount}})</p>
</div>
<div class="bg-white w-16 h-16 flex justify-center items-center mt-12">
<p>+</p>
</div>
</div>
</div>
And accompanying script.
<script>
export default {
name: "CreateTransaction",
data: function() {
return {
drinks: [
{name: 'Cola', price: '1.0', amount: 1},
{name: 'Sinas', price: '1.0', amount: 0}
],
}
}
}
</script>
Finally the css:
.transition-slide-right-enter-active, .transition-slide-right-leave-active {
transition: transform .5s;
}
.transition-slide-right-enter, .transition-slide-right-leave-to {
transform: translateX(100%);
}
As a current workaround I removed the transition component, added the transition-slide-right-enter-active class and if the count == 0 I conditionally add the transition-slide-right-enter class. This does not hide the element but hiding it is not required since I is moved underneath the center block.
you can try the following code
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div class="row" id="todo-list-example">
<div v-for="(drink,index) in drinks" class="w-full flex">
<transition name="transition-slide-right"><button v-show="drink.amount" type="button" class="btn btn-danger" v-on:click="click1(index)">-</button></transition>
<a class="btn btn-success" >{{drink.name}} ({{drink.amount}})</a>
<button type="button" class="btn btn-danger" v-on:click="click2(index)">+</button>
</div>
</div>
<style>
.w-full{margin:10px 0px;}
.transition-slide-right-enter-active, .transition-slide-right-leave-active {
transition: transform .5s;
}
.transition-slide-right-enter, .transition-slide-right-leave-to {
transform: translateX(100%);
}
</style>
<script>
new Vue({
el: '#todo-list-example',
data(){
return {
drinks: [
{name: 'Cola', price: '1.0', amount: 1},
{name: 'Sinas', price: '1.0', amount: 1}
],
}
},
methods:{
click1:function(index){
this.drinks[index].amount=this.drinks[index].amount-1;
},
click2:function(index){
this.drinks[index].amount=this.drinks[index].amount+1;
}
}
})
</script>