Joint JS Plus demo with custom HTML and Navigator

by Roman Bruckner

HTML

<link rel="stylesheet" href="https://resources.jointjs.com/docs/rappid/v3.6/css/lib/rappid.min.css">
<script src="https://resources.jointjs.com/docs/rappid/v3.6/js/lib/lodash.min.js"></script>
<script src="https://resources.jointjs.com/docs/rappid/v3.6/js/lib/backbone.min.js"></script>
<script src="https://resources.jointjs.com/docs/rappid/v3.6/js/lib/lodash.min.js"></script>
<script src="https://resources.jointjs.com/docs/rappid/v3.6/js/lib/rappid.min.js"></script>
<div id="paper"></div>
<div class="toolbar">
  <span id="zoom-out" class="toolbar-button">Zoom Out</span>
  <span id="zoom-in" class="toolbar-button">Zoom In</span>
  <span id="reset" class="toolbar-button">Reset</span>
</div>
<div id="navigator"></div>

CSS

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
}
body {
    display: flex;
    justify-content: center;
    align-items: center;
    overflow-y: hidden;
}
#paper {
    border: 1px solid #E2E2E2;
    background-color: #F3F7F6;
    position: absolute;
    inset: 0;
}
#navigator {
  right: 0;
  position: absolute;
}
/* JointJS HTML Element */
.html-element {
    box-sizing: border-box;
    font-family: sans-serif;
    box-shadow: 4px 4px 4px -1px rgba(0,0,0,0.18);
    padding: 8px 16px;
    background-color: #FCFCFC;
}
.html-element:after {
    content: ' ';
    background: #6C6C6C;
    height: 8px;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
}
.html-element:before {
    content: ' ';
    position: absolute;
    top: 24px;
    right: 16px;
    width: 20px;
    height: 20px;
    background-position: 50% 50%;
    background-repeat: no-repeat;
    background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDBWMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTEuOTkgMkM2LjQ3IDIgMiA2LjQ4IDIgMTJzNC40NyAxMCA5Ljk5IDEwQzE3LjUyIDIyIDIyIDE3LjUyIDIyIDEyUzE3LjUyIDIgMTEuOTkgMnpNMTIgMjBjLTQuNDIgMC04LTMuNTgtOC04czMuNTgtOCA4LTggOCAzLjU4IDggOC0zLjU4IDgtOCA4em0uNS0xM0gxMXY2bDUuMjUgMy4xNS43NS0xLjIzLTQuNS0yLjY3eiIvPjwvc3ZnPg==);
}
.html-element[data-state="done"]:after {
    background:#4666E5;
}
.html-element[data-state="done"]:before {
    background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDBWMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTQuNTktMTIuNDJMMTAgMTQuMTdsLTIuNTktMi41OEw2IDEzbDQgNCA4LTh6Ii8+PC9zdmc+);
}
.html-element[data-state="at-risk"]:after...

JavaScript

(function(joint, util, V) {

  var Element = joint.dia.Element;
  var ElementView = joint.dia.ElementView;

  Element.define('html.Element', {
    size: {
      width: 250,
      height: 228
    },
    ports: { // added ports
      groups: {
        inPorts: {
          position: {
            name: 'top'
          },
          attrs: {
            text: {
              fill: '#000000'
            },
            circle: {
              fill: '#00ff00',
              stroke: '#000000',
              magnet: true
            }
          },
          z: 0
        },
        outPorts: {
          attrs: {
            text: {
              fill: '#000000'
            },
            circle: {
              fill: '#ff0000',
              stroke: '#000000',
              magnet: true
            }
          },
          position: 'right',
          position: 'bottom',
          label: {
            position: 'outside'
          },
          z: 0
        }
      },
      items: [{
          id: 'in',
          group: 'inPorts',
          //attrs: { text: { text: 'in1' }}
        },
        {
          id: 'out',
          group: 'outPorts',
          //attrs: { text: { text: 'out' }}
        }
      ]
    },
    fields: {
      header: 'Task',
      name: '',
      resource: '',
      state: ''
    },
    attrs: {
      placeholder: {
        width: 'calc(w)',
        height: 'calc(h)',
        fill: 'transparent',
        stroke: '#D4D4D4'
      }
    }
  }, {
    markup: [{
      tagName: 'rect',
      selector: 'placeholder'
    }],
    htmlMarkup: [{
      tagName: 'div',
      className: 'html-element',
      selector: 'htmlRoot',
      groupSelector: 'field',
      style: {
        'position': 'absolute',
        'pointer-events': 'auto',
        'user-select': 'none',
        'box-sizing': 'border-box'
      },
      attributes: {
        'data-attribute': 'state'
      },
      children: [{
        tagName: 'label',
        className: 'html-element-header',
      ...