jsPlumb — динамические привязки конечных точек с каждой стороны

Я пытаюсь выяснить, как динамически добавлять якоря конечных точек в контейнер jsPlumb.

Я хотел бы иметь исходные конечные точки с левой стороны и целевые конечные точки только с правой стороны.

Проблема в том, что я не смог найти способ сделать это, не прибегая к некоторым хакам, как я делаю сейчас.

jsPlumb поддерживает непрерывные привязки, но положение отдельных привязок будет пересчитано на основе ориентации между соединителями и количества сплошных анкеров. Это означает, что исходная и целевая конечные точки могут совместно использовать одну и ту же сторону контейнера, чего я хотел бы избежать.

Вот код jsFiddler, который я придумал

Вот часть кода, который я использую для самостоятельного взлома и пересчета позиций привязки (когда нажата кнопка «Добавить»), с некоторыми ошибочными результатами :(

   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);
        });

Ожидаемый результат

Как вы можете видеть на изображении выше, левая сторона имеет только исходные конечные точки (точка), а правая сторона (коробка) — только целевые конечные точки, после добавления новой конечной точки привязки пересчитываются на основе количества привязок на одной стороне.

Это работает, но все еще глючит: позиция обновляется только после того, как я перемещаю контейнер, и соединение между контейнерами также неверно.

То, что я хотел бы иметь, - это способ, чтобы он работал и правильно соединял элементы (желательно, используя правильный код jsPlumb, не прибегая к хакам)


person DMINATOR    schedule 04.03.2013    source источник


Ответы (3)


Наконец-то я понял, как это сделать. Это было проще, чем я думал.

Код в основном тот же, но с некоторыми изменениями, здесь обновлен пример скрипта

<!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>

Изменения, которые я сделал:

  1. Теперь я указываю смещение привязки конечной точки, когда добавляю его, я вычисляю только положение привязки, поэтому смещение никогда не меняется, оно всегда правильно с самого начала:

    var anEndpointSource = {
        endpoint: "Rectangle",
        isSource: true,
        isTarget: false,
        maxConnections: 1,
    
        anchor: [1, 0, 1, 0]
    };
    
  2. Как только конечная точка добавлена, я пересчитываю позиции привязки и вызываю (это перерисует соединения):

    jsPlumb.repaintEverything();

Вот окончательный результат:

конечные точки правильно подключены

person DMINATOR    schedule 19.03.2013
comment
очень хорошо сделано :) Есть ли способ удалить (выбранный) узел? Я пытался создать конечный автомат-редактор с jsplumb, но просто боролся с динамическим добавлением/удалением узлов и соединений. jsfiddle было бы так здорово :) - person Dominik; 27.06.2013

Вы можете удалить состояния двойным щелчком, добавив

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

к вашей функции jsPlumb.ready. вы можете добавить это ко всем своим состояниям, добавив

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();
        });

здесь statemachine-demo — это идентификатор div в вашем контейнере, а state — это класс div состояний.

person Buddhika    schedule 20.03.2014

спасибо за решение, оно отлично работает, когда якорная сторона предопределена, например, здесь источники всегда слева, а цели всегда справа.

Но если они динамические, нам нужно реализовать выбор стороны также самостоятельно?

В качестве обходного пути я установил дополнительные возможные позиции привязки в конфигурации по умолчанию. Любая лучшая идея?

Спасибо

person cn123h    schedule 07.02.2017