API Docs for: v2.7.0
Show:

File: addon/utils/column.js

import { isEmpty } from '@ember/utils';
import O, { get, set, computed, observer } from '@ember/object';
import { A } from '@ember/array';
import {capitalize, dasherize} from '@ember/string';

/**
 * Convert some string to the human readable one
 *
 * @param {string} name value to convert
 * @returns {string}
 * @ignore
 */
export function propertyNameToTitle(name) {
  return capitalize(dasherize(name).replace(/-/g, ' '));
}

/**
 * Column definition class for ModelsTable columns
 *
 * Check its public properties
 *
 * @class ModelsTableColumn
 * @extends Ember.Object
 * @namespace Utils
 * @private
 */
export default O.extend({

  /**
   * Value inverted to the {{#crossLink "Utils.ModelsTableColumn/isHidden:property"}}isHidden{{/crossLink}} initial value
   *
   * It set on column init and not changed any more
   *
   * @property defaultVisible
   * @type boolean
   * @private
   * @readOnly
   */

  /**
   * Field-name of the data's object shown in the current column. If it isn't provided, sorting and filtering options for current column are ignored
   *
   * @default ''
   * @property propertyName
   * @type string
   */
  propertyName: '',

  /**
   * Header for column. If it isn't provided, capitalized `propertyName` is used
   *
   * @default ''
   * @property title
   * @type string
   */
  title: null,

  /**
   * If `true` only `propertyName` will be shown in the column's cells and no components etc. Edit-mode won't affect such column.
   *
   * If `false` column's cells will be processed as usual (components will be used to display data and for edit-mode)
   *
   * @property simple
   * @type boolean
   * @default false
   */
  simple: false,

  /**
   * Custom component used in the column's cells.
   *
   * It will receive several options:
   *
   *  * `data` - whole dataset passed to the `models-table`
   *  * `record` - current row
   *  * `index` - current row index
   *  * `column` - current column (one of the {{#crossLink "Components.ModelsTable/processedColumns:property"}}processedColumns{{/crossLink}})
   *  * `sendAction` - closure action {{#crossLink "Components.ModelsTable/actions.sendAction:method"}}ModelsTable.actions.sendAction{{/crossLink}}
   *  * `expandRow` - closure action {{#crossLink "Components.ModelsTable/actions.expandRow:method"}}ModelsTable.actions.expandRow{{/crossLink}}
   *  * `collapseRow` - closure action {{#crossLink "Components.ModelsTable/actions.collapseRow:method"}}ModelsTable.actions.collapseRow{{/crossLink}}
   *  * `expandAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.expandAllRows:method"}}ModelsTable.actions.expandAllRows{{/crossLink}}
   *  * `collapseAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.collapseAllRows:method"}}ModelsTable.actions.collapseAllRows{{/crossLink}}
   *  * `clickOnRow` - closure action {{#crossLink "Components.ModelsTable/actions.clickOnRow:method"}}ModelsTable.actions.clickOnRow{{/crossLink}}
   *  * `editRow` - closure action {{#crossLink "Components.ModelsTableRow/actions.editRow:method"}}ModelsTable.actions.editRow{{/crossLink}}
   *  * `cancelEditRow` - closure action {{#crossLink "Components.ModelsTableRow/actions.cancelEditRow:method"}}ModelsTable.actions.cancelEditRow{{/crossLink}}
   *  * `themeInstance` - bound from {{#crossLink "Components.ModelsTable/themeInstance:property"}}ModelsTable.themeInstance{{/crossLink}}
   *  * `isExpanded` - is current row expanded
   *  * `isSelected` - is current row selected
   *  * `isEditRow` - is the row editable (one of the {{#crossLink "Components.ModelsTableRow/isEditRow:property"}}processedColumns{{/crossLink}})
   *  * `isColumnEditable` - is the column currently editable
   *
   * @type string
   * @property component
   * @default ''
   */
  component: '',

  /**
   * Custom component used in the column's cells when the row is in edit mode
   *
   * It will receive several options:
   *
   *  * `data` - whole dataset passed to the `models-table`
   *  * `record` - current row
   *  * `index` - current row index
   *  * `column` - current column (one of the {{#crossLink "Components.ModelsTable/processedColumns:property"}}processedColumns{{/crossLink}})
   *  * `sendAction` - closure action {{#crossLink "Components.ModelsTable/actions.sendAction:method"}}ModelsTable.actions.sendAction{{/crossLink}}
   *  * `expandRow` - closure action {{#crossLink "Components.ModelsTable/actions.expandRow:method"}}ModelsTable.actions.expandRow{{/crossLink}}
   *  * `collapseRow` - closure action {{#crossLink "Components.ModelsTable/actions.collapseRow:method"}}ModelsTable.actions.collapseRow{{/crossLink}}
   *  * `expandAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.expandAllRows:method"}}ModelsTable.actions.expandAllRows{{/crossLink}}
   *  * `collapseAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.collapseAllRows:method"}}ModelsTable.actions.collapseAllRows{{/crossLink}}
   *  * `clickOnRow` - closure action {{#crossLink "Components.ModelsTable/actions.clickOnRow:method"}}ModelsTable.actions.clickOnRow{{/crossLink}}
   *  * `editRow` - closure action {{#crossLink "Components.ModelsTableRow/actions.editRow:method"}}ModelsTable.actions.editRow{{/crossLink}}
   *  * `cancelEditRow` - closure action {{#crossLink "Components.ModelsTableRow/actions.cancelEditRow:method"}}ModelsTable.actions.cancelEditRow{{/crossLink}}
   *  * `themeInstance` - bound from {{#crossLink "Components.ModelsTable/themeInstance:property"}}ModelsTable.themeInstance{{/crossLink}}
   *  * `isExpanded` - is current row expanded
   *  * `isSelected` - is current row selected
   *  * `isEditRow` - is the row editable (one of the {{#crossLink "Components.ModelsTableRow/isEditRow:property"}}processedColumns{{/crossLink}})
   *  * `isColumnEditable` - is the column currently editable
   *
   * @type string
   * @property componentForEdit
   * @default ''
   */
  componentForEdit: '',

  /**
   * Is this column allowed to be editable
   *
   * @default true
   * @property editable
   * @type boolean
   */
  editable: true,

  /**
   * Custom component used in the header cell with filter
   *
   * It will receive several options:
   *
   * * `column` - current column (one of the {{#crossLink "Components.ModelsTable/processedColumns:property"}}processedColumns{{/crossLink}})
   * * `data` - whole dataset passed to the `models-table`
   * * `selectedItems` - bound from {{#crossLink "Components.ModelsTable/selectedItems:property"}}ModelsTable.selectedItems{{/crossLink}}
   * * `expandedItems` - bound from {{#crossLink "Components.ModelsTable/expandedItems:property"}}ModelsTable.expandedItems{{/crossLink}}
   * * `themeInstance` - bound from {{#crossLink "Components.ModelsTable/themeInstance:property"}}ModelsTable.themeInstance{{/crossLink}}
   * * `sendAction` - closure action {{#crossLink "Components.ModelsTable/actions.sendAction:method"}}ModelsTable.actions.sendAction{{/crossLink}}
   * * `expandAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.expandAllRows:method"}}ModelsTable.actions.expandAllRows{{/crossLink}}
   * * `collapseAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.collapseAllRows:method"}}ModelsTable.actions.collapseAllRows{{/crossLink}}
   * * `toggleAllSelection` - closure action {{#crossLink "Components.ModelsTable/actions.toggleAllSelection:method"}}ModelsTable.actions.toggleAllSelection{{/crossLink}}
   *
   * @type string
   * @property componentForFilterCell
   * @default ''
   */
  componentForFilterCell: '',

  /**
   * Custom component used in the header cell with sorting and column title
   *
   * It will receive several options:
   *
   * * `column` - current column (one of the {{#crossLink "Components.ModelsTable/processedColumns:property"}}processedColumns{{/crossLink}})
   * * `data` - whole dataset passed to the `models-table`
   * * `selectedItems` - bound from {{#crossLink "Components.ModelsTable/selectedItems:property"}}ModelsTable.selectedItems{{/crossLink}}
   * * `expandedItems` - bound from {{#crossLink "Components.ModelsTable/expandedItems:property"}}ModelsTable.expandedItems{{/crossLink}}
   * * `themeInstance` - bound from {{#crossLink "Components.ModelsTable/themeInstance:property"}}ModelsTable.themeInstance{{/crossLink}}
   * * `sendAction` - closure action {{#crossLink "Components.ModelsTable/actions.sendAction:method"}}ModelsTable.actions.sendAction{{/crossLink}}
   * * `expandAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.expandAllRows:method"}}ModelsTable.actions.expandAllRows{{/crossLink}}
   * * `collapseAllRows` - closure action {{#crossLink "Components.ModelsTable/actions.collapseAllRows:method"}}ModelsTable.actions.collapseAllRows{{/crossLink}}
   * * `toggleAllSelection` - closure action {{#crossLink "Components.ModelsTable/actions.toggleAllSelection:method"}}ModelsTable.actions.toggleAllSelection{{/crossLink}}
   *
   * @type string
   * @property componentForSortCell
   * @default ''
   */
  componentForSortCell: '',

  /**
   * Custom component used in the footer cell
   *
   * It will receive several options:
   *
   * * `selectedItems` - bound from {{#crossLink "Components.ModelsTable/selectedItems:property"}}ModelsTable.selectedItems{{/crossLink}}
   * * `expandedItems` - bound from {{#crossLink "Components.ModelsTable/expandedItems:property"}}ModelsTable.expandedItems{{/crossLink}}
   * * `data` - whole dataset passed to the `models-table`
   * * `mappedSelectedItems` - `selectedItems` mapped by `propertyName`
   * * `mappedExpandedItems` - `expandedItems` mapped by `propertyName`
   * * `mappedData` - `data` mapped by `propertyName`
   *
   * @type string
   * @property componentForFooterCell
   * @default ''
   */
  componentForFooterCell: '',

  /**
   * Colspan for cell in the sorting-row
   *
   * @property colspanForSortCell
   * @type number
   * @default 1
   */
  colspanForSortCell: 1,

  /**
   * @property realColspanForSortCell
   * @type number
   * @default 1
   * @private
   */
  realColspanForSortCell: 1,

  /**
   * Colspan for cell in the filters-row
   *
   * @property colspanForSortCell
   * @type number
   * @default 1
   */
  colspanForFilterCell: 1,

  /**
   * @property realColspanForFilterCell
   * @type number
   * @default 1
   * @private
   */
  realColspanForFilterCell: 1,

  /**
   * Field-name for sorting by current column. If it isn't provided, `propertyName` is used
   *
   * @type string
   * @property sortedBy
   * @default null
   */
  sortedBy: null,

  /**
   * The default sorting for this column. Can be either `asc` or `desc`. Needs to be set in conjunction with `sortPrecedence`,
   otherwise it will not have any effect
   *
   * @type string
   * @property sortDirection
   * @default ''
   */
  sortDirection: '',

  /**
   * Sort precedence for this column - needs to be larger than -1 for sortDirection to take effect
   *
   * @type number
   * @property sortPrecedence
   * @default ''
   */
  sortPrecedence: null,

  /**
   * If sorting should be disabled for this column
   *
   * @property disableSorting
   * @type boolean
   * @default false
   */
  disableSorting: false,

  /**
   * If filtering should be disabled for this column
   *
   * @property disableFiltering
   * @type boolean
   * @default false
   */
  disableFiltering: false,

  /**
   * FilterString a default filtering for this column
   *
   * @property filterString
   * @type string
   * @default ''
   */
  filterString: '',

  /**
   * Custom data's property that is used to filter column. If it isn't provided, `propertyName` is used
   *
   * @type string
   * @property filteredBy
   * @default null
   */
  filteredBy: null,

  /**
   * Sorting is column sorted now
   *
   * @property sorting
   * @type boolean
   * @default false
   */
  sorting: false,

  /**
   * Is current column hidden by default
   *
   * @property isHidden
   * @default false
   * @type boolean
   */
  isHidden: false,

  /**
   * Can current column be hidden. This field determines, if column appears in the columns-dropdown. If `mayBeHidden` is `true` and `isHidden` is also `true` for column, this column always be hidden
   *
   * @property mayBeHidden
   * @default true
   * @type boolean
   */
  mayBeHidden: true,

  /**
   * If `true` select-dropdown will be used for filtering by current column. Options are unique values for <code>data.@each.${propertyName}</code>
   *
   * @property filterWithSelect
   * @type boolean
   * @default false
   */
  filterWithSelect: false,

  /**
   * Should options in the select-box be sorted
   *
   * @property sortFilterOptions
   * @default false
   * @type boolean
   */
  sortFilterOptions: false,

  /**
   * List of option to the filter-box (used if {{#crossLink "Utils.ModelsTableColumn/filterWithSelect:property"}}filterWithSelect{{/crossLink}} is true)
   *
   * @type string[]|number[]|boolean[]
   * @property predefinedFilterOptions
   * @default null
   */
  predefinedFilterOptions: null,

  /**
   * Custom class-name for cells in the current column. This class-name will also be added to the header and filter of the column
   *
   * @property className
   * @default ''
   * @type string
   */
  className: '',

  /**
   * Custom function used to filter rows (used if {{#crossLink "Utils.ModelsTableColumn/filterWithSelect:property"}}filterWithSelect{{/crossLink}} is false)
   *
   * @property filterFunction
   * @type function
   */
  filterFunction: null,

  /**
   * Optional custom function used to sort rows
   *
   * @property sortFunction
   * @type function
   */
  sortFunction: null,

  /**
   * Placeholder for filter-input
   *
   * @property filterPlaceholder
   * @type string
   * @default ''
   */
  filterPlaceholder: '',

  /**
   * If this property is defined, link to the route will be rendered in the cell. {{#crossLink "Utils.ModelsTableColumn/propertyName:property"}}propertyName{{/crossLink}} is used as an anchor. If it's not declared, `id` will be used. <br /> Main idea for `routeName` is to provide a simple way to generate links for each model in the `data`. It should not be used for any other purposes
   *
   * @property routeName
   * @type string
   * @default ''
   */
  routeName: '',
  /**
   * If this property is defined, link to the route will be rendered in the cell. {{#crossLink "Utils.ModelsTableColumn/routeProperty:property"}}routeProperty{{/crossLink}} is used as an anchor. If it's not declared, `id` will be used. <br /> Main idea for `routeName` is to provide a simple way to generate links for each model in the `data`. It should not be used for any other purposes
   *
   * @property routeProperty
   * @type string
   * @default ''
   */
  routeProperty: 'id',
  /**
   * Object containing the definition of the column passed into the component
   *
   * @property originalDefinition
   * @type object
   * @default null
   * @readOnly
   * @private
   */
  originalDefinition: null,

  /**
   * @type string
   * @property cssPropertyName
   * @private
   * @readOnly
   */
  cssPropertyName: computed('propertyName', function () {
    return get(this, 'propertyName').replace(/\./g, '-');
  }),

  /**
   * @type boolean
   * @property isVisible
   * @private
   * @readOnly
   */
  isVisible: computed.not('isHidden'),

  /**
   * @type boolean
   * @property sortAsc
   * @private
   * @readOnly
   */
  sortAsc: computed.equal('sorting', 'asc'),

  /**
   * @type boolean
   * @property sortDesc
   * @private
   * @readOnly
   */
  sortDesc: computed.equal('sorting', 'desc'),

  /**
   * @type boolean
   * @property filterUsed
   * @private
   * @readOnly
   */
  filterUsed: computed.notEmpty('filterString'),

  /**
   * Allow sorting for column or not
   *
   * @type boolean
   * @property useSorting
   * @private
   * @readOnly
   */
  useSorting: computed('sortField', 'disableSorting', function () {
    return get(this, 'sortField') && !get(this, 'disableSorting');
  }),

  /**
   * @property sortField
   * @type string
   * @readonly
   */
  sortField: computed('sortedBy', 'propertyName', function () {
    return get(this, 'sortedBy') || get(this, 'propertyName');
  }).readOnly(),

  /**
   * Allow filtering for column or not
   *
   * @type boolean
   * @property useFilter
   * @private
   * @readOnly
   */
  useFilter: computed('filterField', 'disableFiltering', function () {
    return get(this, 'filterField') && !get(this, 'disableFiltering');
  }),

  /**
   * @type string
   * @property filterField
   * @readonly
   */
  filterField: computed('filteredBy', 'propertyName', function() {
    return get(this, 'filteredBy') || get(this, 'propertyName');
  }).readOnly(),

  /**
   * If preselected option doesn't exist after <code>filterOptions</code> update,
   * <code>filterString</code> is reverted to empty string (basically, filtering for this column is dropped)
   *
   * @method cleanFilterString
   * @private
   */
  cleanFilterString: observer('filterWithSelect', 'filterOptions.[]', 'filterString', function () {
    let filterOptions = get(this, 'filterOptions');
    let filterWithSelect = get(this, 'filterWithSelect');
    let filterString = get(this, 'filterString');
    if (!filterWithSelect || isEmpty(filterOptions)) {
      return;
    }
    const filterOptionExists = A(filterOptions).find(option => {
      const value = get(option, 'value');
      return [value, '' + value].indexOf(filterString) !== -1;
    });
    if (!filterOptionExists) {
      set(this, 'filterString', '');
    }
  })

});