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
Related
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>
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'},
],
}),
})
Thought this would be straight forward but it isn't. With vuetify forms how do I bind a form to the enter button so that it's submit function is invoked with the enter button?
Figures I'd find a work around as soon as I posted the question. I found the answer here:
https://github.com/vuetifyjs/vuetify/issues/1545
Basically I had to add an event listener to the component to attach the enter key press to my authenticate method. Here is the component in questions:
<template>
<v-card>
<v-card-title>
<span class="headline">Login</span>
</v-card-title>
<v-form v-model="isValid">
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="username"
label="User Name"
prepend-icon="mdi-account circle"
:rules="userNameRequired"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-text-field
v-model="password"
label="Password"
:type="showPassword ? 'text' : 'password'"
prepend-icon="mdi-lock"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
#click:append="showPassword = !showPassword"
:rules="passwordRequired"
required
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close"> Close </v-btn>
<v-btn color="blue darken-1" text #click="authenticate" :disabled="!isValid">
Login
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
<script>
import { authenticationService } from "../services/authenticationService/authentication.service";
import User from "../models/user";
export default {
name: "LoginForm",
props: ["userForAuthentication"],
data: () => ({
username: "",
password: "",
user: {},
isValid: true,
showPassword: false,
userNameRequired: [(v) => !!v || "User Name is required"],
passwordRequired: [(v) => !!v || "Password is required"],
}),
methods: {
async authenticate() {
try {
const response = await authenticationService.authenticateUser(
this.$data.username,
this.$data.password
);
if (response.status === 200) {
this.$data.user.shallowClone(response.data.user);
await this.resetData();
this.$emit(
"user-logging-in-event",
this.$data.user,
response.data.token
);
this.$toasted.success(`${this.$data.user.fullName} is logged in.`, {
duration: 3000,
});
} else if (response.status === 400) {
this.$toasted.error("Username or Password is incorrect.", {
duration: 3000,
});
} else {
this.$toasted.error(
"An error occurred while trying to authenticate the user",
{
duration: 3000,
}
);
}
} catch (error) {
this.$toasted.error(error, {
duration: 3000,
});
}
},
close() {
this.$emit("user-logging-in-event", null, null);
},
async resetData() {
this.$data.username = "";
this.$data.password = "";
},
},
mounted() {
let self = this;
window.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
self.authenticate();
}
});
this.$data.user = new User();
this.$data.user.shallowClone(this.$props.userForAuthentication);
},
};
</script>
I have a Vuetify(v. 1.9.0) v-combobox with validation that at least one item should be selected from the menu. For some reason the validation is not triggered and there are no errors in the console.
<v-combobox
multiple
item-text="name"
item-value="id"
:items="items"
:rules="[rules.required]"
></v-combobox>
<script>
export default {
data: () => ({
rules: {
required: value =>
!!value || "Required."
}
})
}
</script>
What am I missing?
Try this example:
<template>
<v-app>
<v-content>
<v-form ref="form">
<div>
<v-combobox
multiple
item-text="name"
item-value="id"
:items="items"
:rules="[required]"
v-model="selected"
/>
selected = {{ selected }}
</div>
<div>
<v-btn #click="$refs.form.validate()">Validate</v-btn>
</div>
</v-form>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'app',
data: () => ({
selected: null,
items: [
{ id: 1, name: 'Option 1' },
{ id: 2, name: 'Option 2' },
{ id: 3, name: 'Option 3' },
],
}),
methods: {
required(value) {
if (value instanceof Array && value.length == 0) {
return 'Required.';
}
return !!value || 'Required.';
},
},
};
</script>
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()
}
}
})