ngx-datatable icon indicating copy to clipboard operation
ngx-datatable copied to clipboard

Horizontal scroll bar is not shown when datatable is empty

Open mingzhi5626 opened this issue 6 years ago • 12 comments

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, post on Stackoverflow or Gitter

Current behavior

If I am using [scrollbarH]="true" and if there it is no rows in datatable, the horizontal scroll bar is not shown, and header is cut off and cannot see more header columns in right side.

Expected behavior

Horizontal scroll bar shows on page and user should be able to see header columns even datatable is empty.

Reproduction of the problem

in demo HorzVertScrolling, comment out this code snippet: /* this.fetch((data) => { this.rows = data; }); */

you can find there is no horizontal scrolling bar and state column on right side is hidden.

screen shot 2018-04-10 at 16 32 07

What is the motivation / use case for changing the behavior?

This is not a user friendly experience when the table is empty.

Please tell us about your environment:

  • Table version: 0.8.x

swimlane/ngx-datatable: "11.2.0",

  • Angular version: 2.0.x

angular/common: "^4.1.3"

  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

Chrome

  • Language: [all | TypeScript X.X | ES6/7 | ES5] typescript: "2.5.3",

mingzhi5626 avatar Apr 10 '18 20:04 mingzhi5626

I have the same issue

nethulap avatar May 25 '18 01:05 nethulap

I had same issue, but doing lots of R&D , I did some custom code, it solve my problem. I created a common method and call from component when data not available.

like : setEmptyMessage() { let msg = 'No data to display.'; if (!this.tableData.length) { this.tableData = [{ 'message': msg }]; } else { delete this.tableData[0]['message']; msg = ''; } this.setEmptyMessageDataTable(msg); }

// create a common method like this setEmptyMessageDataTable(msg) { const hideClass = document.querySelector('datatable-body-row'); const divelm = document.querySelector('#noMsg'); if (msg) { if (hideClass) { hideClass.setAttribute('style', 'display:none;'); }

        if (divelm) {
            document.querySelector('#noMsg').remove();
        }
        // Create a new element
        const element = document.createElement('p');
        element.id = 'noMsg';
        element.innerHTML = msg;
        document.querySelector('datatable-row-wrapper').appendChild(element);
    } else {
        if (divelm) {
            document.querySelector('#noMsg').remove();
        }
        if (hideClass) {
            hideClass.removeAttribute('style');
        }

    }

}

// use setEmptyMessage() where you are fetching dataTable data

this.yourApi.method(req).subscribe( results => { this.setEmptyMessage(); }, error => { } )

renuverma03 avatar Jun 01 '18 09:06 renuverma03

I am facing the same issue, Has anyone found the solution?

abhishek-misra avatar Mar 23 '19 04:03 abhishek-misra

I had same issue, but doing lots of R&D , I did some custom code, it solve my problem. I created a common method and call from component when data not available.

like : setEmptyMessage() { let msg = 'No data to display.'; if (!this.tableData.length) { this.tableData = [{ 'message': msg }]; } else { delete this.tableData[0]['message']; msg = ''; } this.setEmptyMessageDataTable(msg); }

// create a common method like this setEmptyMessageDataTable(msg) { const hideClass = document.querySelector('datatable-body-row'); const divelm = document.querySelector('#noMsg'); if (msg) { if (hideClass) { hideClass.setAttribute('style', 'display:none;'); }

        if (divelm) {
            document.querySelector('#noMsg').remove();
        }
        // Create a new element
        const element = document.createElement('p');
        element.id = 'noMsg';
        element.innerHTML = msg;
        document.querySelector('datatable-row-wrapper').appendChild(element);
    } else {
        if (divelm) {
            document.querySelector('#noMsg').remove();
        }
        if (hideClass) {
            hideClass.removeAttribute('style');
        }

    }

}

// use setEmptyMessage() where you are fetching dataTable data

this.yourApi.method(req).subscribe( results => { this.setEmptyMessage(); }, error => { } )

Do you have any working example. I tried the same code but did not work.

satyajit-shetye avatar Apr 10 '19 08:04 satyajit-shetye

import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core';

@Directive({
    selector: '[emptyRow]'
})
export class EmptyRowDirective implements OnInit, AfterViewChecked {

    textElement: any;
    actionElement : any;

    constructor(private renderer: Renderer2, 
        private hostElement: ElementRef) {
    }

    ngOnInit() {
        this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => {
            this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft;
        });
    }

    ngAfterViewChecked(){
        this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width;
    }

}

Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.

satyajit-shetye avatar May 16 '19 06:05 satyajit-shetye

import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core';

@Directive({
    selector: '[emptyRow]'
})
export class EmptyRowDirective implements OnInit, AfterViewChecked {

    textElement: any;
    actionElement : any;

    constructor(private renderer: Renderer2, 
        private hostElement: ElementRef) {
    }

    ngOnInit() {
        this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => {
            this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft;
        });
    }

    ngAfterViewChecked(){
        this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width;
    }

}

Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.

This should be a slight improvement over your previous solution:

import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core";

/**
 * Directive to add an empty row to `ngx-datatable components that do not have any data.
 * This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
 */
@Directive({
  selector: "[datNgxEmptyRow]"
})
export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked {
  private datatableBodyEl: HTMLElement;
  private datatableHeaderEl: HTMLElement;
  private datatableEmptyRowEl: HTMLElement;
  private datatableRowCenterEl: HTMLElement;

  constructor(private renderer: Renderer2, private hostElRef: ElementRef) {}

  public ngAfterViewInit() {
    this.setUpElementReferences();
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
      this.setUpHeaderScrollListener();
    }
  }

  public ngAfterViewChecked() {
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
    }
  }

  private setUpHeaderScrollListener() {
    this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => {
      this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft;
    });
  }

  private hackEmptyCellWidth() {
    const newWidth = this.datatableRowCenterEl.style.width;
    this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth);
  }

  private setUpElementReferences() {
    const hostEl = this.hostElRef.nativeElement;
    this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0];
    this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0];
    this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
    this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0];
  }

  private get shouldApplyHack(): boolean {
    return !!this.datatableEmptyRowEl;
  }
}

It's not necessary to be this verbose, I just prefer it for my specific use case. This adds a quick check to avoid errors and uses the Render2 class

tabuckner avatar Jul 16 '19 16:07 tabuckner

import { Directive, ElementRef, Renderer2, OnInit, AfterViewChecked } from '@angular/core';

@Directive({
    selector: '[emptyRow]'
})
export class EmptyRowDirective implements OnInit, AfterViewChecked {

    textElement: any;
    actionElement : any;

    constructor(private renderer: Renderer2, 
        private hostElement: ElementRef) {
    }

    ngOnInit() {
        this.hostElement.nativeElement.getElementsByClassName('datatable-body')[0].addEventListener("scroll", event => {
            this.hostElement.nativeElement.getElementsByClassName('datatable-header')[0].scrollLeft = event.srcElement.scrollLeft;
        });
    }

    ngAfterViewChecked(){
        this.hostElement.nativeElement.getElementsByClassName('empty-row')[0].style.width = this.hostElement.nativeElement.getElementsByClassName('datatable-row-center')[0].style.width;
    }

}

Created a directive to achieve this. Add this directive to ngx-datable tag. This is working for now. Any suggestions for improvements is welcome.

This should be a slight improvement over your previous solution:

import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core";

/**
 * Directive to add an empty row to `ngx-datatable components that do not have any data.
 * This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
 */
@Directive({
  selector: "[datNgxEmptyRow]"
})
export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked {
  private datatableBodyEl: HTMLElement;
  private datatableHeaderEl: HTMLElement;
  private datatableEmptyRowEl: HTMLElement;
  private datatableRowCenterEl: HTMLElement;

  constructor(private renderer: Renderer2, private hostElRef: ElementRef) {}

  public ngAfterViewInit() {
    this.setUpElementReferences();
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
      this.setUpHeaderScrollListener();
    }
  }

  public ngAfterViewChecked() {
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
    }
  }

  private setUpHeaderScrollListener() {
    this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => {
      this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft;
    });
  }

  private hackEmptyCellWidth() {
    const newWidth = this.datatableRowCenterEl.style.width;
    this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth);
  }

  private setUpElementReferences() {
    const hostEl = this.hostElRef.nativeElement;
    this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0];
    this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0];
    this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
    this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0];
  }

  private get shouldApplyHack(): boolean {
    return !!this.datatableEmptyRowEl;
  }
}

It's not necessary to be this verbose, I just prefer it for my specific use case. This adds a quick check to avoid errors and uses the Render2 class

I tried this fix but it "sort of work", if there is data in the data-table and I move the horizontal scrollbar to the right, the column headers are pushed to the left instead of staying in position.

LuisMoralesMx avatar Oct 08 '19 14:10 LuisMoralesMx

Now if not working properly if we have data. Ngx datatable header and body is not aligned.

santosh088 avatar Jan 02 '20 19:01 santosh088

is there any solution to this problem I tried the proposed directive but not working with the pinning section

SanaeHanaoui avatar Apr 01 '20 16:04 SanaeHanaoui

// https://github.com/swimlane/ngx-datatable/issues/1367#issuecomment-511893170
import { Directive, Renderer2, ElementRef, AfterViewInit, AfterViewChecked } from "@angular/core";

/**
 * Directive to add an empty row to `ngx-datatable components that do not have any data.
 * This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
 */
@Directive({
    selector: "[datNgxEmptyRow]"
})
export class NgxEmptyRowDirective implements AfterViewInit, AfterViewChecked {
    private datatableBodyEl: HTMLElement;
    private datatableHeaderEl: HTMLElement;
    private datatableEmptyRowEl: HTMLElement;
    private datatableRowCenterEl: HTMLElement;

    constructor(private renderer: Renderer2, private hostElRef: ElementRef) { }

    public ngAfterViewInit() {
        this.setUpElementReferences();
        if (this.shouldApplyHack) {
            this.hackEmptyCellWidth();
            this.setUpHeaderScrollListener();
        }
    }

    public ngAfterViewChecked() {
        if (this.shouldApplyHack) {
            this.hackEmptyCellWidth();
        }
    }

    private setUpHeaderScrollListener() {
        this.renderer.listen(this.datatableBodyEl, "scroll", (event: any) => {
            if (this.shouldApplyHack) {
                this.datatableHeaderEl.scrollLeft = event.srcElement.scrollLeft;
            }
        });
    }

    private hackEmptyCellWidth() {
        const newWidth = this.datatableRowCenterEl.style.width;
        this.renderer.setStyle(this.datatableEmptyRowEl, "width", newWidth);
    }

    private setUpElementReferences() {
        const hostEl = this.hostElRef.nativeElement;
        this.datatableBodyEl = hostEl.getElementsByClassName("datatable-body")[0];
        this.datatableHeaderEl = hostEl.getElementsByClassName("datatable-header")[0];
        this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
        this.datatableRowCenterEl = hostEl.getElementsByClassName("datatable-row-center")[0];
    }

    private get shouldApplyHack(): boolean {
        const hostEl = this.hostElRef.nativeElement;
        this.datatableEmptyRowEl = hostEl.getElementsByClassName("empty-row")[0];
        return !!this.datatableEmptyRowEl;
    }
}

ramakrishnamundru avatar Jun 01 '20 11:06 ramakrishnamundru

A more improved version of the fix, instead of using scrollLeft, use the transformers that the data table uses internally, I made this improvement because we found some inconsistencies when the table has one row, the user scroll to the right and finally add another filter to remove the row, in that scenario the header scroll messed up.

// https://github.com/swimlane/ngx-datatable/issues/1367#issuecomment-511893170
import {
  Directive,
  Renderer2,
  ElementRef,
  AfterViewInit,
  AfterViewChecked,
} from '@angular/core';

/**
 * Directive to add an empty row to `ngx-datatable components that do not have any data.
 * This fixes a bug with ngx-datatable in which empty tables do not have a scrollable header.
 */
@Directive({
  selector: '[appEmptyRow]',
})
export class EmptyRowDirective implements AfterViewInit, AfterViewChecked {
  private datatableBodyEl!: HTMLElement;
  private datatableEmptyRowEl!: HTMLElement;
  private datatableRowCenterEl!: HTMLElement;
  private prevNumberOfRows!: number;
  constructor(private renderer: Renderer2, private hostElRef: ElementRef) { }

  public ngAfterViewInit() {
    this.setUpElementReferences();
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
      this.setUpHeaderScrollListener();
    }
  }

  public ngAfterViewChecked() {
    if (this.shouldApplyHack) {
      this.hackEmptyCellWidth();
    }
    this.restartScrollingOnRowChanges();
  }

  private restartScrollingOnRowChanges() {
    if (this.prevNumberOfRows !== this.numberOfRows) {
      this.prevNumberOfRows = this.numberOfRows;
      this.datatableBodyEl.scrollTo(1, 0);
      this.renderer.setStyle(this.datatableRowCenterEl, 'transform', 'translate3d(0px, 0px, 0px)');
    }
  }

  private setUpHeaderScrollListener() {
    this.renderer.listen(this.datatableBodyEl, 'scroll', (event: any) => {
      if (this.shouldApplyHack) {
        this.renderer.setStyle(this.datatableRowCenterEl, 'transform', `translate3d(-${event.srcElement.scrollLeft}px, 0px, 0px)`)
      }
    });
  }

  private hackEmptyCellWidth() {
    const newWidth = this.datatableRowCenterEl.style.width;
    this.renderer.setStyle(this.datatableEmptyRowEl, 'width', newWidth);
  }

  private setUpElementReferences() {
    const hostEl = this.hostElRef.nativeElement;
    this.datatableBodyEl = hostEl.getElementsByClassName('datatable-body')[0];
    this.datatableEmptyRowEl = hostEl.getElementsByClassName('empty-row')[0];
    this.datatableRowCenterEl = hostEl.getElementsByClassName('datatable-row-center')[0];
  }

  private get shouldApplyHack(): boolean {
    const hostEl = this.hostElRef.nativeElement;
    this.datatableEmptyRowEl = hostEl.getElementsByClassName('empty-row')[0];
    return !!this.datatableEmptyRowEl;
  }

  private get numberOfRows(): number {
    const hostEl = this.hostElRef.nativeElement;
    return hostEl.getElementsByClassName('datatable-body-row').length;
  }
}


jusemon avatar Dec 21 '22 22:12 jusemon

any updates on this issue?

Bettaswamy avatar May 14 '23 22:05 Bettaswamy