Search code examples
javascriptpolymerweb-component

Polymer 1.0 Trying to make a splitter which works like core-splitter and can be called iron-splitter


I am trying to make one of elements named as core-splitter which is deprecated in 1.0 because this plays a key role in our project.

If you are not aware of what core-splitter does i can provide a short description. core-splitter provides a split bar and dragging on the split bar will resize the sibling element. Usually you would want to use core-splitter along with flex layout so that the other sibling element can be flexible.Unfortunately this works for only Polymer 0.5 but this got deprecated in 1.0. Here is the demo in Polymer 0.5 version. Demo for core-splitter in Polymer 0.5

I am able to do that in normal javascript but unable to make it work in Polymer 1.0.

Here is a fiddle

<link rel="import"       href="https://cdn.rawgit.com/Polymer/polymer/master/polymer.html">
    <dom-module id="splitter-element">
      <template>
        <div id="div1" class="split">Bacon ipsum dolor amet beef ribs         meatloaf picanha pork loin pork chop rump pig sausage bacon shank boudin beef fatback. Pork loin turducken t-bone chicken.</div>
        <div class="gutter" style="width: 10px;"></div>
        <div id="div2" class="split">Brisket andouille cow ball tip. Ham ground round short loin tri-tip ribeye t-bone boudin, pork loin turkey drumstick tongue pork chop. Kielbasa doner picanha turducken, swine bacon shank pastrami andouille.</div>
      </template>
     <style>
    .gutter {
       background-color: #eee;
       background-image: url('https://ssl.gstatic.com/ui/v1/icons/mail/grippy_large.png');
       background-repeat: no-repeat;
       background-position: 50%;
       cursor: move;
       cursor: grab;
       cursor: -moz-grab;
       cursor: -webkit-grab;
    }
  .split, .gutter {
      height: 100%;
      float: left;
  }
.gutter:active {
    cursor: grabbing;
    cursor: -moz-grabbing;
    cursor: -webkit-grabbing;
 }
.split {
    box-sizing: border-box;
    padding: 20px;
    overflow-y: scroll;
    overflow-x: hidden;
}
  </style>
  <script>
  Polymer({
    is: 'splitter-element',
    properties:{
      "dragging":{
       Value:false
     }
    },
    "split":function(ids, options){
    options = typeof options !== 'undefined' ?  options : {};

    if (!options.gutterWidth) options.gutterWidth = 10;
    if (!options.minWidth) options.minWidth = 100;
    if (!options.snapOffset) options.snapOffset = 30;

  // Event listeners for drag events, bound to a pair object.
  // Save the pair's left position and width when dragging starts.
  // Prevent selection on start and re-enable it when done.

    var startDragging = function (e) {
        console.log('start');
        e.preventDefault();

         this.dragging = true;

        this.width = this.left.clientWidth + this.right.clientWidth + options.gutterWidth;
        this.x = this.left.getBoundingClientRect().left;

        this.left.addEventListener('selectstart', preventSelection);
        this.left.addEventListener('dragstart', preventSelection);
        this.right.addEventListener('selectstart', preventSelection);
        this.right.addEventListener('dragstart', preventSelection);

        this.left.style.userSelect = 'none';
        this.left.style.webkitUserSelect = 'none';
        this.left.style.MozUserSelect = 'none';

        this.right.style.userSelect = 'none';
        this.right.style.webkitUserSelect = 'none';
        this.right.style.MozUserSelect = 'none';

        if (options.onDragStart) {
            options.onDragStart();
        }
    },

    stopDragging = function () {
        this.dragging = false;

        this.left.removeEventListener('selectstart', preventSelection);
        this.left.removeEventListener('dragstart', preventSelection);
        this.right.removeEventListener('selectstart', preventSelection);
        this.right.removeEventListener('dragstart', preventSelection);

        this.left.style.userSelect = '';
        this.left.style.webkitUserSelect = '';
        this.left.style.MozUserSelect = '';

        this.right.style.userSelect = '';
        this.right.style.webkitUserSelect = '';
        this.right.style.MozUserSelect = '';

        if (options.onDragEnd) {
            options.onDragEnd();
        }
    },

    drag = function (e) {
        if (!this.dragging) return;

        // Get the relative position of the event from the left side of the
        // pair.

        var offsetX = e.clientX - this.x;

        // If within snapOffset of min or max, set offset to min or max

        if (offsetX <=  this.leftMin + options.snapOffset) {
            offsetX = this.leftMin;
        } else if (offsetX >= this.width - this.rightMin - options.snapOffset) {
            offsetX = this.width - this.rightMin;
        }

        // Left width is the same as offset. Right width is total width - left width.

        this.left.style.width = 'calc(' + (offsetX / this.width * 100) + '% - ' + options.gutterWidth / 2 + 'px)';
        this.right.style.width = 'calc(' + (100 - (offsetX / this.width * 100)) + '% - ' + options.gutterWidth / 2 + 'px)';

        if (options.onDrag) {
            options.onDrag();
        }
    },

    preventSelection = function () { return false; },

    // Given a list of DOM element ids and a list of percentage widths,
    // assign each element a width allowing for a gutter between each
    // pair. The number of gutters is ids.length - 1, and the total gutter
    // width is gutterWidth * (ids.length - 1). Before calculating
    // each width, subtract the total gutter width for the parent width.

    parent = document.getElementById(ids[0]).parentNode;

if (!options.widths) {
    var percent = 100 / ids.length;

    options.widths = [];

    for (var i = 0; i < ids.length; i++) {
        options.widths.push(percent);
    };
}

if (!Array.isArray(options.minWidth)) {
    var minWidths = [];

    for (var i = 0; i < ids.length; i++) {
        minWidths.push(options.minWidth);
    };

    options.minWidth = minWidths;
}

for (var i = 0; i < ids.length; i++) {
    var el = document.getElementById(ids[i]);

    if (i > 0) {
        var pair = {
                left: document.getElementById(ids[i - 1]),
                right: el,
                leftMin: options.minWidth[i - 1],
                rightMin: options.minWidth[i],
                dragging: false,
                parent: parent
            },
            gutter = document.createElement('div');

        gutter.className = 'gutter';
        gutter.style.width = options.gutterWidth + 'px';

        gutter.addEventListener('mousedown', startDragging.bind(pair));

        parent.addEventListener('mouseup', stopDragging.bind(pair));
        parent.addEventListener('mousemove', drag.bind(pair));
        parent.addEventListener('mouseleave', stopDragging.bind(pair));

        parent.insertBefore(gutter, el);

        pair.gutter = gutter;
    }

    el.style.width = 'calc(' + options.widths[i] + '% - ' + options.gutterWidth / 2 + 'px)';
}
  },
  "ready":function(){
      this.split(['div1', 'div2'], {
        widths: [25, 75],
        minWidth: 200
    });
  }
});
</script>
</dom-module>
<splitter-element>
</splitter-element>

Solution

  • Update: I upgraded a fork of core-splitter to be compatible with Polymer 1.0 http://github.com/JC-Orozco/iron-splitter

    Answer: The problem is that a polymer element must address Local DOM instead of the regular DOM. That is achieved using this.$[] instead of document.getElementById() and also Polymer.dom(this.root) instead of parent. I was able to execute the code by making the next changes accordingly:

    // parent = document.getElementById(ids[0]).parentNode;
    parent = this.$[ids[0]].parentNode;
    
    // var el = document.getElementById(ids[i]);
    var el = this.$[ids[i]];
    
    // left: document.getElementById(ids[i - 1]),
    left: this.$[ids[i - 1]],
    
    //parent.insertBefore(gutter, el);
    Polymer.dom(this.root).insertBefore(gutter, el);
    

    I also commented the gutter div in the template section because it was being duplicated when a gutter is also created in the javascript code:

    <!-- <div id="gutter1" class="gutter" style="width: 10px;"></div> -->
    

    I used a local server with polymer 1.0 installed from _https://github.com/Polymer/polymer and the next two files inside a splitter-element directory.

    demo.html:

    <!DOCTYPE html>
    <html>
    <head>
      <title>splitter-element</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    
      <script src="../webcomponentsjs/webcomponents-lite.js"></script>
    
      <link rel="import" href="splitter-element.html">
    
      <style>
        .container {
          height: 240px;
          border: 4px solid #aaa;
        }
      </style>
    </head>
    <body>
      <div class="container" horizontal="" layout="">
        <splitter-element direction="left" minsize="128px"></core-splitter>
      </div>
    
    </body>
    </html>
    

    And splitter-element.html

    <link rel="import"       href="../polymer/polymer.html">
    <dom-module id="splitter-element">
      <template>
         <div id="div1" class="split">Bacon ipsum dolor amet beef ribs         meatloaf picanha pork loin pork chop rump pig sausage bacon shank boudin beef fatback. Pork loin turducken t-bone chicken.</div>
         <!-- <div id="gutter1" class="gutter" style="width: 10px;"></div> -->
         <div id="div2" class="split">Brisket andouille cow ball tip. Ham ground round short loin tri-tip ribeye t-bone boudin, pork loin turkey drumstick tongue pork chop. Kielbasa doner picanha turducken, swine bacon shank pastrami andouille.</div>
      </template>
      <style>
        .gutter {
          background-color: #eee;
          background-image: url(handle.svg);
          background-repeat: no-repeat;
          background-position: 50%;
          cursor: move;
          cursor: grab;
          cursor: -moz-grab;
          cursor: -webkit-grab;
        }
        .split, .gutter {
          height: 100%;
          float: left;
        }
        .gutter:active {
          cursor: grabbing;
          cursor: -moz-grabbing;
          cursor: -webkit-grabbing;
        }
        .split {
          box-sizing: border-box;
          padding: 20px;
          overflow-y: scroll;
          overflow-x: hidden;
        }
      </style>
      <script>
        Polymer({
          is: 'splitter-element',
          properties:{
            "dragging":{
             Value:false
            }
          },
          "split":function(ids, options){
            options = typeof options !== 'undefined' ?  options : {};
    
            if (!options.gutterWidth) options.gutterWidth = 10;
            if (!options.minWidth) options.minWidth = 100;
            if (!options.snapOffset) options.snapOffset = 30;
    
            // Event listeners for drag events, bound to a pair object.
            // Save the pair's left position and width when dragging starts.
            // Prevent selection on start and re-enable it when done.
    
            var startDragging = function (e) {
            console.log('start');
            e.preventDefault();
    
            this.dragging = true;
    
            this.width = this.left.clientWidth + this.right.clientWidth + options.gutterWidth;
            this.x = this.left.getBoundingClientRect().left;
    
            this.left.addEventListener('selectstart', preventSelection);
            this.left.addEventListener('dragstart', preventSelection);
            this.right.addEventListener('selectstart', preventSelection);
            this.right.addEventListener('dragstart', preventSelection);
    
            this.left.style.userSelect = 'none';
            this.left.style.webkitUserSelect = 'none';
            this.left.style.MozUserSelect = 'none';
    
            this.right.style.userSelect = 'none';
            this.right.style.webkitUserSelect = 'none';
            this.right.style.MozUserSelect = 'none';
    
            if (options.onDragStart) {
            options.onDragStart();
            }
          },
    
          stopDragging = function () {
            this.dragging = false;
    
            this.left.removeEventListener('selectstart', preventSelection);
            this.left.removeEventListener('dragstart', preventSelection);
            this.right.removeEventListener('selectstart', preventSelection);
            this.right.removeEventListener('dragstart', preventSelection);
    
            this.left.style.userSelect = '';
            this.left.style.webkitUserSelect = '';
            this.left.style.MozUserSelect = '';
    
            this.right.style.userSelect = '';
            this.right.style.webkitUserSelect = '';
            this.right.style.MozUserSelect = '';
    
            if (options.onDragEnd) {
            options.onDragEnd();
            }
          },
    
          drag = function (e) {
            if (!this.dragging) return;
    
            // Get the relative position of the event from the left side of the
            // pair.
    
            var offsetX = e.clientX - this.x;
    
            // If within snapOffset of min or max, set offset to min or max
    
            if (offsetX <=  this.leftMin + options.snapOffset) {
            offsetX = this.leftMin;
            } else if (offsetX >= this.width - this.rightMin - options.snapOffset) {
            offsetX = this.width - this.rightMin;
            }
    
            // Left width is the same as offset. Right width is total width - left width.
    
            this.left.style.width = 'calc(' + (offsetX / this.width * 100) + '% - ' + options.gutterWidth / 2 + 'px)';
            this.right.style.width = 'calc(' + (100 - (offsetX / this.width * 100)) + '% - ' + options.gutterWidth / 2 + 'px)';
    
            if (options.onDrag) {
            options.onDrag();
            }
          },
    
          preventSelection = function () { return false; },
    
          // Given a list of DOM element ids and a list of percentage widths,
          // assign each element a width allowing for a gutter between each
          // pair. The number of gutters is ids.length - 1, and the total gutter
          // width is gutterWidth * (ids.length - 1). Before calculating
          // each width, subtract the total gutter width for the parent width.
    
          // parent = document.getElementById(ids[0]).parentNode;
          parent = this.$[ids[0]].parentNode;
    
          if (!options.widths) {
            var percent = 100 / ids.length;
    
            options.widths = [];
    
            for (var i = 0; i < ids.length; i++) {
            options.widths.push(percent);
            };
          }
    
          if (!Array.isArray(options.minWidth)) {
            var minWidths = [];
    
            for (var i = 0; i < ids.length; i++) {
            minWidths.push(options.minWidth);
            };
    
            options.minWidth = minWidths;
          }
    
          for (var i = 0; i < ids.length; i++) {
            // var el = document.getElementById(ids[i]);
            var el = this.$[ids[i]];
    
            if (i > 0) {
            var pair = {
              // left: document.getElementById(ids[i - 1]),
              left: this.$[ids[i - 1]],
              right: el,
              leftMin: options.minWidth[i - 1],
              rightMin: options.minWidth[i],
              dragging: false,
              parent: parent
            },
            gutter = document.createElement('div');
            //gutter = this.$['gutter1']; 
            // This is the only case in this polymer element where 
            // using the normal DOM is appropriate.
    
            gutter.className = 'gutter';
            gutter.style.width = options.gutterWidth + 'px';
    
            gutter.addEventListener('mousedown', startDragging.bind(pair));
    
            parent.addEventListener('mouseup', stopDragging.bind(pair));
            parent.addEventListener('mousemove', drag.bind(pair));
            parent.addEventListener('mouseleave', stopDragging.bind(pair));
    
            // https://www.polymer-project.org/1.0/docs/devguide/local-dom.html
            //parent.insertBefore(gutter, el);
            Polymer.dom(this.root).insertBefore(gutter, el);
    
            pair.gutter = gutter;
          }
    
          el.style.width = 'calc(' + options.widths[i] + '% - ' + options.gutterWidth / 2 + 'px)';
          }
        },
        "ready":function(){
          this.split(['div1', 'div2'], {
          widths: [25, 75],
          minWidth: 200
          });
        }
        });
      </script>
    </dom-module>