title: jQuery UI draggable not bound to parent bounds
body: <p>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.
  <a href="https://i.stack.imgur.com/FfS1Y.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/FfS1Y.png" alt="enter image description here"></a></p>
  
  <p>The important html code is:</p>
  
  <pre><code>    &lt;button type="button" class="btn btn-primary" onclick="sendData(fetchData())"&gt;Send data&lt;/button&gt;
      &lt;div id="node-container" class="node-container" style="height: 500px; width: 90%"&gt;
          &lt;svg id="svg"/&gt;
      &lt;/div&gt;
      &lt;button type="button" class="btn btn-primary" onclick="sendData(fetchData())"&gt;Send data&lt;/button&gt;
  </code></pre>
  
  <p>And the js for the draggable is:</p>
  
  <pre><code>$(this.domElement).draggable({
      containment: 'parent',
      cancel: '.connection,.output,.data',
      drag: function(event, ui){
          that.updatePosition();
      }
  });
  </code></pre>
  
  <p>Maybe I did something wrong with the parenting so here it is as well:</p>
  
  <pre><code>this.domElement = document.createElement('div');
  document.getElementById('node-container').appendChild(this.domElement);
  </code></pre>
  
  <p>I haven't worked with JavaScript too much and am still learning!</p>
  
  <p>EDIT: Here is the definition of the node-container:</p>
  
  <pre><code>&lt;div id="node-container" class="node-container" style="height: 500px; width: 90%"&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>Complete StoryNode.js:
  <a href="https://hastebin.com/kiyoyekoce.js" rel="nofollow noreferrer">https://hastebin.com/kiyoyekoce.js</a></p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>// ===================================================================
  // 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 ='&lt;i class="fa fa-trash" aria-hidden="true"/&gt;';
      this.domElement.appendChild(remDom);
  
      remDom.onclick = function (e) {
  
          that.detachAll();
  
          for( let i = 0; i &lt; 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 &lt; this.inputs.length; i++){
          if(this.inputs[i] === input)
              return true;
      }
      return false;
  };
  
  Node.prototype.ownsOutput = function(output){
      for(let i = 0; i &lt; 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 &lt; 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 &lt; 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();</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>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%;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"&gt;&lt;/script&gt;
          &lt;script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"&gt;&lt;/script&gt;
  
  &lt;div id="node-container" class="node-container"&gt;
      &lt;svg id="svg"/&gt;
  &lt;/div&gt;
          &lt;button type="button" class="btn btn-primary"&gt;Send data&lt;/button&gt;</code></pre>
  </div>
  </div>
  </p>