使用按键在 PrimeNG 滚动 Table 上选择行不会更改滚动位置
Selecting Rows On PrimeNG Scroll Table With Keypress Doesn't Change Scroll Location
几个星期前,我问并回答了 。
截至目前,代码允许用户在“搜索”输入处于焦点时在 table 行上上下导航并单击“输入”以 select table 行。但是,当使用滚动 table 时,如 stackblitz 所示,selected table 行不会出现,因为它隐藏在滚动条下方。如果我使用 tab
导航到 table 单元格,那么它也会滚动,但不会像在这个用例中那样使用箭头键。
是否可以根据 selectedProduct 移动滚动条位置,这样 table 行就不会隐藏在下方?
Table 在 HTML 中设置:
<p-table
#dl
[columns]="cols"
[value]="products"
selectionMode="single"
[(selection)]="selectedProduct"
(onFilter)="onFilter($event, dt)"
[scrollable]="true"
scrollHeight="270px"
>
...
<!-- table columns and code here -->
</p-table>
组件 TS(删除了一些不必要的信息 -- 可以在 StackBlitz 中找到)
// imports
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild('dl') table: Table;
cpt = 0;
products: Product[];
visibleProducts: Product[];
selectedProduct: Product;
cols: any[];
constructor( ...) {}
ngOnInit() {
this.productService.getProductsSmall().then((data) => {
this.products = data.slice();
this.visibleProducts = this.products;
this.selectedProduct = this.visibleProducts[0];
});
this.cols = [...];
}
onFilter(event, dt) {
this.cpt = 0;
if (event.filteredValue.length > 0) {
this.selectedProduct = event.filteredValue[0];
this.visibleProducts = event.filteredValue;
}
}
@HostListener('keydown.ArrowUp', ['$event']) ArrowUp($event: KeyboardEvent) {
if (this.cpt > 0) {
this.cpt--;
}
this.selectedProduct = this.visibleProducts[this.cpt];
}
@HostListener('keydown.ArrowDown', ['$event']) ArrowDown(
$event: KeyboardEvent
) {
if (this.cpt < this.visibleProducts.length - 1) {
this.cpt++;
}
this.selectedProduct = this.visibleProducts[this.cpt];
}
@HostListener('keydown.Enter', ['$event']) Enter($event: KeyboardEvent) {
alert('opening product: ' + this.selectedProduct.name);
}
}
好的,我最终找到了一个可能的解决方案。我还创建了一个 issue for PrimeNG 以希望将来包含类似的内容,因为以前对此类功能的功能请求尚未完成。
我在我的组件中添加了一个名为 scrollTable()
的函数。
这个函数所做的是首先获取数据table 包装器元素,然后是这个包装器的行。
接下来,我根据之前的 cpt
全局变量将 rowEl
设置为等于当前活动行。如果行项目是数组中的第一个,包装器将滚动到顶部。如果它不是第一项,那么一个小检查是 运行 查看偏移量是否大于总体大小(从 wrapper.offsetHeight 看起来与 p-table
相同' s scrollHeight
属性)。然后计算包装器的 scrolltop 位置并用一个小的偏移量更改以适应 table 行的大小:
app.component.ts
...
scrollTable() {
// No need to scroll if there aren't any products
if (!this.products.length) {
return;
}
let wrapper = this.table.el.nativeElement.children[0].getElementsByClassName(
'p-datatable-wrapper'
)[0];
let rows = wrapper.getElementsByClassName('p-selectable-row');
console.log(wrapper);
let rowEl = rows[this.cpt];
// Scroll to top if first item
if (this.cpt === 0) {
wrapper.scrollTop = 0;
}
// Change scroll position if not first item and at bottom of scrollbar
if (rowEl.offsetTop + rowEl.offsetHeight > wrapper.offsetHeight) {
wrapper.scrollTop +=
rowEl.offsetTop +
rowEl.offsetHeight -
wrapper.scrollTop -
wrapper.offsetHeight +
18;
}
}
...
这是一个 updated StackBlitz 组件中的有效解决方案。
感谢 this GitHub issue 中的评论,它们为我提供了完成滚动部分的初始步骤(因为我已经拥有箭头键功能)
更新
我制定了一个指令,不仅处理 PrimeNG table 行的箭头键导航,还处理滚动。这适用于任何单个 selectable table 包括使用过滤器时。过滤器通过 @Input
传入,突出显示的行通过 @Output
传入。它可能并不完美,但在用户需要更多键盘功能时解决了很多用例。当按下 enter
时,onRowSelect(...)
从 table 中触发,您可以使用组件中的任何函数。希望对其他人有帮助...
import {
Directive,
HostListener,
Output,
EventEmitter,
Input,
} from '@angular/core';
import { Table } from 'primeng/table';
@Directive({
selector: '[tableNavigation]',
})
export class TableNavigationDirective {
/////////
// (onRowSelect)="doSomething()"
// (highlightProduct)="selectedProduct = $event" for the highlighting.
// 'selectedProduct' will be equal to the [(selection)] value in your component
// [visibleItems]="visibleItems" from component for filtering
/////////
@Output() highlightProduct: EventEmitter<any> = new EventEmitter();
// If you want to include filtering, you'll want to use this input.
_visibleItems: any;
@Input('visibleItems') set visibleItems(items: any) {
this._visibleItems = items;
if (this._visibleItems) {
this.rowIndex = 0;
this.highlightProduct.emit(this._visibleItems[0]);
if (this.table.scrollable) {
this.scrollTable();
}
}
}
wrapper: any;
rowIndex = 0;
constructor(private table: Table) {
this._visibleItems = this.table.value;
}
@HostListener('keydown.ArrowUp', ['$event']) ArrowUp($event: KeyboardEvent) {
$event.preventDefault();
if (this.rowIndex > 0) {
this.rowIndex--;
}
this.highlightProduct.emit(this._visibleItems[this.rowIndex]);
if (this.table.scrollable) {
this.scrollTable();
}
}
@HostListener('keydown.ArrowDown', ['$event']) ArrowDown(
$event: KeyboardEvent
) {
$event.preventDefault();
if (this.rowIndex < this._visibleItems.length - 1) {
this.rowIndex++;
}
this.highlightProduct.emit(this._visibleItems[this.rowIndex]);
if (this.table.scrollable) {
this.scrollTable();
}
}
@HostListener('keydown.Enter', ['$event']) Enter($event: KeyboardEvent) {
this.table.onRowSelect.emit({
originalEvent: $event,
index: this.rowIndex,
data: this.table.selection,
type: 'row',
});
}
scrollTable() {
if (this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-scrollable-body')[0]) {
this.wrapper = this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-scrollable-body')[0];
} else if (this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-wrapper')[0]) {
this.wrapper = this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-wrapper')[0];
} else {
this.wrapper = undefined;
console.error("wrapper is undefined, scroll won't work");
}
let rows = this.wrapper.getElementsByClassName('p-selectable-row');
// No need to scroll if there aren't any products
if (!rows.length) {
return;
}
let rowEl = rows[this.rowIndex];
// Scroll all the way to top if first item
if (rowEl === rows[0]) {
this.wrapper.scrollTop = 0;
}
// Change scroll position if not first item and at bottom of scrollbar
if (rowEl.offsetTop + rowEl.offsetHeight > this.wrapper.offsetHeight) {
this.wrapper.scrollTop +=
rowEl.offsetTop +
rowEl.offsetHeight -
this.wrapper.scrollTop -
this.wrapper.offsetHeight;
}
}
}
几个星期前,我问并回答了
截至目前,代码允许用户在“搜索”输入处于焦点时在 table 行上上下导航并单击“输入”以 select table 行。但是,当使用滚动 table 时,如 stackblitz 所示,selected table 行不会出现,因为它隐藏在滚动条下方。如果我使用 tab
导航到 table 单元格,那么它也会滚动,但不会像在这个用例中那样使用箭头键。
是否可以根据 selectedProduct 移动滚动条位置,这样 table 行就不会隐藏在下方?
Table 在 HTML 中设置:
<p-table
#dl
[columns]="cols"
[value]="products"
selectionMode="single"
[(selection)]="selectedProduct"
(onFilter)="onFilter($event, dt)"
[scrollable]="true"
scrollHeight="270px"
>
...
<!-- table columns and code here -->
</p-table>
组件 TS(删除了一些不必要的信息 -- 可以在 StackBlitz 中找到)
// imports
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild('dl') table: Table;
cpt = 0;
products: Product[];
visibleProducts: Product[];
selectedProduct: Product;
cols: any[];
constructor( ...) {}
ngOnInit() {
this.productService.getProductsSmall().then((data) => {
this.products = data.slice();
this.visibleProducts = this.products;
this.selectedProduct = this.visibleProducts[0];
});
this.cols = [...];
}
onFilter(event, dt) {
this.cpt = 0;
if (event.filteredValue.length > 0) {
this.selectedProduct = event.filteredValue[0];
this.visibleProducts = event.filteredValue;
}
}
@HostListener('keydown.ArrowUp', ['$event']) ArrowUp($event: KeyboardEvent) {
if (this.cpt > 0) {
this.cpt--;
}
this.selectedProduct = this.visibleProducts[this.cpt];
}
@HostListener('keydown.ArrowDown', ['$event']) ArrowDown(
$event: KeyboardEvent
) {
if (this.cpt < this.visibleProducts.length - 1) {
this.cpt++;
}
this.selectedProduct = this.visibleProducts[this.cpt];
}
@HostListener('keydown.Enter', ['$event']) Enter($event: KeyboardEvent) {
alert('opening product: ' + this.selectedProduct.name);
}
}
好的,我最终找到了一个可能的解决方案。我还创建了一个 issue for PrimeNG 以希望将来包含类似的内容,因为以前对此类功能的功能请求尚未完成。
我在我的组件中添加了一个名为 scrollTable()
的函数。
这个函数所做的是首先获取数据table 包装器元素,然后是这个包装器的行。
接下来,我根据之前的 cpt
全局变量将 rowEl
设置为等于当前活动行。如果行项目是数组中的第一个,包装器将滚动到顶部。如果它不是第一项,那么一个小检查是 运行 查看偏移量是否大于总体大小(从 wrapper.offsetHeight 看起来与 p-table
相同' s scrollHeight
属性)。然后计算包装器的 scrolltop 位置并用一个小的偏移量更改以适应 table 行的大小:
app.component.ts
...
scrollTable() {
// No need to scroll if there aren't any products
if (!this.products.length) {
return;
}
let wrapper = this.table.el.nativeElement.children[0].getElementsByClassName(
'p-datatable-wrapper'
)[0];
let rows = wrapper.getElementsByClassName('p-selectable-row');
console.log(wrapper);
let rowEl = rows[this.cpt];
// Scroll to top if first item
if (this.cpt === 0) {
wrapper.scrollTop = 0;
}
// Change scroll position if not first item and at bottom of scrollbar
if (rowEl.offsetTop + rowEl.offsetHeight > wrapper.offsetHeight) {
wrapper.scrollTop +=
rowEl.offsetTop +
rowEl.offsetHeight -
wrapper.scrollTop -
wrapper.offsetHeight +
18;
}
}
...
这是一个 updated StackBlitz 组件中的有效解决方案。
感谢 this GitHub issue 中的评论,它们为我提供了完成滚动部分的初始步骤(因为我已经拥有箭头键功能)
更新
我制定了一个指令,不仅处理 PrimeNG table 行的箭头键导航,还处理滚动。这适用于任何单个 selectable table 包括使用过滤器时。过滤器通过 @Input
传入,突出显示的行通过 @Output
传入。它可能并不完美,但在用户需要更多键盘功能时解决了很多用例。当按下 enter
时,onRowSelect(...)
从 table 中触发,您可以使用组件中的任何函数。希望对其他人有帮助...
import {
Directive,
HostListener,
Output,
EventEmitter,
Input,
} from '@angular/core';
import { Table } from 'primeng/table';
@Directive({
selector: '[tableNavigation]',
})
export class TableNavigationDirective {
/////////
// (onRowSelect)="doSomething()"
// (highlightProduct)="selectedProduct = $event" for the highlighting.
// 'selectedProduct' will be equal to the [(selection)] value in your component
// [visibleItems]="visibleItems" from component for filtering
/////////
@Output() highlightProduct: EventEmitter<any> = new EventEmitter();
// If you want to include filtering, you'll want to use this input.
_visibleItems: any;
@Input('visibleItems') set visibleItems(items: any) {
this._visibleItems = items;
if (this._visibleItems) {
this.rowIndex = 0;
this.highlightProduct.emit(this._visibleItems[0]);
if (this.table.scrollable) {
this.scrollTable();
}
}
}
wrapper: any;
rowIndex = 0;
constructor(private table: Table) {
this._visibleItems = this.table.value;
}
@HostListener('keydown.ArrowUp', ['$event']) ArrowUp($event: KeyboardEvent) {
$event.preventDefault();
if (this.rowIndex > 0) {
this.rowIndex--;
}
this.highlightProduct.emit(this._visibleItems[this.rowIndex]);
if (this.table.scrollable) {
this.scrollTable();
}
}
@HostListener('keydown.ArrowDown', ['$event']) ArrowDown(
$event: KeyboardEvent
) {
$event.preventDefault();
if (this.rowIndex < this._visibleItems.length - 1) {
this.rowIndex++;
}
this.highlightProduct.emit(this._visibleItems[this.rowIndex]);
if (this.table.scrollable) {
this.scrollTable();
}
}
@HostListener('keydown.Enter', ['$event']) Enter($event: KeyboardEvent) {
this.table.onRowSelect.emit({
originalEvent: $event,
index: this.rowIndex,
data: this.table.selection,
type: 'row',
});
}
scrollTable() {
if (this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-scrollable-body')[0]) {
this.wrapper = this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-scrollable-body')[0];
} else if (this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-wrapper')[0]) {
this.wrapper = this.table.el.nativeElement.children[0].getElementsByClassName('p-datatable-wrapper')[0];
} else {
this.wrapper = undefined;
console.error("wrapper is undefined, scroll won't work");
}
let rows = this.wrapper.getElementsByClassName('p-selectable-row');
// No need to scroll if there aren't any products
if (!rows.length) {
return;
}
let rowEl = rows[this.rowIndex];
// Scroll all the way to top if first item
if (rowEl === rows[0]) {
this.wrapper.scrollTop = 0;
}
// Change scroll position if not first item and at bottom of scrollbar
if (rowEl.offsetTop + rowEl.offsetHeight > this.wrapper.offsetHeight) {
this.wrapper.scrollTop +=
rowEl.offsetTop +
rowEl.offsetHeight -
this.wrapper.scrollTop -
this.wrapper.offsetHeight;
}
}
}