11

What's the best approach to save and load an flowchart on jsPlumb?

Diogo Cardoso
  • 21,637
  • 26
  • 100
  • 138

5 Answers5

9

I managed to save the chart by simply having all the elements inside an array of objects, where each object has source and target nodes, x, y coordinates.

When saving, simply do JSON.stringify(whole_object), and if loading, simply JSON.parse() and manually position the nodes as well as connect them.

lukas.pukenis
  • 13,057
  • 12
  • 47
  • 81
  • can you please share some code ? I'm confused about how to load and connect them. – coding_idiot Apr 04 '14 at 10:45
  • Oh.. It was long time ago, but as far as I remember simply save the data by JSON.stringify(objectCollection). There are relations, positions, etc. Now when you want to load, do JSON.parse(generatedStrnig) and then manually go through each node in the object and position it(with CSS) and connect them(with jsPlumb API). Sorry I don't remember exactly :) – lukas.pukenis Apr 04 '14 at 13:11
7

My solution save and load jsPlumb:

function Save() {
    $(".node").resizable("destroy");
    Objs = [];
    $('.node').each(function() {
        Objs.push({id:$(this).attr('id'), html:$(this).html(),left:$(this).css('left'),top:$(this).css('top'),width:$(this).css('width'),height:$(this).css('height')});
    });
    console.log(Objs);
}


function Load() {
    var s="";
    for(var i in Objs) {
        var o = Objs[i];
        console.log(o);
        s+='<div id="'+ o.id+'" class="node" style="left:'+ o.left+'; top:'+ o.top+'; width:'+ o.width +'; height:'+ o.height +' "> '+ o.html+'</div>';
    }
    $('#main').html(s);
}

UPD Demo: http://jsfiddle.net/Rra6Y/137/

Note: if demo does not work in JsFiddle, make sure it points to an existing jsPlumb link (links are listed in "External Resources" JsFiddle menu item

srgi0
  • 3,319
  • 1
  • 23
  • 20
  • Hi and thanks for these hints. I just try to create an online finite state machine designer with jsplumb and to do the saving / loading correctly, I need to pass more data than the html you added to my array. Something like "state name", "connectedTo" and so on. You know, something to actually save my "state machine", not just my "drawing". You got a hint on how to do this, perhaps? would be great! – Dominik Jun 27 '13 at 09:17
  • 1
    the jsfiddle link doesn't work. Can't drag and no connections are created. There are errors in the console for save, load & clear. – coding_idiot Apr 04 '14 at 11:37
4

My save functionality does a bit more than just save the x, y position of the element and its connections. I also added saving a connection Label overlay as well as custom text for each element. You can tailor this solution as per your requirements but here it is basically:

   //save functionality
    function IterateDrawnElements(){  //part of save
        var dict = {};
        $('#id_diagram_container').children('div.window').each(function () {
            var pos = $(this).position()
            var diagram_label = $(this).children('div.asset-label').children('div.asset-diagram-label').text()
            if (diagram_label == null || diagram_label == ''){
                diagram_label='';
            }
            dict[this.id] = [pos.left, pos.top, diagram_label];
        });
        return dict;
    }
    function IterateConnections(){  //part of save
        var list = [];
        var conns = jsPlumb.getConnections()
        for (var i = 0; i < conns.length; i++) {
            var source = conns[i].source.id;
            var target = conns[i].target.id;
            try{
                var label = conns[i].getOverlay("label-overlay").labelText;
            }
            catch(err) {
                label = null
            }
            //list.push([source, target])
            if (source != null && target != null){
                list.push([source, target, label]);
            };
        }
        return list;
    }

I initiate all this when the user hits the save button, an ajax call is made back to the server, in this case Django is intercepting the ajax request and saves the data to the database.

//ajax call when save button pressed $save_btn.click(function() {

//drawn elements
var d_elements = IterateDrawnElements();
var d_conns = IterateConnections();
var d_name =$('#id_diagram_name').val();

$.ajax({
    url : ".",
    type : "POST",
    dataType: "json",
    data : {
        drawn_elements: JSON.stringify(d_elements),
        conns: JSON.stringify(d_conns),
        diagram_name: d_name,
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
    success: function (result) {

        if (result.success == true){
            save_status.html(result.message)
        }
        //console.log(JSON.stringify(result));
        $save_btn.attr('disabled','disabled');
        if (result.old_name != false){
            //alert()
            $('#id_diagram_name').val(result.old_name)
        }
    },
    error: function(xhr, textStatus, errorThrown) {
        alert("Please report this error: "+errorThrown+xhr.status+xhr.responseText);
    }
});
//return false; // always return error?

});

To load all this up is even easier and there are many ways you can do this. In Django you can just generate the html straight in your template as well as the js for the connections or you can create a JSON object in javascript for everything and then have javascript draw it all based on the array. I used jquery for this.

//js & connections load

var asset_conns = [
    {% for conn in diagram_conns  %}
    [ {{ conn.source.id }}, {{ conn.target.id }}, '{{ conn.name }}' ],
    {% endfor %}
]


// Takes loaded connections and connects them
for (var i = 0; i< asset_conns.length; i++){
    var source = asset_conns[i][0].toString();
    var target = asset_conns[i][1].toString();
    var label = asset_conns[i][2];
    var c = jsPlumb.connect({source: source, target: target, detachable:true, reattach: true });  //on init already know what kind of anchor to use!
    if (label != null && label != 'None'){
        c.addOverlay([ "Label", { label: label,  id:"label-overlay"} ]);
    }
}

//html right into django template to draw elements, asset element interchangeable terms

{% for element in drawn_elements %}
    <div id="{{ element.asset.id }}" class="window" style="left:{{ element.left }}px;top:{{ element.top }}px;background-image: url('{% static element.asset.asset_mold.image.url %}'); width: {{ element.asset.asset_mold.image.width }}px;height: {{ element.asset.asset_mold.image.height }}px;">
        <div class="asset-label" id="label-{{ element.asset.id }}">
            {#{{ element.asset }}#}<a class="lbl-link" id="lbl-link-{{ element.asset.id }}" href="{{ element.asset.get_absolute_url }}">{{ element.asset }}</a>
            <div class='asset-diagram-label' id="lbl-{{ element.asset.id }}">{% if element.asset.diagram_label  %}{{ element.asset.diagram_label }}{% endif %}</div>
        </div>
        <div class='ep' id="ep-{{ element.asset.id }}"></div>
    </div>
{% endfor %}

You can greatly simplify this but mine also gets a background for the element, as well as label and the shape of the element to use with perimeter anchors. This solution works and is tested. I'll release an open source Djago application for this soon on PyPi.

radtek
  • 34,210
  • 11
  • 144
  • 111
0

I'm using YUI with it. I'm saving the position of each box item being connected in a table. I them have a separate table the stores a parent to child relationship between the items, which is used to determine the lines jsPlumb should draw. I determine this using a selection process in which the first item selected is the parent, and all other items are children. When the "connect" button is clicked, the parent/child selection of the items is cleared. I also toggle this if you click the selected parent - it clear the child selections as well.

JasonMichael
  • 2,463
  • 4
  • 26
  • 25
0

I recently wrote this blog post about why jsPlumb doesn't have a save function (and what I recommend you do):

http://jsplumb.tumblr.com/post/11297005239/why-doesnt-jsplumb-offer-a-save-function

...maybe someone will find it useful.

  • 6
    What people are looking for is a simple way to say: persist this diagram to xml/json, or save it in some other way that can be easily parsed and mapped to update the data model... It seems like that's an obvious usability thing. It seems like what you outlined here will cause me to take a lot of annoying extra steps to get there the way I want to get there. – Brian MacKay Aug 23 '12 at 13:11