vue2-dropzone is working fine for vue2 but not working for vue3.
With the following code
import vue2Dropzone from 'vue2-dropzone'
import 'vue2-dropzone/dist/vue2Dropzone.min.css'
return {
dropzoneOptions: {
autoProcessQueue: false,
addRemoveLinks: true,
url: this.url,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
},
id: null,
myDropZone: null,
supervisorError: ''
}
}
I do have the following error
TypeError: Cannot read property '_c' of undefined vue3
vue3-dropzone
What you are after is vue3-dropzone.
It worked highly similar to the vue2-dropzone package that most of you may have been using with vue2. I myself am one of the contributors to the new vue3-dropzone package. I have just added the example code for those who want to Save Multiple Files at once, as shown below:
Example of Saving Multiple Files
<template>
<div>
<div v-bind="getRootProps()">
<input v-bind="getInputProps()" />
<p v-if="isDragActive">Drop the files here ...</p>
<p v-else>Drag 'n' drop some files here, or click to select files</p>
</div>
<button #click="open">open</button>
</div>
</template>
<script>
import { useDropzone } from "vue3-dropzone";
import axios from "axios";
export default {
name: "UseDropzoneDemo",
setup() {
const url = "{your_url}"; // Your url on the server side
const saveFiles = (files) => {
const formData = new FormData(); // pass data as a form
for (var x = 0; x < files.length; x++) {
// append files as array to the form, feel free to change the array name
formData.append("images[]", files[x]);
}
// post the formData to your backend where storage is processed. In the backend, you will need to loop through the array and save each file through the loop.
axios
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((response) => {
console.info(response.data);
})
.catch((err) => {
console.error(err);
});
};
function onDrop(acceptFiles, rejectReasons) {
saveFiles(acceptFiles); // saveFiles as callback
console.log(rejectReasons);
}
const { getRootProps, getInputProps, ...rest } = useDropzone({ onDrop });
return {
getRootProps,
getInputProps,
...rest,
};
},
};
</script>
As stated in this post: https://github.com/rowanwins/vue-dropzone/issues/578
It looks like vue-dropzone does not support Vue3 as of right now, I mean the mantainer was already struggling to manage the vue 2 and asked for help so it seems legit.
Maybe give a look to this vue3 one: https://github.com/Yaxian/vue3-dropzone
Here is a list of available alternatives: https://github.com/vuejs/awesome-vue#drag-and-drop
well, we're using this package for our production builds:
Vue3 Library Component for drag’n’drop file uploads with image previews.
https://github.com/darknessnerd/drop-zone
Related
I am new to Vue and stuck. I am trying to send user input data from a form into a vuex store. From that vuex store, an action will be called (fetching from API) and I would like that data back into my app and components.
<template>
<div>
<h1>APP NAME</h1>
<form action="submit" #submit.prevent="sendCityName()">
<label for="query"></label>
<input
type="text"
id="query"
v-model="cityName"
>
<button type="submit">Submit</button>
</form>
<h3>{{ lat }}</h3>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
cityName: ''
}
},
computed: {
coordinates () {
return this.$store.state.lat
}
},
methods: {
sendCityName() {
this.$store.commit('fetchCity', this.cityName)
}
},
}
</script>
Here is my index.vue and getting the error "Cannot read properties of undefined (reading 'commit')"
here is my store.js. I want to use the lat and lon across my app.
export const state = () => ({
lat: '',
lon: ''
})
export const mutations = {
SET_LAT(state, payload){
state.lat = payload
},
SET_LON(state, payload){
state.lon = payload
}
}
export const actions = {
async fetchCity({ commit }, cityName) {
// make request
axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "xxxxxxx",
q: cityName,
}
}).then((response) => {
commit('SET_LAT', response.data[0].lat);
commit('SET_LON', response.data[0].lng);
});
},
};
When I button submit I get the error "Cannot read properties of undefined (reading 'commit')"
Here is my working repo with the fixes mentioned below.
There are 3 things in your code:
remove vuex from package.json and run yarn again, that one is already baked into Nuxt as stated in the official documentation, those are the only steps needed
all the files inside of store will be namespaced by default for you, since you do have store/store.js, the proper syntax will be
async sendCityName() {
await this.$store.dispatch('store/fetchCity', this.cityName) // 👈🏻 store prefix
}
since you do use the axios module, you should have the following in your action (using the async/await syntax since it's more modern and preferable)
async fetchCity({ commit }, cityName) {
const response = await this.$axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "3d91ba5b3c11d13158a2726aab902a0b",
q: cityName,
}
})
commit('SET_LAT', response.data[0].lat)
commit('SET_LON', response.data[0].lng)
}
Looking at the browser's console, you also have some errors to fix.
I can also recommend an ESlint + Prettier configuration so that you keep your code error-proof + properly formatted at all times.
<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 add a custom view with some administrative utilities to Spring Boot Admin. The idea is to implement these as endpoints in Springboot Admin and call these endpoints from my custom view, but I don't know how to make a call to the server itself.
When a custom view has parent: 'instances' it will get an axios client for connecting to the current instance, but since the view I'm building isn't tied to a specific instance it doesn't have this. I'm aware I can install axios as a dependency, but I'd like to avoid that if possible to reduce build times. Since SBA itself depends on axios it seems I shouldn't have to install it myself.
Based on this sample, this is what I have right now:
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: {
type: Array,
required: true
}
},
data: () => ({ exampleResponse: "No response" }),
async created() {
const response = await this.axios.get("example");
this.exampleResponse = response.response;
},
};
</script>
ExampleController.kt
#RestController
#RequestMapping("/example")
class ExampleController {
#GetMapping
fun helloWorld() = mapOf("response" to "Hello world!")
}
Console says that it can't read property get of undefined (i.e. this.axios is undefined). Text reads "GET /example: No response"
I'm not sure if this is the best way to do it, but it is a way.
I noticed that I do have access to the desired axios instance within the SBA.use { install(...) { } } block, and learned that this can be passed as a property down to the view.
index.js
/* global SBA */
import example from './example';
import exampleEndpoint from './example-endpoint';
SBA.use({
install({viewRegistry, axios}) {
viewRegistry.addView({
name: 'example',
path: '/example',
component: example,
label: 'Example',
order: 1000,
// this is where we pass it down with the props
// first part is the name, second is the value
props: { "axios": axios },
});
}
});
example.vue
<template>
<div>
<h1>Example View</h1>
<p>
<b>GET /example:</b> <span v-text="exampleResponse" />
</p>
</div>
</template>
<script>
export default {
props: {
applications: { type: Array, required: true },
// this is where we retrieve the prop. the name of the field should
// correspond to the name given above
axios: { type: Object, required: true },
},
data: () => ({
exampleResponse: "No response",
}),
async created() {
// Now we can use our axios instance! And it will be correctly
// configured for talking to Springboot Admin
this.axios.get("example")
.then(r => { this.exampleResponse = r.data.response; })
.catch(() => { this.exampleResponse = "Request failed!" });
},
};
</script>
Based on the code given, it looks like you don't have axios initialized to how you want to use it.
You're calling it via this.axios but it's not in your component i.e
data() {
return {
axios: require("axios") // usually this is imported at the top
}
}
or exposed globally i.e
Vue.prototype.axios = require("axios")
You can simply just import axios and reference it.
<script>
import axios from 'axios';
export default {
created() {
axios.get()
}
}
</script>
I have an API endpoint which works on PUT Request to update User info such as user avatar. This API is built on Django. In my Frontend, I'm using NUXT to upload the file using FilePond, I did the following:
<template>
<section class="section">
<div class="container">
<file-pond
name="test"
ref="pond"
label-idle="Drop files here..."
v-bind:allow-multiple="true"
accepted-file-types="image/jpeg, image/png"
/>
<vs-button success #click='userinfo_put_avatar'>File upload data</vs-button>
</div>
</section>
</template>
<script>
import vueFilePond from 'vue-filepond';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
let FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview);
export default {
components: {
FilePond,
},
methods: {
async userinfo_put_avatar() {
let file = this.$refs.pond.getFiles(0)
let fileupload = new FormData();
fileupload.append('avatar', file)
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
let data = await this.$axios.put('user-info/', fileupload, config);
},
}
};
</script>
This works for me very well. But I want the feature of Filepond to show the upload status with the spinning svg, when in process of uploading and when completed show the green status.
I tried using the pond.processFile(0) but it doesnot upload the file.
I tried using the FilePond.setOptions() but it gives me an error setOptions is not a function. If this could work somehow.
I would be able to overwrite onaddfileprogress event using the following code from GITHUB ISSUE LINK
FilePond.setOptions({
instantUpload: true,
allowImagePreview: false,
server: {
url: '/images',
process: {
url: '/upload',
},
revert: '/revert',
restore: '/restore/',
load: '/load/',
},
onremovefile: function(error, file) {
if (file.serverId) {
let input = document.createElement('input');
input.type = 'hidden';
input.name = 'DeletedFilepondImages';
input.value = file.serverId;
uploadForm.appendChild(input);
}
},
onaddfilestart: function(file) {
console.log(`onaddfilestart`);
buttonForm.classList.add('filepondUpload');
buttonForm.setAttribute('disabled', 'true');
},
onaddfileprogress(file, progress) {
console.log(`onaddfileprogress`);
buttonForm.classList.remove('filepondUpload');
buttonForm.removeAttribute('disabled');
},
});
// get a reference to the input element
const filepondInput = document.querySelector(
'#filepondFileUploader input[type="file"]'
);
// create a FilePond instance at the input element location
const filepondObject = FilePond.create(filepondInput, {
maxFiles: 5,
acceptedFileTypes: ['image/*'],
labelIdle:
'<div class="image-upload__file-upload-content">Add images</div>',
files: filepondInitialFiles,
onprocessfiles() {
console.log('onprocessfiles');
buttonForm.classList.remove('filepondUpload');
buttonForm.removeAttribute('disabled');
},
});
You need to define the server prop on the component. You can use v-bind:server="myServer" and then add it to your component data.
<file-pond
name="test"
ref="pond"
v-bind:server="myServer"/>
export default {
name: 'app',
data: function() {
return {
myServer: {
// server config here
}
};
},
components: {
FilePond
}
};
If you need setOptions, you can import it from vueFilePond
import vueFilePond, { setOptions } from 'vueFilePond'
Using Vue TreeSelect Plugin to load a nested list of nodes from firebase backend. It's doc page says,
It's also possible to have root level options to be delayed loaded. If no options have been initially registered (options: null), vue-treeselect will attempt to load root options by calling loadOptions({ action, callback, instanceId }).
loadOptions (in my App.vue) dispatch vuex action_FolderNodesList, fetches (from firebase) formats (as required by vue-treeselect), and mutates the state folder_NodesList, then tries to update options this.options = this.get_FolderNodesList but this does not seems to work.
Here is the loadOptions method (in app.vue)
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
Vue errors out with Invalid prop: type check failed for prop "options". Expected Array, got String with value ""
I am not sure what am I doing wrong, why that does not work. A working Codesandbox demo
Source
App.vue
<template>
<div class="section">
<div class="columns">
<div class="column is-7">
<div class="field">
<Treeselect
:multiple="true"
:options="options"
:load-options="loadOptions"
:auto-load-root-options="false"
placeholder="Select your favourite(s)..."
v-model="value" />
<pre>{{ get_FolderNodesList }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Treeselect from "#riophae/vue-treeselect";
import "#riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
data() {
return {
value: null,
options: null,
called: false
};
},
components: {
Treeselect
},
computed: mapGetters(["get_FolderNodesList"]),
methods: {
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
}
};
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
folder_NodesList: ""
},
getters: {
get_FolderNodesList(state) {
return state.folder_NodesList;
}
},
mutations: {
mutate_FolderNodesList(state, payload) {
state.folder_NodesList = payload;
}
},
actions: {
action_FolderNodesList({ commit }) {
fmRef.once("value", snap => {
var testObj = snap.val();
var result = Object.keys(testObj).reduce((acc, cur) => {
acc.push({
id: cur,
label: cur,
children: recurseList(testObj[cur])
});
return acc;
}, []);
commit("mutate_FolderNodesList", result);
});
}
}
});
Any help is appreciated.
Thanks
It seems you are calling this.options which would update the entire element while only the current expanding option should be updated.
It seems loadOptions() is called with some arguments that you can use to update only the current childnode. The first argument seems to contain all the required assets so I wrote my loadTreeOptions function like this:
loadTreeOptions(node) {
// On initial load, I set the 'children' to NULL for nodes to contain children
// but inserted an 'action' string with an URL to retrieve the children
axios.get(node.parentNode.action).then(response => {
// Update current node's children
node.parentNode.children = response.data.children;
// notify tree to update structure
node.callback();
}).catch(
errors => this.onFail(errors.response.data)
);
},
Then I set :load-options="loadTreeOptions" on the <vue-treeselect> element on the page. Maybe you were only missing the callback() call which updates the structure. My installation seems simpler than yours but it works properly now.