使用Jquery draggable将拖放/附加功能应用于JSTreegraph

时间:2012-12-18 05:58:11

标签: javascript jquery jquery-ui

我使用JSTreegraph插件绘制树形结构。 但是现在我需要一个拖放功能,其中我可以拖动树的任何节点并连接到任何其他节点,然后第一个节点的所有子节点现在将成为新节点的子节点(它的所有节点)被附上)。

据我所知,这个插件似乎没有这个功能。它只是根据传递给它的数据对象绘制结构。

插件基本上将一个类Node分配给树的所有节点(div),另一个类NodeHover分配给悬停的节点。没有id分配给这些div。

所以我尝试使用JQuery Draggable来查看是否可以通过这样做移动任何节点

$('.Node').draggable();
$('.NodeHover').draggable();

但它似乎没有用。所以任何人都可以帮助我。

如何拖动和附加功能?

* 编辑:: 请原谅我使用Fiddle并不是很好,所以我在这里分享一个示例代码供您使用: *

HTML文件:,它将绘制示例树

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <link href="css/JSTreeGraph.css" rel="stylesheet" type="text/css" />
  <script src="js/JSTreeGraph.js" type="text/javascript"></script>
  <script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
  <script src="js/jquery.ui.position.js" type="text/javascript"></script>
  <style type="text/css">
     .Container {
       position: absolute;
      top: 100px;
      left: 50px;
       id: Container; 
    }


 </style>
</head>
<body>
    <div id="tree">
        Ctrl+Click to Add Node
        <br />
        Double Click to Expand or Collapse
        <br />
        Shift+Click to Delete Node
        <br />
        <select id="dlLayout" onchange="ChangeLayout()">
            <option value="Horizontal">
                Horizontal
            </option>
            <option value="Vertical" selected>
                Vertical
            </option>
        </select>
        <div class="Container" id="dvTreeContainer"></div>


        <script type="text/javascript">

    var selectedNode;
    // Root node
    var rootNode = { Content: "Onida", Nodes:[] };

    // First Level
    rootNode.Nodes[0] = { Content: "Employee Code", navigationType: "0"};
    rootNode.Nodes[1] = { Content: "Problem Area", navigationType: "1"  };

    // Second Level
    rootNode.Nodes[1].Nodes = [{ Content : "ACC-HO", Collapsed: true /* This node renders collapsed */ }, 
                               { Content : "ACC-SALES" },
                               { Content : "BUSI. HEAD",   /*This node looks different*/ ToolTip: "Click ME!" },
                               { Content : "CEO"},
                               { Content : "HO-ADMIN"},
                               { Content : "HO-FACTORY"},
                               { Content : "SALES"}];

    // Third Level
    rootNode.Nodes[1].Nodes[0].Nodes = [{ Content: "Billing" },
                                        { Content: "Credit Limit" },
                                        { Content: "Reconciliation" }];

    rootNode.Nodes[1].Nodes[1].Nodes = [{ Content: "Billing" },
                                        { Content: "Others" }];

    rootNode.Nodes[1].Nodes[2].Nodes = [{ Content: "AC" },
                                        { Content: "CTV" },
                                        { Content: "DVD" },
                                        { Content: "Washing Machine" }];

    rootNode.Nodes[1].Nodes[6].Nodes = [{ Content: "Appointments" },
                                        { Content: "Resignations" },
                                        { Content: "Others" }];

    // Draw the tree for the first time
    RefreshTree();


    function RefreshTree() {
        DrawTree({ Container: document.getElementById("dvTreeContainer"),
                    RootNode: rootNode,
                    Layout: document.getElementById("dlLayout").value,
                    OnNodeClickFirefox: NodeClickFF,
                    OnNodeClickIE: NodeClickIE,
                    OnNodeDoubleClick: NodeDoubleClick
                    });
    }

    //function 
    function NodeClickFF(e) {
        if (e.shiftKey){
            // Delete Node
            if (!this.Node.Collapsed) {
                for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
                    if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
                        this.Node.ParentNode.Nodes.splice(index, 1);
                        break;
                    }
                }
               RefreshTree();
            }
                // return false;
        }
        else if (e.ctrlKey) {
            // Add new Child if Expanded                    
            if (!this.Node.Collapsed) {
                if (!this.Node.Nodes) this.Node.Nodes = new Array();
                var newNodeIndex = this.Node.Nodes.length;
                this.Node.Nodes[newNodeIndex] = new Object();
                this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
                RefreshTree();
            }
                // return false;
        }
        else{
            fnNodeProperties(this.Node);
        }
    }

    function NodeClickIE() {
        if (typeof(event) == "undefined" && event.ctrlKey) {
            // Add new Child if Expanded
            if (!this.Node.Collapsed) {
                if (!this.Node.Nodes) this.Node.Nodes = new Array();
                var newNodeIndex = this.Node.Nodes.length;
                this.Node.Nodes[newNodeIndex] = new Object();
                this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
                RefreshTree();
            }
        }
        else if (typeof(event) == "undefined" && event.shiftKey) {
            // Delete Node
            if (!this.Node.Collapsed) {
                for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
                    if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
                        this.Node.ParentNode.Nodes.splice(index, 1);
                        break;
                    }
                }
                RefreshTree();
            }
        }
        else{
            fnNodeProperties(this.Node);
        }
    }        

    function NodeDoubleClick() {
        if (this.Node.Nodes && this.Node.Nodes.length > 0) { // If has children
            this.Node.Collapsed = !this.Node.Collapsed;
            RefreshTree();
        }
    }

    function ChangeLayout() {

        RefreshTree();
    }

</script>
    </div>
</body>
</html>

JSTreeGraph JS文件:插件js文件

function DrawTree(options) {

// Prepare Nodes
PrepareNode(options.RootNode);

// Calculate Boxes Positions
if (options.Layout == "Vertical") {
    PerformLayoutV(options.RootNode);
} else {
    PerformLayoutH(options.RootNode);
}

// Draw Boxes
options.Container.innerHTML = "";
DrawNode(options.RootNode, options.Container, options);

// Draw Lines
DrawLines(options.RootNode, options.Container);
}

 function DrawLines(node, container) {
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and Is Expanded
    for (var j = 0; j < node.Nodes.length; j++) {
        if(node.ChildrenConnectorPoint.Layout=="Vertical")
            DrawLineV(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
        else
            DrawLineH(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);

        // Children
        DrawLines(node.Nodes[j], container);
    }
}
}

 function DrawLineH(container, startPoint, endPoint) {
    var midY = (startPoint.Y + ((endPoint.Y - startPoint.Y) / 2)); // Half path between start en end Y point

    // Start segment
    DrawLineSegment(container, startPoint.X, startPoint.Y, startPoint.X, midY, 1);

    // Intermidiate segment
    var imsStartX = startPoint.X < endPoint.X ? startPoint.X : endPoint.X; // The lower value will be the starting point
    var imsEndX = startPoint.X > endPoint.X ? startPoint.X : endPoint.X; // The higher value will be the ending point
    DrawLineSegment(container, imsStartX, midY, imsEndX, midY, 1);

    // End segment
    DrawLineSegment(container, endPoint.X, midY, endPoint.X, endPoint.Y, 1);
 }

  function DrawLineV(container, startPoint, endPoint) {
var midX = (startPoint.X + ((endPoint.X - startPoint.X) / 2)); // Half path between start en end X point

// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, midX, startPoint.Y, 1);

// Intermidiate segment
var imsStartY = startPoint.Y < endPoint.Y ? startPoint.Y : endPoint.Y; // The lower value will be the starting point
var imsEndY = startPoint.Y > endPoint.Y ? startPoint.Y : endPoint.Y; // The higher value will be the ending point
DrawLineSegment(container, midX, imsStartY, midX, imsEndY, 1);

// End segment
DrawLineSegment(container, midX, endPoint.Y, endPoint.X, endPoint.Y, 1);
}

function DrawLineSegment(container, startX, startY, endX, endY, lineWidth) {

var lineDiv = document.createElement("div");
lineDiv.style.top = startY + "px";
lineDiv.style.left = startX + "px";

if (startX == endX) { // Vertical Line
    lineDiv.style.width = lineWidth + "px";
    lineDiv.style.height = (endY - startY) + "px";
}
else{ // Horizontal Line
    lineDiv.style.width = (endX - startX) + "px";
    lineDiv.style.height = lineWidth + "px";
}

lineDiv.className = "NodeLine";
container.appendChild(lineDiv);
}

function DrawNode(node, container, options) {
var nodeDiv = document.createElement("div");
nodeDiv.style.top  = node.Top + "px";
nodeDiv.style.left = node.Left + "px";
nodeDiv.style.width = node.Width + "px";
nodeDiv.style.height = node.Height + "px";

if (node.Collapsed) {
    nodeDiv.className = "NodeCollapsed";
} else {
    nodeDiv.className = "Node";
}

if (node.Class) 
    nodeDiv.className = node.Class;

if (node.Content)
    nodeDiv.innerHTML = "<div class='NodeContent'>" + node.Content + "</div>";

if (node.ToolTip)
    nodeDiv.setAttribute("title", node.ToolTip);

nodeDiv.Node = node;

// Events
if (options.OnNodeClickIE){
    //alert('OnNodeClick');
    nodeDiv.onclick = options.OnNodeClickIE;        
}
// Events
if (options.OnNodeClickFirefox){
    //alert('OnNodeClick');
    nodeDiv.onmousedown = options.OnNodeClickFirefox;        
}
//on right click
if (options.OnContextMenu){
        //alert('OnContextMenu');
        nodeDiv.oncontextmenu = options.OnContextMenu;
     }

if (options.OnNodeDoubleClick)
    nodeDiv.ondblclick = options.OnNodeDoubleClick;

nodeDiv.onmouseover = function () { // In
    this.PrevClassName = this.className;
    this.className = "NodeHover";
};

nodeDiv.onmouseout = function () { // Out
    if (this.PrevClassName) {
        this.className = this.PrevClassName;
        this.PrevClassName = null;
    }
};

container.appendChild(nodeDiv);

// Draw children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has Children and is Expanded
    for (var i = 0; i < node.Nodes.length; i++) {
        DrawNode(node.Nodes[i], container, options);
    }
}
}

function PerformLayoutV(node) {

var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 40;
var nodeMarginTop = 20;

var nodeTop = 0; // defaultValue 

// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) {
    for (var i = 0; i < node.Nodes.length; i++) {
        PerformLayoutV(node.Nodes[i]);
    }
}

if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded

    // My Top is in the center of my children
    var childrenHeight = (node.Nodes[node.Nodes.length - 1].Top + node.Nodes[node.Nodes.length - 1].Height) - node.Nodes[0].Top;
    nodeTop = (node.Nodes[0].Top + (childrenHeight / 2)) - (nodeHeight / 2);

    // Is my top over my previous sibling?
    // Move it to the bottom
    if (node.LeftNode && ((node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop) > nodeTop)) {
        var newTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
        var diff = newTop - nodeTop;
        /// Move also my children
        MoveBottom(node.Nodes, diff);
        nodeTop = newTop;
    }

} else {
    // My top is next to my top sibling
    if (node.LeftNode)
        nodeTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
}

node.Top = nodeTop;

// The Left depends only on the level
node.Left = (nodeMarginLeft * (node.Level + 1)) + (nodeWidth * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;

// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = node.Left + nodeWidth;
var pointY = nodeTop + (nodeHeight/2);
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
// Parent: Where the line that connect this node with its parent end
pointX = node.Left;
pointY = nodeTop + (nodeHeight/2);
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
}

function PerformLayoutH(node) {

var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 20;
var nodeMarginTop = 50;

var nodeLeft = 0; // defaultValue 

// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length>0) {
    for (var i = 0; i < node.Nodes.length; i++) {
        PerformLayoutH(node.Nodes[i]);
    }
}

if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded

    // My left is in the center of my children
    var childrenWidth = (node.Nodes[node.Nodes.length-1].Left + node.Nodes[node.Nodes.length-1].Width) - node.Nodes[0].Left;
    nodeLeft = (node.Nodes[0].Left + (childrenWidth / 2)) - (nodeWidth / 2);

    // Is my left over my left node?
    // Move it to the right
    if(node.LeftNode&&((node.LeftNode.Left+node.LeftNode.Width+nodeMarginLeft)>nodeLeft)) {
        var newLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
        var diff = newLeft - nodeLeft;
        /// Move also my children
        MoveRigth(node.Nodes, diff);
        nodeLeft = newLeft;
    }
} else {
    // My left is next to my left sibling
    if (node.LeftNode) 
        nodeLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
}

node.Left = nodeLeft;

// The top depends only on the level
node.Top = (nodeMarginTop * (node.Level + 1)) + (nodeHeight * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;

// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = nodeLeft + (nodeWidth / 2);
var pointY = node.Top + nodeHeight;
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout:"Horizontal" };
// Parent: Where the line that connect this node with its parent end
pointX = nodeLeft + (nodeWidth / 2);
pointY = node.Top;
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Horizontal" };
}

function MoveRigth(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
    nodes[i].Left += distance;
    if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.X += distance;
    if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.X += distance;
    if (nodes[i].Nodes) {
        MoveRigth(nodes[i].Nodes, distance);
    }
}
}

function MoveBottom(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
    nodes[i].Top += distance;
    if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.Y += distance;
    if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.Y += distance;
    if (nodes[i].Nodes) {
        MoveBottom(nodes[i].Nodes, distance);
    }   
}
}

function PrepareNode(node, level, parentNode, leftNode, rightLimits) {

if (level == undefined) level = 0;
if (parentNode == undefined) parentNode = null;
if (leftNode == undefined) leftNode = null;
if (rightLimits == undefined) rightLimits = new Array();

node.Level = level;
node.ParentNode = parentNode;
node.LeftNode = leftNode;

if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and is expanded
    for (var i = 0; i < node.Nodes.length; i++) {
        var left = null;
        if (i == 0 && rightLimits[level]!=undefined) left = rightLimits[level];
        if (i > 0) left = node.Nodes[i - 1];
        if (i == (node.Nodes.length-1)) rightLimits[level] = node.Nodes[i];
        PrepareNode(node.Nodes[i], level + 1, node, left, rightLimits);
    }
}
}

JSTreeGraph CSS文件:

.NodeContent
{
font-family:Verdana;
font-size:small;
}

.Node 
{
position:absolute;
background-color: #CCDAFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}

.NodeHover
{
position:absolute;
background-color: #8FADFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}

.NodeCollapsed
{
position:absolute;
background-color: #8FADFF;
border: 2px solid black;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}


.NodeLine
{
background-color: #000066;
position:absolute;
overflow:hidden;
}

2 个答案:

答案 0 :(得分:2)

我认为你需要的是在拖动时建立修改树逻辑结构的机制,然后重新加载整个树元素。这样,插件将呈现新的修改结构。

答案 1 :(得分:0)

这不是一个简单的问题,你没有概述确切的规格。

  • 移动节点时,是否所有子节点都随之移动?
  • 删除/附加节点时节点及其子元素会发生什么变化?

您正在使用的插件适用于绘制静态树,但它不是以允许动态编辑树的方式编写的。

我必须同意@Bardo一致认为,最简单的方法就是了解树的操作方式。 (值得庆幸的是,该插件似乎提供了一个onNodeClick选项,可以让您了解您打算操作的节点)。一旦树被操纵,那么它必须完全重绘。 (似乎没有一种好方法来部分绘制树)。