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.
Related
I am trying to implement popover functionality for one of my mobile app where I need popover with an arrow on different players icon and display info. of a player in a popover. for this after some R & D, I found that I can use this plugin nativescript-popup. But I am unable to see a popup when I try to implement it. Here are my codes. It's not giving any error but it's not opening any popup too.
Home.vue
<template>
<Page actionBarHidden="true">
<Button #tap="openPopup" ref="btn" style="width:100;height:40;"/>
</Page>
</template>
<script>
import { StackLayout } from 'tns-core-modules/ui/layouts/stack-layout';
import { Label } from 'tns-core-modules/ui/label';
import { ScrollView } from 'tns-core-modules/ui/scroll-view';
import { Popup } from 'nativescript-popup';
import Test from './Test'
export default {
components: {
Test
},
data() {
return {
popup: Popup
}
},
methods: {
_showPopup(source, view) {
this.popup = new Popup({
height: 30,
width: 80,
unit: '%',
elevation: 10,
borderRadius: 25
});
this.popup.showPopup(source, view).then(data => {
console.log('aaaa',data);
}).catch(error => {
console.log('aaaa',error);
});
},
openPopup(arg) {
//this._showPopup(this.$refs.btn.nativeView, Test);
const stack = new StackLayout();
stack.height = '100%';
const lbl = new Label();
lbl.text = 'Osei';
stack.addChild(lbl);
const sv = new ScrollView();
sv.content = stack;
this._showPopup(this.$refs.btn.nativeView, sv);
}
}
</script>
Test.vue
<template>
<StackLayout>
<Label text="NativeScript is the bomb.com" color="#ff4801" fontSize="22" textWrap="true"></Label>
</StackLayout>
</template>
Please suggest to me what am I doing wrong? Any help will be appreciated.
Notes: After openPopup() function code update, its working and popup is opening correctly. How can I use it with directly with the Vue component(Test.vue) instead of creating a view inside a function?
This plugin do not have explicit support for Vue so you can not pass Test which I guess a Vue Component, you have to either pass a {N} View or native view instance.
Edit: You could pragramatically create the instance of Vue component and pass the nativeView of root element to your popup.
Playground Sample
Edit: I have posted the higher level version of getting RadListView to work with the grouping function here. The question here addresses just getting RadListView to work in its basic form.
I have been away from Nativescript pro ui for a few months, and now am trying to put together a multilevel list (with categories, items in each category; and user able to hide and show categories with tap). From the discussion here I see that *ngFor is not the stable way to do a multilevel list (though it is the easiest!)
So now I am trying to use the pro ui listview, but the documentation is a few months old and uses the term "RadListView".
Does RadListView still exist? And what is the best documentation for doing a two or three level list in Nativescript Angular?
Details in case helpful:
So I am now trying to use RadListView to do this, but it is not clear to me that RadListView exists at all anymore. The market place listing for Nativescript pro-ui says the old pro-ui has been deprecated, and each item of the pro ui now must be downloaded individually (link).
It lists an npm listing for the pro ui "ListView", that uses the term "ListView". But, when you click on any of the documentation / sample code links in that npm listing, they all use the term "RadListView" (the old formulation).
I am not able to get RadListView to work. Even for the most simple example (which worked a few months ago), if I use RadListView in my component html, the screen is blank.
For example, I am trying to do a multilevel list. Looks like the "grouping" function in RadListView is the (only?) way to do this. I have cut and pasted in the code from here, but it does not work--blank screen with "RadListView" and no data with just "ListView".
example:
ts:
import { ListViewEventData, LoadOnDemandListViewEventData } from "nativescript-ui-listview";
import { RadListViewComponent } from "nativescript-ui-listview/angular";
export class SampleComponent implements OnInit {
public arrayItems = [
{category:'person', name: 'jim', description: 'a very
nice person'},
{category:'jungle animal', name: 'lion', description:
'king of the jungle'}
]
private _myGroupingFunc: (item: any) => any;
constructor (){
this.myGroupingFunc = (item: arrayItems) => {
return item.category;
};
}
ngOnInit(): void {
}
get myGroupingFunc(): (item: any) => any {
return this._myGroupingFunc;
}
set myGroupingFunc(value: (item: any) => any) {
this._myGroupingFunc = value;
}
}...
html:
<StackLayout>
<ListView [items]="arrayItems" enableCollapsibleGroups="true" [groupingFunction]="myGroupingFunc" >
<ng-template tkListItemTemplate let-arrayItem="item" >
<StackLayout>
<Label [text]="arrayItem.name"></Label>
<Label [text]="arrayItem.description"></Label>
</StackLayout>
</ng-template>
</ListView>
</StackLayout>
With this code, copied from here, there are not entries that appear (just the lines of a ListView with nothing inside). If I say "RadListView" instead of "ListView", the screen is entirely blank. I would definitely appreciate if someone has updated code for this action.
Thanks to Ian MacDonald for his help. RadListView does remain the Nativescript pro-ui version of list view. It works for me like this:
$ tns plugin add nativescript-ui-listview
$ npm i
$ tns update //don't know why/what was out of date, but features like the grouping function did not work for me until I ran this.
coolComponent.module.ts: (if using lazy loading, I was only able to get RadListView to work by importing the module directly into the component module)
import { NativeScriptUIListViewModule } from "nativescript-ui-listview/angular";
...
#ngModule({
imports: [
...
NativeScriptUIListViewModule,
]
...
coolComponent.html:
<GridLayout>
<RadListView [items]="cats" >
<ng-template tkListItemTemplate let-cat="item">
<StackLayout>
<Label [text]="cat"></Label>
</StackLayout>
</ng-template>
</RadListView>
</GridLayout>
coolComponent.ts:
import { Component } from "#angular/core";
import { ListViewEventData, RadListView } from "nativescript-ui-listview";
...
export class CoolComponent {
public cats = ['tiger', 'lion', 'puma']
constructor(){
}
}
I'm trying to implement an action bar with a search-bar in it along with one buttton to show/hide the search-bar.
At start the search-bar must be hidden. Only must be showed title of action-bar and a action item to show search-bar. In the view is working as expected, but the problem arises when I go to another view and then go back to this view. The search-bar is not hidden, but neither is the button. I'm using an observable with a boolean to control the items.
When is tapped onSearch search-bar shows up, and when I catch the clear event I set search-bar to be hidden.
Finally, I am also facing that when I go back to this view, the clearEvent event is called two or three times. I don't understand why this behaviour. I have tried in Android so far.
When I launch the app, the action bar looks like the first image.
If I tap on the search icon the action bar is like the second one
And when I go to a different view and go back is like the third one:
Edit, I have changed the code needed but it does not work yet. Here it's a complete view and js file to reproduce the problem:
xml:
<dpg:DrawerPage navigatedTo="onNavigatedTo" navigatingTo="navigatingTo"
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:dpg="nativescript-telerik-ui/sidedrawer/drawerpage"
xmlns:drawer="nativescript-telerik-ui/sidedrawer"
xmlns:component="customcomponents/menu"
xmlns:lv="nativescript-telerik-ui/listview"
loaded="loaded"
>
<ActionBar class="actionB" title="stores" >
<android>
<NavigationButton icon="res://ic_menu_black_24dp" tap="showSlideout" />
</android>
<ios>
<ActionItem icon="res://ic_menu" ios.position="left" tap="showSlideout" />
</ios>
<ActionItem>
<ActionItem.actionView>
<SearchBar id="search" class="blank" backgroundColor="#3C5AFD" hint="Search..." visibility="{{ myShowSearchBar ? 'visible' : 'collapsed' }}" />
</ActionItem.actionView>
</ActionItem>
<ActionItem tap="onSearch"
ios.systemIcon="12" ios.position="right"
android.systemIcon="ic_menu_search" android.position="actionBar" visibility="{{ myShowSearchBar ? 'collapsed' : 'visible' }}"/>
</ActionBar>
<dpg:DrawerPage.sideDrawer>
<drawer:RadSideDrawer id="drawer" drawerSize="270">
<drawer:RadSideDrawer.drawerContent>
<component:menu />
</drawer:RadSideDrawer.drawerContent>
</drawer:RadSideDrawer>
</dpg:DrawerPage.sideDrawer>
</dpg:DrawerPage>
the js file:
var frameModule = require("ui/frame");
var observable = require("data/observable");
var searchBarModule = require("ui/search-bar");
var topmost;
var drawer;
var page;
var observableView = new observable.Observable({myShowSearchBar: false});
exports.loaded = function(args) {
page = args.object;
topmost = frameModule.topmost();
observableView.set("myShowSearchBar", false);
page.bindingContext = observableView;
drawer = page.getViewById("drawer");
var searchBarView = page.getViewById('search');
if (searchBarView.android) {
searchBarView.android.clearFocus();
}
searchBarView.on(searchBarModule.SearchBar.submitEvent, function (args) {
console.log("Search for " + (args.object).text);
observableView.set("myShowSearchBar", false);
});
searchBarView.on(searchBarModule.SearchBar.clearEvent, function (args) {
observableView.set("myShowSearchBar", false);
});
};
exports.showSlideout = function(){
drawer.toggleDrawerState();
}
exports.onSearch = function(args){
console.log("onSearch");
observableView.set("myShowSearchBar", true);
}
args.object is not the page when accessing it in the clearEvent or via onSearch method. And just before that you are creating the observable property myShowSearchBar for the page.bindingContext. So basically, you are handling different binding contexts.
Better (for readability and reuse) to create the separated view-model and access it via a known variable.
e.g.
var myViewModel = new Observable();
exports.loaded = function(args) {
myViewModel.set("myShowSearchBar", false);
page.bindingContext = myViewModel;
}
exports.onSearch = function(args){
myViewModel.set("myShowSearchBar", true);
}
Even better if use separation of concerns and extract the whole view-model in an own file and then import it.
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.')
)
}
}
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!