From abe1d5c6c7e7385b30665a8640c2a6f0fc29f699 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh <“sufiyan.shaikh@4science.com”> Date: Tue, 26 Apr 2022 17:28:54 +0530 Subject: [PATCH] [DSC-516] Change truncable components in order to show more/less button --- ...-search-result-list-element.component.html | 14 ++- .../truncatable-part.component.html | 8 +- .../truncatable-part.component.scss | 10 ++ .../truncatable-part.component.spec.ts | 78 +++++++++++++++- .../truncatable-part.component.ts | 93 ++++++++++++++++++- .../truncatable/truncatable.component.html | 2 +- .../truncatable/truncatable.component.spec.ts | 11 --- .../truncatable/truncatable.component.ts | 21 +++-- src/assets/i18n/en.json5 | 4 + 9 files changed, 207 insertions(+), 34 deletions(-) diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html index fb0ad21b6e..40f837bcd1 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html @@ -7,13 +7,11 @@ class="lead" [innerHTML]="firstMetadataValue('organization.legalName')"> - - - - - - + + + + diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html index 76595c901f..34227e2583 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html @@ -1,5 +1,11 @@
-
+
+ + + {{ 'item.truncatable-part.show-less' | translate }}
diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss index e69de29bb2..7a1f31f578 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss @@ -0,0 +1,10 @@ +#dontBreakContent:not(.truncated) ~ label{ + display: none; + } + +a { + color: #207698 !important; + text-decoration: none !important; + background-color: transparent !important; + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts index 1b3cdad33e..09109ee400 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts @@ -4,10 +4,16 @@ import { TruncatablePartComponent } from './truncatable-part.component'; import { TruncatableService } from '../truncatable.service'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { getMockTranslateService } from '../../mocks/translate.service.mock'; +import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; +import { mockTruncatableService } from '../../mocks/mock-trucatable.service'; +import { By } from '@angular/platform-browser'; describe('TruncatablePartComponent', () => { let comp: TruncatablePartComponent; let fixture: ComponentFixture; + let translateService: TranslateService; const id1 = '123'; const id2 = '456'; @@ -22,8 +28,16 @@ describe('TruncatablePartComponent', () => { } }; beforeEach(waitForAsync(() => { + translateService = getMockTranslateService(); TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], + imports: [NoopAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], declarations: [TruncatablePartComponent], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, @@ -52,6 +66,11 @@ describe('TruncatablePartComponent', () => { it('lines should equal minlines', () => { expect((comp as any).lines).toEqual(comp.minLines.toString()); }); + + it('collapseButton should be hidden', () => { + const a = fixture.debugElement.query(By.css('#collapseButton')); + expect(a).toBeNull(); + }); }); describe('When the item is expanded', () => { @@ -72,5 +91,62 @@ describe('TruncatablePartComponent', () => { fixture.detectChanges(); expect((comp as any).lines).toEqual('none'); }); + + it('collapseButton should be shown', () => { + (comp as any).setLines(); + (comp as any).expandable = true; + fixture.detectChanges(); + const a = fixture.debugElement.query(By.css('#collapseButton')); + expect(a).not.toBeNull(); + }); }); }); + +describe('TruncatablePartComponent', () => { + let comp: TruncatablePartComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + const identifier = '1234567890'; + let truncatableService; + beforeEach(waitForAsync(() => { + translateService = getMockTranslateService(); + TestBed.configureTestingModule({ + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [TruncatablePartComponent], + providers: [ + { provide: TruncatableService, useValue: mockTruncatableService }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(TruncatablePartComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TruncatablePartComponent); + comp = fixture.componentInstance; // TruncatablePartComponent test instance + comp.id = identifier; + fixture.detectChanges(); + truncatableService = (comp as any).service; + }); + + describe('When toggle is called', () => { + beforeEach(() => { + spyOn(truncatableService, 'toggle'); + comp.toggle(); + }); + + it('should call toggle on the TruncatableService', () => { + expect(truncatableService.toggle).toHaveBeenCalledWith(identifier); + }); + }); + +}); diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 2a375e95d9..fe8279ee07 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -1,6 +1,8 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { TruncatableService } from '../truncatable.service'; import { hasValue } from '../../empty.util'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { NativeWindowRef, NativeWindowService } from 'src/app/core/services/window.service'; @Component({ selector: 'ds-truncatable-part', @@ -49,8 +51,34 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { * Subscription to unsubscribe from */ private sub; + /** + * store variable used for local to expand collapse + */ + expand = false; + /** + * variable to check if expandable + */ + expandable = false; + /** + * variable to check if it is a browser + */ + isBrowser: boolean; + /** + * variable which save get observer + */ + observer; + /** + * variable to save content to be observed + */ + observedContent; - public constructor(private service: TruncatableService) { + public constructor( + private service: TruncatableService, + @Inject(DOCUMENT) private document: any, + @Inject(NativeWindowService) private _window: NativeWindowRef, + @Inject(PLATFORM_ID) platformId: object + ) { + this.isBrowser = isPlatformBrowser(platformId); } /** @@ -67,12 +95,70 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => { if (collapsed) { this.lines = this.minLines.toString(); + this.expand = false; } else { this.lines = this.maxLines < 0 ? 'none' : this.maxLines.toString(); + this.expand = true; } }); } + ngAfterContentChecked() { + if (this.isBrowser) { + if (this.observer && this.observedContent) { + this.toUnobserve(); + } + this.toObserve(); + } + } + + /** + * Function to get data to be observed + */ + toObserve() { + this.observedContent = this.document.querySelectorAll('#dontBreakContent'); + this.observer = new (this._window.nativeWindow as any).ResizeObserver(entries => { + // tslint:disable-next-line:prefer-const + for (let entry of entries) { + if (!entry.target.classList.contains('notruncatable')) { + if (entry.target.scrollHeight > entry.contentRect.height) { + if (entry.target.children.length > 0) { + if (entry.target.children[0].offsetHeight > entry.contentRect.height) { + entry.target.classList.add('truncated'); + } else { + entry.target.classList.remove('truncated'); + } + } else { + entry.target.classList.add('truncated'); + } + } else { + entry.target.classList.remove('truncated'); + } + } + } + }); + this.observedContent.forEach(p => { + this.observer.observe(p); + }); + } + + /** + * Function to remove data which is observed + */ + toUnobserve() { + this.observedContent.forEach(p => { + this.observer.unobserve(p); + }); + } + + /** + * Expands the truncatable when it's collapsed, collapses it when it's expanded + */ + public toggle() { + this.service.toggle(this.id); + this.expandable = !this.expandable; + } + /** * Unsubscribe from the subscription */ @@ -80,5 +166,8 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { if (hasValue(this.sub)) { this.sub.unsubscribe(); } + if (this.isBrowser) { + this.toUnobserve(); + } } } diff --git a/src/app/shared/truncatable/truncatable.component.html b/src/app/shared/truncatable/truncatable.component.html index b524e5e754..342e79f638 100644 --- a/src/app/shared/truncatable/truncatable.component.html +++ b/src/app/shared/truncatable/truncatable.component.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/app/shared/truncatable/truncatable.component.spec.ts b/src/app/shared/truncatable/truncatable.component.spec.ts index b539ab0d56..29100e50d2 100644 --- a/src/app/shared/truncatable/truncatable.component.spec.ts +++ b/src/app/shared/truncatable/truncatable.component.spec.ts @@ -70,15 +70,4 @@ describe('TruncatableComponent', () => { }); }); - describe('When toggle is called', () => { - beforeEach(() => { - spyOn(truncatableService, 'toggle'); - comp.toggle(); - }); - - it('should call toggle on the TruncatableService', () => { - expect(truncatableService.toggle).toHaveBeenCalledWith(identifier); - }); - }); - }); diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index e22ce4441e..61ec9c422a 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -1,6 +1,4 @@ -import { - Component, Input -} from '@angular/core'; +import { AfterViewChecked, Component, ElementRef, Input, OnInit } from '@angular/core'; import { TruncatableService } from './truncatable.service'; @Component({ @@ -13,7 +11,7 @@ import { TruncatableService } from './truncatable.service'; /** * Component that represents a section with one or more truncatable parts that all listen to this state */ -export class TruncatableComponent { +export class TruncatableComponent implements OnInit, AfterViewChecked { /** * Is true when all truncatable parts in this truncatable should be expanded on loading */ @@ -29,7 +27,7 @@ export class TruncatableComponent { */ @Input() onHover = false; - public constructor(private service: TruncatableService) { + public constructor(private service: TruncatableService, private el: ElementRef,) { } /** @@ -61,11 +59,14 @@ export class TruncatableComponent { } } - /** - * Expands the truncatable when it's collapsed, collapses it when it's expanded - */ - public toggle() { - this.service.toggle(this.id); + ngAfterViewChecked() { + const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); + if (truncatedElements?.length > 1) { + for (let i = 0; i < (truncatedElements.length - 1); i++) { + truncatedElements[i].classList.remove('truncated'); + truncatedElements[i].classList.add('notruncatable'); + } + } } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c3c68a6882..12de3ffdea 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2056,6 +2056,10 @@ "item.search.title": "Item Search", + "item.truncatable-part.show-more": "Show more", + + "item.truncatable-part.show-less": "Collapse", + "item.page.abstract": "Abstract",