swimlane / ngx-datatable

✨ A feature-rich yet lightweight data-table crafted for Angular
http://swimlane.github.io/ngx-datatable/
MIT License
4.63k stars 1.68k forks source link

Only one row shown when scrollbarV is enabled. #1924

Open sapabg opened 4 years ago

sapabg commented 4 years ago

I'm submitting a

[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

<ngx-datatable *ngIf="(firm$ | async) && (contragents$ | async)"

table

  [sortType]="'multi'"     
  [headerHeight]="60" 
  [footerHeight]="50"
  [rows]="rows" 
  [columns]="columns" 
  [rowHeight]="60"
  [columnMode]="'force'"
  class="material"   
  [limit]="10"
  [scrollbarV]="true"
  (tableContextmenu)="onTableContextMenu($event)">

This code produces a table that is scrollable but there is only one visible row - basically one row per page.

Expected behavior

It is suppose to show 10 rows per page or at least allow me to set how high or how many elements should the virtual scroll show.

Reproduction of the problem tried to create a plunker but I can't make it work at all.

Please tell us about your environment:

"@swimlane/ngx-datatable": "^17.1.0",

I copied the code from the demo and the result is the same - only one row but the table is scrollable.

brandon-morgan commented 3 years ago

Same issue here after upgrading to v18.0.0 from 15.0.2. Removing [scrollbarV]="true" allows rows to show but can't seem to get both working with vertical scroll

PolarbearDK commented 2 years ago

To anyone who run into this or similar problems:

ngx-datatable calculates how many rows to show based on available space, height of items, header and footer ext. This calculation is performed once when input changes.

If ngx-datatable somehow is coerced into making this calculation at a bad time (no data, control not visible ect.) then it seems to fallback to 1 row.

We have ngx-datatable inside a tab with data being reloaded periodically. If ngx-datatable were hidden when background fetch occurred, then we would get 1 row visible. We fixed this by detecting when tab becomes visible (IntersectionObserver), and changing one of the ngx-datatable parameters to trigger a recalculation,

mrobi commented 3 months ago

I tried to refresh the table and even called the recalculate() function, but in Firefox, and only in Firefox, it still shows everything in one row. It happens only with [scrollbarV]="true" setup

nicoobkio commented 3 months ago

This issue has been affecting us here too since the last 2 days. Only seems to be present for the latest version of firefox.

Here's a stackblitz: https://stackblitz.com/edit/angular-nhma2y?file=src%2Fmain.ts

orimyth commented 3 months ago

This issue has been affecting us here too since the last 2 days. Only seems to be present for the latest version of firefox.

Here's a stackblitz: https://stackblitz.com/edit/angular-nhma2y?file=src%2Fmain.ts

Same issue for me, was working in Firefox 124.0.1 and since 126.0 it breaked, changed nothing in my code. Chromium browsers work fine

Marklb commented 3 months ago

NOTE: I am not positive if this is related to the original reported problem, since my problem and the last commenters problem seems to be from a recent Firefox change. The original post doesn't specify their browser, so it is hard to say if they were maybe using an old or non-typical browser that had an issue with the transform or translate3d features. I think they were referring to a problem with how they were using ngx-datatable and not necessarily a bug with ngx-datatable, but it isn't clear. So, I may open another issue for this Firefox problem.

I took a look at this and I didn't get all the way into what exactly was changed in Firefox, but I found enough to hopefully fix our applications, at least.

Here is what I found:

In ngx-datatable, the rows are positioned using transform: translate3d(${x}px, ${y}px, 0px). The transform is being set on an object that will be applied to the element's style property. That object is build with the following translateXY() function: https://github.com/swimlane/ngx-datatable/blob/5eed8493fa016bcbf49f7fe7d9bc39e41916a4e7/projects/swimlane/ngx-datatable/src/lib/utils/translate.ts#L12-L24

Since ngx-datatable is using vendor prefixes, the resulting object will be { '-moz-transform': `translate3d(${x}px, ${y}px), 0px)`, 'backface-visibility': 'hidden' } in Firefox.

If ngx-datatable was setting those properties directly on the element's styles object then there would not be any problem, but that task is handed off to Angular's ngStyle directive. https://github.com/swimlane/ngx-datatable/blob/5eed8493fa016bcbf49f7fe7d9bc39e41916a4e7/projects/swimlane/ngx-datatable/src/lib/components/body/body.component.ts#L55

Angular's ngStyle directive iterates the object's properties and applies them to the element's style object, which is correct and what we want it to do. The problem is how it does that, which should not be a problem, but Firefox apparently made a change that causes Angular's way to be a problem. They use Renderer2 to set the properties, using the setProperty method. So, the ngStyle directive will end up setting the properties, from the object I mentioned above, the following way:

element.style.setProperty('-moz-transform', `translate3d(${x}px, ${y}px, 0px)`, '')
element.style.setProperty('backface-visibility', 'hidden')

Firefox has an open issue, https://bugzilla.mozilla.org/show_bug.cgi?id=1886134, which is the source of the problem, I think. This is mainly a guess, since I didn't dig into Firefox's code, but I assume the vendor prefix was removed from the core and still exists in the CSS engine. So, I assume setProperty gets handled by the core that no longer recognizes the property and just ignores it without saying anything, for some reason. If you set the property on the element's style property directly then I assume that is passed on to the CSS engine and it still recognizes the property.

If you want to test fixes, without reverting to an older version of Firefox, you can toggle the feature back to how it used to be by going to about:config and setting layout.css.prefixes.transforms to true.

Possible fix:

I haven't done thorough testing to confirm, but is there a reason to still bother with vendor prefixes? At this point, shouldn't all the browsers that Angular supports, also support transform without the need for a vendor prefix? If so, the problem should be fixable by dropping that functionality from ngx-datatable and just using the unprefixed transform property/rule.

If, the prefix is still potentially needed, would it be fine to just set both the prefixed and non-prefixed? So, the return from translateXY() on Firefox would be { '-moz-transform': `translate3d(${x}px, ${y}px), 0px)`, 'transform': `translate3d(${x}px, ${y}px), 0px)`, 'backface-visibility': 'hidden' }

Workarounds:

I didn't find a workaround that I really like, but these may work until ngx-datatable or Firefox is updated.

Option 1: Since, Angular uses Renderer2, you may be able to provide a different Renderer2 that modifies the setProperty method to replace the prefix or not use CSSStyleDeclaration.setProperty. I haven't looked into doing this, yet. It may be simple, but I wasn't sure if there was a safe generic way to do this, since that provider seems to be initialized by a factory and injects providers that don't seem to have exports that were simple to find.

Option 2: I do not like to use a monkey-patch, but it is small and should be fine. I would not want to rely on it as a permanent fix, though.

(() => {
  const original = CSSStyleDeclaration.prototype.setProperty;
  CSSStyleDeclaration.prototype.setProperty = function () {
    const args = [...arguments];
    if (args[0] === '-moz-transform') {
      args[0] = 'transform';
    }
    return original.apply(this, args);
  };
})();

Option 3: You could patch the translateXY function in your node_modules/@swimlane/ngx-datatable, but I don't like to touch dependency files. If you are already using a modified build config then maybe you could just transform it there, but I don't have a lot of experience doing that.

ck0pnu0 commented 2 months ago

I have tried with Option 1 from @Marklb comment above and it worked for me. I've created a service which implements Renderer2 and then it overrides the setStyle method. Below are the steps:

First you need a class which implements RendererFactory2:

import { ɵDomRendererFactory2 as DomRendererFactory2 } from '@angular/platform-browser';

@Injectable()
export class YourRendererFactory implements RendererFactory2 {
    readonly factory = inject(DomRendererFactory2);

    createRenderer(hostElement: any, type: RendererType2): Renderer2 {
        return new YourRendererService(this.factory.createRenderer(hostElement, type));
    }
}

Then I created the YourRendererService class:

export class YourRendererService implements Renderer2 {
    constructor(readonly delegate: Renderer2) {}

    get data(): {[key: string]: any} {
        return this.delegate.data;
    }

    createElement(name: string, namespace?: string) {
        return this.delegate.createElement(name, namespace);
    }
    // ...
    // ...
    // ...
    // Modified name prop FF issue
    setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void {
        const styleWithoutFFPrefix = style.replace(/^(-moz-)/, '');
        return this.delegate.setStyle(el, styleWithoutFFPrefix, value, flags);
    }
}

Last step is to provide it in your module like:

{ provide: RendererFactory2, useClass: YourRendererFactory }