JSFiddle - React, Tailwind, and code Playground

by ChangJoo Park

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>

<div id="app">
  <div class="message-wrapper">
    <message-item v-for="message in messages" class="message-item" v-bind:item="message"></message-item>
  </div>

  <context-menu ref="contextMenu"></context-menu>
</div>

<template id="message-item">
  <div>
    {{item}}
  </div>
</template>

<template id="context-menu">
  <div class="context-menu" v-show="isOpen">
    {{selectedMessage}}
    <div v-for="item in items" class="context-menu-item">
      {{item}}
    </div>
  </div>
</template>

CSS

html,
body {
  box-sizing: border-box;
  user-select: none;
}

.message-item {
  padding: 20px;
  border-bottom: 1px solid tomato;
  border-left: 1px solid tomato;
  border-right: 1px solid tomato;
  cursor: pointer;
}

.message-item:first-child {
  border-top: 1px solid tomato;
}

.context-menu {
  border: 1px solid orange;
  display: inline-block;
  position: absolute;
  width: 100px;
  background-color: orange;
  color: #fff;
}

.context-menu-item {
  text-align: center;
  padding: 3px;
  border-bottom: 1px solid orange;
  cursor: pointer;
}

.context-menu-item:last-child {
  border-bottom: none;
}

JavaScript

var itemClassName = 'message-item';
var itemInContext;

function clickInsideElement(e, className) {
  var el = e.srcElement || e.target;

  if (el.classList.contains(className)) {
    return el;
  } else {
    while (el = el.parentNode) {
      if (el.classList && el.classList.contains(className)) {
        return el;
      }
    }
  }

  return false;
}

function getPosition(e) {
  var posx = 0;
  var posy = 0;

  if (!e) var e = window.event;

  if (e.pageX || e.pageY) {
    posx = e.pageX;
    posy = e.pageY;
  } else if (e.clientX || e.clientY) {
    posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
    posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
  }

  return {
    x: posx,
    y: posy
  }
}

Vue.component('context-menu', {
  template: '#context-menu',
  data: function() {
    return {
      isOpen: false,
      items: ['A', 'B', 'C', 'D'],
      selectedMessage: ''
    }
  }
});

Vue.component('message-item', {
  template: '#message-item',
  mounted: function() {
    console.log(this.$$)
    this.$el.addEventListener('contextmenu', function(event) {
	    taskItemInContext = clickInsideElement( event, itemClassName );
      var contextMenuComponent = this.$parent.$refs.contextMenu;
      if ( taskItemInContext ) {
        contextMenuComponent.$data.isOpen = true;
        contextMenuComponent.$data.selectedMessage = this.item;
        var contextMenuDOM = contextMenuComponent.$el;
        var position = getPosition(event);
        this.$nextTick(function() {
          contextMenuDOM.style.left = position.x + 'px';
          contextMenuDOM.style.top = position.y + 'px';
        })
	      event.preventDefault();
      } else {
        contextMenuComponent.$data.isOpen = false;
        contextMenuComponent.$data.selectedMessage = undefined;     
      	taskItemInContext = null;
      }
    }.bind(this));
  },
  props: ['item']
});

new Vue({
  el: '#app',
  data: function() {
    return {
    ...