Reputation: 403
I have created a js-based node editor. I have a div element I use as a parent for the nodes. Although I define the containment to parent you can move the nodes outside of the parents bounds. You can not move it lower than the last document element though.
The important html code is:
<button type="button" class="btn btn-primary" onclick="sendData(fetchData())">Send data</button>
<div id="node-container" class="node-container" style="height: 500px; width: 90%">
<svg id="svg"/>
</div>
<button type="button" class="btn btn-primary" onclick="sendData(fetchData())">Send data</button>
And the js for the draggable is:
$(this.domElement).draggable({
containment: 'parent',
cancel: '.connection,.output,.data',
drag: function(event, ui){
that.updatePosition();
}
});
Maybe I did something wrong with the parenting so here it is as well:
this.domElement = document.createElement('div');
document.getElementById('node-container').appendChild(this.domElement);
I haven't worked with JavaScript too much and am still learning!
EDIT: Here is the definition of the node-container:
<div id="node-container" class="node-container" style="height: 500px; width: 90%">
</div>
Complete StoryNode.js: https://hastebin.com/kiyoyekoce.js
// ===================================================================
// Node class. Represents a javascript version of the java Node class
// ===================================================================
var nodes = []; //Have second reduced node version running, maybe as a method in the Node? Call simple for loop in web-link
function StoryNode(name) {
this.name = name;
let that = this;
nodes.push(this);
// Add our graphical representation
this.domElement = document.createElement('div');
document.getElementById('node-container').appendChild(this.domElement);
this.domElement.classList.add('node');
this.domElement.classList.add('container-fixed');
this.domElement.setAttribute('title', name.replace("Node",""));
let remDom = document.createElement('div');
remDom.classList.add('delete-node');
remDom.innerHTML ='<i class="fa fa-trash" aria-hidden="true"/>';
this.domElement.appendChild(remDom);
remDom.onclick = function (e) {
that.detachAll();
for( let i = 0; i < nodes.length; i++){
if ( nodes[i] === that) {
nodes.splice(i, 1);
}
}
that.domElement.parentElement.removeChild(that.domElement);
};
// Add the node body
// Directories
var innerDomGroup = document.createElement('div');
innerDomGroup.classList.add('row');
this.domElement.appendChild(innerDomGroup);
// Outputs
this.outputDomGroup = document.createElement('div');
this.outputDomGroup.classList.add('col-fixed-small-1');
innerDomGroup.appendChild(this.outputDomGroup);
// Inner Data
this.dataDomGroup = document.createElement('div');
this.dataDomGroup.classList.add('col-fixed-medium-1');
innerDomGroup.appendChild(this.dataDomGroup);
// Inputs
this.inputDomGroup = document.createElement('div');
this.inputDomGroup.classList.add('col-fixed-small-1');
innerDomGroup.appendChild(this.inputDomGroup);
//Node data
this.inputs = [];
this.data = [];
this.outputs = [];
}
StoryNode.prototype.addInput = function(name, inputType){
let input = new NodeInput(name, inputType);
input.node = this;
this.inputs.push(input);
this.inputDomGroup.appendChild(input.domElement);
return input;
};
StoryNode.prototype.addOutput = function(name, outputType){
let output = new NodeOutput(name, outputType);
output.node = this;
this.outputs.push(output);
this.outputDomGroup.appendChild(output.domElement);
return output;
};
StoryNode.prototype.addData = function(name, outputType){
let data = new NodeData(name, outputType);
data.node = this;
this.data.push(data);
this.dataDomGroup.appendChild(data.domElement);
return data;
};
StoryNode.prototype.ownsInput = function(input){
for(let i = 0; i < this.inputs.length; i++){
if(this.inputs[i] === input)
return true;
}
return false;
};
Node.prototype.ownsOutput = function(output){
for(let i = 0; i < this.outputs.length; i++){
if(this.outputs[i] === output)
return true;
}
return false;
};
StoryNode.prototype.detachAll = function(output){
for(let item of this.inputs){
if(item.output)
item.output.detachInput();
}
for(let item of this.outputs){
if(item.input)
item.detachInput();
}
};
StoryNode.prototype.updatePosition = function(){
for(let j = 0; j < this.outputs.length; j++){
if(this.outputs[j].input != null){
let oP = this.outputs[j].getOutputPoint();
let iP = this.outputs[j].input.getAttachPoint();
let pStr = this.createPath(iP, oP);
this.outputs[j].input.path.setAttributeNS(null, 'd', pStr);
}
}
for(let j = 0; j < this.inputs.length; j++){
if(this.inputs[j].output != null){
let iP = this.inputs[j].getAttachPoint();
let oP = this.inputs[j].output.getOutputPoint();
let pStr = this.createPath(iP, oP);
this.inputs[j].path.setAttributeNS(null, 'd', pStr);
}
}
};
StoryNode.prototype.createPath = function(a, b){
let diff = {
x: b.x - a.x,
y: b.y - a.y
};
let pathStr = 'M' + a.x + ',' + a.y + ' ';
pathStr += 'C';
pathStr += a.x + diff.x / 3 * 2 + ',' + a.y + ' ';
pathStr += a.x + diff.x / 3 + ',' + b.y + ' ';
pathStr += b.x + ',' + b.y;
return pathStr;
};
StoryNode.prototype.moveTo = function(point){
this.domElement.style.top = point.y + 'px';
this.domElement.style.left = point.x + 'px';
this.updatePosition();
};
StoryNode.prototype.initUI = function(){
let that = this;
// Make draggable
// noinspection JSValidateTypes
$(this.domElement).draggable({
containment: 'parent',
cancel: '.connection,.output,.data',
drag: function(event, ui){
that.updatePosition();
}
});
//Mak node design smaller as it holds no data inputs
if(this.data.length === 0) {
this.domElement.classList.remove('container-fixed');
this.domElement.classList.add('container-fixed-min');
this.dataDomGroup.classList.remove('col-fixed-medium-1');
this.dataDomGroup.classList.add('col-fixed-medium-1-min');
}
// Fix positioning
this.domElement.style.position = 'absolute';
document.body.appendChild(this.domElement);
// Update Visual
this.updatePosition();
};
// Node 1
let node = new StoryNode('Page');
// Move to initial positions
node.moveTo({x: 150, y: 20});
// Connect Nodes
// Add to DOM
node.initUI();
body{
background-color: #101010;
color: #d4d4d4;
font-family: sans-serif;
}
.node:before{
content:attr(title) " ";
display: block;
border-top-left-radius:.75em;
border-top-right-radius:.75em;
background-color:#6e6e6e;
padding:0.1em .3em 0em;
margin:-.1em -.3em 0.2em;
}
.node{
background-color: #4e4e4e;
border-radius: .75em;
display: inline-block;
padding:0.1em .3em .25em;
position:absolute;
cursor: move;
}
.connection:after{
position:absolute;
border:solid 1px #dedede;
background-color: #2e2e2e;
width:0.5em;
height:0.5em;
border-radius:0.5em;
}
.delete-node{
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-size: 1rem;
line-height: 1.5;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
position:absolute;
color: #b8b8b8;
background-color: transparent;
border-radius:0.5em;
width:22px;
height:22px;
cursor: default;
top: 3px;
right: 8px;
}
.connection.filled:after{
border:solid 1px transparent;
}
.connection:hover:after{
border-color:red;
}
.output{
left: .1em;
top:.1em;
cursor: pointer;
}
.input{
left: 2em;
top: .1em;
cursor: pointer;
}
.connection{
width:100%;
position:relative;
padding-right:0.5em;
cursor:pointer;
}
.connection:after{
content:"";
right:0em;
top:0.25em;
}
.filled-node:after{
background-color: #d4d9de;
}
.empty-node:after{
border: 2px solid #d4d9de;
}
.filled-boolean:after{
background-color: #4f8fde;
}
.empty-boolean:after{
border: 2px solid #4f8fde;
}
svg{
position:absolute;
top:0px;
left:0px;
z-index:-100;
width:100%;
height:100%;
}
textarea {
resize: none;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}
.round-edge {
border-radius: 15px;
}
.container-fixed {
width: 200px !important;
}
.container-fixed-min {
width: 100px !important;
}
.col-fixed-small-1 {
width: 5px;
}
.col-fixed-small-2 {
width: 5px;
}
.col-fixed-medium-1 {
width: 180px;
padding-left: 15px;
margin-right: -12px;
}
.col-fixed-medium-1-min {
width: 80px;
padding-left: 15px;
margin-right: -12px;
}
.node-container {
background-color: #2e2e2e;
border-radius: 10px;
height: 500px;
width: 90%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<div id="node-container" class="node-container">
<svg id="svg"/>
</div>
<button type="button" class="btn btn-primary">Send data</button>
Upvotes: 3
Views: 669
Reputation: 30883
I suspect that the issue is when you append the element. If it's not appended when you initialize draggable, it may not understand the parent
relationship.
Consider the following example:
$(function() {
function addNode(title, content, tObj) {
if (title == undefined) {
title = "New Node";
}
if (content == undefined) {
content = "<p></p>";
}
if (tObj == undefined) {
tObj = $("#node-container");
}
var node = $("<div>", {
class: "node-element"
}).appendTo(tObj);
$("<div>", {
class: "node-title"
}).html(title).appendTo(node);
$("<span>", {
class: "btn btn-delete-node"
}).html("x").click(function() {
var r = confirm("Are you sure you want to delete this node?");
if (r) {
$(this).closest(".node-element").remove();
}
}).appendTo($(".node-title", node));
$("<div>", {
class: "node-content"
}).html(content).appendTo(node);
node.draggable({
containment: 'parent',
handle: ".node-title",
cancel: '.connection,.output,.data',
stop: function(e, ui) {
// update position
}
});
}
$(".add-node-btn").click(function() {
addNode();
});
});
body {
background-color: #000;
font-family: "arial", san-seriff;
}
.node-container {
background-color: #2a2a2a;
border-radius: 10px;
margin: 3px;
}
.btn-primary {
border: 0;
border-radius: 6px;
padding: 4px;
background-color: #0000ff;
color: #FFF;
}
.node-container .node-element {
background-color: #212121;
border-radius: 6px;
border: 0;
width: 125px;
height: 75px;
}
.node-container .node-element .node-title {
background-color: #484848;
border-radius: 6px 6px 0 0;
border: 0;
min-height: 1em;
color: #fff;
text-align: center;
}
.node-container .node-element .node-title .btn-delete-node {
float: right;
padding-right: 6px;
cursor: pointer;
}
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<button type="button" class="btn btn-primary">Send data</button> <button type="button" class="btn btn-primary add-node-btn">Add Node</button>
<div id="node-container" class="node-container" style="height: 500px; width: 90%">
<svg id="svg" />
</div>
<button type="button" class="btn btn-primary">Send data</button>
Since your example code is not a Minimal, Complete, or Verifiable example, it's difficult to determine when or how your elements are added versus initialized.
Hope that helps.
Upvotes: 1