diff --git a/src/HeaderCell.ts b/src/HeaderCell.ts new file mode 100644 index 0000000..dc45847 --- /dev/null +++ b/src/HeaderCell.ts @@ -0,0 +1,59 @@ +import { v } from '@dojo/widget-core/d'; +import { RegistryMixin, RegistryMixinProperties } from '@dojo/widget-core/mixins/Registry'; +import { ThemeableMixin, theme, ThemeableProperties } from '@dojo/widget-core/mixins/Themeable'; +import WidgetBase from '@dojo/widget-core/WidgetBase'; +import { HasColumn, HasSortDetail, HasSortEvent } from './interfaces'; + +import * as headerCellClasses from './styles/headerCell.css'; + +export const HeaderCellBase = ThemeableMixin(RegistryMixin(WidgetBase)); + +export interface HeaderCellProperties extends ThemeableProperties, HasColumn, HasSortDetail, HasSortEvent, RegistryMixinProperties {} + +@theme(headerCellClasses) +class HeaderCell extends HeaderCellBase { + onSortRequest(): void { + const { + key = '', + sortDetail, + onSortRequest + } = this.properties; + + onSortRequest({ + columnId: key, + descending: Boolean(sortDetail && sortDetail.columnId === key && !sortDetail.descending) + }); + } + + render() { + const { + key, + column, + sortDetail, + onSortRequest + } = this.properties; + + const classes = [ headerCellClasses.headerCell, column.sortable !== false ? headerCellClasses.sortable : null ]; + + const sortClasses = sortDetail ? [ + headerCellClasses.sortArrow, + sortDetail.descending ? headerCellClasses.sortArrowDown : headerCellClasses.sortArrowUp + ] : []; + + const onclick = (onSortRequest && column.sortable !== false) ? { onclick: this.onSortRequest } : {}; + + return v('th', { + role: 'columnheader', + ...onclick, + classes: this.classes(...classes) + }, [ + v('span', [ column.label || column.id ]), + sortDetail && sortDetail.columnId === key ? v('div', { + role: 'presentation', + classes: this.classes(...sortClasses) + }) : null + ]); + } +} + +export default HeaderCell; diff --git a/src/interfaces.ts b/src/interfaces.ts index 464c8db..c5d3722 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,3 +1,8 @@ +export interface SortDetails { + columnId: string; + descending?: boolean; +} + export interface ItemProperties { id: string; data: T; @@ -14,6 +19,14 @@ export interface HasColumn { column: Column; } +export interface HasSortDetail { + sortDetail: SortDetails; +} + +export interface HasSortEvent { + onSortRequest(sortDetail: SortDetails): void; +} + export interface HasItem { item: ItemProperties; } diff --git a/src/styles/dgrid.css b/src/styles/dgrid.css index 4926105..1c2f4dd 100644 --- a/src/styles/dgrid.css +++ b/src/styles/dgrid.css @@ -1 +1,2 @@ +@import 'headerCell.css'; @import 'cell.css'; diff --git a/src/styles/headerCell.css b/src/styles/headerCell.css new file mode 100644 index 0000000..90469ac --- /dev/null +++ b/src/styles/headerCell.css @@ -0,0 +1,25 @@ +.headerCell { + composes: cell from './shared/cell.css'; + position: relative; +} + +.sortable { + cursor: pointer; +} + +.sortArrow { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 4px; +} + +.sortArrowUp:after { + content: '▲'; + display: block; +} + +.sortArrowDown:after { + content: '▼'; + display: block; +} diff --git a/src/styles/headerCell.css.d.ts b/src/styles/headerCell.css.d.ts new file mode 100644 index 0000000..2132e67 --- /dev/null +++ b/src/styles/headerCell.css.d.ts @@ -0,0 +1,5 @@ +export const headerCell: string; +export const sortable: string; +export const sortArrow: string; +export const sortArrowUp: string; +export const sortArrowDown: string; diff --git a/src/styles/images/ui-icons_222222_256x240.png b/src/styles/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..b273ff1 Binary files /dev/null and b/src/styles/images/ui-icons_222222_256x240.png differ diff --git a/tests/unit/HeaderCell.ts b/tests/unit/HeaderCell.ts new file mode 100644 index 0000000..87df4fc --- /dev/null +++ b/tests/unit/HeaderCell.ts @@ -0,0 +1,75 @@ +import { VNode } from '@dojo/interfaces/vdom'; +import FactoryRegistry from '@dojo/widget-core/FactoryRegistry'; +import { assert } from 'chai'; +import * as registerSuite from 'intern/lib/interfaces/object'; +import HeaderCell from '../../src/HeaderCell'; + +registerSuite({ + name: 'HeaderCell', + render: { + 'renders sortable header cell with descending direction'() { + let clicked = false; + const properties = { + registry: new FactoryRegistry(), + onSortRequest() { clicked = true; }, + column: { + id: 'id', + label: 'foo', + sortable: true + }, + sortDetail: { + columnId: 'id', + descending: true + }, + key: 'id' + }; + const headerCell = new HeaderCell(); + headerCell.setProperties(properties); + + const vnode = headerCell.__render__(); + vnode.properties!.onclick!.call(headerCell); + + assert.strictEqual(vnode.vnodeSelector, 'th'); + assert.isFunction(vnode.properties!.onclick); + assert.isTrue(clicked); + assert.equal(vnode.properties!['role'], 'columnheader'); + assert.lengthOf(vnode.children, 2); + assert.equal(vnode.children![0].vnodeSelector, 'span'); + assert.equal(vnode.children![0].text, 'foo'); + assert.equal(vnode.children![1].vnodeSelector, 'div'); + assert.equal(vnode.children![1].properties!['role'], 'presentation'); + }, + 'renders sortable header cell with ascending direction'() { + let clicked = false; + const properties = { + registry: new FactoryRegistry(), + onSortRequest() { clicked = true; }, + column: { + id: 'id', + label: 'foo', + sortable: true + }, + sortDetail: { + columnId: 'id', + descending: false + }, + key: 'id' + }; + const headerCell = new HeaderCell(); + headerCell.setProperties(properties); + + const vnode = headerCell.__render__(); + vnode.properties!.onclick!.call(headerCell); + + assert.strictEqual(vnode.vnodeSelector, 'th'); + assert.isFunction(vnode.properties!.onclick); + assert.isTrue(clicked); + assert.equal(vnode.properties!['role'], 'columnheader'); + assert.lengthOf(vnode.children, 2); + assert.equal(vnode.children![0].vnodeSelector, 'span'); + assert.equal(vnode.children![0].text, 'foo'); + assert.equal(vnode.children![1].vnodeSelector, 'div'); + assert.equal(vnode.children![1].properties!['role'], 'presentation'); + } + } +}); diff --git a/tests/unit/all.ts b/tests/unit/all.ts index f859d21..4ad8a3b 100644 --- a/tests/unit/all.ts +++ b/tests/unit/all.ts @@ -1 +1,2 @@ +import './HeaderCell'; import './Cell'; diff --git a/tslint.json b/tslint.json index 65626cb..f36ac25 100644 --- a/tslint.json +++ b/tslint.json @@ -62,7 +62,7 @@ "variable-declaration": "onespace" } ], "use-strict": false, - "variable-name": [ true, "check-format", "allow-leading-underscore", "ban-keywords" ], + "variable-name": [ true, "check-format", "allow-leading-underscore", "ban-keywords", "allow-pascal-case" ], "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-type", "check-typecast" ] } }