Vuetify date picker add tooltip in disabled days - vue.js

How to add tootlip in the disabled days when using the Vuetify's date picker
I cannot seem to find it in documentation, is this possible somehow?

I don't think that you can do this in Vuetify because there are no slots provided to customize the dates' display. I can think of one solution using pure JS. The strategy would be to-
Find all disabled date elements and assign a title attribute (HTML tooltip) to it.
Important 1- The title attribute will not work with the disabled elements, so, you need to use pointer-events: auto on that disabled element. (Reference from this answer.)
Important 2- Above CSS will add a pointer cursor on the disabled button but it won't trigger any click event.
Here is a working demo. Try hovering on disabled dates and you should see a tooltip. Hope this helps.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
date: '2023-02-16',
}),
mounted() {
let disabled_dates = document.querySelectorAll('.v-date-picker-table button');
disabled_dates.forEach(date => {
if(date.disabled) {
date.title = "tooltip";
}
})
},
methods: {
allowedDates: val => parseInt(val.split('-')[2], 10) % 2 === 0,
},
})
.v-btn--disabled {
pointer-events: auto !important;
}
<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>
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<div id="app">
<v-app id="inspire">
<v-row justify="center">
<v-date-picker
v-model="date"
:allowed-dates="allowedDates"
class="mt-4"
min="2023-02-01"
max="2023-02-28"
></v-date-picker>
</v-row>
</v-app>
</div>

Related

How to make vuetify v-autocomplete behave like a search bar?

I want to make vuetify v-autocomplete behave like google search bar
Do not open dropdown when focusing
Only show dropdown when user press Enter key in searchbar
In https://vuetifyjs.com/en/api/v-autocomplete/, I did not see any function could control dropdown is shown or not
you can bind items to an empty array like searchResults and have your actual items in another array, then you can use #update:search-input event to update the searchText property and then call a method on #keydown.enter to populate the searchResults array.
also use hide-no-data prop to prevent dropdown from opening when there is no data in searchResults array.
side-note: you can pass empty string to append-icon prop to remove the drop down icon from the input
try the demo below by typing 'zz' in the input and pressing enter for example:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
items: ['foo', 'bar', 'fizz', 'buzz'],
searchResults: [],
searchText: '',
msg: '',
}),
methods: {
search() {
this.searchResults = this.items.filter((x) => this.searchText && x.includes(this.searchText));
this.msg = `${this.searchResults.length} result(s) found`;
},
},
});
<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">
<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>
<div id="app">
<v-app>
<v-container>
<v-autocomplete ref="searchBar" :items="searchResults" outlined dense hide-no-data label="type and press Enter to search" #update:search-input="searchText = $event" #keydown.enter="search" :messages="msg" append-icon=""></v-autocomplete>
</v-container>
</v-app>
</div>

vue/nuxt-How can I ensure the value from template is obtained first and passed to the child element?

I'm trying to get width value from Template and
pass the value to child component in vue/nuxt.
However, it doesn't work out. I assume that is because width from template is obtained later stage in life cycle hook.
How could I ensure the width value is obtained first and the child element gets the information?
Parent
<template>
<v-col cols="12" sm="12" md="4" ref="demandContainer">
<div class="chart-container p-4 dark-background">
<div class="font-weight-bold">Demand</div>
<area-chart
v-if="trendsData.length > 0"
:data="trendsData"
:baseWidth="width"
>
</area-chart>
</div>
</v-col>
</template>
<script>
export default {
updated() {
this.width = this.$refs.demandContainer.$el.clientWidth
},
}
</script>
Child
<svg ref="svg-area" :width="baseWidth" :height="baseHeight">
</svg>
Error Message
for the $refs to work you should make sure the template is fully rendered to the DOM and to do this you can have this code in the mounted hook:
mounted() {
this.$nextTick(() => { this.width = this.$refs.demandContainer.$el.clientWidth; });
}
according to vue doc for $nextTick:
vm.$nextTick( [callback] ): Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update. This is the same as the global Vue.nextTick, except that the callback’s this context is automatically bound to the instance calling this method.
so with the help of $nextTick you can make sure the element is rendered to DOM to use the ref defined on the element.
Edit
actually it is $el thats undefined. this.$refs.demandContainer returns the element itself and to get the width you can just have this.$refs.demandContainer.clientWidth.
check the example below:
new Vue({
el: '#app',
vuetify: new Vuetify(),
mounted() {
console.log(this.$refs.demandContainer.clientWidth);
},
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<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>
<div id="app">
<v-app>
<v-main>
<v-container>
<v-col cols="12" sm="12" md="4" ref="demandContainer">
<div class="chart-container p-4 dark-background">
<div class="font-weight-bold">Demand</div>
</div>
</v-col>
</v-container>
</v-main>
</v-app>
</div>
Refs are available only once the component is mounted to the actual DOM.
In created, it is created somewhere but not yet appended to your page.
The issue as explained here is that you may have some issues to pass it to the child component since child's mounted lifecycle hook will happen before the parent's one.
I've tried some super hacky things
async mounted() {
await this.$nextTick()
this.$watch(
() => {
return this.$refs.test.$el.clientWidth
},
(val) => {
console.log('nice', val)
},
)
},
It did not work because clientWidth would be available at an even later point (lifecycle-wise). So, the last solution is probably to use some even more ugly setTimeout.
Or you could fix the issue with CSS, it will be:
faster
less heavy on the webpage
more adapted
less error prone
Because SVG are meant to be super responsive and easy to work with.
Can you show us what you want to do with your SVG?
TLDR: don't use JavaScript to solve a responsive issue here. Having to work with parent + child + mounted lifecycles is not a good idea usually.

Vuetify using font-awesome icons in existing components

I'm trying to follow the instructions on the vuetify docs for integrating font-awesome pro.
Vue.use(Vuetify, {
iconfont: 'fa',
icons: {
'dropdown': 'fal fa-arrow-down',
}
})
Existing components don't pick up these settings. the v-icon components that I don't create myself are still assigning the material-icons class. For example, the icon nested in <v-select> shows up as:
<i class="v-icon material-icons theme--light">arrow_drop_down</i>
I expected that passing a new value under icons.dropdown would change the icon in the <v-select>. Is that not how it's supposed to work?
This might be because the Vuetify plugin has already been installed. Calling Vue.use a second time won't do anything.
If you're calling it twice yourself then the solution is simple. However, it might not be you that's calling it the first time. The example below is using a CDN build of Vuetify and it automatically installs itself without any configuration options.
I haven't actually included FontAwesome but I've used some CSS to show a red square in place of the arrow to show that it is having an effect.
new Vue({
el: '#app',
data () {
return {value: 'Red'}
}
})
.fa-arrow-down {
background: red;
height: 8px;
width: 8px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify#1.5.14/dist/vuetify.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script>
(function() {
const vueUse = Vue.use
Vue.use = function (vuetify) {
Vue.use = vueUse
Vue.use(vuetify, {
iconfont: 'fa',
icons: {
'dropdown': 'fal fa-arrow-down',
}
})
}
})()
</script>
<script src="https://unpkg.com/vuetify#1.5.14/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-select v-model="value" :items="['Red', 'Green', 'Blue']"></v-select>
</v-app>
</div>
You can see the automatic install on lines 19-21 of the source:
https://github.com/vuetifyjs/vuetify/blob/v1.5.14/packages/vuetify/src/index.ts
In the example above I worked around the problem by hooking into Vue.use just before I included Vuetify. Yes, it's a collosal hack, but it allowed me to insert the relevant config object.
An alternative would be to poke in the icons individually by modifying $vuetify directly.
Vue.prototype.$vuetify.icons.dropdown = 'fal fa-arrow-down'
You'd have to do this for each individual icon though, I don't believe there's an equivalent to passing the iconfont to set them all.
Vue.prototype.$vuetify.icons.dropdown = 'fal fa-arrow-down'
new Vue({
el: '#app',
data () {
return {value: 'Red'}
}
})
.fa-arrow-down {
background: red;
height: 8px;
width: 8px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify#1.5.14/dist/vuetify.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#1.5.14/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-select v-model="value" :items="['Red', 'Green', 'Blue']"></v-select>
</v-app>
</div>
If you're not sure whether Vuetify has already been installed there are various ways to find out. Probably the simplest is to check whether Vue.prototype.$vuetify already exists. Alternatively you could put in a debugger statement just before you call Vue.use and step into the code to see what happens. If Vuetify is already installed you'll see it bail out pretty quickly.

How to add a listener to a vue-multiselect after the page has loaded?

I need to add a listener to a vue-multiselect, like this (but this does not work):
$('.multiselect').change(console.log("multiselect changed"));
It must be added in the updated hook.
I've tried a lot of things, but because of the nature of vue-multiselect, conventional jQuery doesn't work ie, you can't listen for a change on the multiselect__input because its value doesn't actually change.
There's a .multiselect__single element that is added\removed depending on the state, but I'd rather not listen\test for html changes.
The vue-multiselect events don't include change, but you're probably looking for input (emitted after the value changes) or searchChanged (emitted after the search query changes).
The idiomatic way to listen to an event is to use this syntax: #EVENTNAME="METHOD". Example:
<multiselect #input="onSelection">
new Vue({
el: '#app',
components: {
Multiselect: window.VueMultiselect.default
},
data() {
return {
value: '',
options: ['list', 'of', 'options']
};
},
methods: {
onSelection(newValue) {
console.log({newValue})
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<multiselect :options="options" v-model="value" #input="onSelection"></multiselect>
<div>value: {{value}}</div>
</div>

Using new Date() with v-date-picker doesn't work

I'm trying to follow this video using Vue and Vuetify to apply the current date with v-model to the date picker component v-date-picker using the data value date that's initially being set with new Date().
This is a simplified structure of my project:
JS
new Vue({
el:"#app",
data: {
date: new Date(),
time: new Date()
}
})
Template
<div id="app">
<v-date-picker v-model="date"></v-date-picker>
{{ date }}
<v-time-picker v-model="time"></v-time-picker>
</div>
And here's a CodePen. Unfortunately I couldn't get the Vuetify CSS to work with the CodePen, but if you open up the console, you'll see that I get errors in the v-date-picker when trying to use new Date() with the v-model directive. Also the date picker isn't rendering. The v-time-picker however works fine.
On my local setup I've created a Vue project with the vue-cli. Here's the error I'm getting there:
[Vue warn]: Error in created hook: "TypeError: dateString.split is not
a function"
found in
--->
at src/components/Meetup/CreateMeetup.vue
at src/App.vue
I'm doing exactly as in the tutorial I'm following, so I don't know if this is a bug with the latest version of either Vue or Vuetify? Or am I missing something?
Obviously (from the error message you're getting) v-datepicker expects to be bound to a String. You might want to try
data: {
date: new Date().toJSON(),
time: new Date().toJSON()
}
https://codepen.io/connexo/pen/ypWxLv
Also see Vuetify API docs (which explicitly states it expects v-model to be of type String):
v-model String null Controls the displayed date. Must use ISO 8601 format.
Instead, use the value attribute in order to overcome the binding.
example
data: {
date: new Date().toISOString().substr(0, 10)
}
<v-text-field slot="activator" :value="dataValue.datePass" label="Date" append-icon="event" readonly style="font-size:14px"></v-text-field>
<v-date-picker v-model="dataValue.datePass"></v-date-picker>
In my case, I needed the date to be stored as a Date object instead of a String. So instead of using v-model in the date-picker, I handled this using #input and :value.
new Vue({
el: '#app',
data() {
return {
isActive: false,
theDate: new Date()
}
},
computed: {
formattedDate() {
return this.theDate ? moment(this.theDate).format('MM/DD/YYYY') : undefined; // Custom format
},
datePickerFormattedDate() {
return this.theDate ? moment(this.theDate).format('YYYY-MM-DD') : undefined; // Date picker objects needs date in this particular format
}
},
methods: {
inputHandler(date) {
if (typeof date === 'string')
date = moment(date).toDate();
this.isActive = false;
this.theDate = date;
}
}
})
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/locale/en-gb.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.3.12/vuetify.min.js">
</script>
<script src="https://cdn.jsdelivr.net/momentjs/2.10.6/moment-with-locales.min.js">
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/material-design-icons/3.0.1/iconfont/material-icons.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons">
<link rel="stylesheet" href="https://unpkg.com/vuetify/dist/vuetify.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
<v-app id="inspire">
<v-content>
<v-container>
<v-layout row wrap>
<v-flex pb-4 xs12>
Stored date: {{ theDate }}
</v-flex>
<v-flex xs12>
<v-text-field :readonly="true" :value="formattedDate" label="I want to enter dates here"></v-text-field>
<v-menu :close-on-content-click="true" v-model="isActive" :nudge-right="40" lazy transition="scale-transition" offset-y full-width min-width="290px">
<v-icon slot="activator">event</v-icon>
<v-date-picker :value="datePickerFormattedDate" #input="inputHandler"></v-date-picker>
</v-menu>
</v-flex>
</v-layout>
</v-container>
</v-content>
<v-footer></v-footer>
</v-app>
</div>
</body>
</html>
You can use a computed property as a "shim" for v-model. The computed property contains all the logic for the type coercion and everything else is business as usual.
JS
new Vue({
el:"#app",
data: {
date: new Date()
},
computed: {
// "shim" for v-date-picker
sdate: {
get() {
return this.date?.toISOString()
},
set(val) {
this.date = new Date(val)
}
}
}
})
Template
<div id="app">
<v-date-picker v-model="sdate"></v-date-picker>
{{ date }}
</div>
Vuetify date picker need date in that particular format ('YYYY-MM-DD'). This could be solved by replacing this with:-
new Vue({
el:"#app",
data: {
date: moment(new Date()).format('YYYY-MM-DD'),
time: new Date()
}
})
From the official examples, it looks like you have to use like this
data: {
date: new Date().toISOString().substr(0, 10),
time: new Date().getHours() + ':' + new Date().getMinutes(),
}
Example -
https://github.com/vuetifyjs/vuetifyjs.com/blob/master/src/examples/date-pickers/dateDialogAndMenu.vue