RE: Using jsPlumb with backbonejs and underscore

25 views
Skip to first unread message

Mark Jones

unread,
Dec 17, 2019, 2:02:53 PM12/17/19
to jsPlumb

Hello All,

Just wondering has anyone here had any experience or exposure to using jsPlumb along with backbonejs https://backbonejs.org and underscorejs https://underscorejs.org?

What i am trying to figure out specifically for backbonejs is that if you have view
that you extend as follows, then how do you get jsPlumb data loaded into a externally loaded view component_details.jst

//Here we are extending the backbonejs View
TestUI.View.BlueprintDetails = Backbone.View.extend({

    //here is the external template we want to populate (appended at the bottom of this page named blueprint_details.jst)
    template: TestUI.Templates.blueprints_blueprint_details,

    events: {
        'click #ComponentDetailsButton' : 'displayComponentDetails',
    },

    blueprintMetadata  : null,
    blueprintComponents: new TestUI.Collection.Components(),
    blueprintSystems   : new TestUI.Collection.Systems(),
   
    initialize: function () {
       
        this.childViews = [];

        this.listenTo(this.model, 'sync', this.getUIMetadata); // will fetch systems on getUIMetadata success
        this.listenTo(this.blueprintSystems, 'sync', this.renderDetails); // pipeline, uiMetadata, and systems must be available before blueprint viz will render

        this.listenTo(this.blueprintSystems, 'add', this.renderSystem); // renders systems in sidebar
        this.listenTo(this.blueprintComponents, 'add', this.renderComponent); // renders components in sidebar
    },

    render: function () {
        this.$el.html(this.template());

        this.model.fetch();

        this.blueprintComponents.fetch();
    },

    renderComponent: function (model) {
        var componentView = new TestUI.View.SidebarComponent({model: model});

        this.$el.find('#Components').append(componentView.render().el);

        this.childViews.push(componentView);
    },

    renderSystem: function (model) {
        var systemView = new TestUI.View.SidebarSystem({model: model});

        this.$el.find('#Components').append(systemView.render().el);

        this.childViews.push(systemView);
    },

    renderDetails: function () {
        this.renderBlueprint(this.convertModelToGraphML());
    },

    renderBlueprint: function (convertedData) {

        //convertedData<--convertModelToGraphML<--convertedForJsplumb

        var self = this;
    
        jsPlumbToolkit.ready(function () {

            var displayComponentDetails = function(params) {
                var data = params.node.data;
                console.log('data::'+data);
                console.log(data);
                console.log('enter displayComponentDetails');
                
//THIS DOES NOT SEEM CORRECT AS I'M PASSING THE DATA OBJECT FROM JSPLUMB AS A PARAMETER
//INTO THE VIEW WHERE WHAT I REALLY WANT TO DO IS ADD IT TO THE MODEL
                
                var componentDetailsView = new TestUI.View.BlueprintComponentDetails({model: self.model}, data);
                
                console.log('componentDetailsView created');
                self.$el.find('#ComponentDetails').append(componentDetailsView.render().el);
                console.log('#ComponentDetails found');
                self.childViews.push(componentDetailsView);
                console.log('exit displayComponentDetails');
            };

            var mainElement = document.querySelector("#jtk-demo-absolute"),
                canvasElement = mainElement.querySelector(".jtk-demo-canvas"),
                controlsElement = mainElement.querySelector(".controls"),
                toolkit = window.toolkit = jsPlumbToolkit.newInstance({
                    groupFactory: function (type, data, callback, originalEvent, isNative) {
                        callback(data);
                    },
                    nodeFactory : function (type, data, callback, originalEvent, isNative) {
                        data.id = "NOID_" + (toolkit.getNodeCount() + 1); // this will allow me to get all nodes with NOID and allow the server to append GUIDs to them
                        callback(data);
                    }
                }),
                view = {
                    nodes : {
                        "default": {
                            template: "tmplNode",
                            events  : {
                                tap  : function (jsplumbEl) {

                                    console.log('***************************');
                                    console.log('BEGIN tap event for jsplumbEl');
                                    console.log('***************************');
                                    console.log(jsplumbEl);
                                    console.log('***************************');
                                    console.log('END tap event for jsplumbEl');
                                    console.log('***************************');
                                },
                                click: function (params) {
                                    console.log('***************************');
                                    console.log('BEGIN click event for params');
                                    console.log('***************************');                                   
                                    for (var key in params) {
                                        if (key==='node') {
                                            for (var item in params[key]) {
                                                if (item==='data') {
                                                    for (var entry in params[key][item]) {
                                                        console.log('name: '+entry, 'value: '+ params[key][item][entry]);                                                                                                   
                                                    }
                                                }
                                            }
                                        }
                                    }    
                                    console.log('***************************');
                                    console.log('END click event for params');
                                    console.log('***************************');  
                                    console.log(params);
                                    
                                    displayComponentDetails(params);
                                    
                                }
                            }
                        }
                    },
                    groups: {
                        "default": {
                            template: "tmplGroup",
                            endpoint: "Blank",
                            anchor  : "Continuous",
                            // revert   : false,
                            orphan  : true,
                            autoSize: true,
                            events  : {
                                tap: function (jsplumbEl) {
                                    console.log(jsplumbEl);
                                }
                            }
                        }
                    },
                    edges : {
                        "default": {
                            editable:true,
                            connector: ["Flowchart", {cornerRadius: 10}],
                            paintStyle: { strokeWidth: 2, stroke: "rgb(132, 172, 179)", outlineWidth: 3, outlineStroke: "transparent" },
                            hoverPaintStyle: { strokeWidth: 2, stroke: "rgb(67,67,67)" },
                            events: {
                                "dblclick": function (params) {
                                    jsPlumbToolkit.Dialogs.show({
                                        id: "dlgConfirm",
                                        data: {
                                            msg: "Delete Edge"
                                        },
                                        onOK: function () {
                                            toolkit.removeEdge(params.edge);
                                        }
                                    });
                                }
                            },                           
                            overlays : [
                                ["Label", {
                                    cssClass: "delete-relationship",
                                    //label   : "<i class='fa fa-times'></i>",
                                    events  : {
                                        "tap": function (params) {
                                            toolkit.removeEdge(params.edge);
                                        }
                                    }
                                }],
                                ["Arrow", { location: 1, width: 10, length: 10 }]
                            ]
                        },
                        "connection":{
                            parent:"default",
                            overlays:[
                                [
                                    "Label", {
                                        label: "${label}",
                                        events:{
                                            click:function(params) {
                                                console.log("_editLabel");
                                                _editLabel(params.edge);
                                            }
                                        }
                                    }
                                ]
                            ]
                        }
                    }
                },

                renderer = window.renderer = toolkit.render({
                    container        : canvasElement,
                    view             : view,
                    layout           : {
                        type: "Absolute"
                    },
                    jsPlumb          : {
                        Anchor         : "Continuous",
                        Endpoint       : "Blank",
                        Connector      : ["StateMachine", {
                            cssClass  : "connectorClass",
                            hoverClass: "connectorHoverClass"
                        }],
                        PaintStyle     : {
                            strokeWidth: 4,
                            stroke     : '#f2f7ff'
                        },
                        HoverPaintStyle: {
                            stroke: '#B8CAE6'
                        },
                        Overlays       : [
                            ["Arrow", {
                                fill    : "#f2f7ff",
                                width   : 20,
                                length  : 20,
                                location: 1
                            }]
                        ]
                    },
                    dragOptions      : {
                        filter   : ".delete *, .group-connect *, .ui-resizable-handle",
                        magnetize: true
                    },
                    events           : {

                        canvasClick: function (e) {
                            toolkit.clearSelection();
                            // this.autoSizeGroups();
                        },
                        modeChanged: function (mode) {
                            jsPlumb.removeClass(jsPlumb.getSelector("[mode]"), "selected-mode");
                            jsPlumb.addClass(jsPlumb.getSelector("[mode='" + mode + "']"), "selected-mode");
                        }
                    },
                    consumeRightClick: false,
                    zoomToFit        : true,
                    activeFiltering  : true,
                    wheelReverse     : true
                }),
               
                undoredo = window.undoredo = new jsPlumbToolkitUndoRedo({
                    toolkit : toolkit,
                    onChange: function (undo, undoSize, redoSize) {
                        controlsElement.setAttribute("can-undo", undoSize > 0);
                        controlsElement.setAttribute("can-redo", redoSize > 0);
                    },
                    compound: true
                });

            // This syntax is a JSON equivalent of GraphML.
            toolkit.load({type: "json", data: convertedData});

            // "tap","dbltap","click","dblclick","mouseover","mouseout","mousemove","mousedown","mouseup","contextmenu"

            // jsPlumb Events
            // https://community.jsplumbtoolkit.com/doc/events.html

            // on home button tap, zoom content to fit.
            jsPlumb.on(controlsElement, "tap", "[reset]", function () {
                toolkit.clearSelection();
                renderer.zoomToFit();
            });

            jsPlumb.on(controlsElement, "tap", "[undo]", function () {
                undoredo.undo();
            });

            jsPlumb.on(controlsElement, "tap", "[redo]", function () {
                undoredo.redo();
            });

            jsPlumb.on(canvasElement, "click", ".delete", function (e) { // for some strange reason 'tap' stopped working
                var info = toolkit.getObjectInfo(this);
                toolkit.removeNode(info.obj);
            });

            jsPlumb.on(canvasElement, "tap", ".group-title .expand", function (e) {
                var info = toolkit.getObjectInfo(this);
                if (info.obj) {
                    renderer.toggleGroup(info.obj);
                }
            });

            jsPlumb.on(canvasElement, "tap", ".group-delete", function (e) {
                var info = toolkit.getObjectInfo(this);
                toolkit.removeGroup(info.obj, true);
            });

            new SurfaceDropManager({
                surface      : renderer,
                source       : document.querySelector(".node-palette"),
                selector     : "[data-node-type]",
                dataGenerator: function (el) {
                    return {
                        type : el.getAttribute("data-node-type"),
                        class: el.getAttribute("data-node-class"),
                        id   : el.getAttribute("data-system-id"),
                        title: el.getAttribute('data-system-name')
                    };
                }
            });

        });

    },

    convertModelToGraphML: function () {
    
        var convertedForJsplumb,
            modelToJSON = this.model.toJSON(),
            connections = modelToJSON.pipelineConnections.connections,
            components = modelToJSON.pipelineComponents.components,

            systemsToJSON = this.blueprintSystems.toJSON(),

            nodes = [],
            edges = [],
            groups = [],

            groupCheck = [],  // store unique system IDs here

            blueprintDetailsView = this,

            pushSystemIDToGroups = function (systemID) {
                var group = {},
                    foundSystem = groupCheck.find(function (element) {
                        return element === systemID;
                    }),
                    systemDisplayName = systemsToJSON.find(function (element) {
                        return element.systemID === systemID;
                    });

                if (!foundSystem) {
                    // if a system ID is not found push it into the groupCheck array to be referenced using foundSystem
                    groupCheck.push(systemID);

                    // set the id and title of the group AKA system
                    group.id = systemID;
                    group.title = systemDisplayName.displayName;

                    // push the group into the groups array
                    groups.push(group);
                }
            },

            i;

        // find the components in the blueprint data and get em in the nodes prop
        // also because systems aren't their own parameter we have to extract that data from the components
        // and place them in the groups prop using pushSystemIDToGroups
        for (i = 0; i < components.length; i++) {
            var component = {};

            component.id = components[i].componentID;
            component.name = components[i].componentID; // we need a name from the JSON
            component.group = components[i].systemID;
            component.class = components[i].componentClass;

            nodes.push(component);

            pushSystemIDToGroups(component.group);
        }

        // find the connections in the blueprint data and get em in the edges prop
        for (i = 0; i < connections.length; i++) {
            var connection = {};

            connection.source = connections[i].sourceComponentID;
            connection.target = connections[i].destinationComponentID;

            edges.push(connection);
        }

        convertedForJsplumb = {
            nodes : nodes,
            edges : edges,
            groups: groups
        };

        if (this.blueprintMetadata) {
            // cycle through the metadata and fix the position to the converted component/node data
            for (i = 0; i < this.blueprintMetadata.uiData.components.length; i++) {
                var foundComponent = convertedForJsplumb.nodes.find(function (item) {
                    return item.id === blueprintDetailsView.blueprintMetadata.uiData.components[i].id;
                });

                foundComponent.top = blueprintDetailsView.blueprintMetadata.uiData.components[i].top;
                foundComponent.left = blueprintDetailsView.blueprintMetadata.uiData.components[i].left;
            }

            // cycle though the metadata and fix the position to the converted system data
            for (i = 0; i < this.blueprintMetadata.uiData.systems.length; i++) {
                var foundSystem = convertedForJsplumb.groups.find(function (item) {
                    return item.id === blueprintDetailsView.blueprintMetadata.uiData.systems[i].id;
                });

                foundSystem.top = blueprintDetailsView.blueprintMetadata.uiData.systems[i].top;
                foundSystem.left = blueprintDetailsView.blueprintMetadata.uiData.systems[i].left;
            }
        }

        return convertedForJsplumb;
    },

    getUIMetadata: function () {
        var pipelineID = this.model.get('pipelineID'),
            blueprintDetailsView = this;
        $.ajax({
            method : 'GET',
            url    : '/uiMetadata/' + pipelineID,
            headers: {
                accept: 'application/json'
            }
        })
            .done(function (res) {
                blueprintDetailsView.blueprintMetadata = res;
                blueprintDetailsView.blueprintSystems.fetch();
            })
            .fail(function () {
                blueprintDetailsView.blueprintSystems.fetch();
            });
    },
 });
 

///////////////////////
//blueprint_details.jst
///////////////////////
<h1>Blueprint</h1>
<div class="flex-row">
    <div class="col colspan-2 after-colspan-2"><input type="button" id="BackButton" class="" value="&lt; Back"/></div>
    <div class="col colspan-2 after-colspan-1"><input type="button" id="SaveBlueprint" class="submit icon-save" value="TEMP Save Blueprint Positions TEMP"/></div>
    <div class="col colspan-2 after-colspan-1"><input type="button" id="DeleteBlueprint" class="destructive icon-delete" value="Delete Blueprint"/></div>
    <div class="col colspan-2"><input type="button" id="DeployBlueprint" class="icon-deploy" value="Deploy Blueprint"/></div>
</div>
<div id="BlueprintContainer"></div>
<div class="jtk-demo-main" id="jtk-demo-absolute">
    <div style="display:flex">
        <div class="sidebar node-palette" id="Components"></div>
        <!-- this is the main drawing area -->
        <div class="jtk-demo-canvas">
            <!-- controls -->
            <div class="controls">
                <span class="control undo" undo="" data-label="undo"></span>
                <span class="control redo" redo="" data-label="redo"></span>
                <span class="control full-screen" data-label="full screen"></span>
                <span class="control exit-full-screen" data-label="exit full screen"></span>
                <span class="control fit-screen" reset="" data-label="fit on canvas"></span>
            </div>
        </div>
        <div id="ComponentDetails"></div>
    </div>
</div>


///////////////////////
//component_details.jst
///////////////////////
<div>
<li><%= data.id %></li>
<li><%= data.group %></li>
<li><%= data.class %></li>
<li><%= data.top %></li>
<li><%= data.left %></li>
</div>
Reply all
Reply to author
Forward
0 new messages