Search code examples
ember.jsember-dataember-cli

EmberJS - Table with multiple checkboxes - how to preserve 'checked' status with pagination


this is template of my component sortable-table-checkbox.hbs

<table class="table table-checkbox" data-page-length="{{dataPageLength}}">
  <thead>
    <tr>
      <th>&nbsp;</th>
      {{#each-in columns as |prop value| }}
        <th
          class="{{if (or (eq (get sortingStatus prop) '')
                          (eq (get sortingStatus prop) 'asc')
                          (eq (get sortingStatus prop) 'desc')) 'sorting'}} w25p {{get sortingStatus prop}}"
          {{action 'toggleSorting' prop}}>
          {{#if value.title}}
            {{value.title}}
          {{else}}
            {{value}}
          {{/if}}
        </th>
      {{/each-in}}
    </tr>
  </thead>
  <tbody>
  {{#each data as |item|}}
    <tr>
      <td>
        <div class="">
          {{input
              type='checkbox'
              checked=item.checked
              change=(action 'chooseItem' item)}}

        </div>
      </td>
      {{#each-in columns as |prop value| }}
        <td>
          {{#collection- value=(get item prop) field=value.field as |col|}}
            {{#number-format isNumber=(eq value.type 'number') value=col as |val|}}
              {{#if value.linkable}}
                {{#link-to value.linkable.route (get item value.linkable.paramField)}}
                  {{value.prefix}}
                  {{val}}
                {{/link-to}}
              {{else}}
                {{value.prefix}}
                {{val}}
              {{/if}}
            {{/number-format}}
          {{/collection-}}
        </td>
      {{/each-in}}
    </tr>
  {{/each}}
  </tbody>
</table>

<p>Your Selected Items:</p>
<ul>
  {{#each selectedItems as |item|}}
    <li>{{item.title}}</li>
  {{else}}
    <em>No items selected</em>
  {{/each}}
</ul>

{{checked}}


{{yield}}

this is my component sortable-table-checkbox.js

import Ember from 'ember';

/**
 * Table component
 *
 * Allows to render dynamic tables with sorting where items are displayed as
 * checkboxes
 *
 * Header titles of table should passed as ``columns`` arg to the component.
 *
 * columns: {
 *   title: {
 *     title: 'Game Title',
 *     sorting: 'title',
 *     linkable: {
 *       route: 'home.cabinet.developer.games.game',
 *       paramField: 'game_uid',
 *     },
 *   },
 *   genres: {
 *     title: 'Genre',
 *   },
 *   amount: {
 *     title: 'Ad Revenue Earned',
 *     prefix: '$',
 *     type: 'number',
 *     sorting: 'amount'
 *   },
 *   platforms_data: {
 *     title: 'Platform',
 *     collection: true,
 *     field: 'title',
 *   },
 *   ages_data: {
 *     title: 'Ages',
 *     collection: true,
 *     field: 'value',
 *   },
 * },
 *
 * Example of using of this component
 *
 * {{sortable-table data=model.games.results columns=columns sorting=sorting}}
 *
 * @class SortingTableComponent
 * @module components/sortable-table
 * @extends Ember.Component
 * @public
 */
export default Ember.Component.extend({

  /**
   * Sorting status
   *
   * @property sortingStatus
   * @type {Object}
   * @default null
   * @public
   */
  sortingStatus: null,

  /**
   * Avoid sharing state between components
   *
   * @method init
   * @return {undefined}
   * @public
   */
  init() {
    this._super(...arguments);
    this.set('sortingStatus', {});
    this.selectedItems = [];
    this.checked=false;
  },

  /**
   * Fill ``sortingStatus`` object with reset statuses
   *
   * @method willInsertElement
   * @return {undefined}
   * @public
   */
  willInsertElement() {
    const columns = this.get('columns');

    for (const element in columns) {
      if (columns[element].sorting) {
        this.set(`sortingStatus.${element}`, '');
      }
    }
  },

  actions: {

    /**
     * Toggle sorting
     *
     * @method toggleSorting
     * @param prop {String} name of property
     * @return {boolean} false if this property doesn't exist in ``sorting`` object
     * @public
     */
    toggleSorting(prop) {
      // If ``prop`` property doesn't exist in ``sorting`` object, return false
      if (!(prop in this.get('sortingStatus'))) {
        return false;
      }

      // Reset another properties sorting state
      for (const element in this.get('sortingStatus')) {
        if (element !== prop) {
          this.set(`sortingStatus.${element}`, '');
        }
      }

      // Set asc if starting state, desc if asc and starting state if desc
      if (this.get(`sortingStatus.${prop}`) === '') {
        this.set(`sortingStatus.${prop}`, 'asc');
      } else if (this.get(`sortingStatus.${prop}`) === 'asc') {
        this.set(`sortingStatus.${prop}`, 'desc');
      } else {
        this.set(`sortingStatus.${prop}`, '');
      }

      // Sending action
      this.sendAction('action', this.get(`columns.${prop}.sorting`), this.get(`sortingStatus.${prop}`));

      return true;
    },

    chooseItem(item, e) {
      const added = e.target.checked;
      this.set('checked', added);
      if (added) {
        item.checked=true;
        this.get('selectedItems').pushObject(item);
      }
      else {
        this.get('selectedItems').removeObject(item);
      }
    },
  },
});

enter image description here

So when I navigate through pages app does API call querying next 5 items to be displayed, when I come back to page #1 I have no item.checked status - as an item is a new object that came from API call, therefore it doesn't have .checked property that I set inside chooseItem action.

How to solve this problem?

Maybe there is a dynamic way to check if checked property of checkbox can be set?


Solution

  • These changes are not saved because you don't save the model with proper checked field on backend. I suggest two ways of dynamically calculating the checked state of checkbox:

    1. The fast one. You can create a custom helper to check that element is in array. Or you can use any existing addon that provides such helper. Then you can write something like this in sortable-table-checkbox.hbs:
    
        {{input type='checkbox' 
        checked=(array-contains selectedItems item.id property='id') 
        change=(action 'chooseItem' item)}}
    
    
    1. You can create a component table-row and move there everything you have in <tr></tr> in sortable-table-checkbox.hbs. The component will have item and selectedItems as parameters. Then you can use Ember computed properties:
    
          //table-row.js
          // ...
          isChecked: Ember.computed('item.id', 'selectedItems.length', function() {
              const itemId = this.get('item.id');
              const selectedItems = this.get('selectedItems');
              return selectedItems.some(el => el.get('id') === itemId);
          })
          // ...
    
    
    
        //table-row.hbs
        {{input type='checkbox'
                checked=isChecked
                change=(action 'chooseItem' item)}}