Nativescript Switch prevent change event firing on initial binding - nativescript

Hi my template is something like the below
<ListView [items]="modules">
<template let-item="item" >
<StackLayout orientation="vertical">
<Switch (checkedChange)="onSwitchModule(item.id,$event)" [checked]="item.active"></Switch>
</StackLayout>
</template>
</ListView>
My controller is
ngOnInit() {
this._moduleService.getUserModules()
.subscribe(
response=>{
this.modules = response.data;
}
)
}
onSwitchModule(itemId) {
console.log(itemID); //Gets called on initial true binding on switch checked
}
The onSwitchModule get called everytime the page loads with item.active is true on any item, how to handle this ?
NOTE: Beginner in Nativescript

What I did to overcome this is I watch for tap events instead of checkedChange:
<Switch (tap)="switchClicked" [checked]="item.active"></Switch>
and in the callback, you can get the current item from bindingContext:
function switchClicked(args) {
const item = args.object.bindingContext.item;
}

I ran into a similar issue: loading up settings data from an API, and having the checked event fire for the value I'd set from the api -- not desirable in my case. I didn't see a great way to prevent events from firing on the initial binding, so I decided to simply ignore events until I knew they were legit events from the user actually using the switch.
I did that by using a property switchReady to keep track of when you want to start recognizing change events. This pattern also keeps the toggle disabled until you're ready to start accepting changes. This makes use of Switch's isEnabled property, see docs here.
Markup
<Switch [checked]="currentSettings.pushTurnedOn" [isEnabled]="switchReady" (checkedChange)="onPushSettingChange($event)" row="0" col="1"></Switch>
Component
export class SettingsComponent implements OnInit {
currentSettings: Settings = new Settings(false)
switchReady: boolean = false
ngOnInit() {
this.getCurrentSettings()
}
public onPushSettingChange(args) {
let settingSwitch = <Switch>args.object
if (settingSwitch.isEnabled) {
// do something with the event/change
} else {
// we aren't ready to accept changes, do nothing with this change
return
}
}
getCurrentSettings() {
this.settingsService.loadCurrentSettings().subscribe(
() => {
this.currentSettings = this.settingsService.currentSettings
// we've applied our api change via data binding, it's okay to accept switch events now
this.switchReady = true
},
err => alert('There was a problem retrieving your settings.')
)
}
}

Related

Cannot capture events from custom component in NS6

I am using NS 6.0 Core. Testing on a physical Android device (have not tried this on iOS yet).
In a nutshell, I have nested components and in the inside component, I want to capture a custom event and pass it to the host component.
Inside component (called TopBar):
<StackLayout padding="10" orintation="horizontal" loaded="onLoaded">
<Label text="" class="wa" fontSize="24" vertcalAlignment="middle" tap="back" />
</StackLayout>
import { StackLayout } from 'tns-core-modules/ui/layouts/stack-layout';
import { EventData } from 'tns-core-modules/ui/core/view/view';
var stack: StackLayout;
let eventData: EventData = {
eventName: "onBackEvent",
object: stack
}
export function onLoaded(args) {
stack = <StackLayout>args.object;
}
export function back() {
stack.notify(eventData);
}
The host component
import { Page } from "tns-core-modules/ui/page/page";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout/stack-layout";
import { EventData, Observable } from "tns-core-modules/data/observable";
var model: Observable = new Observable();
var page: Page;
export function onLoaded(args: EventData) {
page = <Page>args.object;
var topBar: StackLayout = page.getViewById('topBar');
topBar.on('onBackEvent', () => {
console.log('go back');
});
page.bindingContext = model;
}
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:tb="components/shared/top-bar/top-bar" loaded="onLoaded" actionBarHidden="true">
<GridLayout rows="*, 75" columns="*">
<StackLayout class="page-content">
<tb:TopBar id="topBar" height="50"></tb:TopBar>
</StackLayout>
</GridLayout>
</Page>
Any ideas on what I might be missing?
Thanks
{N} automatically trims event names starting with on, hence onBackEvent will be recorded as BackEvent only. So notifying onBackEvent will not have any effect.
In my opinion it makes sense, also when I checked last time Angular didn't use to support event names prefixed with on with event binding. That could also be a reason they had to force this as a standard measure.
So, after hours of trial and error, I manage to make it work.
The issue is that when notifying the client, we have to declare the EventData object as such:
let eventData: EventData = {
eventName: "BackEvent",
object: stack
}
Note that we capture the event by subscribing to onBackEvent but setting up the event name as BackEvent. I am not sure why this works or where in the documentation it is written, but this change did the job.
If anyone has more information about this, please post it here so we can all learn.
Thanks.

Conditional class binding using computed prop Vue 2

I wanted to give some form visual validation cues, so I tried using class binding to do just that. If I use the ternary inline, it doesn't really meet my requirements of what should happen, but when I tried using computed prop, it made all the other components disappear.
If i tried doing it like this: v-bind:class="[(!validation.hasError('form.fullName'))?'has-success':'has-danger noValid']"
It just triggers the animation and the classes once and they stays there. I want to trigger the noValid animation everytime the user clicks my submit button if there's errors in validation.
I'm using simple-vue-validator btw.
Here's the godforsaken component, I use vue now-ui-kit template from Creative Tim as a base and customize my way from there. This is their Form Group Input component, docs here
<fg-input
class="input-lg"
:label="validation.firstError('form.fullName')"
placeholder="Full Name..."
v-model="form.fullName"
addon-left-icon="now-ui-icons users_circle-08"
v-bind:class="{ visualValidation }"
></fg-input>
Button Component: bootstrap-vue, cause their customized button doesn't really serve my purpose
<b-button type="submit" block pill variant="info" #click="submit">Submit Form</b-button>
My computation: noValid is the shaking animation class, has-success and has-danger just changes their appearances.
computed: {
visualValidation: function() {
const successClass = "has-success";
const errorBoi = "has-danger";
const shakeBoi = "noValid";
if (validation.firstError("form.fullName")) {
return errorBoi + " " + shakeBoi;
} else if (!validation.hasError("form.fullName")) {
return successClass;
}
}
}
I thought the variables that i returned would be binded as classes to the form.fullName Model but it's not doing anything, better yet, it made all my other components not rendering.
What should i do here? i'm just starting to learn Vue.js so i don't really understand what's going on here.
Edit from here:
I rewrote the logic to my class binding, and just use method to remove the class on click the components.
here is the updated component:
<fg-input
class="input-lg"
:label="validation.firstError('form.email')"
placeholder="Email Here..."
v-model="form.email"
addon-left-icon="now-ui-icons ui-1_email-85"
v-bind:class=" {'has-success' : isSuccEmail, 'has-danger' : isFailEmail, 'noValid': validNoEmail} "
#click="removeShake()"
></fg-input>
data:
isSuccEmail: false,
isFailEmail: false,
validNoEmail: false
method:
removeShake: function() {
let vm = this;
vm.validNoName = false;
vm.validNoEmail = false;
console.log(vm.validNoEmail);
console.log(vm.validNoName);
},
However, there's currently a bug, where the validator don't validate separately. It gave the class has-success to email even though it was full-name that was successful.
valEmail: function() {
let vm = this;
vm.$validate("form.email").then(function(success) {
if (success) {
vm.isFailEmail = false;
vm.isSuccEmail = true;
} else if (!success) {
vm.isSuccEmail = false;
vm.isFailEmail = true;
vm.validNoEmail = true;
} else {
alert("Fatal Error");
}
});
},
valName: function() {
let vm = this;
vm.$validate("form.fullName").then(function(success) {
if (success) {
vm.isFailName = false;
vm.isSuccName = true;
} else if (!success) {
vm.isSuccName = false;
vm.isFailName = true;
vm.validNoName = true;
console.log(vm);
} else {
alert("Fatal Error");
}
});
}
The $validate is a function of simple-vue-validator, the docs.
Submit function is just calling those two functions above.
I think this because of the promise call, is there a way to call the $validate() without promise?
There are a few problems here.
Firstly, while templates don't require you to use this. you still need to use it within your JS code. You should be seeing an error in the console, or maybe even at compile time depending on how you have linting configured.
if (validation.firstError("form.fullName")) {
What is validation? I assume that should be this.validation. Likewise a couple of lines further down.
Your next problem is here:
v-bind:class="{ visualValidation }"
The braces here are creating an object literal, so it's equivalent to this:
v-bind:class="{ visualValidation: visualValidation }"
This will be conditionally adding the string 'visualValidation' as a class , which isn't what you want. Get rid of the braces:
v-bind:class="visualValidation"

FIre event on toggle of v-show

I am using Laravel with Vuejs. i want to fire an event on component show or hide. how to achieve this?
<album-images v-show ="!gallery"
:album = "album"
:image-arr = "imageArr"></album-images>
You can use a watcher:
watch: {
gallery: {
handler: value => {
this.$emit('gallery-toggled', value)
}
}
}
or a computed:
computed: {
toggle () {
this.$emit('gallery-toggled', this.gallery)
}
}
If you want to do something after the view has updated (i.e the component is actually visible or hidden), you may have to wrap the $emit in nextTick(), see Vue.nextTick( [callback, context] ).
It depends on the exact order of updating the watch or computed, but if you are getting unexpected results in the receiver of the event, this is an option to try.
See the discussion here.
this.$nextTick(function() {
this.$emit('galleryVisible', this.gallery)
});

AsyncStorage - error (React Native)

I have a problem with persisting user's data in a react-native application I am currently working on with my team. We have an app with a list of events and when you click on an event, it opens up the event details page and shows a menu to choose yes/no/maybe (attendance menu) which the user can toggle to select whether or not they are attending the event. Our code looks like this:
import React from 'react';
import { AsyncStorage, Text, View, StyleSheet, Picker } from 'react-native';
export class PickerMenu extends React.Component {
state = { choice: '' }
componentDidMount = () => AsyncStorage.getItem('choice').then((value) => this.setState({ 'choice': value }))
setName = (value) => {
AsyncStorage.setItem('choice', value);
this.setState({ 'choice': value });
}
render() {
return (
<View>
<Picker selectedValue = {this.state.choice} onValueChange = {this.setName} itemStyle = {styles.item}>
<Picker.Item label = "Yes" value = "yes" />
<Picker.Item label = "Maybe" value = "maybe" />
<Picker.Item label = "No" value = "no" />
</Picker>
</View>
);
}
}
We are running the build on Xcode. We want the user's choice to still be present after the page has been refreshed or the app has closed - this works HOWEVER,the choice that the user has selected, stays the same for every event. E.g. if they select 'Maybe' for event 1, the selection 'maybe' is highlighted for every other event in our app.
We had an idea of grabbing unique event IDs and implementing this into the AsyncStorage function but we are unsure of how to do this.
Any help will be great and much appreciated - thank-you!
In this case you need to use the async/await to store at the async storage
like this:
setName = async (value) => {
await AsyncStorage.setItem('choice', value);
this.setState({ 'choice': value });
}
Hope i have helped!
Some useful links for you:
https://facebook.github.io/react-native/docs/asyncstorage.html

Nativescript Observable.propertyChangeEvent

I am trying to create some customer formatting on a field (to reproduce a masked text box functionality).
I have an observable and I am capturing the propertyChange Event. My question is: Can I modify the value of the observed property inside the event handler without entering in an infinite loop?
Here is my code:
model.customer.addEventListener(Observable.propertyChangeEvent, function(data) {
if (data.propertyName.toString() === 'homePhone') {
//Here is where I would like to change the value without triggering the event again
//The below code does not seem to be working
data.value = formatPhone(data.value);
}
});
I looked at https://github.com/bthurlow/nativescript-maskedinput, but unfortunately this module does not support databinding.
Thank you. Appreciate your help.
I have tested nativescript-maskedinput in a sample application and I was able to bind text property of this custom view. In regard to that if you add addEventListener and want to update for example TextField text property manually I think that property won't be updated properly. In addition I am attaching some sample code.
main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:mi="nativescript-maskedinput" navigatingTo="navigatingTo">
<StackLayout>
<Label text="Tap the button" class="title"/>
<mi:MaskedInput mask="1-999-999-9999? x999" hint="1-555-555-5555" text="{{ masktext }}" placeholder="#" />
<Button text="tapToView" tap="onTap" />
</StackLayout>
</Page>
main-page.js
var observable_1 = require("data/observable"); // Event handler for Page "navigatingTo" event attached in main-page.xml
var newObservable = new observable_1.Observable();
function navigatingTo(args) {
// Get the event sender
var page = args.object;
newObservable.set('masktext', '');
page.bindingContext = newObservable;
}
exports.navigatingTo = navigatingTo;
function onTap(args) {
var newvalue = newObservable.get('masktext');
console.log('newValueget + ' + newvalue);
}
exports.onTap = onTap;
I am not sure if it is possible to override the getter, but if it is not possible you can do this:
function isPhoneFormatted(phone) {
//your algorithm wich return true or false
}
model.customer.addEventListener(Observable.propertyChangeEvent, function(data) {
if (data.propertyName.toString() === 'homePhone') {
//Here is where I would like to change the value without triggering the event again
//The below code does not seem to be working
if (!isPhoneFormatted(data.value)) {
data.value = formatPhone(data.value);
}
}
});
Notice that it is not well tested!

Resources