novo-elements
Version:
Bullhorn's NOVO Element Repository for Angular 2
1,050 lines (993 loc) • 48.9 kB
text/typescript
// NG2
import { Component, EventEmitter, Input, Output, DoCheck } from '@angular/core';
import { FormGroup, FormBuilder, FormArray } from '@angular/forms';
// Vendor
import * as dateFns from 'date-fns';
// APP
import { NovoLabelService } from '../../services/novo-label-service';
import { Helpers } from '../../utils/Helpers';
import { FormUtils } from '../../utils/form-utils/FormUtils';
import { ReadOnlyControl, ControlFactory } from './../form/FormControls';
import { CollectionEvent } from '../../services/data-provider/CollectionEvent';
import { PagedArrayCollection } from '../../services/data-provider/PagedArrayCollection';
import { PagedCollection } from '../../services/data-provider/PagedCollection';
export interface NovoTableConfig {
// Paging config
paging?: {
current: number, // current page
itemsPerPage: number, // items per page
onPageChange: Function, // function to handle page changing
rowOptions?: { value: number, label: string }[], // page options
disablePageSelection?: boolean // disables the pages from being selected
};
// Footer config (total footer)
footers?: Array<{
columns: Array<string>, // string array of columns to total
method: string; // method to use for the footer, SUM | AVG, defaults to SUM
labelColumn: string, // column to use as the "total" label
label: string // label to use in the "total" label
}>;
// TODO: When these types are enforced as `boolean | Function`, there's a lint error. That's a bug.
filtering?: boolean | any; // Turn on filtering for the table, boolean or function for filtering callback
sorting?: boolean | any; // Turn on sorting for the table, boolean or function for sorting callback
ordering?: boolean | Function; // Turn on ordering for the table, boolean or function for ordering callback
resizing?: boolean | Function; // Turn on resizing for the table, boolean or function for resizing callback
rowSelectionStyle?: string; // Row selection style, checkbox or row
rowSelect?: boolean; // Turn on row selection
hasDetails?: boolean; // Turn on details row for the table
detailsRenderer?: any; // Renderer/component for the details row
expandAll?: boolean; // should All Rows be expanded by default
selectAllEnabled?: boolean; // Allows the table, while in selection mode to have a select all at the top
}
// TODO - support (1) clicking cell to edit, (2) clicking row to edit, (3) button to trigger full table to edit
export enum NovoTableMode {
VIEW = 1,
EDIT = 2
}
export class NovoTableElement implements DoCheck {
config: NovoTableConfig = {};
columns: Array<any>;
theme: string;
skipSortAndFilterClear: boolean = false;
mode: NovoTableMode = NovoTableMode.VIEW;
editable: boolean = false;
onRowClick: EventEmitter<any> = new EventEmitter();
onRowSelect: EventEmitter<any> = new EventEmitter();
onTableChange: EventEmitter<any> = new EventEmitter();
_dataProvider: PagedArrayCollection<any>;
_rows: Array<any> = [];
selected: Array<any> = [];
activeId: number = 0;
master: boolean = false;
expandAll: boolean = false;
indeterminate: boolean = false;
lastPage: number = 0;
selectedPageCount: number = 0;
showSelectAllMessage: boolean = false;
currentSortColumn: any;
pagedData: Array<any> = [];
pageSelected: any;
// Map to keep track of what dropdowns are toggled
// Used to properly *ngIf the <list> so that the keepFilterFocused Directive
// will properly fire the ngAfterViewInit event
toggledDropdownMap: any = {};
public NovoTableMode = NovoTableMode;
public tableForm: FormGroup = new FormGroup({});
public toast: { theme: string, icon: string, message: string };
public footers: any[] = [];
public grossFlagToAvoidTheTableFromBeingUglyWhenHidingTheToast: boolean = false;
public loading: boolean = false;
set rows(rows: Array<any>) {
this.dataProvider = rows;
if (rows && rows.length > 0) {
this.setupColumnDefaults();
}
// this is a temporary/hacky fix until async dataloading is handled within the table
if (!this.skipSortAndFilterClear) {
this.clearAllSortAndFilters();
}
}
get rows() {
return this._rows;
}
set dataProvider(dp: any) {
this._dataProvider = Array.isArray(dp) ? new PagedArrayCollection<any>(dp) : dp;
this._dataProvider.dataChange.debounceTime(100).subscribe((event: CollectionEvent) => {
switch (event.type) {
case CollectionEvent.CHANGE:
this._rows = event.data;
// Setup form
this.tableForm = this.builder.group({
rows: this.builder.array([])
});
// Remove all selection on sort change if selection is on
if (this.config.rowSelectionStyle === 'checkbox') {
this.pagedData = event.data;
this.pageSelected = this.pagedData.filter(r => r._selected);
this.rowSelectHandler();
}
// Find that columns we might need to sum up via the footer
let columnsToSum = [];
let columnSums = {};
if (this.config.footers) {
this.config.footers.forEach(config => {
columnsToSum.push(...config.columns);
});
// Only have unique columns, filter out duplicates
columnsToSum = columnsToSum.filter((item, index, array) => array.indexOf(item) === index);
}
// Make a form for each row
let tableFormRows = <FormArray>this.tableForm.controls['rows'];
this._rows.forEach((row, index) => {
let rowControls = [];
row.controls = {};
row._editing = {};
row._expanded = this.config.expandAll;
row.rowId = this._rows.length;
this.columns.forEach(column => {
// Use the control passed or use a ReadOnlyControl so that the form has the values
let control = column.editorConfig ? ControlFactory.create(column.editorType, column.editorConfig) : new ReadOnlyControl({ key: column.name });
row.controls[column.name] = control;
rowControls.push(control);
});
this.formUtils.setInitialValues(rowControls, row, false);
tableFormRows.push(this.formUtils.toFormGroup(rowControls));
// Setup the total footer if configured
// Array of keys to total
if (columnsToSum.length !== 0) {
columnsToSum.forEach(column => {
if (Helpers.isBlank(columnSums[column])) {
columnSums[column] = 0;
}
columnSums[column] += row[column];
});
}
});
if (this.mode === NovoTableMode.EDIT) {
this.setTableEdit();
}
// Setup the footers (if any)
if (this.config.footers) {
this.footers = [];
this.config.footers.forEach((footerConfig, footerConfigIndex) => {
let footer = {};
footer[footerConfig.labelColumn] = footerConfig.label;
footerConfig.columns.forEach(column => {
if (footerConfig.method === 'AVG' && this._rows.length !== 0) {
footer[column] = columnSums[column] / this._rows.length;
} else {
footer[column] = columnSums[column];
}
});
this.footers.push(footer);
});
}
break;
default:
break;
}
});
if (this.config.paging) {
this._dataProvider.page = this.config.paging.current;
this._dataProvider.pageSize = this.config.paging.itemsPerPage;
} else {
// Paging turned off, return basically all of the data
this._dataProvider.page = 1;
this._dataProvider.pageSize = 500;
}
if (dp && dp.length > 0) {
this.setupColumnDefaults();
}
this._dataProvider.refresh();
}
get dataProvider() {
return this._dataProvider;
}
get editing() {
return this.mode === NovoTableMode.EDIT;
}
get formValue() {
return this.tableForm.value;
}
constructor(public labels: NovoLabelService, private formUtils: FormUtils, private builder: FormBuilder) { }
onDropdownToggled(event, column): void {
this.toggledDropdownMap[column] = event;
}
onPageChange(event) {
//this.dataProvider.page = event.page;
//this.dataProvider.pageSize = event.itemsPerPage;
}
getOptionDataAutomationId(option) {
if (!Helpers.isBlank(option.value)) {
return option.value;
}
return option;
}
/**
* @name setupColumnDefaults
*/
setupColumnDefaults() {
// Check columns for cell option types
this.columns.forEach(column => {
if (column && column.type) {
switch (column.type) {
case 'date':
// Set options based on dates if there are none
column.options = (column.options || this.getDefaultOptions(column));
break;
default:
break;
}
}
});
}
/**
* @name ngDoCheck
*/
ngDoCheck() {
if (this.config.paging && this.config.paging.current !== this.lastPage) {
this.rowSelectHandler();
this.showSelectAllMessage = false;
}
this.lastPage = this.config.paging ? this.config.paging.current : 1;
}
/**
* @name getPageStart
* @returns {number}
*/
getPageStart() {
return this.config.paging ? (this.dataProvider.page - 1) * this.dataProvider.pageSize : 0;
}
/**
* @name getPageEnd
* @returns {*}
*/
getPageEnd() {
return this.config.paging && this.dataProvider.pageSize > -1 ? this.getPageStart() + this.dataProvider.pageSize : this.rows.length;
}
/**
* @name onFilterClick
* @param column
* @param filter
*/
onFilterClick(column, filter) {
if (filter.range && !column.calendarShow) {
column.calenderShow = true;
return;
}
if (Array.isArray(column.filter) && column.multiple) {
if (~column.filter.indexOf(filter)) {
// Remove filter
column.filter.splice(column.filter.indexOf(filter), 1);
if (filter.range) {
column.calenderShow = false;
}
if (column.filter.length === 0) {
column.filter = null;
}
} else {
// Add filter
column.filter.push(filter);
}
} else {
column.filter = Helpers.isBlank(filter.value) ? filter : filter.value;
}
this.onFilterChange();
}
/**
* @name onFilterClear
* @param column
*/
onFilterClear(column: any): void {
setTimeout(() => {
column.filter = null;
column.freetextFilter = null;
this.onFilterChange();
if (column.originalOptions) {
column.options = column.originalOptions;
}
});
}
clearAllSortAndFilters() {
if (this.config.filtering) {
this.columns.forEach(column => {
column.filter = null;
column.sort = null;
});
}
}
/**
* @name onFilterChange
*
* @description This method updates the row data to reflect the active filters.
*/
onFilterChange() {
if (this.config.filtering) {
// Array of filters
const filters = this.columns.filter(col => !Helpers.isEmpty(col.filter));
if (filters.length) {
let query = {};
for (const column of filters) {
if (Helpers.isFunction(column.match)) {
query[column.name] = (value, record) => {
return column.match(record, column.filter);
};
} else if (column.preFilter && Helpers.isFunction(column.preFilter)) {
query = Object.assign({}, query, column.preFilter(this.escapeCharacters(column.filter)));
} else if (Array.isArray(column.filter)) {
// The filters are an array (multi-select), check value
let options = column.filter;
// We have an array of {value: '', labels: ''}
if (options[0].value || options[0].label) {
options = column.filter.map(opt => opt.value);
}
query[column.name] = { any: options };
} else if (column.type && column.type === 'date') {
if (column.filter.startDate && column.filter.endDate) {
query[column.name] = {
min: dateFns.startOfDay(column.filter.startDate),
max: dateFns.startOfDay(dateFns.addDays(dateFns.startOfDay(column.filter.endDate), 1))
};
} else {
query[column.name] = {
min: column.filter.min ? dateFns.addDays(dateFns.startOfToday(), column.filter.min) : dateFns.startOfToday(),
max: column.filter.max ? dateFns.addDays(dateFns.startOfTomorrow(), column.filter.max) : dateFns.startOfTomorrow()
};
}
} else {
query[column.name] = column.filter;
}
}
if (Helpers.isFunction(this.config.filtering)) {
this.config.filtering(query);
} else {
this._dataProvider.filter = query;
}
} else {
this._dataProvider.filter = {};
}
// Trickle down to keep sort
// this.onSortChange(this.currentSortColumn);
// If paging, reset page
if (this.config.paging) {
this.config.paging.current = 1;
}
// Remove all selection on sort change if selection is on
if (this.config.rowSelectionStyle === 'checkbox') {
this.selectAll(false);
}
}
}
escapeCharacters(filter) {
if (typeof (filter) === 'string') {
return filter.replace(/'/g, '\'\'');
} return filter;
}
/**
* @name isFilterActive
* @param column
* @param filter
* @returns {boolean}
*
* @description
*/
isFilterActive(column, filter) {
//TODO: This needs to be refactored
let isActive = false;
if (column && !Helpers.isBlank(column.filter) && !Helpers.isBlank(filter)) {
if (Array.isArray(column.filter)) {
if (typeof (filter) !== 'string') {
isActive = column.filter.some(item => {
return item.label === filter.label;
});
} else {
isActive = column.filter.includes(filter);
}
} else {
if (typeof (column.filter) === typeof (filter)) {
isActive = (column.filter === filter);
} else {
isActive = (column.filter === filter.value);
}
}
}
return isActive;
}
/**
* @name onSortChange
* @param newSortColumn
*/
onSortChange(column) {
this.currentSortColumn = column;
let sortedColumns: any = this.columns.filter((thisColumn) => {
return thisColumn.sort && thisColumn !== this.currentSortColumn;
});
for (let sortedColumn of sortedColumns) {
sortedColumn.sort = null;
}
if (column) {
if (Helpers.isFunction(this.config.sorting)) {
this.config.sorting();
} else if (Helpers.isFunction(column.preSort)) {
this._dataProvider.sort = [].concat(column.preSort(column));
} else {
this._dataProvider.sort = [{ field: (column.compare || column.name), reverse: column.sort === 'desc' }];
}
}
// Fire table change event
// this.fireTableChangeEvent();
// If paging, reset page
if (this.config.paging) {
this.config.paging.current = 1;
}
// Remove all selection on sort change if selection is on
if (this.config.rowSelectionStyle === 'checkbox') {
this.selectAll(false);
}
}
/**
* @name fireTableChangeEvent
*/
fireTableChangeEvent() {
// Construct a table change object
const onTableChange: any = {};
const filters = this.columns.filter((col) => col.filter && col.filter.length);
onTableChange.filter = filters.length ? filters : false;
onTableChange.sort = this.currentSortColumn ? this.currentSortColumn : false;
onTableChange.rows = this.rows;
// Emit event
this.onTableChange.emit(onTableChange);
}
/**
* @name findColumnIndex
* @param value
* @returns {*}
*/
findColumnIndex(value) {
for (let i = 0; i < this.columns.length; i += 1) {
if (this.columns[i].name === value) {
return i;
}
}
return null;
}
/**
* @name onOrderChange
* @param event
*/
onOrderChange(event) {
const oldIndex = this.findColumnIndex(event.first.name);
const newIndex = this.findColumnIndex(event.second.name);
this.columns.splice(newIndex, 0, this.columns.splice(oldIndex, 1)[0]);
this.onSortChange(this.currentSortColumn);
}
/**
* @name selectPage
*/
expandAllOnPage(expanded) {
this.config.expandAll = !expanded;
for (let row of this.dataProvider.list) {
row._expanded = this.config.expandAll;
}
}
/**
* @name selectPage
*/
selectPage() {
if (!this.master) {
this.selectAll(false);
// Only show the select all message when there is only one new page selected at a time
this.selectedPageCount = this.selectedPageCount > 0 ? this.selectedPageCount - 1 : 0;
this.showSelectAllMessage = false;
} else {
this.indeterminate = false;
//this.pagedData = this.rows.slice(this.getPageStart(), this.getPageEnd());
for (let row of this.pagedData) {
row._selected = this.master;
}
this.selected = this.dataProvider.list.filter(r => r._selected);
this.pageSelected = this.pagedData.filter(r => r._selected);
this.emitSelected(this.selected);
// Only show the select all message when there is only one new page selected at a time
this.selectedPageCount++;
this.showSelectAllMessage = this.selectedPageCount === 1 && this.selected.length !== this.dataProvider.total;
}
}
/**
* @name selectAll
*/
selectAll(value) {
this.master = value;
this.indeterminate = false;
for (let row of this.dataProvider.list) {
row._selected = value;
}
this.selected = value ? this.dataProvider.list : [];
this.showSelectAllMessage = false;
this.selectedPageCount = this.selectedPageCount > 0 ? this.selectedPageCount - 1 : 0;
this.rowSelectHandler();
}
/**
* @name rowSelectHandler
*/
rowSelectHandler() {
//this.pagedData = this.rows.slice(this.getPageStart(), this.getPageEnd());
this.pageSelected = this.pagedData.filter(r => r._selected);
this.selected = this.dataProvider.list.filter(r => r._selected);
if (this.pageSelected.length === 0) {
this.master = false;
this.indeterminate = false;
} else if (this.pageSelected.length === this.pagedData.length) {
this.master = true;
this.indeterminate = false;
} else {
this.master = false;
this.indeterminate = true;
// Breaking the selected page count
this.showSelectAllMessage = false;
this.selectedPageCount = this.selectedPageCount > 0 ? this.selectedPageCount - 1 : 0;
}
this.emitSelected(this.selected);
}
/**
* @name emitSelected
* @param selected
*/
emitSelected(selected) {
this.onRowSelect.emit({ length: selected.length, selected: selected });
}
/**
* @name rowClickHandler
* @param row
*/
rowClickHandler(row) {
if (this.config.rowSelect) {
this.activeId = row.id || 0;
this.onRowClick.emit(row);
}
}
/**
* @name setDateOptions
* @returns {Array}
*/
getDefaultOptions(column) {
// TODO - needs to come from label service - https://github.com/bullhorn/novo-elements/issues/116
let opts: Array<any> = [
{ label: this.labels.past1Day, min: -1, max: 0 },
{ label: this.labels.past7Days, min: -7, max: 0 },
{ label: this.labels.past30Days, min: -30, max: 0 },
{ label: this.labels.past90Days, min: -90, max: 0 },
{ label: this.labels.past1Year, min: -366, max: 0 },
{ label: this.labels.next1Day, min: 0, max: 1 },
{ label: this.labels.next7Days, min: 0, max: 7 },
{ label: this.labels.next30Days, min: 0, max: 30 },
{ label: this.labels.next90Days, min: 0, max: 90 },
{ label: this.labels.next1Year, min: 0, max: 366 }
];
if (column && column.range) {
opts.push({
label: this.labels.customDateRange,
range: true
});
}
return opts;
}
onCalenderSelect(column, event): void {
setTimeout(() => {
if (event.startDate && event.endDate) {
this.onFilterChange();
}
}, 10);
}
onFilterKeywords(config) {
if (config && config.filtering && config.filtering.freetextFilter) {
let filterKeywords = config.filtering.freetextFilter.toLowerCase();
if (!config.filtering.originalOptions) {
config.filtering.originalOptions = config.filtering.options;
}
let newOptions = config.filtering.originalOptions.filter(option => {
let value = option && option.label ? option.label : option;
value = value.toLowerCase() ? value.toLowerCase() : value;
if (value === filterKeywords) {
return true;
} else if (~value.indexOf(filterKeywords) || ~value.indexOf(filterKeywords)) {
return true;
}
return false;
});
config.filtering.options = newOptions;
config.filtering.filter = config.filtering.freetextFilter;
} else {
config.filtering.options = config.filtering.originalOptions;
}
this.onFilterChange();
}
/**
* @name setTableEdit
* @description Sets the Table into EDIT mode, based on the row/column passed you can enter in a few states
* (1) setTableEdit() - don't pass any to put the FULL table into edit mode
* (2) setTableEdit(1) - pass only row to put that FULL row of the table into edit mode
* (3) setTableEdit(1, 1) - pass row and column to put that column of the row of the table into edit mode
* @param {number} [rowNumber]
* @param {number} [columnNumber]
* @memberOf NovoTableElement
*/
setTableEdit(rowNumber?: number, columnNumber?: number): void {
this.mode = NovoTableMode.EDIT;
this._dataProvider.edit();
this._rows.forEach((row, rowIndex) => {
row._editing = row._editing || {};
this.columns.forEach((column, columnIndex) => {
if (column.viewOnly) {
row._editing[column.name] = false;
} else if (Helpers.isEmpty(rowNumber) && Helpers.isEmpty(columnNumber)) {
row._editing[column.name] = true;
} else if (!Helpers.isEmpty(rowNumber) && rowIndex === Number(rowNumber) && Helpers.isEmpty(columnNumber)) {
row._editing[column.name] = true;
} else if (!Helpers.isEmpty(rowNumber) && !Helpers.isEmpty(columnNumber) && rowIndex === Number(rowNumber) && columnIndex === Number(columnNumber)) {
row._editing[column.name] = true;
} else {
row._editing[column.name] = false;
}
});
});
}
/**
* @name leaveEditMode
* @description Leaves edit mode for the Table and puts everything back to VIEW only
* @memberOf NovoTableElement
* @param {cancel} [boolean] - whether or not to save data or undo
*/
private leaveEditMode(cancel: boolean): void {
this.mode = NovoTableMode.VIEW;
this._rows.forEach((row) => {
row._editing = row._editing || {};
this.columns.forEach((column) => {
row._editing[column.name] = false;
});
});
if (cancel) {
this._dataProvider.undo();
} else {
this._dataProvider.commit();
}
this.hideToastMessage();
}
/**
* @name addEditableRow
* @description Adds a new row into the table to be edited, can be called from a local reference of the table in your template
* @param {*} [defaultValue={}]
* @memberOf NovoTableElement
*/
addEditableRow(defaultValue: any = {}): void {
let tableFormRows = <FormArray>this.tableForm.controls['rows'];
let row: any = {};
let rowControls = [];
row.controls = {};
row._editing = {};
row.rowId = this._rows.length + 1;
this.columns.forEach(column => {
// Use the control passed or use a ReadOnlyControl so that the form has the values
let control = column.editorConfig ? ControlFactory.create(column.editorType, column.editorConfig) : new ReadOnlyControl({ key: column.name });
control.value = null; //remove copied column value
row.controls[column.name] = control;
row._editing[column.name] = !column.viewOnly;
rowControls.push(control);
});
this.formUtils.setInitialValues(rowControls, defaultValue, false);
tableFormRows.push(this.formUtils.toFormGroup(rowControls));
this._rows.push(row);
}
/**
* @name validateAndGetUpdatedData
* @description Validates the Form inside of the Table, if there are errors it will display/return the errors for each row.
* If there are no errors, then it will return ONLY the changed data for each row, the data returned will be in the form:
* { id: ID_OF_RECORD, key: value } -- data that was updated
* { id: undefined, key: value } -- data that was added
* @returns {{ changed?: any[], errors?: { errors: any, row: any, index: number }[] }} - either the changed data or errors!
* @memberOf NovoTableElement
*/
validateAndGetUpdatedData(): { changed?: any[], errors?: { errors: any, row: any, index: number }[] } {
if (this.tableForm && this.tableForm.controls && this.tableForm.controls['rows']) {
let changedRows = [];
let errors = [];
// Go over the FormArray's controls
(this.tableForm.controls['rows'] as FormArray).controls.forEach((formGroup: FormGroup, index: number) => {
let changedRow = null;
let error = null;
// Go over the form group controls
Object.keys(formGroup.controls).forEach((key: string) => {
let control = formGroup.controls[key];
// Handle value changing
if (control && control.dirty && !control.errors) {
if (!changedRow) {
// Append the ID, so we have some key to save against
changedRow = {};
if (this._rows[index].id) {
changedRow.id = this._rows[index].id;
}
}
// If dirty, grab value off the form
changedRow[key] = this.tableForm.value['rows'][index][key];
// Set value back to row (should be already done via the server call, but do it anyway)
this._rows[index][key] = changedRow[key];
} else if (control && control.errors) {
// Handle errors
if (!error) {
error = {};
}
error[key] = control.errors;
control.markAsDirty();
control.markAsTouched();
}
});
if (changedRow) {
changedRows.push(changedRow);
}
if (error) {
errors.push({ errors: error, row: this._rows[index], index: index });
}
});
let ret = {};
// Return errors if any, otherwise return the changed rows
if (errors.length === 0) {
return { changed: changedRows };
}
return { errors: errors };
}
}
/**
* @name cancelEditing
* @description Refresh the data provider and leave edit mode
* @memberOf NovoTableElement
*/
cancelEditing(): void {
this.leaveEditMode(true);
}
/**
* @name saveChanges
* @description Refresh the data provider and leave edit mode
* @memberOf NovoTableElement
*/
saveChanges(): void {
this.leaveEditMode(false);
}
/**
* @name displayToastMessage
* @description Displays a toast message inside of the table
* @param {{ icon: string, theme: string, message: string }} toast
* @param {number} [hideDelay]
* @memberOf NovoTableElement
*/
displayToastMessage(toast: { icon: string, theme: string, message: string }, hideDelay?: number): void {
this.loading = false;
this.toast = toast;
if (hideDelay) {
setTimeout(() => this.hideToastMessage(), hideDelay);
}
}
/**
* @name hideToastMessage
* @description Force hide the toast message
* @memberOf NovoTableElement
*/
hideToastMessage(): void {
this.toast = null;
// Hack to make the table display properly after hiding the toast
this.grossFlagToAvoidTheTableFromBeingUglyWhenHidingTheToast = true;
setTimeout(() => {
this.grossFlagToAvoidTheTableFromBeingUglyWhenHidingTheToast = false;
});
}
/**
* @name toggleLoading
* @description display the loading overlay on the table
* @param {boolean} show
* @memberOf NovoTableElement
*/
toggleLoading(show: boolean): void {
this.loading = show;
}
/**
* @name isColumnHidden
* @description hide a column in edit or view mode
* @param {column meta} column
* @returns {boolean}
* @memberOf NovoTableElement
*/
isColumnHidden(column: any): boolean {
return this.editing ? !!column.hideColumnOnEdit : !!column.hideColumnOnView;
}
}