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

Checkbox select all not working properly with server-side pagination #1363

Open FrancescoBorzi opened 6 years ago

FrancescoBorzi commented 6 years ago

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

Using server side pagination and checkbox selection:

Current behavior

Expected behavior

Reproduction of the problem The reproduction steps are listed above. Also, I need to specify that this is NOT related with https://github.com/swimlane/ngx-datatable/issues/874 , since I set the [rowIdentity] function and the single selection works fine even across different pages (except for the "select all" button).

Please tell us about your environment:

Angular CLI: 1.7.3
Node: 8.9.1
OS: linux x64
Angular: 5.2.9
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cli: 1.7.3
@angular-devkit/build-optimizer: 0.3.2
@angular-devkit/core: 0.3.2
@angular-devkit/schematics: 0.3.2
@ngtools/json-schema: 1.2.0
@ngtools/webpack: 1.10.2
@schematics/angular: 0.3.2
@schematics/package-update: 0.3.2
typescript: 2.6.2
webpack: 3.11.0
RonjaKnudtsen commented 6 years ago

Same problem here. Paginating to page 2, and pressing check all unselects the previous list of items. I would suggest that after paginating the check all box will be shown as unselected, and then you can click check all. I am using [rowidentity] as mentioned. And selecting single items works with pagination

RonjaKnudtsen commented 6 years ago

Found a solution using custom checkboxes, and custom select all function. And an object that stores which pages has selected all.

Custom checkbox column taken from http://swimlane.github.io/ngx-datatable/#chkbox-selection-template Here i removed let-allRowsSelected="allRowsSelected" let-selectFn="selectFn" From template, and hooked up my own checkbox function.


<ngx-datatable-column
      [width]="30"
      [sortable]="false"
      [canAutoResize]="false"
      [draggable]="false"
      [resizeable]="false">
      <ng-template
        ngx-datatable-header-template
        let-value="value">
        <input type="checkbox"
               [checked]="selectAllOnPage[pageOffset]"
               (change)="selectAll($event)"
      />
      </ng-template>
      <ng-template
        ngx-datatable-cell-template
        let-value="value"
        let-isSelected="isSelected"
        let-onCheckboxChangeFn="onCheckboxChangeFn"
      >
        <input
          type="checkbox"
          [checked]="isSelected"
          (change)="onCheckboxChangeFn($event)"
        />
      </ng-template>
    </ngx-datatable-column>

With this select all function (CompanyListPaginated are my rows, for the page)

selectAll(event) {
    if (!this.selectAllOnPage[this.pageOffset]) {
      // Unselect all so we dont get duplicates.
      if (this.selected.length > 0) {
        this.companyListPaginated.map(company => {
          this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
        })
      }
      // Select all again
      this.selected.push(...this.companyListPaginated);
      this.selectAllOnPage[this.pageOffset] = true;
    } else {
      // Unselect all
      this.companyListPaginated.map(company => {
        this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
      });
      this.selectAllOnPage[this.pageOffset] = false;
    }
  }

Remember to remove selectAll page index when unselecting a single item.

  onSelect({ selected }) {
    // Make sure we are no longer selecting all
    this.selectAllOnPage[this.pageOffset] = false;

    this.selected.splice(0, this.selected.length);
    this.selected.push(...selected);
  }

However i really wished i didnt have to handle this manually. One of the reasons i chose ngx datatable is that I wanted to avoid making custom select/select all functions.

karansharma27 commented 5 years ago

@RonjaKnudtsen Thank you so much for the solution 👍

ghost commented 5 years ago

Found a solution using custom checkboxes, and custom select all function. And an object that stores which pages has selected all.

Custom checkbox column taken from http://swimlane.github.io/ngx-datatable/#chkbox-selection-template Here i removed let-allRowsSelected="allRowsSelected" let-selectFn="selectFn" From template, and hooked up my own checkbox function.


<ngx-datatable-column
      [width]="30"
      [sortable]="false"
      [canAutoResize]="false"
      [draggable]="false"
      [resizeable]="false">
      <ng-template
        ngx-datatable-header-template
        let-value="value">
        <input type="checkbox"
               [checked]="selectAllOnPage[pageOffset]"
               (change)="selectAll($event)"
      />
      </ng-template>
      <ng-template
        ngx-datatable-cell-template
        let-value="value"
        let-isSelected="isSelected"
        let-onCheckboxChangeFn="onCheckboxChangeFn"
      >
        <input
          type="checkbox"
          [checked]="isSelected"
          (change)="onCheckboxChangeFn($event)"
        />
      </ng-template>
    </ngx-datatable-column>

With this select all function (CompanyListPaginated are my rows, for the page)

selectAll(event) {
   if (!this.selectAllOnPage[this.pageOffset]) {
     // Unselect all so we dont get duplicates.
     if (this.selected.length > 0) {
       this.companyListPaginated.map(company => {
         this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
       })
     }
     // Select all again
     this.selected.push(...this.companyListPaginated);
     this.selectAllOnPage[this.pageOffset] = true;
   } else {
     // Unselect all
     this.companyListPaginated.map(company => {
       this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
     });
     this.selectAllOnPage[this.pageOffset] = false;
   }
 }

Remember to remove selectAll page index when unselecting a single item.

  onSelect({ selected }) {
    // Make sure we are no longer selecting all
    this.selectAllOnPage[this.pageOffset] = false;

    this.selected.splice(0, this.selected.length);
    this.selected.push(...selected);
  }

However i really wished i didnt have to handle this manually. One of the reasons i chose ngx datatable is that I wanted to avoid making custom select/select all functions.

Can you please share the complete code? I implemented your method but I am having a weird issue. When I select the "select all" checkbox I can see the "selected" variable has all 10 items in it but the UI checkboxes for each row doesn't update as checked unless until I hover over the table body or row. As soon as I hover over any row all of the checkboxes get changed to checked state.

Also Noticed one more issue. Lets say I check "select all" checkbox on page 1 then I go to page 3 and again I check "select all" on page 3. Now when I go back to page 1 directly only the checkbox in the header (the "select all" checkbox) is checked but not all of the checkboxes in rows in the table body are in checked state.

RonjaKnudtsen commented 5 years ago

It is a while ago i made this code, and it has changed on my part and contain a lot of business logic, so i can't share my complete code. I tried to show enough that it would work.

If it works when you hover over it, i could only guess that you need to add [rowIdentity], or that the it is not a true copy in the selected variable. If it is mutated it might not trigger a ui update.

As for your second issue. Are you using server-side pagination? In my "setPage" function where i fetch all my items (since it's server side) i check if the whole page was selected, and if any of the new items i fetched is inside the selected array.

Basically you have one angular collection of selected pages and items, and you have the variable that ngx-datatable uses which you must maintain.

In my case i have a dict with all selected items, in addition to the variable that ngx looks into.

However as i already mentioned. This is a hacky workaround, and i wish that swimlane could make their own solution.

ghost commented 5 years ago

Thank you for your reply. My second issue has been resolved but the first one is still there. I am using [rowIdentity] in my table which solved my second issue, but I can't tell what is the reason behind the first issue.

RonjaKnudtsen commented 5 years ago

What type of checkbox are you using? What does your checkAll function look like? I was just using a standard html checkbox.

ghost commented 5 years ago

Here's my checkbox code:

<ngx-datatable-column
       [width]="30"
       [sortable]="false"
       [canAutoResize]="false"
       [draggable]="false"
       [resizeable]="false">
       <ng-template ngx-datatable-header-template let-value="value">
            <input type="checkbox" [checked]="selectAllOnPage[page.offset]" (change)="selectAll($event)" />
       </ng-template>

       <ng-template ngx-datatable-cell-template let-value="value" let-isSelected="isSelected" let-onCheckboxChangeFn="onCheckboxChangeFn">
      <input type="checkbox" [checked]="isSelected" (change)="onCheckboxChangeFn($event)" />
      </ng-template>
 </ngx-datatable-column>

and this is my selectAll function:

selectAll(event) {

    if (!this.selectAllOnPage[this.page.offset]) {
      // Unselect all so we don't get duplicates.

      if (this.selected.length > 0) {
        this.userList.map(user => {
          this.selected = this.selected.filter((selected) => selected.id !== user.id);
        });
      }

      // Select all again
      this.selected.push(...this.userList);
      this.selectAllOnPage[this.page.offset] = true;

    } else {
      // Unselect all
      this.userList.map(user => {
        this.selected = this.selected.filter((selected) => selected.id !== user.id);
      });

      this.selectAllOnPage[this.page.offset] = false;
    }

  }
RonjaKnudtsen commented 5 years ago

Im not sure what the issue is. Do you have these two tags in ?

[selected]="selected"
[selectAllRowsOnPage]="wholePageSelected"

Where wholepageSelected is some variable, or maybe you can use selectAllOnPage[this.page.offset]

ghost commented 5 years ago

I tried [selectAllRowsOnPage] but problem is still there.

crawlinknetworks commented 5 years ago

I have the same problem with server-side pagination, the actual problem is, data object identity mismatch. In order to solve this problem use [rowIdentity]="getId" in <ngx-datatable> and declare getId function in TS.

getId(row) {    
    return row.id;
}
nagamanoj commented 4 years ago

I tried [selectAllRowsOnPage] but problem is still there.

@ghost try adding this in your select all function after

this.selected.push(...this.userList); this.userList = [...userList];

I think data table may not be detecting change

t-pankajkumar commented 4 years ago

Here's my checkbox code:

<ngx-datatable-column
       [width]="30"
       [sortable]="false"
       [canAutoResize]="false"
       [draggable]="false"
       [resizeable]="false">
       <ng-template ngx-datatable-header-template let-value="value">
            <input type="checkbox" [checked]="selectAllOnPage[page.offset]" (change)="selectAll($event)" />
       </ng-template>

       <ng-template ngx-datatable-cell-template let-value="value" let-isSelected="isSelected" let-onCheckboxChangeFn="onCheckboxChangeFn">
      <input type="checkbox" [checked]="isSelected" (change)="onCheckboxChangeFn($event)" />
      </ng-template>
 </ngx-datatable-column>

and this is my selectAll function:

selectAll(event) {

    if (!this.selectAllOnPage[this.page.offset]) {
      // Unselect all so we don't get duplicates.

      if (this.selected.length > 0) {
        this.userList.map(user => {
          this.selected = this.selected.filter((selected) => selected.id !== user.id);
        });
      }

      // Select all again
      this.selected.push(...this.userList);
      this.selectAllOnPage[this.page.offset] = true;

    } else {
      // Unselect all
      this.userList.map(user => {
        this.selected = this.selected.filter((selected) => selected.id !== user.id);
      });

      this.selectAllOnPage[this.page.offset] = false;
    }

  }

The solution is:

// Select all again this.selectAllOnPage[this.pageOffset] = true; this.selected.push(...this.userList); this.userList = {...this.userList}; this.selected = [...this.selected];

yashgarg03 commented 2 years ago

Found a solution using custom checkboxes, and custom select all function. And an object that stores which pages has selected all.

Custom checkbox column taken from http://swimlane.github.io/ngx-datatable/#chkbox-selection-template Here i removed let-allRowsSelected="allRowsSelected" let-selectFn="selectFn" From template, and hooked up my own checkbox function.


<ngx-datatable-column
      [width]="30"
      [sortable]="false"
      [canAutoResize]="false"
      [draggable]="false"
      [resizeable]="false">
      <ng-template
        ngx-datatable-header-template
        let-value="value">
        <input type="checkbox"
               [checked]="selectAllOnPage[pageOffset]"
               (change)="selectAll($event)"
      />
      </ng-template>
      <ng-template
        ngx-datatable-cell-template
        let-value="value"
        let-isSelected="isSelected"
        let-onCheckboxChangeFn="onCheckboxChangeFn"
      >
        <input
          type="checkbox"
          [checked]="isSelected"
          (change)="onCheckboxChangeFn($event)"
        />
      </ng-template>
    </ngx-datatable-column>

With this select all function (CompanyListPaginated are my rows, for the page)

selectAll(event) {
   if (!this.selectAllOnPage[this.pageOffset]) {
     // Unselect all so we dont get duplicates.
     if (this.selected.length > 0) {
       this.companyListPaginated.map(company => {
         this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
       })
     }
     // Select all again
     this.selected.push(...this.companyListPaginated);
     this.selectAllOnPage[this.pageOffset] = true;
   } else {
     // Unselect all
     this.companyListPaginated.map(company => {
       this.selected = this.selected.filter((selected) => selected.uuid !== company.uuid);
     });
     this.selectAllOnPage[this.pageOffset] = false;
   }
 }

Remember to remove selectAll page index when unselecting a single item.

  onSelect({ selected }) {
    // Make sure we are no longer selecting all
    this.selectAllOnPage[this.pageOffset] = false;

    this.selected.splice(0, this.selected.length);
    this.selected.push(...selected);
  }

However i really wished i didnt have to handle this manually. One of the reasons i chose ngx datatable is that I wanted to avoid making custom select/select all functions.

Thanks for your answer here I'm confused that what is selectAllOnPage & pageOffset in this code and also I'm facing one more issue that if I click on select all checkbox then when I'm hovering on list then my records are getting selected otherwise my records are not getting selected can you please help me