Set focus text field inside dialog when dialog opened - javascript

I have custom text field and I must focus it functionality when dialog is opened. I tried focus it using vue.js refs:
Code:
<v-app id="app">
<v-row align="center">
<v-col class="text-center" cols="12">
<v-btn color="primary" #click="openDialog()">Open dialog</v-btn>
</v-col>
</v-row>
<v-dialog v-model="dialog">
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
Dialog
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="6" class="ml-2">
<v-text-field
ref="name"
placeholder="Enter your name..."
type="text"
solo
flat
outlined
></v-text-field>
<v-btn
color="indigo"
dark
#click = "setFocusName()"
>Set focus</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-dialog>
</v-app>
Javascript:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
dialog: false
}
},
methods: {
setFocusName() {
this.$refs.name.focus();
},
openDialog() {
this.dialog = !this.dialog
this.setFocusName()
}
}
});
When I click to open dialog button my text field not focused. Here you can see my full codepen
How I can focus it correctly when dialog is opened?

The problem with your code is that you're trying to access the $ref before it's actually rendered, here's a sandbox with the working code:
https://codepen.io/Djip/pen/ZEYezzm?editors=1011
To resolve the bug i've added a setTimeout to trigger the focus after 0.2 seconds from the open of the dialog. I first tried with $nextTick, but that wasn't enough to do the job - you can try adjusting the timer, to make it as low as possible.
setFocusName() {
this.$refs.name.focus();
},
openDialog() {
this.dialog = !this.dialog
setTimeout(() => {
this.setFocusName()
}, 200)
}

I set the focus logic inside a watcher of the property that toggles the dialog.
watch: {
dialog: function(value) {
if (value) {
setTimeout(() => {
this.$refs.inputrefname.$refs.input.focus();
});
}
}
}

Related

Veutify stopping cursor to move when changing isFocused

Created a code pen where when the user enters text in the first v-autocomplete the dropdown menu for the second v-autocomplete appears. I did with by changing isFocused to true. The issue is the cursor moves to the next v-autocomplete. Is there a way to stop this or another way to show the dropdown menu for the second v-autocomplete?
Here is my code pen
https://codepen.io/aaronk488/pen/MWbKNOq?editors=1010
HTML
<div id="app">
<v-app>
<v-container >
<v-row >
<v-col cols="4" md="4">
<v-autocomplete
ref="autocomplete"
label="Autocomplete"
:items="components"
:search-input.sync="search"
hint="need to open menu on focus, just like click" persistent-hint
></v-autocomplete>
</v-col>
<v-col cols="4" md="4">
<v-autocomplete
ref="autocomplete2"
label="Autocomplete2"
:items="components2"
hint="need to open menu on focus, just like click this" persistent-hint
></v-autocomplete>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
JS
new Vue({
el: "#app",
vuetify: new Vuetify(),
data: {
search: null,
components: [
'Autocompletes', 'Comboboxes', 'Forms', 'Inputs', 'Overflow Buttons', 'Selects', 'Selection Controls', 'Sliders', 'Textareas', 'Text Fields',
],
components2: [
'Autocompletes2', 'Comboboxes2', 'Forms2', 'Inputs2', 'Overflow Buttons2', 'Selects2', 'Selection Controls2', 'Sliders2', 'Textareas2', 'Text Fields2',
],
},
watch: {
search(){
this.$refs.autocomplete2.isMenuActive = true;
this.$refs.autocomplete2.isFocused = true;
}
},
})
Image
Well, you could use one autocomplete and one select.
But there you go, set this code to search(val) method. I think this is what you searching for.
this.$refs.autocomplete2.$refs.menu.isActive = true;
this.$refs.autocomplete2.blur();
Live example

How to append a font-awesome icon to a newly created element within vueJS / Vuetify?

I am trying to create a user list in which the user selects their icon from a row of icons. They activate a modal, enter their information, and select 'add user'. I am having trouble getting the value of the icon clicked, as you can see below I use this.icons[0] which will console log. But I cannot successfully get them to dynamically log. In addition I cannot successfully add the icon to the person (even in the case of calling the index, as stated above. What would be the best/cleanest way to do this? I cleaned out the code a bunch, but this should give a solid understanding of such. If I can provide any further code or info, let me know. Thanks in advance!
<template>
<div>
<v-expansion-panel v-for="user in users" :key="user.name" class="mb-1">
<v-expansion-panel-content>
<div slot="header" class="flex-row">
<v-icon>{{user.icon}}</v-icon>
<span class="px-1">{{user.name}}</span>
<span class="px-1">{{user.age}}</span>
</div>
</v-expansion-panel-content>
</v-expansion-panel>
<v-dialog max-width="600px" v-model="dialog">
<v-btn fab slot="activator" class="primary mb-3">
<v-icon>fa-user-plus</v-icon>
</v-btn>
<v-card>
<v-card-title>
<h2>Add a New User</h2>
</v-card-title>
<v-card-text>
<v-form class="px-3" ref="form">
<v-text-field label="Name" v-model="user.name" prepend-icon="person"></v-text-field>
<v-text-field label="Age" v-model="user.age" prepend-icon="fa-heart"></v-text-field>
<div>
<v-btn flat icon v-for="(icon, index ) in icons" :key="index" #click="appendIcon()">
<v-icon>{{icon}}</v-icon>
</v-btn>
</div>
<v-btn
flat
class="primary mx-0 mt-3"
#click="addUser(user);
dialog=!dialog"
>Add user</v-btn>
</v-form>
</v-card-text>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
methods: {
addUser: function(user) {
this.users.push(user);
this.user = {
name: undefined,
age: undefined,
icon: undefined,
};
console.log(user.name + " added to list");
},
appendIcon: function() {
console.log(this.icons[0]);
}
},
data() {
return {
dialog = fa
users: [],
user: {
name: undefined,
age: undefined,
icon: undefined,
},
icons: [
"fas fa-user",
"far fa-user",
"fas fa-user-cog",
]
};
}
};
</script>
In your buttons for your icons array if you add the individual icon as a parameter you can assign that to your user object. Like so:
#click="appendIcon(icon)"
and the method:
appendIcon(icon) {
this.user.icon = icon
console.log(icon);
}
Then when you call addUser() you can use the already set user object, so you don't need to pass that as a parameter.
#click="addUser";
and that method:
addUser() {
this.users.push(this.user);
this.user = {
name: undefined,
age: undefined,
icon: undefined,
};
console.log(user.name + " added to list");
},

Refactoring vue single file components

As my single file component is getting bigger, I attempted to refactor it a bit. Turns out I have a couple of dialogs that make the .vue file huge (800 lines+). So in order to keep the component clean, I am trying to turn the dialogs into dedicated child components (mydialog). I am using vuetify (familiarity with vuetify is not required).
I have a button in the parent component which enables the dialog. I am sending the dialog prop to the child component which then gets attached to the v-model of the dialog. The problem is, it only works the first time. And that's because as I click the close button, it changes the dialog's value to false and it remains false forever because the child component has lost communication with the parent. Under such circumstances, how can I fix this?
Here is the snippet:
Vue.component('mydialog', {
props:{
dialog: {
type: Boolean,
default: false
}
},
template: `
<div class="text-xs-center">
<v-dialog
v-model="dialog"
width="500"
>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
A Dialog
</v-card-title>
<v-card-text>
Lorem ipsum dolor sit amet,
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
flat
#click="dialog = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
`,
data: function() {
return {
// dialog: false,
}
}
});
new Vue({
el: '#app',
data: {
dialog: false
}
});
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.css" />
</head>
<body>
<div id="app">
<v-app id="inspire">
<v-btn color="red lighten-2" dark #click="dialog=true">Click Me</v-btn>
<mydialog :dialog="dialog"></mydialog>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.js"></script>
</body>
</html>
Your component communication was a bit off: basically your dialog did not inform the app component that it was closed. The following two changes enable the upstream communication between modal and app:
In app template:
<mydialog :dialog.sync="dialog"></mydialog>
In mydialog controller:
data: function() {
return {
dialog$: false,
};
},
methods: {
onClose() {
this.dialog$ = false;
this.$emit('update:dialog', this.dialog$);
},
},
watch: {
dialog: {
immediate: true,
handler() {
this.dialog$ = this.dialog;
},
},
}
The first change makes the app component listen to updates on the dialog props of mydialog.
In mydialog I added a data property dialog$ which reflects the dialog props (because props are considered constant and should not be changed). A watcher takes care that downstream updates on dialog update dialog$. The onClose method updates dialog$ when the dialog was closed and emits an update to subscribers (specifically app).
Vue.component('mydialog', {
props:{
dialog: {
type: Boolean,
default: false
}
},
template: `
<div class="text-xs-center">
<v-dialog
v-model="dialog$"
width="500"
>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
A Dialog
</v-card-title>
<v-card-text>
Lorem ipsum dolor sit amet,
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
flat
#click="onClose"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
`,
data: function() {
return {
dialog$: false,
};
},
methods: {
onClose() {
this.dialog$ = false;
this.$emit('update:dialog', this.dialog$);
},
},
watch: {
dialog: {
immediate: true,
handler() {
this.dialog$ = this.dialog;
},
},
}
});
new Vue({
el: '#app',
data: {
dialog: false
},
template: `
<v-app id="inspire">
<v-btn color="red lighten-2" dark #click="dialog=true">Click Me</v-btn>
<mydialog :dialog.sync="dialog"></mydialog>
</v-app>
`,
});
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.css" />
</head>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.2.0/vuetify.min.js"></script>
</body>
</html>
As #raina77ow has mentioned, you can also solve this kind of issue with an event bus but it's not necessary in this case.

How do I enable text-fields on button click event in Vue.js?

I have a 5 text fields.
1._id,
2.name,
3.display,
4.reference,
5.ref_id
I want to enable only 2nd ,3rd & 4th text fields on a button click. So, I am declaring a variable 'disable' in data section of Vue.js, and calling function enableFields() on button click event. Here's my template code:
<template>
<div>
<!-- Dialog Modal Design -->
<v-dialog v-model="dialog" #keydown.esc="setDialog(false)" width="800px">
<v-card>
<v-card-title
class="grey lighten-4 py-4 title">
<v-icon>fa-envelope-open</v-icon>
Add/Edit a Record
</v-card-title>
<!-- Modal pop up text fields -->
<v-container grid-list-sm class="pa-4">
<v-layout row wrap>
<v-flex xs12 align-center justify-space-between>
<v-layout align-center
v-for="(column, i) in columns"
:key ="i"
v-if="column.field != ''">
<v-text-field
v-bind:value="getEntryFieldData(column)"
:label="column.headerName"
:disabled="disable">
</v-text-field>
<!-- ="(column.headerName == '_id')" -->
</v-layout>
</v-flex>
</v-layout>
</v-container>
<!-- Edit/Update/Cancel Buttons -->
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" #click="onCancel">
<v-icon>fa-ban</v-icon>
Cancel
</v-btn>
<v-btn color="primary" #click="onCancel">
<v-icon>fa-save</v-icon>
Update
</v-btn>
<v-btn v-if="visible"
color="primary" #click="enableFields">
<v-icon>fa-pencil-alt</v-icon>
Edit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
And here's my script:
<script>
import {mapGetters} from 'vuex';
export default {
name: 'MasterModal',
props: {
input: Object,
addBtnClick: Boolean
},
data () {
return {
isReadOnly: false,
dialog: false,
valid: false,
visible: true,
disable: true
};
},
computed: {
...mapGetters('masterData', {
entryState: 'entryState',
entryData: 'entryData',
columns: 'columns'
})
},
watch: {
addBtnClick: function (newValue, oldValue) {
this.setDialog(!this.dialog);
}
},
methods: {
setDialog (bValue) {
this.dialog = bValue;
},
// Called when the cancel button is pressed in the form
// Clears and data currently entered in the form and closes the input modal
onCancel () {
this.setDialog(false);
},
// Reading all column values and filling row data into the textbox in the v-for of template
getEntryFieldData (column) {
return this.entryData[column.field];
},
enableFields () {
this.disable = false;
}
}
};
</script>
Basically I am getting confused about assigning property of each text field
as I am generating them dynamically using v-for.
The fastest way to achieve it is this:
<v-text-field
v-bind:value="getEntryFieldData(column)"
:label="column.headerName"
:disabled="disable || i == 0 || i == 4">
</v-text-field>
So adding,
In HTML
:disabled="setDisable(column.field)" works for me.
In Script
setDisable (colName) {
return this.entryState === 'read' || colName.toLowerCase().indexOf('id') !== -1;
}
works for me. Basically I am checking which column.field text has id and disabling it by checking its index.

VueJS re-read file input when selecting the same file

What i am trying to achieve is to select a file and read its contents in a new dialog box.
I have it working until the point i try and select the same file as the previous loaded one, selecting a different file works perfectly.
I have tried clearing the file input which appears to work but it still doesn't select the file again.
See my example below, any help much appreciated!
Codepen
new Vue({
el: "#app",
data: () => ({
importedData: null,
dialogImport: false
}),
watch: {
importedData: {
handler() {
this.checkData();
},
deep: true
}
},
methods: {
openFileExplorer() {
this.$refs.fileInputImport.value = "";
this.$refs.fileInputImport.click();
},
selectFile(e) {
const self = this;
const file = e.target.files[0];
let reader = new FileReader();
reader.onload = e => {
self.importedData = reader.result;
self.checkedData = true;
};
reader.readAsText(file);
},
checkData() {
// check the file
this.dialogImport = true;
}
}
});
<link href="https://unpkg.com/vuetify#0.17.4/dist/vuetify.min.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<div class="text-xs-center">
<input ref="fileInputImport" type="file" name="fileInputImport" #change="selectFile">
<v-btn color="primary" #click.stop="openFileExplorer()">Choose file</v-btn>
<v-dialog v-model="dialogImport" fullscreen transition="dialog-bottom-transition" :overlay="false" scrollable>
<v-container class="ma-0 pa-0 white" style="max-width:100%">
<v-layout row wrap justify-left>
<v-card style="width:100%;">
<v-toolbar class="amber lighten-1 elevation-0">
<v-toolbar-title>Imported data</v-toolbar-title>
<v-spacer></v-spacer>
<div>
<v-flex xs12>
<v-container fluid>
<v-layout row align-center justify-center>
<v-flex>
<v-tooltip bottom close-delay="0">
<v-btn icon slot="activator" #click.native="dialogImport = false">
<v-icon>close</v-icon>
</v-btn>
<span>Close</span>
</v-tooltip>
</v-flex>
</v-layout>
</v-container>
</v-flex>
</div>
</v-toolbar>
<div>{{ importedData }}</div>
</v-card>
</v-layout>
</v-container>
</v-dialog>
</div>
</v-app>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#0.17.4/dist/vuetify.min.js"></script>
I don't think watch is required. Here is the updated codepen
It was able to read the text and assigns it to importedData. But the importedData has the same content when you select same file, watch is not tried the this.checkData().
Either you reset importedData when your close the dialog or remove the watch as i did.

Categories