Navigation Drawer with NativeScript - nativescript

I'd like create a type of sidemenu with NativeScript, but I don't know how.
How to create a Navigation Drawer with NativeScript?
Exists any module what can do this?

There is no Drawer for now, but it's in the working AFAIK.
Meanwhile, you can check the official repo for NativeScript.
https://github.com/NativeScript/NativeScript/tree/master/apps/TelerikNEXT
Check the TelerikNext app.

Telerik announced Telerik UI for Nativescript as a plugin today.
The plugin now contains side drawer and data visualization tools. It's a commercial product but (only) the side drawer function inside it is free.
You can refer to this doc for detail information.

The drawer is here. Check out TJ Vantoll's boilerplate project to get you started...
https://github.com/tjvantoll/nativescript-template-drawer
Or a TypeScript version of that same template from Ignacio Fuentes...
https://github.com/ignaciofuentes/nativescript-template-drawer-ts

Here's an example of how to create an animated drawer menu with NativeScript 1.3 (which added an animation framework): https://github.com/emiloberg/nativescript-animated-sidebar-menu-example

I don't think it's yet available I think you need to create your own module as a view and do your own navigation (open, close).
However right out of the box I haven't found anything else in their docs.
The other thing I tried is adding a button in the header, and still I only managed to change the color of the header so I think you need to wait some more time to do these simple stuff.
Ref : I'm developing a demo app based on Buxfer and NativeScript.
Source Code: https://github.com/chehabz/Buxfer-NativeScript

I am uploading my working code. It is in Nativescript + Angular 2
drawer.html
<RadSideDrawer [drawerLocation]="currentLocation" [transition]="sideDrawerTransition"tkExampleTitle tkToggleNavButton>
<StackLayout tkDrawerContent class="sideStackLayout">
<StackLayout class="sideTitleStackLayout">
<Label text="Navigation Menu"></Label>
</StackLayout>
<StackLayout class="sideStackLayout">
<Label text="Primary" class="sideLabel sideLightGrayLabel"></Label>
<Label text="Social" class="sideLabel"></Label>
<Label text="Promotions" class="sideLabel"></Label>
<Label text="Labels" class="sideLabel sideLightGrayLabel"></Label>
<Label text="Important" class="sideLabel"></Label>
<Label text="Starred" class="sideLabel"></Label>
<Label text="Sent Mail" class="sideLabel"></Label>
<Label text="Drafts" class="sideLabel"></Label>
</StackLayout>
</StackLayout>
<StackLayout tkMainContent>
<Label [text]="mainContentText" textWrap="true" class="drawerContentText"></Label>
<Button text="OPEN DRAWER" (tap)=openDrawer()></Button>
</StackLayout>
drawer.component.ts
import {Component , OnInit, Input,ElementRef, ViewChild,ChangeDetectionStrategy,ChangeDetectorRef} from "#angular/core";
import { Router } from "#angular/router";
import { Page } from "ui/page";
import {View} from "ui/core/view";
import {Label} from "ui/label";
import {RadSideDrawerComponent, SideDrawerType} from 'nativescript-telerik-ui/sidedrawer/angular';
import {DrawerTransitionBase, SlideInOnTopTransition} from 'nativescript-telerik-ui/sidedrawer';
import * as sideDrawerModule from 'nativescript-telerik-ui/sidedrawer/';
#Component({
selector: "hello",
templateUrl: "shared/hello/app.hello.html",
styleUrls: ["shared/hello/hello.css", "css/app-common.css"],
})
export class HelloComponent implements OnInit{
private _currentNotification: string;
private _sideDrawerTransition: sideDrawerModule.DrawerTransitionBase;
constructor(private _page: Page, private _changeDetectionRef: ChangeDetectorRef) {
this._page.on("loaded", this.onLoaded, this);
}
#ViewChild(RadSideDrawerComponent) public drawerComponent: RadSideDrawerComponent;
private drawer: SideDrawerType;
ngAfterViewInit() {
this.drawer = this.drawerComponent.sideDrawer;
this._changeDetectionRef.detectChanges();
}
ngOnInit() {
}
public onLoaded(args) {
this._sideDrawerTransition = new sideDrawerModule.PushTransition();
}
public get sideDrawerTransition(): sideDrawerModule.DrawerTransitionBase {
return this._sideDrawerTransition;
}
public get currentNotification(): string {
return this._currentNotification;
}
public openDrawer() {
console.log("openDrawer");
this.drawer.showDrawer();
}
public onDrawerOpening() {
console.log("Drawer opening");
this._currentNotification = "Drawer opening";
}
public onDrawerOpened() {
console.log("Drawer opened");
this._currentNotification = "Drawer opened";
}
public onDrawerClosing() {
console.log("Drawer closing");
this._currentNotification = "Drawer closing";
}
public onDrawerClosed() {
console.log("Drawer closed");
this._currentNotification = "Drawer closed";
}
}
Don't forget to import globally in app.module.ts below:
import { SIDEDRAWER_DIRECTIVES } from "nativescript-telerik-ui/sidedrawer/angular";
and in declarations array add SIDEDRAWER_DIRECTIVES:
declarations: [
SIDEDRAWER_DIRECTIVES,
AppComponent,
...navigatableComponents
]

Check this: https://www.nativescript.org/blog/using-cross-platform-native-sidedrawer-component-in-nativescript
They now have the RadSideDrawer component
http://docs.telerik.com/devtools/nativescript-ui/Controls/NativeScript/SideDrawer/overview
Hope this helps!

Related

Nativescript (tap) event on a custom Angular component

I'm trying to bind a (tap) event on a custom Angular component in nativescript.
I created a custom component called 'ns-custom' and tried to bind the (tap) event to it. But it doesn't work.
In the custom component I'm doing this:
<StackLayout>
<Label text="Title"></Label>
<Label text="some text"></Label>
</StackLayout>
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'ns-custom',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomComponent{
constructor() { }
}
And in the parent element I'm doing this:
<ns-custom (tap)="onCustomComponentClick()"></ns-custom>
onCustomComponentClick() {
console.log('custom-component was tapped');
}
I expect the (tap) event to fire when I tap the custom component, but it does not. I built the same structure in pure Angular, and the (click) event does fire if put to a custom component.
I tried to propogate the (tap) event from the custom component like below, but then it fired twice (as expected because the tap event would propogate up to the parent component if I don't use event.stopPropagation()):
<StackLayout (tap)="emitTap()">
<Label text="Title"></Label>
<Label text="some text"></Label>
</StackLayout>
#Component({
selector: 'ns-custom',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomComponent{
#Output() tap = new EventEmitter();
emitTap() {
this.tap.emit();
}
}
And then catch the event on the parent component:
<ns-custom (tap)="onCustomComponentClick()"></ns-custom>
onCustomComponentClick() {
console.log('custom-component was tapped');
}
But now the tap event fires twice. I can solve it by changing the EventEmitter name to something other than 'tap' (for ex. #Output() childTapped = new EventEmitter(); and <ns-custom (childTapped)="onCustomComponentClick()"></ns-custom>) or pass the $event object on tap and then use event.stopPropagation(), but this is not elegant at all.
Any idea how to solve this simple problem in an elegant way?
This is basically answered by #mick-morely in the comments but I thought I would write up a more descriptive example and why I think it is a useful way of doing it.
You basically need to create a custom event for your custom component. While it seems tedious not to be able to re-use the tap event, it can actually improve your code quality, making it more descriptive.
So if I have a custom HoleComponent describing a "Hole", it can look like this:
Hole Template
<GridLayout rows="*" columns="*" (tap)="emitTap()">
<Label row="0" text="A very deep hole"></Label>
</GridLayout>
Hole Code
#Output() holeTap = new EventEmitter();
emitTap() {
this.holeTap.emit();
}
This Hole component can then be used by a parent like this:
Parent template
<Hole (holeTap)="onHoleTap()"></Hole>
Making the event explicitly named actually helps to make the code more readable imho. It also forces the developer to think more about the Domain they are working with which helps in conforming to the ubiquitous language if working with DDD is your thing.

Progress indicator not stopping inside TabView in android platform nativescript

Iam working the nativescript application.I have TabView with three tabs,in each tab i have progress indicator for showing api is loading and also hide the indicator after response came from api.In ios platform the indicator hide perfectly but android platform the indicator was not hide.How to solve the issue?
My TabView html code is:
<TabView [(ngModel)]="tabSelectedIndex" (selectedIndexChange)="onIndexChanged($event)" selectedTabTextColor="#40053e" androidTabsPosition="bottom" androidOffscreenTabLimit="0" iosIconRenderingMode="alwaysOriginal">
<StackLayout *tabItem="{title: 'Security', iconSource: 'res://lock'}">
<StackLayout>
<security></security>
</StackLayout>
</StackLayout>
<StackLayout *tabItem="{title: 'Personal', iconSource: 'res://boy'}">
<StackLayout>
<personal></personal>
</StackLayout>
</StackLayout>
<StackLayout *tabItem="{title: 'Payment', iconSource: 'res://card'}">
<StackLayout>
<payment></payment>
</StackLayout>
</StackLayout>
</TabView>
My TabViw ts code:
import { OnInit, Component } from "#angular/core";
import { TabView } from "tns-core-modules/ui/tab-view";
import { Page } from "ui/page";
#Component({
moduleId: module.id,
selector: "profiletab",
templateUrl: 'profiletab.component.html',
styleUrls: ['profiletab.component.css']
})
export class ProfileTabComponent implements OnInit {
private tabSelectedIndex:number;
private selectedTabName;
tabview:TabView;
constructor(private page:Page) {
this.tabSelectedIndex = 0;
this.selectedTabName = "Security Info";
}
ngOnInit(){
// this.tabview=<TabView>this.page.getViewById("tabcontrol");
// this.tabview.androidOffscreenTabLimit=1;
}
onIndexChanged(args){
let tabView = <TabView>args.object;
switch (tabView.selectedIndex) {
case 0:
this.selectedTabName = "Security Info";
break;
case 1:
this.selectedTabName = "Personal Info";
break;
case 2:
this.selectedTabName = "Payment Info";
break;
default:
break;
}
}
}
I will create indicator inside the tab1,tab2,tab3 like:
ngOnInit(){
this.loadCountries();
}
loadCountries(){
var _this = this;
if (!this.conn.getConnectionStatus()) {
Toast.makeText("Please check your internet connection").show();
return;
}
Indicator.showIndicator("Loading Please Wait...");
this.authenticationService.Get_Countries_List().then(function(response){
_this.renderData(response);
}
});
}
renderData(values){
this.authenticationService.Get_States_List(_this.dataService.userProfile.contact[0].country).then(function(response){
Indicator.hideIndicator();
}
}
You seem to create different instances while showing & hiding indicator. You must call hide of same LoadingIndicator instance on which you did call show.
Source: https://github.com/NathanWalker/nativescript-loading-indicator/issues/58
Also here is an example that demonstrates how you can do this without a plugin and always keep reference of current loading indicator no matter how many times you call show / hide simultaneously. You may even apply similar logic to maintain your LoadingIndicator instance with plugin. Refer ui-helper.ts and home-page.ts.
FYI, by nature of platform iOS seems to use a singleton which is why it works there even if you create multiple LoadingIndicator. But with Android a new progress dialog is created every time when you call show, so you must call hide on same instance.
Also as you are using Angular, I would suggest you to take advantage of framework features like Http Interceptors so you wouldn't have to create / handle indicator for each tab or each api call separately.

Hide tab buttons on Nativescript-Angular TabView

I'm trying to find a way to remove the tab buttons on a element with an Angular 6 app but with no avail so far. Basically, I only want to keep the Tab contents and their swipe functionality.
Apparently there are specific android and iOS methods you can use but I'm unsure how to do that.
<TabView [(ngModel)]="tabSelectedIndex" (selectedIndexChanged)="onSelectedIndexChanged($event)" (loaded)="tabViewLoaded($event)">
<ng-container *ngFor="let article of articles" #tabView>
<StackLayout *tabItem="{title: article.id}">
<StackLayout>
<NewsDetails></NewsDetails>
</StackLayout>
</StackLayout>
</ng-container>
</TabView>
On my .ts file I can find a reference to the element like this:
#ViewChild("tabView") tabView: ElementRef;
ngAfterViewInit() {
console.dir(this.tabView.nativeElement);
}
But I have no idea what to do from now on. Any ideas? All previous questions regarding this have not worked.
Here is a sample playground link: https://play.nativescript.org/?template=play-ng&id=iK9ZTM
Use the code below with the loaded event of TabView.
onTabViewLoaded(event: EventData) {
const tabView = <TabView>event.object;
if (isIOS) {
tabView.viewController.tabBar.hidden = true;
}
if (isAndroid) {
const tabLayout = tabView.nativeViewProtected.tabLayout;
tabLayout.getLayoutParams().height = 0;
tabLayout.requestLayout();
}
}
I recently did that for a sample work I posted in Uplabs

Xamarin Forms Conditional statements

Hello guys: Am stuck with my code here. There is a point in my app where I need to notify users to register and sign in order to save their preferences. I have been trying to add a display alert inside a ViewModel but it's not working. Please help I am stuck.
ApproveViewModel
namespace MyApp.ViewModels
{
public class ApproveViewModel
{
private DataService dataService = new DataService();
public Oppotunity SelectedOppotunity { get; set; }
public ICommand SaveCommand => new Command(async () =>
{
await dataService.PostSaveOppotunity(SelectedOppotunity, Settings.AccessToken);
});
public ApproveViewModel()
{
SelectedOppotunity = new Oppotunity();
}
}
}
ApprovePage.xaml
<ScrollView>
<StackLayout Padding ="15">
<Label Text ="{Binding SelectedOppotunity.Title}"/>
<Label Text ="{Binding SelectedOppotunity.Description }"/>
<Label Text ="{Binding SelectedOppotunity.Organisation}"/>
<Label Text ="{Binding SelectedOppotunity.Venue }"/>
<Label Text ="{Binding SelectedOppotunity.Eligibility}"/>
<Label Text ="{Binding SelectedOppotunity.Benefits}"/>
<Label Text ="{Binding SelectedOppotunity.Province}"/>
<Label Text ="{Binding SelectedOppotunity.Country}"/>
<Label Text ="{Binding SelectedOppotunity.OppotunityLink}"/>
<Label Text ="{Binding SelectedOppotunity.Category}"/>
<Label Text ="{Binding SelectedOppotunity.Deadline}"/>
<!--
<Switch IsToggled ="{Binding SelectedOppotunity.IsApproved}"></Switch>
-->
<Button Text ="Apply" BackgroundColor ="#A91717" TextColor ="White"
Command ="{Binding SaveCommand }"/>
</StackLayout>
The code i wish to invoke on saving:
if (!string.IsNullOrEmpty(Settings.AccessToken))
{
// Implement the SaveCommand from the ViewModel;
}
// Go to Login form to get an access token
else if (!string.IsNullOrEmpty(Settings.Username) &&
!string.IsNullOrEmpty(Settings.Password))
{
MainPage = new NavigationPage(new Login());
}
else
{
//Register first
MainPage = new NavigationPage(new NewRegisterPage());
}
Solution:
You can use MessageCenter subscriptions to work through this problem.
Xamarin.Forms MessagingCenter enables view models and other components
to communicate with without having to know anything about each other
besides a simple Message contract.
Subscribe - Listen for messages with a certain signature and perform
some action when they are received. Multiple subscribers can be
listening for the same message.
Send - Publish a message for listeners to act upon. If no
listeners have subscribed then the message is ignored.
Code in the View Model:
if (!string.IsNullOrEmpty(Settings.AccessToken))
{
// Implement the SaveCommand from the ViewModel;
}
// Go to Login form to get an access token
else if (!string.IsNullOrEmpty(Settings.Username) &&
!string.IsNullOrEmpty(Settings.Password))
{
MainPage = new NavigationPage(new Login());
}
else
{
//Register first
//if you want to notify users to register here, use MessageCenter.Send
MessageCenter.Send(this, "displayAlert")
MainPage = new NavigationPage(new NewRegisterPage());
}
Code in the View:
MessagingCenter.Subscribe<ViewModelName>(this, "displayAlert", (sender) => {
// do something whenever the "displayAlert" message is sent
DisplayAlert("notification", "you should register first", "ok");
});
Send the message where you want to display an alert in the viewModel.
For more information about MessageCenter, you can refer to https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/messaging-center
If you wan't to display an alert from the ViewModel you can:
Application.Current.MainPage.DisplayAlert();

In NativeScript with Angular2 get element value

I have a list, and I want to get the value of the list item.
The view is as follows
<ListView [items]="myItems" (itemTap)="onItemTap($event)">
<template let-item="item" let-i="index" let-odd="odd" let-even="even">
<StackLayout [class.odd]="odd" [class.even]="even">
<Label #myFoo id="grocery-list" [text]='"Value is: " + i'></Label>
</StackLayout>
</template>
In typescript I have the following
import { Component,ViewChild,ElementRef } from "#angular/core";
import {topmost} from "ui/frame";
import {ListView} from "ui/list-view";
export class AppComponent {
#ViewChild("myFoo") myFooRef: ElementRef;
public myItems = [];
constructor() {
this.myItems.push("1");
this.myItems.push("2");
this.myItems.push("3");
}
onItemTap(event){
}
}
I can do the following to get the value
onItemTap(event){
let itemValue = this.myItems[event.index];
console.log(itemValue);
}
This will get the value in the array. But this will return the value in the array only.
As you can see in the view I have the string Value is appended to the value.
So how can I access the text property of the label which is tapped on.
You can access the view of your item template via args.view. From that point, I assume that you will have different text in your list-items so it is important to create unique IDs for each Label via binding(using the Angular index). So you can do the following:
<ListView [items]="myItems" (itemTap)="onItemTap($event)">
<template let-item="item" let-i="index" let-odd="odd" let-even="even">
<StackLayout [class.odd]="odd" [class.even]="even">
<Label [id]="'lbl' + i" [text]='"Value is: " + i'></Label>
</StackLayout>
</template>
</ListView>
and then in your onItemTap
public onItemTap(args: ItemEventData) {
console.log("Item Tapped at cell index: " + args.index);
console.log(args.object); // prints something like ListView(137)
console.log(args.view); // prints something like StackLayout(265)
var lbl = <Label>args.view.getViewById("lbl" + args.index);
console.log(lbl.text); // prints the actual text of the tapped label
}

Resources