jsPlumb - dynamic endpoint anchors on each side
Asked Answered
R

3

11

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)

Reborn answered 4/3, 2013 at 21:5 Comment(0)
R
13

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

Reborn answered 19/3, 2013 at 18:56 Comment(1)
very well done :) Is there a way to "remove" a (chosen) node, too? I tr to create a state machine-editor w/ jsplumb but just struggling w/ dynamically add/remove nodes and connections. A jsfiddle would be so great :)Sheaff
W
0

You can delete states by double click by adding

                     newState.dblclick(function(e) {
                        alert("This will delete the state and its connections");
                        instance.detachAllConnections($(this));
                      $(this).remove();
                      e.stopPropagation();
                    });

function to your jsPlumb.ready function. you can add this to your all states by adding

var windows = jsPlumb.getSelector(".statemachine-demo .state");
            windows.dblclick(function(e) {
            alert("This will delete the state and its connections");
            instance.detachAllConnections($(this));
          $(this).remove();
          e.stopPropagation();
        });

here statemachine-demo is id of div in your contaner and state is class of state divs.

Webfoot answered 20/3, 2014 at 11:16 Comment(0)
R
0

thank you for the solution, it works fine when the anchor side is predefined, like here sources are always on the left side and targets are always on the right.

But if they are dynamic we need to implement the side selection also by ourselves?

As a workaround what I did is to set more possible anchor positions in default config. Any better idea?

thanks

Roi answered 7/2, 2017 at 10:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.