jsPlumb - dynamic endpoint anchors on each side

DMINATOR picture DMINATOR · Mar 4, 2013 · Viewed 13.8k times · Source

I am trying to figure out a way how to add endpoint anchors dynamically to jsPlumb container.

I would like to have source endpoints on the left side and target endpoints on the right side only.

The problem is, that I wasn't able to find any way to do so, without resorting to some hacks, like I am doing now.

jsPlumb supports Continuous Anchors, but position of individual anchor will be recalculated based on the orientation between connectors and number of continuous anchors. This means both source and target endpoints could be sharing the same side of the container, this is something I would like to avoid.

Here is a jsFiddler code I came up with

Here is a part of the code I am using to hack and recalculate anchor positions myself (when Add button is clicked), with some buggy results :(

   function fixEndpoints(endpoints) {

            //there are 2 types - input and output

            var inputAr = $.grep(endpoints, function (elementOfArray, indexInArray) {
                return elementOfArray.isSource; //input
            });

            var outputAr = $.grep(endpoints, function (elementOfArray, indexInArray) {
                return elementOfArray.isTarget; //output
            });

            calculateEndpoint(inputAr, true);
            calculateEndpoint(outputAr, false);
        }

        function calculateEndpoint(endpointArray, isInput) {

            //multiplyer
            var mult = 1 / endpointArray.length;

            for (var i = 0; i < endpointArray.length; i++) {

                if (isInput) {
                    endpointArray[i].anchor.x = 1;
                    endpointArray[i].anchor.y = mult * i;//, 1, 0] };
                } 
                else {
                    endpointArray[i].anchor.x = 0;
                    endpointArray[i].anchor.y = mult * i;//, -1, 0] };
                }
            }
        }



        //Add additional anchor
        $(".button_add").live("click", function () {

            var parentnode = $(this)[0].parentNode.parentNode;

            jsPlumb.addEndpoint(
                parentnode,
                anEndpointSource
            );

            jsPlumb.addEndpoint(
                parentnode,
                anEndpointDestination
            );

            //get list of current endpoints
            var endpoints = jsPlumb.getEndpoints(parentnode);

            //fix endpoints
            fixEndpoints(endpoints);

            jsPlumb.recalculateOffsets();
            jsPlumb.repaint(parentnode);
        });

Expected result

As you can see on the image above, left side has only source endpoints (Dot) and right side (Box) only target endpoints, once new endpoint is added, anchors are recalculated based on the number of anchors on one side.

This works but still buggy: position is updated only once I move the container and connection between containers is not correct as well.

What I would like to have, is a way for it to work and connect items correctly (preferably using correct jsPlumb code without resorting to hacks)

Answer

DMINATOR picture DMINATOR · Mar 19, 2013

I finally figured out how to do it. It was easier than I thought.

Code is basically the same with a few changes, here is updated fiddler sample

<!DOCTYPE html>
<html>
<head>
<title>JS plumb test</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>
    <script type="text/javascript" src="./include/jquery.jsPlumb-1.3.16-all-min.js"></script>

<style>
    .window { 
        background-color: #EEEEEF;
        border: 1px solid #346789;
        border-radius: 0.5em;
        box-shadow: 2px 2px 5px #AAAAAA;
        color: black;
        height: 5em;
        position: absolute;
        width: 5em;
    }

    .window:hover { 
        box-shadow: 2px 2px 19px #AAAAAA;
        cursor: pointer;
    }


    .button_add, .button_add_window, .button_remove, .button {
        background-color: deepskyblue;
        text-align: center;
        border: 1px solid;
    }

    .button_container {
        margin: 5px;
        background-color: #aaaaaa
    }
</style>

<script>

    jsPlumb.ready(function () {


        //FIX DOM:
        $(("#container1"))[0].innerHTML = $(("#container0"))[0].innerHTML;

        //all windows are draggable
        jsPlumb.draggable($(".window"));


        var anEndpointSource = {
            endpoint: "Rectangle",
            isSource: true,
            isTarget: false,
            maxConnections: 1,

            anchor: [1, 0, 1, 0]
        };

        var anEndpointDestination = {
            endpoint: "Dot",
            isSource: false,
            isTarget: true,
            maxConnections: 1,

            anchor: [0, 1, -1, 0]
        };


        //Fixes endpoints for specified target
        function fixEndpoints(parentnode) {

            //get list of current endpoints
            var endpoints = jsPlumb.getEndpoints(parentnode);

            //there are 2 types - input and output

            var inputAr = $.grep(endpoints, function (elementOfArray, indexInArray) {
                return elementOfArray.isSource; //input
            });

            var outputAr = $.grep(endpoints, function (elementOfArray, indexInArray) {
                return elementOfArray.isTarget; //output
            });

            calculateEndpoint(inputAr, true);
            calculateEndpoint(outputAr, false);

            jsPlumb.repaintEverything();
        }

        //recalculate endpoint anchor position manually
        function calculateEndpoint(endpointArray, isInput) {

            //multiplyer
            var mult = 1 / (endpointArray.length+1);

            for (var i = 0; i < endpointArray.length; i++) {

                if (isInput) {

                    //position
                    endpointArray[i].anchor.x = 1;
                    endpointArray[i].anchor.y = mult * (i + 1);
                } 
                else {

                    //position
                    endpointArray[i].anchor.x = 0;
                    endpointArray[i].anchor.y = mult * (i + 1);
                }
            }
        }



        //Add additional anchor
        $(".button_add").live("click", function () {

            var parentnode = $(this)[0].parentNode.parentNode;

            jsPlumb.addEndpoint(
                parentnode,
                anEndpointSource
            );

            jsPlumb.addEndpoint(
                parentnode,
                anEndpointDestination
            );

            fixEndpoints(parentnode);
        });

        //Remove anchor 
        $(".button_remove").live("click", function () {

            var parentnode = $(this)[0].parentNode.parentNode;

            //get list of current endpoints
            var endpoints = jsPlumb.getEndpoints(parentnode);

            //remove 2 last one

            if (endpoints.length > 1) {
                jsPlumb.deleteEndpoint(endpoints[endpoints.length - 2]);
            }

            if (endpoints.length > 0) {
                jsPlumb.deleteEndpoint(endpoints[endpoints.length - 1]);
            }

            fixEndpoints(parentnode);
        });


        //adds new window
        $(".button_add_window").click(function () {

            var id = "dynamic_" + $(".window").length;

            //create new window and add it to the body
            $('<div class="window" id="' + id + '" >').appendTo('body').html($(("#container0"))[0].innerHTML);

            //set jsplumb properties
            jsPlumb.draggable($('#' + id));
        });
    });
</script>

</head>
<body >

    <!-- Adds new windows to the page -->
    <div class="window" style="left: 600px" id="details">
        <p style="text-align: center">Window</p>
        <div class="button_container">
            <div class="button_add_window">Add</div>
        </div>
    </div>

    <!-- Primary window - used as html templated for descendants -->
    <div class="window" style="left: 20px" id="container0">
        <div class="button_container">
            <div class="button_add">Add</div>
            <div class="button_remove">Remove</div>
        </div>
    </div>

    <div class="window" style="left: 200px" id="container1">
    </div>


</body>
</html>

Changes that I made:

  1. Now I specify endpoint anchor offset when I add it, I only calculate anchor position, so offset never changes, it is always correct from the start:

    var anEndpointSource = {
        endpoint: "Rectangle",
        isSource: true,
        isTarget: false,
        maxConnections: 1,
    
        anchor: [1, 0, 1, 0]
    };
    
  2. Once endpoint is added, I re-calculate anchor positions and call (this will repaint connections):

    jsPlumb.repaintEverything();

Here is the final result:

endpoints correctly connected