If your back end is going to return significant amounts of data, you should consider using the DataNavigator
class provided by the framework. There are two models for querying virtualized data from the server:
-
"Load more" model: With this model, a page of data is loaded, the user scrolls to the bottom, and then the next page of data is loaded. There is no way to scroll to "page 5", and all data is represented in a timeline. This works best with APIs that provide continuation tokens, or timeline based data.
-
Paged model: The classic paged model provides either a pager control, or a virtualized scrollbar which represents the paged data. It works best with back end APIs that have a skip/take style data virtualization strategy.
Both of these use the existing QueryCache
and the MsPortalFx.Data.RemoteDataNavigator
entities to orchestrate virtualization.
The 'load more' approach requires setting up a QueryCache
with a navigation element. The navigation element describes the continuation token model:
\SamplesExtension\Extension\Client\Controls\ProductData.ts
this.productsCache = new MsPortalFx.Data.QueryCache<SamplesExtension.DataModels.Product, ProductQueryParams>({
entityTypeName: SamplesExtension.DataModels.ProductType,
sourceUri: MsPortalFx.Data.uriFormatter(ProductData.QueryString),
navigation: {
loadByContinuationToken: (
suppliedQueryView: MsPortalFx.Data.QueryView<SamplesExtension.DataModels.Product, ProductQueryParams>,
query: ProductQueryParams,
reset: boolean,
filter: string): MsPortalFx.Base.Promise => {
var token = reset ? "" :
(suppliedQueryView.metadata() ?
suppliedQueryView.metadata().continuationToken :
"");
return suppliedQueryView.fetch({ token: token, categoryId: query.categoryId });
}
},
processServerResponse: (response: any) => {
return <MsPortalFx.Data.DataCacheProcessedResponse>{
data: response.products,
navigationMetadata: {
totalItemCount: response.totalCount,
continuationToken: response.continuationToken
}
};
}
});
In the view model, use the Pageable
extension for the grid, with the Sequential
type. Instead of the createView
API on the QueryCache, use the createNavigator
API which integrates with the virtualized data system:
\SamplesExtension\Extension\Client\Controls\Grid\ViewModels\PageableGridViewModel.ts
constructor(container: MsPortalFx.ViewModels.PartContainerContract,
initialState: any,
dataContext: ControlsArea.DataContext) {
// create the data navigator from the data context (above)
this._sequentialDataNavigator = dataContext.productDataByContinuationToken.productsCache.createNavigator(container);
// Define the extensions you wish to enable.
var extensions = MsPortalFx.ViewModels.Controls.Lists.Grid.Extensions.Pageable;
// Define the options required to have the extensions behave properly.
var pageableExtensionOptions = {
pageable: {
type: MsPortalFx.ViewModels.Controls.Lists.Grid.PageableType.Sequential,
dataNavigator: this._sequentialDataNavigator
}
};
// Initialize the grid view model.
this.sequentialPageableGridViewModel = new MsPortalFx.ViewModels.Controls.Lists.Grid
.ViewModel<SamplesExtension.DataModels.Product, ProductSelectionItem>(
null, extensions, pageableExtensionOptions);
// Set up which columns to show. If you do not specify a formatter, we just call toString on
// the item.
var basicColumns: MsPortalFx.ViewModels.Controls.Lists.Grid.Column[] = [
{
itemKey: "id",
name: ko.observable(ClientResources.gridProductIdHeader)
},
{
itemKey: "description",
name: ko.observable(ClientResources.gridProductDescriptionHeader)
},
];
this.sequentialPageableGridViewModel.showHeader = true;
this.sequentialPageableGridViewModel.columns =
ko.observableArray<MsPortalFx.ViewModels.Controls.Lists.Grid.Column>(basicColumns);
this.sequentialPageableGridViewModel.summary =
ko.observable(ClientResources.basicGridSummary);
this.sequentialPageableGridViewModel.noRowsMessage =
ko.observable(ClientResources.nobodyInDatabase);
}
public onInputsSet(inputs: any): MsPortalFx.Base.Promise {
return this._sequentialDataNavigator.setQuery({ categoryId: inputs.categoryId });
}
The pageable approach requires setting up a QueryCache
with a navigation element. The navigation element describes the skip-take behavior:
\SamplesExtension\Extension\Client\Controls\ProductPageableData.ts
var QueryString = MsPortalFx.Base.Resources
.getAppRelativeUri("/api/Product/GetPageResult?skip={skip}&take={take}");
var productsCache = new MsPortalFx.Data.QueryCache<SamplesExtension.DataModels.Product, ProductPageableQueryParams>({
entityTypeName: SamplesExtension.DataModels.ProductType,
sourceUri: MsPortalFx.Data.uriFormatter(ProductPageableData.QueryString),
navigation: {
loadBySkipTake: (
suppliedQueryView: MsPortalFx.Data.QueryView<SamplesExtension.DataModels.Product, ProductPageableQueryParams>,
query: ProductPageableQueryParams,
skip: number,
take: number,
filter: string): MsPortalFx.Base.Promise => {
return suppliedQueryView.fetch({ skip: skip.toString(), take: take.toString(), categoryId: query.categoryId });
}
},
processServerResponse: (response: any) => {
return <MsPortalFx.Data.DataCacheProcessedResponse>{
data: response.products,
navigationMetadata: {
totalItemCount: response.totalCount,
continuationToken: response.continuationToken
}
};
}
});
In the view model, use the Pageable
extension for the grid, with the Pageable
type. Instead of the createView
API on the QueryCache, use the createNavigator
API which integrates with the virtualized data system:
\SamplesExtension\Extension\Client\Controls\Grid\ViewModels\PageableGridViewModel.ts
constructor(container: MsPortalFx.ViewModels.PartContainerContract,
initialState: any,
dataContext: ControlsArea.DataContext) {
this._pageableDataNavigator = dataContext.productDataBySkipTake.productsCache.createNavigator(container);
// Define the extensions you wish to enable.
var extensions = MsPortalFx.ViewModels.Controls.Lists.Grid.Extensions.Pageable;
// Define the options required to have the extensions behave properly.
var pageableExtensionOptions = {
pageable: {
type: MsPortalFx.ViewModels.Controls.Lists.Grid.PageableType.Pageable,
dataNavigator: this._pageableDataNavigator,
itemsPerPage: ko.observable(20)
}
};
// Initialize the grid view model.
this.pagingPageableGridViewModel = new MsPortalFx.ViewModels.Controls.Lists.Grid
.ViewModel<SamplesExtension.DataModels.Product, ProductSelectionItem>(
null, extensions, pageableExtensionOptions);
// Set up which columns to show. If you do not specify a formatter, we just call toString on
// the item.
var basicColumns: MsPortalFx.ViewModels.Controls.Lists.Grid.Column[] = [
{
itemKey: "id",
name: ko.observable(ClientResources.gridProductIdHeader)
},
{
itemKey: "description",
name: ko.observable(ClientResources.gridProductDescriptionHeader)
},
];
this.pagingPageableGridViewModel.showHeader = true;
this.pagingPageableGridViewModel.columns =
ko.observableArray<MsPortalFx.ViewModels.Controls.Lists.Grid.Column>(basicColumns);
this.pagingPageableGridViewModel.summary =
ko.observable(ClientResources.basicGridSummary);
this.pagingPageableGridViewModel.noRowsMessage =
ko.observable(ClientResources.nobodyInDatabase);
}
public onInputsSet(inputs: any): MsPortalFx.Base.Promise {
return this._pageableDataNavigator.setQuery({ categoryId: inputs.categoryId });
}