How to use and update NativeScript ListView - nativescript

I am trying to figure out how to use NativeScript's ListView and I can not figure out how. I have yet to find any good tutorials and really need help. I need to be able to show and update (add and subtract items from) a ListView in NativeScript. Thanks in advance!

You can mostly find all the things about the ListView here but I will make a demonstration of ListView practice that I'm using in my app. I'm using MVC structurce so we will have page.xml, page.js and page-viewmodel.js. In page.xml, we will want to have a listview with like this:
<Page>
<ListView items="{{ myItems }}" loadMoreItems="loadMore">
<ListView.itemTemplate>
<Label text="{{ message }}" textWrap="true" />
</ListView.itemTemplate>
</ListView>
</Page>
The <ListView.itemTemplate> is where you define a prototype for an item of data array. You can use a normal array but I suggest using built-in class called ObservableArray for programmatically changing of any item or any property later on.
In the model, which is page-viewmodel.ts, we have:
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
public class PageViewModel extends Observable {
private _myItems = new ObservableArray<MyItem>()
get myItems(): {
return this._myItems
}
public loadItems() {
var dataArray = ["Red", "Blue", "Green"];
for (var i in dataArray) {
var item = MyItem(dataArray[i])
this._myItems.push(item);
}
}
}
var pageViewModel = new PageViewModel();
public class MyItem extends Observable {
public message: String;
constructor(value) {
this.message = value;
}
}
Finally, in the controller which is page.ts:
import {pageViewModel} from "./page-viewmodel"
exports function pageLoaded(args) {
var page = args.object;
page.bindingContext = pageViewModel
}
exports function loadMore(args) {
pageViewModel.loadItems();
}
In conclusion, you define the ListView as well as its prototype in XML. After that, the viewmodel (which is an Observable object) is where you handle the data(add item, delete item, load items from backend, etc). Finally, you import that viewmodel to the controller(page.js) and bind to page.bindingContext so that the XML can receive the data
P/S: I'm writing this in TypeScript. In Javascript, the implementation is basically the same, there are just a bit of difference in the syntax. For example here is how the page.js would look like:
var pageViewModel = require("./page-viewmodel");
function pageLoaded(args) {
var page = args.object;
page.bindingContext = pageViewModel
}
exports.pageLoaded = pageLoaded;

Related

UI freezes for 2-3 seconds when loading a carouselview

I have a carouselview binded to a viewmodel, on the previous page (call it first page) user can select 2 arguments and with the help of those, the next view (call it second page) is generated accordingly. However, I can't wrap my head around why my view won't load asynchronously.
So my problem: When I click the button on the first page the UI would freeze for like a solid 2-3 seconds, and then start load (asynchronously?) and once it's done it's all good.
Also I couldn't really figure out a better way to inherit values from first page to second so if someone has an idea please let me know.
Any help on how can I fix this I would really appreciate.
The viewmodel for second page
public class DataSelectionViewModel : BaseViewModel
{
public ObservableCollection<Items> FilteredData { get; set; }
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
foreach (var data in filtereddata)
{
FilteredData.Add(data);
}
}
}
The carouselview in second page
<ContentPage.BindingContext>
<db:DataSelectionViewModel/>
</ContentPage.BindingContext>
...
<!-- DataTemplate for carouselview has radiobuttons, label and button all in a grid -->
<CarouselView ItemsSource="{Binding FilteredData}">
Second Page c#
public partial class SecondPage : ContentPage
{
public Coll(bool hard, string subject)
{
InitializeComponent();
var vm = (BaseViewModel)BindingContext;
vm.Hard = hard;
vm.Subject = subject;
/* had to set "hard" and "subject" here again, otherwise data won't load */
}
protected override async void OnAppearing()
{
var vm = (DataSelectionViewModel)BindingContext;
base.OnAppearing();
await vm.LoadData.ExecuteAsync().ConfigureAwait(false);
}
}
The first page view containing the button
<Button x:Name="Start" Pressed="ButtonClick"/>
First page c# --> Here I also tried doing it with a command and a pressed at the same time, because I couldn't come up with a way to save variables to second page viewmodel, that's why I use pressed here
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (BaseViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
vm.Subject = vm.Subject.ToLower();
await Navigation.PushAsync(new SecondPage(vm.Hard, vm.Subject));
}
I have tried not using the OnAppearing method to get my data, but then it wouldn't bind to the page and it would not show, if I were to previously fill the ObservableCollection with my data and then load the page although I would love to be able to do this because it would allow me to create a loading popup also.

Should I put the code behind for a button click in my viewModel and if so how can I bind it?

I have XAML code looking like this:
<Button Grid.Column="0" x:Name="nButton" Text="Don't Know" />
and a viewModel that I have linked up:
public CardViewModel card = new CardViewModel();
phraseGrid.BindingContext = card;
The ViewModel looks like this so far:
public class CardViewModel : ObservableProperty
{
string cvmLabel;
public CardViewModel() { }
public string CvmLabel
{
get { return cvmLabel; }
set {
if (value != cvmLabel) {
cvmLabel = value;
NotifyPropertyChanged("CvmLabel");
}
}
}
...
...
Currently I have coded this in the back end C#.
nButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore(App.cfs, phrase, 1);
App.correctButtonPressed = 1;
ResetTimer2();
};
What I would like to know is if this is more appropriate in the viewModel and then if I put it there, how is the best way to code it and link up the binding for the button.
Yes, it's a lot better to implement all the logic in the viewmodel. Handling the clicked event in the code behind couples your view with the code behind and makes unit testing a lot harder. In overall, it's always best to try to reduce coupling as much as possible.
You should use the Command parameter of the button to point to an ICommand in your viewmodel like this:
XAML
<Button Grid.Column="0" x:Name="nButton" Text="Don't Know" Command="{Binding ButtonClickedCommand}" />
ViewModel
public ICommand ButtonClickedCommand {get; set;}
public CardViewModel()
{
var ButtonClickedCommand = new Command (() => Debug.WriteLine ("Command executed"));
}

Xamarin.Forms: Create instance of a DataTemplate

I wan't to create a ContentView with a BindableProperty of type DataTemplate, so that when I use my custom ContentView i can customize how the elements should look like.
But I wan't to arrange and create the contents in code, how can I create an instance from a DataTemplate?
For example, in my custom view, I have a collection of objects, now for each object I want to create a view based on the set data template and set the binding context of that created view to that object.
I worked it out in following way.
I use my custom ContentView in following way:
<controls:MyCustomView Items="{Binding SampleItems}">
<controls:MyCustomView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SampleProperty}" />
</DataTemplate>
</controls:MyCustomView.ItemTemplate>
</controls:MyCustomView>
then in the code behind of the MyCustomView I declare a ItemTemplate bindable property:
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
nameof(ItemTemplate),
typeof(DataTemplate),
typeof(MyCustomView),
propertyChanged: (bObj, oldValue, newValue) =>
{
var view = bObj as MyCustomView;
if (view != null)
view.SampleMethodToArrangeItems();
}
);
now let's say that in the SampleMethodToArrangeItems method I want to create and arrange, the items created from the provided data template:
foreach (var item in Items)
{
var itemView = ItemTemplate.CreateContent() as View;
if (itemView != null)
{
itemView.BindingContext = item;
// Do something with the create view e.g. add it to Grid.Children
}
}

Nativescript custom property not accessible

I am trying to create a custom component in Nativescript. The component works fine with the static data, I want to add some custom properties to that control, but the are not accessible in the code behind. I am trying to create a MCQ like box or a something similar to radio control, so user can only select one option from the given ones.
CustomControl.xml
<StackLayout orientation="vertical" class="form" loaded="loaded">
<Repeater items="{{ items }}">
<Repeater.itemTemplate>
<StackLayout orientation="vertical" tap="itemTapped" id="{{id}}">
<StackLayout orientation="horizontal" verticalAlignment="center">
<Label text="{{text}}" class="form-field" width="88%"/>
<Label text="{{characterCode}}" visibility="{{visible ? 'visible' : 'collapsed'}}" class="icon"/>
</StackLayout>
<StackLayout class="separator"/>
</StackLayout>
</Repeater.itemTemplate>
</Repeater>
</StackLayout>
CustomControl.js
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var _component;
var _viewModel = new Observable();
var _selectedId = null;
exports.loaded = function(args){
_component = args.object;
//passing in _component.items as array throws undefined
var items = getInitializedArray(["Some text","Someother text"]);
_viewModel.set("items", items);
_component.bindingContext = _viewModel;
}
exports.itemTapped = function(args){
var id = args.object.id;
if(_selectedId === null){
var item = _viewModel.get("items").getItem(id);
item.visible = true;
_viewModel.get("items").setItem(item, id);
}else{
var item = _viewModel.get("items").getItem(_selectedId);
item.visible = false;
item = _viewModel.get("items").getItem(id);
item.visible = true;
_viewModel.get("items").setItem(item, id);
}
_selectedId = id;
}
function getInitializedArray(data){
var id=0;
var items = data.map((listItem) => {
return {
text: listItem,
characterCode: String.fromCharCode(0xea11),
visible: false,
id: id++
}
});
return new ObservableArray(items);
}
Trying to use it in my page as
<CustomComponents:CustomControl items="{{items}}"/>
But using args.object.items throws undefined property for object.
I have read that I'll have to use dependency-observable and will have to create a plugin. But I am not using any platform specific thing, I am just creating a component with existing ui components and its pretty simple what I want to achieve. Is there a way to bind custom properties? Plugins are too complex for this, How can I achieve it?
After reading the docs and going through various forums and github issues following is what I have found and thanks to Nick lliev' comment.
To give custom properties to your controls you'll have to use the code only technique, I have written a blog http://mobile.folio3.com/creating-custom-controls-in-nativescript/ describing both the techniques showing how to give custom properties to your custom controls.

Change Label Value on button click in Xamarin MVVM

I'm facing an issue in Xamarin forms Mvvm. I have 2 different layouts say Layout1 and Layout2 which are bounded with a common ViewModel. Layout1 contains multiple Labels which I'm generating dynamically using for loop in xaml.cs file and bind each Label'sTextProperty using SetBinding. Layout2 contain a button.
Now I want to change Text of a particular Label when button clicked.
Layout1.xaml
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Layout1">
<StackLayout x:Name="ParentStack">
// dynamic Labels to be added here..
</StackLayout>
</StackLayout>
Layout1.xaml.cs
public partial class Layout1: StackLayout
{
public Label dummyLabel;
public Layout1()
{
InitializeComponent();
for (int i = 0; i < 3; i++)
{
dummyLabel= new Label
{
Text = " ",
};
dummyLabel.SetBinding (Label.TextProperty,"PhaseValue");
parentRowCells.Children.Add(dummyLabel);
var tapGestureRecognizer_1 = new TapGestureRecognizer();
tapGestureRecognizer_1.SetBinding(TapGestureRecognizer.CommandProperty,"LabelClicked");
tapGestureRecognizer_1.CommandParameter = dummyLabel;
dummyLabel.GestureRecognizers.Add(tapGestureRecognizer_1);
}
}
}
Layout2.Xaml
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Layout2">
<StackLayout x:Name="ParentStack">
<Button Command={Binding ButtonClickedCommand} Text="Click Me" />
</StackLayout>
</StackLayout>
ViewModel.cs
class ViewModel
{
public Label label = new Label();
public string textstring = "new text string";
ICommand _labelClicked;
public ICommand LabelClicked
{
get
{
this._labelClicked= this._labelClicked?? new Command(s =>
{
label = s as Label;
label.Text = "new text"; //this change the text of particular label when clicked but i need it from button clicked event from another layout.
// here I'm getting the instance of label which i clicked on label.
});
return this._labelClicked;
}
}
public ICommand ButtonClickedCommand{ protected set; get; }
public ViewModel()
{
this.ButtonClickCommand = new Command<Button>((key) =>
{
//here I want to change the value of label when button command is clicked.
aa.Text = "this is not changing the text";
});
}
}
Any help in this or do I need to follow some other pattern..??
My first thought would be to add each Label that you add to a List<Label> somewhere that you can access from both layouts... your View Model would seem like the logical place. Then when you click your button, you can find the particular Label whose text you want to change and change it. You will likely then have to reload your list.
However I think that a better way would be to use a ListView instead of a StackLayout. Then you can have an ItemTemplate for the ListView that includes one Label. You can then set up an ObservableCollection<T> of objects to use as the ListView.ItemsSource. You would want to make some custom object that has a Text property, or whatever you want to call the property that will hold the text for the Labels. It is better to use an object for the T in ObservableCollection<T> rather than using ObservableCollection<string> because changes to a string type will not be reflected in the ListView item, but changes to a property of an object (assuming of course that you make it a Bindable Property) will be reflected in those controls that are bound to that property. So in a nutshell, something like (in your ViewModel):
// Class level variable
ObservableCollection<CustomType> dummyLabelContents;
// then either in the ViewModel constructor or somewhere else:
dummyLabelContents = new ObservableCollection<CustomType>();
CustomType dummyText;
for (int i = 0; i < 3; i++)
{
dummyText = new CustomType
{
Text = " ",
};
}
dummyLabelContents.Add(dummyText);
And your CustomType would just be a simple class with only a BindableProperty called Text.
Set up like this, you can assign your ListView.ItemsSource to be the dummyLabelContents ObservableCollection and then whenever you add an item to the ObservableCollection, the ListView will update automatically. Also, since using a custom type with a bindable text property in the ObservableCollection, when that text property is changed the item in the ListView should also update accordingly.

Resources