I am looking for an example on the use of RadDataForm / Vue to update data in a vuex store. I have the data populating no problem, but I cannot find anywhere in the current documentation that explains how to trigger an update function when the data is updated. here is a simple example, how would I trigger the save() function when the data is updated and I click done in the form?
<StackLayout>
<StackLayout orientation="vertical" backgroundColor="lightgray">
<RadDataForm
:source="record"
/>
</StackLayout>
<script>
export default {
data() {}
},
methods:{
save(){
console.log('save')
},
}
computed:{
record (){
return this.$store.getters.record;
}
},
};
```
You just need to add a watcher to a local property, i recommend using mapState from Vuex.
You can have something like this:
computed: mapState({
record: this.$store.getters.record;
})
And also a watcher that sets a callback to a function, based on if a value has changed or not.
watch: {
record: function () {
// something to run when record changes
}
In summary, you have a local mapped variable from your store and a watcher that acts on whether the information has changed or not. Hope this helps
See: MapState Helper
See: Computed properties and watchers
Ok, I found the answer. This is not documented anywhere I can find in the nativescript/vue docs. I found: RadDataForm Commit Documentation. So, using that, I modified to (keeping mapState from Luis.) I added:
v-on:propertyCommitted="save" to RadDataForm
So, this works:
`<StackLayout>
<StackLayout orientation="vertical" backgroundColor="lightgray">
<RadDataForm
:source="record"
v-on:propertyCommitted="save"/>
</StackLayout>
<script>
export default {
data() {}
},
methods:{
save(){
console.log('save')
},
}
computed: mapState({
record(state){
return record = this.$store.getters.record;
}
};`
Here is a more detailed answer for this. obviously, I would set the store value in the save function.
<StackLayout>
<StackLayout orientation="vertical" backgroundColor="lightgray">
<RadDataForm
id="myDataForm"
:source="record"
v-on:propertyCommitted="save"/>
</StackLayout>
<script>
import { mapState } from 'vuex'
import { getViewById } from "tns-core-modules/ui/core/view";
export default {
data() {}
},
methods:{
save(){
console.log('save')
let data = args.object
var dataform = getViewById(data, "myDataForm");
console.log(dataform.editedObject)//<--updated Data Here
},
}
computed: mapState({
record(state){
return record = this.$store.getters.record;
}
};
Related
I have custom data points for each event (for example event.organizer). I want this included in the calendar within the event section.
I looked over the documentation (https://fullcalendar.io/docs/eventDataTransform), but it is pretty slacking, especially in regards to the Vue component (https://fullcalendar.io/docs/vue)
Can anyone assist in how the Vue Component should look? Do I need to add a prop? The following did not work.
import { resources, events } from "/mockdata.js";
<FullCalendar #eventDataTransform="eventDataTransform" :options="calendarOptions" />
export default {
components: {
FullCalendar,
},
data() {
return {
calendarOptions: {
plugins: [dayGridPlugin, interactionPlugin, resourceTimelinePlugin],
initialView: "resourceTimeline",
initialDate: "2021-06-18",
resources,
events,
},
};
},
methods: {
eventDataTransform: function(json) {
console.log(json)
},
},
};
Found within example. https://github.com/fullcalendar/fullcalendar-example-projects/blob/master/vue3-typescript/src/Demo.vue#L115
<FullCalendar :options="calendarOptions">
<template v-slot:eventContent='arg'>
<i>{{ arg.event.extendedProps.organizer }}</i>
</template>
</FullCalendar>
I'm trying to make a re-usable custom Switch component, but my v-model is not working. Here's the situation:
My component correctly emits the tap event
My component updates its data correctly
However, the parent data doesn't get updated despite being hooked to the child with a v-model
Here are some snippets showing my setup:
// MY COMPONENT
<template>
<Switch
dock="right"
backgroundColor="red"
offBackgroundColor="yellow"
v-model="model"
/>
</template>
<script>
export default {
name: "SettingsSwitch",
props: {
value: {
type: Boolean,
required: true
}
},
data() {
return {
model: this.value
};
},
watch: {
model: function(value) {
this.$emit("tap", value);
}
}
};
</script>
In the example below, I have 2 Switches:
- A normal one that works and whose data gets updated
- The one linked to my child component and the data does not get updated
// My parent view
<template>
<ViewWrapper viewTitle="Change your settings" pageColor="tertiary">
<StackLayout class="content-wrapper">
<StackLayout class="category-wrapper">
<DockLayout class="field-wrapper" stretchLastChild="true">
<Switch v-model="myCheck" dock="right" #tap="test" />
<StackLayout>
<Label text="Mon label" class="field-label" />
<Label text="Ma valeur actuelle" class="field-value" />
</StackLayout>
</DockLayout>
<DockLayout class="field-wrapper" stretchLastChild="true">
<SettingsSwitch v-model="myCheck2" #tap="test2" />
<StackLayout>
<Label text="Mon label" class="field-label" />
<Label text="Ma valeur actuelle" class="field-value" />
</StackLayout>
</DockLayout>
</StackLayout>
</StackLayout>
</ViewWrapper>
</template>
<script>
import { ViewWrapper } from "#/components";
import SettingsSwitch from "./SettingsSwitch";
export default {
name: "Settings",
components: { ViewWrapper, SettingsSwitch },
data() {
return {
myCheck: false,
myCheck2: false
};
},
methods: {
test() {
console.log(this.myCheck);
},
test2(v) {
console.log("emit", v); // <--- Value changes each time
console.log("model", this.myCheck2); // <--- Value never changes
}
}
};
</script>
I've tried playing around with different setups, like removing the watch and directly calling a method that does the $emit but it doesn't seem to fix the issue
Any thoughts?
So I managed to fix my issue. My mistake was that I was emitting tap instead of input in my component. I feel stupid but I'm leaving this up instead someone struggles like I did
The bottom line is:
You can use v-model on any tag, but in order to update its value, it will need to receive an "input" event with some data. That's why my child component must perform this.$emit("input", value);
I'm new to Vue, so it's likely I misunderstand something. I want to call a vuex action inside a local function in App.vue like so:
<template>
<div id="app">
<button #click="runFunction(1)">Test</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
doAction({ ID: input });
}
</script>
The action calls a mutation in store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
IDs: []
},
mutations: {
doAction: (state, id) => { state.IDs.push(id) }
},
actions: {
doAction: ({ commit }, id) => { commit('doAction', id) }
}
})
I also have a main.js that sets up the vue:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
The error I'm getting is:
ReferenceError: doAction is not defined
at runFunction
How can I call the mapped action inside a function? Version is Vue 2.6.10
There are several problems with defining runFunction as a 'local function':
function runFunction(input){
doAction({ ID: input });
}
Firstly, this is just a normal JavaScript function and the usual scoping rules apply. doAction would need to be defined somewhere that this function can see it. There is no magic link between this function and the component defined in App.vue. The function will be accessible to code in the component, such as in buttonClicked, but not the other way around.
The next problem is that it won't be available within your template. When you write runTemplate(1) in your template that's going to be looking for this.runTemplate(1), trying to resolve it on the current instance. Your function isn't on the current instance. Given your template includes #click="runFunction(1)" I'm a little surprised you aren't seeing a console error warning that the click handler is undefined.
mapActions accesses the store by using the reference held in this.$store. That reference is created when you add the store to your new Vue({store}). The store may appear to be available by magic but it's really just this.$store, where this is the current component.
It isn't really clear why you're trying to write this function outside of the component. The simplest solution is to add it to the methods. It'll then be available to the template and you can access doAction as this.doAction.
To keep it as a separate function you'd need to give it some sort of access to the store. Without knowing why you want it to be separate in the first place it's unclear how best to achieve that.
Of course it is not defined outside your instance .... you have to import the exported store from store.js on your function component :
<script>
import { mapActions } from 'vuex'
import store from 'store.js'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
store.commit({ ID: input });
}
</script>
I am trying to populate a NativeScript-Vue ListView with templates that contain components of which their types are not known ahead of time. For example, this code does not work as NativeScript does not have a 'component' element but this suggests what I am trying to accomplish:
<ListView for="component in components">
<v-template>
<component :is="component" />
</v-template>
</ListView>
computed: {
components () {
return ['Label', 'Button'];
}
}
Yes, I know you can use if="" in a v-template, but in this case I do not know ahead of time what components need to be loaded in the ListView. In my case I am loading global components in a plugin, and these components will be referenced in the ListView.
Thanks #Manoj. Those wise words made me think, the template can't be dynamic but the contents of the v-template can be. Maybe not for everyone, but this code works for me:
// App.vue
<template>
<Page>
<ActionBar title="Welcome to NativeScript-Vue!"/>
<GridLayout columns="*" rows="400,*">
<ListView ref="lv" for="item in items">
<v-template>
<v-component :type="item.type" :options="item.options" />
</v-template>
</ListView>
</GridLayout>
</Page>
</template>
<script lang="ts">
import Vue from 'nativescript-vue'
import { Component } from 'vue-property-decorator'
import VComponent from './VComponent.vue'
#Component({
components: {
VComponent
}
})
export default class App extends Vue {
get items () {
return [
{type: 'Label', options: [{key: 'text', value: 'I am a Label'}, {key: 'color', value:'red'}] },
{type: 'Button', options: [{key: 'text', value:'I am a Button!'}]}
]
}
}
</script>
// VComponent.vue
<template>
<StackLayout ref="myLayout">
</StackLayout>
</template>
<script lang="ts">
import Vue from 'nativescript-vue'
import { Component, Prop } from 'vue-property-decorator'
import { StackLayout } from 'tns-core-modules/ui/layouts/stack-layout'
import { Label } from 'tns-core-modules/ui/label'
import { Button } from 'tns-core-modules/ui/button'
const classByClassName = {
'Label': Label,
'Button': Button
}
#Component
export default class CoolTemplate extends Vue {
#Prop() type!: string;
#Prop() options;
mounted () {
if (this.type) {
const myLayout = <StackLayout>((<Vue>this.$refs.myLayout).nativeView)
const component = new classByClassName[this.type]
for (var i = 0; i< this.options.length; i++) {
const option = this.options[i];
component[option.key] = option.value
}
myLayout.addChild(component)
}
}
}
</script>
Your template can not be dynamic, that's the whole point of using ListView - keeping them static, so they can be reused as needed. If you like to see different components based on data then you must use multiple templates.
Read more on docs.
How can I use a computed property in the data or emit it via bus?
I have the following vue instance, but myComputed is always undefined but computedData is working correctly.
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
Unfortunately, it is impossible to use computed property in data because of component creation timing: data evaluates Before computed properties.
To make things as simple as possible, just do the work in watcher, unless you want to emit the changes to different components or there're a lot of variables you want to notify, then you may have to use Vuex or the event bus:
var vm = new Vue({
data(){
return{
myComputed: '',
computedData: 'Hello World'
}
},
created() {
this.myComputed = this.computedData;
},
watch: {
computedData() {
this.myComputed = this.computedData;
}
}
});
Computed is already accessible in the template using {{ }}.
But you can use the
watch:{
//your function here
}
instead of computed
If you are using computed/reactive objects then it should be inside the computed and not inside the data.
Simply change your code to use computed instead of data
var vm = new Vue({
data(){
return{}
},
computed: {
computedData(){
return 'Hello World'
},
myComputed(){
return this.computedData
}
}
})
you are trying to use data as computed and this shall not be.
data doesn't act like computed object.
and it's not because of component creation timing. What if we changed the component creation timing ? this will not solve anything as data will take only the first computed value(only one) and will not update after.
you can work just with the computed function
var vm = new Vue({
data(){
return{
//is not necessary
}
},
computed: {
computedData(){
return 'Hello World'
}
}
})
and in your template
<template>
<div>{{ computedData }}</div>
</template>
You are over-coding it. Computed props are accessible in the same manner as data props in your template.
var vm = new Vue({
computed: {
myComputed(){
return 'Hello World'
}
}
})
In the template you have access to this just like you do to data:
<template>
<div>{{ myComputed }}</div>
</template>
https://v2.vuejs.org/v2/guide/computed.html
Try to convert the computed in a method
var vm = new Vue({
data(){
return{
myComputed: this.computedData
}
},
methods: {
computedData(){
return 'Hello World'
}
}
})
This is simple and it works (NOT reactive), but has a cost:
https://medium.com/notonlycss/the-difference-between-computed-and-methods-in-vue-js-9cb05c59ed98
computed is not available at the time data gets initialized.
If it should be a one-time thing (and NOT reactive), you could achieve this by setting the data at the moment where the computed property is available by using the created() hook:
export default {
data: () => ({
myDataBackend: '',
}),
computed: {
computedData () {
return 'Hello World'
}
},
created() {
this.$set(this, 'myDataBackend', this.computedData)
}
}
Futher reading: Vue Documentation on Lifecycle Hooks
In case you are trying to work with v-model:
You could also use :value and some event like #change or #keyup in the element instead.
:value is the value which the input-element initially works with
After writing some letter in the input field, the #keyup event changes the data.
Typically, events carry the updated form value in target.value
The changeMyData method sets the value
the computed property listens to the data change and the :value of the input field gets updated.
Note: I used data as a data store. But you could also use for example vuex instead.
<template>
<div>
<input
type="text"
:value="computedData"
#keyup="changeMyData"
/>
<p>{{myDataBackend}}</p>
</div>
</template>
<script>
export default {
data: () => ({
myDataBackend: 'Hello World'
}),
methods: {
changeMyData(evt) {
this.$set(this, 'myDataBackend', evt.target.value)
console.log('Changed the value to: ' + evt.target.value)
}
},
computed: {
computedData () {
return this.myDataBackend
}
}
}
</script>