How to add items to nativescript actionbar programmatically? - nativescript

How do I programmatically add items to the ActionBar? I've been trying to play around with this code below but the action items never update.
public setActionBarItems(actionBar: ActionBar) {
let tab = TabFactory.getTab(this.currentTabIndex);
let actionItem = new ActionItem();
actionItem.set("ios.systemIcon", "12");
actionItem.set("ios.position", "right");
actionItem.set("android.systemIcon", "ic_menu_search");
actionItem.set("android.position", "right");
actionBar.actionItems.addItem(actionItem);
// for (let actionItem of tab.actionItems) {
// actionBar.actionItems.addItem(actionItem);
// }
}
Do I perhaps need to tell the view somehow to update itself? I also tried setting actionItems="{{actionBarItems}}" on the ActionBar itself, but that throws a warning that the property is read-only.

Not that proud of it right now but here is a possible solution. Will probably refactor it in the future.
view model
export class MainViewModel extends Observable {
public isArticlesListTabVisible : boolean;
public isArchiveTabVisible : boolean;
public isAccountTabVisible : boolean;
public items: Array<BottomBarItem> = [
new BottomBarItem(0, "Archive", "ic_archive_black", "#FF303030"),
new BottomBarItem(1, "My List", "ic_list_black", "#FF303030"),
new BottomBarItem(2, "Account", "ic_account_circle_black", "#FF303030")
];
constructor() {
super();
}
get title() : string {
return "My List";
}
public setActionBarTitle(tab: ITab) {
this.notifyPropertyChange("title", tab.title);
}
public setActionBarItems(currentTab: ITab) {
if (currentTab instanceof ArticlesListTab) {
this.isArticlesListTabVisible = true;
this.isAccountTabVisible = false;
this.isArchiveTabVisible = false;
}
else if (currentTab instanceof AccountTab) {
this.isAccountTabVisible = true;
this.isArticlesListTabVisible = false;
this.isArchiveTabVisible = false;
}
else {
this.isArchiveTabVisible = true;
this.isArticlesListTabVisible = false;
this.isAccountTabVisible = false;
}
this.notifyPropertyChange("isArticlesTabVisible", this.isArticlesListTabVisible);
this.notifyPropertyChange("isAccountTabVisible", this.isAccountTabVisible);
this.notifyPropertyChange("isArchiveTabVisible", this.isArchiveTabVisible);
}
}
xml file
<ActionBar title="{{ title }}" class="action-bar" id="mainActionBar">
<ActionItem tap="{{onSearch}}"
ios.systemIcon="12" ios.position="right"
android.systemIcon="ic_menu_search" android.position="right" visibility="{{isArticlesListTabVisible ? 'visible' : 'collapsed'}}"/>
<ActionItem tap="{{onArticlesFilter}}"
ios.systemIcon="10" ios.position="right"
android.systemIcon="ic_menu_sort_by_size" android.position="popup" text="Newest" visibility="{{isArticlesListTabVisible ? 'visible' : 'collapsed'}}"/>
<ActionItem tap="{{onArticlesFilter}}"
ios.systemIcon="10" ios.position="right"
android.systemIcon="ic_menu_sort_by_size" android.position="popup" text="Oldest" visibility="{{isArticlesListTabVisible ? 'visible' : 'collapsed'}}"/>
<ActionItem tap="{{onArticlesFilter}}"
ios.systemIcon="10" ios.position="right"
android.systemIcon="ic_menu_sort_by_size" android.position="popup" text="Most Progress" visibility="{{isArticlesListTabVisible ? 'visible' : 'collapsed'}}"/>
<ActionItem tap="{{onArticlesFilter}}"
ios.systemIcon="10" ios.position="right"
android.systemIcon="ic_menu_sort_by_size" android.position="popup" text="Least Progress" visibility="{{isArticlesListTabVisible ? 'visible' : 'collapsed'}}"/>
</ActionBar>
code behind
export function onBottomBarLoaded(args: EventData) {
this._bottomBar = args.object as BottomBar;
this._bottomBar.selectItem(1);
this._bottomBar.on('tabSelected', (args) => {
switchBottomBarTab(args.newIndex, args.oldIndex);
let currentTab = TabFactory.getTab(args.newIndex);
_model.setActionBarTitle(currentTab);
_model.setActionBarItems(currentTab);
});
}

Related

Error when trying to update in react-redux

I am trying to update my data in redux but I get an error when I have more than one value in the state.
How I am transferring data into the AllPalletes component below:
<Route exact path='/' render ={(routeProps) => <AllPalletes data = {this.props.palleteNames} />} />
The AllPalletes component, where I am setting up the edit form:
class connectingPalletes extends Component {
render () {
console.log(this.props)
return (
<div>
<Menu inverted>
<Menu.Item header>Home</Menu.Item>
<Menu.Item as = {Link} to = '/createpalette'>Create a Palette</Menu.Item>
</Menu>
<Container>
<Card.Group itemsPerRow={4}>
{this.props.data.map((card) => {
let cardName = card.Name.replace(/\s+/g, '-').toLowerCase()
return (
<Card key = {card._id}>
<Image src = 'https://images.pexels.com/photos/1212406/pexels-photo-1212406.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500' wrapped ui={false}/>
<Card.Content>
<Grid>
<Grid.Column floated = 'left' width ={7}>
{card.edit? (
<PaletteEditForm {...card}/>
) : (
<Card.Header as = {Link} to = {`/palette/${cardName}`}>{card.Name}</Card.Header>
)}
</Grid.Column>
<Grid.Column floated = 'right' width = {5}>
<Icon name = 'pencil' />
<Icon name = 'trash' onClick = {() => this.props.dispatch(removePalette({id: card._id}))}/>
</Grid.Column>
</Grid>
</Card.Content>
</Card>
)
})}
</Card.Group>
<Divider></Divider>
<Divider hidden></Divider>
<Grid centered columns={1}>
<Button as = {Link} to = '/testing'>Go Back</Button>
</Grid>
</Container>
</div>
)
}
}
const AllPalletes = connect()(connectingPalletes)
export default AllPalletes
And here is the edit form:
class EditForm extends Component {
constructor(props) {
super(props)
this.state = {
paletteName: this.props.Name
}
}
handleChange = (e) => {
const val = e.target.value,
s_name = e.target.name
this.setState (() => {
return {
[s_name]: val,
}
})
}
handleSubmit = () => {
let updates = {Name: this.state.paletteName, edit: false}
this.props.dispatch(editPalette(this.props._id, updates))
}
render() {
console.log(this.props)
return (
<Form onSubmit = {this.handleSubmit}>
<Input type = 'text' name = 'paletteName' value = {this.state.paletteName} onChange={this.handleChange} />
</Form>
)
}
}
const PaletteEditForm = connect()(EditForm)
export default PaletteEditForm
My Reducer:
import uuid from 'uuid/v1'
const paletteDefault = [{
Name: "Material UI",
myArray: [],
_id: uuid(),
edit: false
}, {
Name: "Splash UI",
myArray: [],
_id: uuid(),
edit: true
}]
const PaletteReducers = (state = paletteDefault, action) => {
console.log(action)
switch(action.type) {
case 'ADD_PALETTE':
return [...state, action.palette]
case 'REMOVE_PALETTE':
return state.filter(x => x._id !== action.id)
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
default:
return state
}
}
export default PaletteReducers
My Action
// EDIT_PALETTE
const editPalette = (id, updates) => ({
type: 'EDIT_PALETTE',
id,
updates
})
export {addPalette, removePalette, editPalette}
I have a feeling that the problem could be in how I have set up the reducer case.
The edit dispatch only works when I have one value in the state. Otherwise, I am getting this error:
Uncaught TypeError: Cannot read property 'Name' of undefined
at AllPalletes.js:23
Please help..
I found the error. I had not give a return value in the 'EDIT_PALETTE' case, after the if-statement. It was
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
And instead should be:
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
return palette
})

Azure AD B2C Authentication dropped when navigating to an MVVM-based page

Two part question: On starting up, My Xamarin.Forms app.cs navigates to a login page (ContentPage) with a button. I click the button and I login successfully with this event handler in the app code behind:
async void OnLoginButtonClicked(object sender, EventArgs e)
{
try
{
bool authenticated = await App.AuthenticationProvider.LoginAsync();
if (authenticated)
{
Application.Current.MainPage = new PapMobLandingPage();
}
else
{
await DisplayAlert("Authentication", "Authentication", "OK");
}
}
catch (MsalException ex)
{
if (ex.ErrorCode == "authentication_canceled")
{
await DisplayAlert("Authentication", "Authentication was cancelled by the user.", "OK");
}
else
{
await DisplayAlert("An error has occurred", "Exception message: " + ex.Message, "OK");
}
}
catch (Exception ex)
{
await DisplayAlert("Authentication", "Authentication failed in a big way. Exception: " + ex.Message, "OK");
}
}
}
I then get redirected to Page 2 (PapMobLandingPage) which also has a button. I click that PapMobLandingPage button and get redirected to a TabbedPage with the details of the logged in user flying right in there from my Azure SQL Database, no problems. All great til now! Here is the event handler:
public async void GoToTabbedPage(object sender, EventArgs args)
{
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new BotInTabbedPage());
}
Question 1: There is no OnAppearing() in the BotInTabbedPage (TabbedPage) code behind that checks if the user is logged in... there is no acquiring of tokens going on in the TabbedPage initialization, so how does the app know that I'm logged in on this page??
I looked at a similar question from #Creepin and using the link I could not understand the answer to his original question as to whether you have to authenticate each page individually. Link here:
use of AcquireTokenSilentAsync
The reason I ask question 1 is that the tabbed page above is one of six choices the user gets on sign in, so I needed a dashboard page (MVVM based) with six tiles. When I insert this between Page 2 (PapMobLandingPage) and BotInTabbedPage (TabbedPage), and click on the TabbedPage tile, the tapcommand linked to the tile takes me to the BotInTabbedPage (TabbedPage)... BUT...
NO CLIENT DATA! I HAVE BEEN LOGGED OUT!
So to sum up:
Login Page -> PapMobLandingPage -> BotInTabbedPage = Stays authenticated.
Login Page -> PapMobLandingPage -> Dashboard -> BotInTabbedPage = Drops authentication.
If I use a "vanilla" ContentPage with a button:
Login Page -> PapMobLandingPage -> ContentPage -> BotInTabbedPage = Stays authenticated!
So second question is: does anyone have a clue why?
When I say the Dashboard is MVVM based I mean it has a XAML ContentPage, a .cs code behind, with value bindings to a view model which also has a view model template and a template base. This is the code:
Dashboard XAML:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PapWine;assembly=PapWine"
xmlns:artina="clr-namespace:UXDivers.Artina.Shared;assembly=UXDivers.Artina.Shared"
x:Class="PapWine.DashboardTaskMultipleTilesPage"
BackgroundColor="Black"
Title="{ artina:Translate PageTitleDashboardTaskMultipleTiles }">
<ContentPage.Resources>
<ResourceDictionary>
<artina:BoolMemberTemplateSelector
x:Key="Selector"
MemberName="IsNotification">
<artina:BoolMemberTemplateSelector.TrueDataTemplate>
<DataTemplate>
<local:DashboardAppNotificationItemTemplate
WidthRequest="145"
HeightRequest="145" />
</DataTemplate>
</artina:BoolMemberTemplateSelector.TrueDataTemplate>
<artina:BoolMemberTemplateSelector.FalseDataTemplate>
<DataTemplate>
<local:TaskTilesItemTemplate
ShowBackgroundImage="true"
ShowBackgroundColor="true"
ShowiconColoredCircleBackground="false"
TextColor="{ DynamicResource DashboardIconColor }"
WidthRequest="145"
HeightRequest="145"
/>
</DataTemplate>
</artina:BoolMemberTemplateSelector.FalseDataTemplate>
</artina:BoolMemberTemplateSelector>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView
Orientation="Both">
<artina:GridOptionsView
WidthRequest="320"
Margin="0"
Padding="10"
ColumnSpacing="10"
RowSpacing="10"
ColumnCount="2"
ItemsSource="{Binding DashboardTaskMultipleTilesList}"
ItemTemplate="{StaticResource Selector}"
/>
</ScrollView>
</ContentPage>
Dashboard XAML.cs
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
namespace PapWine
{
public partial class DashboardTaskMultipleTilesPage : ContentPage
{
public DashboardTaskMultipleTilesPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardTaskMultipleTilesViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
PublicClientApplication PCA = new PublicClientApplication(Constants.ClientID, Constants.Authority);
IEnumerable<IAccount> accounts = await PCA.GetAccountsAsync();
AuthenticationResult authenticationResult = await PCA.AcquireTokenSilentAsync(Constants.Scopes, GetAccountByPolicy(accounts, Constants.PolicySignUpSignIn), Constants.Authority, false);
JObject user = ParseIdToken(authenticationResult.IdToken);
var currentuseroid = user["oid"]?.ToString();
}
private IAccount GetAccountByPolicy(IEnumerable<IAccount> accounts, string policy)
{
foreach (var account in accounts)
{
string userIdentifier = account.HomeAccountId.ObjectId.Split('.')[0];
if (userIdentifier.EndsWith(policy.ToLower())) return account;
}
return null;
}
JObject ParseIdToken(string idToken)
{
// Get the piece with actual user info
idToken = idToken.Split('.')[1];
idToken = Base64UrlDecode(idToken);
return JObject.Parse(idToken);
}
string Base64UrlDecode(string str)
{
str = str.Replace('-', '+').Replace('_', '/');
str = str.PadRight(str.Length + (4 - str.Length % 4) % 4, '=');
var byteArray = Convert.FromBase64String(str);
var decoded = Encoding.UTF8.GetString(byteArray, 0, byteArray.Count());
return decoded;
}
}
}
Dashboard View Model:
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace PapWine
{
public class DashboardTaskMultipleTilesViewModel : ObservableObject
{
private List<DashboardTaskMultipleTileItem> _dashboardTaskMultipleTilesList;
public DashboardTaskMultipleTilesViewModel()
: base(listenCultureChanges: true)
{
LoadData();
}
public List<DashboardTaskMultipleTileItem> DashboardTaskMultipleTilesList
{
get { return _dashboardTaskMultipleTilesList; }
set { SetProperty(ref _dashboardTaskMultipleTilesList, value); }
}
protected override void OnCultureChanged(CultureInfo culture)
{
LoadData();
}
public async Task LogMeOut()
{
bool loggedOut = await App.AuthenticationProvider.LogoutAsync();
if (loggedOut)
{
Application.Current.MainPage = new LandingPagePreLogin();
}
}
private void LoadData()
{
DashboardTaskMultipleTilesList = new List<DashboardTaskMultipleTileItem>
{
//1 line
new DashboardTaskMultipleTileItem
{
Title = "Log Out",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Lock,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "User Settings",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Gear,
IconColour = "White"
},
//2 line
new DashboardTaskMultipleTileItem
{
Title = "User Info",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.User,
Badge = 12,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "Papillon Shop",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeWeb511Font.store,
Badge = 2,
IconColour = "White"
},
//3 line
new DashboardTaskMultipleTileItem
{
Title = "Check Bottles In",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Book,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "Lay Bottles Down",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Bed,
Badge = 2,
IconColour = "White"
},
};
}
}
public class DashboardTaskMultipleTileItem
{
public string Title { get; set; }
public string Body { get; set; }
public string Avatar { get; set; }
public string BackgroundColor { get; set; }
public string BackgroundImage { get; set; }
public bool ShowBackgroundColor { get; set; }
public bool IsNotification { get; set; }
public string Icon { get; set; }
public int Badge { get; set; }
public string NavigPage { get; set; }
private Xamarin.Forms.Command _tapCommand;
public Xamarin.Forms.Command TapCommand
{
get
{
if (_tapCommand == null)
{
switch (this.Title) {
case "Log Out":
App.AuthenticationProvider.LogoutAsync();
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new LogoutSuccessPage()));
break;
case "User Settings":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserSettingsPage()));
break;
case "User Info":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserProfilePage()));
break;
case "Papillon Shop":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new ProductFamilyMultipleTilesPage()));
break;
case "Check Bottles In":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
break;
case "Check Bottles Out":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
break;
}
}
return _tapCommand;
}
}
}
}
Tiles Item Template:
using Xamarin.Forms;
namespace PapWine
{
public partial class TaskTilesItemTemplate : TaskTilesItemTemplateBase
{
public TaskTilesItemTemplate()
{
InitializeComponent();
}
private async void OnTileTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item == null)
return;
var content = e.Item as DashboardTaskMultipleTileItem;
//await Navigation.PushAsync(new LayBottlesDown()); //pass content if you want to pass the clicked item object to another page
}
}
}
Tiles Item Template Base:
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace PapWine
{
public class TaskTilesItemTemplateBase : ContentView
{
public uint animationDuration = 250;
public bool _processingTag = false;
public static readonly BindableProperty ShowBackgroundImageProperty =
BindableProperty.Create(
nameof(ShowBackgroundImage),
typeof(bool),
typeof(TaskTilesItemTemplate),
true,
defaultBindingMode: BindingMode.OneWay
);
public bool ShowBackgroundImage
{
get { return (bool)GetValue(ShowBackgroundImageProperty); }
set { SetValue(ShowBackgroundImageProperty, value); }
}
public static readonly BindableProperty ShowBackgroundColorProperty =
BindableProperty.Create (
nameof( ShowBackgroundColor ),
typeof ( bool ),
typeof ( TaskTilesItemTemplate ),
false,
defaultBindingMode : BindingMode.OneWay
);
public bool ShowBackgroundColor {
get { return ( bool )GetValue( ShowBackgroundColorProperty ); }
set { SetValue ( ShowBackgroundColorProperty, value ); }
}
public static readonly BindableProperty ShowiconColoredCircleBackgroundProperty =
BindableProperty.Create (
nameof( ShowiconColoredCircleBackground ),
typeof ( bool ),
typeof (TaskTilesItemTemplate),
true,
defaultBindingMode : BindingMode.OneWay
);
public bool ShowiconColoredCircleBackground {
get { return ( bool )GetValue( ShowiconColoredCircleBackgroundProperty ); }
set { SetValue ( ShowiconColoredCircleBackgroundProperty, value ); }
}
public static readonly BindableProperty TextColorProperty =
BindableProperty.Create (
nameof( TextColor ),
typeof ( Color ),
typeof (TaskTilesItemTemplate),
defaultValue : Color.White,
defaultBindingMode : BindingMode.OneWay
);
public Color TextColor {
get { return ( Color )GetValue( TextColorProperty ); }
set { SetValue ( TextColorProperty, value ); }
}
//added start
public Color IconColour
{
get { return (Color)GetValue(TextColorProperty); }
set { SetValue(TextColorProperty, value); }
}
//added end
public async void OnWidgetTapped(object sender, EventArgs e)
{
if (_processingTag)
{
return;
}
_processingTag = true;
try{
await AnimateItem (this, animationDuration );
await SamplesListFromCategoryPage.NavigateToCategory ((SampleCategory)BindingContext, Navigation);
}finally{
_processingTag = false;
}
}
private async Task AnimateItem(View uiElement, uint duration ){
var originalOpacity = uiElement.Opacity;
await uiElement.FadeTo(.5, duration/2, Easing.CubicIn);
await uiElement.FadeTo(originalOpacity, duration/2, Easing.CubicIn);
}
}
}
LoginAsync looks like this:
public async Task<bool> LoginAsync(bool useSilent = false)
{
bool success = false;
//AuthenticationResult authResult = null;
try
{
AuthenticationResult authenticationResult;
if (useSilent)
{
authenticationResult = await ADB2CClient.AcquireTokenSilentAsync(
Constants.Scopes,
GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
Constants.Authority,
false);
UpdateUserInfo(authenticationResult);
}
else
{
authenticationResult = await ADB2CClient.AcquireTokenAsync(
Constants.Scopes,
GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
App.UiParent);
}
if (User == null)
{
var payload = new JObject();
if (authenticationResult != null && !string.IsNullOrWhiteSpace(authenticationResult.IdToken))
{
payload["access_token"] = authenticationResult.IdToken;
}
User = await TodoItemManager.DefaultManager.CurrentClient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
payload);
success = true;
}
}

Nativescript list-view with multiple item templates toggling visibility

I have an issue with the radlistview with multiple item templates and toggling visibility.
I'm trying to re-create an according type display without using the nativescript-accordion plugin. By toggling the visibility attribute for a given item. Here's my xml:
<lv:RadListView row="3" items="{{ locationList }}" id="locationList" iosEstimatedRowHeight="0" itemTap="listViewItemTap" itemTemplateSelector="templateSelector" class="list-group">
<lv:RadListView.itemTemplates>
<template key="header">
<GridLayout columns="auto, *" visibility="{{ isItemVisible ? 'visible' : 'collapsed' }}">
...content...</GridLayout>
</template>
<template key="list-item">
<GridLayout columns="*, auto" rows="auto, 15" visibility="{{ isItemVisible ? 'visible' : 'collapsed' }}">...content...
</GridLayout>
</template>
</lv:RadListView.itemTemplates>
</lv:RadListView>
Here's the itemTap method:
if (tappedItem.type == "list-item" || tappedItem.type == "list-item-no-location") {
// Navigate to the details page with context set to the data item for specified index
topmost().navigate({....}});
} else if (tappedItem.type == "header") {
viewModel.collapseExpandItems(tappedItem);
setTimeout(() => {
if (platformModule.isIOS) {
// Uncomment the lines below to avoid default animation
// UIView.animateWithDurationAnimations(0, () => {
var indexPaths = NSMutableArray.new();
indexPaths.addObject(NSIndexPath.indexPathForRowInSection(rowIndex, args.groupIndex));
//console.log("indexPaths:", indexPaths);
listView.ios.reloadItemsAtIndexPaths(indexPaths);
// });
}
if (platformModule.isAndroid) {
listView.androidListView.getAdapter().notifyItemChanged(rowIndex);
}
}, 550);
And for the loading of the items, here is some code:
var newHeader = new Item(location.type, location.id, location.name, ..., true);
viewModel.locationList.push(newHeader);
var newItem = new Item(listItem.type, listItem.id, listItem.name, ... , true);
viewModel.locationList.push(newItem);
locationList being the ObservableArray in the viewModel.
And here is the Item class in the viewModel:
var Item = (function (_super) {
__extends(Item, _super);
function Item(type, id, name, ..., isItemVisible) {
var _this = _super.call(this) || this;
_this.type = type;
...
_this.isItemVisible = isItemVisible;
return _this;
}
Item.prototype.toggleVisibility = function (args) {
// console.dir(this);
console.log("toggleVisibility value: " + this.isItemVisible);
this.set("isItemVisible", !this.isItemVisible);
};
return Item;
}(Observable.Observable));
And finally the viewModel.collapseExpandItems method in the viewModel:
collapseExpandItems: function(tappedItem) {
this.locationList.forEach(function(item) {
//console.log("isItemVisible:", item.isItemVisible);
if ((item.type === 'list-item') && item.id === tappedItem.id) {
item.toggleVisibility();
}
});
},
It's hiding the items below the header item, but all the items below, even the ones that were not set to visibilty="collapsed".
Please see .gif for the behavior. Any ideas?enter image description here
It seems like momentarily its doing the right thing, but then it hides everything under, which is not what I want. I want it to just hide the items under the tapped header.

Using image-cache for a LIstView in Typescript/Angular2/Nativescript

I've implemented a solution similar to this Answer however I'm using Angular2 conventions to define my page and not traditional Nativescript as in the referenced discussion.
The issue is that my ListView does not react to the notifyPropertyChange event to update the images when the upload into the image-cache is complete. The displayed images do not change from the placeholder image until I force it to update such as scrolling to hide the row and it updates when it reappears or leave the page and return.
I'm thinking the problem is that I'm mixing data/observable and the Angular2 Observable and/or the changeDetection: ChangeDetectionStrategy.OnPush. Suggestions?
Definition of ImageItem
import observable = require("data/observable");
import imageCache = require("ui/image-cache");
import imageSource = require("image-source");
import {Config, everliveConfig } from './config';
var cache = new imageCache.Cache();
cache.maxRequests = 10;
cache.placeholder = imageSource.fromFile("~/resource-seed/butterfly.jpg");
export class ImageItem extends observable.Observable
{
private _imageSrc: string
get imageSrc(): imageSource.ImageSource
{
var image = cache.get(this._imageSrc);
if (image)
{
return image; /* 3 */
}
cache.push(
{
key: this._imageSrc,
url: this._imageSrc,
completed: (image) =>
{
this.notifyPropertyChange('imageSrc', image);
console.log("this.notifyPropertyChange('imageSrc', image)");
}
});
return cache.placeholder;
}
constructor(imageId : string)
{
super();
everliveConfig.files.getDownloadUrlById(imageId)
.then( url => {
this._imageSrc = url;
});
}
}
Layout of my component
<GridLayout rows="32, 32, *" columns="*">
<Label row="0" text='Points of Interest' class='page-title' horizontalAlignment="center" > </Label>
<Label row="1" text='Tap to learn more' class='page-subtitle' horizontalAlignment="center" > </Label>
<ListView row="2" [items]="areas" class="area-group" (itemTap)="onItemTap($event)">
<template let-area="item">
<GridLayout rows="100" columns="38, 100, *" orientation="horizontal">
<Button col="0" [backgroundImage]='checkButtonImagePath(area)'
class='area-checkmark-button'
(tap)='onToggleCheck(area)'>
</Button>
<Image col="1" [src]='area.imageItem.imageSrc' class='area-thumbnail' stretch="aspectFill"></Image>
<Label col="2" [text]='area.Name' class='area-title'> </Label>
</GridLayout>
</template>
</ListView>
</GridLayout>
Component Definition
import { Component, ChangeDetectionStrategy, Inject } from "#angular/core";
import { Router } from '#angular/router';
import { Area, AreaList } from '../../shared/model/area.list';
#Component({
moduleId: module.id,
selector: "area",
templateUrl: "area.html",
styleUrls:["area.common.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AreaComponent {
public areas ;
constructor(
private _router: Router,
#Inject('AreaList') public areaList: AreaList) {
this.areas = areaList.list;
}
public onItemTap(args) {
console.log("Item Tapped at cell index: " + args.index);
var area: Area = this.areaList.area(args.index);
this._router.navigate(["/detail", area.Key]);
}
public onToggleCheck(area:Area) {
area.isChecked = !area.isChecked;
}
public checkButtonImagePath(area):string {
return area.isChecked ? 'res://dawes_11' : 'res://dawes_21';
}
}
Definition of AreaList
import {Config, everliveConfig } from './config';
import { Injectable } from "#angular/core";
import {ImageItem} from '../../shared/model/image.item';
import { ObservableArray } from "data/observable-array";
export class Area {
public isChecked = false;
public imageItem : ImageItem;
constructor(
public Id: string,
public Name: string,
public Description: string,
public Image: string,
public Key: string
) {
this.imageItem = new ImageItem(this.Image);
}
}
#Injectable()
export class AreaList {
public list : Array<Area>;
constructor () {
var areaData = everliveConfig.data('Area');
areaData.get(null, data => {
var l = new Array<Area>();
data.result.forEach(area => {
l.push(new Area(area.Id, area.Name, area.Description,
area.Image, area.Key))
})
this.list = l;
},
err => {
console.log(err.message);
});
}
public area(index: number) : Area {
return this.list[index];
}
public areaByKey(key: string) : Area {
for (let area of this.list) {
if (key == area.Key) {
return area;
}
}
return null;
}
}

Set Image Source on return from Gallery/Camera?

I have an Image view I'm having trouble setting the source for. I'm using a button to execute a TakePictureCommand which calls the TakePicture() method (shown below) which in turn sets my source "ImageSource". Debugging the method shows the image is coming in, but I never see it come up in the UI.
I may not be setting the binding for the Image properly, this is what I have:
Image avatar = new Image();
avatar.Source = ImageSource;
Button setImageBtn = new Button{ Text = "Photo" };
setImageBtn.Clicked += async (sender, e) =>
{
string action = await DisplayActionSheet(
"Event Photo", "Cancel", null, OPTION_CAMERA, OPTION_GALLERY);
if(action == OPTION_CAMERA) {
TakePictureCommand.Execute(null);
}
else if(action == OPTION_GALLERY) {
SelectPictureCommand.Execute(null);
}
};
TakePicture()
private async Task<MediaFile> TakePicture()
{
Setup();
ImageSource = null;
return await _mediaPicker.TakePhotoAsync(
new CameraMediaStorageOptions {
DefaultCamera = CameraDevice.Front,
MaxPixelDimension = 400
}).ContinueWith(t =>
{
if (t.IsFaulted)
{
Status = t.Exception.InnerException.ToString();
}
else if (t.IsCanceled)
{
Status = "Canceled";
}
else
{
var mediaFile = t.Result;
ImageSource = ImageSource.FromStream(() => mediaFile.Source);
return mediaFile;
}
return null;
}, _scheduler);
}
What am I missing here?
Below approach is working for me:
First, in XAML I added Image view
<Image Source="{Binding ImageSource}" VerticalOptions="Fill" HorizontalOptions="Fill"
Aspect="AspectFit"/>
Second, in ViewModel I added ImageSource property like this:
public ImageSource ImageSource
{
get { return _imageSource; }
set { this.SetProperty(ref _imageSource, value); }
}
Third, in command handler:
await TakePicture ();
Forth, code of TakePicture() method the same as you wrote.
My Setup():
if (_mediaPicker != null)
return;
var device = Resolver.Resolve<IDevice>();
_mediaPicker = DependencyService.Get<IMediaPicker>() ?? device.MediaPicker;

Resources