1 | import {Logger, LoggerFactory} from "../logger";
|
2 | import {Qualifier, PostConstruct, Bean, Autowired, PreDestroy} from "../context/context";
|
3 | import {Column} from "../entities/column";
|
4 | import {Utils as _} from "../utils";
|
5 | import {GridOptionsWrapper} from "../gridOptionsWrapper";
|
6 | import {DragService, DragListenerParams} from "./dragService";
|
7 | import {ColumnController} from "../columnController/columnController";
|
8 | import {Environment} from "../environment";
|
9 | import {RowNode} from "../entities/rowNode";
|
10 |
|
11 | export enum DragSourceType { ToolPanel, HeaderCell, RowDrag }
|
12 |
|
13 | export interface DragItem {
|
14 |
|
15 | rowNode?: RowNode;
|
16 |
|
17 | columns?: Column[];
|
18 | visibleState?: {[key: string]: boolean};
|
19 | }
|
20 |
|
21 | export interface DragSource {
|
22 | |
23 |
|
24 | type: DragSourceType;
|
25 |
|
26 | eElement: HTMLElement;
|
27 |
|
28 | dragItemCallback: () => DragItem;
|
29 |
|
30 | dragItemName: string;
|
31 | |
32 |
|
33 | dragSourceDropTarget?: DropTarget;
|
34 |
|
35 | dragStartPixels?: number;
|
36 |
|
37 | dragStarted?: ()=>void;
|
38 |
|
39 | dragStopped?: ()=>void;
|
40 | }
|
41 |
|
42 | export interface DropTarget {
|
43 |
|
44 | getContainer(): HTMLElement;
|
45 | |
46 |
|
47 | getSecondaryContainers?(): HTMLElement[];
|
48 |
|
49 | getIconName?(): string;
|
50 |
|
51 | isInterestedIn(type: DragSourceType): boolean;
|
52 |
|
53 |
|
54 | onDragEnter?(params: DraggingEvent): void;
|
55 |
|
56 | onDragLeave?(params: DraggingEvent): void;
|
57 |
|
58 | onDragging?(params: DraggingEvent): void;
|
59 |
|
60 | onDragStop?(params: DraggingEvent): void;
|
61 | }
|
62 |
|
63 | export enum VDirection {Up, Down}
|
64 |
|
65 | export enum HDirection {Left, Right}
|
66 |
|
67 | export interface DraggingEvent {
|
68 | event: MouseEvent;
|
69 | x: number;
|
70 | y: number;
|
71 | vDirection: VDirection;
|
72 | hDirection: HDirection;
|
73 | dragSource: DragSource;
|
74 | dragItem: DragItem;
|
75 | fromNudge: boolean;
|
76 | }
|
77 |
|
78 | @Bean('dragAndDropService')
|
79 | export class DragAndDropService {
|
80 |
|
81 | @Autowired('gridOptionsWrapper') private gridOptionsWrapper: GridOptionsWrapper;
|
82 | @Autowired('dragService') private dragService: DragService;
|
83 | @Autowired('environment') private environment: Environment;
|
84 | @Autowired('columnController') private columnController: ColumnController;
|
85 |
|
86 | public static ICON_PINNED = 'pinned';
|
87 | public static ICON_ADD = 'add';
|
88 | public static ICON_MOVE = 'move';
|
89 | public static ICON_LEFT = 'left';
|
90 | public static ICON_RIGHT = 'right';
|
91 | public static ICON_GROUP = 'group';
|
92 | public static ICON_AGGREGATE = 'aggregate';
|
93 | public static ICON_PIVOT = 'pivot';
|
94 | public static ICON_NOT_ALLOWED = 'notAllowed';
|
95 |
|
96 | public static GHOST_TEMPLATE =
|
97 | '<div class="ag-dnd-ghost">' +
|
98 | ' <span class="ag-dnd-ghost-icon ag-shake-left-to-right"></span>' +
|
99 | ' <div class="ag-dnd-ghost-label">' +
|
100 | ' </div>' +
|
101 | '</div>';
|
102 |
|
103 | private logger: Logger;
|
104 |
|
105 | private dragSourceAndParamsList: {params: DragListenerParams, dragSource: DragSource}[] = [];
|
106 |
|
107 | private dragItem: DragItem;
|
108 | private eventLastTime: MouseEvent;
|
109 | private dragSource: DragSource;
|
110 | private dragging: boolean;
|
111 |
|
112 | private eGhost: HTMLElement;
|
113 | private eGhostParent: HTMLElement;
|
114 | private eGhostIcon: HTMLElement;
|
115 |
|
116 | private dropTargets: DropTarget[] = [];
|
117 | private lastDropTarget: DropTarget;
|
118 |
|
119 | private ePinnedIcon: HTMLElement;
|
120 | private ePlusIcon: HTMLElement;
|
121 | private eHiddenIcon: HTMLElement;
|
122 | private eMoveIcon: HTMLElement;
|
123 | private eLeftIcon: HTMLElement;
|
124 | private eRightIcon: HTMLElement;
|
125 | private eGroupIcon: HTMLElement;
|
126 | private eAggregateIcon: HTMLElement;
|
127 | private ePivotIcon: HTMLElement;
|
128 | private eDropNotAllowedIcon: HTMLElement;
|
129 |
|
130 | @PostConstruct
|
131 | private init(): void {
|
132 | this.ePinnedIcon = _.createIcon('columnMovePin', this.gridOptionsWrapper, null);
|
133 | this.ePlusIcon = _.createIcon('columnMoveAdd', this.gridOptionsWrapper, null);
|
134 | this.eHiddenIcon = _.createIcon('columnMoveHide', this.gridOptionsWrapper, null);
|
135 | this.eMoveIcon = _.createIcon('columnMoveMove', this.gridOptionsWrapper, null);
|
136 | this.eLeftIcon = _.createIcon('columnMoveLeft', this.gridOptionsWrapper, null);
|
137 | this.eRightIcon = _.createIcon('columnMoveRight', this.gridOptionsWrapper, null);
|
138 | this.eGroupIcon = _.createIcon('columnMoveGroup', this.gridOptionsWrapper, null);
|
139 | this.eAggregateIcon = _.createIcon('columnMoveValue', this.gridOptionsWrapper, null);
|
140 | this.ePivotIcon = _.createIcon('columnMovePivot', this.gridOptionsWrapper, null);
|
141 | this.eDropNotAllowedIcon = _.createIcon('dropNotAllowed', this.gridOptionsWrapper, null);
|
142 | }
|
143 |
|
144 | private setBeans(@Qualifier('loggerFactory') loggerFactory: LoggerFactory) {
|
145 | this.logger = loggerFactory.create('OldToolPanelDragAndDropService');
|
146 | }
|
147 |
|
148 | private getStringType(type: DragSourceType): string {
|
149 | switch (type) {
|
150 | case DragSourceType.RowDrag: return 'row';
|
151 | case DragSourceType.HeaderCell: return 'headerCell';
|
152 | case DragSourceType.ToolPanel: return 'toolPanel';
|
153 | default:
|
154 | console.warn(`ag-Grid: bug - unknown drag type ${type}`);
|
155 | return null;
|
156 | }
|
157 | }
|
158 |
|
159 | public addDragSource(dragSource: DragSource, allowTouch = false): void {
|
160 | let params: DragListenerParams = {
|
161 | eElement: dragSource.eElement,
|
162 | dragStartPixels: dragSource.dragStartPixels,
|
163 | onDragStart: this.onDragStart.bind(this, dragSource),
|
164 | onDragStop: this.onDragStop.bind(this),
|
165 | onDragging: this.onDragging.bind(this)
|
166 | };
|
167 |
|
168 | this.dragSourceAndParamsList.push({params: params, dragSource: dragSource});
|
169 |
|
170 | this.dragService.addDragSource(params, allowTouch);
|
171 | }
|
172 |
|
173 | public removeDragSource(dragSource: DragSource): void {
|
174 | let sourceAndParams = _.find(this.dragSourceAndParamsList, item => item.dragSource === dragSource);
|
175 | if (sourceAndParams) {
|
176 | this.dragService.removeDragSource(sourceAndParams.params);
|
177 | _.removeFromArray(this.dragSourceAndParamsList, sourceAndParams);
|
178 | }
|
179 | }
|
180 |
|
181 | @PreDestroy
|
182 | private destroy(): void {
|
183 | this.dragSourceAndParamsList.forEach( sourceAndParams => {
|
184 | this.dragService.removeDragSource(sourceAndParams.params);
|
185 | });
|
186 | this.dragSourceAndParamsList.length = 0;
|
187 | }
|
188 |
|
189 | public nudge(): void {
|
190 | if (this.dragging) {
|
191 | this.onDragging(this.eventLastTime, true);
|
192 | }
|
193 | }
|
194 |
|
195 | private onDragStart(dragSource: DragSource, mouseEvent: MouseEvent): void {
|
196 | this.dragging = true;
|
197 | this.dragSource = dragSource;
|
198 | this.eventLastTime = mouseEvent;
|
199 | this.dragItem = this.dragSource.dragItemCallback();
|
200 | this.lastDropTarget = this.dragSource.dragSourceDropTarget;
|
201 |
|
202 | if (this.dragSource.dragStarted) {
|
203 | this.dragSource.dragStarted();
|
204 | }
|
205 |
|
206 | this.createGhost();
|
207 | }
|
208 |
|
209 | private onDragStop(mouseEvent: MouseEvent): void {
|
210 | this.eventLastTime = null;
|
211 | this.dragging = false;
|
212 |
|
213 | if (this.dragSource.dragStopped) {
|
214 | this.dragSource.dragStopped();
|
215 | }
|
216 | if (this.lastDropTarget && this.lastDropTarget.onDragStop) {
|
217 | let draggingEvent = this.createDropTargetEvent(this.lastDropTarget, mouseEvent, null, null, false);
|
218 | this.lastDropTarget.onDragStop(draggingEvent);
|
219 | }
|
220 | this.lastDropTarget = null;
|
221 | this.dragItem = null;
|
222 | this.removeGhost();
|
223 | }
|
224 |
|
225 | private onDragging(mouseEvent: MouseEvent, fromNudge: boolean): void {
|
226 |
|
227 | let hDirection = this.workOutHDirection(mouseEvent);
|
228 | let vDirection = this.workOutVDirection(mouseEvent);
|
229 |
|
230 | this.eventLastTime = mouseEvent;
|
231 |
|
232 | this.positionGhost(mouseEvent);
|
233 |
|
234 |
|
235 | let dropTarget = _.find(this.dropTargets, this.isMouseOnDropTarget.bind(this, mouseEvent));
|
236 |
|
237 | if (dropTarget!==this.lastDropTarget) {
|
238 | this.leaveLastTargetIfExists(mouseEvent, hDirection, vDirection, fromNudge);
|
239 | this.enterDragTargetIfExists(dropTarget, mouseEvent, hDirection, vDirection, fromNudge);
|
240 | this.lastDropTarget = dropTarget;
|
241 | } else if (dropTarget) {
|
242 | let draggingEvent = this.createDropTargetEvent(dropTarget, mouseEvent, hDirection, vDirection, fromNudge);
|
243 | dropTarget.onDragging(draggingEvent);
|
244 | }
|
245 | }
|
246 |
|
247 | private enterDragTargetIfExists(dropTarget: DropTarget, mouseEvent: MouseEvent, hDirection: HDirection, vDirection: VDirection, fromNudge: boolean): void {
|
248 | if (!dropTarget) { return; }
|
249 |
|
250 | let dragEnterEvent = this.createDropTargetEvent(dropTarget, mouseEvent, hDirection, vDirection, fromNudge);
|
251 | dropTarget.onDragEnter(dragEnterEvent);
|
252 | this.setGhostIcon(dropTarget.getIconName ? dropTarget.getIconName() : null);
|
253 | }
|
254 |
|
255 | private leaveLastTargetIfExists(mouseEvent: MouseEvent, hDirection: HDirection, vDirection: VDirection, fromNudge: boolean): void {
|
256 | if (!this.lastDropTarget) { return; }
|
257 |
|
258 | let dragLeaveEvent = this.createDropTargetEvent(this.lastDropTarget, mouseEvent, hDirection, vDirection, fromNudge);
|
259 | this.lastDropTarget.onDragLeave(dragLeaveEvent);
|
260 | this.setGhostIcon(null);
|
261 | }
|
262 |
|
263 | private getAllContainersFromDropTarget(dropTarget: DropTarget): HTMLElement[] {
|
264 | let containers = [dropTarget.getContainer()];
|
265 | let secondaryContainers = dropTarget.getSecondaryContainers ? dropTarget.getSecondaryContainers() : null;
|
266 | if (secondaryContainers) {
|
267 | containers = containers.concat(secondaryContainers);
|
268 | }
|
269 | return containers;
|
270 | }
|
271 |
|
272 |
|
273 | private isMouseOnDropTarget(mouseEvent: MouseEvent, dropTarget: DropTarget): boolean {
|
274 | let allContainers = this.getAllContainersFromDropTarget(dropTarget);
|
275 |
|
276 | let mouseOverTarget: boolean = false;
|
277 | allContainers.forEach( (eContainer: HTMLElement) => {
|
278 | if (!eContainer) { return; }
|
279 | let rect = eContainer.getBoundingClientRect();
|
280 |
|
281 |
|
282 | if (rect.width===0 || rect.height===0) {
|
283 | return;
|
284 | }
|
285 | let horizontalFit = mouseEvent.clientX >= rect.left && mouseEvent.clientX <= rect.right;
|
286 | let verticalFit = mouseEvent.clientY >= rect.top && mouseEvent.clientY <= rect.bottom;
|
287 |
|
288 |
|
289 |
|
290 | if (horizontalFit && verticalFit) {
|
291 | mouseOverTarget = true;
|
292 | }
|
293 | });
|
294 |
|
295 | if (mouseOverTarget) {
|
296 | let mouseOverTargetAndInterested = dropTarget.isInterestedIn(this.dragSource.type);
|
297 | return mouseOverTargetAndInterested;
|
298 | } else {
|
299 | return false;
|
300 | }
|
301 | }
|
302 |
|
303 | public addDropTarget(dropTarget: DropTarget) {
|
304 | this.dropTargets.push(dropTarget);
|
305 | }
|
306 |
|
307 | public workOutHDirection(event: MouseEvent): HDirection {
|
308 | if (this.eventLastTime.clientX > event.clientX) {
|
309 | return HDirection.Left;
|
310 | } else if (this.eventLastTime.clientX < event.clientX) {
|
311 | return HDirection.Right;
|
312 | } else {
|
313 | return null;
|
314 | }
|
315 | }
|
316 |
|
317 | public workOutVDirection(event: MouseEvent): VDirection {
|
318 | if (this.eventLastTime.clientY > event.clientY) {
|
319 | return VDirection.Up;
|
320 | } else if (this.eventLastTime.clientY < event.clientY) {
|
321 | return VDirection.Down;
|
322 | } else {
|
323 | return null;
|
324 | }
|
325 | }
|
326 |
|
327 | public createDropTargetEvent(dropTarget: DropTarget, event: MouseEvent, hDirection: HDirection, vDirection: VDirection, fromNudge: boolean): DraggingEvent {
|
328 |
|
329 |
|
330 | let rect = dropTarget.getContainer().getBoundingClientRect();
|
331 | let x = event.clientX - rect.left;
|
332 | let y = event.clientY - rect.top;
|
333 |
|
334 | let dropTargetEvent: DraggingEvent = {
|
335 | event: event,
|
336 | x: x,
|
337 | y: y,
|
338 | vDirection: vDirection,
|
339 | hDirection: hDirection,
|
340 | dragSource: this.dragSource,
|
341 | fromNudge: fromNudge,
|
342 | dragItem: this.dragItem
|
343 | };
|
344 |
|
345 | return dropTargetEvent;
|
346 | }
|
347 |
|
348 | private positionGhost(event: MouseEvent): void {
|
349 | let ghostRect = this.eGhost.getBoundingClientRect();
|
350 | let ghostHeight = ghostRect.height;
|
351 |
|
352 |
|
353 |
|
354 | let browserWidth = _.getBodyWidth() - 2;
|
355 | let browserHeight = _.getBodyHeight() - 2;
|
356 |
|
357 |
|
358 | let top = event.pageY - (ghostHeight / 2);
|
359 |
|
360 | let left = event.pageX - 30;
|
361 |
|
362 | let usrDocument = this.gridOptionsWrapper.getDocument();
|
363 |
|
364 | let windowScrollY = window.pageYOffset || usrDocument.documentElement.scrollTop;
|
365 | let windowScrollX = window.pageXOffset || usrDocument.documentElement.scrollLeft;
|
366 |
|
367 |
|
368 | if (browserWidth>0) {
|
369 | if ( (left + this.eGhost.clientWidth) > (browserWidth + windowScrollX) ) {
|
370 | left = browserWidth + windowScrollX - this.eGhost.clientWidth;
|
371 | }
|
372 | }
|
373 | if (left < 0) {
|
374 | left = 0;
|
375 | }
|
376 | if (browserHeight>0) {
|
377 | if ( (top + this.eGhost.clientHeight) > (browserHeight + windowScrollY) ) {
|
378 | top = browserHeight + windowScrollY - this.eGhost.clientHeight;
|
379 | }
|
380 | }
|
381 | if (top < 0) {
|
382 | top = 0;
|
383 | }
|
384 |
|
385 | this.eGhost.style.left = left + 'px';
|
386 | this.eGhost.style.top = top + 'px';
|
387 | }
|
388 |
|
389 | private removeGhost(): void {
|
390 | if (this.eGhost && this.eGhostParent) {
|
391 | this.eGhostParent.removeChild(this.eGhost);
|
392 | }
|
393 | this.eGhost = null;
|
394 | }
|
395 |
|
396 | private createGhost(): void {
|
397 | this.eGhost = _.loadTemplate(DragAndDropService.GHOST_TEMPLATE);
|
398 | _.addCssClass(this.eGhost, this.environment.getTheme());
|
399 | this.eGhostIcon = <HTMLElement> this.eGhost.querySelector('.ag-dnd-ghost-icon');
|
400 |
|
401 | this.setGhostIcon(null);
|
402 |
|
403 | let eText = <HTMLElement> this.eGhost.querySelector('.ag-dnd-ghost-label');
|
404 | eText.innerHTML = this.dragSource.dragItemName;
|
405 |
|
406 | this.eGhost.style.height = '25px';
|
407 |
|
408 | this.eGhost.style.top = '20px';
|
409 | this.eGhost.style.left = '20px';
|
410 |
|
411 | let usrDocument = this.gridOptionsWrapper.getDocument();
|
412 | this.eGhostParent = <HTMLElement> usrDocument.querySelector('body');
|
413 | if (!this.eGhostParent) {
|
414 | console.warn('ag-Grid: could not find document body, it is needed for dragging columns');
|
415 | } else {
|
416 | this.eGhostParent.appendChild(this.eGhost);
|
417 | }
|
418 |
|
419 | }
|
420 |
|
421 | public setGhostIcon(iconName: string, shake = false): void {
|
422 | _.removeAllChildren(this.eGhostIcon);
|
423 | let eIcon: HTMLElement;
|
424 | switch (iconName) {
|
425 | case DragAndDropService.ICON_ADD: eIcon = this.ePlusIcon; break;
|
426 | case DragAndDropService.ICON_PINNED: eIcon = this.ePinnedIcon; break;
|
427 | case DragAndDropService.ICON_MOVE: eIcon = this.eMoveIcon; break;
|
428 | case DragAndDropService.ICON_LEFT: eIcon = this.eLeftIcon; break;
|
429 | case DragAndDropService.ICON_RIGHT: eIcon = this.eRightIcon; break;
|
430 | case DragAndDropService.ICON_GROUP: eIcon = this.eGroupIcon; break;
|
431 | case DragAndDropService.ICON_AGGREGATE: eIcon = this.eAggregateIcon; break;
|
432 | case DragAndDropService.ICON_PIVOT: eIcon = this.ePivotIcon; break;
|
433 | case DragAndDropService.ICON_NOT_ALLOWED: eIcon = this.eDropNotAllowedIcon; break;
|
434 | default: eIcon = this.eHiddenIcon; break;
|
435 | }
|
436 | this.eGhostIcon.appendChild(eIcon);
|
437 | _.addOrRemoveCssClass(this.eGhostIcon, 'ag-shake-left-to-right', shake);
|
438 | }
|
439 |
|
440 | }
|