mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 14:03:06 +00:00
@@ -78,9 +78,9 @@
|
|||||||
"@angular/upgrade": "2.2.3",
|
"@angular/upgrade": "2.2.3",
|
||||||
"@angularclass/bootloader": "1.0.1",
|
"@angularclass/bootloader": "1.0.1",
|
||||||
"@angularclass/idle-preload": "1.0.4",
|
"@angularclass/idle-preload": "1.0.4",
|
||||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.15",
|
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.18",
|
||||||
"@ngrx/core": "^1.2.0",
|
"@ngrx/core": "^1.2.0",
|
||||||
"@ngrx/effects": "^2.0.0",
|
"@ngrx/effects": "2.0.2",
|
||||||
"@ngrx/router-store": "^1.2.5",
|
"@ngrx/router-store": "^1.2.5",
|
||||||
"@ngrx/store": "^2.2.1",
|
"@ngrx/store": "^2.2.1",
|
||||||
"@ngrx/store-devtools": "^3.2.2",
|
"@ngrx/store-devtools": "^3.2.2",
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"angular2-universal": "2.1.0-rc.1",
|
"angular2-universal": "2.1.0-rc.1",
|
||||||
"angular2-universal-polyfills": "2.1.0-rc.1",
|
"angular2-universal-polyfills": "2.1.0-rc.1",
|
||||||
"body-parser": "1.15.2",
|
"body-parser": "1.15.2",
|
||||||
"bootstrap": "4.0.0-alpha.5",
|
"bootstrap": "4.0.0-alpha.6",
|
||||||
"cerialize": "^0.1.13",
|
"cerialize": "^0.1.13",
|
||||||
"compression": "1.6.2",
|
"compression": "1.6.2",
|
||||||
"express": "4.14.0",
|
"express": "4.14.0",
|
||||||
@@ -102,6 +102,7 @@
|
|||||||
"jsonschema": "^1.1.1",
|
"jsonschema": "^1.1.1",
|
||||||
"methods": "1.1.2",
|
"methods": "1.1.2",
|
||||||
"morgan": "1.7.0",
|
"morgan": "1.7.0",
|
||||||
|
"ng2-pagination": "^2.0.0",
|
||||||
"preboot": "4.5.2",
|
"preboot": "4.5.2",
|
||||||
"reflect-metadata": "^0.1.10",
|
"reflect-metadata": "^0.1.10",
|
||||||
"rxjs": "5.0.0-beta.12",
|
"rxjs": "5.0.0-beta.12",
|
||||||
|
@@ -20,6 +20,14 @@
|
|||||||
"home": "Home"
|
"home": "Home"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"pagination": {
|
||||||
|
"results-per-page": "Results Per Page",
|
||||||
|
"showing": {
|
||||||
|
"label" : "Now showing items ",
|
||||||
|
"detail": "{{ range }} of {{ total }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"title": "DSpace",
|
"title": "DSpace",
|
||||||
|
|
||||||
"404": {
|
"404": {
|
||||||
|
@@ -22,6 +22,7 @@ import { HostWindowResizeAction } from "./shared/host-window.actions";
|
|||||||
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
|
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
|
||||||
|
|
||||||
import { GLOBAL_CONFIG, EnvConfig } from '../config';
|
import { GLOBAL_CONFIG, EnvConfig } from '../config';
|
||||||
|
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||||
|
|
||||||
let comp: AppComponent;
|
let comp: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
@@ -42,6 +43,7 @@ describe('App component', () => {
|
|||||||
declarations: [AppComponent], // declare the test component
|
declarations: [AppComponent], // declare the test component
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: GLOBAL_CONFIG, useValue: EnvConfig },
|
{ provide: GLOBAL_CONFIG, useValue: EnvConfig },
|
||||||
|
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||||
AppComponent
|
AppComponent
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
@@ -8,9 +8,10 @@ import {
|
|||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
import { HostWindowState } from "./shared/host-window.reducer";
|
import { HostWindowState } from "./shared/host-window.reducer";
|
||||||
import { Store } from "@ngrx/store";
|
import { Store } from "@ngrx/store";
|
||||||
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
|
||||||
|
|
||||||
|
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||||
import { EnvConfig, GLOBAL_CONFIG, GlobalConfig } from '../config';
|
import { EnvConfig, GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||||
|
import { NativeWindowRef, NativeWindowService } from "./shared/window.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
@@ -23,6 +24,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
|
@Inject(GLOBAL_CONFIG) public EnvConfig: GlobalConfig,
|
||||||
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private store: Store<HostWindowState>
|
private store: Store<HostWindowState>
|
||||||
) {
|
) {
|
||||||
@@ -33,6 +35,7 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.onInit();
|
||||||
const env: string = EnvConfig.production ? "Production" : "Development";
|
const env: string = EnvConfig.production ? "Production" : "Development";
|
||||||
const color: string = EnvConfig.production ? "red" : "green";
|
const color: string = EnvConfig.production ? "red" : "green";
|
||||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||||
@@ -45,4 +48,12 @@ export class AppComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onInit(): void {
|
||||||
|
if (typeof this._window !== 'undefined') {
|
||||||
|
this.store.dispatch(
|
||||||
|
new HostWindowResizeAction(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
|||||||
HomeModule,
|
HomeModule,
|
||||||
ItemPageModule,
|
ItemPageModule,
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
AppRoutingModule,
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
]
|
]
|
||||||
|
@@ -1,12 +1,20 @@
|
|||||||
export class PaginationOptions {
|
import { NgbPaginationConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
export class PaginationOptions extends NgbPaginationConfig {
|
||||||
/**
|
/**
|
||||||
* The number of results per page.
|
* ID for the pagination instance. Only useful if you wish to
|
||||||
|
* have more than once instance at a time in a given component.
|
||||||
*/
|
*/
|
||||||
resultsPerPage: number = 10;
|
id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The active page.
|
* The active page.
|
||||||
*/
|
*/
|
||||||
currentPage: number = 1;
|
currentPage: number = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A number array that represents options for a context pagination limit.
|
||||||
|
*/
|
||||||
|
pageSizeOptions: Array<number> = [ 5, 10, 20, 40, 60, 80, 100 ];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core';
|
import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { SharedModule } from "../shared/shared.module";
|
import { SharedModule } from "../shared/shared.module";
|
||||||
|
|
||||||
import { isNotEmpty } from "../shared/empty.util";
|
import { isNotEmpty } from "../shared/empty.util";
|
||||||
import { FooterComponent } from "./footer/footer.component";
|
import { FooterComponent } from "./footer/footer.component";
|
||||||
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
import { DSpaceRESTv2Service } from "./dspace-rest-v2/dspace-rest-v2.service";
|
||||||
@@ -11,6 +12,7 @@ import { ItemDataService } from "./data/item-data.service";
|
|||||||
import { RequestService } from "./data/request.service";
|
import { RequestService } from "./data/request.service";
|
||||||
import { RemoteDataBuildService } from "./cache/builders/remote-data-build.service";
|
import { RemoteDataBuildService } from "./cache/builders/remote-data-build.service";
|
||||||
import { CommunityDataService } from "./data/community-data.service";
|
import { CommunityDataService } from "./data/community-data.service";
|
||||||
|
import { PaginationOptions } from "./cache/models/pagination-options.model";
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -31,6 +33,7 @@ const PROVIDERS = [
|
|||||||
ItemDataService,
|
ItemDataService,
|
||||||
DSpaceRESTv2Service,
|
DSpaceRESTv2Service,
|
||||||
ObjectCacheService,
|
ObjectCacheService,
|
||||||
|
PaginationOptions,
|
||||||
ResponseCacheService,
|
ResponseCacheService,
|
||||||
RequestService,
|
RequestService,
|
||||||
RemoteDataBuildService
|
RemoteDataBuildService
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-dark bg-inverse">
|
<nav class="navbar navbar-toggleable-sm navbar-inverse bg-inverse">
|
||||||
<button class="navbar-toggler hidden-sm-up" type="button" (click)="toggle()" aria-controls="collapsingNav" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler navbar-toggler-right" type="button" (click)="toggle()" aria-controls="collapsingNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
<span class="navbar-toggler-icon fa fa-bars fa-fw" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<div [ngClass]="{'clearfix': !(isNavBarCollapsed | async)}">
|
<div [ngClass]="{'clearfix': !(isNavBarCollapsed | async)}">
|
||||||
<a class="navbar-brand" routerLink="/home">
|
<a class="navbar-brand" routerLink="/home">
|
||||||
<!-- TODO: add logo here -->{{ 'title' | translate }}</a>
|
<!-- TODO: add logo here -->{{ 'title' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div [ngbCollapse]="(isNavBarCollapsed | async)" class="collapse navbar-toggleable-xs" id="collapsingNav">
|
<div [ngbCollapse]="(isNavBarCollapsed | async)" class="collapse navbar-collapse" id="collapsingNav">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="navbar-nav mr-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
|
<a class="nav-link" routerLink="/home" routerLinkActive="active"><i class="fa fa-home fa-fw" aria-hidden="true"></i> {{ 'nav.home' | translate }}<span class="sr-only">(current)</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -4,10 +4,11 @@ header nav.navbar {
|
|||||||
border-radius: 0rem;
|
border-radius: 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
header nav.navbar .navbar-toggler {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
header nav.navbar .navbar-toggler:hover {
|
header nav.navbar .navbar-toggler:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header nav.navbar .navbar-toggler .navbar-toggler-icon {
|
||||||
|
background-image: none !important;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
@@ -58,7 +58,7 @@ describe('HeaderComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should close the menu", () => {
|
it("should close the menu", () => {
|
||||||
expect(menu.classList).not.toContain('in');
|
expect(menu.classList).not.toContain('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -73,7 +73,7 @@ describe('HeaderComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should open the menu", () => {
|
it("should open the menu", () => {
|
||||||
expect(menu.classList).toContain('in');
|
expect(menu.classList).toContain('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
191
src/app/shared/host-window.service.spec.ts
Normal file
191
src/app/shared/host-window.service.spec.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { HostWindowService } from "./host-window.service";
|
||||||
|
import { HostWindowState } from "./host-window.reducer";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { Store } from "@ngrx/store";
|
||||||
|
|
||||||
|
describe("HostWindowService", () => {
|
||||||
|
let service: HostWindowService;
|
||||||
|
let mockStore: any;
|
||||||
|
let store: Store<HostWindowState>;
|
||||||
|
|
||||||
|
describe("", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let _initialState = { width: 1600, height: 770 };
|
||||||
|
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||||
|
service = new HostWindowService(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isXs() should return false with width = 1600", () => {
|
||||||
|
service.isXs().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isSm() should return false with width = 1600", () => {
|
||||||
|
service.isSm().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isMd() should return false with width = 1600", () => {
|
||||||
|
service.isMd().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isLg() should return false with width = 1600", () => {
|
||||||
|
service.isLg().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isXl() should return true with width = 1600", () => {
|
||||||
|
service.isXl().subscribe((status) => {
|
||||||
|
expect(status).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let _initialState = { width: 1100, height: 770 };
|
||||||
|
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||||
|
service = new HostWindowService(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isXs() should return false with width = 1100", () => {
|
||||||
|
service.isXs().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isSm() should return false with width = 1100", () => {
|
||||||
|
service.isSm().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isMd() should return false with width = 1100", () => {
|
||||||
|
service.isMd().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isLg() should return true with width = 1100", () => {
|
||||||
|
service.isLg().subscribe((status) => {
|
||||||
|
expect(status).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isXl() should return false with width = 1100", () => {
|
||||||
|
service.isXl().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let _initialState = { width: 800, height: 770 };
|
||||||
|
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||||
|
service = new HostWindowService(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isXs() should return false with width = 800", () => {
|
||||||
|
service.isXs().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isSm() should return false with width = 800", () => {
|
||||||
|
service.isSm().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isMd() should return true with width = 800", () => {
|
||||||
|
service.isMd().subscribe((status) => {
|
||||||
|
expect(status).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isLg() should return false with width = 800", () => {
|
||||||
|
service.isLg().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isXl() should return false with width = 800", () => {
|
||||||
|
service.isXl().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let _initialState = { width: 600, height: 770 };
|
||||||
|
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||||
|
service = new HostWindowService(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isXs() should return false with width = 600", () => {
|
||||||
|
service.isXs().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isSm() should return true with width = 600", () => {
|
||||||
|
service.isSm().subscribe((status) => {
|
||||||
|
expect(status).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isMd() should return false with width = 600", () => {
|
||||||
|
service.isMd().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isLg() should return false with width = 600", () => {
|
||||||
|
service.isLg().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isXl() should return false with width = 600", () => {
|
||||||
|
service.isXl().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
let _initialState = { width: 400, height: 770 };
|
||||||
|
store = new Store<HostWindowState>(undefined, undefined, Observable.of(_initialState));
|
||||||
|
service = new HostWindowService(store);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isXs() should return true with width = 400", () => {
|
||||||
|
service.isXs().subscribe((status) => {
|
||||||
|
expect(status).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isSm() should return false with width = 400", () => {
|
||||||
|
service.isSm().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isMd() should return false with width = 400", () => {
|
||||||
|
service.isMd().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isLg() should return false with width = 400", () => {
|
||||||
|
service.isLg().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("isXl() should return false with width = 400", () => {
|
||||||
|
service.isXl().subscribe((status) => {
|
||||||
|
expect(status).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
58
src/app/shared/host-window.service.ts
Normal file
58
src/app/shared/host-window.service.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { HostWindowState } from "./host-window.reducer";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Store } from "@ngrx/store";
|
||||||
|
import { Observable } from "rxjs/Observable";
|
||||||
|
import { hasValue } from "./empty.util";
|
||||||
|
|
||||||
|
//TODO ideally we should get these from sass somehow
|
||||||
|
export enum GridBreakpoint {
|
||||||
|
XS = 0,
|
||||||
|
SM = 576,
|
||||||
|
MD = 768,
|
||||||
|
LG = 992,
|
||||||
|
XL = 1200
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HostWindowService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store<HostWindowState>
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWidthObs(): Observable<number> {
|
||||||
|
return this.store.select<number>('hostWindow', 'width')
|
||||||
|
.filter(width => hasValue(width));
|
||||||
|
}
|
||||||
|
|
||||||
|
isXs(): Observable<boolean> {
|
||||||
|
return this.getWidthObs()
|
||||||
|
.map(width => width < GridBreakpoint.SM)
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
isSm(): Observable<boolean> {
|
||||||
|
return this.getWidthObs()
|
||||||
|
.map(width => width >= GridBreakpoint.SM && width < GridBreakpoint.MD)
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
isMd(): Observable<boolean> {
|
||||||
|
return this.getWidthObs()
|
||||||
|
.map(width => width >= GridBreakpoint.MD && width < GridBreakpoint.LG)
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLg(): Observable<boolean> {
|
||||||
|
return this.getWidthObs()
|
||||||
|
.map(width => width >= GridBreakpoint.LG && width < GridBreakpoint.XL)
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
isXl(): Observable<boolean> {
|
||||||
|
return this.getWidthObs()
|
||||||
|
.map(width => width >= GridBreakpoint.XL)
|
||||||
|
.distinctUntilChanged();
|
||||||
|
}
|
||||||
|
}
|
32
src/app/shared/pagination/pagination.component.html
Normal file
32
src/app/shared/pagination/pagination.component.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<div class="pagination-masked clearfix top">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col pagination-info">
|
||||||
|
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
|
||||||
|
<span class="align-middle">{{ 'pagination.showing.detail' | translate:showingDetail }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div ngbDropdown #paginationControls="ngbDropdown" class="d-inline-block float-right">
|
||||||
|
<button class="btn btn-outline-primary" id="paginationControls" (click)="$event.stopPropagation(); (paginationControls.isOpen())?paginationControls.close():paginationControls.open();"><i class="fa fa-cog" aria-hidden="true"></i></button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" id="paginationControlsDropdownMenu" aria-labelledby="paginationControls">
|
||||||
|
<h6 class="dropdown-header">{{ 'pagination.results-per-page' | translate}}</h6>
|
||||||
|
<button class="dropdown-item" style="padding-left: 20px" *ngFor="let item of pageSizeOptions " (click)="setPageSize(item)"><i class="fa fa-check {{(item != paginationOptions.pageSize) ? 'invisible' : ''}}" aria-hidden="true"></i> {{item}} </button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-content></ng-content>
|
||||||
|
|
||||||
|
<div class="pagination justify-content-center clearfix bottom">
|
||||||
|
<ngb-pagination [boundaryLinks]="paginationOptions.boundaryLinks"
|
||||||
|
[collectionSize]="collectionSize"
|
||||||
|
[disabled]="paginationOptions.disabled"
|
||||||
|
[ellipses]="paginationOptions.ellipses"
|
||||||
|
[maxSize]="(isXs)?5:paginationOptions.maxSize"
|
||||||
|
[(page)]="currentPage"
|
||||||
|
(pageChange)="doPageChange($event)"
|
||||||
|
[pageSize]="pageSize"
|
||||||
|
[rotate]="paginationOptions.rotate"
|
||||||
|
[size]="(isXs)?'sm':paginationOptions.size"></ngb-pagination>
|
||||||
|
</div>
|
343
src/app/shared/pagination/pagination.component.spec.ts
Normal file
343
src/app/shared/pagination/pagination.component.spec.ts
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
// ... test imports
|
||||||
|
import {
|
||||||
|
async,
|
||||||
|
ComponentFixture,
|
||||||
|
inject,
|
||||||
|
TestBed, fakeAsync, tick
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
|
DebugElement
|
||||||
|
} from "@angular/core";
|
||||||
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import Spy = jasmine.Spy;
|
||||||
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
|
import { StoreModule } from "@ngrx/store";
|
||||||
|
|
||||||
|
// Load the implementations that should be tested
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { Ng2PaginationModule } from 'ng2-pagination';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { PaginationComponent } from './pagination.component';
|
||||||
|
import { PaginationOptions } from '../../core/cache/models/pagination-options.model';
|
||||||
|
import { MockTranslateLoader } from "../testing/mock-translate-loader";
|
||||||
|
|
||||||
|
import { GLOBAL_CONFIG, EnvConfig } from '../../../config';
|
||||||
|
import { ActivatedRouteStub, RouterStub } from "../testing/router-stubs";
|
||||||
|
import { HostWindowService } from "../host-window.service";
|
||||||
|
|
||||||
|
|
||||||
|
function createTestComponent<T>(html: string, type: {new (...args: any[]): T}): ComponentFixture<T> {
|
||||||
|
TestBed.overrideComponent(type, {
|
||||||
|
set: { template: html }
|
||||||
|
});
|
||||||
|
let fixture = TestBed.createComponent(type);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
return fixture as ComponentFixture<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectPages(fixture: ComponentFixture<any>, pagesDef: string[]): void {
|
||||||
|
let de = fixture.debugElement.query(By.css('.pagination'));
|
||||||
|
let pages = de.nativeElement.querySelectorAll('li');
|
||||||
|
|
||||||
|
expect(pages.length).toEqual(pagesDef.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < pagesDef.length; i++) {
|
||||||
|
let pageDef = pagesDef[i];
|
||||||
|
let classIndicator = pageDef.charAt(0);
|
||||||
|
|
||||||
|
if (classIndicator === '+') {
|
||||||
|
expect(pages[i].classList.contains("active")).toBeTruthy();
|
||||||
|
expect(pages[i].classList.contains("disabled")).toBeFalsy();
|
||||||
|
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1));
|
||||||
|
} else if (classIndicator === '-') {
|
||||||
|
expect(pages[i].classList.contains("active")).toBeFalsy();
|
||||||
|
expect(pages[i].classList.contains("disabled")).toBeTruthy();
|
||||||
|
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1));
|
||||||
|
if (normalizeText(pages[i].textContent) !== '...') {
|
||||||
|
expect(pages[i].querySelector('a').getAttribute('tabindex')).toEqual('-1');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expect(pages[i].classList.contains("active")).toBeFalsy();
|
||||||
|
expect(pages[i].classList.contains("disabled")).toBeFalsy();
|
||||||
|
expect(normalizeText(pages[i].textContent)).toEqual(pageDef);
|
||||||
|
if (normalizeText(pages[i].textContent) !== '...') {
|
||||||
|
expect(pages[i].querySelector('a').hasAttribute('tabindex')).toBeFalsy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePageSize(fixture: ComponentFixture<any>, pageSize: string): void {
|
||||||
|
let buttonEl = fixture.nativeElement.querySelector('#paginationControls');
|
||||||
|
let activatedRouteStub: ActivatedRouteStub;
|
||||||
|
let routerStub: RouterStub;
|
||||||
|
|
||||||
|
buttonEl.click();
|
||||||
|
|
||||||
|
let dropdownMenu = fixture.debugElement.query(By.css('#paginationControlsDropdownMenu'));
|
||||||
|
let buttons = dropdownMenu.nativeElement.querySelectorAll('button');
|
||||||
|
|
||||||
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
|
if (buttons[i].textContent.trim() == pageSize) {
|
||||||
|
buttons[i].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePage(fixture: ComponentFixture<any>, idx: number): void {
|
||||||
|
let de = fixture.debugElement.query(By.css('.pagination'));
|
||||||
|
let buttons = de.nativeElement.querySelectorAll('li');
|
||||||
|
|
||||||
|
buttons[idx].querySelector('a').click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(txt: string): string {
|
||||||
|
return txt.trim().replace(/\s+/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Pagination component', () => {
|
||||||
|
|
||||||
|
let fixture: ComponentFixture<PaginationComponent>;
|
||||||
|
let comp: PaginationComponent;
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
let de: DebugElement;
|
||||||
|
let html;
|
||||||
|
let hostWindowServiceStub: HostWindowServiceStub;
|
||||||
|
|
||||||
|
let activatedRouteStub: ActivatedRouteStub;
|
||||||
|
let routerStub: RouterStub;
|
||||||
|
|
||||||
|
//Define initial state and test state
|
||||||
|
let _initialState = { width: 1600, height: 770 };
|
||||||
|
|
||||||
|
// async beforeEach
|
||||||
|
beforeEach(async(() => {
|
||||||
|
activatedRouteStub = new ActivatedRouteStub();
|
||||||
|
routerStub = new RouterStub();
|
||||||
|
hostWindowServiceStub = new HostWindowServiceStub(_initialState.width);
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}), Ng2PaginationModule, NgbModule.forRoot(),
|
||||||
|
RouterTestingModule.withRoutes([
|
||||||
|
{path: 'home', component: TestComponent}
|
||||||
|
])],
|
||||||
|
declarations: [PaginationComponent, TestComponent], // declare the test component
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
|
{ provide: GLOBAL_CONFIG, useValue: EnvConfig },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
{ provide: HostWindowService, useValue: hostWindowServiceStub },
|
||||||
|
PaginationComponent
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
html = `
|
||||||
|
<ds-pagination #p="paginationComponent"
|
||||||
|
[paginationOptions]="paginationOptions"
|
||||||
|
[collectionSize]="collectionSize"
|
||||||
|
(pageChange)="pageChanged($event)"
|
||||||
|
(pageSizeChange)="pageSizeChanged($event)">
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let item of collection | paginate: { itemsPerPage: paginationOptions.pageSize,
|
||||||
|
currentPage: paginationOptions.currentPage, totalItems: collectionSize }"> {{item}} </li>
|
||||||
|
</ul>
|
||||||
|
</ds-pagination>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create Pagination Component', inject([PaginationComponent], (app: PaginationComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should render', () => {
|
||||||
|
expect(testComp.paginationOptions.id).toEqual('test');
|
||||||
|
expect(testComp.paginationOptions.currentPage).toEqual(1);
|
||||||
|
expect(testComp.paginationOptions.pageSize).toEqual(10);
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '» Next']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render and respond to page change', () => {
|
||||||
|
testComp.collectionSize = 30;
|
||||||
|
|
||||||
|
changePage(testFixture, 3);
|
||||||
|
expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']);
|
||||||
|
|
||||||
|
changePage(testFixture, 0);
|
||||||
|
expectPages(testFixture, ['« Previous', '1', '+2', '3', '» Next']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render and respond to collectionSize change', () => {
|
||||||
|
|
||||||
|
testComp.collectionSize = 30;
|
||||||
|
testFixture.detectChanges();
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '» Next']);
|
||||||
|
|
||||||
|
testComp.collectionSize = 40;
|
||||||
|
testFixture.detectChanges();
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '» Next']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render and respond to pageSize change', () => {
|
||||||
|
|
||||||
|
testComp.collectionSize = 30;
|
||||||
|
testFixture.detectChanges();
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '» Next']);
|
||||||
|
|
||||||
|
changePageSize(testFixture, '5');
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '6', '» Next']);
|
||||||
|
|
||||||
|
changePageSize(testFixture, '10');
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '» Next']);
|
||||||
|
|
||||||
|
changePageSize(testFixture, '20');
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '» Next']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit pageChange event with correct value', fakeAsync(() => {
|
||||||
|
|
||||||
|
spyOn(testComp, 'pageChanged');
|
||||||
|
|
||||||
|
changePage(testFixture, 3);
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(testComp.pageChanged).toHaveBeenCalledWith(3);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should emit pageSizeChange event with correct value', fakeAsync(() => {
|
||||||
|
|
||||||
|
spyOn(testComp, 'pageSizeChanged');
|
||||||
|
|
||||||
|
changePageSize(testFixture, '5');
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(testComp.pageSizeChanged).toHaveBeenCalledWith(5);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should set correct route parameters', fakeAsync(() => {
|
||||||
|
let paginationComponent: PaginationComponent = testFixture
|
||||||
|
.debugElement.query(By.css('ds-pagination')).references['p'];
|
||||||
|
routerStub = testFixture.debugElement.injector.get(Router);
|
||||||
|
|
||||||
|
testComp.collectionSize = 60;
|
||||||
|
|
||||||
|
changePage(testFixture, 3);
|
||||||
|
tick();
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith([{pageId: 'test', page: 3, pageSize: 10}]);
|
||||||
|
expect(paginationComponent.currentPage).toEqual(3);
|
||||||
|
|
||||||
|
changePageSize(testFixture, '20');
|
||||||
|
tick();
|
||||||
|
expect(routerStub.navigate).toHaveBeenCalledWith([{pageId: 'test', page: 3, pageSize: 20}]);
|
||||||
|
expect(paginationComponent.pageSize).toEqual(20);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should get parameters from route', () => {
|
||||||
|
|
||||||
|
activatedRouteStub = testFixture.debugElement.injector.get(ActivatedRoute);
|
||||||
|
activatedRouteStub.testParams = {
|
||||||
|
pageId: 'test',
|
||||||
|
page: 2,
|
||||||
|
pageSize: 20
|
||||||
|
};
|
||||||
|
|
||||||
|
testFixture.detectChanges();
|
||||||
|
|
||||||
|
expectPages(testFixture, ['« Previous', '1', '+2', '3', '4', '5', '» Next']);
|
||||||
|
expect(testComp.paginationOptions.currentPage).toEqual(2);
|
||||||
|
expect(testComp.paginationOptions.pageSize).toEqual(20);
|
||||||
|
|
||||||
|
activatedRouteStub.testParams = {
|
||||||
|
pageId: 'test',
|
||||||
|
page: 3,
|
||||||
|
pageSize: 40
|
||||||
|
};
|
||||||
|
|
||||||
|
testFixture.detectChanges();
|
||||||
|
|
||||||
|
expectPages(testFixture, ['« Previous', '1', '2', '+3', '-» Next']);
|
||||||
|
expect(testComp.paginationOptions.currentPage).toEqual(3);
|
||||||
|
expect(testComp.paginationOptions.pageSize).toEqual(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond to windows resize', () => {
|
||||||
|
let paginationComponent: PaginationComponent = testFixture
|
||||||
|
.debugElement.query(By.css('ds-pagination')).references['p'];
|
||||||
|
hostWindowServiceStub = testFixture.debugElement.injector.get(HostWindowService);
|
||||||
|
|
||||||
|
hostWindowServiceStub.setWidth(400);
|
||||||
|
|
||||||
|
hostWindowServiceStub.isXs().subscribe((status) => {
|
||||||
|
paginationComponent.isXs = status;
|
||||||
|
testFixture.detectChanges();
|
||||||
|
expectPages(testFixture, ['-« Previous', '+1', '2', '3', '4', '5', '-...', '10', '» Next']);
|
||||||
|
de = testFixture.debugElement.query(By.css('ul.pagination'));
|
||||||
|
expect(de.nativeElement.classList.contains("pagination-sm")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({selector: 'ds-test-cmp', template: ''})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
collection: string[] = [];
|
||||||
|
collectionSize: number;
|
||||||
|
paginationOptions = new PaginationOptions();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`);
|
||||||
|
this.collectionSize = 100;
|
||||||
|
this.paginationOptions.id = 'test';
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChanged(page) {
|
||||||
|
this.paginationOptions.currentPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSizeChanged(pageSize) {
|
||||||
|
this.paginationOptions.pageSize = pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare a stub service
|
||||||
|
class HostWindowServiceStub {
|
||||||
|
|
||||||
|
private width: number;
|
||||||
|
|
||||||
|
constructor(width) {
|
||||||
|
this.setWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidth(width) {
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
isXs(): Observable<boolean> {
|
||||||
|
return Observable.of(this.width < 576);
|
||||||
|
}
|
||||||
|
}
|
241
src/app/shared/pagination/pagination.component.ts
Normal file
241
src/app/shared/pagination/pagination.component.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
ViewEncapsulation
|
||||||
|
} from '@angular/core'
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { isNumeric } from "rxjs/util/isNumeric";
|
||||||
|
import 'rxjs/add/operator/switchMap';
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
// It is necessary to use ng2-pagination
|
||||||
|
import { DEFAULT_TEMPLATE, DEFAULT_STYLES } from 'ng2-pagination/dist/template';
|
||||||
|
|
||||||
|
import { HostWindowService } from "../host-window.service";
|
||||||
|
import { HostWindowState } from "../host-window.reducer";
|
||||||
|
import { PaginationOptions } from '../../core/cache/models/pagination-options.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default pagination controls component.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
exportAs: 'paginationComponent',
|
||||||
|
selector: 'ds-pagination',
|
||||||
|
templateUrl: 'pagination.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
|
encapsulation: ViewEncapsulation.Emulated
|
||||||
|
})
|
||||||
|
export class PaginationComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of items in collection.
|
||||||
|
*/
|
||||||
|
@Input() collectionSize: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for the NgbPagination component.
|
||||||
|
*/
|
||||||
|
@Input() paginationOptions: PaginationOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the page is changed.
|
||||||
|
* Event's payload equals to the newly selected page.
|
||||||
|
*/
|
||||||
|
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the page size is changed.
|
||||||
|
* Event's payload equals to the newly selected page size.
|
||||||
|
*/
|
||||||
|
@Output() pageSizeChange: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current page.
|
||||||
|
*/
|
||||||
|
public currentPage = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable of HostWindowState type
|
||||||
|
*/
|
||||||
|
public hostWindow: Observable<HostWindowState>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ID for the pagination instance. Only useful if you wish to
|
||||||
|
* have more than once instance at a time in a given component.
|
||||||
|
*/
|
||||||
|
private id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean that indicate if is an extra small devices viewport.
|
||||||
|
*/
|
||||||
|
public isXs: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of items per page.
|
||||||
|
*/
|
||||||
|
public pageSize: number = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A number array that represents options for a context pagination limit.
|
||||||
|
*/
|
||||||
|
private pageSizeOptions: Array<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local variable, which can be used in the template to access the paginate controls ngbDropdown methods and properties
|
||||||
|
*/
|
||||||
|
public paginationControls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber to observable.
|
||||||
|
*/
|
||||||
|
private routeSubscription: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that represents pagination details of the current viewed page
|
||||||
|
*/
|
||||||
|
public showingDetail: any = {
|
||||||
|
range: null,
|
||||||
|
total: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber to observable.
|
||||||
|
*/
|
||||||
|
private stateSubscription: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method provided by Angular. Invoked after the constructor.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.stateSubscription = this.hostWindowService.isXs()
|
||||||
|
.subscribe((status: boolean) => {
|
||||||
|
this.isXs = status;
|
||||||
|
});
|
||||||
|
this.checkConfig(this.paginationOptions);
|
||||||
|
this.id = this.paginationOptions.id || null;
|
||||||
|
this.currentPage = this.paginationOptions.currentPage;
|
||||||
|
this.pageSize = this.paginationOptions.pageSize;
|
||||||
|
this.pageSizeOptions = this.paginationOptions.pageSizeOptions;
|
||||||
|
|
||||||
|
this.routeSubscription = this.route.params
|
||||||
|
.map(params => params)
|
||||||
|
.subscribe(params => {
|
||||||
|
if(this.id == params['pageId']
|
||||||
|
&& (this.paginationOptions.currentPage != params['page']
|
||||||
|
|| this.paginationOptions.pageSize != params['pageSize'])
|
||||||
|
) {
|
||||||
|
this.validateParams(params['page'], params['pageSize']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.setShowingDetail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method provided by Angular. Invoked when the instance is destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.stateSubscription.unsubscribe();
|
||||||
|
this.routeSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param route
|
||||||
|
* Route is a singleton service provided by Angular.
|
||||||
|
* @param router
|
||||||
|
* Router is a singleton service provided by Angular.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
public hostWindowService: HostWindowService
|
||||||
|
){
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set set new page and update route parameters
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* The page being navigated to.
|
||||||
|
*/
|
||||||
|
public doPageChange(page: number) {
|
||||||
|
this.router.navigate([{ pageId: this.id, page: page, pageSize: this.pageSize }]);
|
||||||
|
this.currentPage = page;
|
||||||
|
this.setShowingDetail();
|
||||||
|
this.pageChange.emit(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set set new page size and update route parameters
|
||||||
|
*
|
||||||
|
* @param pageSize
|
||||||
|
* The new page size.
|
||||||
|
*/
|
||||||
|
public setPageSize(pageSize: number) {
|
||||||
|
this.router.navigate([{ pageId: this.id, page: this.currentPage, pageSize: pageSize }]);
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
this.setShowingDetail();
|
||||||
|
this.pageSizeChange.emit(pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set pagination details of the current viewed page.
|
||||||
|
*/
|
||||||
|
private setShowingDetail() {
|
||||||
|
let firstItem;
|
||||||
|
let lastItem;
|
||||||
|
let lastPage = Math.round(this.collectionSize / this.pageSize);
|
||||||
|
|
||||||
|
firstItem = this.pageSize * (this.currentPage - 1) + 1;
|
||||||
|
if (this.currentPage != lastPage) {
|
||||||
|
lastItem = this.pageSize * this.currentPage;
|
||||||
|
} else {
|
||||||
|
lastItem = this.collectionSize;
|
||||||
|
}
|
||||||
|
this.showingDetail = {
|
||||||
|
range: firstItem + ' - ' + lastItem,
|
||||||
|
total: this.collectionSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate query params
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* The page number to validate
|
||||||
|
* @param pageSize
|
||||||
|
* The page size to validate
|
||||||
|
*/
|
||||||
|
private validateParams(page: any, pageSize: any) {
|
||||||
|
let filteredPageSize = this.pageSizeOptions.find(x => x == pageSize);
|
||||||
|
if (!isNumeric(page) || !filteredPageSize) {
|
||||||
|
let filteredPage = isNumeric(page) ? page : this.currentPage;
|
||||||
|
filteredPageSize = (filteredPageSize) ? filteredPageSize : this.pageSize;
|
||||||
|
this.router.navigate([{ pageId: this.id, page: filteredPage, pageSize: filteredPageSize }]);
|
||||||
|
} else {
|
||||||
|
// (+) converts string to a number
|
||||||
|
this.currentPage = +page;
|
||||||
|
this.pageSize = +pageSize;
|
||||||
|
this.pageChange.emit(this.currentPage);
|
||||||
|
this.pageSizeChange.emit(this.pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure options passed contains the required properties.
|
||||||
|
*
|
||||||
|
* @param paginateOptions
|
||||||
|
* The paginate options object.
|
||||||
|
*/
|
||||||
|
private checkConfig(paginateOptions: any) {
|
||||||
|
let required = ['id', 'currentPage', 'pageSize', 'pageSizeOptions'];
|
||||||
|
let missing = required.filter(function (prop) { return !(prop in paginateOptions); });
|
||||||
|
if (0 < missing.length) {
|
||||||
|
throw new Error("Paginate: Argument is missing the following required properties: " + missing.join(', '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -3,13 +3,17 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Ng2PaginationModule } from 'ng2-pagination';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
|
import { PaginationComponent } from "./pagination/pagination.component";
|
||||||
import { FileSizePipe } from "./utils/file-size-pipe";
|
import { FileSizePipe } from "./utils/file-size-pipe";
|
||||||
import { ThumbnailComponent } from "../thumbnail/thumbnail.component";
|
import { ThumbnailComponent } from "../thumbnail/thumbnail.component";
|
||||||
import { SafeUrlPipe } from "./utils/safe-url-pipe";
|
import { SafeUrlPipe } from "./utils/safe-url-pipe";
|
||||||
|
import { HostWindowService } from "./host-window.service";
|
||||||
|
import { NativeWindowFactory, NativeWindowService } from "./window.service";
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -18,6 +22,7 @@ const MODULES = [
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
|
Ng2PaginationModule,
|
||||||
NgbModule
|
NgbModule
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -28,12 +33,15 @@ const PIPES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
ThumbnailComponent
|
|
||||||
// put shared components here
|
// put shared components here
|
||||||
|
PaginationComponent,
|
||||||
|
ThumbnailComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
ApiService
|
ApiService,
|
||||||
|
HostWindowService,
|
||||||
|
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -48,6 +56,9 @@ const PROVIDERS = [
|
|||||||
...MODULES,
|
...MODULES,
|
||||||
...PIPES,
|
...PIPES,
|
||||||
...COMPONENTS
|
...COMPONENTS
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
...PROVIDERS
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SharedModule {
|
export class SharedModule {
|
||||||
|
28
src/app/shared/testing/mock-store.ts
Normal file
28
src/app/shared/testing/mock-store.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
|
export class MockStore<T> extends BehaviorSubject<T> {
|
||||||
|
|
||||||
|
constructor(private _initialState: T) {
|
||||||
|
super(_initialState);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch = (action: Action): void => {
|
||||||
|
}
|
||||||
|
|
||||||
|
select = <R>(pathOrMapFn: any): Observable<T> => {
|
||||||
|
return Observable.of(this.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
nextState(_newState: T) {
|
||||||
|
this.next(_newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockAction implements Action {
|
||||||
|
type = null;
|
||||||
|
payload: {};
|
||||||
|
|
||||||
|
}
|
36
src/app/shared/testing/router-stubs.ts
Normal file
36
src/app/shared/testing/router-stubs.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Params } from "@angular/router";
|
||||||
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
export class RouterStub {
|
||||||
|
//noinspection TypeScriptUnresolvedFunction
|
||||||
|
navigate = jasmine.createSpy('navigate');
|
||||||
|
//navigate1: jasmine.createSpy('navigate');
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActivatedRouteStub {
|
||||||
|
|
||||||
|
// ActivatedRoute.params is Observable
|
||||||
|
private subject = new BehaviorSubject(this.testParams);
|
||||||
|
params = this.subject.asObservable();
|
||||||
|
|
||||||
|
constructor(params?: Params) {
|
||||||
|
if (params) {
|
||||||
|
this.testParams = params;
|
||||||
|
} else {
|
||||||
|
this.testParams = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test parameters
|
||||||
|
private _testParams: {};
|
||||||
|
get testParams() { return this._testParams; }
|
||||||
|
set testParams(params: {}) {
|
||||||
|
this._testParams = params;
|
||||||
|
this.subject.next(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivatedRoute.snapshot.params
|
||||||
|
get snapshot() {
|
||||||
|
return { params: this.testParams };
|
||||||
|
}
|
||||||
|
}
|
18
src/app/shared/window.service.ts
Normal file
18
src/app/shared/window.service.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { OpaqueToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const NativeWindowService = new OpaqueToken('NativeWindowService');
|
||||||
|
|
||||||
|
export class NativeWindowRef {
|
||||||
|
get nativeWindow (): any {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return window;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NativeWindowFactory() {
|
||||||
|
return new NativeWindowRef();
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user