11
\$\begingroup\$

A month ago I reviewed the code in Resizable split DIVs Vue.js, suggesting that the OP use computed properties and bound styles to simplify the code (at least reducing the need to use $refs to access DOM elements). Below are two variations I devised. They are very similar. The main difference is that the second uses -1 for the initial values and ternary operators to check if the data values are set. Are those good improvements? What else would you change?

The user can drag the vertical bar, as well as the two horizontal bars, to resize the containers.

There are two approaches below. In the first one the position data properties are initialized to empty strings, whereas in the second they are initialized to -1. This affects the logic within the computed property methods.

Note: this appears to only work with Chrome, Opera, IE and Edge. There is something with the OPs original code that doesn't allow the dragging to work with FF or Safari. It appears clientX and clientY values are not set in those browsers, which maybe the same as this issue.

sample screenshot

Non-ternary approach with empty strings as initial values

const app = new Vue({
  el: '#app',
  data: {
    lrDividerPos: '',
    rtbDividerPos: '',
    ltbDividerPos: '',
  },
  computed: {
    bottomLeftStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 'px';
      }
      if (this.ltbDividerPos) {
        style.height = (window.innerHeight - this.ltbDividerPos) + 'px';
        style.top = this.ltbDividerPos + 'px';
      }
      return style;
    },
    bottomRightStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.top = this.rtbDividerPos + 'px';
        style.height = (window.innerHeight - this.rtbDividerPos) + 'px';
      }
      return style;
    },
    leftDividerStyles: function() {
      if (this.lrDividerPos) {
        return {
          width: (this.lrDividerPos + 2) + 'px'
        };
      }
      return {};
    },
    ltbDividerStyles: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 2 + 'px';
      }
      if (this.ltbDividerPos) {
        style.top = this.ltbDividerPos + 'px';
      }
      return style;
    },
    lrDividerStyles: function() {
      if (this.lrDividerPos) {
        return {
          left: this.lrDividerPos + 'px'
        };
      }
      return {};
    },
    rtbDividerStyles: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.top = this.rtbDividerPos + 'px';
      }
      return style;
    },
    topLeftStyle: function() {
      const style = {};
      if (this.ltbDividerPos) {
        style.height = this.ltbDividerPos + 'px';
      }
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 'px';
      }
      return style;
    },
    topRightStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.height = this.rtbDividerPos + 'px';
      }
      return style;
    }
  },
  methods: {
    lrDividerDrag: function(e) {
      if (e.clientX) {
        this.lrDividerPos = e.clientX;
      }
    },
    ltbDividerDrag: function(e) {
      if (e.clientY) {
        this.ltbDividerPos = e.clientY;
      }
    },
    rtbDividerDrag: function(e) {
      if (e.clientY) {
        this.rtbDividerPos = e.clientY;
      }
    },
    dividerDragStart: function(e) {
      e.dataTransfer.setDragImage(new Image, 0, 0);
    }
  }
});
.text-area {
  margin: 10px;
}
.container {
  position: absolute;
  overflow: scroll;
  top: 0;
  left: 0;
  height: 50%;
  width: 50%;
}
.top-left {
  background-color: pink;
}

.top-right {
  background-color: lightgreen;
  left: 50%;
}

.bottom-left {
  background-color: lightblue;
  top: 50%;
}

.bottom-right {
  background-color: lightyellow;
  top: 50%;
  left: 50%;
}

.divider {
  position: absolute;
  background-color: black;
}

.left-right {
  width: 4px;
  height: 100%;
  top: 0;
  left: calc(50% - 4px / 2);
}

.right-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 50%;
}

.left-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 0;
}

.left-right:hover {
  cursor: col-resize;
}

.left-top-bottom:hover,
.right-top-bottom:hover {
  cursor: row-resize;
}

::-webkit-scrollbar {
  height: 0;
  width: 0;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
  <div>
    <div class="top-left container" :style="topLeftStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="top-right container" :style="topRightStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="bottom-left container" :style="bottomLeftStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="bottom-right container" :style="bottomRightStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>

    <div class="left-right divider" draggable="true" @dragstart="dividerDragStart" @drag="lrDividerDrag" :style="lrDividerStyles"></div>
    <div class="right-top-bottom divider" draggable="true" @drag="rtbDividerDrag" @dragstart="dividerDragStart" :style="rtbDividerStyles"></div>
    <div class="left-top-bottom divider" draggable="true" @drag="ltbDividerDrag" @dragstart="dividerDragStart" :style="ltbDividerStyles"></div>
  </div>
</div>

Ternary approach with negative initial values

const app = new Vue({
  el: '#app',
  data: {
    lrDividerPos: -1,
    rtbDividerPos: -1,
    ltbDividerPos: -1,
  },
  computed: {
    bottomLeftStyle: function() {
      return {
        height: this.ltbDividerPos > -1 ? (window.innerHeight - this.ltbDividerPos) + 'px' : '',
        top: this.ltbDividerPos > -1 ? this.ltbDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : ''
      };
    },
    bottomRightStyle: function() {
      return {
        height: this.rtbDividerPos > -1 ? (window.innerHeight - this.rtbDividerPos) + 'px' : '',
        left: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : '',
        top: this.rtbDividerPos > -1 ? this.rtbDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? (window.innerWidth - this.lrDividerPos + 2) + 'px' : ''
      };
    },
    leftDividerStyles: function() {
      return {
        width: this.lrDividerPos > -1 ? (this.lrDividerPos + 2) + 'px' : ''
      };
    },
    ltbDividerStyles: function() {
      return {
        top: this.ltbDividerPos > -1 ? this.ltbDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? this.lrDividerPos + 2 + 'px' : ''
      };
    },
    lrDividerStyles: function() {
      return {
        left: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : ''
      };
    },
    rtbDividerStyles: function() {
      return {
        left: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : '',
        top: this.rtbDividerPos > -1 ? this.rtbDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? (window.innerWidth - this.lrDividerPos + 2) + 'px' : ''
      };
    },
    topLeftStyle: function() {
      return {
        height: this.ltbDividerPos > -1 ? this.ltbDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : ''
      };
    },
    topRightStyle: function() {
      return {
        height: this.rtbDividerPos > -1 ? this.rtbDividerPos + 'px' : '',
        left: this.lrDividerPos > -1 ? this.lrDividerPos + 'px' : '',
        width: this.lrDividerPos > -1 ? (window.innerWidth - this.lrDividerPos + 2) + 'px' : ''
      };
    }
  },
  methods: {
    lrDividerDrag: function(e) {
      if (e.clientX) {
        this.lrDividerPos = e.clientX;
      }
    },
    ltbDividerDrag: function(e) {
      if (e.clientY) {
        this.ltbDividerPos = e.clientY;
      }
    },
    rtbDividerDrag: function(e) {
      if (e.clientY) {
        this.rtbDividerPos = e.clientY;
      }
    },
    dividerDragStart: function(e) {
      e.dataTransfer.setDragImage(new Image, 0, 0);
    }
  }
});
.text-area {
  margin: 10px;
}

.container {
  position: absolute;
  overflow: scroll;
  top: 0;
  left: 0;
  height: 50%;
  width: 50%;
}

.top-left {
  background-color: pink;
}

.top-right {
  background-color: lightgreen;
  left: 50%;
}

.bottom-left {
  background-color: lightblue;
  top: 50%;
}

.bottom-right {
  background-color: lightyellow;
  top: 50%;
  left: 50%;
}

.divider {
  position: absolute;
  background-color: black;
}

.left-right {
  width: 4px;
  height: 100%;
  top: 0;
  left: calc(50% - 4px / 2);
}

.right-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 50%;
}

.left-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 0;
}

.left-right:hover {
  cursor: col-resize;
}

.left-top-bottom:hover,
.right-top-bottom:hover {
  cursor: row-resize;
}

::-webkit-scrollbar {
  height: 0;
  width: 0;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
  <div>
    <div class="top-left container" :style="topLeftStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="top-right container" :style="topRightStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="bottom-left container" :style="bottomLeftStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
    <div class="bottom-right container" :style="bottomRightStyle">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>

    <div class="left-right divider" draggable="true" @dragstart="dividerDragStart" @drag="lrDividerDrag" :style="lrDividerStyles"></div>
    <div class="right-top-bottom divider" draggable="true" @drag="rtbDividerDrag" @dragstart="dividerDragStart" :style="rtbDividerStyles"></div>
    <div class="left-top-bottom divider" draggable="true" @drag="ltbDividerDrag" @dragstart="dividerDragStart" :style="ltbDividerStyles"></div>
  </div>
</div>

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

In accordance with the D.R.Y. principle: the markup can be simplified by registering a component - and a template can be used with an X-Template. For example - add a <script> tag like this:

<script type="text/x-template" id="text-container-template">
  <div class="container">
    <div class="text-area">
      <h3>Resize me using the black bars</h3>

      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
      in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </div>
  </div>
</script>

And then register that component before the Vue app instance is created:

Vue.component('text-container', {
  template: '#text-container-template'
});

Then the markup for the main application can be simplified to just the following:

<div id="app">
  <div>
    <text-container class="top-left" :style="topLeftStyle">
    </text-container>
    <text-container class="top-right" :style="topRightStyle">
    </text-container>
    <text-container class="bottom-left" :style="bottomLeftStyle">
    </text-container>
    <text-container class="bottom-right" :style="bottomRightStyle">
    </text-container>
    <div class="left-right divider" draggable="true" @dragstart="dividerDragStart" 
         @drag="lrDividerDrag" :style="lrDividerStyles"></div>
    <div class="right-top-bottom divider" draggable="true" @drag="rtbDividerDrag" 
         @dragstart="dividerDragStart" :style="rtbDividerStyles"></div>
    <div class="left-top-bottom divider" draggable="true" @drag="ltbDividerDrag" 
         @dragstart="dividerDragStart" :style="ltbDividerStyles"></div>
  </div>
</div>

Vue.component('text-container', {
  template: '#text-container-template'
});
const app = new Vue({
  el: '#app',
  data: {
    lrDividerPos: '',
    rtbDividerPos: '',
    ltbDividerPos: '',
  },
  computed: {
    bottomLeftStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 'px';
      }
      if (this.ltbDividerPos) {
        style.height = (window.innerHeight - this.ltbDividerPos) + 'px';
        style.top = this.ltbDividerPos + 'px';
      }
      return style;
    },
    bottomRightStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.top = this.rtbDividerPos + 'px';
        style.height = (window.innerHeight - this.rtbDividerPos) + 'px';
      }
      return style;
    },
    leftDividerStyles: function() {
      if (this.lrDividerPos) {
        return {
          width: (this.lrDividerPos + 2) + 'px'
        };
      }
      return {};
    },
    ltbDividerStyles: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 2 + 'px';
      }
      if (this.ltbDividerPos) {
        style.top = this.ltbDividerPos + 'px';
      }
      return style;
    },
    lrDividerStyles: function() {
      if (this.lrDividerPos) {
        return {
          left: this.lrDividerPos + 'px'
        };
      }
      return {};
    },
    rtbDividerStyles: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.top = this.rtbDividerPos + 'px';
      }
      return style;
    },
    topLeftStyle: function() {
      const style = {};
      if (this.ltbDividerPos) {
        style.height = this.ltbDividerPos + 'px';
      }
      if (this.lrDividerPos) {
        style.width = this.lrDividerPos + 'px';
      }
      return style;
    },
    topRightStyle: function() {
      const style = {};
      if (this.lrDividerPos) {
        style.left = this.lrDividerPos + 'px';
        style.width = (window.innerWidth - this.lrDividerPos + 2) + 'px';
      }
      if (this.rtbDividerPos) {
        style.height = this.rtbDividerPos + 'px';
      }
      return style;
    }
  },
  methods: {
    lrDividerDrag: function(e) {
      if (e.clientX) {
        this.lrDividerPos = e.clientX;
      }
    },
    ltbDividerDrag: function(e) {
      if (e.clientY) {
        this.ltbDividerPos = e.clientY;
      }
    },
    rtbDividerDrag: function(e) {
      if (e.clientY) {
        this.rtbDividerPos = e.clientY;
      }
    },
    dividerDragStart: function(e) {
      e.dataTransfer.setDragImage(new Image, 0, 0);
    }
  }
});
.text-area {
  margin: 10px;
}
.container {
  position: absolute;
  overflow: scroll;
  top: 0;
  left: 0;
  height: 50%;
  width: 50%;
}
.top-left {
  background-color: pink;
}

.top-right {
  background-color: lightgreen;
  left: 50%;
}

.bottom-left {
  background-color: lightblue;
  top: 50%;
}

.bottom-right {
  background-color: lightyellow;
  top: 50%;
  left: 50%;
}

.divider {
  position: absolute;
  background-color: black;
}

.left-right {
  width: 4px;
  height: 100%;
  top: 0;
  left: calc(50% - 4px / 2);
}

.right-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 50%;
}

.left-top-bottom {
  width: 50%;
  height: 4px;
  top: calc(50% - 4px / 2);
  left: 0;
}

.left-right:hover {
  cursor: col-resize;
}

.left-top-bottom:hover,
.right-top-bottom:hover {
  cursor: row-resize;
}

::-webkit-scrollbar {
  height: 0;
  width: 0;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/x-template" id="text-container-template">
    <div class="container">
      <div class="text-area">
        <h3>Resize me using the black bars</h3>

        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
        in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      </div>
    </div>
</script>
<div id="app">
  <div>
    <text-container class="top-left" :style="topLeftStyle">
    </text-container>
    <text-container class="top-right" :style="topRightStyle">
    </text-container>
    <text-container class="bottom-left" :style="bottomLeftStyle">
    </text-container>
    <text-container class="bottom-right" :style="bottomRightStyle">
    </text-container>
    <div class="left-right divider" draggable="true" @dragstart="dividerDragStart" @drag="lrDividerDrag" :style="lrDividerStyles"></div>
    <div class="right-top-bottom divider" draggable="true" @drag="rtbDividerDrag" @dragstart="dividerDragStart" :style="rtbDividerStyles"></div>
    <div class="left-top-bottom divider" draggable="true" @drag="ltbDividerDrag" @dragstart="dividerDragStart" :style="ltbDividerStyles"></div>
  </div>
</div>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.