Gerolmed
Gerolmed

Reputation: 403

jQuery UI draggable not bound to parent bounds

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. enter image description here

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

Answers (1)

Twisty
Twisty

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

Related Questions