Fix closing popups when clicking outside of them (#378)

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/378
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
konrad 2021-01-17 10:36:57 +00:00
parent 6ef4a36bbc
commit 3313801174
7 changed files with 114 additions and 43 deletions

View file

@ -0,0 +1,65 @@
<template>
<transition name="fade">
<filters
@change="change"
v-if="visibleInternal"
v-model="params"
ref="filters"
/>
</transition>
</template>
<script>
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import Filters from '../../../components/list/partials/filters'
export default {
name: 'filter-popup',
data() {
return {
params: null,
visibleInternal: false,
}
},
components: {
Filters,
},
mounted() {
this.params = this.value
document.addEventListener('click', this.hidePopup)
},
beforeDestroy() {
document.removeEventListener('click', this.hidePopup)
},
watch: {
value(newVal) {
this.$set(this, 'params', newVal)
},
visible() {
this.visibleInternal = !this.visibleInternal
},
},
props: {
value: {
required: true,
},
visible: {
type: Boolean,
default: false,
},
},
methods: {
change() {
this.$emit('change', this.params)
this.$emit('input', this.params)
},
hidePopup(e) {
if (this.visibleInternal) {
closeWhenClickedOutside(e, this.$refs.filters.$el, () => {
this.visibleInternal = false
})
}
},
},
}
</script>

View file

@ -2,20 +2,18 @@
<div class="gantt-chart box"> <div class="gantt-chart box">
<div class="filter-container"> <div class="filter-container">
<div class="items"> <div class="items">
<button @click="showTaskFilter = !showTaskFilter" class="button"> <button @click.prevent.stop="showTaskFilter = !showTaskFilter" class="button">
<span class="icon is-small"> <span class="icon is-small">
<icon icon="filter"/> <icon icon="filter"/>
</span> </span>
Filters Filters
</button> </button>
</div> </div>
<transition name="fade"> <filter-popup
<filters
@change="loadTasks" @change="loadTasks"
v-if="showTaskFilter" :visible="showTaskFilter"
v-model="params" v-model="params"
/> />
</transition>
</div> </div>
<div class="dates"> <div class="dates">
<template v-for="(y, yk) in days"> <template v-for="(y, yk) in days">
@ -166,12 +164,12 @@ import PriorityLabel from './partials/priorityLabel'
import TaskCollectionService from '../../services/taskCollection' import TaskCollectionService from '../../services/taskCollection'
import {mapState} from 'vuex' import {mapState} from 'vuex'
import Rights from '../../models/rights.json' import Rights from '../../models/rights.json'
import Filters from '@/components/list/partials/filters' import FilterPopup from '@/components/list/partials/filter-popup'
export default { export default {
name: 'GanttChart', name: 'GanttChart',
components: { components: {
Filters, FilterPopup,
PriorityLabel, PriorityLabel,
EditTask, EditTask,
VueDragResize, VueDragResize,

View file

@ -40,14 +40,14 @@
/> />
<i <i
:class="{'overdue': task.dueDate <= new Date() && !task.done}" :class="{'overdue': task.dueDate <= new Date() && !task.done}"
@click.stop="showDefer = !showDefer" @click.prevent.stop="showDefer = !showDefer"
v-if="+new Date(task.dueDate) > 0" v-if="+new Date(task.dueDate) > 0"
v-tooltip="formatDate(task.dueDate)" v-tooltip="formatDate(task.dueDate)"
> >
- Due {{ formatDateSince(task.dueDate) }} - Due {{ formatDateSince(task.dueDate) }}
</i> </i>
<transition name="fade"> <transition name="fade">
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task"/> <defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
</transition> </transition>
<priority-label :priority="task.priority"/> <priority-label :priority="task.priority"/>
<span> <span>
@ -91,6 +91,7 @@ import Labels from './labels'
import User from '../../misc/user' import User from '../../misc/user'
import Fancycheckbox from '../../input/fancycheckbox' import Fancycheckbox from '../../input/fancycheckbox'
import DeferTask from './defer-task' import DeferTask from './defer-task'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
export default { export default {
name: 'singleTaskInList', name: 'singleTaskInList',
@ -137,11 +138,15 @@ export default {
}, },
mounted() { mounted() {
this.task = this.theTask this.task = this.theTask
document.addEventListener('click', this.hideDeferDueDatePopup)
}, },
created() { created() {
this.task = new TaskModel() this.task = new TaskModel()
this.taskService = new TaskService() this.taskService = new TaskService()
}, },
beforeDestroy() {
document.removeEventListener('click', this.hideDeferDueDatePopup)
},
computed: { computed: {
listColor() { listColor() {
const list = this.$store.getters['lists/getListById'](this.task.listId) const list = this.$store.getters['lists/getListById'](this.task.listId)
@ -197,6 +202,13 @@ export default {
this.error(e, this) this.error(e, this)
}) })
}, },
hideDeferDueDatePopup(e) {
if (this.showDefer) {
closeWhenClickedOutside(e, this.$refs.deferDueDate.$el, () => {
this.showDefer = false
})
}
},
}, },
} }
</script> </script>

View file

@ -11,7 +11,7 @@ export const closeWhenClickedOutside = (event, rootElement, closeCallback) => {
// closing callback when a click happens outside of the rootElement. // closing callback when a click happens outside of the rootElement.
let parent = event.target.parentElement let parent = event.target.parentElement
while (parent !== rootElement) { while (parent !== rootElement) {
if (parent.parentElement === null) { if (parent === null || parent.parentElement === null) {
parent = null parent = null
break break
} }

View file

@ -2,20 +2,18 @@
<div class="kanban-view"> <div class="kanban-view">
<div class="filter-container" v-if="list.isSavedFilter && !list.isSavedFilter()"> <div class="filter-container" v-if="list.isSavedFilter && !list.isSavedFilter()">
<div class="items"> <div class="items">
<button @click="showFilters = !showFilters" class="button"> <button @click.prevent.stop="showFilters = !showFilters" class="button">
<span class="icon is-small"> <span class="icon is-small">
<icon icon="filter"/> <icon icon="filter"/>
</span> </span>
Filters Filters
</button> </button>
</div> </div>
<transition name="fade"> <filter-popup
<filters
@change="() => {filtersChanged = true; loadBuckets()}" @change="() => {filtersChanged = true; loadBuckets()}"
v-if="showFilters" :visible="showFilters"
v-model="params" v-model="params"
/> />
</transition>
</div> </div>
<div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container"> <div :class="{ 'is-loading': loading && !oneTaskUpdating}" class="kanban loader-container">
<div :key="`bucket${bucket.id}`" class="bucket" v-for="bucket in buckets"> <div :key="`bucket${bucket.id}`" class="bucket" v-for="bucket in buckets">
@ -265,7 +263,6 @@ import {Container, Draggable} from 'vue-smooth-dnd'
import PriorityLabel from '../../../components/tasks/partials/priorityLabel' import PriorityLabel from '../../../components/tasks/partials/priorityLabel'
import User from '../../../components/misc/user' import User from '../../../components/misc/user'
import Labels from '../../../components/tasks/partials/labels' import Labels from '../../../components/tasks/partials/labels'
import Filters from '../../../components/list/partials/filters'
import {filterObject} from '@/helpers/filterObject' import {filterObject} from '@/helpers/filterObject'
import {applyDrag} from '@/helpers/applyDrag' import {applyDrag} from '@/helpers/applyDrag'
@ -273,16 +270,17 @@ import {mapState} from 'vuex'
import {saveListView} from '@/helpers/saveListView' import {saveListView} from '@/helpers/saveListView'
import Rights from '../../../models/rights.json' import Rights from '../../../models/rights.json'
import { LOADING, LOADING_MODULE } from '../../../store/mutation-types' import { LOADING, LOADING_MODULE } from '../../../store/mutation-types'
import FilterPopup from '@/components/list/partials/filter-popup'
export default { export default {
name: 'Kanban', name: 'Kanban',
components: { components: {
FilterPopup,
Container, Container,
Draggable, Draggable,
Labels, Labels,
User, User,
PriorityLabel, PriorityLabel,
Filters,
}, },
data() { data() {
return { return {

View file

@ -32,20 +32,18 @@
</span> </span>
</button> </button>
</div> </div>
<button @click="showTaskFilter = !showTaskFilter" class="button"> <button @click.prevent.stop="showTaskFilter = !showTaskFilter" class="button">
<span class="icon is-small"> <span class="icon is-small">
<icon icon="filter"/> <icon icon="filter"/>
</span> </span>
Filters Filters
</button> </button>
</div> </div>
<transition name="fade"> <filter-popup
<filters
@change="loadTasks(1)" @change="loadTasks(1)"
v-if="showTaskFilter" :visible="showTaskFilter"
v-model="params" v-model="params"
/> />
</transition>
</div> </div>
<div class="field task-add" v-if="!list.isArchived && canWrite && list.id > 0"> <div class="field task-add" v-if="!list.isArchived && canWrite && list.id > 0">
@ -174,9 +172,9 @@ import EditTask from '../../../components/tasks/edit-task'
import SingleTaskInList from '../../../components/tasks/partials/singleTaskInList' import SingleTaskInList from '../../../components/tasks/partials/singleTaskInList'
import taskList from '../../../components/tasks/mixins/taskList' import taskList from '../../../components/tasks/mixins/taskList'
import {saveListView} from '@/helpers/saveListView' import {saveListView} from '@/helpers/saveListView'
import Filters from '../../../components/list/partials/filters'
import Rights from '../../../models/rights.json' import Rights from '../../../models/rights.json'
import {mapState} from 'vuex' import {mapState} from 'vuex'
import FilterPopup from '@/components/list/partials/filter-popup'
export default { export default {
name: 'List', name: 'List',
@ -196,7 +194,7 @@ export default {
taskList, taskList,
], ],
components: { components: {
Filters, FilterPopup,
SingleTaskInList, SingleTaskInList,
EditTask, EditTask,
}, },

View file

@ -2,14 +2,14 @@
<div :class="{'is-loading': taskCollectionService.loading}" class="table-view loader-container"> <div :class="{'is-loading': taskCollectionService.loading}" class="table-view loader-container">
<div class="filter-container"> <div class="filter-container">
<div class="items"> <div class="items">
<button @click="() => {showActiveColumnsFilter = !showActiveColumnsFilter; showTaskFilter = false}" <button @click.prevent.stop="() => {showActiveColumnsFilter = !showActiveColumnsFilter; showTaskFilter = false}"
class="button"> class="button">
<span class="icon is-small"> <span class="icon is-small">
<icon icon="th"/> <icon icon="th"/>
</span> </span>
Columns Columns
</button> </button>
<button @click="() => {showTaskFilter = !showTaskFilter; showActiveColumnsFilter = false}" <button @click.prevent.stop="() => {showTaskFilter = !showTaskFilter; showActiveColumnsFilter = false}"
class="button"> class="button">
<span class="icon is-small"> <span class="icon is-small">
<icon icon="filter"/> <icon icon="filter"/>
@ -35,12 +35,12 @@
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">Created By</fancycheckbox> <fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">Created By</fancycheckbox>
</div> </div>
</div> </div>
<filters </transition>
<filter-popup
@change="loadTasks(1)" @change="loadTasks(1)"
v-if="showTaskFilter" :visible="showTaskFilter"
v-model="params" v-model="params"
/> />
</transition>
</div> </div>
<table class="table is-hoverable is-fullwidth"> <table class="table is-hoverable is-fullwidth">
@ -198,12 +198,12 @@ import DateTableCell from '../../../components/tasks/partials/date-table-cell'
import Fancycheckbox from '../../../components/input/fancycheckbox' import Fancycheckbox from '../../../components/input/fancycheckbox'
import Sort from '../../../components/tasks/partials/sort' import Sort from '../../../components/tasks/partials/sort'
import {saveListView} from '@/helpers/saveListView' import {saveListView} from '@/helpers/saveListView'
import Filters from '../../../components/list/partials/filters' import FilterPopup from '@/components/list/partials/filter-popup'
export default { export default {
name: 'Table', name: 'Table',
components: { components: {
Filters, FilterPopup,
Sort, Sort,
Fancycheckbox, Fancycheckbox,
DateTableCell, DateTableCell,