From 60a9565c71e14f540337d1212ddac1a27b2f686e Mon Sep 17 00:00:00 2001 From: MichalKinas <113341662+MichalKinas@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:55:24 +0100 Subject: [PATCH] [ACS-9012] Add the option to truncate display value in text column (#10440) * [ACS-9012] Add the option to truncate disaplay value in text column * [ACS-9012] Use slice as substr is deprecated * [ACS-9012] Remove redundant param --- docs/core/components/data-column.component.md | 26 ++++++++ docs/core/pipes/truncate.pipe.md | 27 +++++++++ .../datatable-cell.component.spec.ts | 20 +++++++ .../datatable-cell.component.ts | 19 +++--- .../datatable/datatable.component.scss | 14 ++--- .../lib/datatable/data/data-column.model.ts | 1 + .../datatable/data/object-datacolumn.model.ts | 2 + lib/core/src/lib/pipes/pipe.module.ts | 4 +- lib/core/src/lib/pipes/truncate.pipe.spec.ts | 60 +++++++++++++++++++ lib/core/src/lib/pipes/truncate.pipe.ts | 28 +++++++++ .../lib/config/document-list.extensions.ts | 1 + 11 files changed, 182 insertions(+), 20 deletions(-) create mode 100644 docs/core/pipes/truncate.pipe.md create mode 100644 lib/core/src/lib/pipes/truncate.pipe.spec.ts create mode 100644 lib/core/src/lib/pipes/truncate.pipe.ts diff --git a/docs/core/components/data-column.component.md b/docs/core/components/data-column.component.md index 8ae1adb127..b7a7458ff0 100644 --- a/docs/core/components/data-column.component.md +++ b/docs/core/components/data-column.component.md @@ -65,6 +65,7 @@ Defines column properties for DataTable, Tasklist, Document List and other compo | currencyConfig | `CurrencyConfig` | [Default currency config](#default-currency-config) | Currency configuration to customize the formatting and display of currency values within the component. | | decimalConfig | `DecimalConfig` | [Default decimal config](#default-decimal-config) | Decimal configuration to customize the formatting and display of decimal values within the component. | | dateConfig | `DateConfig` | [Default date config](#default-date-config) | Date configuration to customize the formatting and localization of date values within the component. | +| maxTextLength | `number` | 250 | Max length of the text displayed in column before truncation. | ## Properties configuration @@ -467,6 +468,31 @@ const shouldPerformAction = this.columns if (shouldPerformAction) { /* action */} ``` +### Specifying max text length in the column + +If you want to restrict max number of characters displayed in the column you can use `maxTextLength` param. + +Example of using `maxTextLength` from a JSON config file: + +```json +[ + {"type": "text", "key": "id", "title": "Id"}, + {"type": "text", "key": "name", "title": "name", "maxTextLength": 100}, +] +``` + +HTML `` element example: + +```html +... + + + ... + +... +``` + + ## See also - [Document list component](../../content-services/components/document-list.component.md) diff --git a/docs/core/pipes/truncate.pipe.md b/docs/core/pipes/truncate.pipe.md new file mode 100644 index 0000000000..69fcba1595 --- /dev/null +++ b/docs/core/pipes/truncate.pipe.md @@ -0,0 +1,27 @@ +--- +Title: Truncate pipe +Added: v7.0.0-alpha.7 +Status: Active +Last reviewed: 2024-11-28 +--- + +# Truncate Pipe + +Truncates the text when it exceeds specified max length. It also appends provided ellipsis at the end of the string. + +## Basic Usage + + + +```HTML +
+ {{ textToTruncate | truncate:100:'***' }} +
+``` + + + +| Name | Type | Default value | Description | +| ------------- | -------- | ------------- | ---------------------------------------------------------------------- | +| maxTextLength | `number` | 250 | Max length of the text that should be displayed prio to truncating. | +| ellipsis | `string` | `...` | String which will be appended to the text when truncating will happen. | diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts index a6949ea319..7c8a6a83a1 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.spec.ts @@ -97,4 +97,24 @@ describe('DataTableCellComponent', () => { expect(component.row.obj).toBe('New Value'); }); + + it('should truncate display text and not truncate tooltip if configured on column', () => { + const row: DataRow = { + id: '1', + isSelected: false, + hasValue: () => true, + getValue: () => 'hello world', + obj: 'Initial Value', + cache: [] + }; + + component.data = new ObjectDataTableAdapter(mockCarsData, mockCarsSchemaDefinition); + component.column = { key: 'car_name', type: 'text', maxTextLength: 10 }; + component.row = row; + + fixture.detectChanges(); + + checkDisplayedText('hello worl...'); + checkDisplayedTooltip('hello world'); + }); }); diff --git a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts index 88d54426b5..2396bb777e 100644 --- a/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts +++ b/lib/core/src/lib/datatable/components/datatable-cell/datatable-cell.component.ts @@ -15,15 +15,7 @@ * limitations under the License. */ -import { - ChangeDetectionStrategy, - Component, - DestroyRef, - inject, - Input, - OnInit, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { DataColumn } from '../../data/data-column.model'; import { DataRow } from '../../data/data-row.model'; import { DataTableAdapter } from '../../data/datatable-adapter'; @@ -32,11 +24,12 @@ import { DataTableService } from '../../services/datatable.service'; import { CommonModule } from '@angular/common'; import { ClipboardDirective } from '../../../clipboard/clipboard.directive'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { TruncatePipe } from '../../../pipes/truncate.pipe'; @Component({ selector: 'adf-datatable-cell', standalone: true, - imports: [CommonModule, ClipboardDirective], + imports: [CommonModule, ClipboardDirective, TruncatePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: ` @@ -47,11 +40,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; [attr.aria-label]="value$ | async" [title]="tooltip" class="adf-datatable-cell-value" - >{{ value$ | async }}{{ column?.maxTextLength ? (value$ | async | truncate : column?.maxTextLength) : (value$ | async) }} - {{ value$ | async }} + {{ + column?.maxTextLength ? (value$ | async | truncate : column?.maxTextLength) : (value$ | async) + }} `, encapsulation: ViewEncapsulation.None, diff --git a/lib/core/src/lib/datatable/components/datatable/datatable.component.scss b/lib/core/src/lib/datatable/components/datatable/datatable.component.scss index ba5ae0d233..07a5dee7cb 100644 --- a/lib/core/src/lib/datatable/components/datatable/datatable.component.scss +++ b/lib/core/src/lib/datatable/components/datatable/datatable.component.scss @@ -325,6 +325,13 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default; justify-content: end; padding-right: 15px; + &:focus-visible, + &:focus-within { + .adf-datatable-hide-actions-without-hover { + visibility: visible; + } + } + &-provided { max-width: 100px !important; justify-content: center; @@ -359,13 +366,6 @@ $data-table-cell-min-width-file-size: $data-table-cell-min-width-1 !default; visibility: hidden; } - .adf-datatable-actions-menu:focus-visible, - .adf-datatable-actions-menu:focus-within { - .adf-datatable-hide-actions-without-hover { - visibility: visible; - } - } - .adf-datatable-cell--image { max-width: $data-table-thumbnail-width; } diff --git a/lib/core/src/lib/datatable/data/data-column.model.ts b/lib/core/src/lib/datatable/data/data-column.model.ts index 84fa8a86a7..594aed0a90 100644 --- a/lib/core/src/lib/datatable/data/data-column.model.ts +++ b/lib/core/src/lib/datatable/data/data-column.model.ts @@ -45,6 +45,7 @@ export interface DataColumn { currencyConfig?: CurrencyConfig; decimalConfig?: DecimalConfig; dateConfig?: DateConfig; + maxTextLength?: number; } export interface LocaleConfig { diff --git a/lib/core/src/lib/datatable/data/object-datacolumn.model.ts b/lib/core/src/lib/datatable/data/object-datacolumn.model.ts index abf6ec2aaa..ac1dd05364 100644 --- a/lib/core/src/lib/datatable/data/object-datacolumn.model.ts +++ b/lib/core/src/lib/datatable/data/object-datacolumn.model.ts @@ -43,6 +43,7 @@ export class ObjectDataColumn implements DataColumn { currencyConfig?: CurrencyConfig; decimalConfig?: DecimalConfig; dateConfig?: DateConfig; + maxTextLength?: number; constructor(input: any) { this.id = input.id ?? ''; @@ -67,5 +68,6 @@ export class ObjectDataColumn implements DataColumn { this.currencyConfig = input.currencyConfig; this.decimalConfig = input.decimalConfig; this.dateConfig = input.dateConfig; + this.maxTextLength = input.maxTextLength; } } diff --git a/lib/core/src/lib/pipes/pipe.module.ts b/lib/core/src/lib/pipes/pipe.module.ts index 092d0d82a3..c38977bab6 100644 --- a/lib/core/src/lib/pipes/pipe.module.ts +++ b/lib/core/src/lib/pipes/pipe.module.ts @@ -30,6 +30,7 @@ import { DecimalNumberPipe } from './decimal-number.pipe'; import { MomentDatePipe } from './moment-date.pipe'; import { MomentDateTimePipe } from './moment-datetime.pipe'; import { DateTimePipe } from './date-time.pipe'; +import { TruncatePipe } from './truncate.pipe'; export const CORE_PIPES = [ LocalizedDatePipe, @@ -44,7 +45,8 @@ export const CORE_PIPES = [ MomentDatePipe, MomentDateTimePipe, DateTimePipe, - InitialUsernamePipe + InitialUsernamePipe, + TruncatePipe ] as const; /** diff --git a/lib/core/src/lib/pipes/truncate.pipe.spec.ts b/lib/core/src/lib/pipes/truncate.pipe.spec.ts new file mode 100644 index 0000000000..b241a969e1 --- /dev/null +++ b/lib/core/src/lib/pipes/truncate.pipe.spec.ts @@ -0,0 +1,60 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TruncatePipe } from './truncate.pipe'; +import { TestBed } from '@angular/core/testing'; +import { CoreTestingModule } from '../testing/core.testing.module'; +import { Injector, runInInjectionContext } from '@angular/core'; + +describe('TruncatePipe', () => { + let pipe: TruncatePipe; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [CoreTestingModule] + }); + const injector = TestBed.inject(Injector); + runInInjectionContext(injector, () => { + pipe = new TruncatePipe(); + }); + }); + + it('should truncate texts longer than maxTextLength value', () => { + const text = 'This is a long text'; + expect(pipe.transform(text, 10)).toBe('This is a ...'); + }); + + it('should not truncate texts shorter than maxTextLength value', () => { + const text = 'Short text'; + expect(pipe.transform(text, 20)).toBe('Short text'); + }); + + it('should truncate texts longer than 250 chars and append "..." by default', () => { + let text = + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes,'; + text += ' nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim.'; + let truncatedString = + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, '; + truncatedString += 'nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium ...'; + expect(pipe.transform(text)).toBe(truncatedString); + }); + + it('should append custom ellipsis when ellipsis param is provided', () => { + const text = 'This is a long text'; + expect(pipe.transform(text, 10, '***')).toBe('This is a ***'); + }); +}); diff --git a/lib/core/src/lib/pipes/truncate.pipe.ts b/lib/core/src/lib/pipes/truncate.pipe.ts new file mode 100644 index 0000000000..309370e3c1 --- /dev/null +++ b/lib/core/src/lib/pipes/truncate.pipe.ts @@ -0,0 +1,28 @@ +/*! + * @license + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + standalone: true, + name: 'truncate' +}) +export class TruncatePipe implements PipeTransform { + transform(value: string, maxTextLength = 250, ellipsis = '...') { + return value.length > maxTextLength ? value.slice(0, maxTextLength) + ellipsis : value; + } +} diff --git a/lib/extensions/src/lib/config/document-list.extensions.ts b/lib/extensions/src/lib/config/document-list.extensions.ts index 432da644e3..89211b0193 100644 --- a/lib/extensions/src/lib/config/document-list.extensions.ts +++ b/lib/extensions/src/lib/config/document-list.extensions.ts @@ -51,4 +51,5 @@ export interface DocumentListPresetRef extends ExtensionElement { }; draggable?: boolean; resizable?: boolean; + maxTextLength?: number; }