2023-04-26 17:20:10 +08:00
|
|
|
<template>
|
2023-05-29 22:34:07 +08:00
|
|
|
<div v-if="!paramsAreBlank"
|
2023-10-19 16:16:09 +08:00
|
|
|
class="sn-action-toolbar p-4 w-full fixed bottom-0 rounded-t-md"
|
2023-05-29 22:34:07 +08:00
|
|
|
:class="{ 'sn-action-toolbar--button-overflow': buttonOverflow }"
|
|
|
|
:style="`width: ${width}px; bottom: ${bottomOffset}px; transform: translateX(${leftOffset}px)`">
|
2023-06-14 21:10:17 +08:00
|
|
|
<div class="sn-action-toolbar__actions flex gap-4">
|
|
|
|
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
|
2023-08-24 15:19:35 +08:00
|
|
|
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
|
2023-05-17 17:27:55 +08:00
|
|
|
</div>
|
2023-05-29 15:33:18 +08:00
|
|
|
<div v-if="!loading && actions.length === 0" class="sn-action-toolbar__message">
|
|
|
|
{{ i18n.t('action_toolbar.no_actions') }}
|
|
|
|
</div>
|
2023-06-16 20:17:46 +08:00
|
|
|
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
|
2023-09-26 22:20:18 +08:00
|
|
|
<div v-if="action.type === 'group' && Array.isArray(action.actions) && action.actions.length > 1" class="export-actions-dropdown sci-dropdown dropup">
|
|
|
|
<button class="btn btn-primary dropdown-toggle single-object-action rounded" type="button" id="exportDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
|
|
|
<i class="sn-icon sn-icon-export"></i>
|
|
|
|
<span>{{ action.group_label }}</span>
|
2023-10-04 08:56:17 +08:00
|
|
|
<span class="sn-icon sn-icon-down"></span>
|
2023-09-26 22:20:18 +08:00
|
|
|
</button>
|
|
|
|
<ul class="sci-dropdown dropup dropdown-menu dropdown-menu-right px-2" aria-labelledby="<%= id %>">
|
|
|
|
<li v-for="groupAction in action.actions" class="">
|
2023-09-28 20:45:28 +08:00
|
|
|
<a :class="`flex gap-2 items-center bg-sn-white color-sn-blue no-underline ${groupAction.button_class}`"
|
2023-09-26 22:20:18 +08:00
|
|
|
:href="(['link', 'remote-modal']).includes(groupAction.type) ? groupAction.path : '#'"
|
|
|
|
:id="groupAction.button_id"
|
|
|
|
:title="groupAction.label"
|
|
|
|
:data-url="groupAction.path"
|
|
|
|
:data-target="groupAction.target"
|
|
|
|
:data-toggle="groupAction.type === 'modal' && 'modal'"
|
|
|
|
:data-object-type="groupAction.item_type"
|
|
|
|
:data-object-id="groupAction.item_id"
|
|
|
|
:data-action="groupAction.type"
|
2023-12-15 23:33:13 +08:00
|
|
|
:data-e2e="`e2e-BT-actionToolbar-${groupAction.name}`"
|
2023-09-26 22:20:18 +08:00
|
|
|
@click="closeExportDropdown($event); doAction(groupAction, $event);">
|
|
|
|
<span class="sn-action-toolbar__button-text">{{ groupAction.label }}</span>
|
|
|
|
</a>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5 bg-sn-white color-sn-blue no-underline ${action.actions[0].button_class}`"
|
|
|
|
v-else-if="action.type === 'group' && Array.isArray(action.actions) && action.actions.length == 1"
|
|
|
|
:href="(['link', 'remote-modal']).includes(action.actions[0].type) ? action.actions[0].path : '#'"
|
|
|
|
:id="action.actions[0].button_id"
|
|
|
|
:title="action.group_label"
|
|
|
|
:data-url="action.actions[0].path"
|
|
|
|
:data-target="action.actions[0].target"
|
|
|
|
:data-toggle="action.actions[0].type === 'modal' && 'modal'"
|
|
|
|
:data-object-type="action.actions[0].item_type"
|
|
|
|
:data-object-id="action.actions[0].item_id"
|
|
|
|
:data-action="action.actions[0].type"
|
2023-12-15 23:33:13 +08:00
|
|
|
:data-e2e="`e2e-BT-actionToolbar-${action.name}`"
|
2023-09-26 22:20:18 +08:00
|
|
|
@click="doAction(action.actions[0], $event);">
|
|
|
|
<i :class="action.actions[0].icon"></i>
|
|
|
|
<span class="sn-action-toolbar__button-text">{{ action.group_label }}</span>
|
|
|
|
</a>
|
|
|
|
<a :class="`rounded flex gap-2 items-center py-1.5 px-2.5 bg-sn-white color-sn-blue no-underline ${action.button_class}`"
|
|
|
|
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
|
|
|
|
:id="action.button_id"
|
|
|
|
:title="action.label"
|
|
|
|
:data-url="action.path"
|
|
|
|
:data-target="action.target"
|
|
|
|
v-else
|
|
|
|
:data-toggle="action.type === 'modal' && 'modal'"
|
|
|
|
:data-object-type="action.item_type"
|
|
|
|
:data-object-id="action.item_id"
|
|
|
|
:data-action="action.type"
|
2023-12-15 23:33:13 +08:00
|
|
|
:data-e2e="`e2e-BT-actionToolbar-${action.name}`"
|
2023-09-26 22:20:18 +08:00
|
|
|
@click="doAction(action, $event)">
|
|
|
|
<i :class="action.icon"></i>
|
|
|
|
<span class="sn-action-toolbar__button-text">{{ action.label }}</span>
|
|
|
|
</a>
|
|
|
|
</div>
|
2023-04-26 17:20:10 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2024-01-04 23:34:36 +08:00
|
|
|
import { debounce } from '../shared/debounce.js';
|
2023-05-17 17:27:55 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
export default {
|
|
|
|
name: 'ActionToolbar',
|
|
|
|
props: {
|
|
|
|
actionsUrl: { type: String, required: true }
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
actions: [],
|
|
|
|
shown: false,
|
|
|
|
multiple: false,
|
|
|
|
params: {},
|
|
|
|
reloadCallback: null,
|
|
|
|
actionsLoadedCallback: null,
|
|
|
|
loaded: false,
|
|
|
|
loading: false,
|
|
|
|
width: 0,
|
|
|
|
bottomOffset: 0,
|
|
|
|
leftOffset: 0,
|
|
|
|
buttonOverflow: false
|
|
|
|
};
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
window.actionToolbarComponent = this;
|
|
|
|
window.onresize = this.setWidth;
|
2023-05-17 17:27:55 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
this.debouncedFetchActions = debounce((params) => {
|
|
|
|
this.params = params;
|
2023-05-17 17:27:55 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
|
|
|
|
this.actions = data.actions;
|
|
|
|
this.loading = false;
|
|
|
|
this.setButtonOverflow();
|
|
|
|
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
|
|
|
|
});
|
|
|
|
}, 10);
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.$nextTick(this.setWidth);
|
|
|
|
window.addEventListener('scroll', this.setLeftOffset);
|
|
|
|
},
|
|
|
|
beforeUnmount() {
|
|
|
|
delete window.actionToolbarComponent;
|
|
|
|
window.removeEventListener('scroll', this.setLeftOffset);
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
paramsAreBlank() {
|
|
|
|
const values = Object.values(this.params);
|
2023-05-29 15:33:18 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
if (values.length === 0) return true;
|
2023-05-29 15:33:18 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
return !values.some((v) => v.length);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
setWidth() {
|
|
|
|
this.width = $(this.$el).parent().width();
|
|
|
|
this.setButtonOverflow();
|
2023-05-29 15:33:18 +08:00
|
|
|
},
|
2024-01-04 23:34:36 +08:00
|
|
|
setButtonOverflow() {
|
|
|
|
// detects if the last action button is outside the toolbar container
|
|
|
|
this.buttonOverflow = false;
|
2023-05-29 22:34:07 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
this.$nextTick(() => {
|
|
|
|
if (
|
|
|
|
!(this.$el.getBoundingClientRect
|
|
|
|
&& document.querySelector('.sn-action-toolbar__action:last-child'))
|
|
|
|
) return;
|
2023-05-29 22:34:07 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
const containerRect = this.$el.getBoundingClientRect();
|
|
|
|
const lastActionRect = document.querySelector('.sn-action-toolbar__action:last-child').getBoundingClientRect();
|
2023-05-17 20:12:40 +08:00
|
|
|
|
2024-01-04 23:34:36 +08:00
|
|
|
this.buttonOverflow = containerRect.left + containerRect.width < lastActionRect.left + lastActionRect.width;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
setLeftOffset() {
|
|
|
|
this.leftOffset = -(window.pageXOffset || document.documentElement.scrollLeft);
|
|
|
|
},
|
|
|
|
setBottomOffset(pixels) {
|
|
|
|
this.bottomOffset = pixels;
|
|
|
|
},
|
|
|
|
fetchActions(params) {
|
|
|
|
this.loading = true;
|
|
|
|
this.debouncedFetchActions(params);
|
|
|
|
},
|
|
|
|
setReloadCallback(func) {
|
|
|
|
this.reloadCallback = func;
|
|
|
|
},
|
|
|
|
setActionsLoadedCallback(func) {
|
|
|
|
this.actionsLoadedCallback = func;
|
|
|
|
},
|
|
|
|
doAction(action, event) {
|
|
|
|
switch (action.type) {
|
|
|
|
case 'legacy':
|
|
|
|
// do nothing, this is handled by legacy code based on the button class
|
|
|
|
break;
|
|
|
|
case 'link':
|
|
|
|
// do nothing, already handled by href
|
|
|
|
break;
|
|
|
|
case 'modal':
|
|
|
|
// do nothihg, boostrap modal handled by data-toggle="modal" and data-target
|
|
|
|
case 'remote-modal':
|
|
|
|
// do nothing, handled by the data-action="remote-modal" binding
|
|
|
|
break;
|
|
|
|
case 'download':
|
|
|
|
event.stopPropagation();
|
|
|
|
window.location.href = action.path;
|
|
|
|
break;
|
|
|
|
case 'request':
|
|
|
|
event.stopPropagation();
|
|
|
|
$.ajax({
|
|
|
|
type: action.request_method,
|
|
|
|
url: action.path,
|
|
|
|
data: this.params
|
|
|
|
}).done((data) => {
|
|
|
|
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'success');
|
|
|
|
}).fail((data) => {
|
|
|
|
HelperModule.flashAlertMsg(data.responseJSON && data.responseJSON.message || data.message, 'danger');
|
|
|
|
}).always(() => {
|
|
|
|
if (this.reloadCallback) this.reloadCallback();
|
|
|
|
});
|
|
|
|
break;
|
2023-04-26 17:20:10 +08:00
|
|
|
}
|
2024-01-04 23:34:36 +08:00
|
|
|
},
|
|
|
|
closeExportDropdown(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
$(event.target).closest('.export-actions-dropdown').removeClass('open');
|
2023-04-26 17:20:10 +08:00
|
|
|
}
|
|
|
|
}
|
2024-01-04 23:34:36 +08:00
|
|
|
};
|
2023-04-26 17:20:10 +08:00
|
|
|
</script>
|