nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / jqTree / src / drag_and_drop_handler.coffee
1 node_module = require './node'
2 util = require './util'
3
4 Position = node_module.Position
5
6 $ = jQuery
7
8
9 class DragAndDropHandler
10     constructor: (tree_widget) ->
11         @tree_widget = tree_widget
12
13         @hovered_area = null
14         @$ghost = null
15         @hit_areas = []
16         @is_dragging = false
17         @current_item = null
18
19     mouseCapture: (position_info) ->
20         $element = $(position_info.target)
21
22         if not @mustCaptureElement($element)
23             return null
24
25         if @tree_widget.options.onIsMoveHandle and not @tree_widget.options.onIsMoveHandle($element)
26             return null
27
28         node_element = @tree_widget._getNodeElement($element)
29
30         if node_element and @tree_widget.options.onCanMove
31             if not @tree_widget.options.onCanMove(node_element.node)
32                 node_element = null
33
34         @current_item = node_element
35         return (@current_item != null)
36
37     mouseStart: (position_info) ->
38         @refresh()
39
40         offset = $(position_info.target).offset()
41
42         node = @current_item.node
43
44         if @tree_widget.options.autoEscape
45             node_name = util.html_escape(node.name)
46         else
47             node_name = node.name
48
49         @drag_element = new DragElement(
50             node_name,
51             position_info.page_x - offset.left,
52             position_info.page_y - offset.top,
53             @tree_widget.element
54         )
55
56         @is_dragging = true
57         @current_item.$element.addClass('jqtree-moving')
58         return true
59
60     mouseDrag: (position_info) ->
61         @drag_element.move(position_info.page_x, position_info.page_y)
62
63         area = @findHoveredArea(position_info.page_x, position_info.page_y)
64         can_move_to = @canMoveToArea(area)
65
66         if can_move_to and area
67             if !area.node.isFolder()
68                 @stopOpenFolderTimer()
69
70             if @hovered_area != area
71                 @hovered_area = area
72
73                 # If this is a closed folder, start timer to open it
74                 if @mustOpenFolderTimer(area)
75                     @startOpenFolderTimer(area.node)
76                 else
77                     @stopOpenFolderTimer()
78
79                 @updateDropHint()
80         else
81             @removeHover()
82             @removeDropHint()
83             @stopOpenFolderTimer()
84
85         if not area
86             if @tree_widget.options.onDragMove?
87                 @tree_widget.options.onDragMove(@current_item.node, position_info.original_event)
88
89         return true
90
91     mustCaptureElement: ($element) ->
92         return not $element.is('input,select,textarea')
93
94     canMoveToArea: (area) ->
95         if not area
96             return false
97         else if @tree_widget.options.onCanMoveTo
98             position_name = Position.getName(area.position)
99
100             return @tree_widget.options.onCanMoveTo(@current_item.node, area.node, position_name)
101         else
102             return true
103
104     mouseStop: (position_info) ->
105         @moveItem(position_info)
106         @clear()
107         @removeHover()
108         @removeDropHint()
109         @removeHitAreas()
110
111         current_item = @current_item
112
113         if @current_item
114             @current_item.$element.removeClass('jqtree-moving')
115             @current_item = null
116
117         @is_dragging = false
118
119         if not @hovered_area and current_item
120             if @tree_widget.options.onDragStop?
121                 @tree_widget.options.onDragStop(current_item.node, position_info.original_event)
122
123         return false
124
125     refresh: ->
126         @removeHitAreas()
127
128         if @current_item
129             @generateHitAreas()
130
131             @current_item = @tree_widget._getNodeElementForNode(@current_item.node)
132
133             if @is_dragging
134                 @current_item.$element.addClass('jqtree-moving')
135
136     removeHitAreas: ->
137         @hit_areas = []
138
139     clear: ->
140         @drag_element.remove()
141         @drag_element = null
142
143     removeDropHint: ->
144         if @previous_ghost
145             @previous_ghost.remove()
146
147     removeHover: ->
148         @hovered_area = null
149
150     generateHitAreas: ->
151         hit_areas_generator = new HitAreasGenerator(
152             @tree_widget.tree,
153             @current_item.node,
154             @getTreeDimensions().bottom
155         )
156         @hit_areas = hit_areas_generator.generate()
157
158     findHoveredArea: (x, y) ->
159         dimensions = @getTreeDimensions()
160
161         if (
162             x < dimensions.left or
163             y < dimensions.top or
164             x > dimensions.right or
165             y > dimensions.bottom
166         )
167             return null
168
169         low = 0
170         high = @hit_areas.length
171         while (low < high)
172             mid = (low + high) >> 1
173             area = @hit_areas[mid]
174
175             if y < area.top
176                 high = mid
177             else if y > area.bottom
178                 low = mid + 1
179             else
180                 return area
181
182         return null
183
184     mustOpenFolderTimer: (area) ->
185         node = area.node
186
187         return (
188             node.isFolder() and
189             not node.is_open and
190             area.position == Position.INSIDE
191         )
192
193     updateDropHint: ->
194         if not @hovered_area
195             return
196
197         # remove previous drop hint
198         @removeDropHint()
199
200         # add new drop hint
201         node_element = @tree_widget._getNodeElementForNode(@hovered_area.node)
202         @previous_ghost = node_element.addDropHint(@hovered_area.position)
203
204     startOpenFolderTimer: (folder) ->
205         openFolder = =>
206             @tree_widget._openNode(
207                 folder,
208                 @tree_widget.options.slide,
209                 =>
210                     @refresh()
211                     @updateDropHint()
212             )
213
214         @stopOpenFolderTimer()
215
216         @open_folder_timer = setTimeout(openFolder, @tree_widget.options.openFolderDelay)
217
218     stopOpenFolderTimer: ->
219         if @open_folder_timer
220             clearTimeout(@open_folder_timer)
221             @open_folder_timer = null
222
223     moveItem: (position_info) ->
224         if (
225             @hovered_area and
226             @hovered_area.position != Position.NONE and
227             @canMoveToArea(@hovered_area)
228         )
229             moved_node = @current_item.node
230             target_node = @hovered_area.node
231             position = @hovered_area.position
232             previous_parent = moved_node.parent
233
234             if position == Position.INSIDE
235                 @hovered_area.node.is_open = true
236
237             doMove = =>
238                 @tree_widget.tree.moveNode(moved_node, target_node, position)
239                 @tree_widget.element.empty()
240                 @tree_widget._refreshElements()
241
242             event = @tree_widget._triggerEvent(
243                 'tree.move',
244                 move_info:
245                     moved_node: moved_node
246                     target_node: target_node
247                     position: Position.getName(position)
248                     previous_parent: previous_parent
249                     do_move: doMove
250                     original_event: position_info.original_event
251             )
252
253             doMove() unless event.isDefaultPrevented()
254
255     getTreeDimensions: ->
256         # Return the dimensions of the tree. Add a margin to the bottom to allow
257         # for some to drag-and-drop the last element.
258         offset = @tree_widget.element.offset()
259
260         return {
261             left: offset.left,
262             top: offset.top,
263             right: offset.left + @tree_widget.element.width(),
264             bottom: offset.top + @tree_widget.element.height() + 16
265         }
266
267
268 class VisibleNodeIterator
269     constructor: (tree) ->
270         @tree = tree
271
272     iterate: ->
273         is_first_node = true
274
275         _iterateNode = (node, next_node) =>
276             must_iterate_inside = (
277                 (node.is_open or not node.element) and node.hasChildren()
278             )
279
280             if node.element
281                 $element = $(node.element)
282
283                 if not $element.is(':visible')
284                     return
285
286                 if is_first_node
287                     @handleFirstNode(node, $element)
288                     is_first_node = false
289
290                 if not node.hasChildren()
291                     @handleNode(node, next_node, $element)
292                 else if node.is_open
293                     if not @handleOpenFolder(node, $element)
294                         must_iterate_inside = false
295                 else
296                     @handleClosedFolder(node, next_node, $element)
297
298             if must_iterate_inside
299                 children_length = node.children.length
300                 for child, i in node.children
301                     if i == (children_length - 1)
302                         _iterateNode(node.children[i], null)
303                     else
304                         _iterateNode(node.children[i], node.children[i+1])
305
306                 if node.is_open
307                     @handleAfterOpenFolder(node, next_node, $element)
308
309         _iterateNode(@tree, null)
310
311     handleNode: (node, next_node, $element) ->
312         # override
313
314     handleOpenFolder: (node, $element) ->
315         # override
316         # return
317         #   - true: continue iterating
318         #   - false: stop iterating
319
320     handleClosedFolder: (node, next_node, $element) ->
321         # override
322
323     handleAfterOpenFolder: (node, next_node, $element) ->
324         # override
325
326     handleFirstNode: (node, $element) ->
327         # override
328
329
330 class HitAreasGenerator extends VisibleNodeIterator
331     constructor: (tree, current_node, tree_bottom) ->
332         super(tree)
333
334         @current_node = current_node
335         @tree_bottom = tree_bottom
336
337     generate: ->
338         @positions = []
339         @last_top = 0
340
341         @iterate()
342
343         return @generateHitAreas(@positions)
344
345     getTop: ($element) ->
346         return $element.offset().top
347
348     addPosition: (node, position, top) ->
349         area = {
350             top: top
351             node: node
352             position: position
353         }
354
355         @positions.push(area)
356         @last_top = top
357
358     handleNode: (node, next_node, $element) ->
359         top = @getTop($element)
360
361         if node == @current_node
362             # Cannot move inside current item
363             @addPosition(node, Position.NONE, top)
364         else
365             @addPosition(node, Position.INSIDE, top)
366
367         if (
368             next_node == @current_node or
369             node == @current_node
370         )
371             # Cannot move before or after current item
372             @addPosition(node, Position.NONE, top)
373         else
374             @addPosition(node, Position.AFTER, top)
375
376     handleOpenFolder: (node, $element) ->
377         if node == @current_node
378             # Cannot move inside current item
379             # Stop iterating
380             return false
381
382         # Cannot move before current item
383         if node.children[0] != @current_node
384             @addPosition(node, Position.INSIDE, @getTop($element))
385
386         # Continue iterating
387         return true
388
389     handleClosedFolder: (node, next_node, $element) ->
390         top = @getTop($element)
391
392         if node == @current_node
393             # Cannot move after current item
394             @addPosition(node, Position.NONE, top)
395         else
396             @addPosition(node, Position.INSIDE, top)
397
398             # Cannot move before current item
399             if next_node != @current_node
400                 @addPosition(node, Position.AFTER, top)
401
402     handleFirstNode: (node, $element) ->
403         if node != @current_node
404             @addPosition(node, Position.BEFORE, @getTop($(node.element)))
405
406     handleAfterOpenFolder: (node, next_node, $element) ->
407         if (
408             node == @current_node.node or
409             next_node == @current_node.node
410         )
411             # Cannot move before or after current item
412             @addPosition(node, Position.NONE, @last_top)
413         else
414             @addPosition(node, Position.AFTER, @last_top)
415
416     generateHitAreas: (positions) ->
417         previous_top = -1
418         group = []
419         hit_areas = []
420
421         for position in positions
422             if position.top != previous_top and group.length
423                 if group.length
424                     @generateHitAreasForGroup(
425                         hit_areas,
426                         group,
427                         previous_top,
428                         position.top
429                     )
430
431                 previous_top = position.top
432                 group = []
433
434             group.push(position)
435
436         @generateHitAreasForGroup(
437             hit_areas,
438             group,
439             previous_top,
440             @tree_bottom
441         )
442
443         return hit_areas
444
445     generateHitAreasForGroup: (hit_areas, positions_in_group, top, bottom) ->
446         # limit positions in group
447         position_count = Math.min(positions_in_group.length, 4)
448
449         area_height = Math.round((bottom - top) / position_count)
450         area_top = top
451
452         i = 0
453         while (i < position_count)
454             position = positions_in_group[i]
455
456             hit_areas.push(
457                 top: area_top,
458                 bottom: area_top + area_height,
459                 node: position.node,
460                 position: position.position
461             )
462
463             area_top += area_height
464             i += 1
465
466         return null
467
468
469 class DragElement
470     constructor: (node_name, offset_x, offset_y, $tree) ->
471         @offset_x = offset_x
472         @offset_y = offset_y
473
474         @$element = $("<span class=\"jqtree-title jqtree-dragging\">#{ node_name }</span>")
475         @$element.css("position", "absolute")
476         $tree.append(@$element)
477
478     move: (page_x, page_y) ->
479         @$element.offset(
480             left: page_x - @offset_x,
481             top: page_y - @offset_y
482         )
483
484     remove: ->
485         @$element.remove()
486
487
488 module.exports =
489     DragAndDropHandler: DragAndDropHandler
490     DragElement: DragElement
491     HitAreasGenerator: HitAreasGenerator