How to Nest List Views in NativeScript - nativescript

I'm trying to display a list of items within a list of items. Basically it's a card game, where each suit is being repeated, and then each card for each suit is being repeated.
<StackLayout margin="10 0 60 0" padding="0 0">
<ListView class="list-group" [items]="hand" (itemTap)="onItemTap($event)"
(itemLoading)="onItemLoading($event)" backgroundColor="#26252A"
style="height:100%">
<ng-template let-suit="item">
<FlexboxLayout flexDirection="row">
<ScrollView orientation="horizontal">
<StackLayout height="100" orientation="horizontal" margin="2.5, 15">
<ListView class="list-group" [items]="suit.cards">
<ng-template let-card="item">
<Label [text]="card" class="card"></Label>
</ng-template>
</ListView>
</StackLayout>
</ScrollView>
</FlexboxLayout>
</ng-template>
</ListView>
</StackLayout>
And this is what my "hand" looks like:
hand: { suit: Suit, fontColor: Color, cards: string[] }[] = [
{ suit: "Red", fontColor: new Color("red"), cards: ["14", "13"] },
{ suit: "Green", fontColor: new Color("green"), cards: ["14", "13", "12", "9"] },
{ suit: "Yellow", fontColor: new Color("yellow"), cards: ["14", "13", "10", "9"] },
{ suit: "Black", fontColor: new Color("black"), cards: ["14", "13", "9"] }
];
When I run it though, I'm only getting the very first card in each suit to be displayed.
You can check it out at the playground here:
https://play.nativescript.org/?template=play-ng&id=XfogFt&v=3
(I'm new to both NaitiveScript and Angular, so I may be missing something simple)

EDIT: It is not advisable to use nested listview as this would break
the recycling and virtualization for cells that are containing a
nested listview
You don't need a scrollview inside the ng-template, if you just remove it, it will show your all the items in each row of parent list.
<ListView class="list-group" [items]="hand" (itemTap)="onItemTap($event)"
(itemLoading)="onItemLoading($event)" backgroundColor="#26252A"
style="height:100%">
<ng-template let-suit="item">
<FlexboxLayout flexDirection="row">
<!-- <ScrollView orientation="horizontal"> -->
<StackLayout height="100" orientation="horizontal" margin="2.5, 15">
<ListView class="list-group" [items]="suit.cards">
<ng-template let-card="item">
<Label [text]="card" class="card"></Label>
</ng-template>
</ListView>
</StackLayout>
<!-- </ScrollView> -->
<Label text="Label"></Label>
</FlexboxLayout>
</ng-template>
</ListView>
I have updated the playground here. You can also use the itemHeight and itemWidth properties here for size tuning.
P.S. The itemHeight and itemWidth properties are iOS specific. If not used, items are sized dynamically depending on the data coming from the source.

As #Narendra mentioned it's not recommended to use nested list views or ngFor inside template.
I guess nativescript-accordion plugin will suit your needs, it basically supports the data structure you are looking for - List Item -> List of Items (Suit -> Cards). If you like to show the items expanded upon loading, all you have to do is, populate the selectedIndexes with all indexes. There is one issue with latest version of the plugin, which can be still handled with a simple math.
Preventing collapse upon tap is still an open feature request, but possible to achieve with an override. But as far I know, this plugin could be the only viable solution for nested list views.

Related

Nativescript RadListView multiple selection: how to remove default style?

I'm using RadListView to create a multiple selection on IOS.
In front of the selection item somehow there is a circle checkbox I couldn't remove, which looks like:
My codes are:
<RadListView [items]="items" selectionBehavior="Press" multipleSelection="true" height="80%">
<ng-template let-item="item">
<Label [text]="item.name"></Label>
</ng-template>
</RadListView>
and:
this.items = [{ name: 'test1' }, { name: 'test2' }, { name: 'test3' }];
Anyone got an idea?
On iOS, those checkboxes appear beside your label is the only way user would distinguish between selected & unselected items.
You may apply padding only for iOS to prevent overlapping,
<RadListView [items]="items" selectionBehavior="Press" multipleSelection="true" height="80%">
<ng-template let-item="item">
<Label ios:paddingLeft="50" [text]="item.name"></Label>
</ng-template>
</RadListView>
If you still prefer a different style for selection, you will have to build your own, you may have a selected flag in each data item and toggle them upon tapping list item and update your CSS (may be background-color) for list item based on the selected flag.

How to access the item context inside RadListView tkListItemSwipeTemplate

Using NativeScript 3 + Angular 5.
I need to allow the user to swipe an item within a RadListView to reveal a short description about the item.
<RadListView
[items]="featuredVideos"
pullToRefresh="true"
selectionBehavior="None"
(itemSwipeProgressStarted)="onSwipeCellStarted($event)"
swipeActions="true"
(pullToRefreshInitiated)="onPullToRefreshInitiated($event)">
<ng-template tkListItemTemplate let-item="item">
<VideoComponent [video]="item"></VideoComponent>
</ng-template>
<ng-template tkListItemSwipeTemplate let-item="item">
<GridLayout columns="*, 500" class="gridLayoutLayout">
<StackLayout id="short-desc" col="1">
<Label [text]="item.shortDescription" class="body" verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
</GridLayout>
</ng-template>
</RadListView
I would like to be able to access the current item inside the tkListItemSwipeTemplate so that I can bind the text property of the label to the short description. Currently I am getting the following error
JS: ERROR TypeError: Cannot read property 'shortDescription' of undefined
I know this question is a little old now, but for anyone who comes here looking for an answer, I have managed to work-around this problem. Bind your label text to a different value i.e.:
<Label [text]="myLabelText"...
Then in your onSwipeCellStarted callback, you can use the index property on the ListViewEventData argument and set 'myLabelText' appropriately e.g.:
onSwipeCellStarted(args: ListViewEventData) {
...
this.myLabelText = featuredVideos[args.index].shortDescription;
}
This should get you out of trouble. Hope it helps :)

Nativescript Stacklayout does not layout properly?

I have this simple layout markup :
<StackLayout orientation="horizontal" #myStack>
<StackLayout width="300" backgroundColor="red">
</StackLayout>
<StackLayout width="300" backgroundColor="green">
</StackLayout>
</StackLayout>
As you can see , both are same width of 300:
Let's see this :
Notice that green is also 300 but we only see a part of it since 300+300 > screen size.
Ok so let's try to animate #myStack (!!) to the left , in order to see the left green :
ngOnInit(): void {
setTimeout(() => {
this.myStack.nativeElement.animate({
translate: { x: -300, y: 0 },
duration: 2000,
curve: enums.AnimationCurve.easeIn
});
},1000)
}
Question:
What is this white area on the right ? there should be also a green section there
Basically this is the situation :
So i'm expecting the green from the right to be scrolled to the left , i'm basically trying to move #myStack left.
How can I make the green area slide from the right ?
PLAYGROUND
Copy/paste this answer so that the community can see the provided solutions:
#RoyiNamir this is expected behavior.
What is happening is that by default the root layout will have an effective width (and height) as the screen size.
You need to explicitly create the wider container if you want to have an effective width larger than the screen width.
There are several approaches on how to achieve that.
- use container with fixed width - demo Playground here
<GridLayout rows="auto, *" columns="600" backgroundColor="lightgray">
<Button row="0" text="animate" (tap)="animate()"></Button>
<StackLayout row="1" orientation="horizontal" #myStack>
<StackLayout width="300" backgroundColor="red">
<Label text="Label"></Label>
</StackLayout>
<StackLayout width="300" backgroundColor="green">
<Label text="Label"></Label>
</StackLayout>
</StackLayout>
</GridLayout>
Note that our container GridLayout has columns set to 600.
Another option is instead of creating fixed size container to use ScrollView (which will measure its children so the children should have predefined size - in your case width="300") - demo Playground here
In the example above the container element is not needed (used just to create the animate Button).
The ScrollView will measure its children (300 + 300 = 600 width) and will take the space needed.

How to show a button if there are no items in a ListView?

I have the following structure:
<ScrollView tkMainContent>
<ListView [items]="students$ | async" class="list-group" *ngIf="students$">
<ng-template let-student="item">
<StackLayout>Student details go here</StackLayout>
I'm not able to show a button inside the ScrollView when there is no student in my list.
How can I still show the button?
Note: I'm testing on a real iOS device.
<FlexboxLayout flexDirection="column">
<GridLayout class="page-content" id="placeholderLayout" visibility="{{ hasContent ? 'collapse' : 'visible' }}">
<Label class="page-icon fa" text=""></Label>
<Label class="page-placeholder" style="white-space: normal" text="Click the camera button to add image"></Label>
</GridLayout>
<ScrollView>
<-- List View Here -->
</ScrollView>
</FlexboxLayout>
I use something like this on NS Core, to show placeholder content. The way to set visibility might be different in angular, but a similar markup should work for you.
In the component.ts, you should take care to evaluate if there is content to show in list view, if there are, then set hasContent to true, and false otherwise.
Hope that helps :) let me know if you face any trouble while implementing this.

Nativescript Listview with header and scroll entire page

I am using ListView with Header portion on top of it like below,
<StackLayout>
<StackLayout height="200">
<Label text="Header content goes in this section"></Label>
<StackLayout>
<ListView [items]='posts'>
<!-- template items goes here -->
</ListView>
</StackLayout>
When we scroll to list the header is sticky in this case.
Is there a option that scroll overrides header also ?.I mean that header also part of scroll.
Fr Angular-2 application you can now use tkTemplateKey deirective and create your own headers, footers, groups and other custom list-view elements.
Example can be found here
Here is the code for a list-view with header and groups.
page.component.html
<ListView [items]="countries" [itemTemplateSelector]="templateSelector" (itemTap)="onItemTapFirstList($event)" class="list-group" separatorColor="white">
<ng-template nsTemplateKey="header" let-header="item">
<Label [text]="header.name" class="list-group-item h3 bg-primary" isUserInteractionEnabled="false" color="white" fontSize="24"></Label>
</ng-template>
<ng-template nsTemplateKey="footer" let-footer="item">
<Label [text]="footer.name" class="list-group-item" backgroundColor="gray"></Label>
</ng-template>
<ng-template nsTemplateKey="cell" let-country="item">
<StackLayout class="list-group-item">
<Label [text]="country.name" class="list-group-item-heading"></Label>
<Label [text]="country.desc" class="list-group-item-text" textWrap="true"></Label>
</StackLayout>
</ng-template>
</ListView>
page.component.ts
#Component({
moduleId: module.id,
templateUrl: "./multi-line-grouped.component.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MultiLineGroupedListViewExampleComponent implements OnInit {
public countries: Array<any> = [];
public templateSelector = (item: any, index: number, items: any) => {
return item.type || "cell";
}
ngOnInit() {
for (let i = 0; i < mockedCounties.length; i++) {
this.countries.push(mockedCounties[i]);
}
}
onItemTapFirstList(args: ItemEventData) {
console.log(args.index);
}
}
Not sure if there's another way, but one way could be moving the header inside the listview. For that to work it needs to be in the posts Array, so you may want to transform that into some sort of wrapping class that can contain eiter a header or item row. Then create two templates inside the listview that depending on the template key render a header or an item.
For details on templates, see https://docs.nativescript.org/cookbook/ui/list-view#define-multiple-item-templates-and-an-item-template-selector-in-xml
You can use *ngFor creating the list.Here is the sample code for doing this.
<ScrollView>
<StackLayout>
//define your header over here
<Label text="hey header"></Label>
<StackLayout *ngFor="let item of <Array>">
<GridLayout columns="4*,*" rows="*,">
<Label row="0" col="0" text="hey label"></Label>
</GridLayout>
<StackLayout>
<StackLayout>
</ScollView>

Resources