How can we file input detect change on SAME file input in Vue Js
<input ref="imageUploader" type="file" #change="uploadImageFile">
We can add #click event and then clear the value of the file input
<template>
....
<input ref="imageUploader" type="file" accept=".jpg, .jpeg" #click="resetImageUploader" #change="uploadImageFile">
....
</template>
<script>
export default {
methods: {
resetImageUploader() {
this.$refs.imageUploader.value = '';
},
uploadImageFile() {
....
}
}
}
</script>
Same thing as #zubair's answer but for vue 3 and a bit more convenient:
<input type="file" #change="renderImage($event)" #click="$event.target.value=''">
The #zubair-0 and #grreeenn's answers are totally valid, Here you can have an implementation initializing the input value with an empty string after the uploaded file is processed because the event only is fired when the value changed, you can do this in Vue 3 using the Template Refs.
<template>
<input
ref="imageUploader"
type="file"
class="custom-file-input"
name="file-upload"
accept="image/png, image/gif, image/jpeg"
#change="uploadImageFile($event)"
>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const imageUploader = ref(null)
const uploadImageFile = (event) => {
console.log('File loaded...')
// We initialize the input value, this is the trick
imageUploader.value.value = ''
}
return {
imageUploader,
uploadImageFile
}
}
}
</script>
Related
I'm working on a complex app so I simplify my question.
I have a input component located in the InputFile.vue and a FormFile.vue. I import the InputFile into the Form and after the user uploads a file to the inputfield and send the post request, I want to send the file with axios to the backend.
<!--INPUTFILE-->
<template>
<input
:id="props.name"
:name="props.name"
type="file"
:ref="props.name"
/>
</template>
// ...
<script setup>
import { defineProps, ref } from "vue";
const props = defineProps({
name: String,
});
let fileName = ref("");
<!-- FORMFILE -->
<template>
<div>
<InputFile name="file" />
</div>
</template>
// ...
<script setup>
import InputFile from "#/components/InputFile";
import { ref } from "vue";
const input_file = InputFile.fileName;
axios.post('post/file', {
input_file: input_file
}).then((res) => console.log(res));
The input_file is not getting the value of the file input (from component InputFile.vue). How can I access the input.value from FormFile ?
You need to handle the file upload via a method.
A Vue method is an object associated with the Vue instance. Functions
are defined inside the methods object. Methods are useful when you
need to perform some action with v-on directive on an element to
handle events.
<template>
<input
v-on:change="handleFileUpload($event)"
:id="props.name"
:name="props.name"
type="file"
:ref="props.name"
/>
</template>
<script>
export default {
...
methods: {
handleFileUpload(e) {
e.preventDefault();
const formData = new FormData();
formData.append('file', e.target.files[0]);
axios.post('post/file',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
},
}
).then((res) => console.log(res));
}
}
}
</script>
How can I set the focus on an HTML input field upon page loading while using the Composition API of VueJS 3?
I have the following HTML:
<div>
<input type="text" id="filter" v-model="filter">
</div>
And have tried this in the setup() function, but that doesn't set the focus:
setup() {
onMounted(() => {
const filter_fld = document.getElementById('filter')
filter_fld.focus()
})
}
I also tried using the below.
HTML:
<div>
<input type="text" ref="filter_fld" v-model="filter">
</div>
And in setup() function:
setup() {
const filter_fld = ref(null)
onMounted(() => {
filter_fld.value?.focus()
})
}
But also to no success unfortunately. Any suggestions?
Have an input with a ref in your template e.g.:
<input ref="filter" />
Then after component is mounted focus it by reference on next tick:
import { ref, onMounted, nextTick } from 'vue';
setup() {
const filter = ref(null);
onMounted(() => {
nextTick(() => {
filter.value.focus();
});
});
return {
filter
};
}
You can also use the autofocus native's HTML attribute:
<input type = "text" id = "filter" v-model = "filter" autofocus/>
I have the following setup:
CustomForm.vue
<template>
<div>
<input v-model="field1" >
<input v-model="field2" >
</div>
</template>
<script>
export default {
data () {
return {
field1: '',
field2: '',
}
}
}
</script>
Parent.vue
<template>
<div>
<child>
<template>
<custom-form />
</template>
</child>
</div>
</template>
<script>
import Child from ...
import CustomForm from ...
</script>
Child.vue
<template>
<div>
<button #click="click" />
<grand-child>
<template>
<slot></slot>
</template>
</grand-child>
</div>
</template>
<script>
import GrandChild from...
export default {
methods: {
click: function () {
var data = ... // get form data
// do something with data then $emit
this.$emit('custom-click', data)
}
}
}
}
</script>
GrandChild.vue
<template>
<div v-for="(item, index) in list" :key="index" >
<input ...>
<input ...>
<slot></slot>
</div>
</template>
Basically I have a CustomForm, I want to pass the form to GrandChild.vue from Parent.vue, but the issue is I don't know how do I retrieve CustomForm data (field1, field2) in Child.vue ie how do I get CustomForm value from click method in Child.vue? Thanks
Instead of trying to extract data from a slot, there are other approaches. One solution is to use Vue's provide/inject feature to inject the form's data.
First, setup CustomForm to allow v-model to capture the form data in Parent:
Upon submitting the form in CustomForm, emit the input event with the form's data.
Add a value prop, as required for `v-model.
CustomForm.vue:
<template>
<form #submit.prevent="submit"> <!-- (1) -->
</form>
</template>
<script>
export default {
props: ['value'], // <-- (2)
methods: {
submit(e) { // <-- (1)
const form = e.target
const formData = {}
for (const [key, value] of new FormData(form).entries()) {
formData[key] = value
}
this.$emit('input', formData)
}
}
}
</script>
Then Parent can bind the form data:
Define parentForm in Parent's data as an object with a subproperty (e.g., parentForm.data).
Bind that subproperty to CustomForm's v-model.
Parent.vue:
<template>
<child>
<custom-form v-model="parentForm.data" /> <!-- (4) -->
</child>
</template>
<script>
export default {
data() {
return {
parentForm: { // <-- (3)
data: {}
}
};
}
}
</script>
Now, Parent can provide form:
Declare a provide method that returns an object with a form subproperty.
Set that property's value to the parentForm property previously declared in (3).
Parent.vue:
export default {
provide() { // <-- (5)
return {
form: this.parentForm // <-- (6)
}
}
}
...and Child or GrandChild can inject form:
Declare an inject property, whose value is a string array containing form (subproperty name from (5)).
GrandChild.vue:
export default {
inject: ['form'] // <-- (7)
}
demo
I am trying to open the Select file dialog box when clicking on the button, It is possible using this.$refs.fileInput.click() in VUE, but this is not working in composition API.
Here is the code for reference: https://codepen.io/imjatin/pen/zYvGpBq
Script
const { ref, computed, watch, onMounted, context } = vueCompositionApi;
Vue.config.productionTip = false;
Vue.use(vueCompositionApi.default);
new Vue({
setup(context) {
const fileInput = ref(null);
const trigger = () => {
fileInput.click()
};
// lifecycle
onMounted(() => {
});
// expose bindings on render context
return {
trigger,fileInput
};
}
}).$mount('#app');
Template
<div id="app">
<div>
<div #click="trigger" class="trigger">Click me</div>
<input type="file" ref="fileInput"/>
</div>
</div>
Thank you.
Have you tried to access it using context.refs.fileInput.click();?
Don't forget that it's setup(props, context) and not setup(context).
Try my edit here: https://codepen.io/ziszo/pen/oNxbvWW
Good luck! :)
I'm working in Vue 3 CLI and have tried several different recommendations and found the following to be the most reliable.
<template>
<input class="btnFileLoad" type="file" ref="oFileHandler" #change="LoadMethod($event)" />
<button class="btn" #click="fileLoad">Load</button>
</template>
<script>
import {ref} from "vue";
export default{
setup(){
const hiddenFileElement = ref({})
return {hiddenFileElement }
}
methods:{
fileLoad() {
this.hiddenFileElement = this.$refs.oFileHandler;
this.hiddenFileElement.click();
},
}
}
</script>
<style>
.btn{ background-color:blue; color:white; }
.btnFileLoad{ display:none }
</style>
I also discovered in Chrome that if the call from the button element to the hidden file handler takes to long, an error message that reads "File chooser dialog can only be shown with a user activation." is displayed in the source view. By defining the hiddenFileElement in setup the problem went away.
I would like to retrieve all input values from my child components (client and advice, seen below), but not sure how to proceed.
client.vue
<template>
<div id="client">
<input type="text" v-model="client.name" />
<input type="text" v-model="client.code" />
</div>
</template>
<script>
export default {
data() {
return {
client: {
name: '',
code: '',
}
}
}
}
</script>
advice.vue
<template>
<div id="advice">
<input type="text" v-model="advice.foo" />
<input type="text" v-model="advice.bar" />
<div v-for="index in 2" :key="index">
<input type="text" v-model="advice.amount[index]" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
}
}
</script>
Each component has more fields than the above example.
My home page (parent) looks as simple as:
<template>
<form id="app" #submit="printForm">
<clientInfo />
<advice />
<input type="submit" value="Print" class="btn" />
</form>
</template>
<script>
import clientInfo from "#/components/clientInfo.vue";
import advice from "#/components/advice.vue";
export default {
components: {
clientInfo,
advice
},
methods: {
printForm() {}
}
}
</script>
My first idea was to $emit, but not sure how to do that efficiently with more than 20 fields without attaching a #emitMethod="parentEmitMethod" to every single field.
My second idea was to have a Vuex store (as seen below), but I don't know how to save all the states at once and not sure if I should.
new Vuex.Store({
state: {
client: {
name:'',
code:''
},
advice: {
foo:'',
bar:'',
amount:[]
}
}
})
You could use FormData to get the values of the form's <input>s or <textarea>s that have a name attribute (anonymous ones are ignored). This works even if the form has nested Vue components that contain <input>s.
export default {
methods: {
printForm(e) {
const form = e.target
const formData = new FormData(form) // get all named inputs in form
for (const [inputName, value] of formData) {
console.log({ inputName, value })
}
}
}
}
demo
You could use v-model with your custom components. Let's say you want to use them like this:
<advice v-model="adviceData"/>
For this, you would need to watch for value changes on your input elements inside your advice component and then emit an input event with the values. This will update the adviceData binded property. One generic way to do this could be including a watcher inside your advice component, like this:
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
},
watch: {
advice: {
handler: function(newValue, oldValue) {
this.$emit('input', newValue);
},
deep: true,
}
},
}
This way you will not have to add a handler for each input field. The deep option must be included if we need to detect changes on nested data in the advice object.
I think you can achieve what you want is when the user writes something using#change this will trigger a method when the input value is changed, you could use a button instead or anything you want, like this:
The child component
<input type="text" v-model="advice.amount[index]" #change="emitCurrStateToParent ()"/>
You gotta add #change="emitCurrStateToParent ()" in every input you have.
emitCurrStateToParent () {
this.$emit("emitCurrStateToParent", this.advice)
}
Then in you parent component
<child-component v-on:emitCurrStateToParent="reciveDataFromChild ($event)"></child-component>
reciveDataFromChild (recivedData) {
// Do something with the data
}
I would use a button instead of #change, like a "Save" button idk, the same goes for vuex, you can use the #change event
saveDataAdviceInStore () {
this.$store.commit("saveAdvice", this.advice)
}
Then in the store
mutations: {
saveAdvice (state, advice) {
state.advice = advice
}
}