vuetifyjs v-list. If click to item second time item not active. v-model="undefined" - vuetify.js

I use standard v-list component of vuetifyjs. For create menu and show list of items.
But if I click second time the active element is hide. And I don't see active element of menu. It is bad for my menu. Link for example v-list
If use pug template below
v-list(dense)
v-list-item-group(color="success" v-model="selectedItem")
v-list-item(v-for="(gallery, key) in galleries" :key="key")
v-list-item-content
v-list-item-title(v-text="gallery")

I have solution. It needs watch variable and set force index by key of clicked item. If we have 'undefined' set force data method
Run examle solution Solution
Code below:
<template>
<div id="app">
<v-app id="inspire">
<v-card max-width="300" >
<v-list dense>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
>
<v-list-item-content #click="setItem(i)">
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-app>
</div>
</template>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
index: 0,
selectedItem: 0,
items: [
{ text: 'Real-Time'},
{ text: 'Audience' },
{ text: 'Conversions'},
],
}),
watch: {
selectedItem() {
if (typeof this.selectedItem === 'undefined') {
setTimeout(() => {
this.selectedItem = this.index;
}, 500);
}
},
},
methods: {
setItem(index) {
this.index = index;
},
},
})

Another solution would be to disable the v-list-item that has the same index as the selectedItem
<template>
<div id="app">
<v-app id="inspire">
<v-card max-width="300" >
<v-list dense>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
:disabled="i == selectedItem"
>
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-app>
</div>
</template>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selectedItem: 0,
items: [
{ text: 'Real-Time'},
{ text: 'Audience' },
{ text: 'Conversions'},
],
}),
})

Related

Is there any option to have one common vuetify rule for two text fields

I have to fill either text-field-1 or text-field-2 or both In a form and I should be able to proceed with save
I tried having a flag variable but I that results in clicking on text-field-1 or text-field-2 to activate the save button
You can use computed property according to your need here is the basic example
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
valid: true,
firstName: '',
lastName: '',
nameRule: [
v => !!v || 'Name is required',
v => (v && v.length <= 10) || 'Name must be less than 10 characters',
],
}),
computed: {
nameRules () {
if(this.firstName.length == 0 && this.lastName.length == 0){
return this.nameRule;
}
if(this.firstName.length > 0 || this.lastName.length > 0 ){
return [];
}
},
},
methods: {
validate () {
this.$refs.form.validate()
},
reset () {
this.$refs.form.reset()
},
resetValidation () {
this.$refs.form.resetValidation()
},
},
})
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app id="inspire">
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-text-field
v-model="firstName"
:counter="10"
:rules="nameRules"
label="First name"
></v-text-field>
<v-text-field
v-model="lastName"
:counter="10"
:rules="nameRules"
label="Last name"
></v-text-field>
<v-btn
:disabled="!valid"
color="success"
class="mr-4"
#click="validate"
>
Validate
</v-btn>
<v-btn
color="error"
class="mr-4"
#click="reset"
>
Reset Form
</v-btn>
<v-btn
color="warning"
#click="resetValidation"
>
Reset Validation
</v-btn>
</v-form>
</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>

Vuetify autocomplete with solo selection and toggle "Select All"

Vuetify newbie here.
My goal is to have a v-autocomplete with solo selection of elements, and a toggle for "Select All".
My approach was by modifying 'v-select__slot' and to change its contents, is this the best approach?
Question: I was not succesfull cleaning previous selections, what is the best way to clean previous selected elements?
Question: After the "Select All" change event, how to close the drop-down?
<div id="app">
<v-app id="inspire">
<v-container fluid>
<v-autocomplete v-model="selectedFruits" :items="fruits" solo ref="selector" :readonly="readonly" #change="changed">
<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 />
</template>
</v-autocomplete>
</v-container>
<br/>
{{selectedFruits}}
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
fruits: [
'Apples',
'...',
'Zucchini',
],
selectedFruits: [],
readonly: false,
}),
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-checkbox-blank-outline'
return 'mdi-checkbox-blank-outline'
},
},
methods: {
changed () {
var el = this.$refs.selector.$el.getElementsByClassName('v-select__slot')[0]
// var add_me = document.createTextNode(this.selectedFruits);
// el.appendChild(add_me, null);
},
toggle () {
this.$nextTick(() => {
var backup = this.$refs.selector.$el.getElementsByTagName('input')[0]
var text = ""
if (this.likesAllFruit) {
this.selectedFruits = []
text = "No fruits"
} else {
this.selectedFruits = this.fruits.slice()
text = "All fruits"
}
var el = this.$refs.selector.$el.getElementsByClassName('v-select__slot')[0]
el.textContent = ''
var add_me = document.createTextNode(text);
el.appendChild(add_me, null);
el.appendChild(backup, null);
})
},
},
created() {
}
})
Example:
https://codepen.io/dotmindlabs/pen/JjXbQyo?editors=1010
TIA
For displaying the toggle selection used a v-slot:label.
Regarding the closing of the drop-down after clicking toggle, it can be achieved with :menu-props="{closeOnClick: true,closeOnContentClick:true}"
Example HTML:
<div id="app">
<v-app id="inspire">
<v-container fluid>
<v-autocomplete
v-model="selectedFruits"
:items="fruits"
:menu-props="{ closeOnClick: true, closeOnContentClick: true }"
>
<template v-slot:label>
<div class="indigo--text">{{ label }}</div>
</template>
<template v-slot:selection="{ item, index }">
<span>{{ item }} </span>
</template>
<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>
</v-autocomplete>
</v-container>
{{selectedFruits}}
</v-app>
</div>
Javascript
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
fruits: [
'Apples',
'....',
'Zucchini',
],
selectedFruits: [],
}),
computed: {
likesAllFruit () {
return this.selectedFruits.length === this.fruits.length
},
icon () {
if (this.likesAllFruit) return 'mdi-close-box'
if (this.likesSomeFruit) return 'mdi-minus-box'
return 'mdi-checkbox-blank-outline'
},
label (){
if (typeof this.selectedFruits === "string") { return ""}
return this.selectedFruits.length > 0 ? "All Fruit" : ""
}
},
methods: {
toggle () {
if (this.likesAllFruit) {
this.selectedFruits = []
} else {
this.selectedFruits = this.fruits.slice()
}
},
},
created(){
this.selectedFruits = this.fruits.slice()
}
})
Codepen:
https://codepen.io/dotmindlabs/pen/RwapobY?editors=1011

Vuetify.js: v-dialog is not working when i close and open again

I using a Vuetify.js for my project
when I used v-dialog component I got a problem that closing the v-dialog and open again.
here is my code.
<div #click="dialog=true">click here</div>
<v-dialog v-model="dialog">
<alert-popup />
</v-dialog>
data() {
return {
dialog : false
}
this is work when I open dialog first time but when I open again only can see the opacity black page
I don`t know which part is wrong. please reply this question. thanks
You didn't provide any code to reproduce the behavior. But I guess the you need provide <v-card> component inside the dialog component. If you don't provide that it just shows the opacity issue.Putting a v-card will eliminate the opacity issue.
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<v-dialog v-model="dialog" max-width="300px">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="dialog = false">Close</v-btn>
<v-btn x-small color="blue darken-1" text #click="dialog = false">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</v-container>
</v-app>
</div>
Here is the working code where there is a v-card component inside v-dialog.
I also see that you've used a custom component called alert-popup inside the v-dialog.If that is the case it needs to be refactored a bit to achieve what you're looking for.
Creating a separate component which you are already doing except <v-dialog>.
Emitting a close event from <alert-popup> component so that open/close and clicking open won't cause issue.
HTML:
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<alert-popup :dialog="dialog" #close="closeMyDialog" />
</v-row>
</v-container>
</v-app>
</div>
Javascript:
let AlertPopup = Vue.component("AlertPopup", {
props: {
dialog: {
type: Boolean,
default: false
}
},
data: () => ({
open: false
}),
methods: {
close() {
this.open = false;
this.$emit("close");
}
},
watch: {
dialog(value) {
this.open = value;
}
},
created() {
this.open = this.dialog;
},
template: `
<v-dialog v-model="open" max-width="300px">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="close">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
`
});
new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
AlertPopup
},
data: (vm) => ({
dialog: false
}),
computed: {},
methods: {
closeMyDialog() {
this.dialog = false;
},
openDialog() {
this.dialog = true;
}
}
});
If look at the AlertPopup component, it just take a prop named dialog and that we can pass from the main component, when button is clicked, it sets to true, which triggers the AlertPopup component.
NOTE:
If we don't emit the close event from the AlertPopup component, then the modal dialog will not open on the second time onwards. The reason for this behavior is the fact that, when the button is clicked from the parent component,which set the dialog to true and pass it to the AlertPopup and whatever things happens inside the AlertPopup remains inside that component and dialog property of parent component never changes.
Here is a working example of how the modal component are triggered from parent component. If we remove the close event, it will not open the modal second time.
Update: It turns out we are unnecessary complicating things, we don't
even need to emit an event, we can pass in the close function as a
prop and react to that, whenever the close button is clicked, it would
call the closeAlert().
Thanks to #Pratik149 for pointing out the bug, I have attached the click outside event handler.
Here is the completely re-factored code.
<div id="app">
<v-app id="inspire">
<v-container>
<v-row justify="start">
<v-btn #click="openDialog">Open</v-btn>
<alert-popup :dialog="dialog" #close="closeMyDialog" :close="closeMyDialog" />
</v-row>
</v-container>
</v-app>
</div>
And the component logic is updated as follow
let AlertPopup = Vue.component("AlertPopup", {
props: {
dialog: {
type: Boolean,
default: false
},
close: {
type: Function,
default: () => {}
}
},
data: () => ({
open: false
}),
methods: {
closeAlert() {
this.close();
}
},
template: `
<v-dialog v-model="dialog" max-width="300px" #click:outside="closeAlert">
<v-card>
<v-card-title>My Dialog</v-card-title>
<v-divider></v-divider>
<v-card-text>
This is text for dialog
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn x-small color="blue darken-1" text #click="closeAlert">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
`
});
new Vue({
el: "#app",
vuetify: new Vuetify(),
components: {
AlertPopup
},
data: (vm) => ({
dialog: false
}),
computed: {},
methods: {
closeMyDialog() {
debugger;
this.dialog = false;
},
openDialog() {
this.dialog = true;
}
}
});
Finally here is the updated codepen

How to make Vutify VMenu right aligned?

I need to make right aligned v-menu with "attach" option.
Template:
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div class="place"></div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach=".place" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
I expect right aligned menu in ".place" element. But the menu is left aligned. Also top border of menu is under the ".place" element. It is strange. How can I fix it?
Demo
It is possible to align the content of v-menu to right border of the page
Here is the working codepen: https://codepen.io/chansv/pen/OJyjWmX
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div>
<div id="attachMenu" style="float: right;position: relative;width: 134px;left: 27px;"></div>
</div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach="#attachMenu" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
You almost got the right solution, the position with the right and left prop is inverted
So this is what you need to do:
<v-menu attach=".place" v-model="show" left>
I am using Vuetify 3 and I had the same problem. I solved it by using
<v-menu location="bottom end">
See :
https://next.vuetifyjs.com/en/components/menus/#location
https://next.vuetifyjs.com/en/components/overlays/#location-strategies

How to wire up external data in Vuetify datatables

I have just started with Vue and found Vuetify ( and very impressed ) . I'm a bit of a newbie with node.js as well but some experience.
I am trying to find some examples on loading data from external API's into the vuetify datagrid - CRUD type stuff, reasonably large amounts of data paginated. The documentation in Vuetify is a little lacking in this regard. Should I be using Vuex?
If you want to call external API using REST, you'll need to use axios, which is a NPM package allowing you to make GET, POST and all that kind.
Let's use this online working API for our example. First, you need to get your data by calling this API. A good tutorial on Internet will show you more details, but let's use this code.
this.todos = axios.get('https://jsonplaceholder.typicode.com/todos/')
.then(response => { this.todos = response.data })
.catch(error => { console.log(error)});
Then you just have to use the datatable like in the documentation. Here is a CodePen to help you see, briefly, how I made the API call and then displayed it. It all comes from the official documentation, just modified to call a REST API. I'll put the code also here, in order to save it for future readers too.
<div id="app">
<v-app id="inspire">
<div>
<v-toolbar flat color="white">
<v-toolbar-title>Todos CRUD</v-toolbar-title>
<v-divider
class="mx-2"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<v-btn slot="activator" color="primary" dark class="mb-2">New Item</v-btn>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.userId" label="User ID"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.id" label="ID"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field v-model="editedItem.title" label="Title"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-checkbox v-model="editedItem.completed" label="Completed?"></v-checkbox>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" flat #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
<v-data-table
:headers="headers"
:items="todos"
class="elevation-1"
>
<template slot="items" slot-scope="props">
<td class="text-xs-right">{{ props.item.userId }}</td>
<td class="text-xs-right">{{ props.item.id }}</td>
<td class="text-xs-right">{{ props.item.title }}</td>
<td class="text-xs-right">{{ props.item.completed }}</td>
<td class="justify-center layout px-0">
<v-icon
small
class="mr-2"
#click="editItem(props.item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(props.item)"
>
delete
</v-icon>
</td>
</template>
<template slot="no-data">
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</div>
</v-app>
</div>
And then the associated JS.
new Vue({
el: '#app',
data: () => ({
dialog: false,
headers: [
{
text: 'User ID',
align: 'left',
sortable: false,
value: 'userId',
width: '10'
},
{ text: 'ID', value: 'id', width: '10' },
{ text: 'Title', value: 'title' },
{ text: 'Completed', value: 'completed' }
],
todos: [],
editedIndex: -1,
editedItem: {
userId: 0,
id: 0,
title: '',
completed: false
},
defaultItem: {
userId: 0,
id: 0,
title: '',
completed: false
}
}),
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
}
},
watch: {
dialog (val) {
val || this.close()
}
},
created () {
this.initialize()
},
methods: {
initialize () {
this.todos = axios.get('https://jsonplaceholder.typicode.com/todos/')
.then(response => { this.todos = response.data })
.catch(error => { console.log(error)});
},
editItem (item) {
this.editedIndex = this.todos.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
const index = this.todos.indexOf(item)
confirm('Are you sure you want to delete this item?') && this.todos.splice(index, 1)
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.todos[this.editedIndex], this.editedItem)
} else {
this.todos.push(this.editedItem)
}
this.close()
}
}
})

Resources