Vuetify change checkbox icon in v-select / v-combobox - vue.js

I use Vuetify but disabled the import of all icons since treeshaking wasn't working properly in Nuxt, instead I followed the advice and import them manually as stated in this thread: vuetifyjs: Adding only used icons to build
However, this means that a lot of components that require icons, e.g v-checkbox, v-select or v-combobox (which uses v-checkbox in their dropdown menus) need their icons added manually. Just using v-checkbox allows for :on-icon & :off-icon props to be used but I can't figure out how I'd reach them when the checkboxes are used by other components.
I've been attempting to change the behaviour in both v-select and v-combobox.
This is as far as I got but clearly this doesn't add the checked icon, just the blank one.
<v-combobox outlined multiple chips v-model="select" :items="items">
<template v-slot:item="{ item }">
<v-icon>{{mdiCheckboxBlankOutline}}</v-icon>{{ item }}
/template>
</v-combobox>
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "#mdi/js";
Data(){
select: ["Stockholm"],
items: [
"Stockholm",
"London",
],
}
My question is therefore, how can replicate the default checkbox behaviour for the combobox menu using imported icons?
This thread seems to talk about it but never ends up showing a code example:
https://github.com/vuetifyjs/vuetify/issues/10904
(Meaning it should look like this)

You can use the item slot, where you are provided with the item, on and attrs object, to replicate the default behaviour.
You bind the on (events) and attrs (properties) objects to the custom list item, to send click events from your list item to combobox component.
Next you set the appropriate icon depending on the selected values. See the code below and the code sandbox.
<template>
<v-app>
<v-combobox
label="Label"
outlined
multiple
chips
v-model="select"
:items="items"
>
<template v-slot:item="{ item, on, attrs }">
<v-list-item v-on="on" v-bind="attrs">
<v-list-item-icon>
<v-icon>
{{ select.includes(item) ? checkIcon : uncheckIcon }}
</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="item" class="text-left"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-combobox>
</v-app>
</template>
<script>
import { mdiCheckboxBlankOutline, mdiCheckboxMarked } from "#mdi/js";
export default {
name: "HelloWorld",
data() {
return {
items: ["One", "Two", "Three"],
select: [],
uncheckIcon: mdiCheckboxBlankOutline,
checkIcon: mdiCheckboxMarked,
};
},
};
</script>
CodeSandbox: https://codesandbox.io/s/recursing-banach-cb7ys?file=/src/components/HelloWorld.vue

Related

VUE - vue draggable wont drag back into empty list column

I'm currently developing a Project Management application using the vue-draggable feature, which allows me to drag tasks into three distinct phases: ToDo, Progress, and Completed. The feature partially works; I can drag all tasks to different phases, but I cannot drag back into an empty phase. Here's the codesandbox and picture. Am I missing something?
<template>
<div>
<v-row>
<v-col>
<draggable :list="todos" group="people" #change="log"></draggable>
</v-col>
<v-col>
<draggable :list="progress" group="people" #change="log"></draggable>
</v-col>
<v-col>
<draggable :list="completed" group="people" #change="log"></draggable>
</v-col>
</v-row>
</div>
</template>
<script>
import draggable from "vuedraggable";
export default {
name: "two-lists",
display: "Two Lists",
order: 1,
components: { draggable },
data() {
return {
todos: [],
progress: [],
completed: [],
};
},
};
</script>
That's becuase you haven't set any min-height to your draggable. So when they are empty the active area of the draggable shrinks all the way down. You can still move the cards if you drag them to the "To Do" title for example, but for a better UX you should set at least a minimum height to it or make them use all the available height so it's easier for the user to drag them back.
<draggable
class="list-group"
:list="todos"
group="people"
#change="log"
style="min-height:400px"
>
...
</draggable>

Dialog inside autocomplete - vuetify

I am trying to put a dialog inside an autocomplete component
<v-autocomplete v-model="item.selected_item"
:items="items"
item-text="name"
return-object
single-line
#change="setItemId(item)"
>
<div slot="prepend-item">
<add-item></add-item>
</div>
</v-autocomplete>
the code for component <add-item>
<v-dialog persistent v-model="dialog" width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn text block v-bind="attrs" v-on="on">{{__("Add Item")}}</v-btn>
</template>
<v-card>
</v-card>
</v-dialog>
now , the dialog button actually shows up inside the autocomplete , but the problem is that the dialog automatically goes of on any mouse click ,
I noticed that this is related somehow to the autocomplete component , I tried to put the <add-item> component outside of the autocomplete and it worked fine .
how can this be solved ?
Instead of putting the whole <add-item/> inside the autocomplete, you can separate the button and the dialog itself, then control the dialog whenever the button is clicked. We can base our solution at this example. We will declare a variable dialog that will control our add-item dialog's visibility. That variable will be handled by our button inside the autocomplete.
Main Component:
<v-autocomplete
...
>
<div slot="prepend-item">
<v-btn text block #click="dialog = true">{{"Add Item"}}</v-btn>
</div>
</v-autocomplete>
<add-item v-model="dialog"/> <!-- Pass the dialog variable to the add-item component -->
export default {
data: () => ({
items: [...],
dialog: false // let's close the dialog initially.
})
}
Add Item Component:
<v-dialog persistent :value="value" width="500">
<v-card>
...
<!-- To close the dialog, we'll need to use $emit() to
let our parent know that the value had changed. -->
<v-btn #click="$emit('input', false)">Close Dialog</v-btn>
</v-card>
</v-dialog>
export default {
props: ["value"], // Pass the value of the dialog from the main component
};
Notice that we used $emit() when we want to close our dialog. We need to use that because the variable that is controlling the dialog is from the main component, and the best way to change the value from add-item is by emitting a value. More explanation here.
Here is a demo.

how to handle a dropdown visibility manually instead of Vuetify controls this

I have created a dropdown menu in vuetify which looks like this:
[![enter image description here][1]][1]
`
<v-menu offset-y>
<template v-slot:activator="{ on }">
<v-btn text v-on="on">
Details
</v-btn>
</template>
<v-list>
<v-list-item>
<v-form ref="form">
<v-radio-group v-model="metrics" required>
<v-radio
label="ABC"
value="abc"
></v-radio>
<v-radio label="XYZ" value="xyz"></v-radio>
</v-radio-group>
<v-divider></v-divider>
<v-radio-group v-model="order" required>
<v-radio
label="Higher"
value="higher"
></v-radio>
<v-radio
label="Lower"
value="lower"
></v-radio>
</v-radio-group>
<v-divider></v-divider>
<v-btn
#click="
sortTableData(
metrics,
order,
$props.tableItems
)
"
>
Apply
</v-btn>
</v-form>
</v-list-item>
</v-list>
</v-menu>`
however,
when I click the Dropdown and select for eg:Installs, the menu
closes..
I have to click the Dropdown again to choose higher/lower..
and the menu closes again..
And again I have to click the Dropdown to Click "Apply" button.
Question : Is there Any way I can hold this menu until I click "Apply"?
Thanks a lot in advance for your help !
Ok, the idea here is to manually handle the dropdown visibility instead of letting Vuetify controls this.
To do so, you need to:
add a :close-on-content-click="false" on your <v-menu> (doc)
add a v-model directive binded to a "data" boolean value (ex: v-model="isDropdownDisplayed"), initialized to false (closed dropdown at load)
The first prop tells Vuetify to not close the dropdown when clicking on content (only an outside click will do it), while the second prop is linking the dropdown visilibity to your custom data boolean value.
As your "data" boolean value is initialized to false (closed dropdown) and is automatically updated to true via the v-model when opening the dropdown, the left thing to do is to pass this value to false on your sortTableData method.
Assuming you're using SFC (but the approach is the same for pure JS components):
Template
<v-menu offset-y :close-on-content-click="false" v-model="isDropdownDisplayed">
...
</v-menu>
Script
{
name: 'MyComponent',
data: function () {
return {
isDropdownDisplayed: false
}
},
methods: {
sortTableData: function (/* args */) {
// ...
this.isDropdownDisplayed = false
// ...
}
}
}

Meaning of v-slot:activator="{ on }"

Looking at the Vuetify example code for v-toolbar, what is the purpose of v-slot:activator="{ on }"? For example:
<template v-slot:activator="{ on }">
<v-toolbar-title v-on="on">
<span>All</span>
<v-icon dark>arrow_drop_down</v-icon>
</v-toolbar-title>
</template>
<script>
export default {
data: () => ({
items: [
'All', 'Family', 'Friends', 'Coworkers'
]
})
}
</script>
As far as I can see, on is not a defined variable anywhere, so I don't see how this is working. When I try it in my project, Internet Explorer throws an error on the <template v-slot:activator="{ on }">, but if I remove it, the page renders.
You're likely referring to this example:
<v-toolbar color="grey darken-1" dark>
<v-menu :nudge-width="100">
<template v-slot:activator="{ on }">
<v-toolbar-title v-on="on">
<span>All</span>
<v-icon dark>arrow_drop_down</v-icon>
</v-toolbar-title>
</template>
...
</v-menu>
</v-toolbar>
The following line declares a scoped slot named activator, and it is provided a scope object (from VMenu), which contains a property named on:
<template v-slot:activator="{ on }">
This uses destructuring syntax on the scope object, which IE does not support.
For IE, you'd have to dereference on from the scope object itself:
<template v-slot:activator="scope">
<v-toolbar-title v-on="scope.on">
But the ideal solution IMO is to use a Vue CLI generated project, which includes a Babel preset (#vue/babel-preset-app) to automatically include the transforms/polyfills needed for the target browsers. In this case, babel-plugin-transform-es2015-destructuring would be automatically applied during the build.
Details on the activator slot
VMenu allows users to specify a slotted template named activator, containing component(s) that activate/open the menu upon certain events (e.g., click). VMenu provides listeners for those events via an object, passed to the activator slot:
<v-menu>
<template v-slot:activator="scopeDataFromVMenu">
<!-- slot content goes here -->
</template>
</v-menu>
The slot content can access VMenu's event listeners like this:
<v-menu>
<template v-slot:activator="scopeDataFromVMenu">
<button v-on="scopeDataFromVMenu.on">Click</button>
</template>
</v-menu>
For improved readability, the scoped data can also be destructured in the template:
<!-- equivalent to above -->
<v-menu>
<template v-slot:activator="{ on }">
<button v-on="on">Click</button>
</template>
</v-menu>
The listeners from the scope object are passed to the <button> with v-on's object syntax, which binds one or more event/listener pairs to the element. For this value of on:
{
click: activatorClickHandler // activatorClickHandler is an internal VMenu mixin
}
...the button's click handler is bound to a VMenu method.
I think the original question is about understanding the "on" object. It is best explained here:
https://github.com/vuetifyjs/vuetify/issues/6866
Essentially "on" is a prop passed in from the activator. What v-on="on" does is bind that on prop to the component. "on" itself is all of the event listeners passed from the activator.
To call out a readability tip, it's possible to use this syntax:
<v-menu>
<template v-slot:activator="{ on: activationEvents }">
<v-btn v-on="activationEvents">
I like turtles 🐢
</v-btn>
</template>
</v-menu>
In my brain this has a more fluent readability than v-on="on", which to me is like observing a conversation consisting solely of:
Person 1: "Hey"
Person 2: "Yep"
Understand? ;)
By the way, activationEvents could be any alias, like "slotEvents", "listeners", "anyOldEvent", or whatever makes more sense to the reader as a renaming of the mysterious on.
Run the below code,you will know what is 'attrs' an 'on' in v-menu.
<v-menu>
<template v-slot:activator="{ on, attrs }">
<div v-bind="attrs" v-on="on">
v-menu slot activator:
<br />
attrs == {{ JSON.stringify(attrs) }}
<br />
on == {{ '{' + Object.keys(on).map(k => k + " : " + on[k]).join(',') + '}' }}
</div>
</template>
</v-menu>
Result:
v-menu slot activator:
attrs == {"role":"button","aria-haspopup":true,"aria-expanded":"false"}
on == {
click:function (e) {if (_this.openOnClick) {onClick && onClick(e);}_this.absoluteX = e.clientX;_this.absoluteY = e.clientY;},
keydown:function () { [native code] }
}
Explanation:
<div v-bind="attrs" v-on="on"> equals
<div
v-bind="{role:'button',aria-haspopup:true,aria-expanded:'false'}"
v-on="{click:function (e) {/*implement by v-menu*/},keydown:function () {/*implement by v-menu*/}}"
>
Starting in vue 2.4.0+, v-on also supports binding to an object of event/listener pairs without an argument. Note when using the object syntax, it does not support any modifiers.
Example:
<!-- v-on's object syntax (vue 2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
About <template> tags in Internet Explorer throws an error :
as vuetify docs say:
Template caveat
Due to Internet Explorer’s limited support for <template> tags, you must send fully compiled dom elements to the browser. This can be done by either building your Vue code in advance or by creating helper components to replace the dom elements. For instance, if sent directly to IE, this will fail:
<!-- Vue Component -->
<template v-slot:items="props">
<td>{‌{ props.item.name }‌}</td>
</template>

Using Vuetify tooltip (v-tooltip) component with an external activator (i.e. not wrapped)

I understand how to use Vuetify's v-tooltip with the tooltip wrapping the component. However, I'm not quite sure how to have the activator button outside.
e.g. I have this (non-working code):
<v-tooltip bottom
:activator="$refs.filterBtn"
>
Filter displayed items
</v-tooltip>
<v-btn
ref="filterBtn"
icon
#click="isFilter = !isFilter"
>
<v-icon>fa-filter</v-icon>
</v-btn>
I also tried using prop activator without the v-bind:, same result
Idea: I want the button to be placed separately from tooltip in order to run unit tests. When testing, shallowMount strips anything inside <v-tooltip> so I can't test the button. The problem is I don't know how to make tooltip show up on hover (just like it does when wrapped), I do not want to trigger it with #click.
EDIT: here's codepen
How about using the v-hover UI Component. Wrap it around your button. Bind a boolean variable to the v-hover using v-model, call it buttonHovering. Then bind a boolean variable to the v-tooltip using v-model, call it showToolTip. Then use a watcher to toggle showToolTip true and false based on the value of buttonHovering. Or you can make showToolTip a computed property that always returns the value of buttonHovering. Lastly, bind the disabled attribute of the v-tooltip to the !buttonHovering property to ensure that the tooltip only displays when hovering over the button and not the tooltip's activator.
new Vue({
el: '#app',
data () {
return {
buttonHovering: false,
showToolTip: false
}
},
watch: {
buttonHovering (newVal) {
this.showToolTip = newVal
}
}
})
<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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.16/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-card>
<v-card-title>
<v-hover v-model="buttonHovering">
<v-btn large>
Hello
</v-btn>
</v-hover>
<v-spacer></v-spacer>
<v-tooltip left v-model="showToolTip" :disabled="!buttonHovering">
<span>Hi from over here!</span>
</v-tooltip>
</v-card-title>
</v-card>
</v-app>
</div>
Try this:
<v-tooltip bottom
v-model="filterBtnTTip"
>
Filter displayed items
</v-tooltip>
<v-btn
icon
#click="isFilter = !isFilter"
#mouseover="filterBtnTTip = true"
#mouseleave="filterBtnTTip = false"
>
<v-icon>fa-filter</v-icon>
</v-btn>
...
data () {
return {
...
filterBtnTTip: false
}
}
<v-app>
<v-tooltip bottom>
<template v-slot:activator="{ on }">
<v-btn
v-on="on"
>
Your button
</v-btn>
</template>
<span> Your hover message </span>
</v-tooltip>
</v-app>
In my case, it was necessary to place the tooltip together with the button in one component (a custom button with a tooltip function). Therefore, I used a prop activator and added the tooltip itself directly to the button:
<template>
<v-btn
:id="id"
>
<span>
<slot></slot>
<v-tooltip
v-if="tooltipText"
:activator="`#${id}`"
top
>
{{ tooltipText }}
</v-tooltip>
</span>
</v-btn>
</template>
<script>
let id = 0;
export default {
data() {
return {
id: `custom-button-${id++}`, //This is how we get a unique id for each created button
}
}
}
</script>
According to the same scheme, you can place v-tooltip anywhere, the main thing is that at the time of mounting the "activator" already exists.