diff --git a/angular.json b/angular.json index 322146d8277..2ed562bf52d 100644 --- a/angular.json +++ b/angular.json @@ -112,6 +112,7 @@ "docs-cdk-clicked": "libs/docs/cdk/clicked", "docs-cdk-data-source": "libs/docs/cdk/data-source", "docs-cdk-disabled": "libs/docs/cdk/disabled", + "docs-cdk-dnd": "libs/docs/cdk/drag-n-drop", "docs-cdk-focusable-item": "libs/docs/cdk/focusable-item", "docs-cdk-focusable-list": "libs/docs/cdk/focusable-list", "docs-cdk-forms": "libs/docs/cdk/forms", diff --git a/apps/docs/src/app/cdk/cdk-documentation.routes.ts b/apps/docs/src/app/cdk/cdk-documentation.routes.ts index ee7325bbdaa..86e7d93271d 100644 --- a/apps/docs/src/app/cdk/cdk-documentation.routes.ts +++ b/apps/docs/src/app/cdk/cdk-documentation.routes.ts @@ -24,6 +24,10 @@ export const ROUTES: Routes = [ path: 'data-source', loadChildren: () => import('@fundamental-ngx/docs/cdk/data-source').then((m) => m.DataSourceDocsModule) }, + { + path: 'drag-n-drop', + loadChildren: () => import('@fundamental-ngx/docs/cdk/drag-n-drop').then((m) => m.DndDocsModule) + }, { path: 'focusable-item', loadChildren: () => diff --git a/apps/docs/src/app/cdk/documentation/cdk-documentation-data.ts b/apps/docs/src/app/cdk/documentation/cdk-documentation-data.ts index e945f692bc4..7b74af76335 100644 --- a/apps/docs/src/app/cdk/documentation/cdk-documentation-data.ts +++ b/apps/docs/src/app/cdk/documentation/cdk-documentation-data.ts @@ -16,6 +16,10 @@ export const directives: SectionInterfaceContent[] = [ url: 'cdk/data-source', name: 'Data Source' }, + { + url: 'cdk/drag-n-drop', + name: 'Drag&Drop' + }, { url: 'cdk/focusable-list', name: 'Focusable List' diff --git a/apps/docs/src/app/platform/documentation/platform-documentation-data.ts b/apps/docs/src/app/platform/documentation/platform-documentation-data.ts index dfbcaba3889..81a1c141079 100644 --- a/apps/docs/src/app/platform/documentation/platform-documentation-data.ts +++ b/apps/docs/src/app/platform/documentation/platform-documentation-data.ts @@ -32,7 +32,18 @@ export const components: SectionInterfaceContent[] = [ { url: 'platform/search-field', name: 'Search Field' }, { url: 'platform/smart-filter-bar', name: 'Smart Filter Bar' }, { url: 'platform/split-menu-button', name: 'Split Menu Button' }, - { url: 'platform/table', name: 'Table' }, + { + url: 'platform/table', + name: 'Table', + subItems: [ + { url: 'platform/table/basic', name: 'Basic examples' }, + { url: 'platform/table/p13-dialog-table', name: 'Personalization dialog' }, + { url: 'platform/table/settings-dialog-table', name: 'Settings dialog' }, + { url: 'platform/table/row-selection', name: 'Row selection' }, + { url: 'platform/table/scrolling', name: 'Scrolling options' }, + { url: 'platform/table/clickable-rows', name: 'Clickable rows' } + ] + }, { url: 'platform/textarea', name: 'Textarea' }, { url: 'platform/thumbnail', name: 'Thumbnail' }, { url: 'platform/time-picker', name: 'Time Picker' }, diff --git a/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.spec.ts b/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.spec.ts index 0a6d80bb4f4..ba1e23c1ac1 100644 --- a/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.spec.ts +++ b/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.spec.ts @@ -93,11 +93,14 @@ describe('DndListDirective', () => { directive.items = [...component.list]; (directive as any)._closestItemIndex = 1; + (directive as any)._closestItemPosition = 'after'; directive.dragEnd(3); expect(directive.itemDropped.emit).toHaveBeenCalledWith({ replacedItemIndex: 1, draggedItemIndex: 3, - items: ['item1', 'item4', 'item2', 'item3'] + items: ['item1', 'item4', 'item2', 'item3'], + insertAt: 'after', + mode: 'shift' }); expect((directive as any)._removeAllLines).toHaveBeenCalled(); diff --git a/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.ts b/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.ts index 39d44433205..99cba06694d 100644 --- a/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.ts +++ b/libs/cdk/src/lib/utils/drag-and-drop/dnd-list/dnd-list.directive.ts @@ -5,6 +5,7 @@ import { ElementRef, EventEmitter, forwardRef, + HostBinding, Input, OnDestroy, Output, @@ -12,7 +13,7 @@ import { } from '@angular/core'; import { merge, Subject } from 'rxjs'; import { startWith, takeUntil } from 'rxjs/operators'; -import { ElementChord, FdDropEvent, LinkPosition, ElementPosition, DndItem } from '../dnd.interfaces'; +import { ElementChord, FdDropEvent, LinkPosition, ElementPosition, DndItem, FdDndDropType } from '../dnd.interfaces'; import { DND_ITEM, DND_LIST } from '../tokens'; @Directive({ @@ -21,15 +22,51 @@ import { DND_ITEM, DND_LIST } from '../tokens'; }) export class DndListDirective implements AfterContentInit, OnDestroy { /** - * Defines if the the element is allowed to be dragged in 2 dimensions, + * Defines if the element is allowed to be dragged in 2 dimensions, * When true - replace indicator will be displayed vertically */ @Input() gridMode = false; - /** When enabled, replace indicator will appear on whole element, instead of horizontal/vertical line before/after element */ + /** + * Defines drop strategy: + * * `shift` mode will create line after closest drop element. + * * `group` mode will create replace indicator on whole closest drop element. + * * `auto` mode will create line after closest drop element, + * if dragged element coordinates are shifted for 30% from center of the closest drop element. + * Otherwise, it will create replace indicator on whole closest drop element. + * + * `shift` mode is the default. + */ @Input() - replaceMode = false; + dropMode: FdDndDropType = 'shift'; + + /** + * Threshold of dragged item over another item to define which type of `dropMode` should be used. + */ + @Input() + threshold = 0.3; + + /** + * @deprecated + * Use `dropMode` property for better configuration. + * + * @description + * When enabled, replace indicator will appear on whole element, instead of horizontal/vertical line before/after element. + */ + @Input() + set replaceMode(value: boolean) { + this._replaceMode = value; + this.dropMode = value ? 'group' : 'shift'; + this._detectedDropMode = this.dropMode; + } + + get replaceMode(): boolean { + return this._replaceMode; + } + + /** @hidden */ + private _replaceMode = false; /** Array of items, that will be sorted */ @Input() @@ -75,6 +112,19 @@ export class DndListDirective implements AfterContentInit, OnDestroy { /** @hidden */ private _draggable = true; + /** @hidden */ + @HostBinding('class') + private readonly _initialClass = 'fd-dnd-list'; + + /** @hidden */ + private _detectedDropMode: 'shift' | 'group'; + + /** @hidden */ + private _linesRemoved = true; + + /** @hidden */ + private _indicatorsRemoved = true; + /** @hidden */ ngAfterContentInit(): void { this._changeDraggableState(this._draggable); @@ -109,8 +159,13 @@ export class DndListDirective implements AfterContentInit, OnDestroy { closestItemIndex = null; } - /** If the closest element is different than the old one, new one is picked. It prevents from performance issues */ - if ((closestItemIndex || closestItemIndex === 0) && closestItemIndex !== this._closestItemIndex) { + /** If the closest element is different from the old one, new one is picked. It prevents from performance issues */ + if ( + (closestItemIndex || closestItemIndex === 0) && + (closestItemIndex !== this._closestItemIndex || this.dropMode === 'auto') + ) { + this._removeAllLines(); + this._removeAllReplaceIndicators(); this._closestItemIndex = closestItemIndex; this._closestItemPosition = this._elementsCoordinates[closestItemIndex].position; // If closest item index is same as dragged item, just remove indicators @@ -120,10 +175,12 @@ export class DndListDirective implements AfterContentInit, OnDestroy { return; } /** Generating line, that shows where the element will be placed, on drop */ - if (this.replaceMode) { + if (this.dropMode === 'group') { this._createReplaceIndicator(this._closestItemIndex); - } else { + } else if (this.dropMode === 'shift') { this._createLine(this._closestItemIndex, this._closestItemPosition); + } else { + this._selectDropModeIndicator(draggedItemIndex, closestItem, closestItemIndex); } } } @@ -131,7 +188,7 @@ export class DndListDirective implements AfterContentInit, OnDestroy { /** Method called, when element is started to be dragged */ dragStart(index: number): void { const draggedItemElement = this._dndItemReference[index].elementRef; - /** Counting all of the elements's chords */ + /** Counting all the element's chords */ this._elementsCoordinates = this._dndItemReference.map((item: DndItem) => item.getElementCoordinates(this._isBefore(draggedItemElement, item.elementRef), this.gridMode) ); @@ -164,7 +221,9 @@ export class DndListDirective implements AfterContentInit, OnDestroy { this.itemDropped.emit({ replacedItemIndex, draggedItemIndex, - items + items, + insertAt: this._closestItemPosition, + mode: this.dropMode !== 'auto' ? this.dropMode : this._detectedDropMode }); this._removeAllLines(); @@ -177,25 +236,70 @@ export class DndListDirective implements AfterContentInit, OnDestroy { } } + /** @hidden */ + private _selectDropModeIndicator( + draggedItemIndex: number, + closestItem: ElementChord | undefined, + closestItemIndex: number + ): void { + if (!closestItem || !this._dndItemReference[draggedItemIndex]) { + return; + } + + let newDetectedDropMode: 'shift' | 'group'; + const draggedElmCoords = + this._dndItemReference[draggedItemIndex].elementRef.nativeElement.getBoundingClientRect(); + + const closestItemBoundaries = getElementBoundaries(closestItem, this.threshold); + const draggedItemStartCoords = getElementStartCoords(draggedElmCoords, closestItem.position); + + if ( + _between(draggedItemStartCoords.x, closestItemBoundaries.x.start, closestItemBoundaries.x.end) && + _between(draggedItemStartCoords.y, closestItemBoundaries.y.start, closestItemBoundaries.y.end) + ) { + newDetectedDropMode = 'group'; + } else { + newDetectedDropMode = 'shift'; + } + + if (newDetectedDropMode === this._detectedDropMode && (!this._linesRemoved || !this._indicatorsRemoved)) { + return; + } + + this._detectedDropMode = newDetectedDropMode; + + if (this._detectedDropMode === 'shift') { + this._createLine(closestItemIndex, this._elementsCoordinates[closestItemIndex].position); + } else { + this._createReplaceIndicator(closestItemIndex); + } + } + /** @hidden */ private _removeAllLines(): void { + this._linesRemoved = true; this.dndItems.forEach((item) => item.removeLine()); } /** @hidden */ private _removeAllReplaceIndicators(): void { + this._indicatorsRemoved = true; this.dndItems.forEach((item) => item.removeReplaceIndicator()); } /** @hidden */ private _createLine(closestItemIndex: number, linkPosition: LinkPosition): void { this._removeAllLines(); + this._removeAllReplaceIndicators(); + this._linesRemoved = false; this._dndItemReference[closestItemIndex].createLine(linkPosition, this.gridMode); } /** @hidden */ private _createReplaceIndicator(closestItemIndex: number): void { + this._removeAllLines(); this._removeAllReplaceIndicators(); + this._indicatorsRemoved = false; this._dndItemReference[closestItemIndex].createReplaceIndicator(); } @@ -263,3 +367,40 @@ function _isMouseOnElement(element: ElementChord, mousePosition: ElementPosition function _between(x: number, min: number, max: number): boolean { return x >= min && x <= max; } + +interface ElementBoundaries { + x: { + start: number; + end: number; + }; + y: { + start: number; + end: number; + }; +} + +function getElementStartCoords(rect: DOMRect, position: ElementChord['position']): { x: number; y: number } { + return { + x: position === 'after' ? rect.x + rect.width : rect.x, + y: position === 'after' ? rect.y + rect.height : rect.y + }; +} + +function getElementBoundaries(coordinates: ElementChord, threshold: number): ElementBoundaries { + const widthOffset = coordinates.width * (coordinates.position === 'after' ? 1 : -1); + const heightOffset = coordinates.height * (coordinates.position === 'after' ? 1 : -1); + const xStart = coordinates.position === 'after' ? coordinates.x : coordinates.x + coordinates.width; + const xEnd = xStart + widthOffset + (widthOffset / 2) * threshold; + const yStart = coordinates.position === 'after' ? coordinates.y : coordinates.y + coordinates.height; + const yEnd = yStart + heightOffset + (heightOffset / 2) * threshold; + return { + x: { + start: xStart > xEnd ? xEnd : xStart, + end: xStart > xEnd ? xStart : xEnd + }, + y: { + start: yStart > yEnd ? yEnd : yStart, + end: yStart > yEnd ? yStart : yEnd + } + }; +} diff --git a/libs/cdk/src/lib/utils/drag-and-drop/dnd.interfaces.ts b/libs/cdk/src/lib/utils/drag-and-drop/dnd.interfaces.ts index 89ab59bc9f2..c3e9c52884e 100644 --- a/libs/cdk/src/lib/utils/drag-and-drop/dnd.interfaces.ts +++ b/libs/cdk/src/lib/utils/drag-and-drop/dnd.interfaces.ts @@ -16,6 +16,8 @@ export interface FdDropEvent { items: Array; replacedItemIndex: number; draggedItemIndex: number; + insertAt: 'before' | 'after' | null; + mode: FdDndDropEventMode; } export interface ElementPosition { @@ -36,3 +38,6 @@ export interface DndItem { listDraggable: boolean; changeCDKDragState: () => void; } + +export type FdDndDropType = 'shift' | 'group' | 'auto'; +export type FdDndDropEventMode = 'shift' | 'group'; diff --git a/libs/cdk/src/lib/utils/drag-and-drop/drag-and-drop.scss b/libs/cdk/src/lib/utils/drag-and-drop/drag-and-drop.scss index 8b5e3d8021c..2a78942c7c9 100644 --- a/libs/cdk/src/lib/utils/drag-and-drop/drag-and-drop.scss +++ b/libs/cdk/src/lib/utils/drag-and-drop/drag-and-drop.scss @@ -138,3 +138,7 @@ } } } + +.fd-dnd-list { + position: relative; +} diff --git a/libs/docs/cdk/drag-n-drop/dnd-docs.component.html b/libs/docs/cdk/drag-n-drop/dnd-docs.component.html new file mode 100644 index 00000000000..0f0f50d7eb0 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/dnd-docs.component.html @@ -0,0 +1,32 @@ +Basic example + +

+ Drag & Drop container should be decorated with fdkDndList directive, which accepts + [items] input property with the array of draggable items. +

+

Developers can define type of drop action by specifying [dropMode] input property:

+
    +
  • + shift will highlight element that's being currently hovered with dragged item with the line, + indicating that dragged item will be placed before or after the hovered element. +
  • +
  • + group will highlight element that's being currently hovered with dragged item by changing + element's background, indicating that dragged item will replace currently hovered element. This options is + suitable for grouping scenarios, when dropping item on another item should create a group, where dropped + item will become a child item of hovered one. +
  • +
  • + auto will combine both approaches described above, and will select appropriate action depending + on the coordinates of dragged item. By default it will apply group mode, if percentage of the + dragged element area which hovers over the hovered item is less than a [threshold] percentage. + Developers can specify the threshold percentage of dragged item over another item to switch between + group and shift modes. +
  • +
+

Each draggable item element should be decorated with fdkDndItem directive.

+
+ + + + diff --git a/libs/docs/cdk/drag-n-drop/dnd-docs.component.ts b/libs/docs/cdk/drag-n-drop/dnd-docs.component.ts new file mode 100644 index 00000000000..81488ad0965 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/dnd-docs.component.ts @@ -0,0 +1,53 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; + +const defaultExampleHtml = 'default-example/default-example.component.html'; +const defaultExampleTs = 'default-example/default-example.component.ts'; + +const diExampleHtml = 'di-example/di-example.component.html'; +const diExampleTs = 'di-example/di-example.component.ts'; +const diRecipientExampleTs = 'di-example/disabled-recipient.directive.ts'; + +@Component({ + selector: 'app-dnd', + templateUrl: './dnd-docs.component.html', + encapsulation: ViewEncapsulation.None +}) +export class DndDocsComponent { + defaultExample: ExampleFile[] = [ + { + code: getAssetFromModuleAssets(defaultExampleHtml), + language: 'html', + fileName: 'disabled-default-example', + component: 'fdkDisabledDefaultExample' + }, + { + code: getAssetFromModuleAssets(defaultExampleTs), + language: 'ts', + fileName: 'disabled-default-example', + component: 'fdkDisabledDefaultExample' + } + ]; + diExample: ExampleFile[] = [ + { + code: getAssetFromModuleAssets(diExampleHtml), + language: 'html', + fileName: 'disabled-di-example', + component: 'fdkDisabledDIExample' + }, + { + code: getAssetFromModuleAssets(diExampleTs), + language: 'ts', + fileName: 'disabled-di-example', + component: 'fdkDisabledDIExample' + }, + { + code: getAssetFromModuleAssets(diRecipientExampleTs), + language: 'ts', + fileName: 'disabled-recipient.directive', + component: 'fdkDisabledRecipientDirective' + } + ]; + + constructor() {} +} diff --git a/libs/docs/cdk/drag-n-drop/dnd-docs.module.ts b/libs/docs/cdk/drag-n-drop/dnd-docs.module.ts new file mode 100644 index 00000000000..4f040cbd990 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/dnd-docs.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { DisabledBehaviorModule, DragAndDropModule } from '@fundamental-ngx/cdk/utils'; +import { DndHeaderComponent } from './dnd-header/dnd-header.component'; +import { ApiComponent, currentComponentProvider, SharedDocumentationPageModule } from '@fundamental-ngx/docs/shared'; +import { API_FILES } from '@fundamental-ngx/docs/fn/shared'; +import { DndDocsComponent } from './dnd-docs.component'; +import { DefaultExampleComponent } from './examples/default-example/default-example.component'; + +const routes: Routes = [ + { + path: '', + component: DndHeaderComponent, + children: [ + { + path: '', + component: DndDocsComponent + }, + { path: 'api', component: ApiComponent, data: { content: API_FILES.tabs } } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes), SharedDocumentationPageModule, DisabledBehaviorModule, DragAndDropModule], + exports: [RouterModule], + declarations: [DndHeaderComponent, DndDocsComponent, DefaultExampleComponent], + providers: [currentComponentProvider('drag-n-drop')] +}) +export class DndDocsModule {} diff --git a/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.html b/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.html new file mode 100644 index 00000000000..63322633d77 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.html @@ -0,0 +1,8 @@ +
Drag&Drop directive
+ +

Drag&Drop directive provides you with a way to easily and declaratively create drag-and-drop interfaces.

+
+ + + + diff --git a/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.ts b/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.ts new file mode 100644 index 00000000000..5c670a28505 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/dnd-header/dnd-header.component.ts @@ -0,0 +1,6 @@ +import { Component } from '@angular/core'; + +@Component({ + templateUrl: './dnd-header.component.html' +}) +export class DndHeaderComponent {} diff --git a/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.html b/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.html new file mode 100644 index 00000000000..2f096949369 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.html @@ -0,0 +1,41 @@ +

[dropMode]="'shift'":

+ +

+ [dropMode]="'group'" +

+ + +

+ [dropMode]="'auto'" +

+ + + +
+ +
+ List item {{ item.name }} +
+
+
+
diff --git a/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.ts b/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.ts new file mode 100644 index 00000000000..43ff518524f --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/examples/default-example/default-example.component.ts @@ -0,0 +1,219 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewEncapsulation, inject } from '@angular/core'; +import { FdDndDropEventMode, FdDndDropType, FdDropEvent } from '@fundamental-ngx/cdk'; + +@Component({ + selector: 'fundamental-ngx-cdk-disabled-example', + templateUrl: './default-example.component.html', + styleUrls: ['../../../../../cdk/src/lib/utils/drag-and-drop/drag-and-drop.scss'], + styles: [ + ` + .fdk-sortable-list__item { + --fdItemShift: 0rem; + box-sizing: border-box; + display: block; + width: 20rem; + max-width: 100%; + background: var(--sapList_Background); + padding: 1rem 0.8rem; + padding-left: calc(0.8rem + var(--fdItemShift)); + border-bottom: var(--sapList_BorderWidth) solid var(--sapList_BorderColor); + } + + .fdk-sortable-list__item.fd-dnd-on-drag { + z-index: 10000; + opacity: 0.9; + } + ` + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DefaultExampleComponent { + private _cdr = inject(ChangeDetectorRef); + + values = generateItems(); + values2 = generateItems(); + values3 = generateItems(); + constructor() {} + + onItemDropped(event: FdDropEvent, property: string): void { + const values = this[property] as ListItem[]; + const draggedItem = values[event.draggedItemIndex]; + const droppedItem = values[event.replacedItemIndex]; + + if (event.mode === 'group') { + this._handleReplaceDropAction(draggedItem, droppedItem, event, property); + } else { + this._handleShiftDropAction(draggedItem, droppedItem, event, property); + } + + this._dragDropUpdateDropItemAttributes(draggedItem, droppedItem, event.mode, property); + + this._cdr.detectChanges(); + } + + /** @hidden */ + private _handleShiftDropAction( + dragItem: ListItem, + dropItem: ListItem, + event: FdDropEvent, + property: string + ): void { + const { allItems, itemsToMove, itemsAfterDropItem, dropItemItems } = this._getNewDragDropItemsPosition( + dragItem, + dropItem, + property + ); + + this[property] = [ + ...allItems, + ...(event.insertAt === 'after' ? dropItemItems : []), + ...itemsToMove, + ...(event.insertAt === 'after' ? [] : dropItemItems), + ...itemsAfterDropItem + ]; + } + + /** @hidden */ + private _handleReplaceDropAction( + dragItem: ListItem, + dropItem: ListItem, + event: FdDropEvent, + property: string + ): void { + const { allItems, itemsToMove, itemsAfterDropItem, dropItemItems } = this._getNewDragDropItemsPosition( + dragItem, + dropItem, + property + ); + + this[property] = [...allItems, ...dropItemItems, ...itemsToMove, ...itemsAfterDropItem]; + } + + private _createGroup(event: FdDropEvent, property: string): ListItem[] { + const items = this[property] as ListItem[]; + const droppedItem = items[event.draggedItemIndex]; + const droppedOnItem = items[event.replacedItemIndex]; + droppedItem.parent = droppedOnItem; + droppedOnItem.children.push(droppedItem); + + return items; + } + + private _getNewDragDropItemsPosition( + dragItem: ListItem, + dropItem: ListItem, + property: string + ): UpdatedDndItemsPosition { + const allItems = this[property]; + + const dragItemIndex = allItems.findIndex((item) => item === dragItem); + const dragItemChildren = this._findItemChildren(dragItem, property); + + const itemsToMove = allItems.splice(dragItemIndex, dragItemChildren.length + 1); + + const dropItemIndex = allItems.findIndex((item) => item === dropItem); + const dropItemChildren = this._findItemChildren(dropItem, property); + + const dropItemItemsLength = dropItemChildren.length + 1; + + const itemsAfterDropItem = allItems.splice( + dropItemIndex + dropItemItemsLength, + allItems.length + dropItemItemsLength + ); + const dropItemItems = allItems.splice(dropItemIndex, dropItemItemsLength); + + return { + allItems, + itemsToMove, + itemsAfterDropItem, + dropItemItems + }; + } + + /** @hidden */ + private _findItemChildren(item: ListItem, property: string): ListItem[] { + const allItems = this[property]; + const itemsLength = allItems.length; + + /** + * Since we are dealing with a flat list + * it means all children go next right after the expandable item + * until the next item has a mutual parent + */ + + let index = allItems.indexOf(item); + const parents = this._getItemParents(item, property); + const children: ListItem[] = []; + + while (index++ < itemsLength) { + const nextItem = allItems[index]; + if (!nextItem?.parent || parents.includes(nextItem.parent)) { + break; + } + children.push(nextItem); + } + + return children; + } + + private _getItemParents(item: ListItem, property: string): ListItem[] { + const parents: ListItem[] = []; + let parent = item.parent || null; + while (parent && parent !== null) { + parents.push(parent); + parent = parent.parent || null; + } + return parents; + } + + private _dragDropUpdateDropItemAttributes( + dragItem: ListItem, + dropItem: ListItem, + mode: FdDndDropEventMode, + property: string + ): void { + if (dragItem.parent) { + // Remove child item from previous parent item. + dragItem.parent.children.splice(dragItem.parent.children.indexOf(dragItem), 1); + } + dragItem.level = dropItem.level + (mode === 'group' ? 1 : 0); + + if (mode === 'group') { + dragItem.parent = dropItem; + dropItem.children.push(dragItem); + } else { + dragItem.parent = dropItem.parent; + dropItem.parent?.children.push(dragItem); + } + + const children = this._findItemChildren(dragItem, property); + children.forEach((item) => { + item.level = this._getItemParents(item, property).length; + }); + } +} + +interface UpdatedDndItemsPosition { + allItems: ListItem[]; + itemsToMove: ListItem[]; + itemsAfterDropItem: ListItem[]; + dropItemItems: ListItem[]; +} + +interface ListItem { + name: string; + parent: null | ListItem; + level: number; + children: ListItem[]; +} + +const generateItems = (length = 10): ListItem[] => + Array.from(Array(length)).map( + (_, index): ListItem => ({ + name: `List item ${index + 1}`, + parent: null, + children: [], + level: 0 + }) + ); diff --git a/libs/docs/cdk/drag-n-drop/index.ts b/libs/docs/cdk/drag-n-drop/index.ts new file mode 100644 index 00000000000..b0e83d9a7cd --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/index.ts @@ -0,0 +1 @@ +export * from './dnd-docs.module'; diff --git a/libs/docs/cdk/drag-n-drop/project.json b/libs/docs/cdk/drag-n-drop/project.json new file mode 100644 index 00000000000..f57be698443 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/project.json @@ -0,0 +1,18 @@ +{ + "name": "docs-cdk-dnd", + "sourceRoot": "libs/docs/cdk/drag-n-drop", + "projectType": "library", + "prefix": "fdp-doc", + "targets": { + "e2e-noop": { + "executor": "@fundamental-ngx/nx-plugin:e2e-test", + "options": { + "e2eTsConfig": "libs/docs/cdk/drag-n-drop/e2e/tsconfig.json", + "baseUrl": "http://localhost:4200", + "configFile": "libs/docs/cdk/drag-n-drop/wdio.conf.js" + }, + "outputs": ["{workspaceRoot}/allure-results/docs-cdk-dnd"] + } + }, + "tags": ["type:lib", "scope:docs"] +} diff --git a/libs/docs/cdk/drag-n-drop/tsconfig.json b/libs/docs/cdk/drag-n-drop/tsconfig.json new file mode 100644 index 00000000000..b1e2f763d54 --- /dev/null +++ b/libs/docs/cdk/drag-n-drop/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": ["**/*.ts"], + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": true, + "target": "es2022", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts"] +} diff --git a/libs/docs/core/list/examples/list-dnd-example/list-dnd-example.component.ts b/libs/docs/core/list/examples/list-dnd-example/list-dnd-example.component.ts index 56144f35974..d1e3a871b8d 100644 --- a/libs/docs/core/list/examples/list-dnd-example/list-dnd-example.component.ts +++ b/libs/docs/core/list/examples/list-dnd-example/list-dnd-example.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { Component, ViewEncapsulation } from '@angular/core'; @Component({ selector: 'fd-list-dnd-example', + encapsulation: ViewEncapsulation.None, templateUrl: './list-dnd-example.component.html' }) export class ListDndExampleComponent { diff --git a/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.html b/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.html new file mode 100644 index 00000000000..111388cf138 --- /dev/null +++ b/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.html @@ -0,0 +1,34 @@ + Activable Rows + +

This example shows activable rows

+ +
    +
  • Use [rowsActivable]="true" input property on the table to make rows activable.
  • +
  • Event (rowActivate) is emitted when row gets activated.
  • +
+
+ + + + + + + + Navigatable rows + + This example shows navigatable rows. + +
    +
  • + Use [rowNavigatable]="true" input property on the table to make rows navigatable. Pass boolean + to change navigatable state for all rows. Pass string with the field of the rows which will be used to + calculate row's navigatable state. +
  • +
  • Use [highlightNavigatedRow]="true" to highlight currently navigated row.
  • +
  • Event (rowNavigate) is emitted when row gets activated.
  • +
+
+ + + + diff --git a/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.ts b/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.ts new file mode 100644 index 00000000000..baf2eddab19 --- /dev/null +++ b/libs/docs/platform/table/child-docs/clickable-rows/clickable-rows-docs.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExampleChildService, ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; +import { PlatformTableActivableExampleComponent } from '../../examples/platform-table-activable-example.component'; + +const platformTableActivableRowSrc = 'platform-table-activable-example.component.html'; +const platformTableActivableRowTsSrc = 'platform-table-activable-example.component.ts'; + +const platformTableNavigatableRowSrc = 'platform-table-navigatable-row-indicator-example.component.html'; +const platformTableNavigatableRowTsSrc = 'platform-table-navigatable-row-indicator-example.component.ts'; + +@Component({ + selector: 'fd-clickable-rows-docs', + templateUrl: './clickable-rows-docs.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ClickableRowsDocsComponent { + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); + navigatableRowFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableNavigatableRowSrc), + fileName: 'platform-table-navigatable-row-indicator-example', + name: 'platform-table-navigatable-row-indicator-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableNavigatableRowTsSrc), + fileName: 'platform-table-navigatable-row-indicator-example', + component: 'PlatformTableNavigatableRowIndicatorExampleComponent', + name: 'platform-table-navigatable-row-indicator-example.component.ts' + } + ]; + + activableRowFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableActivableRowSrc), + fileName: 'platform-table-activable-example', + name: 'platform-table-activable-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableActivableRowTsSrc), + fileName: 'platform-table-activable-example', + component: 'PlatformTableActivableExampleComponent', + name: 'platform-table-activable-example.component.ts' + } + ]; + + constructor() { + this.childService.setLink(this.route.snapshot.routeConfig?.path); + } +} diff --git a/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.html b/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.html new file mode 100644 index 00000000000..e1e0bead52e --- /dev/null +++ b/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.html @@ -0,0 +1,95 @@ + Personalization Dialog. + + The p13n dialog control provides a dialog for tables that allows the user to personalize one or more of the + following attributes: +
    +
  • Columns (visibility and order of columns)
  • +
  • Sorting by multiple columns
  • +
  • Filtering by multiple columns
  • +
  • Grouping by multiple columns
  • +
+
+ + + Table columns visibility and order + + + To make columns settings available connect + <fdp-table-p13-dialog [table]="table"></fdp-table-p13-dialog> to a table. Once there is at + least one fdp-column definition the columns settings button will be available in the table toolbar. + + + + + + + + + Sorting by multiple columns + +
    +
  • + Connect your table to p13 columns dialog by adding + <fdp-table-p13-dialog + [table]="table"><fdp-table-p13n-sort></fdp-table-p13n-sort></fdp-table-p13-dialog> +
  • +
  • Add <fdp-column [sortable]="true"> option to mark a column as sortable.
  • +
+ The order of the criteria is exactly the same as the order in which sorting is applied to the table. +
+ + + + + + + + + Filtering by multiple columns + + +
    +
  • + Connect your table to p13 filtering dialog by adding + <fdp-table-p13-dialog + [table]="table"><fdp-table-p13n-filter></fdp-table-p13n-filter></fdp-table-p13-dialog> +
  • +
  • Add <fdp-column [filterable]="true"> option to mark a column as filterable.
  • +
  • + Add column data type <fdp-column dataType="string | number | date | boolean"> + to show corresponding filtering ui. +
  • +
+ Please note once the fdp-table-p13n-filter is connected to a table the filter option is not available + from column header dialog menu. +
+ + + + + + + + Grouping by multiple columns + +
    +
  • + Connect your table to p13 group dialog by adding + <fdp-table-p13-dialog + [table]="table"><fdp-table-p13n-group></fdp-table-p13n-group></fdp-table-p13-dialog> +
  • +
  • Add <fdp-column [groupable]="true"> option to mark a column as groupable.
  • +
+
+ + + + + + diff --git a/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.ts b/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.ts new file mode 100644 index 00000000000..54e650b25fa --- /dev/null +++ b/libs/docs/platform/table/child-docs/p13-dialog/p13-dialog-docs.component.ts @@ -0,0 +1,88 @@ +import { Component, inject, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExampleChildService, ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; +const platformTableP13ColumnSrc = 'platform-table-p13-columns-example.component.html'; +const platformTableP13ColumnTsSrc = 'platform-table-p13-columns-example.component.ts'; +const platformTableP13SortSrc = 'platform-table-p13-sort-example.component.html'; +const platformTableP13SortTsSrc = 'platform-table-p13-sort-example.component.ts'; +const platformTableP13FilterSrc = 'platform-table-p13-filter-example.component.html'; +const platformTableP13FilterTsSrc = 'platform-table-p13-filter-example.component.ts'; +const platformTableP13GroupSrc = 'platform-table-p13-group-example.component.html'; +const platformTableP13GroupTsSrc = 'platform-table-p13-group-example.component.ts'; + +@Component({ + selector: 'fdp-doc-p13-dialog-docs', + templateUrl: './p13-dialog-docs.component.html', + encapsulation: ViewEncapsulation.None +}) +export class P13DialogDocsComponent { + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); + p13ColumnsFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableP13ColumnSrc), + fileName: 'platform-table-p13-columns-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableP13ColumnTsSrc), + fileName: 'platform-table-p13-columns-example', + component: 'PlatformTableP13ColumnsExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + p13SortFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableP13SortSrc), + fileName: 'platform-table-p13-sort-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableP13SortTsSrc), + fileName: 'platform-table-p13-sort-example', + component: 'PlatformTableP13SortExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + p13FilterFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableP13FilterSrc), + fileName: 'platform-table-p13-filter-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableP13FilterTsSrc), + fileName: 'platform-table-p13-filter-example', + component: 'PlatformTableP13FilterExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + p13GroupFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableP13GroupSrc), + fileName: 'platform-table-p13-group-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableP13GroupTsSrc), + fileName: 'platform-table-p13-group-example', + component: 'PlatformTableP13GroupExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + constructor() { + this.childService.setLink(this.route.snapshot.routeConfig?.path); + } +} diff --git a/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.html b/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.html new file mode 100644 index 00000000000..c14fccf7951 --- /dev/null +++ b/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.html @@ -0,0 +1,45 @@ + Single Row Selection + +

This example shows single row selection

+ +

+ If both selection and search is used in a table, use rowComparator in order to preserve selection + after search is changed +

+ +

+ Consider that in the table with selection column the overall columns width cannot be less than table container. +

+
+ + + + + + + + + Multiple Row Selection + + +

This example shows multiple row selection

+ +

+ Consider that in the table with selection column present the overall columns width cannot be less than table + container. +

+ +

+ You can have preselected rows by providing selectedKey input property for the table with field name + in row object. Any truthy value will be considered as selected. +

+ +

+ You also can disable selecting for some rows by providing selectableKey input property for the + table with field name in row object. Only false value disables selection. +

+
+ + + + diff --git a/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.ts b/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.ts new file mode 100644 index 00000000000..fa5fe216fed --- /dev/null +++ b/libs/docs/platform/table/child-docs/row-selection/row-selection-docs.component.ts @@ -0,0 +1,52 @@ +import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExampleChildService, ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; + +const platformTableSingleRowSelectionSrc = 'platform-table-single-row-selection-example.component.html'; +const platformTableSingleRowSelectionTsSrc = 'platform-table-single-row-selection-example.component.ts'; +const platformTableMultipleRowSelectionSrc = 'platform-table-multiple-row-selection-example.component.html'; +const platformTableMultipleRowSelectionTsSrc = 'platform-table-multiple-row-selection-example.component.ts'; +@Component({ + selector: 'fd-row-selection-docs', + templateUrl: './row-selection-docs.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RowSelectionDocsComponent { + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); + singleRowSelectionFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableSingleRowSelectionSrc), + fileName: 'platform-table-single-row-selection-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableSingleRowSelectionTsSrc), + fileName: 'platform-table-single-row-selection-example', + component: 'PlatformTableSingleRowSelectionExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + multipleRowSelectionFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableMultipleRowSelectionSrc), + fileName: 'platform-table-multiple-row-selection-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableMultipleRowSelectionTsSrc), + fileName: 'platform-table-multiple-row-selection-example', + component: 'PlatformTableMultipleRowSelectionExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + constructor() { + this.childService.setLink(this.route.snapshot.routeConfig?.path); + } +} diff --git a/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.html b/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.html new file mode 100644 index 00000000000..e3ef6e057ba --- /dev/null +++ b/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.html @@ -0,0 +1,48 @@ + Page Scrolling + + In order to get table page scrolling use the next options: +
    +
  • Add [pageScrolling]="true" to trigger a fetch once the table is scrolled to the bottom.
  • +
  • + Add [bodyHeight]="tableBodyHeight" to restrict a table body height so the vertical scrollbar + can appear. +
  • +
  • Add [pageSize]="itemsPerPage" to specify how many items there are per page.
  • +
  • + Add [pageScrollingThreshold]="thresholdInPx" to indicate the threshold between the height of + the remaining content and the need to load new data. +
  • +
+ Consider that columns width automatically calculates only after the first set of data comes to the component. +
+ + + + + + + + Virtual Scroll +

+ When dealing with large amounts of data, it is recommended to use virtual scrolling to improve performance. To + enable Virtual Scroll, use the following properties: +

+ +
    +
  • [virtualScroll]="true" - enables virtual scroll
  • +
  • [bodyHeight]="value" - sets the height of the table body (viewport)
  • +
  • + [rowHeight]="value" - sets the height of the table row if it is different from default for the + current content density +
  • +
+ +

+ Additionally, you may set the renderAhead input property to specify how many rows should be rendered + ahead of the current viewport. Default is 20. +

+ + + + + diff --git a/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.ts b/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.ts new file mode 100644 index 00000000000..c8f8db6024d --- /dev/null +++ b/libs/docs/platform/table/child-docs/scrolling/table-scrolling-docs.component.ts @@ -0,0 +1,52 @@ +import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExampleChildService, ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; + +const platformTablePageScrollingSrc = 'platform-table-page-scrolling-example.component.html'; +const platformTablePageScrollingTsSrc = 'platform-table-page-scrolling-example.component.ts'; +const platformVirtualScrollTableDefaultSrc = 'virtual-scroll/platform-table-virtual-scroll-example.component.html'; +const platformVirtualScrollTableDefaultTsSrc = 'virtual-scroll/platform-table-virtual-scroll-example.component.ts'; +@Component({ + selector: 'fd-table-scrolling-docs', + templateUrl: './table-scrolling-docs.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TableScrollingDocsComponent { + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); + pageScrollingTableFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTablePageScrollingSrc), + fileName: 'platform-table-page-scrolling-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTablePageScrollingTsSrc), + fileName: 'platform-table-page-scrolling-example', + component: 'PlatformTablePageScrollingExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + virtualScrollTableFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformVirtualScrollTableDefaultSrc), + fileName: 'platform-table-virtual-scroll-example', + name: 'platform-table-virtual-scroll-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformVirtualScrollTableDefaultTsSrc), + fileName: 'platform-table-virtual-scroll-example', + component: 'PlatformTableVirtualScrollExampleComponent', + name: 'platform-table-virtual-scroll-example.component.ts' + } + ]; + constructor() { + this.childService.setLink(this.route.snapshot.routeConfig?.path); + } +} diff --git a/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.html b/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.html new file mode 100644 index 00000000000..9525a37b8e7 --- /dev/null +++ b/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.html @@ -0,0 +1,53 @@ + Column Sorting + +

Use [sortable]="true" to mark a column as an sortable one.

+

+ Developers can restrict selecting "Not Sorted" option by providing + [allowDisablingSorting]="false" input property for the + fdp-table-view-settings-dialog component and providing + [initialSortBy] input property of the table. +

+

In this example Name, Description and Price columns can be sorted.

+
+ + + + + + + + Column Filtering + + There are two ways to add filtering. The first one is to use the column option [filterable]="true". + This will add filter input field in the column header menu. +
+ The second one is to use fdp-table-view-settings-filter withing + fdp-table-view-settings-dialog. It gives ability to define filtering type. For now it supports the next + types: +
    +
  • single-select
  • +
  • multi-select
  • +
  • custom
  • +
+
+ + + + + + + + Column Grouping + + Use [groupable]="true" to mark a column as one that can be used to group by. +
+ Once there is at least one a groupable column the Group dialog option appears in the table toolbar. To enable + grouping by some column you can use the column header menu or the Group dialog. In order to ungroup you should use + only the Group dialog. +
+ In this example Name and Status columns can be used to group by. +
+ + + + diff --git a/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.ts b/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.ts new file mode 100644 index 00000000000..0affa0822e2 --- /dev/null +++ b/libs/docs/platform/table/child-docs/settings-dialog/settings-dialog-docs.component.ts @@ -0,0 +1,70 @@ +import { ChangeDetectionStrategy, Component, inject, ViewEncapsulation } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ExampleChildService, ExampleFile, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; + +const platformTableSortableSrc = 'platform-table-sortable-example.component.html'; +const platformTableSortableTsSrc = 'platform-table-sortable-example.component.ts'; +const platformTableGroupableSrc = 'platform-table-groupable-example.component.html'; +const platformTableGroupableTsSrc = 'platform-table-groupable-example.component.ts'; +const platformTableFilterableSrc = 'platform-table-filterable-example.component.html'; +const platformTableFilterableTsSrc = 'platform-table-filterable-example.component.ts'; +@Component({ + selector: 'fd-settings-dialog-docs', + templateUrl: './settings-dialog-docs.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SettingsDialogDocsComponent { + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); + sortableTableFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableSortableSrc), + fileName: 'platform-table-sortable-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableSortableTsSrc), + fileName: 'platform-table-sortable-example', + component: 'PlatformTableSortableExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + filterableTableFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableFilterableSrc), + fileName: 'platform-table-filterable-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableFilterableTsSrc), + fileName: 'platform-table-filterable-example', + component: 'PlatformTableFilterableExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + + groupableTableFiles: ExampleFile[] = [ + { + language: 'html', + code: getAssetFromModuleAssets(platformTableGroupableSrc), + fileName: 'platform-table-groupable-example', + name: 'platform-table-example.component.html' + }, + { + language: 'typescript', + code: getAssetFromModuleAssets(platformTableGroupableTsSrc), + fileName: 'platform-table-groupable-example', + component: 'PlatformTableGroupableExampleComponent', + name: 'platform-table-example.component.ts' + } + ]; + constructor() { + this.childService.setLink(this.route.snapshot.routeConfig?.path); + } +} diff --git a/libs/docs/platform/table/e2e/table-clickable-rows.e2e-spec.ts b/libs/docs/platform/table/e2e/table-clickable-rows.e2e-spec.ts new file mode 100644 index 00000000000..05280290fcb --- /dev/null +++ b/libs/docs/platform/table/e2e/table-clickable-rows.e2e-spec.ts @@ -0,0 +1,63 @@ +import { runCommonTests } from './table-common-tests'; +import { TablePo } from './table.po'; +import { + browserIsSafari, + checkElArrIsClickable, + click, + getElementClass, + refreshPage, + scrollIntoView, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { tableCellArr } from './table-contents'; + +describe('Table component test suite', () => { + const tablePage = new TablePo('/table/clickable-rows'); + const { button, tableRow, tableActivableExample, tableNavigatableRowIndicatorExample, allInputFields } = tablePage; + + beforeAll(async () => { + await tablePage.open(); + }, 1); + + afterEach(async () => { + await refreshPage(); + await waitForPresent(tablePage.root); + await waitForElDisplayed(tablePage.title); + }, 1); + + if (browserIsSafari()) { + // skip due to unknown error where browser closes halfway through the test + return; + } + + describe('Check Activable Rows', () => { + it('should check alert messages', async () => { + await tablePage.checkAlertMessages(tableActivableExample); + }); + + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableActivableExample, tableCellArr); + }); + + it('should check activable row', async () => { + await scrollIntoView(tableActivableExample); + await expect(await getElementClass(tableActivableExample + tableRow)).toContain('fd-table__row--activable'); + }); + }); + + describe('Check Navigatable rows', () => { + it('should check example', async () => { + await scrollIntoView(tableNavigatableRowIndicatorExample); + await click(tableNavigatableRowIndicatorExample + button); + await checkElArrIsClickable(tableNavigatableRowIndicatorExample + tableRow); + + await click(tableNavigatableRowIndicatorExample + button, 1); + await expect(await getElementClass(tableNavigatableRowIndicatorExample + tableRow, 1)).toBe( + 'fd-table__row fd-table__row--main ng-star-inserted' + ); + }); + }); + + runCommonTests(allInputFields, tablePage); +}); diff --git a/libs/docs/platform/table/e2e/table-common-tests.ts b/libs/docs/platform/table/e2e/table-common-tests.ts new file mode 100644 index 00000000000..56e13888db1 --- /dev/null +++ b/libs/docs/platform/table/e2e/table-common-tests.ts @@ -0,0 +1,58 @@ +import { + checkLtrOrientation, + checkRtlOrientation, + click, + getElementArrayLength, + getElementSize, + scrollIntoView, + sendKeys, + waitForElDisplayed +} from '../../../../../e2e'; +import { TablePo } from './table.po'; + +export const runCommonTests = (allInputFields: string, tablePage: TablePo) => { + describe('Check input fields', () => { + it('should check input fields does not change width', async () => { + const inputFieldLength = await getElementArrayLength(allInputFields); + for (let i = 0; i < inputFieldLength; i++) { + if (i === 13) { + continue; + } + const beforeSize = await getElementSize(allInputFields, i); + await scrollIntoView(allInputFields, i); + await click(allInputFields, i); + await sendKeys('test'); + const afterSize = await getElementSize(allInputFields, i); + await expect(beforeSize).toEqual(afterSize); + } + }); + }); + + describe('Check orientation', () => { + it('should check RTL and LTR orientation', async () => { + const exampleAreaContainersArr = '.fd-doc-component'; + const rtlSwitcherArr = 'rtl-switch .fd-switch__handle'; + + const switcherLength = await getElementArrayLength(exampleAreaContainersArr); + for (let i = 0; i < switcherLength; i++) { + if (i === 13) { + continue; + } + await scrollIntoView(rtlSwitcherArr, i); + await click(rtlSwitcherArr, i); + await checkRtlOrientation(exampleAreaContainersArr, i); + await scrollIntoView(rtlSwitcherArr, i); + await click(rtlSwitcherArr, i); + await waitForElDisplayed(exampleAreaContainersArr, i); + await checkLtrOrientation(exampleAreaContainersArr, i); + } + }); + }); + + xdescribe('Check visual regression', () => { + it('should check examples visual regression', async () => { + await tablePage.saveExampleBaselineScreenshot(); + await expect(await tablePage.compareWithBaseline()).toBeLessThan(5); + }); + }); +}; diff --git a/libs/docs/platform/table/e2e/table-p13-dialog.e2e-spec.ts b/libs/docs/platform/table/e2e/table-p13-dialog.e2e-spec.ts new file mode 100644 index 00000000000..63d9bcc7037 --- /dev/null +++ b/libs/docs/platform/table/e2e/table-p13-dialog.e2e-spec.ts @@ -0,0 +1,206 @@ +import { runCommonTests } from './table-common-tests'; +import { TablePo } from './table.po'; +import { + browserIsSafari, + click, + doesItExist, + getAttributeByName, + getElementArrayLength, + getText, + isElementDisplayed, + refreshPage, + scrollIntoView, + setValue, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { + astroTestText, + nameEndTestText, + priceEndTestText, + priceStartTestText, + tableCellArr4, + tableCellArr5, + testText, + testText4, + testText5, + testText7 +} from './table-contents'; + +describe('Table component test suite', () => { + const tablePage = new TablePo('/table/p13-dialog-table'); + const { + tableRow, + tableCellText, + buttonSortedBy, + tableCellPrice, + tableCellName, + filterByColorItem, + tableP13ColumnsExample, + footerButtonOk, + columnHeader, + tableP13SortExample, + tableP13FilterExample, + tableP13GroupExample, + popoverDropdownButton, + buttonAdd, + buttonRemove, + dialogInput, + expandedButton, + dropdownList, + dropdownOption, + dialogButton, + allInputFields, + ellipsisButton, + expandedOption, + buttonFilter + } = tablePage; + + beforeAll(async () => { + await tablePage.open(); + }, 1); + + afterEach(async () => { + await refreshPage(); + await waitForPresent(tablePage.root); + await waitForElDisplayed(tablePage.title); + }, 1); + + if (browserIsSafari()) { + // skip due to unknown error where browser closes halfway through the test + return; + } + + describe('Check Table columns visibility and order', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableP13ColumnsExample, tableCellArr4); + }); + + it('should check searching and placeholder in dialog', async () => { + await tablePage.checkPlaceholder(tableP13ColumnsExample, 2); + await tablePage.checkSearchingInDialog(); + }); + + it('should check sorting of columns', async () => { + await tablePage.checkSortingColumns(tableP13ColumnsExample, ellipsisButton); + }); + }); + + describe('Check Sorting by multiple columns', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableP13SortExample, tableCellArr4); + }); + + it('should check sorting ascending and descending by name', async () => { + await scrollIntoView(tableP13SortExample); + await click(tableP13SortExample + ellipsisButton); + await click(popoverDropdownButton); + await click(buttonSortedBy); + await click(footerButtonOk); + await expect((await getText(tableP13SortExample + tableCellName)).trim()).toBe(testText); + await expect((await getText(tableP13SortExample + tableCellName, 15)).trim()).toBe(nameEndTestText); + + await click(tableP13SortExample + columnHeader); + await click(filterByColorItem, 1); + await expect((await getText(tableP13SortExample + tableCellName)).trim()).toBe(nameEndTestText); + await expect((await getText(tableP13SortExample + tableCellName, 15)).trim()).toBe(testText); + }); + + it('should check sorting ascending and descending by price', async () => { + await scrollIntoView(tableP13SortExample); + await click(tableP13SortExample + ellipsisButton); + await click(buttonAdd); + await click(buttonRemove); + await click(popoverDropdownButton); + await click(buttonSortedBy, 1); + await click(footerButtonOk); + + await expect((await getText(tableP13SortExample + tableCellPrice)).trim()).toBe(priceStartTestText); + await expect((await getText(tableP13SortExample + tableCellPrice, 15)).trim()).toBe(priceEndTestText); + + await click(tableP13SortExample + columnHeader, 2); + await click(filterByColorItem, 1); + await expect((await getText(tableP13SortExample + tableCellPrice)).trim()).toBe(priceEndTestText); + await expect((await getText(tableP13SortExample + tableCellPrice, 15)).trim()).toBe(priceStartTestText); + }); + + it('should check searching and placeholder in dialog', async () => { + await tablePage.checkPlaceholder(tableP13SortExample, 3); + }); + + it('should check sorting of columns', async () => { + await tablePage.checkSortingColumns(tableP13SortExample, ellipsisButton, 1); + }); + + it('should check impossible select columns multiple times', async () => { + await scrollIntoView(tableP13SortExample); + await click(tableP13SortExample + ellipsisButton); + await click(popoverDropdownButton); + await expect(await isElementDisplayed(dropdownList)).toBe(true); + await click(dropdownOption); + + await click(dialogButton, 1); + await click(popoverDropdownButton, 2); + await expect(await isElementDisplayed(dropdownList)).toBe(true); + await click(dropdownOption); + + await click(dialogButton, 3); + await click(popoverDropdownButton, 4); + await expect(await doesItExist(dropdownList)).toBe(false); + }); + }); + + describe('Check Filtering by multiple columns', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableP13FilterExample, tableCellArr5); + }); + + it('should check filtering with include and exclude', async () => { + await scrollIntoView(tableP13FilterExample); + await click(tableP13FilterExample + ellipsisButton); + await setValue(dialogInput, astroTestText); + await click(expandedButton, 1); + await click(popoverDropdownButton, 2); + await click(filterByColorItem, 1); + await setValue(dialogInput, testText7, 1); + await click(footerButtonOk); + + const rowLength = await getElementArrayLength(tableP13FilterExample + tableRow); + await expect(rowLength).toEqual(1); + await expect((await getText(tableP13FilterExample + tableRow + tableCellText)).trim()).toBe(testText4); + await expect((await getText(tableP13FilterExample + tableRow + tableCellText, 1)).trim()).toBe(testText5); + }); + + it('should check searching and placeholder in dialog', async () => { + await tablePage.checkPlaceholder(tableP13FilterExample, 3); + await tablePage.checkSearchingInDialog(); + }); + + it('should check sorting of columns', async () => { + await tablePage.checkSortingColumns(tableP13FilterExample, ellipsisButton, 1); + }); + // skipped due to https://github.com/SAP/fundamental-ngx/issues/7005 + xit('should check Exclude section in dialog always open', async () => { + await scrollIntoView(tableP13FilterExample); + await click(tableP13FilterExample + buttonFilter); + await expect(await getAttributeByName(expandedOption, 'aria-expanded')).toBe('false'); + }); + }); + + describe('Check Grouping by multiple columns', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableP13GroupExample, tableCellArr4); + }); + + it('should check searching and placeholder in dialog', async () => { + await tablePage.checkPlaceholder(tableP13GroupExample, 3); + await tablePage.checkSearchingInDialog(); + }); + + it('should check sorting of columns', async () => { + await tablePage.checkSortingColumns(tableP13GroupExample, ellipsisButton, 1); + }); + }); + + runCommonTests(allInputFields, tablePage); +}); diff --git a/libs/docs/platform/table/e2e/table-row-selection.e2e-spec.ts b/libs/docs/platform/table/e2e/table-row-selection.e2e-spec.ts new file mode 100644 index 00000000000..a62ff6a78db --- /dev/null +++ b/libs/docs/platform/table/e2e/table-row-selection.e2e-spec.ts @@ -0,0 +1,74 @@ +import { runCommonTests } from './table-common-tests'; +import { TablePo } from './table.po'; +import { + browserIsSafari, + click, + getAttributeByName, + refreshPage, + scrollIntoView, + setValue, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { tableCellArr } from './table-contents'; + +describe('Table component test suite', () => { + const tablePage = new TablePo('/table/row-selection'); + const { + button, + input, + tableRow, + tableSingleRowSelectionExample, + tableMultipleRowSelectionExample, + tableCell, + allInputFields + } = tablePage; + + beforeAll(async () => { + await tablePage.open(); + }, 1); + + afterEach(async () => { + await refreshPage(); + await waitForPresent(tablePage.root); + await waitForElDisplayed(tablePage.title); + }, 1); + + if (browserIsSafari()) { + // skip due to unknown error where browser closes halfway through the test + return; + } + + describe('Check Single Row Selection', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableSingleRowSelectionExample, tableCellArr); + }); + + it('should check table item single selection', async () => { + await scrollIntoView(tableSingleRowSelectionExample); + await click(tableSingleRowSelectionExample + tableRow + tableCell); + await expect(await getAttributeByName(tableSingleRowSelectionExample + tableRow, 'aria-selected')).toBe( + 'true' + ); + }); + + it('should check selected row not gets unselected', async () => { + await scrollIntoView(tableSingleRowSelectionExample); + await setValue(tableSingleRowSelectionExample + input, 'Astro'); + await click(tableSingleRowSelectionExample + button, 1); + await click(tableSingleRowSelectionExample + tableCell); + await click(tableSingleRowSelectionExample + button); + await expect(await getAttributeByName(tableSingleRowSelectionExample + tableRow, 'aria-selected', 1)).toBe( + 'true' + ); + }); + }); + + describe('Check Multi Row Selection', () => { + it('should verify checkboxes', async () => { + await tablePage.checkAllCheckbox(tableMultipleRowSelectionExample, true); + }); + }); + + runCommonTests(allInputFields, tablePage); +}); diff --git a/libs/docs/platform/table/e2e/table-scrolling.e2e-spec.ts b/libs/docs/platform/table/e2e/table-scrolling.e2e-spec.ts new file mode 100644 index 00000000000..b9d102a6741 --- /dev/null +++ b/libs/docs/platform/table/e2e/table-scrolling.e2e-spec.ts @@ -0,0 +1,78 @@ +import { runCommonTests } from './table-common-tests'; +import { TablePo } from './table.po'; +import { + browserIsSafari, + click, + doesItExist, + getElementArrayLength, + getText, + pause, + refreshPage, + scrollIntoView, + setValue, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { tableCellArr2, testText2 } from './table-contents'; + +describe('Table component test suite', () => { + const tablePage = new TablePo('/table/scrolling'); + const { + input, + tableRow, + tableCellText, + tableCellDescription, + tableCellName, + busyIndicator, + buttonSearch, + tablePageScrollingExample, + allInputFields + } = tablePage; + + beforeAll(async () => { + await tablePage.open(); + }, 1); + + afterEach(async () => { + await refreshPage(); + await waitForPresent(tablePage.root); + await waitForElDisplayed(tablePage.title); + }, 1); + + if (browserIsSafari()) { + // skip due to unknown error where browser closes halfway through the test + return; + } + + describe('Check Page Scrolling', () => { + it('should check table item single selection', async () => { + await scrollIntoView(tablePageScrollingExample); + await setValue(tablePageScrollingExample + input, testText2); + await click(tablePageScrollingExample + buttonSearch); + await expect(await doesItExist(busyIndicator)).toBe(true, "busy indicator isn't displayed"); + await pause(500); + await expect(await doesItExist(busyIndicator)).toBe(false, 'busy indicator is displayed'); + const rowLength = await getElementArrayLength(tablePageScrollingExample + tableRow); + await expect(rowLength).toEqual(1); + const cellLength = await getElementArrayLength(tablePageScrollingExample + tableRow + tableCellText); + for (let i = 0; i < cellLength; i++) { + await scrollIntoView(tablePageScrollingExample + tableRow + tableCellText, i); + await expect((await getText(tablePageScrollingExample + tableRow + tableCellText, i)).trim()).toBe( + tableCellArr2[i] + ); + } + }); + + it('should check scroll', async () => { + await scrollIntoView(tablePageScrollingExample); + await scrollIntoView(tablePageScrollingExample + tableRow, 40); + + await expect((await getText(tablePageScrollingExample + tableCellName, 40)).trim()).toBe('Product name 40'); + await expect((await getText(tablePageScrollingExample + tableCellDescription, 40)).trim()).toBe( + 'Product description goes here 40' + ); + }); + }); + + runCommonTests(allInputFields, tablePage); +}); diff --git a/libs/docs/platform/table/e2e/table-settings-dialog.e2e-spec.ts b/libs/docs/platform/table/e2e/table-settings-dialog.e2e-spec.ts new file mode 100644 index 00000000000..0dbf532a0f2 --- /dev/null +++ b/libs/docs/platform/table/e2e/table-settings-dialog.e2e-spec.ts @@ -0,0 +1,221 @@ +import { wait } from '@nrwl/nx-cloud/lib/utilities/waiter'; +import { runCommonTests } from './table-common-tests'; +import { TablePo } from './table.po'; +import { + browserIsSafari, + click, + doesItExist, + getElementArrayLength, + getText, + isElementClickable, + refreshPage, + scrollIntoView, + setValue, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { + descriptionEndTestText, + descriptionStartTestText, + massaTestText, + nameEndTestText, + nameStartTestText, + nuncTestText, + pharetraTestText, + priceEndTestText, + priceStartTestText, + tableCellArr, + groupTableCellArr +} from './table-contents'; + +describe('Table component test suite', () => { + const tablePage = new TablePo('/table/settings-dialog-table'); + const { + tableRow, + tableSortableExample, + buttonSortedBy, + barButton, + tableCellDescription, + tableCellPrice, + tableCellName, + buttonSortedOrder, + tableGroupableExample, + tableFilterableExample, + tableCellStatusColor, + tableCellStatus, + dialogFilters, + filterInput, + filterButtonOk, + filterResetButton, + allInputFields, + sortableIcon, + sortableOption, + sortablePopover, + ellipsisButton, + buttonFilter + } = tablePage; + + beforeAll(async () => { + await tablePage.open(); + }, 1); + + afterEach(async () => { + await refreshPage(); + await waitForPresent(tablePage.root); + await waitForElDisplayed(tablePage.title); + }, 1); + + if (browserIsSafari()) { + // skip due to unknown error where browser closes halfway through the test + return; + } + + describe('Check Column Sorting', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableSortableExample, tableCellArr); + }); + + it('should check ascending sorting by name, description and price', async () => { + await scrollIntoView(tableSortableExample); + await tablePage.chooseSortOptionBy(tableSortableExample, ellipsisButton, 2); + await expect((await getText(tableSortableExample + tableCellDescription)).trim()).toBe( + descriptionStartTestText + ); + await expect((await getText(tableSortableExample + tableCellDescription, 15)).trim()).toBe( + descriptionEndTestText + ); + + await tablePage.chooseSortOptionBy(tableSortableExample, ellipsisButton, 3); + await expect((await getText(tableSortableExample + tableCellPrice)).trim()).toBe(priceStartTestText); + await expect((await getText(tableSortableExample + tableCellPrice, 15)).trim()).toBe(priceEndTestText); + + await tablePage.chooseSortOptionBy(tableSortableExample, ellipsisButton, 1); + await expect((await getText(tableSortableExample + tableCellName)).trim()).toBe(nameStartTestText); + await expect((await getText(tableSortableExample + tableCellName, 15)).trim()).toBe(nameEndTestText); + }); + + it('should check descending sorting by name, description and price', async () => { + await scrollIntoView(tableSortableExample); + await click(tableSortableExample + ellipsisButton); + await click(buttonSortedOrder, 1); + await click(buttonSortedBy, 2); + await click(barButton); + await expect((await getText(tableSortableExample + tableCellDescription)).trim()).toBe( + descriptionEndTestText + ); + await expect((await getText(tableSortableExample + tableCellDescription, 15)).trim()).toBe( + descriptionStartTestText + ); + + await tablePage.chooseSortOptionBy(tableSortableExample, ellipsisButton, 3); + await expect((await getText(tableSortableExample + tableCellPrice)).trim()).toBe(priceEndTestText); + await expect((await getText(tableSortableExample + tableCellPrice, 15)).trim()).toBe(priceStartTestText); + + await tablePage.chooseSortOptionBy(tableSortableExample, ellipsisButton, 1); + await expect((await getText(tableSortableExample + tableCellName)).trim()).toBe(nameEndTestText); + await expect((await getText(tableSortableExample + tableCellName, 15)).trim()).toBe(nameStartTestText); + }); + + it('should check after selecting sorting option popover closed', async () => { + await scrollIntoView(tableSortableExample); + await click(sortableIcon); + await click(sortableOption); + await expect(await doesItExist(sortablePopover)).toBe(false, 'sortable popover still displayed'); + }); + }); + + describe('Check Column Filtering', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableFilterableExample, tableCellArr, 1); + }); + + it('should check filtering by status color', async () => { + await tablePage.chooseFilter(2, 1); + const rowLength = await getElementArrayLength(tableFilterableExample + tableRow); + for (let i = 0; i < rowLength; i++) { + await expect((await getText(tableCellStatusColor, i)).trim()).toBe('positive'); + } + + await tablePage.chooseFilter(2, 2); + const tableRowLength = await getElementArrayLength(tableFilterableExample + tableRow); + for (let i = 0; i < tableRowLength; i++) { + await expect((await getText(tableCellStatusColor, i)).trim()).toBe('negative'); + } + + await tablePage.chooseFilter(2, 3); + await expect(await doesItExist(tableFilterableExample + tableRow)).toBe(false, ''); + }); + + it('should check filtering by status', async () => { + await tablePage.chooseFilter(1, 0); + const rowLength = await getElementArrayLength(tableFilterableExample + tableRow); + for (let i = 0; i < rowLength; i++) { + await expect((await getText(tableFilterableExample + tableCellStatus, i)).trim()).toBe('Out of stock'); + } + await refreshPage(); + await tablePage.chooseFilter(1, 1); + const tableRowLength = await getElementArrayLength(tableFilterableExample + tableRow); + for (let i = 0; i < tableRowLength; i++) { + await expect((await getText(tableFilterableExample + tableCellStatus, i)).trim()).toBe(tableCellArr[3]); + } + }); + + it('should check on filter by price reset button is clickable', async () => { + await scrollIntoView(tableFilterableExample); + await click(tableFilterableExample + buttonFilter); + await click(dialogFilters); + await setValue(filterInput, '10'); + await setValue(filterInput, '40', 1); + await click(filterButtonOk); + await wait(500); + await click(tableFilterableExample + buttonFilter); + await click(dialogFilters); + await expect(await isElementClickable(filterResetButton)).toBe(true, 'reset button not clickable'); + }); + }); + + describe('Check Column Grouping', () => { + it('should check table item single selection', async () => { + await tablePage.findElementInTable(tableGroupableExample, groupTableCellArr); + }); + + it('should verify checkboxes', async () => { + await tablePage.checkAllCheckbox(tableGroupableExample); + }); + + it('should check ascending sorting by name and status', async () => { + await scrollIntoView(tableGroupableExample); + await tablePage.chooseSortOptionBy(tableGroupableExample, ellipsisButton, 0); + await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(tableCellArr[1]); + await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( + pharetraTestText + ); + + await tablePage.chooseSortOptionBy(tableGroupableExample, ellipsisButton, 1); + await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(nuncTestText); + await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe(massaTestText); + }); + + it('should check descending sorting by name and status', async () => { + await scrollIntoView(tableGroupableExample); + await click(tableGroupableExample + ellipsisButton); + await click(buttonSortedOrder, 1); + await click(buttonSortedBy, 1); + await click(barButton); + await wait(500); + await tablePage.chooseSortOptionBy(tableGroupableExample, ellipsisButton, 0); + await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(pharetraTestText); + await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( + tableCellArr[1] + ); + + await tablePage.chooseSortOptionBy(tableGroupableExample, ellipsisButton, 1); + await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(tableCellArr[1]); + await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( + 'integer ac leo pellentesque' + ); + }); + }); + + runCommonTests(allInputFields, tablePage); +}); diff --git a/libs/docs/platform/table/e2e/table.e2e-spec.ts b/libs/docs/platform/table/e2e/table.e2e-spec.ts index e99634ace00..21f9e6040c2 100644 --- a/libs/docs/platform/table/e2e/table.e2e-spec.ts +++ b/libs/docs/platform/table/e2e/table.e2e-spec.ts @@ -1,19 +1,14 @@ +import { runCommonTests } from './table-common-tests'; import { wait } from '@nrwl/nx-cloud/lib/utilities/waiter'; import { TablePo } from './table.po'; import { - acceptAlert, browserIsSafari, - checkElArrIsClickable, - checkLtrOrientation, - checkRtlOrientation, click, doesItExist, - getAlertText, getAttributeByName, getElementArrayLength, getElementClass, getElementPlaceholder, - getElementSize, getText, getValue, isElementClickable, @@ -22,46 +17,23 @@ import { pause, refreshPage, scrollIntoView, - sendKeys, setValue, waitForElDisplayed, waitForPresent } from '../../../../../e2e'; import { - alertTestText1, - alertTestText2, - astroTestText, - descriptionEndTestText, - descriptionStartTestText, - massaTestText, - nameEndTestText, - nameStartTestText, - nuncTestText, - pharetraTestText, placeholderTestText, - priceEndTestText, - priceStartTestText, tableCellArr, - tableCellArr2, tableCellArr3, - tableCellArr4, - tableCellArr5, tableCellArr6, tableCellArr7, testText, - testText2, testText3, - testText4, - testText5, - testText7, - testTextName, - testTextSearch, - groupTableCellArr, freezeTableCellArr } from './table-contents'; describe('Table component test suite', () => { - const tablePage = new TablePo(); + const tablePage = new TablePo('/table/basic'); const { tableDefaultExample, button, @@ -69,44 +41,11 @@ describe('Table component test suite', () => { tableRow, tableCellText, tableCustomWidthExample, - tableActivableExample, - tableSingleRowSelectionExample, - tableSortableExample, - buttonSortedBy, - barButton, - tableCellDescription, - tableCellPrice, - tableCellName, - buttonSortedOrder, - tableMultipleRowSelectionExample, - tableGroupableExample, tableFreezableExample, tableLoadingExample, busyIndicator, buttonSearch, - tablePageScrollingExample, tableInitialStateExample, - tableNavigatableRowIndicatorExample, - tableFilterableExample, - filterItem, - filterByColorItem, - tableCellStatusColor, - tableCellStatus, - tableP13ColumnsExample, - dialogCompactInput, - dialogItemText, - dialogMoveToBottom, - footerButtonOk, - dialogItem, - columnHeader, - tableP13SortExample, - tableP13FilterExample, - tableP13GroupExample, - popoverDropdownButton, - buttonAdd, - buttonRemove, - dialogInput, - expandedButton, tableCustomColumnExample, inputFields, playgroundExample, @@ -122,28 +61,12 @@ describe('Table component test suite', () => { checkbox, playgroundSchemaInput, toolbarText, - dropdownList, - dropdownOption, - dialogButton, - tableCell, tableNoItemsTemplateExample, tableSemanticExample, tableRowClassExample, - dialogFilters, - filterInput, - filterButtonOk, - filterResetButton, allInputFields, - sortableIcon, - sortableOption, - sortablePopover, - buttonActionOne, - buttonActionTwo, - ellipsisButton, - expandedOption, tableRowInitialState, tableCellInitialState, - buttonFilter, synchronizeButton, tableTreeExample, arrowButton, @@ -168,21 +91,21 @@ describe('Table component test suite', () => { describe('Check Simple Table example', () => { it('should check alert messages', async () => { - await checkAlertMessages(tableDefaultExample); + await tablePage.checkAlertMessages(tableDefaultExample); }); it('should check table item single selection', async () => { - await findElementInTable(tableDefaultExample, tableCellArr); + await tablePage.findElementInTable(tableDefaultExample, tableCellArr); }); }); describe('Check Custom Column Width & Column Resizing', () => { it('should check alert messages', async () => { - await checkAlertMessages(tableCustomWidthExample); + await tablePage.checkAlertMessages(tableCustomWidthExample); }); it('should check table item single selection', async () => { - await findElementInTable(tableCustomWidthExample, tableCellArr); + await tablePage.findElementInTable(tableCustomWidthExample, tableCellArr); }); // Simple example was updated to use custom heading. @@ -200,21 +123,6 @@ describe('Table component test suite', () => { }); }); - describe('Check Activable Rows', () => { - it('should check alert messages', async () => { - await checkAlertMessages(tableActivableExample); - }); - - it('should check table item single selection', async () => { - await findElementInTable(tableActivableExample, tableCellArr); - }); - - it('should check activable row', async () => { - await scrollIntoView(tableActivableExample); - await expect(await getElementClass(tableActivableExample + tableRow)).toContain('fd-table__row--activable'); - }); - }); - describe('Check Custom Column', () => { it('should check table item single selection', async () => { await scrollIntoView(tableCustomColumnExample); @@ -240,183 +148,6 @@ describe('Table component test suite', () => { }); }); - describe('Check Single Row Selection', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableSingleRowSelectionExample, tableCellArr); - }); - - it('should check table item single selection', async () => { - await scrollIntoView(tableSingleRowSelectionExample); - await click(tableSingleRowSelectionExample + tableRow + tableCell); - await expect(await getAttributeByName(tableSingleRowSelectionExample + tableRow, 'aria-selected')).toBe( - 'true' - ); - }); - - it('should check selected row not gets unselected', async () => { - await scrollIntoView(tableSingleRowSelectionExample); - await setValue(tableSingleRowSelectionExample + input, 'Astro'); - await click(tableSingleRowSelectionExample + button, 1); - await click(tableSingleRowSelectionExample + tableCell); - await click(tableSingleRowSelectionExample + button); - await expect(await getAttributeByName(tableSingleRowSelectionExample + tableRow, 'aria-selected', 1)).toBe( - 'true' - ); - }); - }); - - describe('Check Multi Row Selection', () => { - it('should verify checkboxes', async () => { - await checkAllCheckbox(tableMultipleRowSelectionExample, true); - }); - }); - - describe('Check Column Sorting', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableSortableExample, tableCellArr); - }); - - it('should check ascending sorting by name, description and price', async () => { - await scrollIntoView(tableSortableExample); - await chooseSortOptionBy(tableSortableExample, ellipsisButton, 2); - await expect((await getText(tableSortableExample + tableCellDescription)).trim()).toBe( - descriptionStartTestText - ); - await expect((await getText(tableSortableExample + tableCellDescription, 15)).trim()).toBe( - descriptionEndTestText - ); - - await chooseSortOptionBy(tableSortableExample, ellipsisButton, 3); - await expect((await getText(tableSortableExample + tableCellPrice)).trim()).toBe(priceStartTestText); - await expect((await getText(tableSortableExample + tableCellPrice, 15)).trim()).toBe(priceEndTestText); - - await chooseSortOptionBy(tableSortableExample, ellipsisButton, 1); - await expect((await getText(tableSortableExample + tableCellName)).trim()).toBe(nameStartTestText); - await expect((await getText(tableSortableExample + tableCellName, 15)).trim()).toBe(nameEndTestText); - }); - - it('should check descending sorting by name, description and price', async () => { - await scrollIntoView(tableSortableExample); - await click(tableSortableExample + ellipsisButton); - await click(buttonSortedOrder, 1); - await click(buttonSortedBy, 2); - await click(barButton); - await expect((await getText(tableSortableExample + tableCellDescription)).trim()).toBe( - descriptionEndTestText - ); - await expect((await getText(tableSortableExample + tableCellDescription, 15)).trim()).toBe( - descriptionStartTestText - ); - - await chooseSortOptionBy(tableSortableExample, ellipsisButton, 3); - await expect((await getText(tableSortableExample + tableCellPrice)).trim()).toBe(priceEndTestText); - await expect((await getText(tableSortableExample + tableCellPrice, 15)).trim()).toBe(priceStartTestText); - - await chooseSortOptionBy(tableSortableExample, ellipsisButton, 1); - await expect((await getText(tableSortableExample + tableCellName)).trim()).toBe(nameEndTestText); - await expect((await getText(tableSortableExample + tableCellName, 15)).trim()).toBe(nameStartTestText); - }); - - it('should check after selecting sorting option popover closed', async () => { - await scrollIntoView(tableSortableExample); - await click(sortableIcon); - await click(sortableOption); - await expect(await doesItExist(sortablePopover)).toBe(false, 'sortable popover still displayed'); - }); - }); - - describe('Check Column Filtering', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableFilterableExample, tableCellArr, 1); - }); - - it('should check filtering by status color', async () => { - await chooseFilter(2, 1); - const rowLength = await getElementArrayLength(tableFilterableExample + tableRow); - for (let i = 0; i < rowLength; i++) { - await expect((await getText(tableCellStatusColor, i)).trim()).toBe('positive'); - } - - await chooseFilter(2, 2); - const tableRowLength = await getElementArrayLength(tableFilterableExample + tableRow); - for (let i = 0; i < tableRowLength; i++) { - await expect((await getText(tableCellStatusColor, i)).trim()).toBe('negative'); - } - - await chooseFilter(2, 3); - await expect(await doesItExist(tableFilterableExample + tableRow)).toBe(false, ''); - }); - - it('should check filtering by status', async () => { - await chooseFilter(1, 0); - const rowLength = await getElementArrayLength(tableFilterableExample + tableRow); - for (let i = 0; i < rowLength; i++) { - await expect((await getText(tableFilterableExample + tableCellStatus, i)).trim()).toBe('Out of stock'); - } - await refreshPage(); - await chooseFilter(1, 1); - const tableRowLength = await getElementArrayLength(tableFilterableExample + tableRow); - for (let i = 0; i < tableRowLength; i++) { - await expect((await getText(tableFilterableExample + tableCellStatus, i)).trim()).toBe(tableCellArr[3]); - } - }); - - it('should check on filter by price reset button is clickable', async () => { - await scrollIntoView(tableFilterableExample); - await click(tableFilterableExample + buttonFilter); - await click(dialogFilters); - await setValue(filterInput, '10'); - await setValue(filterInput, '40', 1); - await click(filterButtonOk); - - await click(tableFilterableExample + buttonFilter); - await click(dialogFilters); - await expect(await isElementClickable(filterResetButton)).toBe(true, 'reset button not clickable'); - }); - }); - - describe('Check Column Grouping', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableGroupableExample, groupTableCellArr); - }); - - it('should verify checkboxes', async () => { - await checkAllCheckbox(tableGroupableExample); - }); - - it('should check ascending sorting by name and status', async () => { - await scrollIntoView(tableGroupableExample); - await chooseSortOptionBy(tableGroupableExample, ellipsisButton, 0); - await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(tableCellArr[1]); - await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( - pharetraTestText - ); - - await chooseSortOptionBy(tableGroupableExample, ellipsisButton, 1); - await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(nuncTestText); - await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe(massaTestText); - }); - - it('should check descending sorting by name and status', async () => { - await scrollIntoView(tableGroupableExample); - await click(tableGroupableExample + ellipsisButton); - await click(buttonSortedOrder, 1); - await click(buttonSortedBy, 1); - await click(barButton); - await chooseSortOptionBy(tableGroupableExample, ellipsisButton, 0); - await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(pharetraTestText); - await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( - tableCellArr[1] - ); - - await chooseSortOptionBy(tableGroupableExample, ellipsisButton, 1); - await expect((await getText(tableGroupableExample + tableCellDescription)).trim()).toBe(tableCellArr[1]); - await expect((await getText(tableGroupableExample + tableCellDescription, 15)).trim()).toBe( - 'integer ac leo pellentesque' - ); - }); - }); - describe('Check Column Freezing', () => { it('should check table item single selection', async () => { await scrollIntoView(tableFreezableExample + input); @@ -433,13 +164,13 @@ describe('Table component test suite', () => { }); it('should verify checkboxes', async () => { - await checkAllCheckbox(tableFreezableExample); + await tablePage.checkAllCheckbox(tableFreezableExample); }); }); describe('Check Loading/Busy State', () => { it('should check alert messages', async () => { - await checkAlertMessages(tableLoadingExample); + await tablePage.checkAlertMessages(tableLoadingExample); }); it('should check busy indicator', async () => { @@ -464,36 +195,6 @@ describe('Table component test suite', () => { }); }); - describe('Check Page Scrolling', () => { - it('should check table item single selection', async () => { - await scrollIntoView(tablePageScrollingExample); - await setValue(tablePageScrollingExample + input, testText2); - await click(tablePageScrollingExample + buttonSearch); - await expect(await doesItExist(busyIndicator)).toBe(true, "busy indicator isn't displayed"); - await pause(500); - await expect(await doesItExist(busyIndicator)).toBe(false, 'busy indicator is displayed'); - const rowLength = await getElementArrayLength(tablePageScrollingExample + tableRow); - await expect(rowLength).toEqual(1); - const cellLength = await getElementArrayLength(tablePageScrollingExample + tableRow + tableCellText); - for (let i = 0; i < cellLength; i++) { - await scrollIntoView(tablePageScrollingExample + tableRow + tableCellText, i); - await expect((await getText(tablePageScrollingExample + tableRow + tableCellText, i)).trim()).toBe( - tableCellArr2[i] - ); - } - }); - - it('should check scroll', async () => { - await scrollIntoView(tablePageScrollingExample); - await scrollIntoView(tablePageScrollingExample + tableRow, 40); - - await expect((await getText(tablePageScrollingExample + tableCellName, 40)).trim()).toBe('Product name 40'); - await expect((await getText(tablePageScrollingExample + tableCellDescription, 40)).trim()).toBe( - 'Product description goes here 40' - ); - }); - }); - describe('Check Initial State', () => { it('should check table item single selection', async () => { await scrollIntoView(tableInitialStateExample); @@ -535,7 +236,7 @@ describe('Table component test suite', () => { }); it('should check checkboxes', async () => { - await checkAllCheckbox(tableTreeExample); + await tablePage.checkAllCheckbox(tableTreeExample); }); it('should check expanded table row', async () => { @@ -548,151 +249,6 @@ describe('Table component test suite', () => { }); }); - describe('Check Table columns visibility and order', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableP13ColumnsExample, tableCellArr4); - }); - - it('should check searching and placeholder in dialog', async () => { - await checkPlaceholder(tableP13ColumnsExample, 2); - await checkSearchingInDialog(); - }); - - it('should check sorting of columns', async () => { - await checkSortingColumns(tableP13ColumnsExample, ellipsisButton); - }); - }); - - describe('Check Sorting by multiple columns', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableP13SortExample, tableCellArr4); - }); - - fit('should check sorting ascending and descending by name', async () => { - await scrollIntoView(tableP13SortExample); - await click(tableP13SortExample + ellipsisButton); - await wait(500); - await click(popoverDropdownButton); - await click(buttonSortedBy); - await click(footerButtonOk); - await expect((await getText(tableP13SortExample + tableCellName)).trim()).toBe(testText); - await expect((await getText(tableP13SortExample + tableCellName, 15)).trim()).toBe(nameEndTestText); - - await click(tableP13SortExample + columnHeader); - await click(filterByColorItem, 1); - await expect((await getText(tableP13SortExample + tableCellName)).trim()).toBe(nameEndTestText); - await expect((await getText(tableP13SortExample + tableCellName, 15)).trim()).toBe(testText); - }); - - it('should check sorting ascending and descending by price', async () => { - await scrollIntoView(tableP13SortExample); - await click(tableP13SortExample + ellipsisButton); - await click(buttonAdd); - await click(buttonRemove); - await click(popoverDropdownButton); - await click(buttonSortedBy, 1); - await click(footerButtonOk); - - await expect((await getText(tableP13SortExample + tableCellPrice)).trim()).toBe(priceStartTestText); - await expect((await getText(tableP13SortExample + tableCellPrice, 15)).trim()).toBe(priceEndTestText); - - await click(tableP13SortExample + columnHeader, 2); - await click(filterByColorItem, 1); - await expect((await getText(tableP13SortExample + tableCellPrice)).trim()).toBe(priceEndTestText); - await expect((await getText(tableP13SortExample + tableCellPrice, 15)).trim()).toBe(priceStartTestText); - }); - - it('should check searching and placeholder in dialog', async () => { - await checkPlaceholder(tableP13SortExample, 3); - }); - - it('should check sorting of columns', async () => { - await checkSortingColumns(tableP13SortExample, ellipsisButton, 1); - }); - - it('should check impossible select columns multiple times', async () => { - await scrollIntoView(tableP13SortExample); - await click(tableP13SortExample + ellipsisButton); - await click(popoverDropdownButton); - await expect(await isElementDisplayed(dropdownList)).toBe(true); - await click(dropdownOption); - - await click(dialogButton, 1); - await click(popoverDropdownButton, 2); - await expect(await isElementDisplayed(dropdownList)).toBe(true); - await click(dropdownOption); - - await click(dialogButton, 3); - await click(popoverDropdownButton, 4); - await expect(await doesItExist(dropdownList)).toBe(false); - }); - }); - - describe('Check Filtering by multiple columns', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableP13FilterExample, tableCellArr5); - }); - - it('should check filtering with include and exclude', async () => { - await scrollIntoView(tableP13FilterExample); - await click(tableP13FilterExample + ellipsisButton); - await setValue(dialogInput, astroTestText); - await click(expandedButton, 1); - await click(popoverDropdownButton, 2); - await click(filterByColorItem, 1); - await setValue(dialogInput, testText7, 1); - await click(footerButtonOk); - - const rowLength = await getElementArrayLength(tableP13FilterExample + tableRow); - await expect(rowLength).toEqual(1); - await expect((await getText(tableP13FilterExample + tableRow + tableCellText)).trim()).toBe(testText4); - await expect((await getText(tableP13FilterExample + tableRow + tableCellText, 1)).trim()).toBe(testText5); - }); - - it('should check searching and placeholder in dialog', async () => { - await checkPlaceholder(tableP13FilterExample, 3); - await checkSearchingInDialog(); - }); - - it('should check sorting of columns', async () => { - await checkSortingColumns(tableP13FilterExample, ellipsisButton, 1); - }); - // skipped due to https://github.com/SAP/fundamental-ngx/issues/7005 - xit('should check Exclude section in dialog always open', async () => { - await scrollIntoView(tableP13FilterExample); - await click(tableP13FilterExample + buttonFilter); - await expect(await getAttributeByName(expandedOption, 'aria-expanded')).toBe('false'); - }); - }); - - describe('Check Grouping by multiple columns', () => { - it('should check table item single selection', async () => { - await findElementInTable(tableP13GroupExample, tableCellArr4); - }); - - it('should check searching and placeholder in dialog', async () => { - await checkPlaceholder(tableP13GroupExample, 3); - await checkSearchingInDialog(); - }); - - it('should check sorting of columns', async () => { - await checkSortingColumns(tableP13GroupExample, ellipsisButton, 1); - }); - }); - - describe('Check Navigatable rows', () => { - it('should check example', async () => { - await scrollIntoView(tableNavigatableRowIndicatorExample); - await click(tableNavigatableRowIndicatorExample + button); - await checkElArrIsClickable(tableNavigatableRowIndicatorExample + tableRow); - - await click(tableNavigatableRowIndicatorExample + button, 1); - await expect(await getElementClass(tableNavigatableRowIndicatorExample + tableRow, 1)).toBe( - 'fd-table__row fd-table__row--main ng-star-inserted' - ); - }); - }); - describe('Checks for all examples', () => { it('should check placeholders in all input fields', async () => { const inputLength = await getElementArrayLength(inputFields); @@ -807,162 +363,49 @@ describe('Table component test suite', () => { describe('Check Custom component to render "No data" message', () => { it('should check alert messages', async () => { - await checkAlertMessages(tableNoItemsTemplateExample); + await tablePage.checkAlertMessages(tableNoItemsTemplateExample); }); }); describe('Check Semantic Highlighting', () => { it('should check alert messages', async () => { - await checkAlertMessages(tableSemanticExample); + await tablePage.checkAlertMessages(tableSemanticExample); }); it('should check table item single selection', async () => { - await findElementInTable(tableSemanticExample, tableCellArr); + await tablePage.findElementInTable(tableSemanticExample, tableCellArr); }); }); describe('Check Row custom CSS class', () => { it('should check table item single selection', async () => { - await findElementInTable(tableRowClassExample, tableCellArr); + await tablePage.findElementInTable(tableRowClassExample, tableCellArr); }); }); describe('Check Wrapping text in columns', () => { it('should check table item single selection', async () => { - await findElementInTable(tableWrapExample, tableCellArr7); + await tablePage.findElementInTable(tableWrapExample, tableCellArr7); }); it('should check alert messages', async () => { - await checkAlertMessages(tableWrapExample); + await tablePage.checkAlertMessages(tableWrapExample); }); }); describe('Check No outer borders', () => { it('should check table item single selection', async () => { - await findElementInTable(tableNoOuterBordersExample, tableCellArr); + await tablePage.findElementInTable(tableNoOuterBordersExample, tableCellArr); }); it('should check alert messages', async () => { - await checkAlertMessages(tableNoOuterBordersExample); + await tablePage.checkAlertMessages(tableNoOuterBordersExample); }); it('should check checkboxes', async () => { - await checkAllCheckbox(tableNoOuterBordersExample); - }); - }); - - describe('Check input fields', () => { - it('should check input fields does not change width', async () => { - const inputFieldLength = await getElementArrayLength(allInputFields); - for (let i = 0; i < inputFieldLength; i++) { - if (i === 13) { - continue; - } - const beforeSize = await getElementSize(allInputFields, i); - await scrollIntoView(allInputFields, i); - await click(allInputFields, i); - await sendKeys('test'); - const afterSize = await getElementSize(allInputFields, i); - await expect(beforeSize).toEqual(afterSize); - } - }); - }); - - describe('Check orientation', () => { - it('should check RTL and LTR orientation', async () => { - const exampleAreaContainersArr = '.fd-doc-component'; - const rtlSwitcherArr = 'rtl-switch .fd-switch__handle'; - - const switcherLength = await getElementArrayLength(exampleAreaContainersArr); - for (let i = 0; i < switcherLength; i++) { - if (i === 13) { - continue; - } - await scrollIntoView(rtlSwitcherArr, i); - await click(rtlSwitcherArr, i); - await checkRtlOrientation(exampleAreaContainersArr, i); - await scrollIntoView(rtlSwitcherArr, i); - await click(rtlSwitcherArr, i); - await waitForElDisplayed(exampleAreaContainersArr, i); - await checkLtrOrientation(exampleAreaContainersArr, i); - } - }); - }); - - xdescribe('Check visual regression', () => { - it('should check examples visual regression', async () => { - await tablePage.saveExampleBaselineScreenshot(); - await expect(await tablePage.compareWithBaseline()).toBeLessThan(5); + await tablePage.checkAllCheckbox(tableNoOuterBordersExample); }); }); - async function checkAlertMessages(selector: string): Promise { - await scrollIntoView(selector + button); - await click(selector + buttonActionOne); - await expect(await getAlertText()).toBe(alertTestText1); - await acceptAlert(); - - await click(selector + buttonActionTwo); - await expect(await getAlertText()).toBe(alertTestText2); - await acceptAlert(); - } - - async function findElementInTable(selector: string, arr: string[], count: number = 0): Promise { - await scrollIntoView(selector + input); - await setValue(selector + input, testText); - await click(selector + buttonSearch); - const rowLength = await getElementArrayLength(selector + tableRow); - await expect(rowLength).toEqual(1); - const cellLength = await getElementArrayLength(selector + tableRow + tableCellText); - for (let i = 0; i < cellLength - count; i++) { - await expect((await getText(selector + tableRow + tableCellText, i)).trim()).toBe(arr[i]); - } - } - - async function chooseSortOptionBy(selector: string, transparentButton: string, index: number): Promise { - await click(selector + transparentButton); - await click(buttonSortedBy, index); - await click(barButton); - } - - async function checkAllCheckbox(selector, skipFirst = false): Promise { - await scrollIntoView(selector); - await click(selector + 'fd-checkbox'); - const checkboxLength = await getElementArrayLength(selector + tableRow); - for (let i = 0; i < checkboxLength; i++) { - await expect(await getAttributeByName(selector + tableRow, 'aria-selected', i)).toBe( - skipFirst && i === 0 ? 'false' : 'true' - ); - } - } - - async function chooseFilter(indexFilter: number, indexBy): Promise { - await scrollIntoView(tableFilterableExample); - await click(tableFilterableExample + ellipsisButton); - await click(filterItem, indexFilter); - await click(filterByColorItem, indexBy); - await click(barButton); - } - - async function checkPlaceholder(selector: string, index: number = 0): Promise { - await scrollIntoView(selector); - await click(selector + button, index); - await expect(await getElementPlaceholder(dialogCompactInput)).toBe(testTextSearch); - } - - async function checkSearchingInDialog(): Promise { - await setValue(dialogCompactInput, testTextName); - const itemLength = await getElementArrayLength(dialogItemText); - await expect(itemLength).toEqual(1); - await expect((await getText(dialogItemText)).trim()).toBe(testTextName); - } - - async function checkSortingColumns(selector: string, transparentButton: string, index: number = 0): Promise { - await scrollIntoView(selector); - await click(selector + transparentButton, index); - await click(dialogMoveToBottom); - await click(dialogItem); - await click(footerButtonOk); - await expect((await getText(selector + columnHeader, 3)).trim()).toBe(testTextName); - } + runCommonTests(allInputFields, tablePage); }); diff --git a/libs/docs/platform/table/e2e/table.po.ts b/libs/docs/platform/table/e2e/table.po.ts index 7d3cecba17b..93fd93ef2cd 100644 --- a/libs/docs/platform/table/e2e/table.po.ts +++ b/libs/docs/platform/table/e2e/table.po.ts @@ -1,8 +1,20 @@ -import { PlatformBaseComponentPo, waitForElDisplayed, waitForPresent } from '../../../../../e2e'; +import { + acceptAlert, + click, + getAlertText, + getAttributeByName, + getElementArrayLength, + getElementPlaceholder, + getText, + PlatformBaseComponentPo, + scrollIntoView, + setValue, + waitForElDisplayed, + waitForPresent +} from '../../../../../e2e'; +import { alertTestText1, alertTestText2, testText, testTextName, testTextSearch } from './table-contents'; export class TablePo extends PlatformBaseComponentPo { - readonly url = '/table'; - tableDefaultExample = 'fdp-platform-table-default-example '; tableCustomWidthExample = 'fdp-platform-table-custom-width-example '; tableActivableExample = 'fdp-platform-table-activable-example '; @@ -91,6 +103,10 @@ export class TablePo extends PlatformBaseComponentPo { synchronizeButton = '.fdp-search-field__loading'; arrowButton = '.fd-table__cell--expand'; + constructor(public readonly url: string) { + super(); + } + async open(): Promise { await super.open(this.url); await waitForPresent(this.root); @@ -108,4 +124,74 @@ export class TablePo extends PlatformBaseComponentPo { async compareWithBaseline(specName: string = 'table'): Promise { return super.compareWithBaseline(specName, await this.getScreenshotFolder()); } + + async checkAlertMessages(selector: string): Promise { + await scrollIntoView(selector + this.button); + await click(selector + this.buttonActionOne); + await expect(await getAlertText()).toBe(alertTestText1); + await acceptAlert(); + + await click(selector + this.buttonActionTwo); + await expect(await getAlertText()).toBe(alertTestText2); + await acceptAlert(); + } + + async findElementInTable(selector: string, arr: string[], count: number = 0): Promise { + await scrollIntoView(selector + this.input); + await setValue(selector + this.input, testText); + await click(selector + this.buttonSearch); + const rowLength = await getElementArrayLength(selector + this.tableRow); + await expect(rowLength).toEqual(1); + const cellLength = await getElementArrayLength(selector + this.tableRow + this.tableCellText); + for (let i = 0; i < cellLength - count; i++) { + await expect((await getText(selector + this.tableRow + this.tableCellText, i)).trim()).toBe(arr[i]); + } + } + + async chooseSortOptionBy(selector: string, transparentButton: string, index: number): Promise { + await click(selector + transparentButton); + await click(this.buttonSortedBy, index); + await click(this.barButton); + } + + async checkAllCheckbox(selector, skipFirst = false): Promise { + await scrollIntoView(selector); + await click(selector + 'fd-checkbox'); + const checkboxLength = await getElementArrayLength(selector + this.tableRow); + for (let i = 0; i < checkboxLength; i++) { + await expect(await getAttributeByName(selector + this.tableRow, 'aria-selected', i)).toBe( + skipFirst && i === 0 ? 'false' : 'true' + ); + } + } + + async chooseFilter(indexFilter: number, indexBy): Promise { + await scrollIntoView(this.tableFilterableExample); + await click(this.tableFilterableExample + this.ellipsisButton); + await click(this.filterItem, indexFilter); + await click(this.filterByColorItem, indexBy); + await click(this.barButton); + } + + async checkPlaceholder(selector: string, index: number = 0): Promise { + await scrollIntoView(selector); + await click(selector + this.button, index); + await expect(await getElementPlaceholder(this.dialogCompactInput)).toBe(testTextSearch); + } + + async checkSearchingInDialog(): Promise { + await setValue(this.dialogCompactInput, testTextName); + const itemLength = await getElementArrayLength(this.dialogItemText); + await expect(itemLength).toEqual(1); + await expect((await getText(this.dialogItemText)).trim()).toBe(testTextName); + } + + async checkSortingColumns(selector: string, transparentButton: string, index: number = 0): Promise { + await scrollIntoView(selector); + await click(selector + transparentButton, index); + await click(this.dialogMoveToBottom); + await click(this.dialogItem); + await click(this.footerButtonOk); + await expect((await getText(selector + this.columnHeader, 3)).trim()).toBe(testTextName); + } } diff --git a/libs/docs/platform/table/platform-table-docs.component.html b/libs/docs/platform/table/platform-table-docs.component.html index fe7cd36e2d4..10e905a6b9b 100644 --- a/libs/docs/platform/table/platform-table-docs.component.html +++ b/libs/docs/platform/table/platform-table-docs.component.html @@ -37,22 +37,6 @@ - Activable Rows - -

This example shows activable rows

- -
    -
  • Use [rowsActivable]="true" input property on the table to make rows activable.
  • -
  • Event (rowActivate) is emitted when row gets activated.
  • -
-
- - - - - - - Custom Column This example shows custom column definition implementation @@ -71,110 +55,6 @@ - Single Row Selection - -

This example shows single row selection

- -

- If both selection and search is used in a table, use rowComparator in order to preserve selection - after search is changed -

- -

- Consider that in the table with selection column the overall columns width cannot be less than table container. -

-
- - - - - - - - - Multiple Row Selection - - -

This example shows multiple row selection

- -

- Consider that in the table with selection column present the overall columns width cannot be less than table - container. -

- -

- You can have preselected rows by providing selectedKey input property for the table with field name - in row object. Any truthy value will be considered as selected. -

- -

- You also can disable selecting for some rows by providing selectableKey input property for the - table with field name in row object. Only false value disables selection. -

-
- - - - - - - - Column Sorting - -

Use [sortable]="true" to mark a column as an sortable one.

-

- Developers can restrict selecting "Not Sorted" option by providing - [allowDisablingSorting]="false" input property for the - fdp-table-view-settings-dialog component and providing - [initialSortBy] input property of the table. -

-

In this example Name, Description and Price columns can be sorted.

-
- - - - - - - - Column Filtering - - There are two ways to add filtering. The first one is to use the column option [filterable]="true". - This will add filter input field in the column header menu. -
- The second one is to use fdp-table-view-settings-filter withing - fdp-table-view-settings-dialog. It gives ability to define filtering type. For now it supports the next - types: -
    -
  • single-select
  • -
  • multi-select
  • -
  • custom
  • -
-
- - - - - - - - Column Grouping - - Use [groupable]="true" to mark a column as one that can be used to group by. -
- Once there is at least one a groupable column the Group dialog option appears in the table toolbar. To enable - grouping by some column you can use the column header menu or the Group dialog. In order to ungroup you should use - only the Group dialog. -
- In this example Name and Status columns can be used to group by. -
- - - - - - - Column Freezing

Use [freezeColumnsTo]="columnName" to freeze columns up to and including.

@@ -211,30 +91,6 @@ - Page Scrolling - - In order to get table page scrolling use the next options: -
    -
  • Add [pageScrolling]="true" to trigger a fetch once the table is scrolled to the bottom.
  • -
  • - Add [bodyHeight]="tableBodyHeight" to restrict a table body height so the vertical scrollbar - can appear. -
  • -
  • Add [pageSize]="itemsPerPage" to specify how many items there are per page.
  • -
  • - Add [pageScrollingThreshold]="thresholdInPx" to indicate the threshold between the height of - the remaining content and the need to load new data. -
  • -
- Consider that columns width automatically calculates only after the first set of data comes to the component. -
- - - - - - - Initial State To setup initial state use next properties: @@ -263,14 +119,26 @@

Drag & Drop

- Tree table supports Drag & Drop by default, so rows can be moved & placed inside other rows. Please consider that - Drag & Drop isn't possible yet with [sortable], [groupable], - [filterable] columns and [freezeColumnsTo] enabled. +

+ Tree table supports Drag & Drop by default, so rows can be moved & placed inside other rows. Please consider + that Drag & Drop isn't possible yet with [sortable], [groupable], + [filterable] columns and [freezeColumnsTo] enabled. +


- Drag and drop is available via keyboard by focusing a row or cell, holding the alt/option key and using the up or - down arrows. +

+ Drag and drop is available via keyboard by focusing a row or cell, holding the alt/option key and using the up + or down arrows. +

+

+ By default, keyboard drag & drop works in shift mode, which means that focused item will ne moved on the same + level as it's siblings. +

+

+ By pressing alt/option + shift key, drag & drop will use 'group' mode with the next sibling based on the + direction of drag & drop. +

@@ -279,150 +147,6 @@

Drag & Drop

- Virtual Scroll -

- When dealing with large amounts of data, it is recommended to use virtual scrolling to improve performance. To - enable Virtual Scroll, use the following properties: -

- -
    -
  • [virtualScroll]="true" - enables virtual scroll
  • -
  • [bodyHeight]="value" - sets the height of the table body (viewport)
  • -
  • - [rowHeight]="value" - sets the height of the table row if it is different from default for the - current content density -
  • -
- -

- Additionally, you may set the renderAhead input property to specify how many rows should be rendered - ahead of the current viewport. Default is 20. -

- - - - - - - - - Personalization Dialog. - - The p13n dialog control provides a dialog for tables that allows the user to personalize one or more of the - following attributes: -
    -
  • Columns (visibility and order of columns)
  • -
  • Sorting by multiple columns
  • -
  • Filtering by multiple columns
  • -
  • Grouping by multiple columns
  • -
-
- - - Table columns visibility and order - - - To make columns settings available connect - <fdp-table-p13-dialog [table]="table"></fdp-table-p13-dialog> to a table. Once there is at - least one fdp-column definition the columns settings button will be available in the table toolbar. - - - - - - - - - Sorting by multiple columns - -
    -
  • - Connect your table to p13 columns dialog by adding - <fdp-table-p13-dialog - [table]="table"><fdp-table-p13n-sort></fdp-table-p13n-sort></fdp-table-p13-dialog> -
  • -
  • Add <fdp-column [sortable]="true"> option to mark a column as sortable.
  • -
- The order of the criteria is exactly the same as the order in which sorting is applied to the table. -
- - - - - - - - - Filtering by multiple columns - - -
    -
  • - Connect your table to p13 filtering dialog by adding - <fdp-table-p13-dialog - [table]="table"><fdp-table-p13n-filter></fdp-table-p13n-filter></fdp-table-p13-dialog> -
  • -
  • Add <fdp-column [filterable]="true"> option to mark a column as filterable.
  • -
  • - Add column data type <fdp-column dataType="string | number | date | boolean"> - to show corresponding filtering ui. -
  • -
- Please note once the fdp-table-p13n-filter is connected to a table the filter option is not available - from column header dialog menu. -
- - - - - - - - Grouping by multiple columns - -
    -
  • - Connect your table to p13 group dialog by adding - <fdp-table-p13-dialog - [table]="table"><fdp-table-p13n-group></fdp-table-p13n-group></fdp-table-p13-dialog> -
  • -
  • Add <fdp-column [groupable]="true"> option to mark a column as groupable.
  • -
-
- - - - - - - - Navigatable rows - - This example shows navigatable rows. - -
    -
  • - Use [rowNavigatable]="true" input property on the table to make rows navigatable. Pass boolean - to change navigatable state for all rows. Pass string with the field of the rows which will be used to - calculate row's navigatable state. -
  • -
  • Use [highlightNavigatedRow]="true" to highlight currently navigated row.
  • -
  • Event (rowNavigate) is emitted when row gets activated.
  • -
-
- - - - - - - Custom component to render "No data" message @@ -497,7 +221,7 @@

Drag & Drop

- + diff --git a/libs/docs/platform/table/platform-table-docs.component.ts b/libs/docs/platform/table/platform-table-docs.component.ts index a67908ef57a..7b321eb715e 100644 --- a/libs/docs/platform/table/platform-table-docs.component.ts +++ b/libs/docs/platform/table/platform-table-docs.component.ts @@ -1,7 +1,12 @@ -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { RtlService } from '@fundamental-ngx/cdk/utils'; +import { ContentDensityMode } from '@fundamental-ngx/core/content-density'; import { DatetimeAdapter } from '@fundamental-ngx/core/datetime'; +import { Schema, SchemaFactoryService } from '@fundamental-ngx/docs/schema'; + +import { ExampleChildService, ExampleFile, getAsset, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; import { TableColumnFreezeEvent, TableDataSource, @@ -10,9 +15,8 @@ import { TableRowSelectionChangeEvent, TableSortChangeEvent } from '@fundamental-ngx/platform/table'; - -import { ExampleFile, getAsset, getAssetFromModuleAssets } from '@fundamental-ngx/docs/shared'; -import { Schema, SchemaFactoryService } from '@fundamental-ngx/docs/schema'; +import { ExampleItem } from './examples/platform-table-data-items-example'; +import { TableDataProviderExample } from './examples/platform-table-data-provider-example'; const platformTableDefaultSrc = 'platform-table-default-example.component.html'; const platformTableDefaultTsSrc = 'platform-table-default-example.component.ts'; @@ -22,39 +26,15 @@ const platformTableCustomTitleSrc = 'platform-table-custom-title-example.compone const platformTableCustomTitleTsSrc = 'platform-table-custom-title-example.component.ts'; const platformTableCustomWidthSrc = 'platform-table-custom-width-example.component.html'; const platformTableCustomWidthTsSrc = 'platform-table-custom-width-example.component.ts'; -const platformTableSingleRowSelectionSrc = 'platform-table-single-row-selection-example.component.html'; -const platformTableSingleRowSelectionTsSrc = 'platform-table-single-row-selection-example.component.ts'; -const platformTableMultipleRowSelectionSrc = 'platform-table-multiple-row-selection-example.component.html'; -const platformTableMultipleRowSelectionTsSrc = 'platform-table-multiple-row-selection-example.component.ts'; -const platformTableSortableSrc = 'platform-table-sortable-example.component.html'; -const platformTableSortableTsSrc = 'platform-table-sortable-example.component.ts'; -const platformTableGroupableSrc = 'platform-table-groupable-example.component.html'; -const platformTableGroupableTsSrc = 'platform-table-groupable-example.component.ts'; -const platformTableFilterableSrc = 'platform-table-filterable-example.component.html'; -const platformTableFilterableTsSrc = 'platform-table-filterable-example.component.ts'; const platformTableFreezableSrc = 'platform-table-freezable-example.component.html'; const platformTableFreezableTsSrc = 'platform-table-freezable-example.component.ts'; const platformTableLoadingSrc = 'platform-table-loading-example.component.html'; const platformTableLoadingTsSrc = 'platform-table-loading-example.component.ts'; -const platformTablePageScrollingSrc = 'platform-table-page-scrolling-example.component.html'; -const platformTablePageScrollingTsSrc = 'platform-table-page-scrolling-example.component.ts'; const platformTableInitialStateSrc = 'platform-table-initial-state-example.component.html'; const platformTableInitialStateTsSrc = 'platform-table-initial-state-example.component.ts'; -const platformTableP13ColumnSrc = 'platform-table-p13-columns-example.component.html'; -const platformTableP13ColumnTsSrc = 'platform-table-p13-columns-example.component.ts'; -const platformTableP13SortSrc = 'platform-table-p13-sort-example.component.html'; -const platformTableP13SortTsSrc = 'platform-table-p13-sort-example.component.ts'; -const platformTableP13FilterSrc = 'platform-table-p13-filter-example.component.html'; -const platformTableP13FilterTsSrc = 'platform-table-p13-filter-example.component.ts'; -const platformTableP13GroupSrc = 'platform-table-p13-group-example.component.html'; -const platformTableP13GroupTsSrc = 'platform-table-p13-group-example.component.ts'; const platformTreeTableDefaultSrc = 'platform-table-tree-example.component.html'; const platformTreeTableDefaultTsSrc = 'platform-table-tree-example.component.ts'; -const platformVirtualScrollTableDefaultSrc = 'virtual-scroll/platform-table-virtual-scroll-example.component.html'; -const platformVirtualScrollTableDefaultTsSrc = 'virtual-scroll/platform-table-virtual-scroll-example.component.ts'; -const platformTableNavigatableRowSrc = 'platform-table-navigatable-row-indicator-example.component.html'; const platformTableNoOuterBordersSrc = 'platform-table-navigatable-row-indicator-example.component.html'; -const platformTableNavigatableRowTsSrc = 'platform-table-navigatable-row-indicator-example.component.ts'; const platformTableNoOuterBordersTsSrc = 'platform-table-navigatable-row-indicator-example.component.ts'; const platformTableSemanticSrc = 'platform-table-semantic-example.component.html'; const platformTableSemanticTsSrc = 'platform-table-semantic-example.component.ts'; @@ -79,13 +59,10 @@ const platformTableNgForTsSrc = 'platform-table-columns-ngfor-example.component. const illustrationDialogNoMail = '/assets/images/sapIllus-Dialog-NoMail.svg'; -import { TableDataProviderExample } from './examples/platform-table-data-provider-example'; -import { ExampleItem } from './examples/platform-table-data-items-example'; -import { ContentDensityMode } from '@fundamental-ngx/core/content-density'; - @Component({ selector: 'fdp-table-docs', templateUrl: './platform-table-docs.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, providers: [RtlService] }) export class PlatformTableDocsComponent { @@ -181,86 +158,6 @@ export class PlatformTableDocsComponent { } ]; - singleRowSelectionFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableSingleRowSelectionSrc), - fileName: 'platform-table-single-row-selection-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableSingleRowSelectionTsSrc), - fileName: 'platform-table-single-row-selection-example', - component: 'PlatformTableSingleRowSelectionExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - multipleRowSelectionFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableMultipleRowSelectionSrc), - fileName: 'platform-table-multiple-row-selection-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableMultipleRowSelectionTsSrc), - fileName: 'platform-table-multiple-row-selection-example', - component: 'PlatformTableMultipleRowSelectionExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - sortableTableFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableSortableSrc), - fileName: 'platform-table-sortable-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableSortableTsSrc), - fileName: 'platform-table-sortable-example', - component: 'PlatformTableSortableExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - filterableTableFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableFilterableSrc), - fileName: 'platform-table-filterable-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableFilterableTsSrc), - fileName: 'platform-table-filterable-example', - component: 'PlatformTableFilterableExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - groupableTableFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableGroupableSrc), - fileName: 'platform-table-groupable-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableGroupableTsSrc), - fileName: 'platform-table-groupable-example', - component: 'PlatformTableGroupableExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - freezableTableFiles: ExampleFile[] = [ { language: 'html', @@ -293,22 +190,6 @@ export class PlatformTableDocsComponent { } ]; - pageScrollingTableFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTablePageScrollingSrc), - fileName: 'platform-table-page-scrolling-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTablePageScrollingTsSrc), - fileName: 'platform-table-page-scrolling-example', - component: 'PlatformTablePageScrollingExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - initialStateFiles: ExampleFile[] = [ { language: 'html', @@ -325,70 +206,6 @@ export class PlatformTableDocsComponent { } ]; - p13ColumnsFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableP13ColumnSrc), - fileName: 'platform-table-p13-columns-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableP13ColumnTsSrc), - fileName: 'platform-table-p13-columns-example', - component: 'PlatformTableP13ColumnsExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - p13SortFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableP13SortSrc), - fileName: 'platform-table-p13-sort-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableP13SortTsSrc), - fileName: 'platform-table-p13-sort-example', - component: 'PlatformTableP13SortExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - p13FilterFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableP13FilterSrc), - fileName: 'platform-table-p13-filter-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableP13FilterTsSrc), - fileName: 'platform-table-p13-filter-example', - component: 'PlatformTableP13FilterExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - - p13GroupFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableP13GroupSrc), - fileName: 'platform-table-p13-group-example', - name: 'platform-table-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableP13GroupTsSrc), - fileName: 'platform-table-p13-group-example', - component: 'PlatformTableP13GroupExampleComponent', - name: 'platform-table-example.component.ts' - } - ]; - treeTableFiles: ExampleFile[] = [ { language: 'html', @@ -405,38 +222,6 @@ export class PlatformTableDocsComponent { } ]; - virtualScrollTableFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformVirtualScrollTableDefaultSrc), - fileName: 'platform-table-virtual-scroll-example', - name: 'platform-table-virtual-scroll-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformVirtualScrollTableDefaultTsSrc), - fileName: 'platform-table-virtual-scroll-example', - component: 'PlatformTableVirtualScrollExampleComponent', - name: 'platform-table-virtual-scroll-example.component.ts' - } - ]; - - navitableRowFiles: ExampleFile[] = [ - { - language: 'html', - code: getAssetFromModuleAssets(platformTableNavigatableRowSrc), - fileName: 'platform-table-navigatable-row-indicator-example', - name: 'platform-table-navigatable-row-indicator-example.component.html' - }, - { - language: 'typescript', - code: getAssetFromModuleAssets(platformTableNavigatableRowTsSrc), - fileName: 'platform-table-navigatable-row-indicator-example', - component: 'PlatformTableNavigatableRowIndicatorExampleComponent', - name: 'platform-table-navigatable-row-indicator-example.component.ts' - } - ]; - noDataCustomMessageFiles: ExampleFile[] = [ { language: 'html', @@ -590,6 +375,8 @@ export class PlatformTableDocsComponent { dataSource: TableDataSource; + childService = inject(ExampleChildService); + route = inject(ActivatedRoute); constructor( private schemaFactory: SchemaFactoryService, private _cd: ChangeDetectorRef, @@ -597,6 +384,7 @@ export class PlatformTableDocsComponent { ) { this.schema = this.schemaFactory.getComponent('fdp-table'); this.dataSource = new TableDataSource(new TableDataProviderExample(datetimeAdapter)); + this.childService.setLink(this.route.snapshot.routeConfig?.path); } onSchemaValues(data): void { diff --git a/libs/docs/platform/table/platform-table.module.ts b/libs/docs/platform/table/platform-table.module.ts index 61d6c1a278b..06af85d09ae 100644 --- a/libs/docs/platform/table/platform-table.module.ts +++ b/libs/docs/platform/table/platform-table.module.ts @@ -20,6 +20,7 @@ import { API_FILES } from '@fundamental-ngx/docs/platform/shared'; import { ApiComponent, currentComponentProvider, + ExampleChildService, getI18nKey, I18nDocsComponent, SharedDocumentationPageModule @@ -59,15 +60,26 @@ import { PlatformTableInitialLoadingExampleComponent } from './examples/initial- import { PlatformTableColumnsNgforExampleComponent } from './examples/platform-table-columns-ngfor-example.component'; import { ToolbarModule } from '@fundamental-ngx/core/toolbar'; import { PlatformTableVirtualScrollExampleComponent } from './examples/virtual-scroll/platform-table-virtual-scroll-example.component'; +import { P13DialogDocsComponent } from './child-docs/p13-dialog/p13-dialog-docs.component'; +import { SettingsDialogDocsComponent } from './child-docs/settings-dialog/settings-dialog-docs.component'; +import { RowSelectionDocsComponent } from './child-docs/row-selection/row-selection-docs.component'; +import { TableScrollingDocsComponent } from './child-docs/scrolling/table-scrolling-docs.component'; +import { ClickableRowsDocsComponent } from './child-docs/clickable-rows/clickable-rows-docs.component'; const routes: Routes = [ { path: '', component: PlatformTableHeaderComponent, children: [ - { path: '', component: PlatformTableDocsComponent }, + { path: '', redirectTo: 'basic', pathMatch: 'full' }, + { path: 'basic', component: PlatformTableDocsComponent }, { path: 'api', component: ApiComponent, data: { content: API_FILES.table } }, - { path: 'i18n', component: I18nDocsComponent, data: getI18nKey('platformTable') } + { path: 'i18n', component: I18nDocsComponent, data: getI18nKey('platformTable') }, + { path: 'p13-dialog-table', component: P13DialogDocsComponent, data: { child: true } }, + { path: 'settings-dialog-table', component: SettingsDialogDocsComponent }, + { path: 'scrolling', component: TableScrollingDocsComponent }, + { path: 'row-selection', component: RowSelectionDocsComponent }, + { path: 'clickable-rows', component: ClickableRowsDocsComponent } ] } ]; @@ -125,12 +137,18 @@ const routes: Routes = [ PlatformTableResponsiveColumnsExampleComponent, PlatformTableInitialLoadingExampleComponent, PlatformTableColumnsNgforExampleComponent, - PlatformTableVirtualScrollExampleComponent + PlatformTableVirtualScrollExampleComponent, + P13DialogDocsComponent, + SettingsDialogDocsComponent, + RowSelectionDocsComponent, + TableScrollingDocsComponent, + ClickableRowsDocsComponent ], providers: [ RtlService, platformContentDensityModuleDeprecationsProvider('fdp-table'), - currentComponentProvider('table') + currentComponentProvider('table'), + ExampleChildService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/libs/docs/shared/src/index.ts b/libs/docs/shared/src/index.ts index bfc51d169cb..0cfdea7d438 100644 --- a/libs/docs/shared/src/index.ts +++ b/libs/docs/shared/src/index.ts @@ -38,3 +38,4 @@ export * from './lib/tokens/package-json.token'; export * from './lib/tokens/current-component.token'; export * from './lib/getAsset'; export * from './lib/i18n'; +export * from './lib/services/example-child.service'; diff --git a/libs/docs/shared/src/lib/core-helpers/header-tabs/header-tabs.component.html b/libs/docs/shared/src/lib/core-helpers/header-tabs/header-tabs.component.html index 8ce42af37eb..1608ffbd5a1 100644 --- a/libs/docs/shared/src/lib/core-helpers/header-tabs/header-tabs.component.html +++ b/libs/docs/shared/src/lib/core-helpers/header-tabs/header-tabs.component.html @@ -2,7 +2,7 @@
  • ): void { + link = link || './'; + this._link.next(link); + } +} diff --git a/libs/platform/src/lib/table/table.component.html b/libs/platform/src/lib/table/table.component.html index 8fb89f7b6a2..0165c6c3107 100644 --- a/libs/platform/src/lib/table/table.component.html +++ b/libs/platform/src/lib/table/table.component.html @@ -154,7 +154,7 @@ [tabIndex]="pageScrolling ? 0 : -1" [items]="_tableRows" [draggable]="_rowsDraggable" - [replaceMode]="true" + [dropMode]="dropMode" [style.transform]="_virtualScrollTransform" (itemDropped)="_dragDropItemDrop($event)" > @@ -205,8 +205,10 @@ (click)="_onRowClick(row)" (keydown.enter)="_onRowClick(row)" (keydown.space)="_onRowClick(row, $event)" - (keydown.alt.arrowup)="_dragRowFromKeyboard('up', $event, rowIdx)" - (keydown.alt.arrowdown)="_dragRowFromKeyboard('down', $event, rowIdx)" + (keydown.alt.arrowup)="_dragRowFromKeyboard('up', $event, rowIdx, 'shift')" + (keydown.alt.arrowdown)="_dragRowFromKeyboard('down', $event, rowIdx, 'shift')" + (keydown.alt.shift.arrowup)="_dragRowFromKeyboard('up', $event, rowIdx, 'group')" + (keydown.alt.shift.arrowdown)="_dragRowFromKeyboard('down', $event, rowIdx, 'group')" (started)="_dragDropStart()" [main]="true" > diff --git a/libs/platform/src/lib/table/table.component.scss b/libs/platform/src/lib/table/table.component.scss index d35696fa1f9..8d32a410ed4 100644 --- a/libs/platform/src/lib/table/table.component.scss +++ b/libs/platform/src/lib/table/table.component.scss @@ -85,7 +85,7 @@ $fd-table-scrollbar-width-with-border: calc(#{$fd-table-scrollbar-width} + var(- &.fd-dnd-on-drag { &.fd-dnd-placeholder { - padding-left: 2.8rem; + //padding-left: 2.8rem; @include fd-rtl() { padding-left: 0.5rem; diff --git a/libs/platform/src/lib/table/table.component.spec.ts b/libs/platform/src/lib/table/table.component.spec.ts index aae2e0218e0..951ec211d5f 100644 --- a/libs/platform/src/lib/table/table.component.spec.ts +++ b/libs/platform/src/lib/table/table.component.spec.ts @@ -1177,7 +1177,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 0, - draggedItemIndex: 1 + draggedItemIndex: 1, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1195,7 +1197,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 0, - draggedItemIndex: 1 + draggedItemIndex: 1, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1207,7 +1211,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 0, - draggedItemIndex: 1 + draggedItemIndex: 1, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1231,7 +1237,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 1, - draggedItemIndex: 0 + draggedItemIndex: 0, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1251,7 +1259,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 2, - draggedItemIndex: 1 + draggedItemIndex: 1, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1269,7 +1279,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 2, - draggedItemIndex: 1 + draggedItemIndex: 1, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1285,7 +1297,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 9, - draggedItemIndex: 0 + draggedItemIndex: 0, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1299,7 +1313,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 0, - draggedItemIndex: 9 + draggedItemIndex: 9, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1313,7 +1329,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 5, - draggedItemIndex: 0 + draggedItemIndex: 0, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); @@ -1327,7 +1345,9 @@ class TreeTableDataProviderMock extends TableDataProvider { tableComponent._dragDropItemDrop({ items: [], replacedItemIndex: 5, - draggedItemIndex: 9 + draggedItemIndex: 9, + mode: 'group', + insertAt: 'after' }); fixture.detectChanges(); diff --git a/libs/platform/src/lib/table/table.component.ts b/libs/platform/src/lib/table/table.component.ts index 34172b98396..9d847ca8b6a 100644 --- a/libs/platform/src/lib/table/table.component.ts +++ b/libs/platform/src/lib/table/table.component.ts @@ -30,6 +30,8 @@ import { import { NgForm } from '@angular/forms'; import { + FdDndDropEventMode, + FdDndDropType, FdDropEvent, FDK_FOCUSABLE_GRID_DIRECTIVE, FocusableCellPosition, @@ -130,6 +132,13 @@ interface GroupTableRowValueType { count: number; } +interface UpdatedDndRowsPosition { + allRows: TableRow[]; + rowsToMove: TableRow[]; + rowsAfterDropRow: TableRow[]; + dropRowItems: TableRow[]; +} + let tableUniqueId = 0; /** @@ -462,6 +471,12 @@ export class TableComponent extends Table implements AfterViewInit, @Input() renderAhead = 40; + /** + * Row drop mode. + */ + @Input() + dropMode: FdDndDropType = 'auto'; + /** Event emitted when current preset configuration has been changed. */ @Output() presetChanged = new EventEmitter(); @@ -1688,10 +1703,10 @@ export class TableComponent extends Table implements AfterViewInit, } this._dragDropUpdateDragParentRowAttributes(dragRow); - this._dragDropRearrangeTreeRows(dragRow, dropRow); - this._dragDropUpdateDropRowAttributes(dragRow, dropRow); + this._dragDropRearrangeTreeRows(dragRow, dropRow, event); + this._dragDropUpdateDropRowAttributes(dragRow, dropRow, event.mode); - if (!dropRow.expanded) { + if (!dropRow.expanded && event.mode === 'group') { this._toggleExpandableTableRow(dropRow); } else { this._onTableRowsChanged(); @@ -1744,21 +1759,26 @@ export class TableComponent extends Table implements AfterViewInit, } /** @hidden */ - _dragRowFromKeyboard(dir: string, event: Event, currentRowIndex: number): void { + _dragRowFromKeyboard(dir: string, event: Event, currentRowIndex: number, mode: 'shift' | 'group'): void { + if (!this._rowsDraggable) { + return; + } event.preventDefault(); - if (this._rowsDraggable) { - let replacedIndex; - dir === 'up' ? (replacedIndex = currentRowIndex - 1) : (replacedIndex = currentRowIndex + 1); - - if (this._tableRowsVisible[replacedIndex]) { - const dragDropEvent = { - items: this._tableRowsVisible, - draggedItemIndex: currentRowIndex, - replacedItemIndex: replacedIndex - }; - this._dragDropItemDrop(dragDropEvent); + let replacedIndex; + dir === 'up' ? (replacedIndex = currentRowIndex - 1) : (replacedIndex = currentRowIndex + 1); + + if (this._tableRowsVisible[replacedIndex]) { + const dragDropEvent: FdDropEvent> = { + items: this._tableRowsVisible, + draggedItemIndex: currentRowIndex, + replacedItemIndex: replacedIndex, + insertAt: dir === 'down' ? 'after' : 'before', + mode + }; + this._dragDropItemDrop(dragDropEvent); + setTimeout(() => { (event.target as HTMLElement).focus(); - } + }); } } @@ -1793,19 +1813,24 @@ export class TableComponent extends Table implements AfterViewInit, } /** @hidden */ - private _dragDropUpdateDropRowAttributes(dragRow: TableRow, dropRow: TableRow): void { + private _dragDropUpdateDropRowAttributes(dragRow: TableRow, dropRow: TableRow, mode: FdDndDropEventMode): void { if (dragRow.parent) { // Remove child row from previous parent row. dragRow.parent.children.splice(dragRow.parent.children.indexOf(dragRow), 1); } - dragRow.parent = dropRow; - dragRow.level = dropRow.level + 1; + dragRow.level = dropRow.level + (mode === 'group' ? 1 : 0); - if (!this._isTreeRow(dropRow)) { - dropRow.type = TableRowType.TREE; - } + if (mode === 'group') { + dragRow.parent = dropRow; + if (!this._isTreeRow(dropRow)) { + dropRow.type = TableRowType.TREE; + } - dropRow.children.push(dragRow); + dropRow.children.push(dragRow); + } else { + dragRow.parent = dropRow.parent; + dropRow.parent?.children.push(dragRow); + } const children = this._findRowChildren(dragRow); children.forEach((row) => { @@ -1814,7 +1839,16 @@ export class TableComponent extends Table implements AfterViewInit, } /** @hidden */ - private _dragDropRearrangeTreeRows(dragRow: TableRow, dropRow: TableRow): void { + private _dragDropRearrangeTreeRows(dragRow: TableRow, dropRow: TableRow, event: FdDropEvent): void { + if (event.mode === 'shift') { + this._handleShiftDropAction(dragRow, dropRow, event); + } else { + this._handleReplaceDropAction(dragRow, dropRow, event); + } + } + + /** @hidden */ + private _getNewDragDropRowsPosition(dragRow: TableRow, dropRow: TableRow): UpdatedDndRowsPosition { const allRows = this._tableRows; const dragRowIndex = allRows.findIndex((row) => row === dragRow); @@ -1825,10 +1859,43 @@ export class TableComponent extends Table implements AfterViewInit, const dropRowIndex = allRows.findIndex((row) => row === dropRow); const dropRowChildren = this._findRowChildren(dropRow); - const rowsBefore = allRows.slice(0, dropRowIndex + dropRowChildren.length + 1); - const rowsAfter = allRows.slice(dropRowIndex + dropRowChildren.length + 1); + const dropRowItemsLength = dropRowChildren.length + 1; + + const rowsAfterDropRow = allRows.splice(dropRowIndex + dropRowItemsLength, allRows.length + dropRowItemsLength); + const dropRowItems = allRows.splice(dropRowIndex, dropRowItemsLength); + + return { + allRows, + rowsToMove, + rowsAfterDropRow, + dropRowItems + }; + } + + /** @hidden */ + private _handleShiftDropAction(dragRow: TableRow, dropRow: TableRow, event: FdDropEvent): void { + const { allRows, rowsToMove, rowsAfterDropRow, dropRowItems } = this._getNewDragDropRowsPosition( + dragRow, + dropRow + ); + + this._tableRows = [ + ...allRows, + ...(event.insertAt === 'after' ? dropRowItems : []), + ...rowsToMove, + ...(event.insertAt === 'after' ? [] : dropRowItems), + ...rowsAfterDropRow + ]; + } + + /** @hidden */ + private _handleReplaceDropAction(dragRow: TableRow, dropRow: TableRow, event: FdDropEvent): void { + const { allRows, rowsToMove, rowsAfterDropRow, dropRowItems } = this._getNewDragDropRowsPosition( + dragRow, + dropRow + ); - this._tableRows = [...rowsBefore, ...rowsToMove, ...rowsAfter]; + this._tableRows = [...allRows, ...dropRowItems, ...rowsToMove, ...rowsAfterDropRow]; } /** @hidden */ diff --git a/tsconfig.base.json b/tsconfig.base.json index 7c134c3469a..59e6051e534 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -136,6 +136,7 @@ "@fundamental-ngx/docs/cdk/clicked": ["libs/docs/cdk/clicked/index.ts"], "@fundamental-ngx/docs/cdk/data-source": ["libs/docs/cdk/data-source/index.ts"], "@fundamental-ngx/docs/cdk/disabled": ["libs/docs/cdk/disabled/index.ts"], + "@fundamental-ngx/docs/cdk/drag-n-drop": ["libs/docs/cdk/drag-n-drop/index.ts"], "@fundamental-ngx/docs/cdk/focusable-grid": ["libs/docs/cdk/focusable-grid/index.ts"], "@fundamental-ngx/docs/cdk/focusable-item": ["libs/docs/cdk/focusable-item/index.ts"], "@fundamental-ngx/docs/cdk/focusable-list": ["libs/docs/cdk/focusable-list/index.ts"],