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:
parent
6ef4a36bbc
commit
3313801174
7 changed files with 114 additions and 43 deletions
65
src/components/list/partials/filter-popup.vue
Normal file
65
src/components/list/partials/filter-popup.vue
Normal 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>
|
|
@ -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"
|
:visible="showTaskFilter"
|
||||||
v-if="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,
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{{ $store.getters['lists/getListById'](task.listId).title }}
|
{{ $store.getters['lists/getListById'](task.listId).title }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||||
<span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'">
|
<span class="parent-tasks" v-if="typeof task.relatedTasks.parenttask !== 'undefined'">
|
||||||
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
<template v-for="(pt, i) in task.relatedTasks.parenttask">
|
||||||
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">, </template>
|
||||||
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()}"
|
:visible="showFilters"
|
||||||
v-if="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 {
|
||||||
|
|
|
@ -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)"
|
:visible="showTaskFilter"
|
||||||
v-if="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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
|
||||||
@change="loadTasks(1)"
|
|
||||||
v-if="showTaskFilter"
|
|
||||||
v-model="params"
|
|
||||||
/>
|
|
||||||
</transition>
|
</transition>
|
||||||
|
<filter-popup
|
||||||
|
@change="loadTasks(1)"
|
||||||
|
:visible="showTaskFilter"
|
||||||
|
v-model="params"
|
||||||
|
/>
|
||||||
</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,
|
||||||
|
|
Loading…
Reference in a new issue