Change image icon when the active tab is change - vue.js

Current I have a v-tab that have four tabs with image icon and text as well. But when the tab is active, the active tab icon should change to another image. How should I made it?
<v-tabs v-model="tabs" class="tabs-menu">
<v-tab
v-for="item in items"
:key="item.id"
>
<img :src="item.icon" />
{{ item.name }}
</v-tab>
</v-tabs>
data() {
return {
tabs: null,
items: [
{ icon: "/planeInactive.svg", name: "plane" },
{ icon: "/hotelInactive.svg", name: "hotel" },
{ icon: "/planehotelInactive.svg", name: "plane + hotel" },
{ icon: "/planeInactive.svg", name: "students" },
],
};
},

Use some default value for your v-model, 'tab' to make a tab active by default. (I am using the name property of the tab in v-model but you can use any unique property you want.)
Use this v-model to check which tab is active and using the condition operator, update the icon. (I used dummy icons you can use yours.)
Here's the demo where you should see that when the tab is active, the colorful flight icon would show otherwise black flight icon would be displayed.
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#6.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-main>
<v-container>
<v-tabs
v-model="tab"
>
<v-tab
v-for="(item, index) in items"
:key="index"
:href="`#${item.name}`"
class="home-tabs"
active-class="activeTab"
>
<img :src="tab == item.name ? item.icon : inactive_icon" />
{{ item.name }}
</v-tab>
</v-tabs>
</v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<script>
new Vue({
el: '#app',
data () {
return {
tab: 'Flights',
inactive_icon: 'https://www.svgrepo.com/show/413929/fly.svg',
items: [
{ icon: "https://www.svgrepo.com/show/424654/airplane-flight-travel.svg", name: "Flights" },
{ icon: "https://www.svgrepo.com/show/424654/airplane-flight-travel.svg", name: "Hotels" },
{ icon: "https://www.svgrepo.com/show/424654/airplane-flight-travel.svg", name: "Flights + Hotels" },
{ icon: "https://www.svgrepo.com/show/424654/airplane-flight-travel.svg", name: "Students" },
],
}
},
vuetify: new Vuetify(),
})
</script>
</body>
</html>
</html>

Related

vuetify v-alert incorrectly dismissing sibling when transition is applied

I am using vuetify v-alert with v-for=alert in alerts looping through alerts array, when an alert is dismissed #input event is fired and I'm removing it from alerts array.
The problem I am facing, that when the element is clicked, transition is applied, and the sibling alert is now shown at the position where the one which was dismissed, seems like the click event is fired for the sibling element as well and the sibling v-alert is isVisible == false but the #input event is not fired.
If I remove transition="scroll-y-reverse-transition" from the v-alert it works properly.
Why is this happening?
const alertsComponent = {
name: "MyAlertsComponent",
template: "#alerts",
data() {
return {
alerts: []
};
},
created() {
this.$root.$off("alert");
this.$root.$on("alert", this.addAlert);
},
methods: {
closeAlert(idx, alert) {
console.log(`deleting alert idx: ${idx} - ${alert}`);
this.$delete(this.alerts, idx);
},
addAlert(alert, ...args) {
alert.type = alert.type || "error";
this.alerts.unshift(alert);
}
}
};
const app = new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
alertsComponent
},
mounted() {
[...Array(8).keys()].forEach((e) => {
this.fireAlert(this.counter++);
});
},
methods: {
fireAlert(val = this.counter++) {
const alert = this.generateAlert(val);
this.$root.$emit("alert", alert);
},
generateAlert(val, type = "error") {
return {
val,
type
};
}
},
data() {
return {
counter: 1
};
}
});
.alert-section {
max-height: 400px;
padding: 5px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main>
<v-container>
<header>VueTify!</header>
<hr>
<v-row>
<v-col>
<v-btn #click="fireAlert()">Add Alert</v-btn>
</v-col>
</v-row>
<alerts-component>Hi</alerts-component>
</v-container>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<template id="alerts">
<div>
<div class="alert-section overflow-y-auto">
<v-alert v-for="(alert, index) in alerts" :key="index" dense dismissible elevation="5" text :type="alert.type" #input="closeAlert(index, alert)" #addAlert="addAlert"
transition="scroll-y-reverse-transition"
>
{{ alert.val }}
</v-alert>
</div>
<hr>
<pre class="code">{{ JSON.stringify(alerts, null, 2) }}
</pre>
</div>
</template>
Looks like the problem is using the index as key.
If I change :key="alert.val" it works fine.

How to add a custom button in vuetify select

I have a scenario like I have to list a few hundred categories and I have to show them in a select box. Since the list is huge, skip and limit is implemented in the backend, so that it will limit the categories to 20s. My case is like when the user sees the first 20 categories, in the end, I have to add some button stating like 'Load more' so that when the user clicks on it, they can see the next 20 categories. But I have no idea how to add a button in a vuetify select. Can someone help me with this?
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
fruits: [
'Item 1',
'Item 2',
'Item 3',
'Item 4'
],
selectedFruits: [],
}),
computed: {
likesAllFruit () {
return this.selectedFruits.length === this.fruits.length
},
likesSomeFruit () {
return this.selectedFruits.length > 0 && !this.likesAllFruit
},
icon () {
if (this.likesAllFruit) return 'mdi-close-box'
if (this.likesSomeFruit) return 'mdi-minus-box'
return 'mdi-checkbox-blank-outline'
},
},
methods: {
toggle () {
this.$nextTick(() => {
if (this.likesAllFruit) {
this.selectedFruits = []
} else {
this.selectedFruits = this.fruits.slice()
}
})
},
loadMore () {
console.log('load more ...')
}
},
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.2.26/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.2.26/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container fluid>
<v-select
v-model="selectedFruits"
:items="fruits"
label="Favorite Fruits"
multiple
>
<template v-slot:prepend-item>
<v-list-item
ripple
#click="toggle"
>
<v-list-item-action>
<v-icon :color="selectedFruits.length > 0 ? 'indigo darken-4' : ''">{{ icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Select All</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider class="mt-2"></v-divider>
</template>
<template v-slot:append-item>
<v-divider class="mb-2"></v-divider>
<v-btn color="primary" #click="loadMore">
Click me for load more items.
</v-btn>
</template>
</v-select>
</v-container>
</v-app>
</div>
In Vuetify 2.x there is an virtual scroll component, but I still found the v-intersect easiest to use...
markup:
<v-autocomplete
v-model="selected"
:items="beers"
item-text="name"
item-value="id"
label="Search da beers.."
return-object
autocomplete="off"
>
<template v-slot:append-item>
<div v-intersect="onIntersect" class="pa-4 teal--text">
Loading more items ...
</div>
</template>
</v-autocomplete>
data() {
return {
beers: [],
selected: null,
perPage: 30,
page: 1,
}
},
methods: {
getBeers() {
console.log('page', this.page)
const apiUrl = `https://api.punkapi.com/v2/beers?page=${this.page}&per_page=${this.perPage}`
axios.get(apiUrl)
.then(response => {
this.beers = [
...this.beers,
...response.data
]
})
},
onIntersect () {
console.log('load more...')
this.page += 1
this.getDate()
},
},
created() {
this.getData()
}
Working Codeply

How to set an attribute on a v-select option?

Just starting with Vue and need some advice on best practices.
What I'm trying to achieve: set makeId equal to the makes option id, based on what option is selected. So, for example, when I select "bmw" from the dropdown, I want makeId to equal 2.
Without Vuetify, I cloud set a data attribute to each option during the v-for loop, and then #change just grab that attribute and assign the value to the makeid.
How can I achieve what I'm trying to do with Vuetify?
<v-select v-model="car.make" :items="makes" item-value="name" item-text="name"> </v-select>
data: function() {
return {
car: { make: "", makeId: "" },
makes: [{ name: "audi", id: "1" }, { name: "bmw", id: "2" }]
};
}
UPDATED:
you can bind the id as the item-value and on the v-model you need to assign car.madeIdinstead of car.make if you need a single value:
<v-select v-model="car.makeId" :items="makes" item-value="id" item-text="name"> </v-select>
Or you can use an extra method for object binding :
here is a fork for it: codepen
The main difficulty here is that you've got two different formats for your objects. Having to map between id/name and make/makeId complicates things.
This would be easy without the property mapping, you could just set return-object on the v-select.
Assuming that isn't possible, one alternative would be to have car as a computed property based on the selected value to perform the property renaming. This would look like this:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectedCar: null,
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
computed: {
car () {
const selectedCar = this.selectedCar
if (selectedCar) {
return {
make: selectedCar.name,
makeId: selectedCar.id
}
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select v-model="selectedCar" :items="makes" item-value="name" item-text="name" return-object> </v-select>
</v-app>
</div>
We could instead flip around the relationship between car and selectedCar so that car is in data and selectedCar is the computed property. For that we'd need to implement a getter and a setter:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
car: {make: '', makeId: ''},
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
computed: {
selectedCar: {
get () {
return this.makes.find(make => make.id === this.car.makeId)
},
set (selectedCar) {
this.car = {
make: selectedCar.name,
makeId: selectedCar.id
}
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select v-model="selectedCar" :items="makes" item-value="name" item-text="name" return-object> </v-select>
</v-app>
</div>
A third alternative is to ditch v-model altogether and just use the :value/#input pair directly:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
car: {make: '', makeId: ''},
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
methods: {
onInput (id) {
const make = this.makes.find(make => make.id === id)
this.car = {
make: make.name,
makeId: make.id
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select :value="car.makeId" :items="makes" item-value="id" item-text="name" #input="onInput"> </v-select>
</v-app>
</div>

Vue.js - How to switch the URL of an image dynamically?

I am working on a site where the user can select an image via radio selection.
I would like to dynamically update the image URL depending on selection of the user. My approach is to use a computed variable which returns the URL from a list of objects depending on the selection of the user.
<template>
<v-img
:src="require(currBackgroundURL)"
class="my-3"
contain
width="397"
height="560"
></v-img>
</template>
<script>
// data() ...
currBackground: 0,
backgrounds: [
{
name: "Flowers",
url: "../assets/background/bg_1.png"
},
// ...
computed: {
currBackgroundURL: function() {
return this.backgrounds[this.currBackground].url
}
}
</script>
Unfortunately, i get an error which says Critical dependency: the request of a dependency is an expression.
And the browser console says: [Vue warn]: Error in render: "Error: Cannot find module '../assets/background/bg_1.png'"
Question: What is the right way to switch the URL of the image dynamically?
Thanks for your help!
Here is a working example:
var app = new Vue({
el: '#app',
data: () => ({
currBackground: 0,
backgrounds: [
{
name: "black",
url: "https://dummyimage.com/600x400/000/fff"
},
{
name: "blue",
url: "https://dummyimage.com/600x400/00f/fff"
},
{
name: "red",
url: "https://dummyimage.com/600x400/f00/fff"
}
]
}),
computed: {
currBackgroundURL: function() {
return this.backgrounds[this.currBackground].url
}
},
methods: {
nextImage() {
this.currBackground += 1
if (this.currBackground > 2) {
this.currBackground = 0
}
}
}
})
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.18/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-btn #click="nextImage()">Change image</v-btn>
<v-img
:src="currBackgroundURL"
class="my-3"
contain
width="397"
height="560"
></v-img>
</div>
</body>
I removed the require.
The src is a link/path so you don't need require. require will try to take a path and load it into a module instead of a link/path.
Hopefully, this helps.

Integrating strike-through the list on its click or on selecting checkbox in to-do-list app?

I am creating a demo of todolist app and I want to strike-through the list on click of list or selecting particular checkbox. Here is my code:
<div v-for="(item, index) in toDoList" :key="index">
<v-checkbox v-model="item.status"></v-checkbox>
<textfield>{{item.title}}</textfield>
</div>
where status is having a boolean value.
If you want your <textfield> to be responding to click events, a small change to your markup is required:
<v-checkbox
:label="item.title"
v-model="item.status"
></v-checkbox>
And then you can add this additional style to your stylesheet to target the <label> element of a checked checkbox:
.input-group--active label {
text-decoration: line-through;
}
new Vue({
el: '#app',
data: {
toDoList: [
{ title: 'Lorem', status: null },
{ title: 'ipsum', status: null },
{ title: 'dolor', status: null },
{ title: 'sit', status: null },
{ title: 'amet', status: null }
]
}
});
.input-group--active label {
text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<!-- Vuetify dependencies -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.0.14/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.0.14/vuetify.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
<div id="app">
<div
v-for="(item, index) in toDoList"
:key="index">
<v-checkbox
:label="item.title"
v-model="item.status"
></v-checkbox>
</div>
</div>
An alternative solution is simply adding a custom class to your checked elements using :class binding, e.g.:
<div
v-for="(item, index) in toDoList"
:key="index"
:bind="{ 'is-selected': item.status' }">
And for your CSS:
.is-selected label {
text-decoration: line-through;
}
Note that since text-decoration is not inheritable, using .is-selected { text-decoration: line-through; } will not work. You will have to select for the descendant <label> element.
new Vue({
el: '#app',
data: {
toDoList: [
{ title: 'Lorem', status: null },
{ title: 'ipsum', status: null },
{ title: 'dolor', status: null },
{ title: 'sit', status: null },
{ title: 'amet', status: null }
]
}
});
.is-selected label {
text-decoration: line-through;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<!-- Vuetify dependencies -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.0.14/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.0.14/vuetify.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
<div id="app">
<div
v-for="(item, index) in toDoList"
:key="index"
:class="{ 'is-selected': item.status }">
<v-checkbox
:label="item.title"
v-model="item.status"
></v-checkbox>
</div>
</div>