1 node_module = require './node'
2 util = require './util'
4 Position = node_module.Position
9 class DragAndDropHandler
10 constructor: (tree_widget) ->
11 @tree_widget = tree_widget
19 mouseCapture: (position_info) ->
20 $element = $(position_info.target)
22 if not @mustCaptureElement($element)
25 if @tree_widget.options.onIsMoveHandle and not @tree_widget.options.onIsMoveHandle($element)
28 node_element = @tree_widget._getNodeElement($element)
30 if node_element and @tree_widget.options.onCanMove
31 if not @tree_widget.options.onCanMove(node_element.node)
34 @current_item = node_element
35 return (@current_item != null)
37 mouseStart: (position_info) ->
40 offset = $(position_info.target).offset()
42 node = @current_item.node
44 if @tree_widget.options.autoEscape
45 node_name = util.html_escape(node.name)
49 @drag_element = new DragElement(
51 position_info.page_x - offset.left,
52 position_info.page_y - offset.top,
57 @current_item.$element.addClass('jqtree-moving')
60 mouseDrag: (position_info) ->
61 @drag_element.move(position_info.page_x, position_info.page_y)
63 area = @findHoveredArea(position_info.page_x, position_info.page_y)
64 can_move_to = @canMoveToArea(area)
66 if can_move_to and area
67 if !area.node.isFolder()
68 @stopOpenFolderTimer()
70 if @hovered_area != area
73 # If this is a closed folder, start timer to open it
74 if @mustOpenFolderTimer(area)
75 @startOpenFolderTimer(area.node)
77 @stopOpenFolderTimer()
83 @stopOpenFolderTimer()
86 if @tree_widget.options.onDragMove?
87 @tree_widget.options.onDragMove(@current_item.node, position_info.original_event)
91 mustCaptureElement: ($element) ->
92 return not $element.is('input,select,textarea')
94 canMoveToArea: (area) ->
97 else if @tree_widget.options.onCanMoveTo
98 position_name = Position.getName(area.position)
100 return @tree_widget.options.onCanMoveTo(@current_item.node, area.node, position_name)
104 mouseStop: (position_info) ->
105 @moveItem(position_info)
111 current_item = @current_item
114 @current_item.$element.removeClass('jqtree-moving')
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)
131 @current_item = @tree_widget._getNodeElementForNode(@current_item.node)
134 @current_item.$element.addClass('jqtree-moving')
140 @drag_element.remove()
145 @previous_ghost.remove()
151 hit_areas_generator = new HitAreasGenerator(
154 @getTreeDimensions().bottom
156 @hit_areas = hit_areas_generator.generate()
158 findHoveredArea: (x, y) ->
159 dimensions = @getTreeDimensions()
162 x < dimensions.left or
163 y < dimensions.top or
164 x > dimensions.right or
165 y > dimensions.bottom
170 high = @hit_areas.length
172 mid = (low + high) >> 1
173 area = @hit_areas[mid]
177 else if y > area.bottom
184 mustOpenFolderTimer: (area) ->
190 area.position == Position.INSIDE
197 # remove previous drop hint
201 node_element = @tree_widget._getNodeElementForNode(@hovered_area.node)
202 @previous_ghost = node_element.addDropHint(@hovered_area.position)
204 startOpenFolderTimer: (folder) ->
206 @tree_widget._openNode(
208 @tree_widget.options.slide,
214 @stopOpenFolderTimer()
216 @open_folder_timer = setTimeout(openFolder, @tree_widget.options.openFolderDelay)
218 stopOpenFolderTimer: ->
219 if @open_folder_timer
220 clearTimeout(@open_folder_timer)
221 @open_folder_timer = null
223 moveItem: (position_info) ->
226 @hovered_area.position != Position.NONE and
227 @canMoveToArea(@hovered_area)
229 moved_node = @current_item.node
230 target_node = @hovered_area.node
231 position = @hovered_area.position
232 previous_parent = moved_node.parent
234 if position == Position.INSIDE
235 @hovered_area.node.is_open = true
238 @tree_widget.tree.moveNode(moved_node, target_node, position)
239 @tree_widget.element.empty()
240 @tree_widget._refreshElements()
242 event = @tree_widget._triggerEvent(
245 moved_node: moved_node
246 target_node: target_node
247 position: Position.getName(position)
248 previous_parent: previous_parent
250 original_event: position_info.original_event
253 doMove() unless event.isDefaultPrevented()
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()
263 right: offset.left + @tree_widget.element.width(),
264 bottom: offset.top + @tree_widget.element.height() + 16
268 class VisibleNodeIterator
269 constructor: (tree) ->
275 _iterateNode = (node, next_node) =>
276 must_iterate_inside = (
277 (node.is_open or not node.element) and node.hasChildren()
281 $element = $(node.element)
283 if not $element.is(':visible')
287 @handleFirstNode(node, $element)
288 is_first_node = false
290 if not node.hasChildren()
291 @handleNode(node, next_node, $element)
293 if not @handleOpenFolder(node, $element)
294 must_iterate_inside = false
296 @handleClosedFolder(node, next_node, $element)
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)
304 _iterateNode(node.children[i], node.children[i+1])
307 @handleAfterOpenFolder(node, next_node, $element)
309 _iterateNode(@tree, null)
311 handleNode: (node, next_node, $element) ->
314 handleOpenFolder: (node, $element) ->
317 # - true: continue iterating
318 # - false: stop iterating
320 handleClosedFolder: (node, next_node, $element) ->
323 handleAfterOpenFolder: (node, next_node, $element) ->
326 handleFirstNode: (node, $element) ->
330 class HitAreasGenerator extends VisibleNodeIterator
331 constructor: (tree, current_node, tree_bottom) ->
334 @current_node = current_node
335 @tree_bottom = tree_bottom
343 return @generateHitAreas(@positions)
345 getTop: ($element) ->
346 return $element.offset().top
348 addPosition: (node, position, top) ->
355 @positions.push(area)
358 handleNode: (node, next_node, $element) ->
359 top = @getTop($element)
361 if node == @current_node
362 # Cannot move inside current item
363 @addPosition(node, Position.NONE, top)
365 @addPosition(node, Position.INSIDE, top)
368 next_node == @current_node or
369 node == @current_node
371 # Cannot move before or after current item
372 @addPosition(node, Position.NONE, top)
374 @addPosition(node, Position.AFTER, top)
376 handleOpenFolder: (node, $element) ->
377 if node == @current_node
378 # Cannot move inside current item
382 # Cannot move before current item
383 if node.children[0] != @current_node
384 @addPosition(node, Position.INSIDE, @getTop($element))
389 handleClosedFolder: (node, next_node, $element) ->
390 top = @getTop($element)
392 if node == @current_node
393 # Cannot move after current item
394 @addPosition(node, Position.NONE, top)
396 @addPosition(node, Position.INSIDE, top)
398 # Cannot move before current item
399 if next_node != @current_node
400 @addPosition(node, Position.AFTER, top)
402 handleFirstNode: (node, $element) ->
403 if node != @current_node
404 @addPosition(node, Position.BEFORE, @getTop($(node.element)))
406 handleAfterOpenFolder: (node, next_node, $element) ->
408 node == @current_node.node or
409 next_node == @current_node.node
411 # Cannot move before or after current item
412 @addPosition(node, Position.NONE, @last_top)
414 @addPosition(node, Position.AFTER, @last_top)
416 generateHitAreas: (positions) ->
421 for position in positions
422 if position.top != previous_top and group.length
424 @generateHitAreasForGroup(
431 previous_top = position.top
436 @generateHitAreasForGroup(
445 generateHitAreasForGroup: (hit_areas, positions_in_group, top, bottom) ->
446 # limit positions in group
447 position_count = Math.min(positions_in_group.length, 4)
449 area_height = Math.round((bottom - top) / position_count)
453 while (i < position_count)
454 position = positions_in_group[i]
458 bottom: area_top + area_height,
460 position: position.position
463 area_top += area_height
470 constructor: (node_name, offset_x, offset_y, $tree) ->
474 @$element = $("<span class=\"jqtree-title jqtree-dragging\">#{ node_name }</span>")
475 @$element.css("position", "absolute")
476 $tree.append(@$element)
478 move: (page_x, page_y) ->
480 left: page_x - @offset_x,
481 top: page_y - @offset_y
489 DragAndDropHandler: DragAndDropHandler
490 DragElement: DragElement
491 HitAreasGenerator: HitAreasGenerator