created FhcBase.js plugin which coordinates time orderly setup between Alerts, Phrasen & Api Plugins. These have slightly rewritten Adaption as BaseAlerts.js, BasePhrase.js & BaseApi.js which have internal ready states activated once their required dependencies have been injected. Added await/async to several handlers to guarantee initialized cross dependencies in all plugins; While testing new Plugin Setup found a few legacy Instances of $fhcApi in Cis4 Studium.js that were only still working because plugins/Phrasen is still importing the legacyApi. Fixed that code and the corresponding errors in Studium.php that where now alerted into ui! wow! Also added FhcBase Plugin to all apps inside fhc-core/public/js/apps;

This commit is contained in:
Johann Hoffmann
2026-01-23 13:48:04 +01:00
parent ef8314c33a
commit 94eb7021bb
26 changed files with 1189 additions and 66 deletions
@@ -29,7 +29,7 @@ class Studium extends FHCAPI_Controller
public function __construct()
{
parent::__construct([
'getStudienAllSemester'=> self::PERM_LOGGED,
'getAllStudienSemester'=> self::PERM_LOGGED,
'getStudiengaengeForStudienSemester'=> self::PERM_LOGGED,
'getStudienplaeneBySemester'=> self::PERM_LOGGED,
'getLvEvaluierungInfo'=> self::PERM_LOGGED,
@@ -51,7 +51,7 @@ class Studium extends FHCAPI_Controller
//------------------------------------------------------------------------------------------------------------------
// Public methods
public function getStudienAllSemester(){
public function getAllStudienSemester(){
$parameter_studiensemester = $this->input->get('studiensemester',true);
$parameter_studiengang = $this->input->get('studiengang',true);
@@ -59,24 +59,26 @@ class Studium extends FHCAPI_Controller
$parameter_studienplan = $this->input->get('studienplan',true);
$aktuelles_studiensemester = current($this->getDataOrTerminateWithError($this->StudiensemesterModel->getAktOrNextSemester()));
if($this->getDataOrTerminateWithError($this->StudentModel->isStudent(getAuthUID()))){
$studentLehrverband =$this->StudentlehrverbandModel->loadWhere(["student_uid" => getAuthUID(), "studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz]);
$studentLehrverband = current($this->getDataOrTerminateWithError($studentLehrverband));
$lv_result =$this->StudentlehrverbandModel->loadWhere([
"student_uid" => getAuthUID(),
"studiensemester_kurzbz" => $aktuelles_studiensemester->studiensemester_kurzbz
]);
$lv_data = $this->getDataOrTerminateWithError($lv_result);
if ($studentLehrverband = current($lv_data)) {
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
$student_studiengang = $studentLehrverband->studiengang_kz;
$student_semester = $studentLehrverband->semester;
}
$student_studiensemester = $studentLehrverband->studiensemester_kurzbz;
$student_studiengang = $studentLehrverband->studiengang_kz;
$student_semester = $studentLehrverband->semester;
$student_studienplan = $this->getStudienPlanFromPrestudentStatus(getAuthPersonId())->studienplan_id;
if(!isset($parameter_studiensemester))
$parameter_studiensemester = $student_studiensemester;
if(!isset($parameter_studiengang))
$parameter_studiengang = $student_studiengang;
if(!isset($parameter_semester))
$parameter_semester = $student_semester;
if(!isset($parameter_studienplan))
$parameter_studienplan = $student_studienplan;
$parameter_studiensemester = $parameter_studiensemester ?? $student_studiensemester;
$parameter_studiengang = $parameter_studiengang ?? $student_studiengang;
$parameter_semester = $parameter_semester ?? $student_semester;
$parameter_studienplan = $parameter_studienplan ?? $student_studienplan;
}
if(isset($parameter_studiensemester)){
+37
View File
@@ -0,0 +1,37 @@
export default {
getAllStudienSemester(studiensemester=undefined, studiengang=undefined, semester=undefined, studienplan=undefined) {
return {
method: 'get',
url: '/api/frontend/v1/Studium/getAllStudienSemester',
params: {studiensemester, studiengang, semester, studienplan}
};
},
getStudiengaengeForStudienSemester(studiensemester) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getStudiengaengeForStudienSemester/${studiensemester}`,
};
},
getStudienplaeneBySemester(studiengang, studiensemester) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getStudienplaeneBySemester`,
params: {
studiengang,
studiensemester,
}
};
},
getLvPlanForStudiensemester(studiensemester_kurzbz, lvid) {
return {
method: 'get',
url: `/api/frontend/v1/LvPlan/getLvPlanForStudiensemester/${studiensemester}/${lvid}`,
};
},
getLvEvaluierungInfo(studiensemester_kurzbz, lvid) {
return {
method: 'get',
url: `/api/frontend/v1/Studium/getLvEvaluierungInfo/${studiensemester_kurzbz}/${lvid}`,
};
}
};
+4 -3
View File
@@ -1,9 +1,9 @@
import FhcSearchbar from "../components/searchbar/searchbar.js";
import CisMenu from "../components/Cis/Menu.js";
import PluginsPhrasen from '../plugins/Phrasen.js';
import ApiSearchbar from '../api/factory/searchbar.js';
import Theme from "../plugins/Theme.js";
import FhcBase from '../plugins/FhcBase/FhcBase.js';
// import PluginsPhrasen from '../plugins/Phrasen.js';
const app = Vue.createApp({
name: 'CisApp',
components: {
@@ -148,6 +148,7 @@ app.use(primevue.config.default, {
tooltip: 8000
}
})
app.use(PluginsPhrasen);
// app.use(PluginsPhrasen);
app.use(FhcBase);
app.use(Theme);
app.mount('#cis-header');
+2 -1
View File
@@ -1,5 +1,6 @@
import PluginsPhrasen from '../../plugins/Phrasen.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'DocumentsApp',
@@ -89,5 +90,5 @@ const app = Vue.createApp({
setScrollbarWidth();
app.use(PluginsPhrasen);
app.use(FhcBase);
app.mount('#content');
+2 -3
View File
@@ -1,7 +1,6 @@
import ProfilUpdateView from "../../components/Cis/ProfilUpdate/ProfilUpdateView.js";
import PluginsPhrasen from "../../plugins/Phrasen.js";
import ApiProfilUpdate from '../../api/factory/profilUpdate.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
// TODO: sobald in verwendung den vue router pfad zu ProfilUpdateView definieren und diese app in component auslagern
const app = Vue.createApp({
@@ -34,4 +33,4 @@ const app = Vue.createApp({
});
},
});
app.use(PluginsPhrasen).mount("#content");
app.use(FhcBase).mount("#content");
+2 -2
View File
@@ -1,6 +1,6 @@
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import DashboardAdmin from '../../components/Dashboard/Admin.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'AdminApp',
@@ -12,5 +12,5 @@ const app = Vue.createApp({
DashboardAdmin
}
});
app.use(PluginsPhrasen);
app.use(FhcBase);
app.mount('#main');
+3 -2
View File
@@ -1,5 +1,6 @@
import FhcDashboard from '../../components/Dashboard/Dashboard.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from '../../plugins/FhcBase/FhcBase.js';
import Theme from '../../plugins/Theme.js';
import contrast from '../../directives/contrast.js';
import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js";
@@ -330,7 +331,7 @@ app.use(primevue.config.default, {
}
})
app.directive('tooltip', primevue.tooltip);
app.use(PluginsPhrasen);
app.use(FhcBase);
app.use(Theme);
app.directive('contrast', contrast);
app.mount('#fhccontent');
+2 -2
View File
@@ -1,6 +1,6 @@
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
import DashboardAdmin from '../components/Dashboard/Admin.js';
import Phrases from "../plugin/Phrasen.js"
import FhcBase from "../plugins/FhcBase/FhcBase.js";
Vue.createApp({
name: 'DashboardAdminApp',
@@ -13,4 +13,4 @@ Vue.createApp({
},
mounted() {
}
}).use(Phrases).mount('#main');
}).use(FhcBase).mount('#main');
+2 -2
View File
@@ -1,5 +1,5 @@
import LVVerwaltung from "../components/LVVerwaltung/LVVerwaltung.js";
import Phrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -98,5 +98,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -2
View File
@@ -21,7 +21,7 @@ import {LogsViewerTabulatorEventHandlers} from './TabulatorSetup.js';
import {CoreFilterCmpt} from '../../components/filter/Filter.js';
import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js';
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const logsViewerApp = Vue.createApp({
data: function() {
@@ -42,5 +42,5 @@ const logsViewerApp = Vue.createApp({
}
});
logsViewerApp.use(PluginsPhrasen).mount('#main');
logsViewerApp.use(FhcBase).mount('#main');
+2 -2
View File
@@ -1,7 +1,7 @@
//TODO Manu
//use this instead of Nachrichten.js
import NewMessage from "../../components/Messages/Details/NewMessage/NewDiv.js";
import Phrasen from "../../plugin/Phrasen.js";
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -23,5 +23,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -3
View File
@@ -1,6 +1,5 @@
import NewMessage from "../components/Messages/Details/NewMessage/NewDiv.js";
import Phrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -23,5 +22,5 @@ app
overlay: 1100
}
})
.use(Phrasen)
.use(FhcBase)
.mount('#main');
+2 -5
View File
@@ -16,10 +16,7 @@
*/
import FhcStudentenverwaltung from "../components/Stv/Studentenverwaltung.js";
import fhcapifactory from "./api/fhcapifactory.js";
import PluginsPhrasen from "../plugins/Phrasen.js";
import FhcBase from "../plugins/FhcBase/FhcBase.js";
const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
@@ -217,5 +214,5 @@ app
overlay: 1100
}
})
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#main');
+2 -2
View File
@@ -1,7 +1,7 @@
import StudierendenantragAntrag from "../../components/Studierendenantrag/Antrag.js";
import StudierendenantragStatus from "../../components/Studierendenantrag/Status.js";
import StudierendenantragInfoblock from "../../components/Studierendenantrag/Infoblock.js";
import PluginsPhrasen from '../../plugins/Phrasen.js';
import FhcBase from "../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'AntragApp',
@@ -21,5 +21,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import StudierendenantragLeitung from '../../../components/Studierendenantrag/Leitung.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'LeitungApp',
@@ -8,6 +8,6 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.use(primevue.config.default,{zIndex: {overlay: 9999}})
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import LvZuweisung from '../../../components/Studierendenantrag/Lvzuweisung.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'LvzuweisungApp',
@@ -13,5 +13,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
+2 -2
View File
@@ -1,5 +1,5 @@
import LvPopup from '../../../components/Studierendenantrag/Leitung/LvPopup.js';
import PluginsPhrasen from '../../../plugins/Phrasen.js';
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const app = Vue.createApp({
name: 'StudentApp',
@@ -8,5 +8,5 @@ const app = Vue.createApp({
}
});
app
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#wrapper');
@@ -17,7 +17,7 @@
import LvTemplateUebersicht from '../../../lehre/lvplanung/LvTemplateUebersicht.js';
import {CoreNavigationCmpt} from '../../../components/navigation/Navigation.js';
import PluginsPhrasen from "../../../plugins/Phrasen.js";
import FhcBase from "../../../plugins/FhcBase/FhcBase.js";
const lvTemplatesApp = Vue.createApp({
@@ -30,5 +30,5 @@ const lvTemplatesApp = Vue.createApp({
lvTemplatesApp
.use(primevue.config.default,{zIndex: {overlay: 9999}})
.use(PluginsPhrasen)
.use(FhcBase)
.mount('#main')
-1
View File
@@ -3,7 +3,6 @@ import FhcSearchbar from "../searchbar/searchbar.js";
import CisSprachen from "./Sprachen.js"
import ThemeSwitch from "./ThemeSwitch.js";
import ApiCisMenu from '../../api/factory/cis/menu.js';
export default {
components: {
CisMenuEntry,
@@ -2,9 +2,10 @@ import BsModal from "../../Bootstrap/Modal.js";
import LvMenu from "./LvMenu.js";
import ApiAddons from '../../../api/factory/addons.js';
import ApiStudium from '../../../api/factory/studium.js';
export default {
name: "LvUebersicht",
props:{
event:{
type:Object,
@@ -63,7 +64,7 @@ export default {
// check lv evaluierung info
if (this.studium_studiensemester) {
this.$fhcApi.factory.studium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
this.$api.call(ApiStudium.getLvEvaluierungInfo(this.studium_studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
.then(data => data.data)
.then(res => {
this.lvEvaluierungMessage = res.message;
@@ -72,7 +73,7 @@ export default {
// check if the lv has lvplan entries for this studiensemester
if (this.studiensemester && this.event) {
return this.$fhcApi.factory.studium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id)
this.$api.call(ApiStudium.getLvPlanForStudiensemester(this.studiensemester, this.event.lehreinheit_id ?? this.event.lehrveranstaltung_id))
.then(data => data.data)
.then(res => {
if (Array.isArray(res) && res.length > 0) {
@@ -31,7 +31,7 @@ export default {
this.event.lektor.slice(0, 3).map(lektor => lektor.kurzbz).join("\n")
+ "\n" + this.$p.t('lehre/weitereLektoren', [this.event.lektor.length - 3])
].join(": "));
} else {console.log(this.event.lektor);
} else {
tooltipArray.push([
this.$p.t('lehre/lektor'),
this.event.lektor.map(lektor => lektor.kurzbz).join("\n")
+8 -7
View File
@@ -1,7 +1,8 @@
import LvUebersicht from "../Mylv/LvUebersicht.js";
import ApiStudium from '../../../api/factory/studium.js';
export default {
name: "Studium",
data(){
return {
studienSemester :[],
@@ -97,28 +98,28 @@ export default {
return value;
},
changeSelectedStudienSemester(studiensemester_kurzbz) {
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester_kurzbz, this.selectedStudiengang, this.selectedSemester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedStudienGang(studiengang_kz) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, studiengang_kz, this.selectedSemester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedSemester(semester) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, semester, this.selectedStudienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
})
},
changeSelectedStudienPlan(studienplan_id) {
this.$fhcApi.factory.studium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id)
this.$api.call(ApiStudium.getAllStudienSemester(this.selectedStudiensemester, this.selectedStudiengang, this.selectedSemester, studienplan_id))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
@@ -256,8 +257,8 @@ export default {
const studienordnung = JSON.parse(this.getDataFromLocalStorage("studienordnung")) ?? undefined;
// only fetch default data if no data is stored in the local storage
this.$fhcApi.factory.studium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung)
this.$api.call(ApiStudium.getAllStudienSemester(studiensemester, studiengang, semester, studienordnung))
.then(data => data.data)
.then(res => {
this.extractPropertyValues(res);
+359
View File
@@ -0,0 +1,359 @@
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";
export default {
init(app, $p) {
let resolveReady;
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
const helperAppContainer = document.createElement('div');
document.body.appendChild(helperAppContainer);
const helperApp = Vue.createApp({
name: "FhcAlertApp",
components: { PvToast, PvConfirm },
methods: {
mailToUrl(slotProps) {
let mailTo = FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto;
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();
}
},
computed: {
showmaillink() { return FHC_JS_DATA_STORAGE_OBJECT.systemerror_mailto !== ''; }
},
template: /* html */`
<pv-toast ref="toast" class="fhc-alert" :base-z-index="99999">
<template #message="{ message }">
<!--span :class="slotProps.iconClass"></span-->
<div class="p-toast-message-text">
<span class="p-toast-summary">{{ message.summary }}</span>
<div v-if="message.detail && message.html" class="p-toast-detail" v-html="message.detail"></div>
<div v-else-if="message.detail" class="p-toast-detail">{{ message.detail }}</div>
</div>
</template>
</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
v-if="showmaillink"
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 alertCollapseText">
{{slotProps.message.detail}}
</div>
</div>
</div>
</template>
</pv-toast>
<pv-confirm group="fhcAlertConfirm"></pv-confirm>`
});
// Link the helper app to the main app's phrasen system
helperApp.config.globalProperties.$p = $p;
helperApp.use(PvConfig);
helperApp.use(PvConfirmationService);
const helperAppInstance = helperApp.mount(helperAppContainer);
const $fhcAlert = {
// Internal storage for cross-plugin dependencies
deps: { $p },
ready: readyPromise,
// Method used by FhcBase to inject the API later
setDeps(deps) {
Object.assign(this.deps, deps);
// Alert is ready when both Phrases and API are present
if (this.deps.$p && this.deps.$api) resolveReady();
},
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 });
},
async alertSystemError(message) {
//TODO(Manu) for translation of content of template: restructure in data
//and update definitions with translations
await this.ready;
if (Array.isArray(message))
return message.forEach(this.alertSystemError);
helperAppInstance.$refs.alert.add({
severity: 'error',
summary: Vue.computed(() => this.deps.$p.t('alert/systemerror')),
detail: message});
},
async confirmDelete() {
await this.ready;
return new Promise((resolve, reject) => {
helperAppInstance.$confirm.require({
group: 'fhcAlertConfirm',
header: Vue.computed(() => this.deps.$p.t('alert/attention')),
message: Vue.computed(() => this.deps.$p.t('alert/confirm_delete')),
acceptLabel: Vue.computed(() => this.deps.$p.t('ui/loeschen')),
acceptClass: 'p-button-danger',
rejectLabel: Vue.computed(() => this.deps.$p.t('ui/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 ?? Vue.computed(() => this.deps.$p.t('alert/attention')),
message: options?.message ?? '',
acceptLabel: options?.acceptLabel ?? 'Ok',
acceptClass: options?.acceptClass ?? 'btn btn-primary',
rejectLabel: options?.rejectLabel ?? Vue.computed(() => this.deps.$p.t('ui/abbrechen')),
rejectClass: options?.rejectClass ?? 'btn btn-outline-secondary',
accept() {
resolve(true);
},
reject() {
resolve(false);
},
});
});
},
alertDefault(severity, title, message, sticky = false, html = false) {
let options = { severity: severity, summary: title, detail: message, html };
if (!sticky)
options.life = 3000;
helperAppInstance.$refs.toast.add(options);
},
alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false, html = false){
// Check, if array has only string values
if (messageArray.every(message => typeof message === 'string')) {
messageArray.forEach(message => this.alertDefault(severity, title, message, sticky, html));
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 this.alertSystemError(error);
// Error is array of strings
if (Array.isArray(error) && error.every(err => typeof err === 'string'))
return error.every(this.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 this.alertSystemError(errMsg);
}
// Fallback
this.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 this.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(this.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')) {
this.alertWarning(JSON.stringify(msg.data.retval));
} else {
this.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
this.alertWarning(JSON.stringify(message.data.retval));
} else {
this.alertSystemError(JSON.stringify(message));
}
return;
}
// Fallback
this.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 => this.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
this.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(this.alertError);
}
return;
}
}
if (error?.response?.status == 400) {
let errors = CoreRESTClient.getError(error.response.data);
this.alertError((typeof errors === 'object') ? Object.values(errors) : errors);
} else {
this.handleSystemError(error);
}
}
};
return $fhcAlert;
}
};
+589
View File
@@ -0,0 +1,589 @@
/**
* BaseApi.js - Contains the original api plugin methods call, get, post, etc.
* Dependencies like phrasen($p) or alert($fhcAlert) are optional and can be injected at initialization
* or at a later stage.
*/
export class BaseApi {
constructor(deps = {}) {
// optional dependencies like { $fhcAlert: ..., $p: ... }
this.deps = deps;
this.resolveReady = null;
this.ready = new Promise(resolve => { this.resolveReady = resolve; });
// If deps were passed in constructor, resolve immediately
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
this.setupDefaultConfig()
this.axiosInstance = axios.create({
timeout: 500000,
baseURL: FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + "/"
});
this.setupInterceptors();
}
// === public functions START ===
// in case of api instantiation before vue app mount and later usage via fhcBase plugin
setDependencies(deps) {
Object.assign(this.deps, deps);
if (this.deps.$fhcAlert && this.deps.$p) this.resolveReady();
}
getUri(url) {
return this.axiosInstance.getUri({ url });
}
get(form, uri, params, config) {
[uri, params, config] = this.get_config(form, uri, params, config);
if (params) {
config = config ? { ...config, params } : { params };
}
return this.axiosInstance.get(uri, config);
}
post(form, uri, data, config) {
[uri, data, config] = this.get_config(form, uri, data, config);
return this.axiosInstance.post(uri, data, config);
}
getErrorHandler(config) {
return this.get_error_handler(config);
}
async call(factory, configoverwrite, form) {
if (Array.isArray(factory)) {
return Promise.allSettled(factory.map((config, index) => {
if (!Array.isArray(config)) config = ['#' + index, config];
return this.call(config[1], { errorHeader: config[0], errorHandling: false });
})).then(result => {
const [ , , config ] = this.get_config(form, undefined, undefined, configoverwrite || {});
const errorConfig = this.get_error_handler(config);
if (!errorConfig.success && !errorConfig.fail) {
return result;
}
const typedErrors = {};
for (let res of result) {
const [ allowed, item ] = res.status === 'fulfilled'
? [ errorConfig.success, res.value ]
: [ errorConfig.fail, res.reason ];
if (!allowed)
return;
const errors = this.popHandleableErrors(errorConfig, this.get_error_list(item));
for (let type in errors) {
if (!typedErrors[type])
typedErrors[type] = {
[item.config.errorHeader]: errors[type]
};
else
typedErrors[type][item.config.errorHeader] = errors[type];
}
};
for (let errType in typedErrors) {
errorConfig.handler[errType](typedErrors[errType]);
}
return result;
});
}
let { method = 'get', url, params, config } = factory;
if (configoverwrite !== undefined) config = configoverwrite;
method = method.toLowerCase();
return method === 'post' ? this.post(form, url, params, config) : this.get(form, url, params, config);
}
// === public functions END ===
// === private functions START ===
setupInterceptors() {
this.axiosInstance.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;
});
this.axiosInstance.interceptors.response.use(
response => {
if (response.config?.errorHandling == 'off'
|| response.config?.errorHandling === false
|| response.config?.errorHandling == 'fail')
return this.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'] || this.DEFAULT_ERROR_CONFIG.handler[err.type])(err, response.config)
);
return this.clean_return_value(response);
},
error => {
if (error.code == 'ERR_CANCELED')
return Promise.reject({ handled: true, ...error });
const errorConfig = this.get_error_handler(error.config);
if (!errorConfig.fail)
return Promise.reject(error);
const remaining = this.get_error_list(error);
const errors = this.popHandleableErrors(errorConfig, remaining);
for (let type in errors) {
errorConfig.handler[type](errors[type]);
}
if (remaining.length)
return Promise.reject(error);
return Promise.reject({ handled: true, ...error });
}
);
}
setupDefaultConfig() {
this.DEFAULT_ERROR_CONFIG = {
success: true,
fail: true,
combine: {
form: ['validation', 'general'],
toast: ['validation', 'general', 'not_found', 'site_failed']
},
handler: {
form: (form, errors) => {
form.clearValidation();
errors.forEach(err => form.setFeedback(
false,
err.messages || err.message
));
},
toast: async (errors) => {
await this.ready;
async function _format_toast(errors) {
errors = errors.reduce((result, err) => {
switch (err.type) {
case 'not_found':
case 'site_failed':
if (err.message)
result[err.message] = [err.url];
else
result._default = [err.url];
break;
case 'general':
if (!result._default)
result._default = [];
result._default.push(err.message);
break;
case 'validation':
Object.entries(err.messages)
.forEach(([field, msg]) => {
if (!result[field])
result[field] = [];
if (Array.isArray(msg))
result[field].push(...msg);
else
result[field].push(msg);
});
break;
}
return result;
}, {});
let counter = 0;
const msgs = await Promise.all(Object.entries(errors)
.sort((a, b) => ['_default'].indexOf(b[0]) - ['_default'].indexOf(a[0])) // sort _default first
.map(async ([field, msgs]) => {
if (field == '_default') {
await this.deps.$p.loadCategory('dashboard');
const general = this.deps.$p.t('dashboard/general');
field = '<dt class="d-none">' + general + '</dt>';
} else {
field = '<dt>' + field + '</dt>';
}
counter += msgs.length;
return field
+ '<dd>'
+ msgs.join('</dd><dd>')
+ '</dd>';
}));
return {
counter,
msgs
}
}
let counter, msgs;
if (Array.isArray(errors)) {
({ counter, msgs } = await _format_toast(errors));
} else {
({ counter, msgs } = await Object.entries(errors)
.reduce(async (res, [title, errs]) => {
const result = await res;
const { counter, msgs } = await _format_toast(errs);
result.counter += counter;
result.msgs.push('<dt>'
+ title
+ '</dt><dd><dl>'
+ msgs.join('')
+ '</dl></dd>');
return result;
}, Promise.resolve({ counter: 0, msgs: []})));
}
await this.deps.$p.loadCategory('ui');
const n_errors = this.deps.$p.t('ui/n_errors', { n: counter });
this.deps.$fhcAlert.alertDefault(
'error',
n_errors,
'<dl>' + msgs.join('') + '</dl>',
true,
true
);
},
php: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
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':
if (title)
title += ': PHP ' + error.severity;
else
title = 'PHP ' + error.severity;
this.deps.$fhcAlert.alertDefault('warn', title, message, true);
break;
case 'Notice':
case 'User Notice':
case 'Runtime Notice':
if (title)
title += ': PHP ' + error.severity;
else
title = 'PHP ' + error.severity;
this.deps.$fhcAlert.alertDefault('info', title, message, true);
break;
default:
message = 'Type: PHP ' + error.severity + '\n\n' + message;
if (title)
message = title + '\n\n' + message;
this.deps.$fhcAlert.alertSystemError(message);
break;
}
});
},
exception: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
var message = '';
if (title)
message += title + '\n\n';
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';
});
}
this.deps.$fhcAlert.alertSystemError(message);
});
},
db: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
var message = '';
if (title)
message += title + '\n\n';
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';
this.deps.$fhcAlert.alertSystemError(message);
});
},
auth: async (errors) => {
await this.ready;
this._send_array_or_object(errors, (error, title) => {
if (title)
title += ': ' + error.message;
else
title = error.message;
var message = '';
message += 'Controller name: ' + error.controller + '\n';
message += 'Method name: ' + error.method + '\n';
message += 'Required permissions: ' + error.required_permissions;
this.deps.$fhcAlert.alertDefault(
'error',
title,
message,
true
);
});
}
}
};
}
_send_array_or_object(errors, func) {
if (!errors) return;
if (Array.isArray(errors)) {
errors.forEach(error => func(error));
return;
}
// Handle Single Error Object
if (errors.type) {
func(errors);
return;
}
// Handle Category Container
Object.entries(errors).forEach(([title, value]) => {
const errorList = Array.isArray(value) ? value : [value];
errorList.forEach(error => {
if (error && typeof error === 'object') {
func(error, title);
}
});
});
}
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];
}
clean_return_value(response) {
if (typeof response.data === 'string' || response.data instanceof String)
return this.clean_return_value({ data: response });
const result = response.data;
delete response.data;
if (!result)
return {meta: {response}, data: null};
if (!result.meta)
result.meta = { response };
else
result.meta.response = response;
return result;
}
merge_error_config(config) {
if (config === false || config === 'off')
return { ...this.DEFAULT_ERROR_CONFIG, success: false, fail: false };
if (!config || config === true)
return { ...this.DEFAULT_ERROR_CONFIG };
if (config === 'success')
return { ...this.DEFAULT_ERROR_CONFIG, fail: false };
if (config === 'fail')
return { ...this.DEFAULT_ERROR_CONFIG, success: false };
const { success, fail, handler, combine } = config;
config = { ...this.DEFAULT_ERROR_CONFIG };
Object.entries({ fail, success }).forEach(([key, value]) => {
if (value !== undefined)
config[key] = value;
});
Object.entries({ handler, combine }).forEach(([key, value]) => {
if (value !== undefined)
config[key] = { ...config[key], ...value };
});
return config;
}
get_error_handler(config) {
const result = this.merge_error_config(config?.errorHandling);
if (!config?.form) {
result.combine = { ...result.combine, form: [] };
} else {
const formHandler = result.handler.form;
result.handler = { ...result.handler, form: errors => formHandler(config.form, errors) };
}
return result;
}
get_error_list(error) {
if (error.response) {
if (error.response.status == 404) {
return [{
type: 'not_found',
message: error.message,
url: error.request.responseURL
}];
} else {
if (error.response.data.errors == undefined) return [];
return error.response.data.errors;
}
} else if (error.request) {
return [{
type: 'site_failed',
message: error.message,
url: error.request.responseURL
}];
} else {
return [{
type: 'script',
message: error.message
}];
}
}
popHandleableErrors(errorHandling, errors) {
const result = {};
const copy = [];
if (errors == undefined) return {};
while (errors.length)
copy.push(errors.pop());
for (let error of copy) {
let type = error.type;
let newType = null;
for (let t in errorHandling.combine) {
let newTypeCombinesType = errorHandling
.combine[t]
.includes(type);
let newTypeHasHandler = errorHandling.handler[t];
if (newTypeCombinesType && newTypeHasHandler) {
newType = t;
if (newType == 'form')
break;
}
}
if (newType)
type = newType;
const handler = errorHandling.handler[type];
if (handler) {
if (!result[type])
result[type] = [];
if (Array.isArray(error))
result[type].push(...error);
else
result[type].push(error);
continue;
}
errors.push(error);
}
return result;
}
// === private functions END ===
}
+100
View File
@@ -0,0 +1,100 @@
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 const user_locale = Vue.computed(() => {
if (!user_language.value) return null;
return FHC_JS_DATA_STORAGE_OBJECT.server_languages.find(l => l.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;
}
export default {
init(app) {
// Create a controller to resolve the promise later
let resolveReady;
const readyPromise = new Promise(resolve => { resolveReady = resolve; });
const $p = {
deps: {},
ready: readyPromise,
setDeps(deps) {
Object.assign(this.deps, deps);
// Once we have the API, we are ready to load data
if (this.deps.$api) resolveReady();
},
user_language,
user_locale,
async loadCategory(category) {
if (Array.isArray(category))
return Promise.all(category.map(cat => this.loadCategory(cat)));
// 2. SAFETY: Check if API is available via deps
await this.ready;
if (!loadingModules[category])
loadingModules[category] = this.deps.$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;
},
async setLanguage(language) {
await this.ready;
const catArray = Object.keys(categories);
return this.deps.$api.call(ApiPhrasen.setLanguage(catArray, language)).then(res => {
res.data.forEach(row => { categories[row.category][row.phrase] = row.text; });
user_language.value = language;
return res;
});
}
};
return $p;
}
};
+37
View File
@@ -0,0 +1,37 @@
import Phrasen from './BasePhrasen.js';
import Alert from './BaseAlert.js';
import { BaseApi } from './BaseApi.js';
export default {
install(app, options = {}) {
// init in order
const $p = Phrasen.init(app);
const $fhcAlert = Alert.init(app, $p);
// try to reuse existing CoreRESTClient api instance if one has been active since before
// fhcBase Plugin install
let $api = app.config.globalProperties.$api;
if (!($api instanceof BaseApi)) {
$api = new BaseApi({ $fhcAlert, $p }, options);
} else {
// If api existed pre-app install
$api.setDependencies({ $fhcAlert, $p });
}
// set ready promise for awaiting async functions
$p.setDeps({ $api, $fhcAlert });
$fhcAlert.setDeps({ $api });
// globalProperties Binding & provide
app.config.globalProperties.$p = $p;
app.config.globalProperties.$fhcAlert = $fhcAlert;
app.config.globalProperties.$api = $api;
app.config.globalProperties.$fhcApi = $api;
app.provide('$api', $api);
app.provide('$fhcApi', $api);
app.provide('$p', $p);
app.provide('$fhcAlert', $fhcAlert);
}
};