Nativescript Vue: RadListView isn't refreshing when data changes - nativescript

I have a RadListView for a prop, totalMovies, which is an array. Each movie contains an image field and favorite field. If the favorite field is false, then an unfilled heart image shows and there's an event that fires on tap that changes that movie's favorite to true. After clicking the heart, I see in console that it registers and I verified again with VueTools that totalMovies shows the movies I favorites as having favorite: true, but the image that shows is always the unfilled-heart image. I am guessing or assuming that the RadListView is not refreshing properly?
<RadListView
for="(movie,index) in totalMovies"
#itemTap="onItemTap($event)"
itemHeight="80"
:key="index"
gridSpanCount=1
>
<v-template>
<FlexboxLayout class="item-row" :key="index" flexDirection="row" width="100%" height="100%">
<Image v-if="movie.favorite" width="20" src="~/assets/images/heart-filled.png" />
<Image v-else #tap="handleToggleFavorite(movie)" width="20" src="~/assets/images/heart-unfilled.png" />
</FlexboxLayout>
</v-template>
</RadListView>
EDIT: Adding playground: http://play.nativescript.org/?template=play-vue&id=GNo11S&v=2

ObservableArray listens to changes on array index and refreshes the list view. So if you are using ObservableArray, try to update item at index using setItem method.
Otherwise in your case simple array should work as Vue can detect the changes.

Related

Adding horizontal ScrollView to code-generated GridLayout

I have this sample code that works as I want:
<ScrollView orientation="horizontal">
<GridLayout columns="*,*,*,*,*,*" >
<Label class="gridlabel" col="0" text="Monday" />
<Label class="gridlabel" col="1" text="Tuesday" />
<Label class="gridlabel" col="2" text="Wednesday" />
<Label class="gridlabel" col="3" text="Thursday" />
<Label class="gridlabel" col="4" text="Friday" />
<Label class="gridlabel" col="5" text="Saturday" />
</GridLayout>
</ScrollView>
That is, the labels within the GridLayout scroll horizontally.
I have a component that generates a GridLayout, and now I need to wrap that in a horizontal ScrollView.
That is, I for each label:
let label = new Label();
// Add tap event handler for each tab
label.on("tap", function () {
onTabTap(label, "tabTap");
}.bind(label));
label.id = key;
label.text = key;
label.class = "gridtab";
this.addColumn(new ItemSpec(1, GridUnitType.STAR));
GridLayout.setColumn(label, i);
GridLayout.setRow(label, 0);
this.addChild(label);
But when I try to add the ScrollView, I get errors. If I try to add the labels to the ScrollView, such as scrollView.addChild(label) (where scrollView is an instance of ScrollView), I get "scrollView.addChild is not a function". (See this similar SO post). If, as suggested in the mentioned post, I use scrollView.content = this; then I get the error, Error: View already has a parent.
So, the question is, from code, how do I replicate the hierarchy from my sample xml? That is, how can I wrap the generated GridLayout in a horizontal ScrollView?
Edit 7/17/2020
Upon reflection, I don't think this can work given my component's current design. That is, it subclasses GridLayout, and I want the generated GridLayout to be wrapped by a ScrollView, but that would be external to the content generated by the component, yes? It almost seems I'd need to subclasss ScrollView, and then generate the GridLayout within.
So, I was ultimately able to resolve this by subclassing StackLayout, then within the StackLayout adding a ScrollView, and within the ScrollView adding the GridLayout. The "magic" is:
scroll.content = grid;
this.addChild(scroll);
Where scroll is the ScrollView instance, and grid is the GridLayout instance.
Then, after spending a day on this I found I didn't actually need horizontal scrolling after all, but at least I know what to do should the need arise.

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 :)

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.

How to add an activity-indicator on a page level?

I would like to add an activity-indicator widget in my login page but I would like it to cover the whole screen, so I can prevent double click on the Login button.
Any idea, thanks!
If you wrap everything in a GridLayout, add a StackLayout as the last item in the row you want to cover. The StackLayout by default will cover the whole screen. Then you can show/hide via data. For example:
<GridLayout>
<StackLayout>
// All your page content goes here!
</StackLayout>
<StackLayout class="dimmer" visibility="{{showLoading ? 'visible' : 'collapsed'}}"/>
<GridLayout rows="*" visibility="{{showLoading ? 'visible' : 'collapsed'}}">
<ActivityIndicator busy="true" />
</GridLayout>
</GridLayout>
I have a "dimmer" StackLayout that I animate to be semi transparent black, then the Activity Indicator sits on top.
not sure what layout you have i will only put example(somehow simplified) from my project
Inside page u can put something like this, both StackLayout and ActivityIndicator are inside GridLayout which takes whole size of page
<GridLayout rows="*" columns="*">
<StackLayout visibility="{{ showLogin ? 'visible' : 'collapse'}}" row="0" column="0">
<!--Login form, as you have defined-->
</StackLayout>
<!--Indicator on whole page, colSpan and rowSpan force ActivityIndicator to takes whole page-->
<ActivityIndicator visibility="{{ !showLogin ? 'visible' : 'collapse'}}" busy="{{ !showLogin }}" rowSpan="1" colSpan="1" row="0" column="0" />
</GridLayout>
And inside javascript code
/*somehow add showLogin property to bindingContext*/
page.bindingContext.set("showLogin",false) //false for show ActivityIndicator
/*or*/
page.bindingContext.set("showLogin",true) //true for show form
But best would be to put to already defined Observable which you should have assigned to bindingContext
So based on showLogin property u will get visible either ActivityIndicator(on whole page) or form
Not sure if i forgot something but if something, write comment :)
The activity indicator on its own won’t prevent dual submissions of your forms. In addition to displaying an ActivityIndicator, you should also set the isEnabled flag on your Button UI components to false during the submission. For example:
<!-- template -->
<Button [isEnabled]="!isAuthenticating" (tap)="submit()"></Button>
// JavaScript/TypeScript
export class LoginComponent {
isAuthenticating = false;
submit() {
this.isAuthenticating = true;
doTheActualLogin()
.then(() => {
this.isAuthenticating = false;
});
}
}
You can find a complete implementation of a login that prevents dual submissions and uses an ActivityIndicator in the NativeScript Groceries sample. Take a look at how the isAuthenticating flag is used in this login folder for the specific implementation.

Resources