Related
Scenario
I have inherited older backbone app with master-details scenario. On master view I have list of items (project) and when I click on an item I can edit it on details view (ProjectFormView).
The problem
When I edit the project on ProjectFormView, all the previously opened project are edited with the same values as well.
Details:
I've discovered, that the UI events like input change are triggered also on previously opened ProjectFormViews, so it look like some kind of memory leak.
This is how the view is instantiated:
displayProject: function(appType, appId, projectId) {
if (this.applicationDetailsModel === undefined ||
this.applicationDetailsModel.get('formType') !== appType ||
this.applicationDetailsModel.get('id') !== appId)
{
this.navigateTo = 'projects';
this.navigateToItem = projectId;
this.navigate('form/' + appType + '/' + appId, { trigger: true });
return;
}
var that = this;
require(['views/projectFormView'], function(ProjectFormView) {
var tooltips = that.tooltipsCollection
.findWhere({ form: 'project' })
.get('fields');
if (that.isCurrentView(that.projectFormView, appId, appType) === false) {
that.projectFormView = new ProjectFormView({
el: $content,
tooltips: tooltips,
projectScale: that.projectScale,
workTypes: that.workTypes
});
}
that.projectFormView.listenToOnce(that.projectScale, 'sync', that.projectFormView.render);
that.projectFormView.listenToOnce(that.workTypes, 'sync', that.projectFormView.render);
that.renderItem(that.projectFormView, that.projectsCollection, projectId, 'projects');
that.highlightItem('projects');
});
},
And the view. Notice the comment in SetValue
return ApplicantFormView.extend({
events: {
'change #newProject input': 'processProject',
'change #newProject select': 'processProject',
'change #newProject textarea': 'processProject',
},
template: JST['app/scripts/templates/projectForm.hbs'],
initialize: function (options) {
this.projectScale = options.projectScale;
this.workTypes = options.workTypes;
this.tooltips = options.tooltips;
},
render: function () {
Backbone.Validation.bind(this, {
selector: 'id'
});
this.$el.html(this.template(
{
project: this.model.attributes,
projectScale: this.projectScale.toJSON(),
workTypes: this.workTypes.toJSON(),
appType: profileModel.get('loadedAppType'),
appId: profileModel.get('applicationId')
}
));
this.$('.datepicker').datepicker({
endDate: 'today',
minViewMode: 1,
todayBtn: 'linked',
orientation: 'top auto',
calendarWeeks: true,
toggleActive: true,
format: 'MM yyyy',
autoclose: true
});
this.$('.datepicker').parent().removeClass('has-error');
this.$('.error-msg').hide();
this.$el.updatePolyfill();
this.revalidation();
return this;
},
processProject: function (event) {
this.setValue(event);
this.save();
},
setValue: function (event) {
//This is called on each input change as many times as many projects were previously opened.
event.preventDefault();
var $el = $(event.target),
id,
value;
if ($el.attr('type') === 'checkbox') {
id = $el.attr('id');
value = $el.is(':checked');
} else if ($el.attr('type') === 'radio') {
id = $el.attr('name');
value = $('input:radio[name ="' + id + '"]:checked').val();
} else {
id = $el.attr('id');
value = $el.val();
}
this.model.set(id, value);
Dispatcher.trigger('upd');
},
});
Do you have any tips what could be causing the memory leak?
Looks like all the views are attached to $content. Whenever you create a new view to this element, a new set of event listeners would be attached to this element.
Ideally you should remove existing views before creating new ones to free up memory using view's remove method.
If you can't do this for some reason and want to keep all view objects created in memory at the same time, they need to have their own elements to bind events to.
You can do this by removing el: $content,
This let's backbone create an element for each view.
Then do $content.append(view.el) after the view creation.
You'll have to detach these element from $content while creating new views.
I have simple python flask app where I send JSON data to my HTML and with goJS I display my graph which looks like this:
I want to make custom choices dropdown for users to edit node and link text. So far, I used this code to make nodes text selectable in dropdown list:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="{{url_for('static', filename='go.js')}}"></script>
<!-- custom text editors -->
<script src="{{url_for('static', filename='TextEditorSelectBox.js')}}"></script>
<script src="{{url_for('static', filename='TextEditorRadioButtons.js')}}"></script>
<script src="{{url_for('static', filename='DataInspector.js')}}"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="{{url_for('static', filename='DataInspector.css')}}" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script id="code">
var nodeChoices = ['choice 1', 'choice 2', 'choice 3', 'choice 4', 'choice 5'];
var linkChoices = ['link choice 1', 'link choice 2', 'link choice 3', 'link choice 4', 'link choice 5'];
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"textEditingTool.defaultTextEditor": window.TextEditorSelectBox,
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout()
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
}
else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
myDiagram.addDiagramListener("textEdited", function(e) {
console.log("Text is edited");
console.log(e);
//CHECK IF LINK,
//IF YES REMOVE THAT OPTION FROM LIST
});
myDiagram.addDiagramListener("SelectionDeleting", function(e) {
console.log("inside SelectionDeleting");
console.log(e);
//CHECK IF LINK,
//IF YES PUT THAT OPTION BACK IN OPTION LIST
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true, // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
textEditor: window.TextEditorRadioButtons,
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: nodeChoices
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial", { 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock, // the label text
{
textAlign: "center",
font: "12pt helvetica, arial, sans-serif",
margin: 4,
editable: true, // enable in-place editing
textEditor: window.TextEditorRadioButtons,
//choices: JSON.parse('{{ choices | tojson | safe}}')
choices: linkChoices
},
// editing the text automatically updates the model data
new go.Binding("text").makeTwoWay())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
console.log("graphDataString");
console.log(graphDataString);
myDiagram.model = go.Model.fromJson(graphDataString);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id=formWrapper style="padding: 30px;">
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</body>
</html>
And It looks like this:
It's all fine with node text, but I have problem with link text. I want when user selects one option that he can not use that option anymore on other link texts. Also I want to make when that link is deleted that that option is available again.
As you can see in code, I added 2 event listeners on my diagram (textEdited and SelectionDeleting) and they work fine when user is editing text or deleting something, but I don't know how to extract information about event object and its text.
I need to make sure it is link so I can remove or add that event object text in my choices list. Any help will be appreciated.
OK, let's assume that the list of choices for the link labels is held in the Model.modelData object. I'll name the property "choices", but of course you can use whatever name you like.
myDiagram.model.set(myDiagram.model.modelData, "choices", ["one", "two", "three"]);
Your Link template might look something like:
myDiagram.linkTemplate =
$(go.Link,
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" }),
$(go.TextBlock,
{
background: "white",
editable: true,
textEditor: window.TextEditorSelectBox, // defined in extensions/textEditorSelectBox.js
textEdited: function(tb, oldstr, newstr) {
var choices = tb.diagram.model.modelData.choices;
var idx = choices.indexOf(newstr);
if (idx >= 0 && oldstr !== newstr) {
console.log("removing choice " + idx + ": " + newstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.splice(idx, 1);
tb.diagram.model.set(tb.diagram.model.modelData, "choices", newchoices);
tb.editable = false; // don't allow choice again
}
}
},
new go.Binding("text"),
new go.Binding("choices").ofModel())
);
Note how the TextBlock.textEditor is defined to be a TextEditorSelectBox and the TextBlock.textEdited event handler is defined to set the modelData.choices property to be a new Array without the chosen string.
It also sets TextBlock.editable back to false so that the user cannot re-choose for that Link. That's one way to avoid problems with repeated edits; but you could implement your own policies. In retrospect I think the more likely policy would be to add the old value to and remove the new value from the modelData.choices Array.
Also, you'll want to implement a Model Changed listener that notices when Links have been removed from the model, so that you can add its choice back to the myDiagram.model.modelData.choices Array. In your Diagram initialization:
$(go.Diagram, . . .,
{
"ModelChanged": function(e) {
if (e.change === go.ChangedEvent.Remove && e.modelChange === "linkDataArray") {
var linkdata = e.oldValue;
var oldstr = linkdata.text;
if (!oldstr) return;
var choices = e.model.modelData.choices;
var idx = choices.indexOf(oldstr);
if (idx < 0) {
console.log("adding choice: " + oldstr);
var newchoices = Array.prototype.slice.call(choices);
newchoices.push(oldstr);
e.model.set(e.model.modelData, "choices", newchoices);
}
}
}
})
I have simple python flask app where I send JSON data to my HTML and with goJS I display my graph which looks like this:
Users can add new nodes and links, but what I want is that starting graph is locked so that users can not edit or delete any nodes or links from that starting graph. I just want that they can add new nodes and links and link it to starting graph nodes.
I really tried to search for this specific case but I haven't found what I am looking for. In documentation there are some options like disabling whole diagram or set it to read only, but that is not what I need. There are some mentions that you can make specific user permissions, but there is not any examples provided, so I need help.
Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>State Chart</title>
<meta name="description" content="A finite state machine chart with editable and interactive features." />
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.7.27/go.js"></script>
<script src="https://gojs.net/latest/extensions/TextEditorRadioButtons.js"></script>
<script src="https://gojs.net/latest/extensions/TextEditorSelectBox.js"></script>
<script src="https://gojs.net/latest/extensions/DataInspector.js"></script>
<link href="https://gojs.net/latest/extensions/DataInspector.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script id="code">
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
{
// start everything in the middle of the viewport
initialContentAlignment: go.Spot.Center,
// have mouse wheel events zoom in and out instead of scroll up and down
"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
// support double-click in background creating a new node
"clickCreatingTool.archetypeNodeData": { text: "new node" },
// enable undo & redo
"undoManager.isEnabled": true,
"layout": new go.ForceDirectedLayout()
});
// when the document is modified, add a "*" to the title and enable the "Save" button
myDiagram.addDiagramListener("Modified", function(e) {
var button = document.getElementById("SaveButton");
if (button) button.disabled = !myDiagram.isModified;
var idx = document.title.indexOf("*");
if (myDiagram.isModified) {
if (idx < 0) document.title += "*";
} else {
if (idx >= 0) document.title = document.title.substr(0, idx);
}
});
// define the Node template
myDiagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
// define the node's outer shape, which will surround the TextBlock
$(go.Shape, "RoundedRectangle",
{
parameter1: 20, // the corner has a large radius
fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
stroke: null,
portId: "", // this Shape is the Node's port, not the whole Node
fromLinkable: true, fromLinkableDuplicates: true,
toLinkable: true, toLinkableDuplicates: true,
cursor: "pointer"
}),
$(go.TextBlock,
{
font: "bold 11pt helvetica, bold arial, sans-serif",
editable: true // editing the text automatically updates the model data
//textEditor: window.TextEditorRadioButtons, // defined in textEditorRadioButtons.js
// this specific TextBlock has its own choices:
//choices: ['One', 'Two', 'Three', 'Four']
},
new go.Binding("text").makeTwoWay())
);
myDiagram.nodeTemplate.selectionAdornmentTemplate =
$(go.Adornment, "Spot",
$(go.Panel, "Auto",
$(go.Shape, { stroke: "dodgerblue", strokeWidth: 2, fill: null }),
$(go.Placeholder)
),
$(go.Panel, "Horizontal",
{ alignment: go.Spot.Top, alignmentFocus: go.Spot.Bottom },
$("Button",
{ click: editText }, // defined below, to support editing the text of the node
$(go.TextBlock, "t",
{ font: "bold 10pt sans-serif", desiredSize: new go.Size(15, 15), textAlign: "center" })
),
$("Button",
{ // drawLink is defined below, to support interactively drawing new links
click: drawLink, // click on Button and then click on target node
actionMove: drawLink // drag from Button to the target node
},
$(go.Shape,
{ geometryString: "M0 0 L8 0 8 12 14 12 M12 10 L14 12 12 14" })
),
$("Button",
{
actionMove: dragNewNode, // defined below, to support dragging from the button
_dragData: { text: "?????", color: "lightgray" }, // node data to copy
click: clickNewNode // defined below, to support a click on the button
},
$(go.Shape,
{ geometryString: "M0 0 L3 0 3 10 6 10 x F1 M6 6 L14 6 14 14 6 14z", fill: "gray" })
)
)
);
function editText(e, button) {
//console.log(e);
var node = button.part.adornedPart;
console.log("node");
//console.log(node);
e.diagram.commandHandler.editTextBlock(node.findObject("TEXTBLOCK"));
//$("#nodeText").val(node.findObject("TEXTBLOCK"));
}
function drawLink(e, button) {
var node = button.part.adornedPart;
var tool = e.diagram.toolManager.linkingTool;
tool.startObject = node.port;
e.diagram.currentTool = tool;
tool.doActivate();
}
// used by both clickNewNode and dragNewNode to create a node and a link
// from a given node to the new node
function createNodeAndLink(data, fromnode) {
var diagram = fromnode.diagram;
var model = diagram.model;
var nodedata = model.copyNodeData(data);
model.addNodeData(nodedata);
var newnode = diagram.findNodeForData(nodedata);
var linkdata = model.copyLinkData({});
model.setFromKeyForLinkData(linkdata, model.getKeyForNodeData(fromnode.data));
model.setToKeyForLinkData(linkdata, model.getKeyForNodeData(newnode.data));
model.addLinkData(linkdata);
diagram.select(newnode);
return newnode;
}
// the Button.click event handler, called when the user clicks the "N" button
function clickNewNode(e, button) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("Create Node and Link");
var fromnode = button.part.adornedPart;
var newnode = createNodeAndLink(button._dragData, fromnode);
newnode.location = new go.Point(fromnode.location.x + 200, fromnode.location.y);
e.diagram.commitTransaction("Create Node and Link");
}
// the Button.actionMove event handler, called when the user drags within the "N" button
function dragNewNode(e, button) {
var tool = e.diagram.toolManager.draggingTool;
if (tool.isBeyondDragSize()) {
var data = button._dragData;
if (!data) return;
e.diagram.startTransaction("button drag"); // see doDeactivate, below
var newnode = createNodeAndLink(data, button.part.adornedPart);
newnode.location = e.diagram.lastInput.documentPoint;
// don't commitTransaction here, but in tool.doDeactivate, after drag operation finished
// set tool.currentPart to a selected movable Part and then activate the DraggingTool
tool.currentPart = newnode;
e.diagram.currentTool = tool;
tool.doActivate();
}
}
// using dragNewNode also requires modifying the standard DraggingTool so that it
// only calls commitTransaction when dragNewNode started a "button drag" transaction;
// do this by overriding DraggingTool.doDeactivate:
var tool = myDiagram.toolManager.draggingTool;
tool.doDeactivate = function() {
// commit "button drag" transaction, if it is ongoing; see dragNewNode, above
if (tool.diagram.undoManager.nestedTransactionNames.elt(0) === "button drag") {
tool.diagram.commitTransaction();
}
go.DraggingTool.prototype.doDeactivate.call(tool); // call the base method
};
// replace the default Link template in the linkTemplateMap
myDiagram.linkTemplate =
$(go.Link, // the whole link panel
{
curve: go.Link.Bezier,
adjusting: go.Link.Stretch,
reshapable: true,
relinkableFrom: true,
relinkableTo: true,
toShortLength: 3
},
new go.Binding("points").makeTwoWay(),
new go.Binding("curviness"),
$(go.Shape, // the link shape
{ strokeWidth: 1.5 }),
$(go.Shape, // the arrowhead
{ toArrow: "standard", stroke: null }),
$(go.Panel, "Auto",
$(go.Shape, // the label background, which becomes transparent around the edges
{
fill: $(go.Brush, "Radial",
{ 0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)" }),
stroke: null
}),
$(go.TextBlock, "?????", // the label text
{
textAlign: "center",
font: "9pt helvetica, arial, sans-serif",
margin: 4,
editable: true // enable in-place editing
},
// editing the text automatically updates the model data
new go.Binding("text").makeTwoWay())
)
);
var inspector = new Inspector('myInspectorDiv', myDiagram,
{
// uncomment this line to only inspect the named properties below instead of all properties on each object:
// includesOwnProperties: false,
properties: {
"text": { },
// an example of specifying the type
"password": { show: Inspector.showIfPresent, type: 'password' },
// key would be automatically added for nodes, but we want to declare it read-only also:
"key": { readOnly: true, show: Inspector.showIfPresent },
// color would be automatically added for nodes, but we want to declare it a color also:
"color": { show: Inspector.showIfPresent, type: 'color' },
// Comments and LinkComments are not in any node or link data (yet), so we add them here:
"Comments": { show: Inspector.showIfNode },
"flag": { show: Inspector.showIfNode, type: 'checkbox' },
"LinkComments": { show: Inspector.showIfLink },
"isGroup": { readOnly: true, show: Inspector.showIfPresent }
}
});
// read in the JSON data from flask
loadGraphData();
}
function loadGraphData() {
var graphDataString = JSON.parse('{{ diagramData | tojson | safe}}');
console.log("graphDataString");
console.log(graphDataString);
myDiagram.model = go.Model.fromJson(graphDataString);
}
function saveGraphData(form, event) {
console.log("inside saveGraphData");
event.preventDefault();
document.getElementById("mySavedModel").value = myDiagram.model.toJson();
form.submit();
}
function zoomToFit(){
console.log("inside zoomToFit");
myDiagram.zoomToRect(myDiagram.documentBounds);
}
function zoomIn(){
console.log("inside zoomIn");
myDiagram.commandHandler.increaseZoom();
}
function zoomOut(){
console.log("inside zoomOut");
myDiagram.commandHandler.decreaseZoom();
}
</script>
</head>
<body onload="init()">
<div id=formWrapper style="padding: 30px;">
<form method="POST" action="http://localhost:5000/updateResultFile" name="updateResultFileForm"
id="updateResultFileForm"
onsubmit="saveGraphData(this, event);">
<div id="graphWrapper" style="margin-bottom: 15px;">
<div id="myDiagramDiv" style="border: solid 1px black; width: 100%; height: 800px;margin-bottom: 15px;"></div>
<div style="display: none;"><input id="mySavedModel" name="mySavedModel"></div>
<button class="btn btn-default" type="submit"> Save <i class="fa fa-save"> </i> </button>
</div>
</form>
<div id="myInspectorDiv">
</div>
<div>
<button class="btn btn-default" onclick="zoomToFit()"> Zoom to fit <i class="fa fa-search"> </i> </button>
<button class="btn btn-default" onclick="zoomIn()"> Zoom in <i class="fa fa-search-plus"> </i> </button>
<button class="btn btn-default" onclick="zoomOut()"> Zoom out <i class="fa fa-search-minus"> </i> </button>
</div>
</div>
</body>
</html>
Any help will be appreciated
Yes, that documentation page about user permissions, https://gojs.net/latest/intro/permissions.html, should give you an answer.
I'm guessing that you don't want to disable all deletions by setting Diagram.allowDelete to false, or to disable all in-place text editing by setting Diagram.allowTextEdit to false. Instead you probably want to prevent deleting or editing the original nodes and links but not any newly created nodes or links. Is that right?
If so, you can set or bind Part.deletable and Part.textEditable to false on all those original Nodes and Links. You can do that in an "InitialLayoutCompleted" DiagramEvent listener. For example in a Diagram initialization:
$(go.Diagram, . . .,
{ . . .,
"InitialLayoutCompleted": function(e) {
e.diagram.nodes.each(function(n) { n.deletable = false; n.textEditable = false; });
e.diagram.links.each(function(l) { l.deletable = false; l.textEditable = false; });
},
. . .
})
Of course you wouldn't need to set Link.textEditable to false if you didn't have any editable text labels in your Links, but you do seem to.
This problem is related to ExtJS's recently released D3 integrated package, and not original one.
If I run 'sencha app watch', it works fine, however it fails if it is ran with 'sencha app build'. This is what I'm shown:
ReferenceError: d3 is not defined
...pply(d,f)}}return e},getSvg:function(){return this.svg||(this.svg=d3.select(this...
which is clearly sencha's own code (seeing as I don't call d3.something anywhere in my code).
So my question boils down to: What are key differences between app watch and build? How would I go about debugging it (I can't even open the file that this error points to, as my text editor freezes if I try to)?
This is the code, where d3 is used (and no where else):
Ext.define('Dashboards.view.svgmap.SvgMap', {
extend: 'Ext.panel.Panel',
xtype: 'svgmap',
requires: ['Ext.d3.interaction.PanZoom', 'Ext.d3.*'],
controller: 'svgmap',
config: {
mapWidth: 800,
mapHeight: 600,
styling: [],
colorRegions: [],
},
statics: {
regionList: [],
},
layout: {
type: 'vbox',
align: 'stretch'
},
find: function(name) {
...
},
xml : function(url, callback) {
/*d3.xml function equivalent*/
...
},
initComponent: function() {
var me = this;
this.items = [{
xtype: 'd3-svg',
reference: 'svgContainer',
width: this.getMapWidth(),
height: this.getMapHeight(),
interactions: {
type: 'panzoom',
zoom: {
extent: [1, 4],
doubleTap: false,
mouseWheel: this.getEnableScroll()
}
},
listeners: {
scenesetup: function(component, scene) {
var regions = me.getColorRegions();
var width = me.getMapWidth();
var height = me.getMapHeight();
var styling = me.getStyling();
var svg = scene.append("svg")
.attr("width", width)
.attr("height", height)
.attr("preserveAspectRatio", "xMinYMin meet")
.classed("svg-content", true);
me.xml(Dashboards.Properties.serviceRootUrl + "/LietuvosZemelapisNoStyle.svg",
function(error, documentFragment) {
if (error) {
console.log(error);
return;
}
var svgNode = documentFragment
.getElementsByTagName("svg")[0];
svg.node().appendChild(svgNode);
for (var i = 0; i < regions.length; i++) {
var r = regions[i];
var sel = svg.select('#' + me.find(r.code).id);
sel.attr('color', r.color)
.attr('fill', r.color)
.attr('fill-opacity', 1);
}
});
}
}
}];
this.callParent(arguments);
}
});
After running into same issue again, and stumbling onto my own old unanswered question, I suppose I should write down what fixed it this time.
In app.json, "js" array property was missing an entry
{
"path": "${framework.dir}/build/ext-all-rtl-debug.js"
}
I'm trying to force a vector layer in OpenLayers 3 to periodically reload its data from a GeoJSON source. The GeoJSON source changes from time to time.
After many different attempts using different methods, I think this is the closest I've managed to get so far:
$(document).ready(function(){
map.once("postcompose", function(){
//start refreshing each 3 seconds
window.setInterval(function(){
var source = testLayer.getSource();
var params = source.getParams();
params.t = new Date().getMilliseconds();
source.updateParams(params);
}, 3000);
});
});
However this is giving me the following (every 3 seconds of course):
Uncaught TypeError: source.getParams is not a function
Here's all of the other code I'm using to load and display the GeoJSON file. The other layers in the map are loaded in the same way:
var testSource = new ol.source.Vector({
url: '/js/geojson/testfile.geojson',
format: new ol.format.GeoJSON()
});
...
window.testLayer = new ol.layer.Vector({
source: testSource,
style: function(feature, resolution) {
var style = new ol.style.Style({
fill: new ol.style.Fill({color: '#ffffff'}),
stroke: new ol.style.Stroke({color: 'black', width: 1}),
text: new ol.style.Text({
font: '12px Verdana',
text: feature.get('testvalue'),
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.1)'
}),
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 1)',
width: 1
})
})
});
return style
}
});
...
var olMapDiv = document.getElementById('olmap');
var map = new ol.Map({
layers: [paddocksLayer,zonesLayer,structuresLayer,roadsLayer,testLayer],
interactions: ol.interaction.defaults({
altShiftDragRotate: false,
dragPan: false,
rotate: false
}).extend([new ol.interaction.DragPan({kinetic: null})]),
target: olMapDiv,
view: view
});
view.setZoom(1);
view.setCenter([0,0]);
I read somewhere else that getParams simply doesn't work on vector layers, so this error wasn't entirely unexpected.
Is there an alternative method which will reload (not just re-render) a vector layer?
Thanks in advance. I must also apologise in advance as I'll need really specific guidance with this - I'm very new to JS and OpenLayers.
Gareth
(fiddle)
You can achieve this with a custom AJAX loader (when you pass url to ol.source.Vector this is AJAX as well). Then you can reload your JSON file as you will.
You can use any AJAX library you want but this is as simple as:
var utils = {
refreshGeoJson: function(url, source) {
this.getJson(url).when({
ready: function(response) {
var format = new ol.format.GeoJSON();
var features = format.readFeatures(response, {
featureProjection: 'EPSG:3857'
});
source.addFeatures(features);
source.refresh();
}
});
},
getJson: function(url) {
var xhr = new XMLHttpRequest(),
when = {},
onload = function() {
if (xhr.status === 200) {
when.ready.call(undefined, JSON.parse(xhr.response));
}
},
onerror = function() {
console.info('Cannot XHR ' + JSON.stringify(url));
};
xhr.open('GET', url, true);
xhr.setRequestHeader('Accept','application/json');
xhr.onload = onload;
xhr.onerror = onerror;
xhr.send(null);
return {
when: function(obj) { when.ready = obj.ready; }
};
}
};