import { or, alias } from '@ember/object/computed'; import Component from '@ember/component'; import Sortable from 'shared/mixins/sortable-base'; import StickyHeader from 'shared/mixins/sticky-table-header'; import layout from './template'; import pagedArray from 'ember-cli-pagination/computed/paged-array'; import { computed } from '@ember/object'; import { defineProperty, get, set } from '@ember/object'; import { inject as service } from '@ember/service' import { isArray } from '@ember/array'; import { observer } from '@ember/object' import { run } from '@ember/runloop'; import { isAlternate, isMore, isRange } from 'shared/utils/platform'; import { filter } from 'ui/utils/search-text'; function toggleInput(node, on) { let id = get(node, 'id'); if ( id ) { let input = $(`input[nodeid="${id}"]`); // eslint-disable-line if ( input && input.length && !input[0].disabled ) { // can't reuse the input ref here because the table has rerenderd and the ref is no longer good $(`input[nodeid="${id}"]`).prop('checked', on); // eslint-disable-line let tr = $(`input[nodeid="${id}"]`).closest('tr'); // eslint-disable-line let first = true; while ( tr && (first || tr.hasClass('sub-row') ) ) { tr.toggleClass('row-selected', on); tr = tr.next(); first = false; } } } } export default Component.extend(Sortable, StickyHeader, { prefs: service(), intl: service(), bulkActionHandler: service(), layout, body: null, groupByKey: null, groupByRef: null, preSorts: null, sortBy: null, descending: false, headers: null, extraSearchFields: null, extraSearchSubFields: null, prefix: false, suffix: false, bulkActions: true, rowActions: true, search: true, searchToWormhole: null, paging: true, subRows: false, checkWidth: 40, actionsWidth: 40, availableActions: null, selectedNodes: null, prevNode: null, searchText: null, isVisible: true, page: 1, pagingLabel: 'pagination.generic', selectionChanged: null, showHeader: or('bulkActions', 'searchInPlace'), // ----- sortableContent: alias('body'), init() { this._super(...arguments); set(this, 'selectedNodes', []); if (get(this, 'bulkActions')) { this.actionsChanged(); } if ( get(this, 'bulkActions') ) { run.schedule('afterRender', () => { let table = $(this.element).find('> TABLE'); // eslint-disable-line let self = this; // need this context in click function and can't use arrow func there table.on('click', '> TBODY > TR', (e) => { self.rowClick(e); }); table.on('mousedown', '> TBODY > TR', (e) => { if ( isRange(e) || e.target.tagName === 'INPUT') { e.preventDefault(); } }); }); } let watchKey = 'pagedContent.[]'; if ( get(this, 'groupByKey') ) { watchKey = `pagedContent.@each.${ get(this, 'groupByKey').replace(/\..*/g, '') }`; } defineProperty(this, 'groupedContent', computed(watchKey, 'groupByKey', 'groupByRef', 'pagedContent', 'selectedNodes', () => { let ary = []; let map = {}; let groupKey = get(this, 'groupByKey'); let refKey = get(this, 'groupByRef') || ''; get(this, 'pagedContent').forEach((obj) => { let group = get(obj, groupKey) || ''; let ref = get(obj, refKey) || { displayName: group }; let entry = map[group]; if ( entry ) { entry.items.push(obj); } else { entry = { group, ref, items: [obj] }; map[group] = entry; ary.push(entry); } if ( get(this, 'selectedNodes').includes(obj) ) { run.next(this, () => { toggleInput(obj, true); }); } }); return ary; })); }, didReceiveAttrs() { if (get(this, 'isVisible')) { this.triggerResize(); } }, actions: { clearSearch() { set(this, 'searchText', ''); }, executeBulkAction(name, e) { e.preventDefault(); let handler = get(this, 'bulkActionHandler'); let nodes = get(this, 'selectedNodes'); if (isAlternate(e)) { var available = get(this, 'availableActions'); var action = available.findBy('action', name); let alt = get(action, 'altAction'); if ( alt ) { name = alt; } } if ( typeof handler[name] === 'function' ) { get(this, 'bulkActionHandler')[name](nodes); } else { nodes.forEach((node) => { node.send(name); }); } }, executeAction(action) { var node = get(this, 'selectedNodes')[0]; node.send(action); }, }, selection: observer('selectedNodes.[]', function() { const callback = get(this, 'selectionChanged'); if (typeof callback === 'function') { callback(get(this, 'selectedNodes')); } }), // Pick a new sort if the current column disappears. headersChanged: observer('headers.@each.name', function() { let sortBy = get(this, 'sortBy'); let headers = get(this, 'headers') || []; if ( headers && headers.get('length') ) { let cur = headers.findBy('name', sortBy); if ( !cur ) { run.next(this, function() { this.send('changeSort', headers.get('firstObject.name')); }); } } }), pagedContentChanged: observer('pagedContent.[]', function() { this.cleanupOrphans(); }), pageCountChanged: observer('indexFrom', 'filtered.length', function() { // Go to the last page if we end up past the last page let from = get(this, 'indexFrom'); let last = get(this, 'filtered.length'); var perPage = get(this, 'perPage'); if ( get(this, 'page') > 1 && from > last) { let page = Math.ceil(last / perPage); set(this, 'page', page); } }), sortKeyChanged: observer('sortBy', function() { set(this, 'page', 1); }), actionsChanged: observer('selectedNodes.@each._availableActions', 'pagedContent.@each._availableActions', function() { if (!get(this, 'bulkActions')) { return; } let nodes = get(this, 'selectedNodes'); let disableAll = false; if ( !nodes.length ) { disableAll = true; let firstNode = get(this, 'pagedContent.firstObject'); if ( firstNode ) { nodes = [firstNode]; } } const map = {}; get(this, 'pagedContent').forEach((node) => { get(node, '_availableActions').forEach((act) => { if ( !act.bulkable ) { return; } let obj = map[act.action]; if ( !obj ) { obj = $().extend(true, {}, act);// eslint-disable-line map[act.action] = obj; } if ( act.enabled !== false ) { obj.anyEnabled = true; } }); }); nodes.forEach((node) => { get(node, '_availableActions').forEach((act) => { if ( !act.bulkable ) { return; } let obj = map[act.action]; if ( !obj ) { obj = $().extend(true, {}, act); // eslint-disable-line map[act.action] = obj; } obj.available = (obj.available || 0) + (act.enabled === false ? 0 : 1 ); obj.total = (obj.total || 0) + 1; }) }); let out = Object.values(map).filterBy('anyEnabled', true); if ( disableAll ) { out.forEach((x) => { set(x, 'enabled', false); }); } else { out.forEach((x) => { if ( x.available < x.total ) { set(x, 'enabled', false); } else { set(x, 'enabled', true); } }); } set(this, 'availableActions', out); }), searchInPlace: computed('search', 'searchToWormhole', function() { return get(this, 'search') && !get(this, 'searchToWormhole'); }), perPage: computed('paging', 'prefs.tablePerPage', function() { if ( get(this, 'paging') ) { return get(this, 'prefs.tablePerPage'); } else { return 100000; } }), // hide bulckActions if content is empty. internalBulkActions: computed('bulkActions', 'sortableContent.[]', function(){ let bulkActions = get(this, 'bulkActions'); if (bulkActions && get(this, 'sortableContent')){ let sortableContent = get(this, 'sortableContent'); return !!sortableContent.get('length'); } else { return false; } }), // Flow: body [-> sortableContent] -> arranged -> filtered -> pagedContent [-> groupedContent] pagedContent: pagedArray('filtered.[]', { page: alias('parent.page'), perPage: alias('parent.perPage') }), // For data-title properties on