diff --git a/docs/content-services/components/tag-node-list.component.md b/docs/content-services/components/tag-node-list.component.md index b5aafc72ef..52b1dd753a 100644 --- a/docs/content-services/components/tag-node-list.component.md +++ b/docs/content-services/components/tag-node-list.component.md @@ -27,9 +27,23 @@ Shows tags for a node. | ---- | ---- | ------------- | ----------- | | nodeId | `string` | | The identifier of a node. | | showDelete | `boolean` | true | Show delete button | +| limitTagsDisplayed | `boolean` | false | Should limit number of tags displayed to as much as fits into container | ### Events | Name | Type | Description | | ---- | ---- | ----------- | | results | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when a tag is selected. | + +## Details +### Limit number of tags displayed initially + +To limit number of tags initially displayed set `limitTagsDisplayed` to `true`. + +```html + + +``` +Now when tag chips will exceed the size of the container number of displayed chips will be limited to as much as fits together with view more button. At least one tag will always be displayed, when one tag and view more button won't fit into one line the button will be displayed under the tag. diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index 40cbe3e0fd..2d98b19dcd 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -544,5 +544,8 @@ "NODE_COUNTER": { "SELECTED_COUNT": "{{ count }} selected" }, + "TAG_NODE_LIST": { + "VIEW_MORE": "View {{ count }} more" + }, "swsdp": "Sample: Web Site Design Project" } diff --git a/lib/content-services/src/lib/tag/tag-node-list.component.html b/lib/content-services/src/lib/tag/tag-node-list.component.html index 641115cb71..2482f8c015 100644 --- a/lib/content-services/src/lib/tag/tag-node-list.component.html +++ b/lib/content-services/src/lib/tag/tag-node-list.component.html @@ -1,9 +1,19 @@ - - - {{currentEntry.entry.tag}} - cancel - - - +
+ + + {{currentEntry.entry.tag}} + cancel + + + + +
diff --git a/lib/content-services/src/lib/tag/tag-node-list.component.scss b/lib/content-services/src/lib/tag/tag-node-list.component.scss index edc2a7e19c..059f14c698 100644 --- a/lib/content-services/src/lib/tag/tag-node-list.component.scss +++ b/lib/content-services/src/lib/tag/tag-node-list.component.scss @@ -1,7 +1,31 @@ .adf-tag-node-list { + display: flex; + align-items: center; + flex-direction: row; + width: inherit; + + &.adf-flex-column { + flex-direction: column; + } + + .adf-view-more-button { + margin: 4px; + color: var(--theme-text-color); + } + + .adf-full-width { + width: 100%; + } + + .adf-hidden-btn { + visibility: hidden; + } + .adf-tag-chips { color: var(--theme-primary-color-default-contrast); background-color: var(--theme-primary-color); + height: auto; + word-break: break-word; } .adf-tag-chips-delete { diff --git a/lib/content-services/src/lib/tag/tag-node-list.component.spec.ts b/lib/content-services/src/lib/tag/tag-node-list.component.spec.ts index a6e42b637f..0cc31e39f7 100644 --- a/lib/content-services/src/lib/tag/tag-node-list.component.spec.ts +++ b/lib/content-services/src/lib/tag/tag-node-list.component.spec.ts @@ -34,11 +34,17 @@ describe('TagNodeList', () => { skipCount: 0, maxItems: 100 }, - entries: [{ - entry: {tag: 'test1', id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'} - }, {entry: {tag: 'test2', id: 'fcb92659-1f10-41b4-9b17-851b72a3b597'}}, { - entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'} - }] + entries: [ + { + entry: {tag: 'test1', id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'} + }, + { + entry: {tag: 'test2', id: 'fcb92659-1f10-41b4-9b17-851b72a3b597'} + }, + { + entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'} + } + ] } }; @@ -62,14 +68,13 @@ describe('TagNodeList', () => { element = fixture.nativeElement; component = fixture.componentInstance; + component.nodeId = 'fake-node-id'; fixture.detectChanges(); }); describe('Rendering tests', () => { it('Tag list relative a single node should be rendered', async () => { - component.nodeId = 'fake-node-id'; - component.ngOnChanges(); fixture.detectChanges(); await fixture.whenStable(); @@ -84,8 +89,6 @@ describe('TagNodeList', () => { }); it('Tag list click on delete button should delete the tag', async () => { - component.nodeId = 'fake-node-id'; - spyOn(tagService, 'removeTag').and.returnValue(of(true)); component.ngOnChanges(); @@ -99,7 +102,6 @@ describe('TagNodeList', () => { }); it('Should not show the delete tag button if showDelete is false', async () => { - component.nodeId = 'fake-node-id'; component.showDelete = false; component.ngOnChanges(); @@ -111,7 +113,6 @@ describe('TagNodeList', () => { }); it('Should show the delete tag button if showDelete is true', async () => { - component.nodeId = 'fake-node-id'; component.showDelete = true; component.ngOnChanges(); @@ -121,5 +122,66 @@ describe('TagNodeList', () => { const deleteButton: any = element.querySelector('#tag_chips_delete_test1'); expect(deleteButton).not.toBeNull(); }); + + it('should not render view more button by default', async () => { + component.ngOnChanges(); + fixture.detectChanges(); + await fixture.whenStable(); + + const viewMoreButton = element.querySelector('.adf-view-more-button'); + const tagChips = element.querySelectorAll('.adf-tag-chips'); + expect(viewMoreButton).toBeNull(); + expect(tagChips.length).toBe(3); + }); + }); + + describe('Limit tags display', () => { + beforeEach(async () => { + component.limitTagsDisplayed = true; + element.style.maxWidth = '200px'; + component.tagsEntries = dataTag.list.entries; + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it('should render view more button when limiting is enabled', async () => { + component.ngOnChanges(); + fixture.detectChanges(); + await fixture.whenStable(); + const viewMoreButton = element.querySelector('.adf-view-more-button'); + const tagChips = element.querySelectorAll('.adf-tag-chips'); + expect(viewMoreButton).not.toBeNull(); + expect(tagChips.length).toBe(component.tagsEntries.length); + }); + + it('should not render view more button when limiting is enabled and all tags fits into container', async () => { + element.style.maxWidth = '800px'; + + component.ngOnChanges(); + fixture.detectChanges(); + await fixture.whenStable(); + + const viewMoreButton = element.querySelector('.adf-view-more-button'); + const tagChips = element.querySelectorAll('.adf-tag-chips'); + expect(viewMoreButton).toBeNull(); + expect(tagChips.length).toBe(3); + }); + + it('should display all tags when view more button is clicked', async () => { + component.ngOnChanges(); + fixture.detectChanges(); + await fixture.whenStable(); + + let viewMoreButton: HTMLButtonElement = element.querySelector('.adf-view-more-button'); + let tagChips = element.querySelectorAll('.adf-tag-chips'); + viewMoreButton.click(); + fixture.detectChanges(); + await fixture.whenStable(); + + viewMoreButton = element.querySelector('.adf-view-more-button'); + tagChips = element.querySelectorAll('.adf-tag-chips'); + expect(viewMoreButton).toBeNull(); + expect(tagChips.length).toBe(3); + }); }); }); diff --git a/lib/content-services/src/lib/tag/tag-node-list.component.ts b/lib/content-services/src/lib/tag/tag-node-list.component.ts index bf44939b78..d968cacbb4 100644 --- a/lib/content-services/src/lib/tag/tag-node-list.component.ts +++ b/lib/content-services/src/lib/tag/tag-node-list.component.ts @@ -15,11 +15,12 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit, ViewChild, ElementRef, ViewChildren, QueryList } from '@angular/core'; import { TagService } from './services/tag.service'; import { TagEntry } from '@alfresco/js-api'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { MatChip } from '@angular/material/chips'; /** * @@ -30,10 +31,10 @@ import { takeUntil } from 'rxjs/operators'; selector: 'adf-tag-node-list', templateUrl: './tag-node-list.component.html', styleUrls: ['./tag-node-list.component.scss'], - encapsulation: ViewEncapsulation.None, - host: { class: 'adf-tag-node-list' } + encapsulation: ViewEncapsulation.None }) export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit { + /* eslint no-underscore-dangle: ["error", { "allow": ["_elementRef"] }]*/ /** The identifier of a node. */ @Input() nodeId: string; @@ -42,7 +43,20 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit { @Input() showDelete = true; - tagsEntries: TagEntry[]; + /** Should limit number of tags displayed */ + @Input() + limitTagsDisplayed = false; + + @ViewChild('nodeListContainer') + containerView: ElementRef; + + @ViewChildren(MatChip) + tagChips: QueryList; + + tagsEntries: TagEntry[] = []; + calculationsDone = false; + columnFlexDirection = false; + undisplayedTagsCount = 0; /** Emitted when a tag is selected. */ @Output() @@ -66,6 +80,14 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit { this.tagService.refresh .pipe(takeUntil(this.onDestroy$)) .subscribe(() => this.refreshTag()); + + this.results + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + if (this.limitTagsDisplayed && this.tagsEntries.length > 0) { + this.calculateTagsToDisplay(); + } + }); } ngOnDestroy() { @@ -87,4 +109,37 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit { this.refreshTag(); }); } + + displayAllTags(event: Event): void { + event.preventDefault(); + event.stopPropagation(); + this.limitTagsDisplayed = false; + this.refreshTag(); + } + + private calculateTagsToDisplay() { + let tagsToDisplay = 1; + const containerWidth: number = this.containerView.nativeElement.clientWidth; + const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth; + const tagChipMargin = this.getTagChipMargin(this.tagChips.get(0)); + const tagChipsWidth: number = this.tagChips.reduce((acc, val, index) => { + if (containerWidth - viewMoreBtnWidth > acc + val._elementRef.nativeElement.offsetWidth) { + tagsToDisplay = index + 1; + } + return acc + val._elementRef.nativeElement.offsetWidth + tagChipMargin; + }, 0); + if ((containerWidth - tagChipsWidth) <= 0) { + this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth)); + this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay; + this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay); + } else { + this.limitTagsDisplayed = false; + } + this.calculationsDone = true; + } + + private getTagChipMargin(chip: MatChip): number { + const tagChipStyles = window.getComputedStyle(chip._elementRef.nativeElement); + return parseInt(tagChipStyles.marginLeft, 10) + parseInt(tagChipStyles.marginRight, 10); + } } diff --git a/lib/extensions/src/lib/components/dynamic-column/dynamic-column.component.ts b/lib/extensions/src/lib/components/dynamic-column/dynamic-column.component.ts index 195f954bfb..2eb35d021b 100644 --- a/lib/extensions/src/lib/components/dynamic-column/dynamic-column.component.ts +++ b/lib/extensions/src/lib/components/dynamic-column/dynamic-column.component.ts @@ -44,6 +44,7 @@ import { ExtensionService } from '../../services/extension.service'; .adf-dynamic-column { display: flex; align-items: center; + width: inherit; } ` ]