From 3b1d94e23f32f5dc83107437f7080f32df9ff129 Mon Sep 17 00:00:00 2001 From: Bram Wiepjes <bramw@protonmail.com> Date: Tue, 16 Jul 2019 20:43:03 +0200 Subject: [PATCH] fixed element not found bug and fire less events in Context component --- web-frontend/components/Context.vue | 91 +++++++++++++++++-------- web-frontend/config/nuxt.config.base.js | 6 +- web-frontend/directives/clickOutside.js | 19 ------ web-frontend/directives/moveToBody.js | 18 ----- web-frontend/plugins/directives.js | 7 -- 5 files changed, 62 insertions(+), 79 deletions(-) delete mode 100644 web-frontend/directives/clickOutside.js delete mode 100644 web-frontend/directives/moveToBody.js delete mode 100644 web-frontend/plugins/directives.js diff --git a/web-frontend/components/Context.vue b/web-frontend/components/Context.vue index f96ec1fdb..89a206594 100644 --- a/web-frontend/components/Context.vue +++ b/web-frontend/components/Context.vue @@ -1,10 +1,5 @@ <template> - <div - v-move-to-body - v-click-outside="hide" - class="context" - :class="{ 'visibility-hidden': !open }" - > + <div class="context" :class="{ 'visibility-hidden': !open }"> <slot></slot> </div> </template> @@ -26,15 +21,30 @@ export default { * element and in the child element we need to register the child with their parent to * prevent this. */ - beforeMount() { + mounted() { let $parent = this.$parent while ($parent !== undefined) { if ($parent.registerContextChild) { $parent.registerContextChild(this.$el) - break } $parent = $parent.$parent } + + // Move the rendered element to the top of the body so it can be positioned over any + // other element. + const body = document.body + body.insertBefore(this.$el, body.firstChild) + }, + /** + * Make sure the context menu is not open and all the events on the body are removed + * and that the element is removed from the body. + */ + destroyed() { + this.hide() + + if (this.$el.parentNode) { + this.$el.parentNode.removeChild(this.$el) + } }, methods: { /** @@ -62,33 +72,56 @@ export default { } if (value) { - const css = this.calculatePosition(target, vertical, horizontal, offset) + this.show(target, vertical, horizontal, offset) + } else { + this.hide() + } + }, + /** + * Calculate the position, show the context menu and register a click event on the + * body to check if the user has clicked outside the context. + */ + show(target, vertical, horizontal, offset) { + const css = this.calculatePosition(target, vertical, horizontal, offset) - // Set the calculated positions of the context. - for (const key in css) { - const value = css[key] !== null ? Math.ceil(css[key]) + 'px' : 'auto' - this.$el.style[key] = value - } + // Set the calculated positions of the context. + for (const key in css) { + const value = css[key] !== null ? Math.ceil(css[key]) + 'px' : 'auto' + this.$el.style[key] = value } // If we store the element who opened the context menu we can exclude the element // when clicked outside of this element. - this.opener = value ? target : null - this.open = value - }, - hide(event) { - // Checks if the click is inside one of our children. In that code the context - // must stay open. - const isChild = this.children.some(element => - isElement(element, event.target) - ) + this.opener = target + this.open = true - // Checks if the context is already opened, if the click was not on the opener - // because he can trigger the toggle method and if the click was not in one of - // our child contexts. - if (this.open && !isElement(this.opener, event.target) && !isChild) { - this.open = false + this.$el.clickOutsideEvent = event => { + if ( + // Check if the context menu is still open + this.open && + // If the click was outside the context element because we want to ignore + // clicks inside it.s + !isElement(this.$el, event.target) && + // If the click was not on the opener because he can trigger the toggle + // method. + !isElement(this.opener, event.target) && + // If the click was not inside one of the context children of this context + // menu. + !this.children.some(element => isElement(element, event.target)) + ) { + this.hide() + } } + document.body.addEventListener('click', this.$el.clickOutsideEvent) + }, + /** + * Hide the context menu and make sure the body event is removed. + */ + hide() { + this.opener = null + this.open = false + + document.body.removeEventListener('click', this.$el.clickOutsideEvent) }, /** * Calculates the absolute position of the context based on the original clicked @@ -147,8 +180,6 @@ export default { /** * A child context can register itself with the parent to prevent closing of the * parent when clicked inside the child. - * - * @param element HTMLElement */ registerContextChild(element) { this.children.push(element) diff --git a/web-frontend/config/nuxt.config.base.js b/web-frontend/config/nuxt.config.base.js index f090cdcef..5bdb09e02 100644 --- a/web-frontend/config/nuxt.config.base.js +++ b/web-frontend/config/nuxt.config.base.js @@ -25,11 +25,7 @@ export default { /* ** Plugins to load before mounting the App */ - plugins: [ - { src: '@/plugins/auth.js' }, - { src: '@/plugins/directives.js' }, - { src: '@/plugins/vuelidate.js' } - ], + plugins: [{ src: '@/plugins/auth.js' }, { src: '@/plugins/vuelidate.js' }], /* ** Nuxt.js modules diff --git a/web-frontend/directives/clickOutside.js b/web-frontend/directives/clickOutside.js deleted file mode 100644 index 6d1c7bf3b..000000000 --- a/web-frontend/directives/clickOutside.js +++ /dev/null @@ -1,19 +0,0 @@ -import { isElement } from '@/utils/dom' - -/** - * This directive calls a custom method if the user clicks outside of the - * element. - */ -export default { - bind: (el, binding, vnode) => { - el.clickOutsideEvent = event => { - if (!isElement(el, event.target)) { - vnode.context[binding.expression](event) - } - } - document.body.addEventListener('click', el.clickOutsideEvent) - }, - unbind: el => { - document.body.removeEventListener('click', el.clickOutsideEvent) - } -} diff --git a/web-frontend/directives/moveToBody.js b/web-frontend/directives/moveToBody.js deleted file mode 100644 index c51404d09..000000000 --- a/web-frontend/directives/moveToBody.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * This directive moves the whole element to the document body so that it can be - * positioned over another element. - */ -export default { - inserted: el => { - const body = document.body - - // The element is added as first child in the body so that child contexts - // are being shown on top of their parent. - body.insertBefore(el, body.firstChild) - }, - unbind: el => { - if (el.parentNode) { - el.parentNode.removeChild(el) - } - } -} diff --git a/web-frontend/plugins/directives.js b/web-frontend/plugins/directives.js deleted file mode 100644 index 64a9c8c3f..000000000 --- a/web-frontend/plugins/directives.js +++ /dev/null @@ -1,7 +0,0 @@ -import Vue from 'vue' - -import moveToBody from '@/directives/moveToBody' -import clickOutside from '@/directives/clickOutside' - -Vue.directive('move-to-body', moveToBody) -Vue.directive('click-outside', clickOutside)