diff --git a/packages/toast-ui.grid/cypress/integration/column.spec.ts b/packages/toast-ui.grid/cypress/integration/column.spec.ts index 1af10c841..358f16ca7 100644 --- a/packages/toast-ui.grid/cypress/integration/column.spec.ts +++ b/packages/toast-ui.grid/cypress/integration/column.spec.ts @@ -483,3 +483,53 @@ describe('move column', () => { }); }); }); + +describe('customHeader', () => { + let customHeader: HTMLElement; + + const CUSTOM_HEADER_CLASS_NAME = 'custom-header'; + const data = [ + { name: 'Kim', age: 10, price: 100 }, + { name: 'Lee', age: 20, price: 200 }, + { name: 'Ryu', age: 30, price: 300 }, + { name: 'Han', age: 40, price: 400 }, + ]; + + beforeEach(() => { + customHeader = document.createElement('div'); + customHeader.className = CUSTOM_HEADER_CLASS_NAME; + customHeader.textContent = 'Custom Header'; + }); + + it('should render the given HTML element to the header.', () => { + const columns = [ + { name: 'name', header: 'Name', customHeader }, + { name: 'age', header: 'Age' }, + { name: 'price', header: 'Price' }, + ]; + + cy.createGrid({ + data, + columns, + }); + + cy.getHeaderCell('name').click().should('contain.html', customHeader.outerHTML); + }); + + it('should sets the textContent of the customHeader to the value of the header property if no header property is passed.', () => { + const columns = [ + { name: 'name', customHeader }, + { name: 'age', header: 'Age' }, + { name: 'price', header: 'Price' }, + ]; + + cy.createGrid({ + data, + columns, + }); + + cy.gridInstance() + .invoke('getColumn', 'name') + .should('contain', { header: customHeader.textContent }); + }); +}); diff --git a/packages/toast-ui.grid/src/grid.tsx b/packages/toast-ui.grid/src/grid.tsx index ae9bff3d4..bda7109b0 100644 --- a/packages/toast-ui.grid/src/grid.tsx +++ b/packages/toast-ui.grid/src/grid.tsx @@ -255,6 +255,8 @@ if ((module as any).hot) { * the text line is broken by fitting to the column's width and new line characters.(This option will be deprecated) * @param {boolean} [options.columns.rowSpan=false] - If set to true, apply dynamic rowspan to column data. * If it is not a top-level relational column of a column relationship or the grid has tree data, dynamic rowspan is not applied. + * @param {HTMLElement} [options.columns.customHeader] - If set HTML element, the element will render in header cell. + * If set without header property, the text content of element will be set to header property. * @param {Object} [options.summary] - The object for configuring summary area. * @param {number} [options.summary.height] - The height of the summary area. * @param {string} [options.summary.position='bottom'] - The position of the summary area. ('bottom', 'top') diff --git a/packages/toast-ui.grid/src/store/column.ts b/packages/toast-ui.grid/src/store/column.ts index c1b013907..2ff025c1d 100644 --- a/packages/toast-ui.grid/src/store/column.ts +++ b/packages/toast-ui.grid/src/store/column.ts @@ -1,44 +1,44 @@ import { - OptColumn, - OptRowHeader, - OptTree, + Dictionary, OptCellEditor, OptCellRenderer, + OptColumn, OptColumnHeaderInfo, OptComplexColumnInfo, - Dictionary, OptFilter, + OptRowHeader, OptRowHeaderColumn, + OptTree, } from '@t/options'; import { - ColumnOptions, AlignType, - VAlignType, - Relations, - ColumnInfo, - CellRendererOptions, CellEditorOptions, + CellRendererOptions, ClipboardCopyOptions, - ColumnFilterOption, Column, + ColumnFilterOption, ColumnHeaderInfo, + ColumnInfo, + ColumnOptions, + Relations, + VAlignType, } from '@t/store/column'; import { FilterOptionType } from '@t/store/filterLayerState'; import { observable } from '../helper/observable'; import { isRowNumColumn } from '../helper/column'; import { createMapFromArray, + findIndex, + findProp, includes, - omit, - isString, + isEmpty, isFunction, + isNumber, isObject, + isString, isUndefined, - isNumber, - findProp, + omit, uniq, - isEmpty, - findIndex, } from '../helper/common'; import { DefaultRenderer } from '../renderer/default'; import { editorMap } from '../editor/manager'; @@ -244,6 +244,7 @@ export function createColumn( filter, className, comparator, + customHeader, } = column; const editorOptions = createEditorOptions(editor); @@ -262,7 +263,7 @@ export function createColumn( return observable({ name, escapeHTML, - header: header || name, + header: header || customHeader?.textContent || name, hidden: Boolean(hidden), resizable: isUndefined(resizable) ? Boolean(columnOptions.resizable) : Boolean(resizable), align: align || 'left', @@ -295,6 +296,7 @@ export function createColumn( comparator, autoResizing: width === 'auto', rowSpan, + customHeader, }); } diff --git a/packages/toast-ui.grid/src/view/columnHeader.tsx b/packages/toast-ui.grid/src/view/columnHeader.tsx index 5e3b29689..21af12eb1 100644 --- a/packages/toast-ui.grid/src/view/columnHeader.tsx +++ b/packages/toast-ui.grid/src/view/columnHeader.tsx @@ -1,11 +1,11 @@ -import { h, Component } from 'preact'; +import { Component, h } from 'preact'; import { cls } from '../helper/dom'; import { HeaderCheckbox } from './headerCheckbox'; import { SortingButton } from './sortingButton'; import { SortingOrder } from './sortingOrder'; import { FilterButton } from './filterButton'; -import { isRowHeader, isCheckboxColumn } from '../helper/column'; -import { HeaderRenderer, ColumnHeaderInfo } from '@t/renderer'; +import { isCheckboxColumn, isRowHeader } from '../helper/column'; +import { ColumnHeaderInfo, HeaderRenderer } from '@t/renderer'; import Grid from '../grid'; import { isFunction } from '../helper/common'; import { isDraggableColumn } from '../query/column'; @@ -28,15 +28,35 @@ export class ColumnHeader extends Component { private getElement(type: string) { const { columnInfo } = this.props; - const { name, sortable, sortingType, filter, headerRenderer, header } = columnInfo; + const { + name, + sortable, + sortingType, + filter, + headerRenderer, + header, + customHeader, + } = columnInfo; if (headerRenderer) { return null; } switch (type) { - case 'checkbox': - return isCheckboxColumn(name) ? : header; + case 'checkbox': { + if (isCheckboxColumn(name)) { + return ; + } + + if (this.el && customHeader) { + this.el.appendChild(customHeader); + + return null; + } + + return header; + } + case 'sortingBtn': return sortable && ; case 'sortingOrder': diff --git a/packages/toast-ui.grid/src/view/headerCheckbox.tsx b/packages/toast-ui.grid/src/view/headerCheckbox.tsx index dac97156b..07a873d2c 100644 --- a/packages/toast-ui.grid/src/view/headerCheckbox.tsx +++ b/packages/toast-ui.grid/src/view/headerCheckbox.tsx @@ -1,9 +1,11 @@ -import { h, Component } from 'preact'; +import { Component, h } from 'preact'; import { connect } from './hoc'; import { DispatchProps } from '../dispatch/create'; +import { ColumnInfo } from '@t/store/column'; interface StoreProps { header: string; + customHeader: ColumnInfo['customHeader']; checkedAllRows: boolean; disabled: boolean; } @@ -63,8 +65,11 @@ export const HeaderCheckbox = connect((store) => { column: { allColumnMap }, } = store; + const { header, customHeader } = allColumnMap._checked; + return { - header: allColumnMap._checked.header, + header, + customHeader, checkedAllRows, disabled: disabledAllCheckbox, }; diff --git a/packages/toast-ui.grid/types/renderer/index.d.ts b/packages/toast-ui.grid/types/renderer/index.d.ts index 9b1cb16a4..5ae58c57a 100644 --- a/packages/toast-ui.grid/types/renderer/index.d.ts +++ b/packages/toast-ui.grid/types/renderer/index.d.ts @@ -1,11 +1,11 @@ import TuiGrid from '../index'; import { CellRenderData, RowKey } from '../store/data'; import { - ColumnInfo, AlignType, - VAlignType, - SortingType, ColumnFilterOption, + ColumnInfo, + SortingType, + VAlignType, } from '../store/column'; export type CellRendererProps = CellRenderData & { @@ -29,6 +29,7 @@ export interface CellRendererClass { export interface ColumnHeaderInfo { name: string; header: string; + customHeader?: ColumnInfo['customHeader']; headerAlign?: AlignType; headerVAlign?: VAlignType; sortable?: boolean; diff --git a/packages/toast-ui.grid/types/store/column.d.ts b/packages/toast-ui.grid/types/store/column.d.ts index f3e1c196a..aca10d11e 100644 --- a/packages/toast-ui.grid/types/store/column.d.ts +++ b/packages/toast-ui.grid/types/store/column.d.ts @@ -1,7 +1,7 @@ import { Side } from './focus'; import { CellValue, Row } from './data'; -import { OptTree, Dictionary, OptColumnHeaderInfo, GridEventListener } from '../options'; -import { HeaderRendererClass, CellRendererClass, CellRendererProps } from '../renderer'; +import { Dictionary, GridEventListener, OptColumnHeaderInfo, OptTree } from '../options'; +import { CellRendererClass, CellRendererProps, HeaderRendererClass } from '../renderer'; import { CellEditorClass } from '../editor'; import { FilterOptionType, OperatorType } from './filterLayerState'; @@ -125,6 +125,7 @@ export interface InvalidColumn { export interface CommonColumnInfo { header: string; + customHeader?: HTMLElement; hidden: boolean; align: AlignType; valign: VAlignType;