I'm using Vue Froala to do the image upload using the Froala config options which are basically an object added to the Vue data property. Something like this:
data() {
return {
config: {
imageUploadURL: '/api/image/store',
imageUploadParam: 'image',
imageUploadParams: {id: this.id} //this is the dynamic ID
}
}
}
When I upload an image and those config options are triggered, the ID that I'm trying to pass is always undefined. I guess it is because when I first load the page, the Vue data properties are initialized at the very beginning and the dynamic ID that I'm retrieving from a prop, is not yet available.
I tried assigning the data property imageUploadParams a computed property that would return the ID but was still undefined
If I use a watcher to assign the ID afterwards, I get null. Here is what I get in the POST code (php)
+request: Symfony\Component\HttpFoundation\ParameterBag {#45
#parameters: array:1 [
"id" => null
]
}
Any idea?
You could use a watch:
data() {
return {
config: {
imageUploadURL: '/api/image/store',
imageUploadParam: 'image',
imageUploadParams: { id: '' }
}
}
},
watch: {
id(value) {
this.config.imageUploadParams.id = value;
}
}
Now whenever id changes, config.imageUploadParams will be set.
Assigning a variable in JavaScript sets the value at the time of the assignment. Since your id is empty when it's assigned, there's no reference to use later to access future async properties. Assigning a computed won't help for the same reason.
Using watch for async, and computed for sync is a good general rule
Edit from chat
It seems <froala> isn't able to respond to your reactive data and is only using the initial config value. Put a v-if="id" on the component to prevent it from being created until the id is ready:
<froala v-if="id">
Related
I am trying to get a Vue.js computed property to do some reconfiguring of data and when I do it inside the computed property I get an error about side effects. I looked it up on here and found that using a method is the correct way of doing this. Now when trying to use a method it results in error messages in the console about it can't read the property of 'units' from selectedBuilding.
What I am trying to figure out is how to have it so the units array is populated with the correct data depending on the computed return value
Below is the code stripped down for the script section of my Vue.js file.
export default {
name: "Form",
data: () => ({
building: {},
buildings: [
{
"units": [ // Bunch of data in here that isn't important//],
"buildingID": "<an ID according to a custom format>",
}],
units: [],
}),
methods: {
formatUnits: function(selectedBuildingID) {
let selectedBuilding = this.buildings.find(building => building.buildingID === selectedBuildingID)
this.units = selectedBuilding.units
return true
}
},
computed: {
useExistingBuilding() {
if(this.building === 'New') {return true}
else {
this.formatUnits(this.building)
return false
}
},
}
};
</script>
Error message:
TypeError: Cannot read property 'units' of undefined
Since this.building has no buildingID (thus, undefined), and there is no item in buildings that has undefined as the value for buildingID, it fails.
This is because the find method will return undefined when the condition couldn't be satisfied.
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
MDN Reference
In the computed property you are passing the whole building rather than the id, whereas in your method (in find) you are comparing the ids.
You need to pass the building id to your method.
Also in your data you are declaring building as an object, and then in your computed property, you are asserting whether building equals New which is a string.
I would advise the first thing you have to do is rethink your data types and the flow of your data between the template, computed properties, methods and data.
I have a small problem, I created a package to manage forms, everything worked correctly until I needed to create a dynamic form based on an API response.
Here is the package: https://github.com/nulvem/js-form
It can be installed with npm:
npm install #nulvem/js-form
Here is my Vue component calling the Form class:
<script>
import {Form} from '#nulvem/js-form'
export default {
data() {
return {
form: new Form({
template: {
value: null,
validation: {
rules: 'required',
messages: {
required: 'You must select the type of report'
}
}
}
})
}
},
mounted() {
this.form.template = 'random-id'
// These data will be obtained from an API
let fields = [
'start_date',
'end_date',
'wallets',
]
fields.forEach((field) => {
let fieldData = {
value: null,
validation: {
rules: 'required',
messages: {
required: `You must enter the field ${field.description}`
}
}
}
this.form.$addField(field, fieldData)
})
// Here we can only see the getter/setter of the template property that was passed when instantiating the Form class
console.log(this.form)
}
}
</script>
The problem is: The fields I passed to the form during the creation of new Form ({...}) instance are reactive, since when I add new fields by calling the this.form.$AddField() function the fields are not getting reactive.
The funny thing is that the construct of the Form class calls a function called this.form.$AddFields() that calls the this.form.$AddField() function several times.
Why it works inside the constructor and don't when called separated? How to fix this issue?
Any idea what might be going on? This is driving me crazy, I've been trying to find the problem for several hours and I can't find it.
Edit
As a quick fix we make a change in the package where it is possible to pass the Form instance as a third parameter to the $addFields function, so the attributes are reactive, however we would like this to work without having to pass this parameter.
VueJS lose the reactivity of properties that are added to your data after the render. You can read more about this, here: https://br.vuejs.org/v2/guide/reactivity.html.
A quick way to try to solve this problem is adding a this.$forceUpdate() everytime that you add a new property, but it also can bring another problems depending of your context.
Or you can try using Vue.set() method, instead of your $addField method. You can read more in the same link above.
remove the context and change this line for reactivity:
this.$initialValues[field] = fieldDeclaration.value;
=>>>
this.$initialValues[field] = fieldDeclaration.value;
this.$initialValues = Object.assign({}, this.$initialValues);
I am navigating into a view from another view and passing the itemdId as param value to vue router.
I want to be able to call firebase with that itemId so that I can filter the data and the filtered result/data is used in the UI. I am using vuefire.
What is happening is that vue starts rendering before the data is available in created() and I see error is the console saying view is referring to undefined property values.
Is there a way to render the view after the data is available?
I have tried the using beforeMount as well as created and beforeCreate approaches.
See code below:
<h1>{{projects[0].project_name}}</h1> //it says type error. cannot read property project_name of undefined. Sometimes on refresh it works but most times it does not.
Script code below:
let projectsRef = db.ref('projects');
export default {
name: 'ProjectDetailsOpenInvesting',
props: {
data: Object
},
firebase:{
projects: projectsRef
},
data(){
return {
projects:[],
.....
}
},
created(){
var that = this;
console.log(this.$route.params.itemid) //this works
this.projects = this.projects.filter(function(p){
return p.project_id == that.$route.params.itemid //this works as well
})
}
Firebase screenshot here
As you have mentioned, one approach is to fetch after navigation, i.e. fetch the data in the component's created hook.
To do that with vuefire you need to programatically bind the Realtime Database projectsRef Reference to the projects property in your Vue application, as follows:
created(){
console.log(this.$route.params.itemid)
const itemId = this.$route.params.itemid;
this.$rtdbBind('projects', projectsRef.orderByKey().equalTo(itemId)).then(projects => {
this.projects === projects;
});
}
As explained in the API doc:
$rtdbBind returns a Promise that is resolved once the data has been
retrieved and synced into the state.
Note that you need to install the rtdbPlugin plugin: https://vuefire.vuejs.org/api/vuefire.html#rtdbplugin
Also note that instead of filtering the desired project item in the front-end (with filter(function(p){return p.project_id == that.$route.params.itemid}))), we filter it in the back-end, at the level of the database (projectsRef.orderByKey().equalTo(itemId)) which is more efficient and which avoids transmitting the entire set of objects from the back-end to the front-end (and avoids paying for this downloaded volume, see https://firebase.google.com/pricing?authuser=0).
I am passing an object 'item' from parent to 'child' component. It works just fine. Please refer this.
As you change the values from dropdown, it updates the UI. My issue is that the same exact code does not work in my application (running locally on my machine). I even tried adding {{item.type}} in html, but it does not change (sticks to original value). One thing I noticed that, if I put #change='onChange' and printed the value in onChange method and it prints updated value.
Really unable to find solution to fix this. Any help would be great. Thanks.
The issue is you are adding the type property to your model after the item was already bound to data, and Vue cannot detect changes to properties added that way.
The fix is to make sure there is a type property on item,
item: {
"direct_sale_price": "",
"is_auction": true,
"is_tender": false,
"type": null
}
or to properly add it using $set.
created: function () {
if (this.item.is_auction) {
this.$set(this.item, 'type', 'auction')
} else if (this.item.direct_sale_price) {
this.$set(this.item, 'type', 'direct-sale')
} else if (this.item.is_tender) {
this.$set(this.item, 'type', 'tender')
} else {
this.$set(this.item, 'type', 'plain')
}
}
Building a project using Vue.js (and Laravel), the following (greatly simplified) code results in the below error:
Vue component:
<template>
<input type="text" class="form-control" v-model="main_object[item_id][my_answer_key]">
</template>
<script>
export default {
props: [
],
data() {
return {
main_object: {},
item_id: '1234',
my_answer_key: '5678'
}
},
ready: function () {
vm = this;
},
methods: {
}
}
</script>
Error received:
We know you can use the vm.$set() method to add properties to the object. However, we’re building the model path on the fly (item_id and my_answer_key change depending on various user options being selected). It seems like we have to write a method that determines if the object property is already set, and if it's not set, to then set it. Is there a better way to accomplish the above?
You could seemingly get around this by using the created hook and $set:
created: function () {
this.$set(this.item_id + "." + this.my_answer_key, "")
}
However, if item_id and my_answer_key can change, this approach will ultimately not work because Vue does not have dynamic two way binding. In other words, the binding in your v-model will be created once and will not change later if either of the item_id or my_answer_key values change.
So, to accomplish something like this, you might need to resort to a kludge like using a v-if and toggling it to destroy and recreate the input (and it's binding). That might work.
Sometimes, computed's can help with these situations. Bind your input to a simple attribute on your data model and use a computed to generate the actual nested data model you need elsewhere.