How to create grid with 2 columns of cards [Nativescript and Angular] - android

Need grid to be something like this:
card1 card2
card3 card4
...
And to be dynamic
Here is my try:
component:
export class MenusComponent implements OnInit {
public menus: any;
constructor() {
this.menus = [
{
image: 'https://www.ducksdailyblog.com/wp-content/uploads/2018/12/Wooden-Post-Country-Fences-Direction.jpg',
title: 'Jelovnik'
},
{
image: 'https://www.ducksdailyblog.com/wp-content/uploads/2018/12/Wooden-Post-Country-Fences-Direction.jpg',
title: 'Pica'
},
{
image: 'https://www.ducksdailyblog.com/wp-content/uploads/2018/12/Wooden-Post-Country-Fences-Direction.jpg',
title: 'Dorucak'
}
]
}
Here is my try:
<GridLayout columns="*,*" rows="*">
<ListView class="list-group" [items]="menus">
<ng-template let-menu="item" let-i="index" let-odd="odd" let-even="even">
<StackLayout [col]="even ? 0 : 1">
<Label [text]="menu.title"></Label>
<Image height="108" width="100%" [src]="menu.image"></Image>
</StackLayout>
</ng-template>
</ListView>
I need to be able to add mre things inside list view so I assume there should be anoter grid as well. What is the right way to do this? Cards need to be equal and to take whole screen.

Using ListViewStaggeredLayout works for me here is the ref: https://docs.nativescript.org/angular/ui/ng-components/ng-radlistview/item-layouts

Related

Navigating nested routes in NativeScript Angular

In my NativeScript Angular Project this is my router module:
export const routes = [
{path: "user", component: UserComponent,
children: [
{
path: "dashboard",
component: DashboardComponent
},
{
path: "notice",
component: NoticeComponent
},
{
path: "attendance",
component: AttendanceComponent
},
{
path: "subject",
component: SubjectComponent,
children: [
{
path: "discussion",
component: DiscussionComponent
},
{
path: "assignment",
component: AssignmentComponent,
children: [
{
path: "assignment-report", component: AssignmentReportComponent
}
]
}
]
}
]
}
];
The user.component.html has a bottom bar with links for Dashboard, Notice and Attendance components which are displayed in a router outlet in UserComponent. A user clicks on a subject in user/dashboard route to go to a subject. The SubjectComponent again has two tabs for 'Discussion' and 'Assignment' displayed in a router outlet inside SubjectComponent.
Problem: When I am in SubjectComponent and click on any of the links in the bottom bar to navigate to Dashboard, Attendance or Notice, the app crashes and I get this error:
An uncaught Exception occurred on "main" thread.
Calling js method onPause failed
Error: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference .
It works fine if I just remove the <page-router-outlet> in SubjectComponent. I know I am somewhere messing up in nesting these routes but the error doesn't throw anything specific so I am unable to figure out.
Playground Sample: Open the playground sample and click on "Open Subject" button. When you are on the Subject Page, click on "Dashboard" button at the bottom. You will get the error.
Playground Link: https://play.nativescript.org/?template=play-ng&id=qGvsVF&v=11
Adding some code snippets that might be relevant to this problem.
user.component.html
<GridLayout style="background-color: #ffffff;" rows="*, auto">
<StackLayout class="content-container" row="0">
<page-router-outlet></page-router-outlet>
</StackLayout>
<StackLayout class="bottom-bar" row="1">
<StackLayout class="menu-container" horizontalAlignment="center" orientation="horizontal">
<Image (tap)="openDashboard()" [ngStyle]="{'border-color': isHome?'#1d87c9' :'#ffffff'}" class="btn-home" [src]="menuImages[0]"></Image>
<Image (tap)="openNotice()" [ngClass]="{'btn-active': isNotice}" class="btn-icon" [src]="menuImages[1]"></Image>
<Image (tap)="openAttendance()" [ngClass]="{'btn-active': isAttendance}" class="btn-icon" [src]="menuImages[2]"></Image>
</StackLayout>
</StackLayout>
openDashboard() method in user.component.ts. openNotice() and openAttendance() are similar
import { RouterExtensions } from "nativescript-angular/router";
constructor(private routerExtensions: RouterExtensions) {}
openDashboard(){
this.routerExtensions.navigateByUrl('user/dashboard');
this.menuImages = ["res://uni_full","res://notice_default","res://attendance_default"];
this.isHome = true;
this.isNotice = false;
this.isAttendance = false;
}
subject.component.html
<StackLayout class="main">
<GridLayout rows="auto, *">
<StackLayout row="0" class="subject-ribbon">
<StackLayout class="tab-container" orientation="horizontal">
<StackLayout (tap)="changeTab('discussion')" class="tab">
<Label class="tab-label" text=" Discussion"></Label>
<Label class="tab-indicator"></Label>
</StackLayout>
<StackLayout (tap)="changeTab('assignment')" class="tab">
<Label class="tab-label" text=" Assignment"></Label>
<Label class="tab-indicator"></Label>
</StackLayout>
</StackLayout>
</StackLayout>
<ScrollView (scroll)="onScroll($event)" row="1">
<StackLayout>
<page-router-outlet></page-router-outlet>
</StackLayout>
</ScrollView>
</GridLayout>
subject.component.ts
import { RouterExtensions } from "nativescript-angular/router";
constructor(private routerExtensions: RouterExtensions) {}
changeTab(tabName){
this.tabMode = tabName;
this.routerExtensions.navigateByUrl('user/subject/' + tabName);
}
I suspect the issue is that you had wrapped your frame (page-router-outlet) with a ScrollView. It's not a good practice for various reasons and the app seems to crash because of that. Moving the ScrollView within the page resolves the issue.
Updated Playground

How to output a List of object and its nested list of object inside Angular-Nativesript ListView?

Good day,
I having problem in angular-nativescript shared code project.
the model and components works fine but the output is not as i expected
i think the problem is in my view(Homecomponent.tns.html)
I have an angular model contains list of object(products) inside a object(ProductGroup) here is structure
product group:
export class ProductGroup {
public groupName : string;
public products : ProductItem[];
}
ProductItem:
export class ProductItem {
public itemDescription : string;
public remarks : string;
}
i already pre-populate it inside my component ngOnInit
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
})
export class HomeComponent implements OnInit {
public productGroups : ProductGroup[];
ngOnInit() {
///initiate group
this.productGroups = [];
///insert some items for testing
this.productGroups.push
(
{
groupName : "Cars",
products : [
{ itemDescription : "Car", remarks : "" },
{ itemDescription : "Car1",remarks : "" },
{ itemDescription : "Car2",remarks : "" }
]
},
{ groupName : "Trucks",products : [
{ itemDescription : "Truck", remarks : "" },
{ itemDescription : "Truck1",remarks : "" },
{ itemDescription : "Truck2", remarks : ""}]
}
);
//trying to check if it is properly populated
console.log(this.productGroups);
}
}
i already console.log the product groups and it seems populated and should work fine.
but im trying to output all the productItem inside the listview with the header of the group name but the it only output like this
_________________________________________________
Cars
Car
_________________________________________________
Trucks
Truck
_________________________________________________
here is my components.tns.html
<ActionBar>
<ActionItem title="Test"></ActionItem>
</ActionBar>
<StackLayout>
<GridLayout columns="*" >
<ListView [items] = "productGroups">
<ng-template let-group="item" >
<StackLayout>
<Label [text]="group.groupName"></Label>
<StackLayout>
<ListView [items] = "group.products">
<ng-template let-products="item">
<Label [text]="products.itemDescription"></Label>
</ng-template>
</ListView>
</StackLayout>
</StackLayout>
</ng-template>
</ListView>
</GridLayout>
</StackLayout>
Im hoping that my question makes sense to you guys. im just expecting output like this
_________________________________________________
Cars
Car
Car1
Car2
_________________________________________________
Trucks
Truck
Truck1
Truck2
_________________________________________________
Hoping to have someone help me regarding this
thanks for your help in advance
Try nativescript-accordion plugin. If you like all the items to be expanded by default, create an array with all indexes and apply it to selectedIndexes property.
tns plugin add nativescript-accordion
I have created a sample playground for you here. It's working fine on ios.
<GridLayout>
<ListView class="list-group" [items]="productGroups" (itemTap)="onItemTap($event)"
style="height:1250px">
<ng-template let-group="item">
<StackLayout >
<Label [text]="group.groupName"></Label>
<StackLayout>
<ListView [items]="group.products">
<ng-template let-products="item">
<Label [text]="products.itemDescription"></Label>
</ng-template>
</ListView>
</StackLayout>
</StackLayout>
</ng-template>
</ListView>
</GridLayout>

Horizontal WrapLayout shows elements vertically instead of horizontally

I'm completely new to NativeScript but it looks like a sweet platform. I'm writing a toy app and below is my setup:
//code behind
import {AddExpenseModel} from "./add-expense-view-model";
import {EventData} from "tns-core-modules/data/observable";
import {Page} from "tns-core-modules/ui/page";
let expenseModel = new AddExpenseModel();
export function navigatingTo(args: EventData) {
let page = <Page>args.object;
let textField = page.getViewById("tags");
textField.on("textChange", (ev)=>{expenseModel.onTagsTextFieldChange(ev)});
page.bindingContext = expenseModel;
}
export function submit() {
expenseModel.createNewExpense()
}
//view-model
import {Observable, PropertyChangeData} from "tns-core-modules/data/observable";
import {ObservableArray} from "tns-core-modules/data/observable-array";
let u = require('underscore');
export class AddExpenseModel extends Observable {
...
public parsed_tags;
constructor() {
super();
this.parsed_tags = new ObservableArray([]);
...
}
public onTagsTextFieldChange(ev) {
let that = this;
// empty
u.forEach(u.range(this.parsed_tags.length), function (_) {
that.parsed_tags.pop()
});
let parsed = this.parseTags(ev.value);
u.forEach(parsed, function (el) {
that.parsed_tags.push(el);
});
}
private getParsedTags() {
//unbox
return u.map(this.parsed_tags, (el: string) => el)
}
private parseTags(tag_string) {
let arr = u.map(tag_string.split(','), (tag: string) => tag.trim().toLocaleLowerCase());
arr = u.uniq(arr);
arr = u.filter(arr, u.negate(u.isEmpty))
return arr;
}
}
//view
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<StackLayout class="p-20">
<Label text="Add new expense"/>
<text-field
id="name" text="{{ name }}"
row="0"/>
<text-field id="amount" stext="{{ amount, amount | numberConverter }}" />
<text-field id="tags" secure="false" text="{{ tags }}"
/>
<WrapLayout orientation="horizontal" height="300" width="300">
<ListView items="{{ parsed_tags }}">
<ListView.itemsLayout>
<Label text="{{ $value }}" width="70" backgroundColor="red"/>
</ListView.itemsLayout>
</ListView>
</WrapLayout>
<button text="Add expense" id="submit-button" tap="submit"/>
</StackLayout>
</Page>
What I want to achieve is that when a user writes in the tags textfield, stylised Labels appear in the WrapLayout. This works, however, the Labels appear always stacked vertically. Here's a screenshot
I tried to move the whole WrapLayout section out of the StackedLayout section, but I get a cannot read property 'on' of undefined, undefined.
Putting Labels inside of WrapLayout not within a ListView (i.e. static) works as expected, which makes me thing I probably misuse either the ListView or the WrapLayout. Any directions will be appreciated :)
Cheers
Update
Following Eddy's advice I used a Repeater. Using the following xml
<Repeater items="{{ parsed_tags }}">
<Repeater.itemsLayout>
<WrapLayout orientation="horizontal" backgroundColor="#d3d3d3"/>
</Repeater.itemsLayout>
<Repeater.itemTemplate>
<Label text="{{ $value }}" backgroundColor="#84ddff" marginRight="5" marginLeft="5"/>
</Repeater.itemTemplate>
</Repeater>
correctly produces this:
I still don't see why using a ListView wouldn't work. I used the debugger from Sidekick and this is what I see.
<ListView items="{{ parsed_tags }}">
<ListView.itemsLayout>
<WrapLayout orientation="horizontal" backgroundColor="#d3d3d3" marginRight="5" marginLeft="5"/>
</ListView.itemsLayout>
<ListView.itemTemplate>
<Label text="{{ $value }}" backgroundColor="#84ddff" marginRight="5" marginLeft="5"/>
</ListView.itemTemplate>
</ListView>

toggle listview content in nativescript with angular

hi i need to toggle individual content in listview when the respective button is clicked in nativescript angular,
i added bellow my code. if anyone know please answer me. thanks in advance
import { Component, OnInit } from "#angular/core";
import { Item } from "./item";
import { ItemService } from "./item.service";
#Component({
selector: "ns-items",
moduleId: module.id,
templateUrl: "./items.component.html",
})
export class ItemsComponent implements OnInit {
items: Item[];
isList = true;
toggle(){
this.isList = !this.isList;
}
constructor(private itemService: ItemService) { }
ngOnInit(): void {
this.items = this.itemService.getItems();
}
}
and here my items.component.html
<ActionBar title="My App" class="action-bar">
</ActionBar>
<StackLayout class="page">
<ListView [items]="items" class="list-group">
<template let-item="item">
<GridLayout columns="auto,auto" width="210" height="210" >
<Label [text]="item.name" col="0"
class="list-group-item" visibility="{{isList ? 'visible' : 'collapse'}}"></Label>
<Button [text]="isList ? 'hide' : 'Show'" col="1" (tap)="toggle()"></Button>
</GridLayout>
</template>
</ListView>
</StackLayout>
here problem is when i click the button all the labels are toggle. so i need to generate the variable dynamically. i m very beginner so anyone can help me?
thank in advance.
Guess you have modified the starter template. So if you want hide label on particular list item, try this.
Add visible property to item.ts
export class Item {
id: number;
name: string;
role: string;
visible: boolean;
}
Set value to visible in your item.service.ts. It will be like below.
{ id: 1, name: "Ter Stegen", role: "Goalkeeper", visible: true },
{ id: 3, name: "Piqué", role: "Defender", visible: true },
Your list template should be
<template let-item="item">
<GridLayout class="list-group-item" columns="*,auto">
<Label col="0" [text]="item.name" [visibility]="item.visible ? 'visible' : 'collapse'"></Label>
<Button class="btn" col="1" [text]="item.visible ? 'Hide' : 'Show'" (tap)="toggle(item)"></Button>
</GridLayout>
</template>
And finally the toggle method will be,
ngOnInit(): void {
this.items = this.itemService.getItems();
}
toggle(item: Item) {
item.visible = !item.visible;
}

Nativescript Angular 2 disable background tap events

I have a login button which when clicked will log the user in by making http calls to the server.
While this is happening I want the activity indicator to show up and disable the button and every other thing on the page and just show the activity indicator over it.
Note that I will place time outs and other measures to make sure that the Activity indicator doesn't end up trapping the user.
Also, I do not want the content in the background to disappear. I just want the activity indicator to overlap it.
Here is my ui code which was taken for this SO answer https://stackoverflow.com/a/39124735/4412482
<GridLayout>
<StackLayout>
//page content goes here
</StackLayout>
</StackLayout>
<StackLayout class="dimmer" visibility="{{showLoading ? 'visible' : 'collapsed'}}"></StackLayout>
<GridLayout rows="*" visibility="{{showLoading ? 'visible' : 'collapsed'}}">
<ActivityIndicator busy="true" width="50" height="50" color="#0c60ee"></ActivityIndicator>
</GridLayout>
</GridLayout>
To disable the events to the components you could use isUserInteractionEnabled property, which will disable the whole interaction to the component and then you could show the ActivityIndicator. I am providing sample code below.
app.component.html
<GridLayout rows="*">
<StackLayout row="0" class="p-20">
<Label text="Tap the button" class="h1 text-center"></Label>
<Button text="TAP" (tap)="onTap()" class="btn btn-primary btn-active" [isUserInteractionEnabled]="buttoninteraction" ></Button>
<Label [text]="message" class="h2 text-center" textWrap="true"></Label>
</StackLayout>
<ActivityIndicator row="0" [busy]="acstate" row="1" class="activity-indicator"></ActivityIndicator>
</GridLayout>
app.component.ts
import { Component } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "app.component.html",
})
export class AppComponent {
public counter: number = 16;
public buttoninteraction = true;
public acstate = false;
public get message(): string {
if (this.counter > 0) {
return this.counter + " taps left";
} else {
return "Hoorraaay! \nYou are ready to start building!";
}
}
public onTap() {
this.counter--;
this.buttoninteraction=false;
this.acstate=true;
}
}
Hope this helps.

Categories

Resources