UNPKG

11 kBPlain TextView Raw
1import {Component} from "../../widgets/component";
2import {Column} from "../../entities/column";
3import {Utils as _} from "../../utils";
4import {Autowired} from "../../context/context";
5import {IMenuFactory} from "../../interfaces/iMenuFactory";
6import {GridOptionsWrapper} from "../../gridOptionsWrapper";
7import {SortController} from "../../sortController";
8import {LongTapEvent, TouchListener} from "../../widgets/touchListener";
9import {IComponent} from "../../interfaces/iComponent";
10import {EventService} from "../../eventService";
11import {RefSelector} from "../../widgets/componentAnnotations";
12import {Events} from "../../events";
13import {ColumnApi} from "../../columnController/columnApi";
14import {GridApi} from "../../gridApi";
15
16export interface IHeaderParams {
17 column: Column;
18 displayName: string;
19 enableSorting: boolean;
20 enableMenu: boolean;
21 showColumnMenu: (source:HTMLElement)=>void;
22 progressSort: (multiSort?: boolean)=>void;
23 setSort: (sort: string, multiSort?: boolean)=>void;
24 columnApi: ColumnApi,
25 api: GridApi,
26 context: any,
27 template: string
28}
29
30export interface IHeader {
31
32}
33
34export interface IHeaderComp extends IHeader, IComponent<IHeaderParams> {
35
36}
37
38export class HeaderComp extends Component implements IHeaderComp {
39
40 private static TEMPLATE =
41 '<div class="ag-cell-label-container" role="presentation">' +
42 ' <span ref="eMenu" class="ag-header-icon ag-header-cell-menu-button" aria-hidden="true"></span>' +
43 ' <div ref="eLabel" class="ag-header-cell-label" role="presentation">' +
44 ' <span ref="eText" class="ag-header-cell-text" role="columnheader"></span>' +
45 ' <span ref="eFilter" class="ag-header-icon ag-filter-icon" aria-hidden="true"></span>' +
46 ' <span ref="eSortOrder" class="ag-header-icon ag-sort-order" aria-hidden="true"></span>' +
47 ' <span ref="eSortAsc" class="ag-header-icon ag-sort-ascending-icon" aria-hidden="true"></span>' +
48 ' <span ref="eSortDesc" class="ag-header-icon ag-sort-descending-icon" aria-hidden="true"></span>' +
49 ' <span ref="eSortNone" class="ag-header-icon ag-sort-none-icon" aria-hidden="true"></span>' +
50 ' </div>' +
51 '</div>';
52
53 @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
54 @Autowired('sortController') private sortController: SortController;
55 @Autowired('menuFactory') private menuFactory: IMenuFactory;
56 @Autowired('eventService') private eventService: EventService;
57
58 @RefSelector('eFilter') private eFilter: HTMLElement;
59 @RefSelector('eSortAsc') private eSortAsc: HTMLElement;
60
61 @RefSelector('eSortDesc') private eSortDesc: HTMLElement;
62 @RefSelector('eSortNone') private eSortNone: HTMLElement;
63 @RefSelector('eSortOrder') private eSortOrder: HTMLElement;
64 @RefSelector('eMenu') private eMenu: HTMLElement;
65 @RefSelector('eLabel') private eLabel: HTMLElement;
66 @RefSelector('eText') private eText: HTMLElement;
67
68 private params:IHeaderParams;
69
70 private lastMovingChanged = 0;
71
72 public init(params: IHeaderParams): void {
73 let template:string = _.firstExistingValue(
74 params.template,
75 HeaderComp.TEMPLATE
76 );
77
78 this.setTemplate(template);
79 this.params = params;
80
81 this.setupTap();
82 this.setupIcons(params.column);
83 this.setupMenu();
84 this.setupSort();
85 this.setupFilterIcon();
86 this.setupText(params.displayName);
87 }
88
89 private setupText(displayName: string): void {
90 if (this.eText) {
91 this.eText.innerHTML = displayName;
92 }
93 }
94
95 private setupIcons(column:Column): void {
96 this.addInIcon('sortAscending', this.eSortAsc, column);
97 this.addInIcon('sortDescending', this.eSortDesc, column);
98 this.addInIcon('sortUnSort', this.eSortNone, column);
99 this.addInIcon('menu', this.eMenu, column);
100 this.addInIcon('filter', this.eFilter, column);
101 }
102
103 private addInIcon(iconName: string, eParent: HTMLElement, column: Column): void {
104 if (eParent == null) return;
105
106 let eIcon = _.createIconNoSpan(iconName, this.gridOptionsWrapper, column);
107 eParent.appendChild(eIcon);
108 }
109
110 private setupTap(): void {
111 if (this.gridOptionsWrapper.isSuppressTouch()) { return; }
112
113 let touchListener = new TouchListener(this.getGui());
114
115 if (this.params.enableMenu) {
116 let longTapListener = (event: LongTapEvent)=> {
117 this.gridOptionsWrapper.getApi().showColumnMenuAfterMouseClick(this.params.column, event.touchStart);
118 };
119 this.addDestroyableEventListener(touchListener, TouchListener.EVENT_LONG_TAP, longTapListener);
120 }
121
122 if (this.params.enableSorting) {
123 let tapListener = ()=> {
124 this.sortController.progressSort(this.params.column, false, "uiColumnSorted");
125 };
126
127 this.addDestroyableEventListener(touchListener, TouchListener.EVENT_TAP, tapListener);
128 }
129
130 this.addDestroyFunc( ()=> touchListener.destroy() );
131 }
132
133 private setupMenu(): void {
134
135 // if no menu provided in template, do nothing
136 if (!this.eMenu) {
137 return;
138 }
139
140 // we don't show the menu if on an ipad, as the user cannot have a mouse on the ipad, so
141 // makes no sense. instead the user must long-tap if on an ipad.
142 let dontShowMenu = !this.params.enableMenu || _.isUserAgentIPad();
143
144 if (dontShowMenu) {
145 _.removeFromParent(this.eMenu);
146 return;
147 }
148
149 this.eMenu.addEventListener('click', ()=> this.showMenu(this.eMenu));
150
151 if (!this.gridOptionsWrapper.isSuppressMenuHide()) {
152 this.eMenu.style.opacity = '0';
153 this.addGuiEventListener('mouseover', ()=> {
154 this.eMenu.style.opacity = '1';
155 });
156 this.addGuiEventListener('mouseout', ()=> {
157 this.eMenu.style.opacity = '0';
158 });
159 }
160 let style = <any> this.eMenu.style;
161 style['transition'] = 'opacity 0.2s, border 0.2s';
162 style['-webkit-transition'] = 'opacity 0.2s, border 0.2s';
163 }
164
165 public showMenu(eventSource: HTMLElement) {
166 this.menuFactory.showMenuAfterButtonClick(this.params.column, eventSource);
167 }
168
169 private removeSortIcons(): void {
170 _.removeFromParent(this.eSortAsc);
171 _.removeFromParent(this.eSortDesc);
172 _.removeFromParent(this.eSortNone);
173 _.removeFromParent(this.eSortOrder);
174 }
175
176 public setupSort(): void {
177 let enableSorting = this.params.enableSorting;
178
179 if (!enableSorting) {
180 this.removeSortIcons();
181 return;
182 }
183
184 let sortUsingCtrl = this.gridOptionsWrapper.isMultiSortKeyCtrl();
185
186 // keep track of last time the moving changed flag was set
187 this.addDestroyableEventListener(this.params.column, Column.EVENT_MOVING_CHANGED, ()=> {
188 this.lastMovingChanged = new Date().getTime();
189 });
190
191 // add the event on the header, so when clicked, we do sorting
192 if (this.eLabel) {
193 this.addDestroyableEventListener(this.eLabel, 'click', (event:MouseEvent) => {
194
195 // sometimes when moving a column via dragging, this was also firing a clicked event.
196 // here is issue raised by user: https://ag-grid.zendesk.com/agent/tickets/1076
197 // this check stops sort if a) column is moving or b) column moved less than 200ms ago (so caters for race condition)
198 let moving = this.params.column.isMoving();
199 let nowTime = new Date().getTime();
200 // typically there is <2ms if moving flag was set recently, as it would be done in same VM turn
201 let movedRecently = (nowTime - this.lastMovingChanged) < 50;
202 let columnMoving = moving || movedRecently;
203
204 if (!columnMoving) {
205 let multiSort = sortUsingCtrl ? (event.ctrlKey || event.metaKey) : event.shiftKey;
206 this.params.progressSort(multiSort);
207 } else {
208 console.log(`kipping sort cos of moving ${this.lastMovingChanged}`);
209 }
210 });
211 }
212
213 this.addDestroyableEventListener(this.params.column, Column.EVENT_SORT_CHANGED, this.onSortChanged.bind(this));
214 this.onSortChanged();
215
216 this.addDestroyableEventListener(this.eventService, Events.EVENT_SORT_CHANGED, this.setMultiSortOrder.bind(this));
217 this.setMultiSortOrder();
218 }
219
220 private onSortChanged(): void {
221
222 _.addOrRemoveCssClass(this.getGui(), 'ag-header-cell-sorted-asc', this.params.column.isSortAscending());
223 _.addOrRemoveCssClass(this.getGui(), 'ag-header-cell-sorted-desc', this.params.column.isSortDescending());
224 _.addOrRemoveCssClass(this.getGui(), 'ag-header-cell-sorted-none', this.params.column.isSortNone());
225
226 if (this.eSortAsc) {
227 _.addOrRemoveCssClass(this.eSortAsc, 'ag-hidden', !this.params.column.isSortAscending());
228 }
229
230 if (this.eSortDesc) {
231 _.addOrRemoveCssClass(this.eSortDesc, 'ag-hidden', !this.params.column.isSortDescending());
232 }
233
234 if (this.eSortNone) {
235 let alwaysHideNoSort = !this.params.column.getColDef().unSortIcon && !this.gridOptionsWrapper.isUnSortIcon();
236 _.addOrRemoveCssClass(this.eSortNone, 'ag-hidden', alwaysHideNoSort || !this.params.column.isSortNone());
237 }
238 }
239
240 // we listen here for global sort events, NOT column sort events, as we want to do this
241 // when sorting has been set on all column (if we listened just for our col (where we
242 // set the asc / desc icons) then it's possible other cols are yet to get their sorting state.
243 private setMultiSortOrder(): void {
244
245 if (!this.eSortOrder) {
246 return;
247 }
248
249 let col = this.params.column;
250 let allColumnsWithSorting = this.sortController.getColumnsWithSortingOrdered();
251 let indexThisCol = allColumnsWithSorting.indexOf(col);
252 let moreThanOneColSorting = allColumnsWithSorting.length > 1;
253 let showIndex = col.isSorting() && moreThanOneColSorting;
254
255 _.setVisible(this.eSortOrder, showIndex);
256
257 if (indexThisCol>=0) {
258 this.eSortOrder.innerHTML = (indexThisCol+1).toString();
259 } else {
260 this.eSortOrder.innerHTML = '';
261 }
262 }
263
264 private setupFilterIcon(): void {
265
266 if (!this.eFilter) {
267 return;
268 }
269
270 this.addDestroyableEventListener(this.params.column, Column.EVENT_FILTER_CHANGED, this.onFilterChanged.bind(this));
271 this.onFilterChanged();
272 }
273
274 private onFilterChanged(): void {
275 let filterPresent = this.params.column.isFilterActive();
276 _.addOrRemoveCssClass(this.eFilter, 'ag-hidden', !filterPresent);
277 }
278
279}