How to change vue data from outside? - vue.js

I have a function outside of export default object. How can I make Stop button to change into play button after 1 second?
In Short: I want to change playingStatus into false out of scope.
Please check {N} PlayGround link for question
<template>
<Page>
<ActionBar title="Home" />
<ScrollView>
<StackLayout>
<Image v-show="!playingStatus" #tap="startFunc"
src="https://cdn3.iconfinder.com/data/icons/iconic-1/32/play_alt-512.png"
stretch="aspectFit" />
<Image v-show="playingStatus"
src="https://cdn3.iconfinder.com/data/icons/eightyshades/512/24_Stop-512.png"
stretch="aspectFit" />
</StackLayout>
</ScrollView>
</Page>
</template>
<script>
function playForAWhile() {
setTimeout(stop, 1000);
}
function stop() {
playingStatus =
false; // scope error... How to change vue data from outside?
}
export default {
data() {
return {
playingStatus: false
};
},
methods: {
startFunc() {
this.playingStatus = true;
playForAWhile();
}
}
};
</script>
<style scoped>
</style>
Or instead of changing variable from outside, how can I toggle images without vue object.
Thanks in advance.

Related

NativeScript-vue custom switch not working

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);

Why can't apollo get data in first request?

I am using props to get the Id of birds.
When I first start the app and click to enter the second-page, data won't load.
It gives the following error;
JS: [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"
JS:
JS: found in
JS:
JS: ---> <Detail> at components/Detail.vue
JS: <Frame>
JS: <Root>
But after I click on button second time it loads properly.
I have created a repo showing the problem. Please check it here:
github repo
Working graphql playground
<template>
<Page>
<StackLayout>
<Label :text="bird.name" textWrap="true" />
<Button text="get data" #tap="getData" />
</StackLayout>
</Page>
</template>
<script>
import { gql } from "apollo-boost";
export default {
props: ["birdID"],
apollo: {
bird: {
query: gql`
query($id: ID!) {
bird(id: $id) {
id
name
}
}
`,
variables() {
return {
id: this.birdID,
};
},
},
},
methods: {
getData() {
console.log("bird data: ", this.bird);
},
},
mounted() {},
};
</script>
<style></style>
Is there a better way to get data?
Answer was correct
I have updated github repo with solution.
https://github.com/kaanguru/query-data-from-prop
The issue is that your template
<Label :text="bird.name" textWrap="true" />
attempts to display bird.name before your query has completed.
The Vue Apollo documentation shows a simple work-around by defining some initial defaults
You can initialize the property in your vue component's data hook
data: () => ({
bird: {
name: null
}
}),
Alternately, make use of the Loading state feature to conditionally render your components
<Label v-if="$apollo.loading" text="loading" textWrap="true" />
<StackLayout v-else>
<Label :text="bird.name" textWrap="true" />
<Button text="get data" #tap="getData" />
</StackLayout>

RadDataForm update example

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;
}
};

How can I use dynamic components in a ListView

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.

In vuejs, how to pass the html element when click a element?

I want to change a button color when I click that button.
If at jquery, I might use $(this).
<button v-on:click="clicked($event)">Change my color</button>
in script
...
methods: {
clicked:function(event){
???
}
}
How can I use the element which I clicked?
You should think about states for that. Avoid thinking the jQuery way in these situations because it will make your component less reactive to its state.
You could have a class attached to the button that depends on the state of it. When clicked, you change the state and then it would change the class applied to it. Check out the example:
http://jsfiddle.net/taokbg7q/
<button :class="{clicked: btnClicked}" #click="clickBtn"> My Button </button>
// ...
data: {
btnClicked: false
},
methods: {
clickBtn() {
this.btnClicked = true
}
}
// ...
.clicked {
background-color: red;
}
you can use style and class as dynamicly:
<button v-on:click="clicked()" :style="{color:
myColor}">Change my color</button
data(){
return {
myColor: 'black'
}
}
methods: {
clicked (){
this.myColor = 'red';
}
}
Bind a dynamic inline style to button and change its value with an event handler.
Events handlers
Binding inline styles
HTML
<div id="app">
<button v-on:click="clicked" :style="{color: activeColor}">Change my color</button>
</div>
JavaScript
new Vue({
el: '#app',
data: {
activeColor: null
},
methods: {
clicked() {
this.activeColor = 'red'
}
}
});