UNPKG

@bigfishtv/cockpit

Version:

378 lines (350 loc) 12.6 kB
/** * Table Utilities * @module Utilities/tableUtils */ import _get from 'lodash/get' import moment from 'moment' import * as SortTypes from '../constants/SortTypes' import * as Conditions from '../constants/Conditions' import { isNumeric, isString, isFunction } from './typeUtils' import { titleCase } from './stringUtils' import { getSchemaWithAssociations } from './formUtils' import FixedDataTableCheckboxCell from '../components/table/cell/FixedDataTableCheckboxCell' import FixedDataTableDateCell from '../components/table/cell/FixedDataTableDateCell' import FixedDataTableHtmlCell from '../components/table/cell/FixedDataTableHtmlCell' import FixedDataTableAssetCell from '../components/table/cell/FixedDataTableAssetCell' import FixedDataTableDecimalCell from '../components/table/cell/FixedDataTableDecimalCell' /** * Returns string swapping between ASC and DESC * @param {String} sortDir * @return {String} */ export function reverseSortDirection(sortDir) { return sortDir === SortTypes.DESC ? SortTypes.ASC : SortTypes.DESC } /** * Returns a sort function that takes into account sort direction and type * @param {String} columnKey - key that will be in both objects to compare * @param {String} sortDirection - direction of sort either ASC or DESC * @param {String} [sortType=string] - sortType: string, numeric, boolean * @return {Function} - returns sort function */ export function sortByObjectKey(columnKey, sortDirection = SortTypes.ASC, sortType = 'string') { return (a, b) => { let valA = _get(a, columnKey) let valB = _get(b, columnKey) let sortVal = 0 if (valA === null || valA === undefined) valA = sortType == 'string' ? '' : 0 if (valB === null || valB === undefined) valB = sortType == 'string' ? '' : 0 if (sortType == 'string') { valA = valA && typeof valA.toLowerCase == 'function' ? valA.toLowerCase() : valA valB = valB && typeof valB.toLowerCase == 'function' ? valB.toLowerCase() : valB sortVal = valA > valB ? 1 : valA < valB ? -1 : 0 } else if (sortType == 'numeric' || sortType == 'boolean') { sortVal = valB - valA > 0 ? -1 : valB - valA < 0 ? 1 : 0 } if (sortVal !== 0 && sortDirection === SortTypes.DESC) sortVal = sortVal * -1 return sortVal } } /** * Takes schema column, looks at type and resolves to javascript sort type * @param {Object} column * @param {String} column.type - e.g. integer, float, decimal, boolean * @return {string} */ export function getSortTypeFromColumn(column) { switch (column.type) { case 'integer': return 'numeric' break case 'float': return 'numeric' break case 'decimal': return 'numeric' break case 'boolean': return 'boolean' break } return 'string' } /** * Takes schema column, looks at type and resolves to corresponding table cell component * @param {Object} column * @param {String} column.type - e.g. datetime, timestamp, boolean, text * @return {React.Component} - Returns react component, else null */ export function getCellComponentFromColumn(column) { switch (column.type) { case 'datetime': return FixedDataTableDateCell break case 'timestamp': return FixedDataTableDateCell break case 'boolean': return FixedDataTableCheckboxCell break case 'text': return FixedDataTableHtmlCell break case 'decimal': case 'float': return FixedDataTableDecimalCell break } switch (column.className) { case 'Tank.Assets': return FixedDataTableAssetCell break } return null } /** * Generates default field attributes from a schema column * @param {Object} column * @param {String} column.type * @param {String} column.property * @return {Object} */ export function getFieldAttributesFromColumn(column) { let attributes = {} switch (column.property) { case 'id': attributes = { ...attributes, width: 40, fixed: true } break } switch (column.type) { case 'json': attributes = { ...attributes, sortable: false } break } switch (column.className) { case 'Tank.Assets': attributes = { ...attributes, width: 80 } break } return attributes } /** * Generates default table fields from schema * @param {Array} schema * @param {Function} componentResolver - precursor function to resolve component based on scema column * @param {Function} attributeModifier - precursor function to resolve additional field attributes based on scema column * @return {Array} - returns array of table fields */ export function getFieldsFromSchema(schema = [], componentResolver = () => null, attributeModifier = () => ({})) { return schema.map(column => { const sortType = getSortTypeFromColumn(column) const Cell = componentResolver(column) || getCellComponentFromColumn(column) const additionalAttributes = { ...attributeModifier(column), ...getFieldAttributesFromColumn(column) } return { key: column.property, value: column.title || titleCase(column.property), resizable: true, sortable: true, schema: column, sortType, Cell, ...additionalAttributes, } }) } /** * Takes schema and assocations and adds 'belongsToMany' assocations to schema before returning result from 'getFieldsFromSchema' * @param {Array} schema * @param {Object[]} assocations * @param {String} assocations[].type - e.g. belongsTo, belongsToMany * @param {Function} componentResolver - precursor function to resolve component based on scema column * @param {Function} attributeModifier - precursor function to resolve additional field attributes based on scema column * @return {Array} - returns array of table fields */ export function getFieldsFromSchemaAndAssociations( schema = [], associations = [], componentResolver = () => null, attributeModifier = () => ({}) ) { const newSchema = getSchemaWithAssociations(schema, associations) return normalizeFields(getFieldsFromSchema(newSchema, componentResolver, attributeModifier)) } /** * Takes a table fields and fixes any undefined variables in each one * @param {Object[]} fields * @param {String} fields[].sortType * @param {Boolean} fields[].resizable * @param {Boolean} fields[].sortable * @return {Array} */ export function normalizeFields(fields) { let order = 0 return fields .map(field => { if (typeof field.sortType == 'undefined') field.sortType = 'string' if (typeof field.resizable == 'undefined') field.resizable = true if (typeof field.sortable == 'undefined') field.sortable = true if (typeof field.order == 'undefined') field.order = ++order return field }) .sort((a, b) => (a.order < b.order ? -1 : a.order > b.order ? 1 : 0)) } /** * Filters a data set by a query string, takes schemaTypes object to smartly skip columns of certain type * @param {Array} data * @param {String} queryString * @param {Object} schemaTypes - Keyed object containing schema column types, e.g. {id: 'numeric', title: 'string'} * @return {Array} - returns filtered data array */ export function filterDataByQuery(data = [], queryString = '', schemaTypes = {}) { // first check if anything is being searched if (!queryString || queryString === '') return data // filter all rows return data.filter(row => { let rowPassed = false // map over field names for every row Object.keys(schemaTypes).map(columnName => { const rowValue = _get(row, columnName) // if a number has been searched for then allow for searching in numeric fields if ( isNumeric(queryString) && schemaTypes[columnName] == 'numeric' && isNumeric(rowValue) && rowValue.toString().indexOf(queryString.toString()) >= 0 ) { rowPassed = true // otherwise search for query string in all string-type columns } else if ( schemaTypes[columnName] == 'string' && isString(rowValue) && rowValue.toLowerCase().indexOf(queryString.toLowerCase()) >= 0 ) { rowPassed = true } }) return rowPassed }) } /** * Takes data array, schema array and query string and smarly filters data using schema * @param {Array} data * @param {Array} schema * @param {String} queryString * @return {Array} - returns filtered data */ export function filterDataByQueryWithSchema(data = [], schema = [], queryString = '') { // create an associative object for column name => content type const schemaTypes = {} for (let column of schema) { schemaTypes[column.property] = getSortTypeFromColumn(column) } return filterDataByQuery(data, queryString, schemaTypes) } /** * Takes data array, table fields and query string and smarly filters data using table fields * @param {Array} data * @param {Array} fields * @param {String} queryString * @return {Array} - returns filtered data */ export function filterDataByQueryWithFields(data = [], fields = [], queryString = '') { // create an associative object for column name => content type const schemaTypes = {} for (let column of fields) { schemaTypes[column.key] = column.sortType } return filterDataByQuery(data, queryString, schemaTypes) } /** * Takes data array and a keyed object of filters that correspond to keys in data array objects * @param {Array} data * @param {Object} filterset * @return {Array} - returns filtered data */ export function filterDataByFilterset(data = [], filterset = {}, condition = Conditions.OR) { const properties = Object.keys(filterset) if (!properties.length) return data return data.filter(row => { let passes = 0 for (let property of properties) { if (filterset[property] === null) { passes++ } else { const rowValue = _get(row, property, undefined) if (rowValue !== undefined) { if (isFunction(filterset[property]) && filterset[property](rowValue, row)) passes++ else if (rowValue === filterset[property]) passes++ } } } if ((condition == Conditions.OR && passes > 0) || (condition == Conditions.AND && passes === properties.length)) return true return false }) } /** * Filters out data not within the date range. * * @param {Array} data * @param {String} property * @param {String|Date} fromDate * @param {String|Date} toDate * @return {Array} filtered data */ export function filterDataByDateRange(data, property, fromDate, toDate) { if (fromDate) { fromDate = moment(fromDate) data = data.filter(row => { const value = _get(row, property, undefined) return value && moment(value) >= fromDate }) } if (toDate) { toDate = moment(toDate) data = data.filter(row => { const value = _get(row, property, undefined) return value && moment(value) <= toDate }) } return data } /** * Used for creating compact filterset rules * Returns a function that takes override values and calls createFilterset the default values, override values, and callback function * @param {Object} defaultValues Default filterset values * @param {Function} callback Typically 'handleFilterChange' passed in from AutoTableIndex * @return {Function} Returns function that takes override values */ export function createFiltersetGenerator(defaultValues = {}, callback = () => {}) { return (values = {}) => createFilterset(defaultValues, values, callback) } /** * Takes default values, override values and a callback function * Returns an empty function that calls the callback function with the combined values * @param {Object} defaultValues * @param {Object} values * @param {Function} callback Typically 'handleFilterChange' passed in from AutoTableIndex * @return {Function} Returns function that can be directly called in on onClick prop */ export function createFilterset(defaultValues = {}, values = {}, callback = () => {}) { return () => callback({ ...defaultValues, ...values }) } /** * Used for getting a currently selected filterset label based off rules provided * Note that the order in which the rules are provided is important as the loop short circuits as soon as a checker returns true * @param {Object} filterset Current filterset, typically provided by AutoTableIndex * @param {Object} rules Rules object where key is label and value is a checker function that should return a boolean * @param {String} defaultLabel Default label to return if no rules are matched * @return {String} */ export function getFiltersetLabel(filterset = {}, rules = {}, defaultLabel = 'All') { let filtersetLabel = defaultLabel for (const [label, checker] of Object.entries(rules)) { if (checker && checker(filterset)) { filtersetLabel = label break } } return filtersetLabel }