Skip to content

Commit

Permalink
Fixing pagination type issues
Browse files Browse the repository at this point in the history
  • Loading branch information
abaker-gavant committed Aug 3, 2020
1 parent f82dade commit a64b60a
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 49 deletions.
89 changes: 64 additions & 25 deletions addon/mixins/controller-pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,46 @@ import { tryInvoke } from '@ember/utils';
import NativeArray from '@ember/array/-private/native-array';
import { reject } from 'rsvp';
import { A } from '@ember/array';
import { buildQueryParams, PaginationController, sortDirection } from '@gavant/ember-pagination/utils/query-params';
import { buildQueryParams, sortDirection } from '@gavant/ember-pagination/utils/query-params';
import DS from 'ember-data';
import RouterService from '@ember/routing/router-service';
import Controller from '@ember/controller';
export type ConcreteSubclass<T> = new (...args: any[]) => T;
export type PaginationControllerClass = ConcreteSubclass<PaginationController>;

export interface PaginationController extends Controller {
offset: number | undefined;
limit: number;
sort: string[];
hasMore: boolean;
modelName: string;
isLoadingPage: boolean;
pagingRootKey: string | null;
filterRootKey: string | null;
includeKey: string;
fetchModels(
this: PaginationController,
queryParams: any
): DS.AdapterPopulatedRecordArray<any> & DS.PromiseArray<any>;
clearModels(this: PaginationController): void;
_loadModels(this: PaginationController, reset: boolean): Promise<any[]>;
loadModels(reset?: boolean): Promise<any[]> | undefined;
filterModels(this: PaginationController): Promise<any[]> | undefined;
metadata?: {
[key: string]: any;
};
serverQueryParams?: any[];
serverDateFormat?: string;
}

interface ArrayMeta {
meta: any;
}

export type ConcreteSubclass<T> = new(...args: any[]) => T;
export type SearchQuery = DS.AdapterPopulatedRecordArray<any> & DS.RecordArray<any> & DS.PromiseArray<any> & ArrayMeta;

export default function ControllerPaginationClass<T extends ConcreteSubclass<any>>(ControllerSubclass: T) {
class PaginationControllerClass extends ControllerSubclass {
export function ControllerPaginationClass<U extends ConcreteSubclass<Controller>>(ControllerSubclass: U) {
class PaginationControllerClass extends ControllerSubclass implements PaginationController {
@service router!: RouterService;
sort: NativeArray<any> = A();
hasMore: boolean = true;
Expand All @@ -21,6 +53,13 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
pagingRootKey: string | null = 'page';
filterRootKey: string | null = 'filter';
includeKey: string = 'include';
modelName = '';
metadata?: {
[key: string]: any;
} = {};

serverQueryParams = <any>[];
serverDateFormat = undefined;

@or('isLoadingPage', 'isLoadingRoute') isLoadingModels!: boolean;
@readOnly('model.length') offset: number | undefined;
Expand All @@ -35,9 +74,9 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
return Math.ceil(this.model.length / this.limit);
}

async _loadModels(this: PaginationController, reset: boolean) {
async _loadModels(reset: boolean) {
this.set('isLoadingPage', true);
if(reset) {
if (reset) {
this.clearModels();
}

Expand All @@ -46,15 +85,15 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
const queryParams = buildQueryParams(this, offset, limit);
let models = [];
try {
const result = await this.fetchModels(queryParams);
const result = await this.fetchModels(queryParams) as SearchQuery;
models = result.toArray();
setProperties(this, {
metadata: result.meta,
hasMore: models.length >= limit
});

tryInvoke(this.model, 'pushObjects', [models]);
} catch(errors) {
} catch (errors) {
reject(errors);
}

Expand All @@ -67,17 +106,17 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* It should return a promise, which resolves to an array-like object (such as a DS.RecordArray)
* @returns - the result of `store.query`
*/
fetchModels(this: PaginationController, queryParams: any) {
const modelName = this.modelName as never;
return this.store.query(modelName, queryParams);
fetchModels(queryParams: any): SearchQuery {
const modelName = this.modelName;
return this.store.query(modelName, queryParams) as SearchQuery;
}

/**
* Change the sorting and call `filterModels`. Will only load models if not currently making an API call
* @param reset - Clear models
* @returns - an array of models
*/
loadModels(this: PaginationController, reset: boolean = false) {
loadModels(reset: boolean = false): Promise<any[]> | undefined {
if (!this.isLoadingPage) {
return this._loadModels(reset);
} else {
Expand All @@ -89,7 +128,7 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* Filter models
* @returns - an array of models
*/
filterModels(this: PaginationController) {
filterModels() {
return this.loadModels(true);
}

Expand All @@ -105,7 +144,7 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* @returns - an array of models
*/
@action
loadMoreModels(this: PaginationController) {
loadMoreModels() {
return this.loadModels();
}

Expand All @@ -114,7 +153,7 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* @returns - an array of models
*/
@action
reloadModels(this: PaginationController) {
reloadModels() {
return this.loadModels(true);
}

Expand All @@ -124,12 +163,12 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* @returns - result of api call
*/
@action
async removeModel(this: PaginationController, model: DS.Model) {
async removeModel(model: DS.Model) {
try {
let result = await tryInvoke(model, 'destroyRecord');
this.model.removeObject(model);
return result;
} catch(error) {
} catch (error) {
return reject(error);
}
}
Expand All @@ -138,12 +177,12 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* Clear models
*/
@action
clearModels(this: PaginationController) {
clearModels() {
this.set('model', A());
}

@action
filter(this: PaginationController) {
filter() {
return this.filterModels();
}

Expand All @@ -155,11 +194,11 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* @returns - an array of models
*/
@action
changeSorting(this: PaginationController, sort: string[], dir: sortDirection, isSorted: boolean) {
changeSorting(sort: string[], dir: sortDirection, isSorted: boolean) {
const sorting = this.sort;
const sortedValue = `${dir === "desc" ? '-' : ''}${sort}`;
const oppositeSortedValue = `${dir === "asc" ? '-' : ''}${sort}`;
if(!isSorted) {
const sortedValue = `${dir === 'desc' ? '-' : ''}${sort}`;
const oppositeSortedValue = `${dir === 'asc' ? '-' : ''}${sort}`;
if (!isSorted) {
sorting.removeObject(sortedValue);
sorting.removeObject(oppositeSortedValue);
} else if (!sorting.includes(oppositeSortedValue) && !sorting.includes(sortedValue)) {
Expand All @@ -178,8 +217,8 @@ export default function ControllerPaginationClass<T extends ConcreteSubclass<any
* @returns - an array of models
*/
@action
clearFilters(this: PaginationController) {
this.serverQueryParams.forEach((param: any) => this.set(param, null));
clearFilters() {
this.serverQueryParams?.forEach((param: any) => this.set(param, null));
return this.filterModels();
}
}
Expand Down
20 changes: 11 additions & 9 deletions addon/mixins/route-pagination.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { set, setProperties } from '@ember/object';
import { assert } from '@ember/debug';
import DS from 'ember-data';
import { PaginationController, buildQueryParams } from '@gavant/ember-pagination/utils/query-params';
import { buildQueryParams } from '@gavant/ember-pagination/utils/query-params';
import Route from '@ember/routing/route';
import { PaginationControllerClass, SearchQuery } from './controller-pagination';

export type ConcreteSubclass<T> = new(...args: any[]) => T;

export default function RoutePaginationClass<T extends ConcreteSubclass<any>>(RouteSubclass: T) {
export type ConcreteSubclass<T> = new (...args: any[]) => T;
export type PaginationRoute = ConcreteSubclass<Route>;
export default function RoutePaginationClass<T extends ConcreteSubclass<Route>>(RouteSubclass: T) {
class PaginationRouteClass extends RouteSubclass {
/**
* Adds functionality `modelName`, `metadata`, and `hasMore` to the controller
* @param controller - The controller you want the functionality to be added on to
* @param model - The result returned from a `store.query`
*/
setupController(controller: PaginationController, model: any) {
setupController(controller: InstanceType<PaginationControllerClass>, model: any) {
assert(
'Model is not an instanceof DS.AdapterPopulatedRecordArray. In order to use the RoutePaginationMixin, the model returned must be an instance of DS.AdapterPopulatedRecordArray or DS.RecordArray which comes from using store.query',
model instanceof DS.AdapterPopulatedRecordArray || model instanceof DS.RecordArray
Expand All @@ -35,8 +37,8 @@ export default function RoutePaginationClass<T extends ConcreteSubclass<any>>(Ro
* Should be passed in using `/` separators i.e. `accounts/index`
* @returns - Controller query params
*/
getControllerParams(this: PaginationController, routeName: string = this.routeName): any {
const controller = this.controllerFor(routeName);
getControllerParams(this: InstanceType<PaginationRoute>, routeName: string = this.routeName): any {
const controller = this.controllerFor<any>(routeName) as InstanceType<PaginationControllerClass>;
return buildQueryParams(controller);
}

Expand All @@ -45,8 +47,8 @@ export default function RoutePaginationClass<T extends ConcreteSubclass<any>>(Ro
* @param controller - The controller you want the functionality to be added on to
* @param isExiting - Is the controller exiting
*/
resetController(controller: PaginationController, isExiting: boolean) {
super.resetController(controller, isExiting);
resetController(controller: InstanceType<PaginationControllerClass>, isExiting: boolean, transition: any) {
super.resetController(controller, isExiting, transition);

if (isExiting) {
set(controller, 'model', null);
Expand Down
21 changes: 6 additions & 15 deletions addon/utils/query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ import { get, set, getWithDefault } from '@ember/object';
import { isArray } from '@ember/array';
import { isEmpty } from '@ember/utils';
import moment from 'moment';
import { PaginationController, PaginationControllerClass } from '../mixins/controller-pagination';

export interface PaginationController {
offset: number | undefined;
limit: number;
sort: string[];
modelName: string;
pagingRootKey: string | null;
filterRootKey: string | null;
includeKey: string;
[key: string]: any;
}

export enum sortDirection {
ascending = "asc",
Expand All @@ -28,14 +19,14 @@ export enum sortDirection {
* @returns - Object with query params to send to server
*/
export function buildQueryParams(
controller: PaginationController,
controller: InstanceType<PaginationControllerClass>,
offset: number = 0,
limit: number = 10,
queryParamListName: string = 'serverQueryParams',
includeListName: string = 'include'
) {
const filterList: string[] = controller[queryParamListName];
const includeList: string[] = controller[includeListName];
const filterList: string[] = controller[queryParamListName as keyof PaginationController] ;
const includeList: string[] = controller[includeListName as keyof PaginationController];
let queryParams = getParamsObject(filterList, controller);
let pagingRoot = queryParams;

Expand All @@ -60,7 +51,7 @@ export function buildQueryParams(
* @param context - The pagination controller instance
* @returns - Object with query params to send to server
*/
export function getParamsObject(parameters: string[] | undefined, context: PaginationController) {
export function getParamsObject(parameters: string[] | undefined, context: InstanceType<PaginationControllerClass>) {
let params: any = {};
let filterRoot = params;

Expand All @@ -79,7 +70,7 @@ export function getParamsObject(parameters: string[] | undefined, context: Pagin
key = paramArray[0];
valueKey = paramArray[1];
}
let value = get(context, valueKey);
let value = get(context, valueKey as keyof PaginationController);
if (moment.isMoment(value) || moment.isDate(value)) {
let serverDateFormat = getWithDefault(context, 'serverDateFormat', 'YYYY-MM-DDTHH:mm:ss');
value = moment(value).format(serverDateFormat);
Expand Down

0 comments on commit a64b60a

Please sign in to comment.