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="< 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>