VueJs - Passing variables between methods - vue.js

There is a v-select component and on change I am firing up fillData(selected) where selected is the v-model. And I need to update the label in datacollection.datasets.label on change. How do I do that ?
<script>
import BarChart from './BarChart.js'
import { mapGetters, mapActions } from "vuex";
export default {
name : "TestLegPerformance",
components: {
BarChart
},
data: () => ({
datacollection : {
labels: ['Week-1','Week-2','Week-3'],
datasets: [
{
label: '',
backgroundColor: '#C58917',
data: [40, 50, 20]
}
]
},
selected: []
}),
computed: {
...mapGetters({
planNames: "planNames"
})
},
mounted () {
this.getAllPlanNamesAction();
},
methods: {
...mapActions(["getAllPlanNamesAction"]),
fillData(selected){
console.log(selected)
},
}
}
</script>

Inside methods, you can reference to data properties using this.
In your case, you can use this.datacollection.datasets.label and assign to it:
methods: {
// ...
fillData(selected){
this.datacollection.datasets[0].label = selected;
},
}
Of course, this assuming that selected is the string you want to assign to the label.
Note: the this will only work when you declare the methods using methodName() {} (as you are) or methodName: function (){.... So don't use arrow functions when declaring vue methods, they will mess up your this.
Bind to events using # (v-on) not : v-bind)
Your template:
<v-select label="Select a Plan" :items="planNames" v-model="selected" single-line max-height="auto" :change="fillData(selected)" required >
To listen to the change event, don't use:
:change="fillData(selected)"
use
#change="fillData"
Don't send an argument (it will mess things up). v-select will send you one already.
Notice the replacement of : with #.
The first, : is an alias to v-bind. So :change="xyz" is the same as v-bind:change="xyz".
The second, # is an alias to v-on. So #change="xyz" is the same as v-on:change="xyz". which is what you want.
See demo JSFiddle here.
Updating label of vue-chartjs's BarChart automatically
Even though you are
using the reactiveProp mixin; and
changing the label
The chart is not reflecting the changes (the label does not change) automatically.
I noticed this happens because the chart only reacts to whole datacollection changes, not to inner properties (like label).
So the solution is to:
"clone" datacollection
update the label of the clone
assign the clone to this.datacollection
And the chart will react (the label change will be reflected).
So, change your fillData method to the following:
fillData(selected){
let collectionClone = Object.assign({}, this.datacollection);
collectionClone.datasets[0].label = selected;
this.datacollection = collectionClone;
},
Check here a working DEMO CODESANDBOX of this solution (see the changeLabelAndReassign() method of BarChart.vue).

Related

How does this `layout` prop get updated without `:layout.sync`?

I don't understand how vue-grid-layout manages to change myLayout in the following code sandbox:
<grid-layout
:layout="myLayout"
....
>
I thought that:
<MyComponent :layout="myLayout"/>
Meant that any changes to myLayout here in the parent scope would become changes in in the layout prop in MyComponent, but MyComponent would not be able to change myLayout.
Which is what the sync modifier was for, so that:
<MyComponent :layout.sync="myLayout"/>
Would be the equivalent of:
v-bind:layout="myLayout"
v-on:update:layout="myLayout = $event"
And so without the .sync, it would not be possible for a component to change a prop in the parent scope.
But the code sandbox demonstrates that vue-grid-layout manages to change myLayout using just :layout="myLayout".
(:layout.sync="layout" is used in the README code for vue-grid-layout and then I understand how it works, but it works without the .synctoo, which I don't understand.)
What am I missing or misunderstanding?
In javascript objects are passed around by reference, if you must prop an object, clone it and manipulate the clone to avoid it updating the original object.
for example:
props: {
myLayout: Object
},
data() {
return {
layout: {}
}
},
watch: {
myLayout: {
handler(myLayout) {
this.layout = { ...myLayout };
},
immediate: true
}
}

Reseting a config option on vue-flatpickr programatically

I have a vue date component that is composed of a vue-flatpickr-component. When I pass config options in as props, of course, they work as expected, however, if want to change one of the config options which should be possible, it won't propagate down. I'm not a Vue guru, any advice would be helpful.
I'm using a page component in a Laravel app, it shouldn't be relevant, however, just in case someone answers with vuex or vue-router, those won't work here.
Here are the form elements in play from page.vue:
<material-select
name="specialist"
label="Specialist"
default-text="CHOOSE HOMEVISIT SPECIALIST"
:options="staffMembers"
v-model="form.specialist"
:validation-error="form.errors.first('specialist')"
class="mb-4"
></material-select>
<div class="w-1/2">
<material-date
label="Appointment date"
name="appointment_date"
v-model="form.appointment_date"
:validation-error="form.errors.first('appointment_date')"
class="mb-4"
:external-options="{
enable: this.appointmentDates,
}"
></material-date>
<pre>{{ this.appointmentDates }}</pre>
</div>
Here is the computed property driving the config change:
computed: {
appointmentDates(){
if(this.form.specialist !== null){
return this.availableDates[this.form.specialist - 1]
}
return []
},
When a different home visit specialist is chosen, it will update with Vue's reactivity.
I have a computed property changing the config options. Here are the props data and the relevant computed property from the MaterialDate.vue file:
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
export default {
components: {
flatPickr
},
props: {
value: String,
label: String,
validationError: String,
name: {required:true},
optional: {
default: false
},
externalOptions: {}
},
data() {
return {
defaults: {disableMobile: true,},
options: this.externalOptions
}
},
computed: {
config(){
return Object.assign({}, this.defaults, this.options)
},
This will of course never update the enabled dates option because the prop is immutable, I need to get access to the set(option, value) section of the wrapped by vue-flatpickr-component. However, my Vue kungfu is not really strong enough to source dive it to see how I might access it and programatically call set('enabled', [new dates]).
Sometimes, you shouldn't code when you are tired :) But Hopefully this will help someone at some point. I was over thinking this. Data is passed down through props, and if controlling data changes it has to be reflected in the propagated data. Much like v-model with it's value prop.
So instead of binding the config object on this.options which doesn't stay hooked to it's prop value that it was initialized from, the computed function should be calculated from the prop which will change based on the new passed in options prop.
so simply change the computed function to:
computed: {
config(){
return Object.assign({}, this.defaults, this. externalOptions)
},
and remove the data element.
... Elementary
Sorry for the cheese it's late and I feel relieved.

vue-konva: removing specific node from stage working incorrectly

I'm passing down an array of image urls as props to my Konva component, creating a image object for each url, storing that object in a data object (using Vue's $set method to keep the object literal reactive), then using v-for to create a v-image for each image object in my data object. This seems to be working fine, however I'm running into a problem where if I try to remove one of the images, 2 images will be removed. This only happens if the image that I try to remove is not the topmost image. In the console, I'm getting the warning Konva warning: Node has no parent. zIndex parameter is ignored.. My hunch is that this is a result of konva's destroy method clashing with vue's $delete method on a data object used in a v-for. I've been battling with this for hours, and would appreciate any help I can get. Relevant code is below. Thanks!
Parent
<template>
<editor ref="editor" :image-props.sync="images"/>
<button #click="remove">remove</button>
</template>
export default {
components: {
Editor,
},
data() {
return {
images: [url1, url2, etc...],
};
},
methods: {
remove() {
this.$refs.editor.removeSelectedImage();
},
}
child
<template>
<div>
<v-stage>
<v-layer>
<v-group>
<v-image v-for="image in Object.values(images)"
:key="image.id" :config="image"/>
</v-group>
</v-layer>
</v-stage>
</div>
</template>
export default {
props: {
imageProps: Array,
},
data() {
return {
images: {},
selectedNode: null //this gets updated on click
},
watch: {
imageProps() {
this.registerImages();
},
mounted() {
this.registerImages();
},
methods: {
registerImages() {
this.imageProps.forEach(url => {
if (!this.images[url]) {
let img = new Image();
img.src = url;
img.onload = () => {
this.$set(this.images, url, {
image: img,
draggable: true,
name: url,
x: 0,
y: 0,
});
}
}
});
},
removeSelectedLayer() {
let newImageProps = this.imageProps.filter(url => url !== this.selectedImageName);
this.$emit('update:image-props', newImageProps);
this.selectedNode.destroy();
this.$delete(this.images, this.selectedImageName);
this.stageArea.draw();
},
If I inspect the component in Vue devtools, the images object looks correct as well as imageProps, (even the Vue DOM tree looks right with the correct amount of v-images) however the canvas shows 1 less image than it should. Again, this only happens if I remove a image that wasn't initially on top. It seems to function fine if I remove the top-most image.
When you are developing an app with vue-konva it is better not to touch Konva nodes manually (there only rare cases when you need it, like updating Konva.Transformer).
You don't need to call node.destroy() manually. Just an item for your data.
From your demo, I see you are not using key attribute (you are using image.id for that, but it is undefined). It is very important to use keys in such case.
Updated demo: https://codesandbox.io/s/30qpxpx38q

Declaring Reactive Properties (Adding component blocks dynamically from array push)

So, I'm attempting to create a Gutenburg style blog, I'm working on a block creation method. I should caveat - this is entirely my method, if this is the wrong method - great, let me know, but please let me know the correct way! :)
Back to the question. In my project I have the following component.
baseComponent.vue:
<template>
<component v-for="contentBlock in contentBlocks" v-bind:is="contentBlocks.blockComponent" v-bind:key="contentBlock.id" transition="fade" transition-mode="out-in"></component>
</template>
<script>
import CodeBlockComponent from './codeBlockComponent';
export default {
name: 'BaseComponent',
components: {
CodeBlockComponent <!-- Corresponds to the name given in './codeBlockComponent'
},
data: () => ({
contentBlocks: []
})
watch: {
contentBlocks () {
console.log(this.contentBlocks)
}
},
methods: {
addCodeBlock () {
console.log('Code Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'code', 'blockComponent': 'CodeBlockComponent', 'content': '' })
},
addQuoteBlock () {
console.log('Quote Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'quote', 'content': '' })
}
}
}
</script>
N.B. Where above I have stripped the complexity from my template.
Within this same baseComponent I also have buttons which add blocks to the contentBlocks array, where my watch method is definitely finding blocks when added to this array (see below for a screenshot of the console output):
So, everything seems to be going ok - I'm now ready to add block Components. I add the first one, and I receive the following error in the console:
vue.runtime.esm.js?2b0e:587 [Vue warn]: Property or method "CodeBlockComponent" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I took one look at the documentations where it advised to head to, which was here: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
An example of the CodeBlockComponent.vue:
<template>
<div>
<p>HELLO WORLD FROM THE CODE BLOCK!!</p>
</div>
</template>
<script>
export default {
name: 'CodeBlockComponent',
data: () => ({
}),
computed: {
},
watch: {
},
methods: {
}
}
</script>
I took one look, and I'll be 100% honest - I don't quite understand what it is telling me to do...I feel like I have declared a reactive property? Any advise or pointers anyone can give me would be greatly appreciated!
I'm assuming I can't simply import CodeBlockComponent from './codeBlockComponent'; for this sort of dynamic rendering of components? (But, I really don't know at this point)...
I think what you described should work just fine in case if you've imported and declared in
components: { ... }
all possible names which could be found in each contentBlock.blockComponent. Notice those components must have their name: <String> exactly the same as in your contentBlock.blockComponent. I can't see in your example this prop for item from addQuoteBlock by the way.
You also provided link on documentation, but it's about props, which works just fine in your example. Recheck section about dynamic components, maybe it will help: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components (notice links on fiddles)
One more thing to check: does your component in baseComponent.vue wrapped with some ? Component must have single root element. Component with v-for probably won't go.

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'