Dialog inside autocomplete - vuetify - vue.js

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.

Related

How to test V-BTN inside V-MENU?

I'm trying to write test for my component and in this component i have a <V-MENU> and when it's activated, I'll get a form inside that component and in this form there's a <V-BTN>. so i managed to reach to buttons or items outside this menu but when i try to find my button inside menu, test will fail.
Basic Vue Component :
<v-btn
data-testid="working-button"
#click="workingbutton()"
>
Working Button
</v-btn>
<v-menu
v-model="menu"
data-testid="isMenu"
:close-on-content-click="false"
:nudge-width="200"
offset-x
>
<v-btn
data-testid="btn-one"
#click="doSomething"
>
BTN 1
</v-btn>
<v-card data-testid="card-test">
<v-text-field
v-model="country"
outlined
label="Email"/>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
data-testid="btn-two"
#click="doSomething"
>
{{ login ? 'Login' : 'Register' }}
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
Test Unit :
const wrapper = mount(TestComponent, {
store,
localVue,
vuetify
})
// Passed
expect(wrapper.vm.menu).toBe(false);
expect(wrapper.find('[data-testid="working-button"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="isMenu"]').exists()).toBe(true);
const menuButton = wrapper.find('[data-testid="working-button"]')
menuButton.vm.$emit('click')
expect(wrapper.vm.menu).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="btn-one"]').exists()).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="card-test"]').exists()).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="btn-two"]').exists()).toBe(true);
Above codes are a basic example which I've tried to make it short in order to don't waste your time in reading it.
So as you can see in top I've triggered Working button to set menu to true, then tried to find the button, and the reason that I've did this was that, i thought maybe menu should be true, but it wasn't and it's still fail.
Like most components that create popups or overlays, the content of Vuetify's v-menu isn't rendered within the component itself, but at the point specified in its attach property, by default the root of your Vue app.
You will have to look outside your wrapper for the rendered menu. This should do it, assuming you have createWrapper imported from vue-test-utils:
const menu = createWrapper(document.body).find('.v-menu__content');
(You could also pass the specific element that is your app root instead of document.body to be more precise.) Then you would look for everything within the menu on the menu wrapper instead of the wrapper wrapper.

Vuetify - Close menu dialog with out v-dialog (using activator)

I have a menu dialog inside a table column to update a corresponding value.
The menu dialog when opened shows a card with a select box and update button.
The menu is activated using v-on which works exactly as intended but I have no way of closing the menu.
Since it's inside a table, using a v-model and changing the value causes multiple menus/select boxes to be shown to be opened.
<v-menu :close-on-content-click="false" :close-on-click="false">
<template v-slot:activator="{ on: { click } }">
<v-chip #click="click" small>{{item[header.value]}}</v-chip>
</template>
<v-card>
<!-- <v-card-title class="subtitle-2 pb-0 pt-1">Update Status</v-card-title> -->
<v-select items="Status" class="px-4 pb-2" hide-details label="Status"></v-select>
<v-card-actions>
<!-- <v-spacer></v-spacer> -->
<v-btn color="primary" #click="" text>Update</v-btn>
<v-btn color="warning" text>Cancel</v-btn>
</v-card-actions>
</v-card>
</v-menu>
How can I close the menu without using a v-model?
It was an easy solution...
I added a v-model to the menu dialog and created an object in data display: {}. The v-model on the menu dialog was v-model="display[item.id]" using the item id as an index of sorts and then I could just use a method to close it.
close(id) {
this.display[id] = false;
},
Done.

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
// ...
}
}
}

Vuetify VTooltip trigger only on activator click

I wanted to trigger Vuetify tooltip VTooltip only when the activator is clicked rather than hovered. I tried to bind it with a variable but still triggered on hover.
methods: {
doCopy(){
// copy logic
this.showCopied = true;
setTimeout(() => {
this.showCopied = false
}, 1000)
}
}
<VTooltip v-model="showCopied">
<template #activator="{ on }">
<VBtn v-on="on" #click="doCopy"> COPY </VBtn>
</template>
</VTooltip>
This is actually more complicated than I expected thanks to some bugs. You should be able to just do <v-tooltip :open-on-hover="false">, but a focus listener is still added which causes the click to close the tooltip immediately. Instead you need to bind the click and blur events separately, and add retain-focus-on-click to the button so it doesn't blur immediately.
Full solution:
<v-tooltip bottom :open-on-hover="false">
<template #activator="{ on }">
<v-btn #click="on.click" #blur="on.blur" retain-focus-on-click>Copy</v-btn>
</template>
<span>Copy</span>
</v-tooltip>
It turns out I have to disable the default event handler of the activator.
Simply removing default event object (on) binding solves the issue.
<VTooltip v-model="showCopied">
<template #activator={}>
<VBtn #click="doCopy"> COPY </VBtn>
</template>
</VTooltip>
[UPDATED] based on #Kael Watts-Deuchar answer
NB: the v-model biding is mandatory
In vuetify 2.6.1 you can do it like this now
<v-tooltip
open-on-click
:open-on-hover="false"
>
<template
v-slot:activator="{ on }"
>
<v-btn
v-on="on"
>
button with tooltip
</v-btn>
</template>
<span>tooltip message</span>
</v-tooltip>

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.