Nativescript Show element while scrolling up in ListView - parallax effect - nativescript

I would like to get an advice on how to go around implementing a feature seen in many apps like Whatsapp or Facebook, where you have a list and a header which is not visible always, but is gradually shown when the user begins to scroll upwards from any place within a list.
In whatsapp and facebook the upward scrolling gesture causes the Search bar to slowly appear at the top of the screen, while the list itself is not scrolling until the search bar appears at full (at least this is the android implementation).
I need an advice on how to implement this using Nativescript angular with Telerik RadListView (android + ios). As far as I know, putting a ListView inside a ScrollView generally is not advised by telerik.
Thanks!
Edit: I learned it is called a parallax effect, and found examples of it in native android, however, not in nativescript with ListView (did find an example with ScrollView and regular StackLayout, not with a ListView inside).

You can check the available "Implementing a Parallax Scroll Effect" sample in the 'Samples' section of the official NativeScript marketplace website that shows how to implement just that effect. Just go to Market.nativescript.org and search for 'parallax'. Also there is a plugin that provides such functionality but I am not sure of its quality.

Here is an example of a scrollable parallax effect RadListView implemented using Angular (no ScrollView needed). It also provides an example of sticking the list header at the top.
Please me know if it works for you.
Component template:
<GridLayout class="page">
<RadListView (scrolled)="onScroll($event)" [items]="dataItems" itemReorder="true"
(itemReordered)="onItemReordered($event)">
<ListViewGridLayout tkListViewLayout scrollDirection="Vertical" spanCount="1" ios:itemHeight="150"
dynamicItemSize="false"></ListViewGridLayout>
<ng-template tkListItemTemplate let-item="item">
<StackLayout orientation="vertical">
<!-- list item content goes here -->
</StackLayout>
</ng-template>
<ng-template tkListViewHeader>
<StackLayout>
<GridLayout #fixedHeaderContainer class="fixed-header-container">
<label text="Fixed Content" verticalAlignment="center"></label>
</GridLayout>
<StackLayout class="list-header-container">
<StackLayout #listHeaderContainer>
<label text="List Title"></label>
</StackLayout>
</StackLayout>
</StackLayout>
</ng-template>
</RadListView>
<GridLayout verticalAlignment="top" [height]="dockContainerHeight" [opacity]="dockContainerOpacity">
<FlexboxLayout justifyContent="flex-start" alignItems="center" class="docked-label-wrapper">
<button class="fas" text=""></button>
<StackLayout flexGrow="1" height="100%" [opacity]="dockContentOpacity" orientation="horizontal">
<label text="List Title"></label>
</StackLayout>
</FlexboxLayout>
</GridLayout>
Component scss:
.fixed-header-container {
height: 200;
padding: 0 16;
background-color: green;
label {
font-size: 30;
font-weight: 700;
color: $white;
}
}
.list-header-container {
margin-top: -12;
border-radius: 12 12 0 0;
background-color: #ffffff;
label {
margin: 16 0;
font-size: 22;
color: black;
vertical-align: center;
}
.smaller-label {
font-size: 12;
color: #909090;
}
}
RadListView {
height: 100%;
background-color: #ffffff;
}
.docked-label-wrapper {
margin: 0 0 10;
background-color: #ffffff;
.fas {
margin: 0;
font-size: 18;
}
label {
font-size: 18;
color: black;
vertical-align: center;
}
}
Component ts:
import { Component, ElementRef, OnInit, ViewChild } from '#angular/core';
import { ListViewEventData, ListViewScrollEventData } from 'nativescript-ui-listview';
import { DataItem, DataItemService } from './data-items.service';
export const DOCK_HEADER_HEIGHT = 58;
#Component({
moduleId: module.id,
selector: 'app-comp-name',
templateUrl: './comp-name.component.html',
styleUrls: ['./comp-name.component.scss']
})
export class CompNameComponent implements OnInit {
dataItems: DataItem[];
dockContainerHeight = DOCK_HEADER_HEIGHT;
dockContainerOpacity = 0;
dockContentOpacity = 0;
#ViewChild('fixedHeaderContainer', { static: false })
fixedHeaderContainerRef: ElementRef;
#ViewChild('listHeaderContainer')
listHeaderContainerRef: ElementRef;
constructor(private _dataItemService: DataItemService) {}
ngOnInit(): void {
this.dataItems = this._dataItemService.getDataItems();
}
onItemReordered(args: ListViewEventData) {
console.log('Item reordered. Old index: ' + args.index + ' ' + 'new index: ' + args.data.targetIndex);
}
onScroll(args: ListViewScrollEventData) {
if (!this.fixedHeaderContainerRef) {
return;
}
const offset = args.scrollOffset < 0 ? 0 : args.scrollOffset;
const fixedHeaderHeight = this.fixedHeaderContainerRef.nativeElement.getActualSize().height;
this.applyFixedHeaderTransition(offset);
this.applyTitleTransition(offset, fixedHeaderHeight);
this.applyDockHeaderTransition(offset, fixedHeaderHeight);
}
private applyFixedHeaderTransition(scrollOffset: number) {
this.fixedHeaderContainerRef.nativeElement.translateY = scrollOffset;
}
private applyTitleTransition(scrollOffset: number, fixedHeaderHeight: number) {
const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
const titleElement = this.listHeaderContainerRef.nativeElement;
if (maxHeightChange < scrollOffset) {
titleElement.translateX = -(scrollOffset - maxHeightChange) / 1.2;
titleElement.translateY = -(scrollOffset - maxHeightChange) * 2;
titleElement.scaleX = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
titleElement.scaleY = 1 - (scrollOffset - maxHeightChange) / fixedHeaderHeight;
} else {
titleElement.translateX = 0;
titleElement.translateY = 0;
titleElement.scaleX = 1;
titleElement.scaleY = 1;
}
}
private applyDockHeaderTransition(scrollOffset: number, fixedHeaderHeight: number) {
const maxHeightChange = fixedHeaderHeight - DOCK_HEADER_HEIGHT;
const containerOpacity = 1 - scrollOffset / maxHeightChange;
this.dockContainerOpacity = containerOpacity <= 0 ? 1 : 0;
this.dockContentOpacity = (scrollOffset - (fixedHeaderHeight - DOCK_HEADER_HEIGHT)) / DOCK_HEADER_HEIGHT - 0.2;
}
}

Related

Listview for Nativescript 7

Does the Listview work with Nativescript 7 with Angular I can still see it need tns-core modules?
I get a lot of errors on the nativescript-ui-listview ever since the upgrade.
In NS7 you have to use #nativescript/core instead of tns-core which is kept for the retro compatibility.
Meaning you have to replace all tns-core occurrences with the #nativescript/core.
The ListView works.
I got the RadListView to render but the swiping is not docking and the buttons not disappearing after the swipe. Here the versions I am working with whilst following some tutorial:
"dependencies": {
"#angular/animations": "~11.0.0",
"#angular/common": "~11.0.0",
"#angular/compiler": "~11.0.0",
"#angular/core": "~11.0.0",
"#angular/forms": "~11.0.0",
"#angular/platform-browser": "~11.0.0",
"#angular/platform-browser-dynamic": "~11.0.0",
"#angular/router": "~11.0.0",
"#fortawesome/fontawesome-free": "^5.15.1",
"#nativescript/angular": "~11.0.0",
"#nativescript/core": "~7.1.0",
"#nativescript/theme": "~3.0.0",
"nativescript-toasty": "^3.0.0-alpha.2",
"nativescript-ui-listview": "^9.0.4",
"nativescript-ui-sidedrawer": "^9.0.3",
"reflect-metadata": "~0.1.12",
"rxjs": "^6.6.0",
"zone.js": "~0.11.1"
}
Like Robertino mentioned you will have to import from #nativescript/core rather than tns-core-modules. Check out the component file:
import { Component, OnInit, Inject, ViewChild } from '#angular/core';
import { FavoriteService } from '../services/favorite.service';
import { Dish } from '../shared/dish';
import { ListViewEventData, RadListView, SwipeActionsEventData } from 'nativescript-ui-listview';
import { RadListViewComponent } from 'nativescript-ui-listview/angular';
import { ObservableArray } from '#nativescript/core/data/observable-array';
import { View } from '#nativescript/core/ui/core/view';
import { confirm } from "#nativescript/core/ui";
import { ToastDuration, ToastPosition, Toasty } from 'nativescript-toasty';
#Component({
selector: 'app-favorites',
moduleId: module.id,
templateUrl: './favorites.component.html',
styleUrls: ['./favorites.component.css']
})
export class FavoritesComponent implements OnInit {
favorites: ObservableArray<Dish>;
errMess: string;
#ViewChild('myListView') listViewComponent: RadListViewComponent;
constructor(private favoriteservice: FavoriteService,
#Inject('baseURL') private baseURL) {
}
ngOnInit() {
this.favoriteservice.getFavorites()
.subscribe(favorites => this.favorites = new ObservableArray(favorites),
errmess => this.errMess = errmess);
}
deleteFavorite(id: number) {
console.log('delete', id);
let options = {
title: "Confirm Delete",
message: 'Do you want to delete Dish '+ id,
okButtonText: "Yes",
cancelButtonText: "No",
neutralButtonText: "Cancel"
};
confirm(options).then((result: boolean) => {
if(result) {
this.favorites = null;
this.favoriteservice.deleteFavorite(id)
.subscribe(favorites => {
const toast = new Toasty({
text:"Deleted Dish "+ id,
duration: ToastDuration.SHORT,
position: ToastPosition.BOTTOM
});
toast.show();
this.favorites = new ObservableArray(favorites);
},
errmess => this.errMess = errmess);
}
else {
console.log('Delete cancelled');
}
});
}
public onCellSwiping(args: ListViewEventData) {
var swipeLimits = args.data.swipeLimits;
var currentItemView = args.object;
var currentView;
if(args.data.x > 200) {
}
else if (args.data.x < -200) {
}
}
public onSwipeCellStarted(args: SwipeActionsEventData) {
const swipeLimits = args.data.swipeLimits;
const swipeView = args['object'];
const leftItem = swipeView.getViewById<View>('mark-view');
const rightItem = swipeView.getViewById<View>('delete-view');
swipeLimits.left = leftItem.getMeasuredWidth();
swipeLimits.right = rightItem.getMeasuredWidth();
swipeLimits.threshold = leftItem.getMeasuredWidth()/2;
}
public onSwipeCellFinished(args: ListViewEventData) {
}
public onLeftSwipeClick(args: ListViewEventData) {
console.log('Left swipe click');
this.listViewComponent.listView.notifySwipeToExecuteFinished();
}
public onRightSwipeClick(args: ListViewEventData) {
this.deleteFavorite(args.object.bindingContext.id);
this.listViewComponent.listView.notifySwipeToExecuteFinished();
}
}
Here is the template:
<ActionBar title="My Favorites" class="action-bar">
</ActionBar>
<StackLayout class="page">
<RadListView #myListView [items]="favorites" *ngIf="favorites"
selectionBehavior="none" (itemSwipeProgressEnded)="onSwipeCellFinished($event)"
(itemSwipeProgressStarted)="onSwipeCellStarted($event)"
(itemSwipeProgressChanged)="onCellSwiping($event)"
swipeActions="true">
<ListViewLinearLayout tkListViewLayout scrollDirection="vertical"
itemInsertAnimation="Default" itemDeleteAnimation="Default">
</ListViewLinearLayout>
<ng-template tkListItemTemplate let-item="item">
<StackLayout orientation="horizontal" class="listItemStackLayout">
<Image row="0" col="0" rowSpan="2" height="60" width="60"
[src]="baseURL + item.image" class="thumb p-16"></Image>
<GridLayout rows="auto, *" columns="*">
<Label row="0" col="0" [text]="item.name" class="labelName"></Label>
<Label row="1" col="0" [text]="item.description" class="labelText" textWrap="true"></Label>
</GridLayout>
</StackLayout>
</ng-template>
<GridLayout *tkListItemSwipeTemplate columns="auto, * , auto" class="listItemSwipeGridLayout">
<StackLayout id="mark-view" class="markViewStackLayout" col="0"
(tap)="onLeftSwipeClick($event)">
<Label text="" class="swipetemplateLabel fas"
verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
<StackLayout id="delete-view" class="deleteViewStackLayout" col="2"
(tap)="onRightSwipeClick($event)">
<Label text="" class="swipetemplateLabel fas"
verticalAlignment="center" horizontalAlignment="center"></Label>
</StackLayout>
</GridLayout>
</RadListView>
<ActivityIndicator busy="true" *ngIf="!(favorites || errMess)" width="50"
height="50" class="activity-indicator"></ActivityIndicator>
<Label *ngIf="errMess" [text]="'Error: ' + errMess"></Label>
</StackLayout>
And the css class:
.listItemStackLayout {
background-color: white;
padding: 10;
}
.labelName {
font-size: 20;
font-weight: bold;
margin-bottom: 8;
margin-left: 16;
}
.labelTitle {
font-size: 14;
font-weight: bold;
}
.labelText {
font-size: 12;
margin-left: 16;
}
.markViewStackLayout {
background-color: blue;
padding: 16;
}
.deleteViewStackLayout {
background-color: red;
padding: 16;
}
.listItemSwipeGridLayout {
background-color: white;
}
.swipetemplateLabel {
size: 20;
font-size: 28;
color: white;
}
There are a few lines of code you may have to remove for things to work on your end.

Making the CKEditor 5 inline placeholder widget editable

I've added the placeholder widget to my CKEditor 5 build. However I don't like that I can't change the text after it is inserted. I tried removing isObject from the schema but that didn't do anything. I'd appreciate it if someone could show me how this can be achieved.
In version 4 the edit of the text is done via popup called by double clicking on the placeholder: https://ckeditor.com/cke4/addon/placeholder
In version 5 the placeholder was not fully implemented intentionally
"We didn't decide, though to implement a ready-to-use placeholder feature as it's usually needed to work differently in various systems"
https://github.com/ckeditor/ckeditor5/issues/871
So the only thing you can do is to implement the function you are missing yourself. I suggest looking at how version 4 does and understanding if it is applicable to version 5.
I have implemented it just with additional HTML block and control it with state (in my case I'm using React).
const CKInlineEditorField = ({
defaultValue = '',
onChange,
placeholder = '',
}) => {
const [showPlaceholder, setShowPlaceholder] = useState(false);
const { t } = useTranslation();
useEffect(() => {
if (!defaultValue) {
setShowPlaceholder(true);
}
}, [defaultValue]);
return (
<div>
<Paper variant="outlined" className="InlineEditor">
<div className="InlineEditor__placeholder">
{showPlaceholder && `${t(placeholder)}...`}
</div>
<div className="InlineEditor__editor">
<CKEditor
editor={InlineEditor}
data={defaultValue}
onChange={(event, editor) => {
const data = editor.getData();
setShowPlaceholder(!data);
}}
onBlur={(event, editor) => {
const data = editor.getData();
onChange(data);
}}
/>
</div>
</Paper>
</div>
);
};
And SCSS file:
.InlineEditor {
background-color: #eee;
position: relative;
&__placeholder {
position: absolute;
top: 15px;
left: 10px;
z-index: 0;
}
&__editor {
position: relative;
z-index: 2;
}
}

Nativescript Composite components

Criei um component com estilo .SCSS porem ele não aplica o estilo.
precisar ser especificamente .css o aquivo de estilo?
(English translation)
I created a component with .SCSS style but it does not apply the style. need to be specifically .css the style file?
{
...,
"devDependencies": {
"css-loader": "^3.4.2",
"nativescript-dev-webpack": "~1.5.1",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2",
"typescript": "~3.5.3",
"webpack": "^4.42.1"
},
...
}
.container-search {
margin: 8;
padding: 0 5;
border-radius: 30;
background-color: white;
textField {
border-width: 1;
border-color: transparent;
font-size: 14;
margin: 0;
color: #333;
}
button {
width: 35;
height: 35;
padding: 0;
margin: 0;
color: #999;
border-radius: 100%;
background-color: #fff;
font-size: 16;
font-weight: 700;
}
}
<StackLayout class="container-search" loaded="onLoaded">
<GridLayout rows="auto" columns="auto,*,auto" >
<Button row="0" col="0" id="searchButton" text="" class="las" tap="{{ onSearchSubmit }}" />
<TextField row="0" col="1" id="searchField" hint="Pesquise.." autocorrect="false" returnKeyType="search" returnPress="{{ onSearchSubmit }}" />
<Button row="0" col="2" id="searchClear" text="" class="las" tap="{{ onSearchClear }}" visibility="hidden" />
</GridLayout>
</StackLayout>
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout/stack-layout";
import { TextField } from "tns-core-modules/ui/text-field/text-field";
import { Button } from "tns-core-modules/ui/button/button";
import { Internationalization } from "~/pages/#shared/utilities/internationalization";
export class SearchBar {
public static shared: SearchBar;
private static searchField: TextField;
private static searchButton: Button;
private static searchClear: Button;
public static onLoaded(args: any) {
const innerComponent = args.object as StackLayout;
const bindingContext = {...innerComponent.bindingContext};
SearchBar.searchField = innerComponent.getViewById("searchField") as TextField;
SearchBar.searchButton = innerComponent.getViewById("searchButton") as Button;
SearchBar.searchClear = innerComponent.getViewById("searchClear") as Button;
SearchBar.searchField.on("textChange", SearchBar.onTextChangeST);
bindingContext.internationalization = Internationalization.singleton().getData();
bindingContext.onSearchSubmit = SearchBar.onSearchSubmit;
bindingContext.onSearchClear = SearchBar.onSearchClear;
innerComponent.bindingContext = bindingContext;
SearchBar.shared = SearchBar;
}
public static get onSearchSubmit(): (args: any) => void {
return SearchBar.onSearchSubmitST;
}
public static get onSearchClear(): (args: any) => void {
return SearchBar.onSearchClearST;
}
// Auxiliaries Methods
private static onTextChangeST(args: any) {
const text = SearchBar.searchField.text;
const bindingContext = args.object.page.bindingContext;
if(text != ""){
SearchBar.searchClear.visibility = "visible";
} else {
SearchBar.searchClear.visibility = "collapse";
bindingContext.search("");
}
}
private static onSearchSubmitST(args: any) {
const text = SearchBar.searchField.text;
const bindingContext = args.object.page.bindingContext;
if(args.object.id == "searchButton") {
setTimeout(() => {
SearchBar.searchField.dismissSoftInput();
}, 100);
}
const result = bindingContext.search(text);
if(result == 0) { ()=>{} }
}
private static onSearchClearST(args: any) {
const bindingContext = args.object.page.bindingContext;
SearchBar.searchField.text = "";
bindingContext.searchClear();
}
}
export const onLoaded = SearchBar.onLoaded;
Not necessary .css I think You need to include your file in the webpack this is a link to show you an exemple :How to inciude a local html file in a webview in nativescript main-page.js?
Second option is :Migrate the project to the latest version wiht tns migrate if it is not done .
Third option : You need to compile the .scss files with :
npm install --save-dev node-sass sass-loader
I am letting you this link : https://www.tjvantoll.com/2019/08/30/nativescript-sass/
In the case you have not seen this you may try this link
https://www.tjvantoll.com/2019/08/30/nativescript-sass/

text color in kendo-numerictextbox in KendoUI for Angular

I want to change the text color in a kendo-numerictextbox for KendoUI for Angular.
The style attribute has no effect. With Jquery we can change the style but how can we do this in KendoUI for Angular ?
<kendo-numerictextbox class="form-control"
[decimals]="1"
[spinners]="false"
[format]="'n1'"
tabindex="{{i}}"
style="font-size:12px; padding:1px; color:red"
[formControlName]="item.Index" >
</kendo-numerictextbox>
import { Component } from "#angular/core";
#Component({
selector: "my-app",
template: `
<style>
::ng-deep .k-numerictextbox .k-numeric-wrap .k-input {
color: red;
}
</style>
<kendo-numerictextbox
[value]="value"
[min]="0"
[max]="100"
[autoCorrect]="autoCorrect"
style="font-size:12px; padding:1px;"
>
</kendo-numerictextbox>
`
})
export class AppComponent {
public autoCorrect: boolean = false;
public value: number = 5;
}

Kendo Display Multiple Bar Charts on Web Page

I'm using Kendo UI and trying to display multiple charts on a single web page. Everything works until I try to add a second bar chart. One of the charts does not display and just shows a generic chart image (it looks like it is not finding the data but if I reorder they reverse what is happening). I can display multiple line charts. Any ideas about what might be happening?
Below is my html for the two charts without the SVG info:
<div kendo-chart="" k-options="vm.barChartOptions" ng-show="this.dataItem.visible" class="move k-block ng-scope k-chart" id="costPerPound" style="float: left; margin: 5px 0px; position: relative;" data-uid="b543ff9a-ee57-4ce7-a39d-8f69a0505a2b" role="option" aria-selected="false" data-role="chart"></div>
<div kendo-chart="" k-options="vm.barChartOptions" ng-show="this.dataItem.visible" class="move k-block ng-scope k-chart" id="numShipments" style="float: left; margin: 5px 0px; position: relative;" data-uid="97094bf1-4366-4974-b92f-edf36d1980f4" role="option" aria-selected="false" data-role="chart"></div>
Here are is the k-options info. As you can see I am setting most of my information at render.
vm.barChartOptions = {
dataSource: vm.chartData_datasource,
series: [
{
}
],
valueAxis: {
line: {
visible: false
},
labels: {
rotation: "auto"
}
},
tooltip: {
visible: true,
template: "#= series.name #: #= value #"
},
render: function (e) {
var chart = e.sender;
var chartData = vm.findChartData(e);
if (chartData != null) {
chartData.categoryAxisField = vm.firstToLower(chartData.categoryAxisField);
chart.options.title.text = chartData.title;
chart.options.name = chartData.htmlID;
chart.options.categoryAxis.field = chartData.categoryAxisField;
chart.options.categoryAxis.labels.format = chartData.categoryAxisLabel;
chart.options.legend.position = chartData.legendPosition;
chart.options.seriesDefaults.type = chartData.chartType;
for (var i = 0; i < chart.options.series.length; i++) {
chart.options.series[i].type = chartData.chartType;
chart.options.series[i].field = vm.firstToLower(chartData.dataField);
}
}
}
}
I had same issue. It can be resolved by just providing different name
to each chart.

Resources