Vuetify: How to add aria-labelledby to v-dialog? - vuejs2

I am trying to use aria-labelledby attribute on a Vuetify dialog, like this:
<v-dialog v-model="show" max-width="600px" aria-labelledby="testDialogTitle">
<span id="testDialogTitle">Test Dialog</span>
</v-dialog>
But the attribute is not added to the element with role="dialog", it is added to the element which references it from my main template. How can I add this attribute to my dialog with role="dialog"?
Thanks

Thanks to the hints in the comments, I was able to achieve this by doing:
<v-dialog v-model="show" max-width="600px" ref="dialog" aria-labelledby="testDialogTitle">
<span id="testDialogTitle">Test Dialog</span>
</v-dialog>
watch: {
show: {
immediate: true,
handler: function (newValue) {
if (newValue) {
this.$nextTick(() => {
const dialog = this.$refs?.dialog;
const content = dialog?.$refs?.content;
if (content) {
// Copy aria-labelledby from v-dialog to new rendered element.
content.setAttribute('aria-labelledby', dialog.$attrs['aria-labelledby']);
}
});
}
}
}
}

Related

beforeRouteLeave doesn't work imediately when using with modal and emit function

I have a Vue application with many child components. In my case, I have some parent-child components like this. The problem is that in some child components, I have a section to edit information. In case the user has entered some information and router to another page but has not saved it, a modal will be displayed to warn the user. I followed the instructions on beforeRouteLeave and it work well but I got a problem. When I click the Yes button from the modal, I'll emit a function #yes='confirm' to the parent component. In the confirm function, I'll set this.isConfirm = true. Then I check this variable inside beforeRouteLeave to confirm navigate. But in fact, when I press the Yes button in modal, the screen doesn't redirect immediately. I have to click one more time to redirect. Help me with this case
You can create a base component like the following one - and then inherit (extend) from it all your page/route-level components where you want to implement the functionality (warning about unsaved data):
<template>
<div />
</template>
<script>
import events, { PAGE_LEAVE } from '#/events';
export default
{
name: 'BasePageLeave',
beforeRouteLeave(to, from, next)
{
events.$emit(PAGE_LEAVE, to, from, next);
}
};
</script>
events.js is simply a global event bus.
Then, in your page-level component you will do something like this:
<template>
<div>
.... your template ....
<!-- Unsaved changes -->
<ConfirmPageLeave :value="modified" />
</div>
</template>
<script>
import BasePage from 'src/components/BasePageLeave';
import ConfirmPageLeave from 'src/components/dialogs/ConfirmPageLeave';
export default
{
name: 'MyRouteName',
components:
{
ConfirmPageLeave,
},
extends: BasePage,
data()
{
return {
modified: false,
myData:
{
... the data that you want to track and show a warning
}
};
}.
watch:
{
myData:
{
deep: true,
handler()
{
this.modified = true;
}
}
},
The ConfirmPageLeave component is the modal dialog which will be shown when the data is modified AND the user tries to navigate away:
<template>
<v-dialog v-model="showUnsavedWarning" persistent>
<v-card flat>
<v-card-title class="pa-2">
<v-spacer />
<v-btn icon #click="showUnsavedWarning = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="pt-2 pb-3 text-h6">
<div class="text-h4 pb-4">{{ $t('confirm_page_leave') }}</div>
<div>{{ $t('unsaved_changes') }}</div>
</v-card-text>
<v-card-actions class="justify-center px-3 pb-3">
<v-btn class="mr-4 px-4" outlined large tile #click="showUnsavedWarning = false">{{ $t('go_back') }}</v-btn>
<v-btn class="ml-4 px-4" large tile depressed color="error" #click="ignoreUnsaved">{{ $t('ignore_changes') }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import events, { PAGE_LEAVE } from '#/events';
export default
{
name: 'ConfirmPageLeave',
props:
{
value:
{
// whether the monitored data has been modified
type: Boolean,
default: false
}
},
data()
{
return {
showUnsavedWarning: false,
nextRoute: null,
};
},
watch:
{
showUnsavedWarning(newVal)
{
if (!newVal)
{
this.nextRoute = null;
}
},
},
created()
{
events.$on(PAGE_LEAVE, this.discard);
window.addEventListener('beforeunload', this.pageLeave);
},
beforeDestroy()
{
events.$off(PAGE_LEAVE, this.discard);
window.removeEventListener('beforeunload', this.pageLeave);
},
methods:
{
discard(to, from, next)
{
if (this.value)
{
this.nextRoute = next;
this.showUnsavedWarning = true;
}
else next();
},
pageLeave(e)
{
if (this.value)
{
const confirmationMsg = this.$t('leave_page');
(e || window.event).returnValue = confirmationMsg;
return confirmationMsg;
}
},
ignoreUnsaved()
{
this.showUnsavedWarning = false;
if (this.nextRoute) this.nextRoute();
},
}
};
</script>
<i18n>
{
"en": {
"confirm_page_leave": "Unsaved changes",
"unsaved_changes": "If you leave this page, any unsaved changes will be lost.",
"ignore_changes": "Leave page",
"go_back": "Cancel",
"leave_page": "You're leaving the page but there are unsaved changes.\nPress OK to ignore changes and leave the page or CANCEL to stay on the page."
}
}
</i18n>

Vue trigger click event after v-for has create a new dom

I use v-for to generate task tabs.
Tasks can be created by users, and after users have created a new task I want the UI changing to the newly created tab automatically.
i.e. After a new tab has been added to the dom tree, itself click event will be triggered and the callback function activateTask will execute.
<template>
<v-container>
<v-btn #click="createNewTask"></v-btn>
<v-tabs>
<v-tab v-for="task in tasks" :key="task.uuid" #click="activateTask">
{{ task.name }}
</v-tab>
</v-tabs>
</v-container>
</template>
<script>
export default {
data: {
tasks: [],
},
method: {
createNewTask() {
this.tasks.push({
uuid: util.generateUUID(),
name: "name",
});
},
},
};
</script>
You can bind v-tabs with v-model to control active-tab.
<template>
<v-container>
<v-btn #click="createNewTask"></v-btn>
<v-tabs v-model="currentTab">
<v-tab v-for="task in tasks" :key="task.uuid" #click="activateTask">
{{ task.name }}
</v-tab>
</v-tabs>
</v-container>
</template>
<script>
export default {
data: {
tasks: [],
currentTab: null
},
method: {
createNewTask() {
this.tasks.push({
uuid: util.generateUUID(),
name: "name",
});
this.currentTab = this.tasks.length-1
},
},
};
</script>
Figure out a way that using the update hook and a variable to check whether a new tab dom is created. If true select the last child node from dom tree and dispatch click event.
So ugly 😔.
updated() {
this.$nextTick(() => {
if (!this.newTaskCreated) {
return
}
this.$el.querySelectorAll('.task-tab:last-child')[0].dispatchEvent(new Event('click'))
this.newTaskCreated = false
})
}

how to detect change of actual value not just OnChange nuxt vuetify

As a result of
export default {
name: "Details",
async asyncData({ redirect, params, store }) {
if (
!store
I am returning a few values in which one of them is
return {
camera: c,
thumbnail_url: thumbnail_url,
camera, and then in my form fields where I am populating a Vuetify dialog, Text Field inputs
such as
<v-dialog v-model="dialog" max-width="600px">
<v-card>
<v-card-text>
<v-layout class="model-container">
<v-row>
<v-col cols="12" lg="7" md="7" sm="12" xs="12">
<v-text-field
v-model="camera.name"
class="caption bottom-padding"
required
>
<template v-slot:label>
<div class="caption">
Name
</div>
</template>
</v-text-field>
my issue is, I have a button as
<v-btn color="primary" text #click="updateCamera">
Save
</v-btn>
which I only want to make disable false, only if there is an actual change occurs to, this.camera, in updateCamera method, I can use the updated values as
async updateCamera() {
let payload = {
name: this.camera.name,
but I want to enable or disable the button on when change occurs,
I had tried #input, I have also tried to watch camera object
<v-text-field
v-model="camera.name"
class="caption bottom-padding"
required
#input="up($event, camera)"
>
This way I tried to get some info about event, such as which text field it is, so I can compare, but in up method it only passes input value.
in watch
camera: function() {
this.$nextTick(() => {
console.log(this.camera)
})
}
camera: {
handler: function(val) {
this.$nextTick(() => {
console.log(val)
})
/* ... */
},
immediate: true
}
I have tried this but nothing worked.
Of course, we can enable or disable a button on change but not just if the user places an A and then deletes it, not such change.
Any help would be wonderful
Update:
Even after using this
camera: {
handler: function(newValue) {
if (newValue === this.dumpyCamera) {
console.log(this.dumpyCamera)
console.log(newValue)
console.log("here")
this.updateButton = true
} else {
this.updateButton = false
}
},
deep: true
}
both new and old values are the same.
I have tried to add new variable dumyCamera and on mount I have assigned this.camera value to this.dumyCamera but when something changes in camera, it changes this.dumyCamera as well? why is this the case?
You should be able to recognize any changes made to this.camera by using a watcher
watch: {
camera: {
handler (newValue, oldValue) {
// do something here because your this.camera changed
},
deep: true
}
}

How can I call ajax before display datepicker in vuetify?

Look at this : https://codepen.io/positivethinking639/pen/mddejJN
My script of vue like this :
data: () => ({
modalTest: false,
dateTest: null,
time: null,
allowedTimes: ['8:30 am','9:00am','10:30am','1:30pm','3:30 pm']
}),
methods: {
saveData() {
this.$refs.dialogTest.save(this.dateTest)
},
allowedDates: val => parseInt(val.split('-')[2], 10) % 2 === 0,
setTime(time) {
this.time = time
}
I want before call datepicker, I call ajax first
How can I do it?
#Max proposal is not fully answers the question.
Let's add new data property which will trigger the show of calendar component:
isAjaxCompl: false,
Move the button out of template to directly change dialog v-model:
<v-btn color="success" #click="openDialog()">call date</v-btn>
Make the function which will be fired on dialog open:
openDialog() {
this.modalTest = true;
axios.get('https://reqres.in/api/users?delay=1').then((response) => {
this.isAjaxCompl = true;
})
},
Finally, add v-if which will show calendar component only when axios get the response:
<v-date-picker v-if="isAjaxCompl" v-model="dateTest" scrollable :allowed-dates="allowedDates">
Link to the corresponding CodePen:
https://codepen.io/RobbyFront/pen/RwwWewM
You need to move the dialog button outside and add a #click method for showing the dialog
Your Code
<template v-slot:activator="{ on }">
<v-btn color="success" dark v-on="on">call date</v-btn>
</template>
New Code
Html
<v-btn color="success" dark #click="showDate">call date</v-btn>
Code
showDate(){
console.log("Ajax calling");
this.modalTest = true;
}
Here the pen

Setting v-select value programmatically does not fire #change in vuetify

I am trying to set v-select value programmatically, however the associated #change event does not fire. If I manually select the option, the event fires as expected.
Template:
<div id="app">
<v-app dark>
<v-select
v-model="currentItem"
:items="items"
#change="itemChanged"
/>
</v-app>
</div>
JS:
Vue.use(Vuetify);
var vm = new Vue({
el: "#app",
methods: {
itemChanged(item) {
console.log('item changed',item);
}
},
data: {
currentItem: '',
items: [
{ text: "Name", value: 'name' },
{ text: "Number", value: 'number' },
{ text: "Inputs", value: 'inputs' }
]
},
mounted() {
setTimeout(() => {
this.currentItem = 'inputs';
}, 2000);
}
});
The jsfiddle is here
Does anybody have any solutions aside from manually calling the function when setting the value programmatically?
The change event is defined in such a way so it is emitted when the input is changed by user interaction (see the event documentation). If you want to monitor all the changes, you can either use the input event:
<v-select
v-model="currentItem"
:items="items"
#input="itemChanged"/>
https://jsfiddle.net/0qbomzta/
or add a watcher to watch the currentItem:
<div id="app">
<v-app dark>
<v-select
v-model="currentItem"
:items="items"/>
</v-app>
</div>
In Vue instance, add:
watch: {
currentItem (newVal) {
console.log('item changed', newVal);
}
},
https://jsfiddle.net/w5voub3h/