search filter and listview in nativescript with angular - nativescript-angular

I am new to nativescript!
I am trying to implement a search filter with listview in nativescript-angular, but I am not getting result filter from listview. My HTML component files' code follows.
Here I take searchbar component and listview for showing on screen (home.html):
<ActionBar title="Search" class="action-bar">
<SearchBar hint="Enter search term here"
[text]="searchTerm" (textChange)="onTextChanged($event)"
(clear)="onClear($event)" (submit)="onSubmit($event)" >
</SearchBar>
</ActionBar>
<StackLayout backgroundColor="#66cdaa" padding="5">
<!-- <Label text="Select country"></Label> -->
<listView [items]="dataItems"
suggestMode="Suggest" displayMode="Tokens" backgroundColor="yellow" hint="serch suggestion">
<SuggestionView tkAutoCompleteSuggestionView>
<ng-template tkSuggestionItemTemplate let-item="item">
<StackLayout orientation="vertical" padding="10">
<Label [text]="item.text"></Label>
</StackLayout>
</ng-template>
</SuggestionView>
</listView>
</StackLayout>
My home.component.ts file code:
import { Component, ViewChild,OnInit } from "#angular/core";
import { ObservableArray } from "tns-core-modules/data/observable-array";
import { TokenModel, AutoCompleteCompletionMode, AutoCompleteDisplayMode, AutoCompleteSuggestMode } from "nativescript-ui-autocomplete";
import { SearchBar } from "tns-core-modules/ui/search-bar";
export class BrowseComponent {
data = [];
name="countries"
searchPhrase: string;
private _items: ObservableArray<TokenModel>;
private countries = ["Australia", "Albania", "Austria", "Argentina", Maldives",
"Bulgaria", "Belgium", "Cyprus", "Italy", "Japan", "Denmark", "Finland",
"France", "Germany", "Greece", "Hungary", "Ireland", "Saudi Arabia",
"Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Slovakia",
"Slovenia","South Africa","South Korea","Spain","Sri Lanka",
"St Kitts & Nevis","St Lucia","St Vincent","St Lucia","Sudan",
"Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan",
"Tajikistan","Tanzania","Thailand","Timor","Togo","Tonga",
"Trinidad & Tobago","Tunisia","Turkey","Turkmenistan",
"Turks & Caicos","Uganda","Ukraine","United Arab Emirates",
"United Kingdom","Uruguay","Uzbekistan","Venezuela","Vietnam",
"Virgin Islands (US)","Yemen","Zambia","Zimbabwe"];
Here, I take country array and I am trying to filter this country list from html but I am not getting how to implement:
constructor() {
this.initDataItems();
// Use the component constructor to inject providers.
}
These are the functions from searchbar:
onSumit(args) {
const searchBar = args.object as SearchBar;
console.log(`Searching for ${searchBar.text}`);
}
onTextChanged(dataItem) {
const searchBar = dataItem as SearchBar;
console.log(`Input changed! New value: ${dataItem}`);
}
onClear(args) {
const searchBar = args.object as SearchBar;
console.log(`Clear event raised`);
}
get dataItems(): ObservableArray<TokenModel> {
return this._items;
}
Here this is observable Array I take:
private initDataItems() {
this._items = new ObservableArray<TokenModel>();
for (let i = 0; i < this.countries.length; i++) {
this._items.push(new TokenModel(this.countries[i], undefined));
}
ngOnInit(): void {
// Init your component properties here.
}
}

GlobalCompanyArrList: Array<Company>;
onTextChanged(dataItem) {
const searchBar = dataItem as SearchBar;
const searchValue = searchBar.text.toLowerCase();
let arrayItems=this.GlobalCompanyArrList; //Set Main countries Arraylist data to Local Arraylist for Searching
this.companylist_arr=new Array();
if (searchValue !== "") {
for (let i = 0; i < arrayItems.length; i++) {
if (arrayItems[i].NAME.toLowerCase().indexOf(searchValue) !== -1) {
this.companylist_arr.push(arrayItems[i]);
}
}
}
console.log("SearchBar text changed! New value: " + searchBar.text);
}

Related

LitElement initialize State from Properties without default value

I'm trying out Lit and I'm having trouble figuring out how to initialize a state field from props. Here's a basic contrived pagination component:
export class Pagination extends BaseElement {
static styles = [BaseElement.styles];
#property({type: Number})
selected = 0;
#state()
_selected = this.selected;
#property({type: Number})
total!: number;
#property({type: Number})
pageSize: number = 10;
#state()
_numPages = Math.ceil(this.total / this.pageSize);
render() {
const numPages = Math.ceil(this.total / this.pageSize);
console.log(numPages, this._numPages, this._selected);
return html`
<ul class="pagination">
<li>${msg("Previous", {id: 'pagination.previous', desc: 'Previous button in pagination'})}</li>
${Array.from(Array(_numPages).keys()).map((e, i) => html`<li class=${this._selected === i ? "active" : ""}>${i + 1}</li>`)}
<li>${msg("Next", {id: 'pagination.next', desc: 'Previous button in pagination'})}</li>
</ul>
`;
}
}
This fails in render when using this._numPages (throws RangeError for the map because it's NaN), but is fine with numPages. It seems like if the state is using public properties that have defaults, it works, but otherwise it fails. I think this has to do with the fact that when the element is created it doesn't have props yet, so the initial first render doesn't have a value. But what is the right pattern to achieve this then in that case? The documentation here says "In some cases, internal reactive state may be initialized from public properties—for example, if there is a expensive transformation between the user-visible property and the internal state." (https://lit.dev/docs/components/properties/#public-properties-and-internal-state).
For completeness, here's a snippet of output of tsc:
import { __decorate } from "tslib";
import { html } from 'lit';
import { localized, msg } from '#lit/localize';
import { property } from 'lit/decorators/property.js';
import { state } from 'lit/decorators/state.js';
import { BaseElement } from '../BaseElement.js';
let Pagination = class Pagination extends BaseElement {
constructor() {
super(...arguments);
this.selected = 0;
this._selected = this.selected;
this.pageSize = 10;
this._numPages = Math.ceil(this.total / this.pageSize);
}
render() {
const numPages = Math.ceil(this.total / this.pageSize);
console.log(this._selected, this._numPages);
return html `
<ul class="pagination">
<li>${msg("Previous", { id: 'pagination.previous', desc: 'Previous button in pagination' })}</li>
${Array.from(Array(numPages).keys()).map((e, i) => html `<li class=${this.selected === i ? "active" : ""}>${i + 1}</li>`)}
<li>${msg("Next", { id: 'pagination.next', desc: 'Previous button in pagination' })}</li>
</ul>
`;
}
};
Pagination.styles = [BaseElement.styles];
__decorate([
property({ type: Number })
], Pagination.prototype, "selected", void 0);
__decorate([
state()
], Pagination.prototype, "_selected", void 0);
__decorate([
property({ type: Number })
], Pagination.prototype, "total", void 0);
__decorate([
property({ type: Number })
], Pagination.prototype, "pageSize", void 0);
__decorate([
state()
], Pagination.prototype, "_numPages", void 0);
Pagination = __decorate([
localized()
], Pagination);
export { Pagination };
The #property decorator only declares the property for any lit-element component. To initialize the property with an default value, you need to declare it in Constructor.
I am not clear on your issue, what i understand that _numPages is giving error. So you can declare and initialize it as follow.
Below is your modified code.
export class Pagination extends BaseElement {
static styles = [BaseElement.styles];
#property({type: Number})
selected = 0;
#state()
_selected = this.selected;
#property({type: Number})
total!: number;
#property({type: Number})
pageSize: number = 10;
#property({type: Number})
_numPages: number;
constructor() {
super();
this._numPages = 10
}
#state()
_numPages = Math.ceil(this.total / this.pageSize);
render() {
const numPages = Math.ceil(this.total / this.pageSize);
console.log(numPages, this._numPages, this._selected);
return html`
<ul class="pagination">
<li>${msg("Previous", {id: 'pagination.previous', desc: 'Previous button in pagination'})}</li>
${Array.from(Array(_numPages).keys()).map((e, i) => html`<li class=${this._selected === i ? "active" : ""}>${i + 1}</li>`)}
<li>${msg("Next", {id: 'pagination.next', desc: 'Previous button in pagination'})}</li>
</ul>
`;
}
}
Assigning the values in the class fields feels risky. I would need to review this, but I think this will not use the instance values so it might have unintended side-effects. I would wait until the instance is available (like the constructor), but, setting it in the constructor is also too soon as the property does not yet have the value from the component, rather it has the initial value from the class-field.
So we need to delay this even further.
I've solved this by using the firstUpdated lifecycle hook:
#customElement("my-component")
export class MyComponent extends LitElement {
#property()
myProperty: string = "";
#state()
_internalState: string = "";
override async firstUpdated() {
// avoid unnecessary calls by waiting for any pending updated to complete.
await this.updateComplete;
this._internalState = this.myProperty;
}
...
}

Populating nativescript radList view with Data from Webservice

I have a RadList view which am trying to populate with data from a web service. The data returned from the service is in JSON format. So am trying to create a service which i will use to get the information by passing the respective URL. Below are the codes:-
interface class (cps-admin.interface.ts)
export class AddTenantInterface {
tenant_id: string;
tenant_names: string;
tenant_contact: string;
tenant_email: string;
starting_date: string;
rent_amount: string;
initial_water_reading: string;
water_unit_cost: string;
}
the service (CPSAdminService.ts)
import { Injectable } from "#angular/core";
import { AddTenantInterface } from "../interfaces/cps-admin.interface";
import { Observable } from "rxjs";
import { HttpClient, HttpHeaders, HttpResponse } from "#angular/common/http";
#Injectable()
export class CPSAdminService {
public _fetchTenantListUrl: string = "http://192.168.137.1/cps/fetchTenantsObj.php"; // fetch tenants list api url
constructor(private _http: HttpClient) {}
fetchTenantsList() {
let headers = this.createRequestHeader();
return this._http.get(this._fetchTenantListUrl, { headers: headers });
}
private createRequestHeader() {
// set headers here e.g.
let headers = new HttpHeaders({
"AuthKey": "my-key",
"AuthToken": "my-token",
"Content-Type": "application/json",
});
return headers;
}
}
Here is the tenants-component.ts
import { Component, OnInit, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy} from "#angular/core";
import { AddTenantInterface } from "./../../interfaces/cps-admin.interface";
import { CPSAdminService } from "./../../services/cps-admin.service";
import * as ApplicationSettings from "application-settings";
import { ObservableArray } from "tns-core-modules/data/observable-array";
import { RadListViewComponent } from "nativescript-ui-listview/angular";
import { ListViewLinearLayout, ListViewEventData, RadListView, LoadOnDemandListViewEventData } from "nativescript-ui-listview";
import { setTimeout } from "tns-core-modules/timer";
import { TextField } from "ui/text-field";
#Component({
selector: "tenants-list",
moduleId: module.id,
templateUrl: "./tenants-list.component.html",
styleUrls: ["./tenants-list.css"]
})
export class TenantListComponent implements OnInit {
public rentItems: ObservableArray<AddTenantInterface>;
private _sourceDataItems: ObservableArray<AddTenantInterface>;
private layout: ListViewLinearLayout;
public searchPaymentsList: string;
private _myFilteringFunc: (item: any) => any;
#ViewChild("myListView") myListViewComponent: RadListViewComponent;
constructor(private _cPSAdminService: CPSAdminService, private _changeDetectionRef: ChangeDetectorRef) {}
ngOnInit(): void {
this.layout = new ListViewLinearLayout();
this.layout.scrollDirection = "Vertical";
this.initDataItems();
this._changeDetectionRef.detectChanges();
this.rentItems = new ObservableArray<AddTenantInterface>();
this.addMoreItemsFromSource(6);
}
get myFilteringFunc(): (item: any) => any {
return this._myFilteringFunc;
}
set myFilteringFunc(value: (item: any) => any) {
this._myFilteringFunc = value;
}
public onTextChanged(args) {
let searchBar = <TextField>args.object;
let listView = this.myListViewComponent.listView;
this.myFilteringFunc = (item: AddTenantInterface) => {
return item.tenant_names.includes(searchBar.text) || item.starting_date.includes(searchBar.text);
};
if (!listView.filteringFunction) {
listView.filteringFunction = this.myFilteringFunc;
} else {
listView.filteringFunction = undefined;
}
}
get rentDataItems(): ObservableArray<AddTenantInterface> {
return this.rentItems;
}
public addMoreItemsFromSource(chunkSize: number) {
let newItems = this._sourceDataItems.splice(0, chunkSize);
this.rentDataItems.push(newItems);
}
public onLoadMoreItemsRequested(args: LoadOnDemandListViewEventData) {
const that = new WeakRef(this);
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
const listView: RadListView = args.object;
that.get().addMoreItemsFromSource(2);
listView.notifyLoadOnDemandFinished();
}, 1000);
} else {
args.returnValue = false;
}
}
// ===== **PROBLEM IS HERE**
private initDataItems() {
this._sourceDataItems = new ObservableArray(this._cPSAdminService.fetchTenantsList());
}
}
Note where i have marked "PROBLEM IS HERE", Here is the error image clip
I just don't what the problem. When i place "any" as the method return in fetchTenantsList() like so fetchTenantsList(): any , the error disappears but nothing is displayed in list.
And when i hard code the data there like below, it works;
tenantData: AddTenantInterface[] = [
{
"tenant_id":"542948",
"tenant_names":"Jane Doe",
"tenant_contact":"0787916686",
"tenant_email":"jane.doe#ymail.com",
"starting_date":"2004-08-09",
"rent_amount":"850000",
"initial_water_reading":"100",
"water_unit_cost":"250"
},
{
"tenant_id":"575550",
"tenant_names":"Emily Clarke",
"tenant_contact":"07752654868",
"tenant_email":"emily.clarke#gmail.com",
"starting_date":"2007-07-04",
"rent_amount":"700000",
"initial_water_reading":"400",
"water_unit_cost":"250"
},
{
"tenant_id":"422031",
"tenant_names":"John Doe",
"tenant_contact":"0772485364",
"tenant_email":"john.doe#ymail.com",
"starting_date":"2008-12-14",
"rent_amount":"700000",
"initial_water_reading":"120",
"water_unit_cost":"250"
}
];
fetchTenantsList(): AddTenantInterface[] {
return this.tenantData;
}
Here is my component html:
<GridLayout class="page page-content custom_font_family m-5" rows="50, *">
<StackLayout class="input-field" row="0">
<TextField
hint="search..."
[(ngModel)]='searchPaymentsList'
secure="false"
returnKeyType="done"
(textChange)="onTextChanged($event)"
autocorrect="false"
autocapitalizationType="allCharacters"
focus="onFocus"
blur="onBlur"
class="input input-border"
color="navy"
textFieldHintColor="#bfbfbf"></TextField>
</StackLayout>
<GridLayout tkExampleTitle tkToggleNavButton row="1" rows="*">
<RadListView [items]="rentDataItems" loadOnDemandMode="Manual" (loadMoreDataRequested)="onLoadMoreItemsRequested($event)" [filteringFunction]="myFilteringFunc" #myListView row="0">
<ng-template tkListItemTemplate let-item="item" let-i="index" let-odd="odd" let-even="even">
<StackLayout [class.odd]="odd" [class.even]="even" class="list-group-item p-y-10 m-y-2 t-17 p-x-5">
<Label [text]='item.starting_date | date: "d-M-y"'></Label>
<Label [text]='item.tenant_id + ". "'></Label>
<Label [text]='item.tenant_names'></Label>
<Label [text]='item.tenant_contact'></Label>
<Label [text]='item.tenant_email'></Label>
<Label [text]='item.rent_amount | currency:"UG ":"Ug. ": "3.1-1"'></Label>
</StackLayout>
<!--</GridLayout>-->
</ng-template>
<ng-template tkListViewHeader>
<GridLayout class="header" rows="*" columns="30, auto, auto, auto, auto, auto">
<Label row="0" col="0" text='Date'></Label>
<Label row="0" col="1" text='No '></Label>
<Label row="0" col="2" text='Names'></Label>
<Label row="0" col="3" text='Contact'></Label>
<Label row="0" col="4" text='Email'></Label>
<Label row="0" col="5" text='Rent'></Label>
</GridLayout>
</ng-template>
<ListViewLinearLayout *tkIfIOS tkListViewLayout itemHeight="120"></ListViewLinearLayout>
<div *tkIfIOS>
<GridLayout *tkListLoadOnDemandTemplate class="loadOnDemandItemGridLayout">
<Label text="Load More" horizontalAlignment="center" verticalAlignment="center"></Label>
</GridLayout>
</div>
</RadListView>
</GridLayout>
</GridLayout>
Any help is appreciated.
this._cPSAdminService.fetchTenantsList()
This will return an Observable that emits the result from server when the request is completed. You can't simply pass it to Observable Array.
It must be something like this,
this._cPSAdminService.fetchTenantsList()
.subscribe((result) => {
this._sourceDataItems = new ObservableArray(result);
});
Alright my bad. I was supposed to remove
this.addMoreItemsFromSource(6) from ngOnInit() and put it inside the subscribe.
Here is the solution.
import { Component, OnInit, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy} from "#angular/core";
import { AddTenantInterface } from "./../../interfaces/cps-admin.interface";
import { CPSAdminService } from "./../../services/cps-admin.service";
import * as ApplicationSettings from "application-settings";
import { ObservableArray } from "tns-core-modules/data/observable-array";
import { RadListViewComponent } from "nativescript-ui-listview/angular";
import { ListViewLinearLayout, ListViewEventData, RadListView, LoadOnDemandListViewEventData } from "nativescript-ui-listview";
import { setTimeout } from "tns-core-modules/timer";
import { TextField } from "ui/text-field";
#Component({
selector: "tenants-list",
moduleId: module.id,
templateUrl: "./tenants-list.component.html",
styleUrls: ["./tenants-list.css"]
})
export class TenantListComponent implements OnInit {
public rentItems: ObservableArray<AddTenantInterface>;
private _sourceDataItems: ObservableArray<AddTenantInterface>;
private layout: ListViewLinearLayout;
public searchPaymentsList: string;
private _myFilteringFunc: (item: any) => any;
#ViewChild("myListView") myListViewComponent: RadListViewComponent;
constructor(private _cPSAdminService: CPSAdminService, private _changeDetectionRef: ChangeDetectorRef) {}
ngOnInit(): void {
this.layout = new ListViewLinearLayout();
this.layout.scrollDirection = "Vertical";
this.initDataItems();
this._changeDetectionRef.detectChanges();
this.rentItems = new ObservableArray<AddTenantInterface>();
//this.addMoreItemsFromSource(6); // remove this and put it inthe initDataItems method
}
get myFilteringFunc(): (item: any) => any {
return this._myFilteringFunc;
}
set myFilteringFunc(value: (item: any) => any) {
this._myFilteringFunc = value;
}
public onTextChanged(args) {
let searchBar = <TextField>args.object;
let listView = this.myListViewComponent.listView;
this.myFilteringFunc = (item: AddTenantInterface) => {
return item.tenant_names.includes(searchBar.text) || item.starting_date.includes(searchBar.text);
};
if (!listView.filteringFunction) {
listView.filteringFunction = this.myFilteringFunc;
} else {
listView.filteringFunction = undefined;
}
}
get rentDataItems(): ObservableArray<AddTenantInterface> {
return this.rentItems;
}
public addMoreItemsFromSource(chunkSize: number) {
let newItems = this._sourceDataItems.splice(0, chunkSize);
this.rentDataItems.push(newItems);
}
public onLoadMoreItemsRequested(args: LoadOnDemandListViewEventData) {
const that = new WeakRef(this);
if (this._sourceDataItems.length > 0) {
setTimeout(function () {
const listView: RadListView = args.object;
that.get().addMoreItemsFromSource(2);
listView.notifyLoadOnDemandFinished();
}, 1000);
} else {
args.returnValue = false;
}
}
public initDataItems() {
//this._sourceDataItems = new ObservableArray(this._cPSAdminService.fetchTenantsList());
this._cPSAdminService.fetchTenantsList()
.subscribe( (result) => {
this._sourceDataItems = new ObservableArray(result);
this.addMoreItemsFromSource(6); // put it here
},
error => {
console.log("Error: ", error);
});
}
}
Thanks #Manoj

React Virtualized - Table Example - How do I make it work?

I've been struggling to make this react virtualized table example work & seriously starting to doubt my sanity. I've created a react app and I'm just trying to render the example table inside App.js with this:
class App extends Component {
render() {
var data = [1,2,3,4,5,6,7,8,9,10];
return (
<TableExample
list={data}
/>
);
}
}
React keeps saying list isn't defined - it seems obvious I'm not getting the data into the component the right way. I haven't been able to understand the example code, what props need to be passed in and what they should be named. Sorry for the stupid question but I've been stuck forever not knowing where else to find an answer. The table example code is below:
/** #flow */
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import * as React from 'react';
import {
ContentBox,
ContentBoxHeader,
ContentBoxParagraph,
} from '../demo/ContentBox';
import {LabeledInput, InputRow} from '../demo/LabeledInput';
import AutoSizer from '../AutoSizer';
import Column from './Column';
import Table from './Table';
import SortDirection from './SortDirection';
import SortIndicator from './SortIndicator';
import styles from './Table.example.css';
export default class TableExample extends React.PureComponent {
static contextTypes = {
list: PropTypes.instanceOf(Immutable.List).isRequired,
};
constructor(props, context) {
super(props, context);
const sortBy = 'index';
const sortDirection = SortDirection.ASC;
const sortedList = this._sortList({sortBy, sortDirection});
this.state = {
disableHeader: false,
headerHeight: 30,
height: 270,
hideIndexRow: false,
overscanRowCount: 10,
rowHeight: 40,
rowCount: 1000,
scrollToIndex: undefined,
sortBy,
sortDirection,
sortedList,
useDynamicRowHeight: false,
};
this._getRowHeight = this._getRowHeight.bind(this);
this._headerRenderer = this._headerRenderer.bind(this);
this._noRowsRenderer = this._noRowsRenderer.bind(this);
this._onRowCountChange = this._onRowCountChange.bind(this);
this._onScrollToRowChange = this._onScrollToRowChange.bind(this);
this._rowClassName = this._rowClassName.bind(this);
this._sort = this._sort.bind(this);
}
render() {
const {
disableHeader,
headerHeight,
height,
hideIndexRow,
overscanRowCount,
rowHeight,
rowCount,
scrollToIndex,
sortBy,
sortDirection,
sortedList,
useDynamicRowHeight,
} = this.state;
const rowGetter = ({index}) => this._getDatum(sortedList, index);
return (
<ContentBox>
<ContentBoxHeader
text="Table"
sourceLink="https://github.com/bvaughn/react-virtualized/blob/master/source/Table/Table.example.js"
docsLink="https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md"
/>
<ContentBoxParagraph>
The table layout below is created with flexboxes. This allows it to
have a fixed header and scrollable body content. It also makes use of{' '}
<code>Grid</code> for windowing table content so that large lists are
rendered efficiently. Adjust its configurable properties below to see
how it reacts.
</ContentBoxParagraph>
<ContentBoxParagraph>
<label className={styles.checkboxLabel}>
<input
aria-label="Use dynamic row heights?"
checked={useDynamicRowHeight}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this._updateUseDynamicRowHeight(event.target.checked)
}
/>
Use dynamic row heights?
</label>
<label className={styles.checkboxLabel}>
<input
aria-label="Hide index?"
checked={hideIndexRow}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this.setState({hideIndexRow: event.target.checked})
}
/>
Hide index?
</label>
<label className={styles.checkboxLabel}>
<input
aria-label="Hide header?"
checked={disableHeader}
className={styles.checkbox}
type="checkbox"
onChange={event =>
this.setState({disableHeader: event.target.checked})
}
/>
Hide header?
</label>
</ContentBoxParagraph>
<InputRow>
<LabeledInput
label="Num rows"
name="rowCount"
onChange={this._onRowCountChange}
value={rowCount}
/>
<LabeledInput
label="Scroll to"
name="onScrollToRow"
placeholder="Index..."
onChange={this._onScrollToRowChange}
value={scrollToIndex || ''}
/>
<LabeledInput
label="List height"
name="height"
onChange={event =>
this.setState({height: parseInt(event.target.value, 10) || 1})
}
value={height}
/>
<LabeledInput
disabled={useDynamicRowHeight}
label="Row height"
name="rowHeight"
onChange={event =>
this.setState({
rowHeight: parseInt(event.target.value, 10) || 1,
})
}
value={rowHeight}
/>
<LabeledInput
label="Header height"
name="headerHeight"
onChange={event =>
this.setState({
headerHeight: parseInt(event.target.value, 10) || 1,
})
}
value={headerHeight}
/>
<LabeledInput
label="Overscan"
name="overscanRowCount"
onChange={event =>
this.setState({
overscanRowCount: parseInt(event.target.value, 10) || 0,
})
}
value={overscanRowCount}
/>
</InputRow>
<div>
<AutoSizer disableHeight>
{({width}) => (
<Table
ref="Table"
disableHeader={disableHeader}
headerClassName={styles.headerColumn}
headerHeight={headerHeight}
height={height}
noRowsRenderer={this._noRowsRenderer}
overscanRowCount={overscanRowCount}
rowClassName={this._rowClassName}
rowHeight={useDynamicRowHeight ? this._getRowHeight : rowHeight}
rowGetter={rowGetter}
rowCount={rowCount}
scrollToIndex={scrollToIndex}
sort={this._sort}
sortBy={sortBy}
sortDirection={sortDirection}
width={width}>
{!hideIndexRow && (
<Column
label="Index"
cellDataGetter={({rowData}) => rowData.index}
dataKey="index"
disableSort={!this._isSortEnabled()}
width={60}
/>
)}
<Column
dataKey="name"
disableSort={!this._isSortEnabled()}
headerRenderer={this._headerRenderer}
width={90}
/>
<Column
width={210}
disableSort
label="The description label is really long so that it will be truncated"
dataKey="random"
className={styles.exampleColumn}
cellRenderer={({cellData}) => cellData}
flexGrow={1}
/>
</Table>
)}
</AutoSizer>
</div>
</ContentBox>
);
}
_getDatum(list, index) {
return list.get(index % list.size);
}
_getRowHeight({index}) {
const {list} = this.context;
return this._getDatum(list, index).size;
}
_headerRenderer({dataKey, sortBy, sortDirection}) {
return (
<div>
Full Name
{sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />}
</div>
);
}
_isSortEnabled() {
const {list} = this.context;
const {rowCount} = this.state;
return rowCount <= list.size;
}
_noRowsRenderer() {
return <div className={styles.noRows}>No rows</div>;
}
_onRowCountChange(event) {
const rowCount = parseInt(event.target.value, 10) || 0;
this.setState({rowCount});
}
_onScrollToRowChange(event) {
const {rowCount} = this.state;
let scrollToIndex = Math.min(
rowCount - 1,
parseInt(event.target.value, 10),
);
if (isNaN(scrollToIndex)) {
scrollToIndex = undefined;
}
this.setState({scrollToIndex});
}
_rowClassName({index}) {
if (index < 0) {
return styles.headerRow;
} else {
return index % 2 === 0 ? styles.evenRow : styles.oddRow;
}
}
_sort({sortBy, sortDirection}) {
const sortedList = this._sortList({sortBy, sortDirection});
this.setState({sortBy, sortDirection, sortedList});
}
_sortList({sortBy, sortDirection}) {
const {list} = this.context;
return list
.sortBy(item => item[sortBy])
.update(
list => (sortDirection === SortDirection.DESC ? list.reverse() : list),
);
}
_updateUseDynamicRowHeight(value) {
this.setState({
useDynamicRowHeight: value,
});
}
}
Looking at some previous questions, it seems that the example is using some components that are not included inside dist package. Thats probably why you are getting undefined error.
Here is the most basic example of Tables in react virtulized:
import React from 'react';
import ReactDOM from 'react-dom';
import { Column, Table } from 'react-virtualized';
import 'react-virtualized/styles.css'; // only needs to be imported once
// Table data as an array of objects
const list = [
{ name: 'Brian Vaughn', description: 'Software engineer' }
// And so on...
];
// Render your table
ReactDOM.render(
<Table
width={300}
height={300}
headerHeight={20}
rowHeight={30}
rowCount={list.length}
rowGetter={({ index }) => list[index]}
>
<Column
label='Name'
dataKey='name'
width={100}
/>
<Column
width={200}
label='Description'
dataKey='description'
/>
</Table>,
document.getElementById('example')
);
When learning new library, its always best to start with the most simple example then expand on it. Here is the link to the full table docs.
The issue with the example is that in order for contextTypes to work, the parent component needs to define corresponding contextTypes and a getChildContext function.
In the parent component:
class App extends React.Component {
static childContextTypes = {
list: PropTypes.instanceOf(Immutable.List).isRequired
};
getChildContext() {
return {
list
};
}
render() {
return <TableExample />;
}
}
Mentioned in this issue; see lines 48-53 and 68-76 of the react-virtualized demo Application.js.

react bootstrap grouplist renders badly on mobile chrome

i have a React App which worked fine, both on mobile and pc.
i am using 'react-bootstrap' listgroup and modal inside a material-ui tabs component.
it all worked fine until a few days ago on mobile the modal stopped rendering and the list items started to wrap instead of filling the entire witdh of the screen.
on pc chrome works fine, if i activate in VS Code chrome debugger the issue appears.
my code added below:
CatalogListItem.js
import React,{ Component } from 'react';
import {ListGroupItem} from 'react-bootstrap';
class CatalogListItem extends Component {
render() {
return (
<ListGroupItem bsStyle={(this.props.catalogItem.isCount)? null :"danger"}
align="right"
header={this.props.catalogItem.itemName + " - " + this.props.catalogItem.measureId.measureName}
onClick={() => {this.props.onClickHandler(this.props.catalogItem)}}
>
<img src={this.props.catalogItem.itemPicPath}
height="70" width="70" alt="item" align="left" />
<br/>
<span align="right">
מלאי קבוע: {this.props.catalogItem.itemStock} ,
מחיר: {this.props.catalogItem.itemPrice[0].price} ש"ח
פיקדון: {this.props.catalogItem.itemDeposit} ש"ח
<br/>
הנחה: {this.props.catalogItem.itemDiscount}%,
הנחה צוברת: {this.props.catalogItem.additionalDiscount}%
</span>
</ListGroupItem>
);
}
}
export default CatalogListItem;
CatalogTabs.js
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import React,{Component} from 'react';
import {Tabs,Tab} from 'material-ui/Tabs';
import {List} from 'material-ui/List';
import PropTypes from 'prop-types';
import FloatingActionButton from 'material-ui/FloatingActionButton';
import ContentAdd from 'material-ui/svg-icons/content/add';
import CatalogListItem from '../catalogPage/CatalogListItem';
import CatalogItemFormModal from './CatalogItemFormModal';
// eslint-disable-next-line
const propTypes = {
//tabsTitles: PropTypes.object.isRequired,
pathName: PropTypes.string.isRequired
};
const fabStyle = {
bottom: 20,
left: 10,
position: 'fixed'
};
const emptyCatalogItem = {
"itemId": "",
"itemName": "",
"supplierId": {},
"measureId": {},
"itemStock": 0,
"itemPrice": [{price:0}],
"itemDeposit": 0,
"itemDiscount": 0,
"additionalDiscount": 0,
"itemPicPath": "http://freevector.co/wp-content/uploads/2012/04/70626-label-
beer-bottle.png",
"isCount": true
}
class CatalogTabs extends Component {
constructor(props) {
super(props);
this.state = {focusedCatalogItem:emptyCatalogItem,
showModal:false};
}
showModal(catalogItem){
this.setState({focusedCatalogItem:JSON.parse(JSON.stringify(catalogItem))});
this.setState({showModal: true});
}
hideModal(){
this.setState({showModal: false});
}
Listitemsfactory(supplier){
return(
<div>
{ this.props.catalogData.catalog
.sort((a, b) => a.itemName.localeCompare(b.itemName))
.map((catalogItem)=> {
if (supplier._id === catalogItem.supplierId._id)
return <CatalogListItem key={catalogItem._id}
catalogItem={catalogItem}
onClickHandler=
{this.showModal.bind(this)} />
return false;
}
)}
</div>);
}
render(){
return(
<div>
<MuiThemeProvider>
<Tabs>
{this.props.suppliersData.suppliers.map((supplier)=>
<Tab label={(supplier.supplierName.length > 6)?
supplier.supplierName.substring(0,6)+"..."
:
supplier.supplierName}
key={supplier._id}>
<List>
{this.Listitemsfactory(supplier)}
</List>
</Tab>
)}
</Tabs>
<FloatingActionButton onClick={()=>this.showModal(emptyCatalogItem)}
style={fabStyle}>
<ContentAdd/>
</FloatingActionButton>
</MuiThemeProvider>
<CatalogItemFormModal
suppliers={this.props.suppliersData.suppliers}
measures={this.props.measuresData}
heading={"פרטי פריט"}
onItemNameChange={this.onItemNameChange.bind(this)}
onItemSupplierChange={this.onItemSupplierChange.bind(this)}
onItemMeasureChange={this.onItemMeasureChange.bind(this)}
onItemPriceChange={this.onItemPriceChange.bind(this)}
onItemDepositChange={this.onItemDepositChange.bind(this)}
onItemDiscountChange={this.onItemDiscountChange.bind(this)}
onAdditionalDiscountChange={this.onAdditionalDiscountChange.bind(this)}
onItemStockChange={this.onItemStockChange.bind(this)}
onIsCountChange={this.onIsCountChange.bind(this)}
onItemDelete={this.startDeleteItem.bind(this)}
itemData = {this.state.focusedCatalogItem}
showModal={this.state.showModal}
onHide={this.hideModal.bind(this)}
onOk={this.startUpdateItem.bind(this)}/>
</div>
);}
onItemNameChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.itemName = newValue;
this.setState({focusedCatalogItem:item});
}
onItemSupplierChange(event, key, payload){
let item = this.state.focusedCatalogItem;
item.supplierId._id = payload;
this.setState({focusedCatalogItem:item});
}
onItemMeasureChange(event,key,payload){
let item = this.state.focusedCatalogItem;
item.measureId._id = payload;
this.setState({focusedCatalogItem:item});
}
onItemPriceChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.itemPrice = newValue;
this.setState({focusedCatalogItem:item});
}
onItemDepositChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.itemDeposit = newValue;
this.setState({focusedCatalogItem:item});
}
onItemDiscountChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.itemDiscount = newValue;
this.setState({focusedCatalogItem:item});
}
onAdditionalDiscountChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.additionalDiscount = newValue;
this.setState({focusedCatalogItem:item});
}
onItemStockChange(event,newValue){
let item = this.state.focusedCatalogItem;
item.itemStock = newValue;
this.setState({focusedCatalogItem:item});
}
onIsCountChange(isInputChecked){
let item = this.state.focusedCatalogItem;
item.isCount = isInputChecked;
this.setState({focusedCatalogItem:item});
}
startUpdateItem(catalogItem){
console.log("item that is sent to server:" +
JSON.stringify(catalogItem));
let catalogForSend = JSON.parse(JSON.stringify(catalogItem));
catalogForSend.supplierId = catalogItem.supplierId._id;
catalogForSend.measureId = catalogItem.measureId._id;
console.log("item that is sent to server:" +
JSON.stringify(catalogForSend));
this.props.updateCatalogItem(catalogForSend);
}
startDeleteItem(catalogItem){
this.props.deleteCatalogItem(catalogItem);
}
}
export default CatalogTabs;
CatalogPage.js
import React, { Component } from 'react';
import {bindActionCreators} from 'redux';
import {connect,} from 'react-redux';
import { ToastContainer } from 'react-toastify';
import Header from '../common/Header';
import CatalogTabs from './CatalogTabs';
import LoadingIndicator from '../common/Loading';
import {setFocusedCatalogItem,updateCatalogItem,deleteCatalogItem} from
'../../store/actions/catalogActions'
// this class detirme what page to display - cataloglist or an item form
// this is decided by the url - if provided an item id or not
class CatalogPage extends Component {
render() {
return (
<div>
<Header pageTitle="קטלוג"
isUserLoggedIn = {this.props.userData.isUserLoggedIn}/>
<CatalogTabs
pathName={this.props.location.pathname}
catalogData = {this.props.catalogData}
suppliersData = {this.props.suppliersData}
measuresData = {this.props.measuresData.measures}
userData = {this.props.userData}
setFocusedCatalogItem={this.props.setFocusedCatalogItem}
updateCatalogItem={this.props.updateCatalogItem}
deleteCatalogItem={this.props.deleteCatalogItem}/>
<LoadingIndicator isLoading =
{this.props.catalogData.fetchingCatalogData}/>
<ToastContainer/>
</div>
);
}
}
function mapStateToProps(state){
return{
userData: state.userData,
catalogData: state.catalogData,
suppliersData: state.suppliersData,
measuresData: state.measuresData
}
}
function matchDispachToProps(dispatch){
return bindActionCreators({setFocusedCatalogItem:setFocusedCatalogItem,
updateCatalogItem:updateCatalogItem,
deleteCatalogItem:deleteCatalogItem},dispatch)
}
export default connect(mapStateToProps,matchDispachToProps)(CatalogPage);

Implementing Material Table with api call Angular

I have been developing an application with Angular 2 and it has admittedly been a struggle doing so as a new developer. I've managed quite a bit so far but I do need some assistance. I'm including a plunkr that I'm using for reference to get a Material Table with pagination, filter and sort, but this example, as well as all of the other examples on material.angular.io show an example with a database that's essentially hardcoded/generated in the component class. I have a service that calls the api for an SQL query and I'd like to populate the table in the example with that, however my attempts so far have been miserable failures and I think I've overwhelmed myself in the process.
By request I can post my component code, but I fear that I've gutted/revised it beyond the point of any use. But until then below is the plunkr with what I want to implement, and the service class I'd like to use to populate the data table in place of the plunkr's database and data source.
Please let me know if you can help, you'd be saving me a huge headache.
https://plnkr.co/edit/EU3BBlViWpPf2NJW4PXx?p=preview
My service
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class RcgqueueService {
constructor(private http: Http) { }
populateRCGQueue() {
return this.http.get('/api/rcgqueue').map(res => res.json());
}
}
And my current pathetic attempt at the component code
import { Component, ElementRef, ViewChild, OnInit, forwardRef } from '#angular/core';
import { DataSource, SelectionModel } from '#angular/cdk/collections';
import { MatPaginator, MatSort, MatTable } from '#angular/material';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/debounceTime';
import { RcgqueueService } from './rcgqueue.service';
#Component({
selector: 'app-rcgqueue',
templateUrl: './rcgqueue.component.html',
styleUrls: ['./rcgqueue.component.css']
})
export class RcgqueueComponent implements OnInit {
isDataAvailable = false;
displayedColumns = ['changeId', 'changeTitle', 'dateSubmitted', 'changeSponsor', 'changeDescription'];
dataChange: BehaviorSubject<ChangeData[]> = new BehaviorSubject<ChangeData[]>([]);
get data(): ChangeData[] { return this.dataChange.value; }
dataSource: ExampleDataSource | null;
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(forwardRef(() => MatSort)) sort: MatSort;
#ViewChild('filter') filter: ElementRef;
constructor(private rcgservice: RcgqueueService) {
}
populateRCGQueue() {
this.rcgservice.populateRCGQueue().subscribe(rcgitems => {
this.dataChange = rcgitems;
this.isDataAvailable = true;
})
}
ngOnInit() {
this.populateRCGQueue();
this.dataSource = new ExampleDataSource(this, this.paginator, this.sort);
Observable.fromEvent(this.filter.nativeElement, 'keyup')
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => {
if (!this.dataSource) { return; }
this.dataSource.filter = this.filter.nativeElement.value;
});
}
}
export interface ChangeData {
ChangeId: string;
ChangeTitle: string;
DateSubmitted: string;
ChangeSponsor: string;
ChangeDescription: string;
}
/**
* Data source to provide what data should be rendered in the table. Note that the data source
* can retrieve its data in any way. In this case, the data source is provided a reference
* to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
* the underlying data. Instead, it only needs to take the data and send the table exactly what
* should be rendered.
*/
export class ExampleDataSource extends DataSource<any> {
_filterChange = new BehaviorSubject('');
get filter(): string { return this._filterChange.value; }
set filter(filter: string) { this._filterChange.next(filter); }
dataChange: BehaviorSubject<ChangeData[]> = new BehaviorSubject<ChangeData[]>([]);
get data(): ChangeData[] { return this.dataChange.value; }
filteredData: ChangeData[] = [];
renderedData: ChangeData[] = [];
constructor(private rcgcomponent: RcgqueueComponent,
private _paginator: MatPaginator,
private _sort: MatSort) {
super();
this._filterChange.subscribe(() => this._paginator.pageIndex = 0);
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<ChangeData[]> {
// Listen for any changes in the base data, sorting, filtering, or pagination
const displayDataChanges = [
this.rcgcomponent.dataChange,
this._sort.sortChange,
this._filterChange,
this._paginator.page,
];
return Observable.merge(...displayDataChanges).map(() => {
// Filter data
this.filteredData = this.rcgcomponent.data.slice().filter((item: ChangeData) => {
const searchStr = (item.ChangeDescription + item.ChangeSponsor).toLowerCase();
return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
});
// Sort filtered data
const sortedData = this.sortData(this.filteredData.slice());
// Grab the page's slice of the filtered sorted data.
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
this.renderedData = sortedData.splice(startIndex, this._paginator.pageSize);
return this.renderedData;
});
}
disconnect() { }
/** Returns a sorted copy of the database data. */
sortData(data: ChangeData[]): ChangeData[] {
if (!this._sort.active || this._sort.direction === '') { return data; }
return data.sort((a, b) => {
let propertyA: number | string = '';
let propertyB: number | string = '';
switch (this._sort.active) {
case 'changeId': [propertyA, propertyB] = [a.ChangeId, b.ChangeId]; break;
case 'changeTitle': [propertyA, propertyB] = [a.ChangeTitle, b.ChangeTitle]; break;
case 'dateSubmitted': [propertyA, propertyB] = [a.DateSubmitted, b.DateSubmitted]; break;
case 'changeSponsor': [propertyA, propertyB] = [a.ChangeSponsor, b.ChangeSponsor]; break;
case 'changeDescription': [propertyA, propertyB] = [a.ChangeDescription, b.ChangeDescription]; break;
}
const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
const valueB = isNaN(+propertyB) ? propertyB : +propertyB;
return (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1);
});
}
}
I've removed anything having to do with selection from the table from the plunkr code, what is left here is where I am currently. I'm very sorry if this ends up more hindering than helpful.
Oh and this may be helpful, my api.js on the server side with the query I'm using. (The only one so far)
const express = require('express');
const async = require('async');
const jwt = require('jsonwebtoken');
const shape = require('shape-json');
const router = express.Router();
var sql = require('mssql/msnodesqlv8');
var config = {
driver: 'msnodesqlv8',
connectionString: 'Driver=SQL Server Native Client 11.0;Server=localhost;Database=Change;Trusted_Connection=yes;',
}
var conn = new sql.ConnectionPool(config);
conn.connect().then(function() {
log("Change Governance Database Connection opened");
}).catch(function (err) {
console.error(new Date() + " - Issue connecting to the MS SQL database.", err);
});
router.get('/', (req, res) => {
res.send('api works');
});
router.get('/rcgqueue', (req, res) => {
new sql.Request(conn)
.query('SELECT ChangeId, ChangeTitle, DateSubmitted, ChangeSponsor, ChangeDescription FROM dbo.ChangeEvaluationForm;')
.then(function(recordset) {
log("Successful query request for RCG Records.");
res.send(recordset.recordset);
}).catch(function(err) {
log(err);
res.send("Issue querying database!");
});
});
/********************************/
/* Functions */
/********************************/
// Log lines with date/time for server
function log(msg) {
console.log(new Date() + " - " + msg);
};
module.exports = router;
EDIT: Adding template and errors.
Template
<!-- Issues
https://github.com/angular/angular/issues/16614
https://github.com/angular/angular/issues/17572 -->
<div *ngIf="isDataAvailable">
<div class="example-container mat-elevation-z8">
<div class="example-header">
<md-input-container floatPlaceholder="never">
<input mdInput #filter placeholder="Filter">
</md-input-container>
</div>
<md-table #table [dataSource]="dataSource" mdSort>
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- ChangeId Column -->
<ng-container cdkColumnDef="changeId">
<md-header-cell *cdkHeaderCellDef md-sort-header> ChangeId </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.ChangeId}} </md-cell>
</ng-container>
<!-- ChangeTitle Column -->
<ng-container cdkColumnDef="changeTitle">
<md-header-cell *cdkHeaderCellDef md-sort-header> Change Title </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.ChangeTitle}}% </md-cell>
</ng-container>
<!-- DateSubmitted -->
<ng-container cdkColumnDef="dateSubmitted">
<md-header-cell *cdkHeaderCellDef md-sort-header> Date Submitted </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.DateSubmitted}} </md-cell>
</ng-container>
<!-- ChangeSponsor -->
<ng-container cdkColumnDef="changeSponsor">
<md-header-cell *cdkHeaderCellDef md-sort-header> Change Sponsor </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.ChangeSponsor}} </md-cell>
</ng-container>
<!-- ChangeDescription -->
<ng-container cdkColumnDef="changeDescription">
<md-header-cell *cdkHeaderCellDef md-sort-header> Change Description </md-header-cell>
<md-cell *cdkCellDef="let row"> {{row.ChangeDescription}} </md-cell>
</ng-container>
<md-header-row *cdkHeaderRowDef="displayedColumns"></md-header-row>
<md-row *cdkRowDef="let row; columns: displayedColumns;">
</md-row>
</md-table>
<div class="example-no-results" [style.display]="dataSource.renderedData.length == 0 ? '' : 'none'">
No changes found matching filter.
</div>
<md-paginator #paginator [length]="dataSource.filteredData.length" [pageIndex]="0" [pageSize]="25" [pageSizeOptions]="[5, 10, 25, 100]">
</md-paginator>
</div>
</div>
And finally a screenshot of my errors
The reason why you are getting
Cannot set property pageIndex of undefined
is that you wrap table in *ngIf and by the time you pass #ViewChilds to DataSource class they are not initialized yet.
I solved it by calling initialization after getting data:
this.rcgservice.populateRCGQueue().subscribe(rcgitems => {
this.dataChange.next(rcgitems);
this.isDataAvailable = true;
this.cdRef.detectChanges(); // make sure that all ViewChilds were initialized
this.initSource();
Another mistake is that you assign data to BehaviourSubject
this.rcgservice.populateRCGQueue().subscribe(rcgitems => {
this.dataChange = rcgitems;
it should be:
this.dataChange.next(rcgitems);
I also added some safe navigation operators to your template:
[length]="dataSource?.filteredData.length"
and
[style.display]="dataSource?.renderedData.length == 0 ? '' : 'none'"
Plunker Example
If we don't use ngIf then we do not need ChangeDetectorRef anymore
Plunker Example

Resources