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>
Related
I am trying to pass a value into a child component. The child component will then preform the save operation. The parent doesn't need to know anything about it. I am able to pass in the object but not save its updated form.
Parent
<template>
<div v-show="isOpened">
<EditModal #toggle="closeModal" #update:todo="submitUpdate($event)"
:updatedText="editText" :todo="modalPost" />
</div>
</template>
<script setup lang="ts">
import Post from "../components/Post.vue";
import { api } from "../lib/api";
import { ref } from "vue";
import { onMounted } from "vue-demi";
import EditModal from "../components/EditModal.vue";
const postArr = ref('');
const message = ref('');
let isOpened = ref(false);
let modalPost = ref('');
let editText = ref('');
function closeModal() {
isOpened.value = false
}
function openModal(value: string) {
isOpened.value = true
modalPost.value = value
}
// call posts so the table loads updated item
function submitUpdate(value: any) {
console.log("called update in parent " + value)
editText.value = value
posts()
}
</script>
Child EditModal
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" :value="props.todo.post"></textarea>
</div>
<!-- Modal footer -->
<div>
<button data-modal-toggle="defaultModal" type="button"
#click="update(props.todo.blogId, props.todo.post)">Save</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { api } from "../lib/api";
import { reactive, ref } from "vue";
const props = defineProps({
todo: String,
updatedText: String,
})
const emit = defineEmits(
['toggle','update:todo']
);
function setIsOpened(value: boolean) {
emit('toggle', value);
}
function update(id: string, value: string) {
console.log('the value ' + value)
try {
api.updateBlog(id, value).then( res => {
emit('update:todo', value)
emit('toggle', false);
})
} catch (e) {
console.log('Error while updating post: '+ e)
}
}
</script>
I know the props are read only, therefore I tried to copy it I can only have one model.
I do not see the reason I should $emit to the parent and pass something to another variable to pass back to the child.
I am trying to pass in text to a modal component where it can edit the text and the child component saves it.
Advice?
First, your component should be named EditTodo no EditModal because you are not editing modal. All Edit components should rewrite props to new local variables like ref or reactive, so you can work on them, they won't be read only any more.
Child EditTodo.vue
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" v-model="data.todo.title"></textarea>
</div>
<div>
<button data-modal-toggle="defaultModal" type="button" #click="update()">Save</button>
</div>
</div>
</template>
<script setup lang="ts">
import { api } from "../lib/api";
import { reactive } from "vue";
const props = defineProps<{ id: number, todo: { title: string} }>()
const emit = defineEmits(['toggle']);
const data = reactive({ ...props })
// I assuming api.updateBlog will update data in database
// so job here should be done just need toogle false modal
// Your array with todos might be not updated but here is
// no place to do that. Well i dont know how your API works.
// Database i use will automaticly update arrays i updated objects
function update() {
try {
api.updateBlog(data.id, data.todo ).then(res => {
emit('toggle', false);
})
}
}
</script>
Parent
<template>
<div>
<BaseModal v-if="todo" :show="showModal">
<EditTodo :id="todo.id" :todo="todo.todo" #toggle="(value) => showModal = value"></EditTodo>
</BaseModal>
</div>
</template>
<script setup lang="ts">
const showModal = ref(false)
const todo = reactive({ id: 5, todo: { title: "Todo number 5"} })
</script>
I separated a modal object with edit form, so you can create more forms and use same modal. And here is a simple, not fully functional modal.
<template>
<div class="..."><slot></slot></div>
</template>
<script setup>
defineProps(['show'])
defineEmits(['toogle'])
</script>
You might want to close modal when user click somewhere outside of modal.
<template>
<div class="home">
<h1>BPMN Lint Analyzer</h1>
<!-- Get File from DropZone -->
<DropZone #drop.prevent="drop" #change="selectedFile"/>
<span class="file-info">File:{{dropzoneFile.name}}</span>
<button #click="sendFile" >Upload File</button>
<!-- Display Response Data (Not Working)-->
<div v-if="showResponseData">
<p>Testing: {{responseData}}</p>
</div>
</div>
</template>
<script>
import DropZone from '#/components/DropZone.vue'
import {ref} from "vue"
import axios from 'axios'
export default {
name: 'HomeView',
components: {
DropZone
},
setup(){
let dropzoneFile = ref("")
//Define Response variable and visibility toggle
var responseData=''
// var showResponseData = false
//Methods
const drop = (e) => {
dropzoneFile.value = e.dataTransfer.files[0]
}
const selectedFile = () => {
dropzoneFile.value = document.querySelector('.dropzoneFile').files[0]
}
//API Call
const sendFile = () => {
let formData = new FormData()
formData.append('file', dropzoneFile.value)
axios.post('http://localhost:3000/fileupload', formData,{
headers: {
'Content-Type':'multipart/form-data'
}
}).catch(error => {
console.log(error)
}).then(response => {
responseData = response.data
console.log(responseData);
})
// showResponseData=true
}
return{dropzoneFile, drop, selectedFile, sendFile}
}
}
</script>
I'm trying to pass the response from sendFile, which is stored in responseData back to the template to display it in a div to begin with. I'm not sure if a lifecycle hook is needed.
Current output:
I played around with toggles, I tried to convert everything to options API. Tried adding logs but I'm still struggling to understand what I'm looking for.
Unfortunately I am stuck with the Composition API in this case even if the application itself is very simple. I'm struggling to learn much from the Docs so I'm hoping to find a solution here. Thank you!
You need to make responseData reactive, so try to import ref or reactive from vue:
import {ref} from 'vue'
then create your variable as a reactive:
const responseData = ref(null)
set data to your variable:
responseData.value = response.data
in template check data:
<div v-if="responseData">
<p>Testing: {{responseData}}</p>
</div>
finally return it from setup function (if you want to use it in template):
return{dropzoneFile, drop, selectedFile, sendFile, responseData}
I'm trying to use tiptap with Vue.js with the <script setup> approach of creating a Single File Component (SFC).
TextEditor.vue
<template>
<editor-content :editor="editor" class="editor" />
</template>
<script lang="ts" setup>
import { useEditor, EditorContent } from '#tiptap/vue-3'
import StarterKit from '#tiptap/starter-kit'
const props = defineProps({
modelValue: {
type: String,
default: "",
}
})
const emit = defineEmits(['update:modelValue'])
const editor = useEditor({
content: props.modelValue,
extensions: [StarterKit],
onUpdate: ({editor}) => {
let content = editor.getHTML()
emit('update:modelValue', content)
}
})
</script>
I then use this component like this:
<template>
<text-editor v-model="myModel.content" />
</template>
This works when <text-editor> is loaded after myModel.content is defined.
However, if <text-editor> loads before myModel.content is set from my database API, then the text content remains blank. From what I understand from looking at the examples in the tiptap docs, I need to somehow use watch to update my editor when props.modelValue is changed using something like this:
watch(() => props.modelValue, (newValue, oldValue) => {
const isSame = newValue === oldValue
console.log(`Same: ${isSame}`)
if (isSame) {
return
}
editor.commands.setContent(newValue, false)
})
However, in the snippet above, editor is a ShallowRef type and doesn't have a reference to commands to call setContent.
What is the best way to get the above example to work when loading tiptap with the <script setup> approach?
You need to access the ref actual value with .value
editor.value?.commands.setContent('<p>test</p>', false)
When i am learning the vue-test-utils from the offical site conditional-rendering.
I tried to change the option api to composition api.
It seems like the mount option data not working with the composition api.
Nav.vue Composition API test FAIL
<template>
<div>
<a id="profile" href="/profile">My Profile</a>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const admin = ref(false)
</script>
Nav.vue Option API test PASS
<template>
<div>
<a id="profile" href="/profile">My Profile</a>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</div>
</template>
<script>
export default {
data() {
return {
admin: false,
}
},
}
</script>
Nav.spec.js test
test('renders an admin link', () => {
const wrapper = mount(Nav, {
data() {
return {
admin: true
}
}
})
// Again, by using `get()` we are implicitly asserting that
// the element exists.
expect(wrapper.get('#admin').text()).toEqual('Admin')
})
I found a solution but I don't know if it's a good solution
test("renders an admin link", async () => {
const wrapper = mount(Nav);
wrapper.vm.admin = true;
await nextTick();
expect(wrapper.get("#admin").text()).toEqual("Admin");
});
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>