mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-24 10:22:18 +00:00
refactor fhcApi
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
import FhcAlert from './FhcAlert.js';
|
||||
|
||||
|
||||
export default {
|
||||
install: (app, options) => {
|
||||
if (app.config.globalProperties.$api) {
|
||||
return;
|
||||
}
|
||||
app.use(FhcAlert);
|
||||
|
||||
function _get_config(form, uri, data, config) {
|
||||
if (typeof form == 'string' && config === undefined) {
|
||||
[uri, data, config] = [form, uri, data];
|
||||
form = undefined;
|
||||
} else if (form) {
|
||||
if (typeof form != 'object')
|
||||
throw new TypeError('Parameter 1 of _get_config must be an object or a string');
|
||||
if (uri === undefined && data === undefined && config === undefined) {
|
||||
config = form;
|
||||
form = undefined;
|
||||
}
|
||||
}
|
||||
if (form) {
|
||||
// NOTE(chris): check if form is fhc-form
|
||||
if (!form.clearValidation || !form.setFeedback)
|
||||
throw new TypeError("'form' is not a Form Component");
|
||||
|
||||
form = {
|
||||
clearValidation: form.clearValidation,
|
||||
setFeedback: form.setFeedback
|
||||
};
|
||||
|
||||
if (config)
|
||||
config.form = form;
|
||||
else
|
||||
config = {form};
|
||||
}
|
||||
|
||||
return [uri, data, config];
|
||||
}
|
||||
|
||||
function _clean_return_value(response) {
|
||||
const result = response.data;
|
||||
delete response.data;
|
||||
if (!result.meta)
|
||||
result.meta = {response};
|
||||
else
|
||||
result.meta.response = response;
|
||||
return result;
|
||||
}
|
||||
const baseURL = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/";
|
||||
const fhcApiAxios = axios.create({
|
||||
timeout: 500000,
|
||||
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
|
||||
});
|
||||
|
||||
fhcApiAxios.interceptors.request.use(config => {
|
||||
if (config.method != 'post' || !config.data)
|
||||
return config;
|
||||
|
||||
if (config.data instanceof FormData)
|
||||
return config;
|
||||
|
||||
if (!Object.values(config.data).every(item => {
|
||||
if (item instanceof FileList)
|
||||
return false;
|
||||
if (Array.isArray(item))
|
||||
return item.every(i => !(i instanceof File));
|
||||
return true;
|
||||
})) {
|
||||
const newData = Object.entries(config.data).reduce((nd, [key, item]) => {
|
||||
if (item instanceof FileList) {
|
||||
for (const file of item)
|
||||
nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file);
|
||||
} else if (Array.isArray(item)) {
|
||||
if (item.every(i => !(i instanceof File))) {
|
||||
nd.jsondata[key] = item;
|
||||
} else {
|
||||
item.forEach(file => nd.FormData.append(key + (item.length > 1 ? '[]' : ''), file));
|
||||
}
|
||||
} else {
|
||||
nd.jsondata[key] = item;
|
||||
}
|
||||
return nd;
|
||||
}, {
|
||||
FormData: new FormData(),
|
||||
jsondata: {}
|
||||
});
|
||||
newData.FormData.append('_jsondata', JSON.stringify(newData.jsondata));
|
||||
config.data = newData.FormData;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
fhcApiAxios.interceptors.response.use(response => {
|
||||
if (response.config?.errorHandling == 'off'
|
||||
|| response.config?.errorHandling === false
|
||||
|| response.config?.errorHandling == 'fail')
|
||||
return _clean_return_value(response);
|
||||
|
||||
// NOTE(chris): loop through errors
|
||||
if (response.data.errors)
|
||||
response.data.errors = response.data.errors.filter(
|
||||
err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config)
|
||||
);
|
||||
|
||||
return _clean_return_value(response);
|
||||
}, error => {
|
||||
if (error.code == 'ERR_CANCELED')
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
|
||||
if (error.config?.errorHandling == 'off'
|
||||
|| error.config?.errorHandling === false
|
||||
|| error.config?.errorHandling == 'success')
|
||||
return Promise.reject(error);
|
||||
|
||||
if (error.response) {
|
||||
if (error.response.status == 404) {
|
||||
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL, true);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
}
|
||||
|
||||
// NOTE(chris): loop through errors
|
||||
error.response.data.errors = error.response.data.errors.filter(
|
||||
err => (error.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, error.config)
|
||||
);
|
||||
if (!error.response.data.errors.length)
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
} else if (error.request) {
|
||||
app.config.globalProperties.$fhcAlert.alertDefault('error', error.message, error.request.responseURL);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
} else {
|
||||
app.config.globalProperties.$fhcAlert.alertError(error.message);
|
||||
return Promise.reject({...{handled: true}, ...error});
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
app.config.globalProperties.$api = {
|
||||
getUri(url) {
|
||||
return fhcApiAxios.getUri({url});
|
||||
},
|
||||
get(form, uri, params, config) {
|
||||
[uri, params, config] = _get_config(form, uri, params, config);
|
||||
if (params) {
|
||||
if (config)
|
||||
config.params = params;
|
||||
else
|
||||
config = {params};
|
||||
}
|
||||
return fhcApiAxios.get(uri, config);
|
||||
},
|
||||
post(form, uri, data, config) {
|
||||
[uri, data, config] = _get_config(form, uri, data, config);
|
||||
return fhcApiAxios.post(uri, data, config);
|
||||
},
|
||||
call(factory, configoverwrite, form) {
|
||||
let {method, url, params, config} = factory;
|
||||
if (configoverwrite !== undefined) {
|
||||
config = configoverwrite;
|
||||
}
|
||||
if (!method) {
|
||||
method = 'get';
|
||||
}
|
||||
if (method.toLowerCase)
|
||||
method = method.toLowerCase();
|
||||
if (method == 'get') {
|
||||
return this.get(form, url, params, config);
|
||||
} else if (method == 'post') {
|
||||
return this.post(form, url, params, config);
|
||||
} else {
|
||||
console.error("FhcApi: method not allowed:", method);
|
||||
}
|
||||
},
|
||||
_defaultErrorHandlers: {
|
||||
validation(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
if (config?.form) {
|
||||
config.form.clearValidation();
|
||||
config.form.setFeedback(false, error.messages);
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(error.messages)) {
|
||||
error.messages.forEach($fhcAlert.alertError);
|
||||
return false;
|
||||
} else if (typeof error.messages == 'object') {
|
||||
if (config?.errorHeader)
|
||||
Object.values(error.messages).forEach(
|
||||
value => $fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
value,
|
||||
true
|
||||
)
|
||||
);
|
||||
else
|
||||
Object.entries(error.messages).forEach(
|
||||
([key, value]) => $fhcAlert.alertDefault('error', key, value, true)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
general(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
if (config?.form)
|
||||
config.form.setFeedback(false, error.message);
|
||||
else if (config?.errorHeader)
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
error.message,
|
||||
true
|
||||
);
|
||||
else
|
||||
$fhcAlert.alertError(error.message);
|
||||
},
|
||||
php(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
switch (error.severity) {
|
||||
case 'Warning':
|
||||
case 'Core Warning':
|
||||
case 'Compile Warning':
|
||||
case 'User Warning':
|
||||
$fhcAlert.alertDefault('warn', 'PHP ' + error.severity, message, true);
|
||||
break;
|
||||
case 'Notice':
|
||||
case 'User Notice':
|
||||
case 'Runtime Notice':
|
||||
$fhcAlert.alertDefault('info', 'PHP ' + error.severity, message, true);
|
||||
break;
|
||||
default:
|
||||
message = 'Type: PHP ' + error.severity + '\n\n' + message;
|
||||
$fhcAlert.alertSystemError(message);
|
||||
break;
|
||||
}
|
||||
},
|
||||
exception(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Type: ' + error.class + '\n\n';
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
if (error.backtrace && error.backtrace.length) {
|
||||
message += '\nBacktrace: ';
|
||||
error.backtrace.forEach(err => {
|
||||
message += '\n\tFile: ' + err.file + '\n';
|
||||
message += '\tLine: ' + err.line + '\n';
|
||||
message += '\tFunction: ' + err.function + '\n';
|
||||
});
|
||||
}
|
||||
$fhcAlert.alertSystemError(message);
|
||||
},
|
||||
db(error) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
if (error.heading !== undefined)
|
||||
message += error.heading + '\n\n';
|
||||
if (error.code !== undefined)
|
||||
message += 'Code: ' + error.code + '\n\n';
|
||||
if (error.sql !== undefined)
|
||||
message += 'SQL: ' + error.sql + '\n\n';
|
||||
if (error.message !== undefined)
|
||||
message += 'Message: ' + error.message + '\n\n';
|
||||
else if (error.messages !== undefined)
|
||||
message += 'Messages: ' + error.messages.join('\n\t') + '\n\n';
|
||||
if (error.filename !== undefined)
|
||||
message += 'Filename: ' + error.filename + '\n';
|
||||
if (error.line !== undefined)
|
||||
message += 'Line Number: ' + error.line + '\n';
|
||||
|
||||
$fhcAlert.alertSystemError(message);
|
||||
},
|
||||
auth(error, config) {
|
||||
const $fhcAlert = app.config.globalProperties.$fhcAlert;
|
||||
|
||||
var message = '';
|
||||
message += 'Controller name: ' + error.controller + '\n';
|
||||
message += 'Method name: ' + error.method + '\n';
|
||||
message += 'Required permissions: ' + error.required_permissions;
|
||||
if (config?.errorHeader)
|
||||
$fhcAlert.alertDefault(
|
||||
'error',
|
||||
Array.isArray(config.errorHeader) ? app.config.globalProperties.$p.t.apply(null, config.errorHeader) : config.errorHeader,
|
||||
error.message,
|
||||
true
|
||||
);
|
||||
else
|
||||
$fhcAlert.alertDefault('error', error.message, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
app.provide('$api', app.config.globalProperties.$api);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,424 @@
|
||||
/**
|
||||
* Copyright (C) 2022 fhcomplete.org
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @usage:
|
||||
* Preperations:
|
||||
* Be sure to have PrimeVue loaded with the toast and confirmdialog
|
||||
* components as primevue variable
|
||||
* Install:
|
||||
* Import this Plugin and install it with the app.use() function.
|
||||
* Use:
|
||||
* In your component you can call now the global property $fhcAlert
|
||||
* which has the following functions:
|
||||
*
|
||||
* alertSuccess
|
||||
* ------------
|
||||
* Displays a success message
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertInfo
|
||||
* ---------
|
||||
* Displays an info message
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertWarning
|
||||
* ------------
|
||||
* Displays a warning
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertError
|
||||
* ----------
|
||||
* Displays an error
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* alertSystemError
|
||||
* ----------------
|
||||
* Displays an alert with the error details and a button to mail
|
||||
* the error to the Support Team
|
||||
* @param string message
|
||||
* @return void
|
||||
*
|
||||
* confirmDelete
|
||||
* -------------
|
||||
* Displays a confirmation dialog and returns a Promise which resolves
|
||||
* with true or false depending und the pressed button.
|
||||
* @return Promise
|
||||
*
|
||||
* alertDefault
|
||||
* ------------
|
||||
* Displays an alert
|
||||
* @param string severity can be 'success', 'info', 'warning', 'error'
|
||||
* @param string title
|
||||
* @param string message
|
||||
* @param boolean sticky (optional) defaults to false
|
||||
* @return void
|
||||
*
|
||||
* alertMultiple
|
||||
* -------------
|
||||
* Displays multiple alerts
|
||||
* @param array messageArray
|
||||
* @param string severity (optional) defaults to 'info'
|
||||
* @param string title (optional) defaults to 'Info'
|
||||
* @param boolean sticky (optional) defaults to false
|
||||
* @return void
|
||||
*
|
||||
* handleSystemError
|
||||
* -----------------
|
||||
* Automatiticly determine how to display an system error and display it.
|
||||
* This would be used in a catch block of an ajax call.
|
||||
* @param mixed error
|
||||
* @return void
|
||||
*
|
||||
* handleSystemMessage
|
||||
* -------------------
|
||||
* Automatiticly determine how to display a message and display it.
|
||||
* @param mixed message
|
||||
* @return void
|
||||
*/
|
||||
import PvConfig from "../../../index.ci.php/public/js/components/primevue/config/config.esm.min.js";
|
||||
import PvToast from "../../../index.ci.php/public/js/components/primevue/toast/toast.esm.min.js";
|
||||
import PvConfirm from "../../../index.ci.php/public/js/components/primevue/confirmdialog/confirmdialog.esm.min.js";
|
||||
import PvConfirmationService from "../../../index.ci.php/public/js/components/primevue/confirmationservice/confirmationservice.esm.min.js";
|
||||
|
||||
import {CoreRESTClient} from '../RESTClient.js';
|
||||
|
||||
const helperAppContainer = document.createElement('div');
|
||||
|
||||
const helperApp = Vue.createApp({
|
||||
name: "FhcAlertApp",
|
||||
components: {
|
||||
PvToast,
|
||||
PvConfirm
|
||||
},
|
||||
methods: {
|
||||
mailToUrl(slotProps) {
|
||||
let mailTo = 'noreply@technikum-wien.at'; // TODO domain anpassen
|
||||
let subject = 'Meldung%20Systemfehler';
|
||||
let body = `
|
||||
Danke, dass Sie uns den Fehler melden. %0D%0A %0D%0A
|
||||
Bitte beschreiben Sie uns ausführlich das Problem.%0D%0A
|
||||
Bsp: Ich habe X ausgewählt und Y angelegt. Beim Speichern erhielt ich die Fehlermeldung. [Optional: Ich habe den Browser Z verwendet.]%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
PROBLEM: ... %0D%0A %0D%0A %0D%0A
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A
|
||||
Fehler URL: ` + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method + `%0D%0A
|
||||
Fehler Meldung: ` + slotProps.message.detail + `%0D%0A
|
||||
-----------------------------------------------------------------------------------------------------------------------------------%0D%0A %0D%0A
|
||||
Wir kümmern uns um eine rasche Behebung des Problems!`
|
||||
|
||||
return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body;
|
||||
},
|
||||
openMessagecard(e) {
|
||||
bootstrap.Collapse.getOrCreateInstance(e.target.getAttribute('href')).toggle();
|
||||
}
|
||||
},
|
||||
unmounted() {
|
||||
helperAppContainer.parentElement.removeChild(helperAppContainer);
|
||||
},
|
||||
template: `
|
||||
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999"></pv-toast>
|
||||
<pv-toast ref="alert" class="fhc-alert" :base-z-index="99999" position="center">
|
||||
<template #message="slotProps">
|
||||
<i class="fa fa-circle-exclamation fa-2xl mt-3"></i>
|
||||
<div class="p-toast-message-text">
|
||||
<span class="p-toast-summary">{{slotProps.message.summary}}</span>
|
||||
<div class="p-toast-detail my-3">Sorry! Ein interner technischer Fehler ist aufgetreten.</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a
|
||||
class="align-bottom flex-fill me-2"
|
||||
data-bs-toggle="collapse"
|
||||
:href="'#fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'fhcAlertCollapseMessageCard' + slotProps.message.id"
|
||||
@click="openMessagecard"
|
||||
>
|
||||
Fehler anzeigen
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary flex-fill"
|
||||
target="_blank"
|
||||
:href="mailToUrl(slotProps)"
|
||||
>
|
||||
Fehler melden
|
||||
</a>
|
||||
</div>
|
||||
<div ref="messageCard" :id="'fhcAlertCollapseMessageCard' + slotProps.message.id" class="collapse mt-3">
|
||||
<div class="card card-body text-body small">
|
||||
{{slotProps.message.detail}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</pv-toast>
|
||||
<pv-confirm group="fhcAlertConfirm"></pv-confirm>`
|
||||
});
|
||||
|
||||
helperApp.use(PvConfig);
|
||||
helperApp.use(PvConfirmationService);
|
||||
|
||||
const helperAppInstance = helperApp.mount(helperAppContainer);
|
||||
|
||||
document.body.appendChild(helperAppContainer);
|
||||
|
||||
|
||||
export default {
|
||||
install: (app, options) => {
|
||||
const $fhcAlert = {
|
||||
alertSuccess(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSuccess);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'success', summary: 'Info', detail: message, life: 1000});
|
||||
},
|
||||
alertInfo(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertInfo);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'info', summary: 'Info', detail: message, life: 3000 });
|
||||
},
|
||||
alertWarning(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertWarning);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'warn', summary: 'Achtung', detail: message});
|
||||
},
|
||||
alertError(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertError);
|
||||
helperAppInstance.$refs.toast.add({ severity: 'error', summary: 'Achtung', detail: message });
|
||||
},
|
||||
alertSystemError(message) {
|
||||
if (Array.isArray(message))
|
||||
return message.forEach(this.alertSystemError);
|
||||
helperAppInstance.$refs.alert.add({ severity: 'error', summary: 'Systemfehler', detail: message});
|
||||
},
|
||||
confirmDelete() {
|
||||
return new Promise((resolve, reject) => {
|
||||
helperAppInstance.$confirm.require({
|
||||
group: 'fhcAlertConfirm',
|
||||
header: 'Achtung',
|
||||
message: 'Möchten Sie sicher löschen?',
|
||||
acceptLabel: 'Löschen',
|
||||
acceptClass: 'p-button-danger',
|
||||
rejectLabel: 'Abbrechen',
|
||||
rejectClass: 'p-button-secondary',
|
||||
accept() {
|
||||
resolve(true);
|
||||
},
|
||||
reject() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
confirm(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
helperAppInstance.$confirm.require({
|
||||
group: options?.group ?? 'fhcAlertConfirm',
|
||||
header: options?.header ?? 'Achtung',
|
||||
message: options?.message ?? '',
|
||||
acceptLabel: options?.acceptLabel ?? 'Ok',
|
||||
acceptClass: options?.acceptClass ?? 'btn btn-primary',
|
||||
rejectLabel: options?.rejectLabel ?? 'Abbrechen',
|
||||
rejectClass: options?.rejectClass ?? 'btn btn-outline-secondary',
|
||||
accept() {
|
||||
resolve(true);
|
||||
},
|
||||
reject() {
|
||||
resolve(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
alertDefault(severity, title, message, sticky = false) {
|
||||
let options = { severity: severity, summary: title, detail: message};
|
||||
|
||||
if (!sticky)
|
||||
options.life = 3000;
|
||||
|
||||
helperAppInstance.$refs.toast.add(options);
|
||||
},
|
||||
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){
|
||||
// Check, if array has only string values
|
||||
if (messageArray.every(message => typeof message === 'string')) {
|
||||
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handleSystemError(error) {
|
||||
// don't show an error message to the user if the error was an aborted request
|
||||
if(error.hasOwnProperty('name') && error.name.toLowerCase() === "AbortError".toLowerCase())
|
||||
return;
|
||||
|
||||
// Error is string
|
||||
if (typeof error === 'string')
|
||||
return $fhcAlert.alertSystemError(error);
|
||||
|
||||
// Error is array of strings
|
||||
if (Array.isArray(error) && error.every(err => typeof err === 'string'))
|
||||
return error.every($fhcAlert.alertSystemError);
|
||||
|
||||
// Error has been handled already
|
||||
if (error.hasOwnProperty('handled') && error.handled)
|
||||
return;
|
||||
|
||||
// Error is object
|
||||
if (typeof error === 'object' && error !== null) {
|
||||
let errMsg = '';
|
||||
|
||||
|
||||
if (error.hasOwnProperty('response') && error.response?.data?.retval)
|
||||
errMsg += 'Error Message: ' + (error.response.data.retval.message || error.response.data.retval) + '\r\n';
|
||||
else if (error.hasOwnProperty('message'))
|
||||
errMsg += 'Error Message: ' + error.message.toUpperCase() + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('config') && error.config.hasOwnProperty('url'))
|
||||
errMsg += 'Error ConfigURL: ' + error.config.url + '\r\n';
|
||||
|
||||
if (error.hasOwnProperty('stack'))
|
||||
errMsg += 'Error Stack: ' + error.stack + '\r\n';
|
||||
|
||||
// Fallback object error message
|
||||
if (errMsg == '')
|
||||
errMsg = 'Error Message: ' + JSON.stringify(error) + '\r\n';
|
||||
|
||||
errMsg += 'Error Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method;
|
||||
|
||||
return $fhcAlert.alertSystemError(errMsg);
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$fhcAlert.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
handleSystemMessage(message) {
|
||||
// Message is string
|
||||
if (typeof message === 'string')
|
||||
return $fhcAlert.alertWarning(message);
|
||||
|
||||
// Message is array of strings
|
||||
if (Array.isArray(message)) {
|
||||
// If Array has only Strings
|
||||
if (message.every(msg => typeof msg === 'string'))
|
||||
return message.every($fhcAlert.alertWarning);
|
||||
|
||||
// If Array has only Objects
|
||||
if (message.every(msg => typeof msg === 'object') && msg !== null) {
|
||||
return message.every(msg => {
|
||||
if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) {
|
||||
$fhcAlert.alertWarning(JSON.stringify(msg.data.retval));
|
||||
} else {
|
||||
$fhcAlert.alertSystemError(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Message is Object with data property
|
||||
if (typeof message === 'object' && message !== null){
|
||||
if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) {
|
||||
// NOTE(chris): changed: alertSystemError => alertWarning
|
||||
$fhcAlert.alertWarning(JSON.stringify(message.data.retval));
|
||||
} else {
|
||||
$fhcAlert.alertSystemError(JSON.stringify(message));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
$fhcAlert.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method);
|
||||
},
|
||||
resetFormValidation(form) {
|
||||
const event = new Event('fhc-form-reset');
|
||||
form.querySelectorAll(['[data-fhc-form-validate],[data-fhc-form-error]']).forEach(el => el.dispatchEvent(event));
|
||||
/*const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
if (alert) {
|
||||
alert.innerHTML = '';
|
||||
alert.classList.add('d-none');
|
||||
}
|
||||
form.querySelectorAll('.invalid-feedback').forEach(n => n.remove());
|
||||
form.querySelectorAll('.is-invalid').forEach(n => n.classList.remove('is-invalid'));
|
||||
form.querySelectorAll('.is-valid').forEach(n => n.classList.remove('is-valid'));*/
|
||||
},
|
||||
handleFormValidation(error, form) {
|
||||
if (form === undefined) {
|
||||
if (error && error.nodeType === Node.ELEMENT_NODE)
|
||||
return err => $fhcAlert.handleFormValidation(err, error);
|
||||
} else {
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
if (typeof errors !== "object")
|
||||
errors = error.response.data;
|
||||
|
||||
// NOTE(chris): reset form validation
|
||||
$fhcAlert.resetFormValidation(form);
|
||||
|
||||
// NOTE(chris): set form input validation
|
||||
const notFound = Object.entries(errors).filter(([key, detail]) => {
|
||||
const input = form.querySelector('[data-fhc-form-validate="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
|
||||
input.dispatchEvent(new CustomEvent('fhc-form-invalidate', {detail}));
|
||||
|
||||
/*const input = form.querySelector('[name="' + key + '"]');
|
||||
if (!input)
|
||||
return true;
|
||||
input.classList.add('is-invalid');
|
||||
const feedback = document.createElement('div');
|
||||
feedback.classList.add('invalid-feedback');
|
||||
feedback.innerHTML = detail;
|
||||
input.after(feedback);*/
|
||||
return false;
|
||||
}).map(arr => arr[1]);
|
||||
|
||||
|
||||
//const alert = form.querySelector('div.alert.alert-danger[role="alert"]');
|
||||
const alert = form.querySelector('[data-fhc-form-error]');
|
||||
if (alert && notFound.length) {
|
||||
alert.dispatchEvent(new CustomEvent('fhc-form-error', {detail: notFound}));
|
||||
/*notFound.forEach(txt => {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = txt;
|
||||
alert.append(p);
|
||||
});
|
||||
|
||||
if (notFound.length) {
|
||||
alert.lastChild.classList.add('mb-0');
|
||||
alert.classList.remove('d-none');
|
||||
}*/
|
||||
} else {
|
||||
notFound.forEach($fhcAlert.alertError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.response?.status == 400) {
|
||||
let errors = CoreRESTClient.getError(error.response.data);
|
||||
$fhcAlert.alertError((typeof errors === 'object') ? Object.values(errors) : errors);
|
||||
} else {
|
||||
$fhcAlert.handleSystemError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
app.config.globalProperties.$fhcAlert = $fhcAlert;
|
||||
app.provide('$fhcAlert', app.config.globalProperties.$fhcAlert);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import PluginsApi from './Api.js';
|
||||
import ApiPhrasen from '../api/factory/phrasen.js';
|
||||
|
||||
const categories = Vue.reactive({});
|
||||
const loadingModules = {};
|
||||
let user_language = Vue.ref(FHC_JS_DATA_STORAGE_OBJECT.user_language);
|
||||
export let user_locale = Vue.computed(()=>{
|
||||
if(!user_language.value) return null;
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.server_languages.find(language => language.sprache == user_language.value).LC_Time;
|
||||
});
|
||||
|
||||
function extractCategory(obj, category) {
|
||||
return obj.filter(e => e.category == category).reduce((res, elem) => {
|
||||
if (!res[elem.phrase])
|
||||
res[elem.phrase] = elem.text;
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
function getValueForLoadedPhrase(category, phrase, params) {
|
||||
let result = categories[category][phrase];
|
||||
if (!result)
|
||||
return '<< PHRASE ' + phrase + '>>';
|
||||
if (params)
|
||||
result = result.replace(/\{([^}]*)\}/g, (match, p1) => params[p1] === undefined ? match : params[p1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const phrasen = {
|
||||
user_language,
|
||||
user_locale,
|
||||
setLanguage(language) {
|
||||
const catArray = Object.keys(categories)
|
||||
return this.config.globalProperties.$api
|
||||
.call(ApiPhrasen.setLanguage(catArray, language))
|
||||
.then(res => {
|
||||
res.data.forEach(row => {
|
||||
categories[row.category][row.phrase] = row.text
|
||||
})
|
||||
|
||||
// update the reactive data that holds the current active user_language
|
||||
user_language.value = language;
|
||||
|
||||
return res
|
||||
})
|
||||
},
|
||||
loadCategory(category) {
|
||||
if (Array.isArray(category))
|
||||
return Promise.all(category.map(this.config.globalProperties
|
||||
.$p.loadCategory));
|
||||
|
||||
if (!loadingModules[category])
|
||||
loadingModules[category] = this.config.globalProperties.$api
|
||||
.call(ApiPhrasen.loadCategory(category))
|
||||
.then(res => res?.data ? extractCategory(res.data, category) : {})
|
||||
.then(res => {
|
||||
categories[category] = res;
|
||||
});
|
||||
return loadingModules[category];
|
||||
},
|
||||
t_ref(category, phrase, params) {
|
||||
console.warn('deprecated');
|
||||
return Vue.computed(() => this.t(category, phrase, params));
|
||||
},
|
||||
t(category, phrase, params) {
|
||||
if (params === undefined && (
|
||||
(Array.isArray(category) && category.length == 2) ||
|
||||
(category.split && category.split('/').length == 2))
|
||||
) {
|
||||
params = phrase;
|
||||
[category, phrase] = category.split ? category.split('/') : category;
|
||||
}
|
||||
if (phrase === undefined) {
|
||||
console.error('invalid input', category, phrase, params);
|
||||
return '';
|
||||
}
|
||||
let val = Vue.computed(() => {
|
||||
if (!categories[category])
|
||||
return '';
|
||||
return getValueForLoadedPhrase(category, phrase, params);
|
||||
});
|
||||
if (!categories[category])
|
||||
this.loadCategory(category);
|
||||
return val.value;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
install(app, options) {
|
||||
app.use(PluginsApi);
|
||||
app.config.globalProperties.$p = {
|
||||
t: phrasen.t,
|
||||
loadCategory: cat => phrasen.loadCategory.call(app, cat),
|
||||
setLanguage: lang => phrasen.setLanguage.call(app, lang),
|
||||
user_language: user_language,
|
||||
user_locale,
|
||||
t_ref: phrasen.t_ref
|
||||
};
|
||||
app.provide('$p', app.config.globalProperties.$p);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
function doCopy (copy, original, copyArray) {
|
||||
// Callback function to iterate on array or object elements
|
||||
function callback (value, key) {
|
||||
// Copy the contents of objects
|
||||
if (Highcharts.isObject(value, !copyArray) &&
|
||||
!Highcharts.isClass(value) &&
|
||||
!Highcharts.isDOMElement(value)
|
||||
) {
|
||||
copy[key] = doCopy(copy[key] || Highcharts.isArray(value) ? [] : {}, value, copyArray)
|
||||
} else {
|
||||
// Primitives are copied over directly
|
||||
copy[key] = original[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (Highcharts.isArray(original)) {
|
||||
original.forEach(callback)
|
||||
} else {
|
||||
Highcharts.objectEach(original, callback)
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
const copyObject = function (obj, copyArray) {
|
||||
return doCopy({}, obj, copyArray)
|
||||
}
|
||||
|
||||
const highchartsPlugin = {
|
||||
|
||||
install(app, options) {
|
||||
|
||||
function destroyChart() {
|
||||
if (this.chart) {
|
||||
this.chart.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function generateVueComponent(Highcharts, VueVersion) {
|
||||
const VUE_MAJOR = VueVersion.split('.')[0]
|
||||
const VERSION_DEPENDENT_PROPS = VUE_MAJOR < 3
|
||||
? {
|
||||
// Fallback options for Vue v2 to keep backward compatibility.
|
||||
render: (createElement) => createElement('div', {
|
||||
ref: 'chart'
|
||||
}),
|
||||
beforeDestroy: destroyChart
|
||||
// The new Vue's 3 syntax.
|
||||
} : {
|
||||
render () {
|
||||
return Vue.h('div', { ref: 'chart' })
|
||||
},
|
||||
beforeUnmount: destroyChart
|
||||
}
|
||||
|
||||
return {
|
||||
template: '<div ref="chart"></div>',
|
||||
props: {
|
||||
constructorType: {
|
||||
type: String,
|
||||
default: 'chart'
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
callback: Function,
|
||||
updateArgs: {
|
||||
type: Array,
|
||||
default: () => [true, true]
|
||||
},
|
||||
highcharts: {
|
||||
type: Object
|
||||
},
|
||||
deepCopyOnUpdate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
handler (newValue) {
|
||||
this.chart.update(copyObject(newValue, this.deepCopyOnUpdate), ...this.updateArgs)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let HC = this.highcharts || Highcharts
|
||||
|
||||
// Check whether the chart configuration object is passed, as well as the constructor is valid.
|
||||
if (this.options && HC[this.constructorType]) {
|
||||
this.chart = HC[this.constructorType](
|
||||
this.$refs.chart,
|
||||
copyObject(this.options, true), // Always pass the deep copy when generating a chart. #80
|
||||
this.callback ? this.callback : null
|
||||
)
|
||||
} else {
|
||||
(!this.options) ? console.warn('The "options" parameter was not passed.') : console.warn(`'${this.constructorType}' constructor-type is incorrect. Sometimes this error is caused by the fact, that the corresponding module wasn't imported.`)
|
||||
}
|
||||
},
|
||||
...VERSION_DEPENDENT_PROPS
|
||||
}
|
||||
}
|
||||
|
||||
app.component(
|
||||
options.tagName || 'highcharts',
|
||||
generateVueComponent(options.highcharts || Highcharts, Vue.version)
|
||||
)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
export default highchartsPlugin
|
||||
Reference in New Issue
Block a user