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.
Related
I have a implement syncfusion Carousel and binding items using ItemTemplate.When i load the items to Carousel all item appears in Carousel view.But i need to add a doted indicator for it.
When user swipe though the Carousel by the dots should indicate current position.
When reading from documentation of syncfusion rotator have this functionality.
I need to add this to carousel view.
Here you can find all the SfCarousel Class Members.
And there's no property for the dots you refered in the SfCarousel print.
In fact, I think you are confusing it with another component called SfRotator. (that has an identical example like your print). and the property you are looking for is called: DotPlacement.
And can have the following states:
None //No Dots
Default //Dots Inside the Rotator View
OutSide //Dots Outside the Rotator View
We have analyzed your query and currently we don’t have dots view support in CarouselView. However, we can fulfill this requirement by using Border or Button control as like below code snippet.
Custom DotsView:
XAML:
<border:SfBorder BorderColor="{Binding ThumbBorder}" HorizontalOptions="Center" VerticalOptions="Center" BorderWidth="5" CornerRadius="50" />
Carousel View:
XAML:
<carousel:SfCarousel x:Name="carousel" Grid.Row="0" Offset="0" ItemsSource="{Binding ImageCollection}" ItemTemplate="{StaticResource itemTemplate}"
ItemHeight="200" SelectionChanged="Carousel_SelectionChanged"
ItemWidth="200" />
<StackLayout Grid.Row="1" HorizontalOptions="Center" x:Name="rotatorThumb" BackgroundColor="Transparent" Orientation="Horizontal"/>
C#:
public MainPage()
{
InitializeComponent();
Command command = new Command((object thumb) =>
{
var thumbView = thumb as DotsView;
if (thumbView != null)
{
((rotatorThumb.Children[carousel.SelectedIndex] as DotsView).BindingContext as CarouselModel).
ThumbBorder = Color.LightGray;
carousel.SelectedIndex = thumbView.Index;
(thumbView.BindingContext as CarouselModel).ThumbBorder = Color.Red;
}
});
for (var i = 0; i < ImageCollection.Count; i++)
{
var itemView = new DotsView() { BindingContext = ImageCollection[i], Index = i };
if (carousel.SelectedIndex == i)
(itemView.BindingContext as CarouselModel).ThumbBorder = Color.Red;
TapGestureRecognizer thumbTap = new TapGestureRecognizer();
thumbTap.Command = command;
itemView.GestureRecognizers.Add(thumbTap);
thumbTap.CommandParameter = itemView;
rotatorThumb.Children.Add(itemView);
}
}
Output:
Sample
I have a small project using XamarinForms and Prism MVVM.
On the settings page I save Author's ID from the Picker.
When I return to the settings page I want that Author to be selected by default in the picker.
This is my Picker in Xaml:
<Picker x:Name="authorPicker" Title="Select Author" FontSize="Medium"
HorizontalOptions="StartAndExpand" VerticalOptions="Center"
ItemsSource="{Binding NoteAuthors}"
ItemDisplayBinding="{Binding Name}"
SelectedItem="{Binding SelectedAuthor, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1" />
When Author is selected I got this in ViewModel and it works fine:
private NoteAuthor _selectedAuthor;
public NoteAuthor SelectedAuthor
{
get { return _selectedAuthor; }
set
{ if (_selectedAuthor != value)
{
SetProperty(ref _selectedAuthor, value);
}
}
}
In the ViewModel > OnNavigatingTo function I call the GetAuthor function which returns Author based on previously saved ID.
public async void GetAuthor(int author_id)
{
NewNoteAuthor = await App.Database.GetAuthorById(author_id);
if(NewNoteAuthor != null && NewNoteAuthor.ID > 0)
{
SelectedAuthor = NewNoteAuthor;
}
}
How can I "jump" to this Author when page opens? The assignment in GetAuthor function doesn't work for me.
After you retrieved NoteAuthors from database, you've to set SelectedAuthor by referencing one of them. Picker uses reference equality, so loading another instance of author from database in GetAuthor won't work at all. Following code solves this problem, and it also improves your code's performance.
NoteAuthors = await // read them from db ...
SelectedAuthor = NoteAuthors.SingleOrDefault(a => a.Id == author_id); // don't load it from database again.
Here is what I tried. Note that I removed parts of the XAML to make the question shorter but I tested it out by setting detailx.text in my cs and when I do it that way it displays XXXX:
<Frame x:Class="Japanese.PhrasesFrame">
<StackLayout x:Name="phrasesFrameStackLayout" >
<Grid x:Name="phraseGrid">
<Grid x:Name="wordGrid" >
<Grid x:Name="detailGrid">
<Label x:Name="detail1" Text="{Binding English}" XAlign="Center" />
<Label x:Name="detailx" XAlign="Center" />
In my CS I have
public PhrasesFrame()
{
InitializeComponent();
correctButton.Clicked += correctButtonClicked;
resetButton.Clicked += resetButtonClicked;
SetBindings();
wordGrid.BindingContext = AS.phrase;
}
In a method I have this:
public partial class PhrasesFrame : Frame
{
Random rand = new Random();
public PhrasesFrame()
{
InitializeComponent();
wordGrid.BindingContext = AS.phrase;
AS.phrase = new PSCViewModel() { English = "abcd" };
this.detailx.Text = "XXXX";
}
My problem is that when I used Binding English then the label never shows the text abcd but I do see the XXXX
Can someone tell me if I am missing something obvious. I tried everything but no success.
Try changing the order, like this:
AS.phrase = new PSCViewModel() { English = "abcd" };
wordGrid.BindingContext = AS.phrase;
Whenever you set the BindingContext at that moment it is applied, as is. If you make changes afterwards, you can, but you need to implement the INotifyPropertyChanged event to fire off a signal to the UI indicating which property has changed. Only then will it be update in the UI.
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;
When a Windows Phone 7 application opens a view, a certain order of business is followed in order to create. As far as constructors and events go, I have found this order to be true:
Constructor
OnNavigatedTo
OnLoaded
However, I am in a position where I need to databind a List to a ListBox after the basic view (background, other elements etc) has loaded. So I need to know when and how to know that the view is loaded before I get on with the data binding.
I have tried to do this on the OnLoaded-event, but it seems like if I do the data binding here - and right after it traverse those elements - they don't seem to exist yet (the VisualTreeHelper-class can't seem to find the nodes). So as you see, I am stuck.
Any help would be greatly appreciated.
Edit: As requested, here is some more information about what's going on.
My List is populated by some custom (not too complicated) objects, including an asynchronously loaded image (courtesy of delay.LowProfileImageLoader) and a rectangle.
The XAML:
<ListBox x:Name="ChannelsListBox" ItemsSource="{Binding AllChannels}">
//...
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="ChannelTile" Margin="6,6,6,6" Tap="ChannelTile_Tap" Opacity="0.4">
<!-- context menu goes here -->
<Rectangle Width="136" Height="136" Fill="{StaticResource LightGrayColor}" />
<Image Width="136" Height="136" delay:LowProfileImageLoader.UriSource="{Binding ImageUri}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code-behind:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
UpdateApplicationBar();
pickChannelsViewModel = new PickChannelsViewModel();
DataContext = pickChannelsViewModel;
if (hasUpdatedTiles)
{
pickChannelsViewModel.IsLoading = false; // Set by UpdateTiles()
}
}
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
// This is where I would data bind the list (instead of in XAML)
UpdateTiles(); // Traverses the list and changes opacity of "selected" items.
}
protected void UpdateTiles()
{
foreach (var item in ChannelsListBox.Items)
{
if (pickChannelsViewModel.SelectedChannels.Contains(item as Channel))
{
var index = ChannelsListBox.Items.IndexOf(item);
// This returns null when databinding in codebehind,
// but not in XAML
ListBoxItem currentItem = ChannelsListBox.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem;
if (currentItem != null && VisualTreeHelper.GetChildrenCount(currentItem) == 1)
{
var OuterWrapper = VisualTreeHelper.GetChild(currentItem, 0);
var MiddleWrapper = VisualTreeHelper.GetChild(OuterWrapper, 0);
var InnerWrapper = VisualTreeHelper.GetChild(MiddleWrapper, 0);
Grid currentItemGrid = VisualTreeHelper.GetChild(InnerWrapper, 0) as Grid;
currentItemGrid.Opacity = 1.0;
}
}
}
pickChannelsViewModel.IsLoading = false;
hasUpdatedTiles = true;
}
The items themselves are in-memory (fetched from REST at an earlier stage in the application), so should be available instantaneously.
The issue I am trying to resolve is a fairly long load time on this particularly view (there is about 140 of these items being created, then filtered through and changing the opacity).
I believe you are doing something like:
myListBox.ItemSource=myList;
Once you set the ItemSource of a ListBox the changes in your List should be reflected in the ListBox at all times. If the ListBox is empty the reason must be that the List is not being populated properly or invalid Bindings in the ItemTemplate. You should debug and find out if your List has any items by inserting a breakpoint in the Loaded() method. Also, you've not mentioned what items does your List contains or, where is it being populated in the application? Incomplete information doesn't help anyone.