tns-template-master-detail-progress-ng
Version:
Master-detail interface to display tables from a Progress backend and inspect and edit selected records.
365 lines (323 loc) • 15.6 kB
text/typescript
import { ChangeDetectorRef, Component, OnInit, ViewChild } from "@angular/core";
import { ObservableArray } from "data/observable-array";
import { RouterExtensions } from "nativescript-angular/router";
import {
ListViewEventData, ListViewLinearLayout, ListViewLoadOnDemandMode,
RadListView
} from "nativescript-ui-listview";
import { DrawerTransitionBase, SlideInOnTopTransition } from "nativescript-ui-sidedrawer";
import { RadSideDrawerComponent } from "nativescript-ui-sidedrawer/angular";
import { isAndroid, isIOS } from "platform";
import { progress } from "@progress/jsdo-core";
import { EventData, View } from "tns-core-modules/ui/core/view/view";
import { alert, confirm } from "tns-core-modules/ui/dialogs";
import { JsdoSettings } from "../../shared/jsdo.settings";
import { ProgressService } from "../../shared/progress.service";
import { Customer } from "../shared/customer.model";
import { CustomerService } from "../shared/customer.service";
const SEARCH_DELAY = 300;
/* ***********************************************************
* This is the master list component in the master-detail structure.
* This component gets the data, passes it to the master view and displays it in a list.
* It also handles the navigation to the details page for each item.
*************************************************************/
export class CustomerListComponent implements OnInit {
drawerComponent: RadSideDrawerComponent;
search: string;
scrollCount = 0; // This keeps track of number of times user performed scrolling
// This would allow us to calculate number of records loaded at client side
_recCount: number;
_skipRec: number;
_pullToRefreshCount = 0; // We use this to keep track of howmany times refresh performed
private _isLoading: boolean = false;
private _customers: ObservableArray<Customer> = new ObservableArray<Customer>([]);
private timer;
private isAndroid: boolean = isAndroid;
private isIOS: boolean = isIOS;
private _sideDrawerTransition: DrawerTransitionBase;
constructor(
private _customerService: CustomerService,
private _routerExtensions: RouterExtensions,
private _changeDetectionRef: ChangeDetectorRef,
private _progressService: ProgressService
) { }
/* ***********************************************************
* Use the "ngOnInit" handler to get the data and assign it to the
* private property that holds it inside the component.
*************************************************************/
ngOnInit(): void {
this._isLoading = true;
/* ***********************************************************
* The data is retrieved remotely from a Progress Data Service.
* The actual data retrieval code is wrapped in a data service.
* Check out the service in customers/shared/customer.service.ts
*************************************************************/
this._fetchCustomers();
}
get customers(): ObservableArray<Customer> {
return this._customers;
}
get isLoading(): boolean {
return this._isLoading;
}
get sideDrawerTransition(): DrawerTransitionBase {
return this._sideDrawerTransition;
}
// According to guidelines, if you have a drawer on your page, you should always
// have a button that opens it. Use the showDrawer() function to open the app drawer section.
onDrawerButtonTap(): void {
this.drawerComponent.sideDrawer.showDrawer();
}
onAddButtonTap(): void {
if (!this._customerService.hasEditSupport()) {
alert("Service does not provide Add functionality.");
} else {
this._routerExtensions.navigate(["/customers/customer-detail-edit", -1, true],
{
animated: true,
transition: {
name: "slideTop",
duration: 200,
curve: "ease"
}
});
}
}
/* ***********************************************************
* Use the "itemTap" event handler of the <RadListView> to navigate to the
* item details page. Retrieve a reference for the data item (the id) and pass it
* to the item details page, so that it can identify which data item to display.
* Learn more about navigating with a parameter in this documentation article:
* http://docs.nativescript.org/angular/core-concepts/angular-navigation.html#passing-parameter
*************************************************************/
onCustomerItemTap(args: ListViewEventData): void {
const tappedCustomerItem = args.view.bindingContext;
this._routerExtensions.navigate(["/customers/customer-detail", tappedCustomerItem._id],
{
animated: true,
transition: {
curve: "ease",
duration: 200,
name: "slide"
}
});
}
onSwipeCellStarted(args: ListViewEventData) {
const swipeLimits = args.data.swipeLimits;
const swipeView = args.object;
const leftItem = swipeView.getViewById<View>("delete-view");
swipeLimits.right = leftItem.getMeasuredWidth();
swipeLimits.left = 0;
swipeLimits.threshold = leftItem.getMeasuredWidth();
}
onLeftSwipeClick(args: ListViewEventData) {
const customerItem = args.view.bindingContext;
const customerName = customerItem.Name;
const options = {
cancelButtonText: "Cancel",
message: "\"" + customerName + "\" will be deleted forever.",
okButtonText: "Delete",
title: "Delete"
};
confirm(options)
.then((result: boolean) => {
// result can be true/false
if (result) {
this._customerService.delete(customerItem)
.then((result1) => {
this._customers.forEach((item) => {
if (item.CustNum === customerItem.CustNum) {
this._customers.splice(this._customers.indexOf(item), 1);
}
});
}, (error) => {
// Delete was not successful, so let's back out the deletion
if (error && error.message) {
alert({ title: "Error", message: error.message, okButtonText: "Ok" });
} else {
alert({ title: "Error", message: "Error deleting record.", okButtonText: "Ok" });
}
}).catch((e) => {
alert({ title: "Error", message: e.message, okButtonText: "Ok" });
});
}
}, (error) => {
alert({ title: "Error", message: "Error resolving deleted record.", okButtonText: "Ok" });
}).catch((e) => {
alert({ title: "Error", message: e.message, okButtonText: "Ok" });
});
}
onSearchChange(args?: EventData) {
let searchFilter: any = JsdoSettings.searchFilter;
let params;
try {
if (typeof (searchFilter) === "object") {
searchFilter = JSON.parse(
JSON.stringify(searchFilter).replace("$SEARCH", this.search));
} else if (typeof (searchFilter) === "string") {
searchFilter = searchFilter.replace("$SEARCH", this.search);
} else {
searchFilter = "";
}
} catch (e) {
searchFilter = "";
}
if (this.search.length === 0) {
params = {
filter: JsdoSettings.filter,
sort: JsdoSettings.sort,
top: JsdoSettings.pageSize,
skip: ((JsdoSettings.pageNumber) - 1) * (JsdoSettings.pageSize)
};
this._skipRec = 0; // Reset the skiplist if the search is cleared off
} else {
params = {
filter: searchFilter,
sort: { field: "Name", dir: "asc" }
};
}
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
this._fetchCustomers(params);
}, SEARCH_DELAY);
}
onSearchBarLoaded(event) {
if (event.object.android) {
event.object.android.clearFocus();
}
}
/**
* This gets triggered when refresh (Pull down) operation is performed in the Listview
* in mobile app. As part of this we perform a read() operation with same filter criteria
* such that all records are fetched again from server
* @param args - ListViewEventData
*/
onPullToRefreshInitiated(args: ListViewEventData) {
const listView: RadListView = args.object;
// Check for the value of ngModel's search value. If it is set then perform the read based on search criteria
// If this not set, then read customer information from server via Data Service directly
if (this.search === undefined) {
// We want to use the same filter criteria while refreshing the listview
const params = {
filter: JsdoSettings.filter,
sort: JsdoSettings.sort,
top: JsdoSettings.pageSize,
skip: ((JsdoSettings.pageNumber) - 1) * (JsdoSettings.pageSize)
// mergeMode: JsdoSettings.mergeMode // We dont need to use mergeMode in case of refreshing
// In this scenario we want to fetch list based on original filter criteria only
};
if (this.timer) {
clearTimeout(this.timer);
}
if (args.object.loadOnDemandMode === ListViewLoadOnDemandMode.None) {
this._skipRec = 0; // Because listView.loadOnDemandMode navigates to its function and makes read call
listView.loadOnDemandMode = ListViewLoadOnDemandMode.Auto;
}
this.timer = setTimeout(() => {
this._fetchCustomers(params);
listView.notifyPullToRefreshFinished();
}, 50);
} else {
this.onSearchChange();
listView.notifyPullToRefreshFinished();
}
this._pullToRefreshCount = this._pullToRefreshCount + 1;
this._skipRec = 0; // Reset the skiplist once a refresh is performed
}
/**
* Gets triggered when performing 'incremental scrolling' of data in the mobile screen.
* This method is responsible for fetching more records from backend
* @param args ListViewEventData
*/
onLoadMoreItemsRequested(args: ListViewEventData) {
if (this.search === undefined || this.search.length === 0) {
this.scrollCount = this.scrollCount + 1;
// Build a params object which then can be passed to DataSource
const params = {
filter: JsdoSettings.filter,
sort: JsdoSettings.sort,
top: JsdoSettings.pageSize,
skip: ((JsdoSettings.pageNumber) - 1) * (JsdoSettings.pageSize),
pageSize: JsdoSettings.pageSize,
maxRecCount: JsdoSettings.maxRecCount,
// Default value is set to MODE_MERGE for 'Incremental Scrolling'
mergeMode: progress.data.JSDO.MODE_MERGE
};
if (!this._skipRec) {
this._skipRec = params.skip;
}
// Let's modify/increment the skip value considering a read() hasbeen performed
// by the Incremental Scrolling functionality.
params.skip = this._skipRec + params.pageSize;
this._skipRec = params.skip;
this._recCount = this._customers.length;
const listView: RadListView = args.object;
// If max record count is available/specified in the settings or if the number of records
// loaded in client reaches to max count then send an alert
if (params.maxRecCount && (((this.scrollCount) * (params.pageSize) === params.maxRecCount)
&& (this._recCount) === (params.maxRecCount))) {
this._isLoading = false;
args.object.notifyLoadOnDemandFinished();
alert("Reached max size. Increase limit.");
listView.loadOnDemandMode = ListViewLoadOnDemandMode.None;
} else {
this._customerService.load(params)
.finally(() => {
this._isLoading = false;
})
.subscribe((customers: Array<Customer>) => {
customers.splice(0, this._customers.length);
this._customers.push(customers);
this._isLoading = false;
// Setting the loadOnDemandMode to None if the last resultset from server is empty
if (this._customerService.dataSource._isLastResultSetEmpty) {
listView.loadOnDemandMode = ListViewLoadOnDemandMode.None;
this._isLoading = false;
}
args.object.notifyLoadOnDemandFinished();
}, (error) => {
if (error && error.message) {
alert("Error: \n" + error.message);
} else {
alert("Error reading records - onLoadMoreItemsRequested() section");
}
});
args.returnValue = true;
}
} else {
args.object.notifyLoadOnDemandFinished();
}
}
/**
* This function is responsible for fetching customers remotely via
* Progress Data Service
* @param params - A parameter object which includes filtering and
* sorting criteria
*/
private _fetchCustomers(params?: any) {
this._customerService.load(params)
.finally(() => {
this._isLoading = false;
})
.subscribe((customers: Array<Customer>) => {
this._customers = new ObservableArray(customers);
this._isLoading = false;
}, (error) => {
// If we're unauthorized, we need to re-login.
// Otherwise, we display the error
if (error && error.message) {
alert("Error: \n" + error.message);
} else {
alert("Error reading records.");
}
});
}
}