Initial OpenECOMP Portal commit
[portal.git] / ecomp-portal-FE / client / bower_components / jqTree / src / drag_and_drop_handler.coffee
diff --git a/ecomp-portal-FE/client/bower_components/jqTree/src/drag_and_drop_handler.coffee b/ecomp-portal-FE/client/bower_components/jqTree/src/drag_and_drop_handler.coffee
new file mode 100644 (file)
index 0000000..38b1d52
--- /dev/null
@@ -0,0 +1,491 @@
+node_module = require './node'
+util = require './util'
+
+Position = node_module.Position
+
+$ = jQuery
+
+
+class DragAndDropHandler
+    constructor: (tree_widget) ->
+        @tree_widget = tree_widget
+
+        @hovered_area = null
+        @$ghost = null
+        @hit_areas = []
+        @is_dragging = false
+        @current_item = null
+
+    mouseCapture: (position_info) ->
+        $element = $(position_info.target)
+
+        if not @mustCaptureElement($element)
+            return null
+
+        if @tree_widget.options.onIsMoveHandle and not @tree_widget.options.onIsMoveHandle($element)
+            return null
+
+        node_element = @tree_widget._getNodeElement($element)
+
+        if node_element and @tree_widget.options.onCanMove
+            if not @tree_widget.options.onCanMove(node_element.node)
+                node_element = null
+
+        @current_item = node_element
+        return (@current_item != null)
+
+    mouseStart: (position_info) ->
+        @refresh()
+
+        offset = $(position_info.target).offset()
+
+        node = @current_item.node
+
+        if @tree_widget.options.autoEscape
+            node_name = util.html_escape(node.name)
+        else
+            node_name = node.name
+
+        @drag_element = new DragElement(
+            node_name,
+            position_info.page_x - offset.left,
+            position_info.page_y - offset.top,
+            @tree_widget.element
+        )
+
+        @is_dragging = true
+        @current_item.$element.addClass('jqtree-moving')
+        return true
+
+    mouseDrag: (position_info) ->
+        @drag_element.move(position_info.page_x, position_info.page_y)
+
+        area = @findHoveredArea(position_info.page_x, position_info.page_y)
+        can_move_to = @canMoveToArea(area)
+
+        if can_move_to and area
+            if !area.node.isFolder()
+                @stopOpenFolderTimer()
+
+            if @hovered_area != area
+                @hovered_area = area
+
+                # If this is a closed folder, start timer to open it
+                if @mustOpenFolderTimer(area)
+                    @startOpenFolderTimer(area.node)
+                else
+                    @stopOpenFolderTimer()
+
+                @updateDropHint()
+        else
+            @removeHover()
+            @removeDropHint()
+            @stopOpenFolderTimer()
+
+        if not area
+            if @tree_widget.options.onDragMove?
+                @tree_widget.options.onDragMove(@current_item.node, position_info.original_event)
+
+        return true
+
+    mustCaptureElement: ($element) ->
+        return not $element.is('input,select,textarea')
+
+    canMoveToArea: (area) ->
+        if not area
+            return false
+        else if @tree_widget.options.onCanMoveTo
+            position_name = Position.getName(area.position)
+
+            return @tree_widget.options.onCanMoveTo(@current_item.node, area.node, position_name)
+        else
+            return true
+
+    mouseStop: (position_info) ->
+        @moveItem(position_info)
+        @clear()
+        @removeHover()
+        @removeDropHint()
+        @removeHitAreas()
+
+        current_item = @current_item
+
+        if @current_item
+            @current_item.$element.removeClass('jqtree-moving')
+            @current_item = null
+
+        @is_dragging = false
+
+        if not @hovered_area and current_item
+            if @tree_widget.options.onDragStop?
+                @tree_widget.options.onDragStop(current_item.node, position_info.original_event)
+
+        return false
+
+    refresh: ->
+        @removeHitAreas()
+
+        if @current_item
+            @generateHitAreas()
+
+            @current_item = @tree_widget._getNodeElementForNode(@current_item.node)
+
+            if @is_dragging
+                @current_item.$element.addClass('jqtree-moving')
+
+    removeHitAreas: ->
+        @hit_areas = []
+
+    clear: ->
+        @drag_element.remove()
+        @drag_element = null
+
+    removeDropHint: ->
+        if @previous_ghost
+            @previous_ghost.remove()
+
+    removeHover: ->
+        @hovered_area = null
+
+    generateHitAreas: ->
+        hit_areas_generator = new HitAreasGenerator(
+            @tree_widget.tree,
+            @current_item.node,
+            @getTreeDimensions().bottom
+        )
+        @hit_areas = hit_areas_generator.generate()
+
+    findHoveredArea: (x, y) ->
+        dimensions = @getTreeDimensions()
+
+        if (
+            x < dimensions.left or
+            y < dimensions.top or
+            x > dimensions.right or
+            y > dimensions.bottom
+        )
+            return null
+
+        low = 0
+        high = @hit_areas.length
+        while (low < high)
+            mid = (low + high) >> 1
+            area = @hit_areas[mid]
+
+            if y < area.top
+                high = mid
+            else if y > area.bottom
+                low = mid + 1
+            else
+                return area
+
+        return null
+
+    mustOpenFolderTimer: (area) ->
+        node = area.node
+
+        return (
+            node.isFolder() and
+            not node.is_open and
+            area.position == Position.INSIDE
+        )
+
+    updateDropHint: ->
+        if not @hovered_area
+            return
+
+        # remove previous drop hint
+        @removeDropHint()
+
+        # add new drop hint
+        node_element = @tree_widget._getNodeElementForNode(@hovered_area.node)
+        @previous_ghost = node_element.addDropHint(@hovered_area.position)
+
+    startOpenFolderTimer: (folder) ->
+        openFolder = =>
+            @tree_widget._openNode(
+                folder,
+                @tree_widget.options.slide,
+                =>
+                    @refresh()
+                    @updateDropHint()
+            )
+
+        @stopOpenFolderTimer()
+
+        @open_folder_timer = setTimeout(openFolder, @tree_widget.options.openFolderDelay)
+
+    stopOpenFolderTimer: ->
+        if @open_folder_timer
+            clearTimeout(@open_folder_timer)
+            @open_folder_timer = null
+
+    moveItem: (position_info) ->
+        if (
+            @hovered_area and
+            @hovered_area.position != Position.NONE and
+            @canMoveToArea(@hovered_area)
+        )
+            moved_node = @current_item.node
+            target_node = @hovered_area.node
+            position = @hovered_area.position
+            previous_parent = moved_node.parent
+
+            if position == Position.INSIDE
+                @hovered_area.node.is_open = true
+
+            doMove = =>
+                @tree_widget.tree.moveNode(moved_node, target_node, position)
+                @tree_widget.element.empty()
+                @tree_widget._refreshElements()
+
+            event = @tree_widget._triggerEvent(
+                'tree.move',
+                move_info:
+                    moved_node: moved_node
+                    target_node: target_node
+                    position: Position.getName(position)
+                    previous_parent: previous_parent
+                    do_move: doMove
+                    original_event: position_info.original_event
+            )
+
+            doMove() unless event.isDefaultPrevented()
+
+    getTreeDimensions: ->
+        # Return the dimensions of the tree. Add a margin to the bottom to allow
+        # for some to drag-and-drop the last element.
+        offset = @tree_widget.element.offset()
+
+        return {
+            left: offset.left,
+            top: offset.top,
+            right: offset.left + @tree_widget.element.width(),
+            bottom: offset.top + @tree_widget.element.height() + 16
+        }
+
+
+class VisibleNodeIterator
+    constructor: (tree) ->
+        @tree = tree
+
+    iterate: ->
+        is_first_node = true
+
+        _iterateNode = (node, next_node) =>
+            must_iterate_inside = (
+                (node.is_open or not node.element) and node.hasChildren()
+            )
+
+            if node.element
+                $element = $(node.element)
+
+                if not $element.is(':visible')
+                    return
+
+                if is_first_node
+                    @handleFirstNode(node, $element)
+                    is_first_node = false
+
+                if not node.hasChildren()
+                    @handleNode(node, next_node, $element)
+                else if node.is_open
+                    if not @handleOpenFolder(node, $element)
+                        must_iterate_inside = false
+                else
+                    @handleClosedFolder(node, next_node, $element)
+
+            if must_iterate_inside
+                children_length = node.children.length
+                for child, i in node.children
+                    if i == (children_length - 1)
+                        _iterateNode(node.children[i], null)
+                    else
+                        _iterateNode(node.children[i], node.children[i+1])
+
+                if node.is_open
+                    @handleAfterOpenFolder(node, next_node, $element)
+
+        _iterateNode(@tree, null)
+
+    handleNode: (node, next_node, $element) ->
+        # override
+
+    handleOpenFolder: (node, $element) ->
+        # override
+        # return
+        #   - true: continue iterating
+        #   - false: stop iterating
+
+    handleClosedFolder: (node, next_node, $element) ->
+        # override
+
+    handleAfterOpenFolder: (node, next_node, $element) ->
+        # override
+
+    handleFirstNode: (node, $element) ->
+        # override
+
+
+class HitAreasGenerator extends VisibleNodeIterator
+    constructor: (tree, current_node, tree_bottom) ->
+        super(tree)
+
+        @current_node = current_node
+        @tree_bottom = tree_bottom
+
+    generate: ->
+        @positions = []
+        @last_top = 0
+
+        @iterate()
+
+        return @generateHitAreas(@positions)
+
+    getTop: ($element) ->
+        return $element.offset().top
+
+    addPosition: (node, position, top) ->
+        area = {
+            top: top
+            node: node
+            position: position
+        }
+
+        @positions.push(area)
+        @last_top = top
+
+    handleNode: (node, next_node, $element) ->
+        top = @getTop($element)
+
+        if node == @current_node
+            # Cannot move inside current item
+            @addPosition(node, Position.NONE, top)
+        else
+            @addPosition(node, Position.INSIDE, top)
+
+        if (
+            next_node == @current_node or
+            node == @current_node
+        )
+            # Cannot move before or after current item
+            @addPosition(node, Position.NONE, top)
+        else
+            @addPosition(node, Position.AFTER, top)
+
+    handleOpenFolder: (node, $element) ->
+        if node == @current_node
+            # Cannot move inside current item
+            # Stop iterating
+            return false
+
+        # Cannot move before current item
+        if node.children[0] != @current_node
+            @addPosition(node, Position.INSIDE, @getTop($element))
+
+        # Continue iterating
+        return true
+
+    handleClosedFolder: (node, next_node, $element) ->
+        top = @getTop($element)
+
+        if node == @current_node
+            # Cannot move after current item
+            @addPosition(node, Position.NONE, top)
+        else
+            @addPosition(node, Position.INSIDE, top)
+
+            # Cannot move before current item
+            if next_node != @current_node
+                @addPosition(node, Position.AFTER, top)
+
+    handleFirstNode: (node, $element) ->
+        if node != @current_node
+            @addPosition(node, Position.BEFORE, @getTop($(node.element)))
+
+    handleAfterOpenFolder: (node, next_node, $element) ->
+        if (
+            node == @current_node.node or
+            next_node == @current_node.node
+        )
+            # Cannot move before or after current item
+            @addPosition(node, Position.NONE, @last_top)
+        else
+            @addPosition(node, Position.AFTER, @last_top)
+
+    generateHitAreas: (positions) ->
+        previous_top = -1
+        group = []
+        hit_areas = []
+
+        for position in positions
+            if position.top != previous_top and group.length
+                if group.length
+                    @generateHitAreasForGroup(
+                        hit_areas,
+                        group,
+                        previous_top,
+                        position.top
+                    )
+
+                previous_top = position.top
+                group = []
+
+            group.push(position)
+
+        @generateHitAreasForGroup(
+            hit_areas,
+            group,
+            previous_top,
+            @tree_bottom
+        )
+
+        return hit_areas
+
+    generateHitAreasForGroup: (hit_areas, positions_in_group, top, bottom) ->
+        # limit positions in group
+        position_count = Math.min(positions_in_group.length, 4)
+
+        area_height = Math.round((bottom - top) / position_count)
+        area_top = top
+
+        i = 0
+        while (i < position_count)
+            position = positions_in_group[i]
+
+            hit_areas.push(
+                top: area_top,
+                bottom: area_top + area_height,
+                node: position.node,
+                position: position.position
+            )
+
+            area_top += area_height
+            i += 1
+
+        return null
+
+
+class DragElement
+    constructor: (node_name, offset_x, offset_y, $tree) ->
+        @offset_x = offset_x
+        @offset_y = offset_y
+
+        @$element = $("<span class=\"jqtree-title jqtree-dragging\">#{ node_name }</span>")
+        @$element.css("position", "absolute")
+        $tree.append(@$element)
+
+    move: (page_x, page_y) ->
+        @$element.offset(
+            left: page_x - @offset_x,
+            top: page_y - @offset_y
+        )
+
+    remove: ->
+        @$element.remove()
+
+
+module.exports =
+    DragAndDropHandler: DragAndDropHandler
+    DragElement: DragElement
+    HitAreasGenerator: HitAreasGenerator