Compare commits

..

3 Commits

Author SHA1 Message Date
adisposkofh fac320bfce minor fix 2026-04-21 11:20:38 +02:00
adisposkofh 6c2820f900 minor fix 2026-04-21 11:19:36 +02:00
adisposkofh 6a53c9fae4 fixed multiple news page issues 2026-04-20 15:54:24 +02:00
4 changed files with 488 additions and 518 deletions
+114 -92
View File
@@ -2,115 +2,137 @@ import Pagination from "../../Pagination/Pagination.js";
import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js";
import BsConfirm from "../../Bootstrap/Confirm.js";
import ApiCms from '../../../api/factory/cms.js';
import ApiCms from "../../../api/factory/cms.js";
export default {
name: "NewsComponent",
components: {
Pagination,
StudiengangInformation,
},
data() {
return {
content: null,
maxPageCount: 0,
page_size: 10,
page:1,
};
},
watch:{
'$p.user_language.value':function(sprache){
this.fetchNews();
}
},
computed:{
sprache: function(){
return this.$p.user_language.value;
components: {
Pagination,
StudiengangInformation,
},
},
methods: {
fetchNews() {
return this.$api
.call(ApiCms.getNews(this.page, this.page_size, this.sprache))
.then(res => res.data)
.then(result => {
this.content = result;
inject: ["isMobile"],
data() {
return {
content: null,
maxPageCount: 0,
page_size: 10,
page: 1,
};
},
watch: {
"$p.user_language.value": function (sprache) {
this.fetchNews();
},
},
computed: {
sprache: function () {
return this.$p.user_language.value;
},
},
methods: {
async fetchNews() {
let newsResponse = await this.$api.call(
ApiCms.getNews(this.page, this.page_size, this.sprache),
);
this.content = newsResponse.data;
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
el.addEventListener("click", (evt) => {
evt.preventDefault();
BsConfirm.popup(el.dataset.confirm)
.then(() => {
Axios.get(el.href)
.then((res) => {
// TODO(chris): check for success then show message and/or reload
location = location;
})
.catch((err) => console.error("ERROR:", err));
document.querySelectorAll("#cms [data-confirm]").forEach((el) => {
el.addEventListener("click", (evt) => {
evt.preventDefault();
BsConfirm.popup(el.dataset.confirm)
.then(() => {
Axios.get(el.href)
.then((res) => {
// TODO(chris): check for success then show message and/or reload
location = location;
})
.catch(() => {
});
});
});
document.querySelectorAll("#cms [data-href]").forEach((el) => {
el.href = el.dataset.href.replace(
/^ROOT\//,
FHC_JS_DATA_STORAGE_OBJECT.app_root
);
});
Vue.nextTick(()=>{
document.querySelectorAll(".card-header").forEach((el) => {
el.classList.add("fhc-primary");
});
document.querySelectorAll(".row").forEach((el) => {
el.classList.add("w-100");
el.classList.add("align-items-center");
});
document.querySelectorAll(".row h2").forEach((el) => {
el.classList.add("mb-0");
});
.catch((err) => console.error("ERROR:", err));
})
.catch(() => {});
});
});
document.querySelectorAll("#cms [data-href]").forEach((el) => {
el.href = el.dataset.href.replace(
/^ROOT\//,
FHC_JS_DATA_STORAGE_OBJECT.app_root,
);
});
})
await this.$nextTick();
this.formatExternalHtml();
},
async loadNewPageContent(data) {
let newsResponse = await this.$api.call(
ApiCms.getNews(data.page, data.rows),
);
this.content = newsResponse.data;
await this.$nextTick();
this.formatExternalHtml();
},
formatExternalHtml() {
document
.querySelectorAll(".news-list-item .card-header")
.forEach((el) => {
el.classList.add("fhc-primary");
});
document.querySelectorAll(".news-list-item .row").forEach((el) => {
el.classList.add("w-100");
el.classList.add("align-items-center");
});
document
.querySelectorAll(".news-list-item .row h2")
.forEach((el) => {
el.classList.add("mb-0");
});
},
loadNewPageContent(data) {
this.$api
.call(ApiCms.getNews(data.page, data.rows))
.then(res => res.data)
.then(result => {
this.content = result;
});
}
},
created() {
this.fetchNews();
afterPageUpdated(event) {
this.page = event.page;
this.page_size = event.rows;
this.$refs.newsPageHeading.scrollIntoView({block: 'end'});
this.loadNewPageContent(event);
},
},
created() {
this.fetchNews();
this.$api
.call(ApiCms.getNewsRowCount())
.then(res => res.data)
.then(result => {
.then((res) => res.data)
.then((result) => {
this.maxPageCount = result;
});
},
template: /*html*/ `
<h2 class="fhc-primary-color">News</h2>
<hr/>
<pagination v-show="content?true:false" :page_size="page_size" @page="page=$event.page; loadNewPageContent($event)" :maxPageCount="maxPageCount">
</pagination>
<div class="container-fluid mt-4">
<div class="row">
<div class="col" v-html="content">
</div>
<div class="col-auto">
<div style="width:15rem">
<studiengang-information></studiengang-information>
},
template: /*html*/ `
<div :class="{'pb-3': isMobile}" class="overflow-x-hidden">
<h2 ref="newsPageHeading" class="fhc-primary-color">News</h2>
<hr/>
<pagination
v-show="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
:maxPageCount="maxPageCount"
></pagination>
<div class="container-fluid mt-4">
<div class="row">
<div class="col" v-html="content">
</div>
<div class="col-auto">
<div style="width:15rem">
<studiengang-information></studiengang-information>
</div>
</div>
</div>
</div>
<pagination
v-show="content?true:false"
:page="page"
:page_size="page_size"
@pageUpdated="afterPageUpdated($event)"
:maxPageCount="maxPageCount"
></pagination>
</div>
<pagination v-show="content?true:false" :page_size="page_size" @page="loadNewPageContent" :maxPageCount="maxPageCount">
</pagination>
`,
};
+20 -47
View File
@@ -30,7 +30,6 @@ export default {
menuOpen:true,
};
},
inject: ["isMobile"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -59,7 +58,7 @@ export default {
},
site_url(){
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
},
}
},
methods: {
fetchMenu() {
@@ -113,26 +112,10 @@ export default {
});
},
template: /*html*/`
<div
id="header-options-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div class="d-flex flex-row align-items-center gap-2 h-100" style="width: 79px">
<button id="nav-main-btn" class="navbar-toggler rounded-0 px-2 border-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<span v-if="isMobile" class="d-flex flex-row align-items-center">
<theme-switch></theme-switch>
</span>
</div>
</div>
<fhc-searchbar
:searchoptions="searchbaroptions"
:searchfunction="searchfunction"
ref="searchbar"
id="nav-search"
class="fhc-searchbar w-100 py-1 py-lg-2"
></fhc-searchbar>
<button id="nav-main-btn" class="navbar-toggler rounded-0" type="button" data-bs-toggle="offcanvas" data-bs-target="#nav-main" aria-controls="nav-main" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<fhc-searchbar ref="searchbar" id="nav-search" class="fhc-searchbar w-100 py-1 py-lg-2" :searchoptions="searchbaroptions" :searchfunction="searchfunction"></fhc-searchbar>
<div id="nav-logo" class="d-none d-lg-block">
<div class="d-flex h-100 justify-content-between">
<a :href="rootUrl">
@@ -141,32 +124,22 @@ export default {
<theme-switch></theme-switch>
</div>
</div>
<div
id="header-usermenu-collapsible"
class="collapse multi-collapse collapse-horizontal show"
>
<div
:style="!isMobile ? '' : 'width: 51px'"
id="nav-user"
>
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
<div id="nav-user">
<button id="nav-user-btn" class="btn btn-link rounded-0" type="button" data-bs-toggle="collapse" data-bs-target="#nav-user-menu" aria-expanded="false" aria-controls="nav-user-menu">
<img :src="avatarUrl" :alt="$p.t('profilUpdate/profilBild')" class="bg-dark avatar rounded-circle border border-dark"/>
</button>
<ul ref="navUserDropdown"
@[\`shown.bs.collapse\`]="handleShowNavUser"
@[\`hide.bs.collapse\`]="handleHideNavUser"
id="nav-user-menu" class="top-100 end-0 collapse list-unstyled" aria-labelledby="nav-user-btn">
<li><a class="fhc-dark-bg btn rounded-0 d-block" :href="site_url + '/Cis/Profil'" id="menu-profil">Profil</a></li>
<li >
<cis-sprachen @languageChanged="fetchMenu"></cis-sprachen>
</li>
<li><hr class="dropdown-divider m-0 "></li>
<li ><a class="fhc-dark-bg btn rounded-0 d-block" :href="logoutUrl">Logout</a></li>
</ul>
</div>
<nav id="nav-main" class="offcanvas offcanvas-start" tabindex="-1" aria-labelledby="nav-main-btn" data-bs-backdrop="false">
<div id="nav-main-sticky">
<div id="nav-main-toggle" class="position-static d-none d-lg-block ">
+52 -29
View File
@@ -1,37 +1,60 @@
export default {
components: {
paginator: primevue.paginator,
},
emits: ["update:rows"],
props: {
maxPageCount: {
type: Number,
default: 0,
},
page_size: {
type: Number,
required: true,
},
},
data() {
return {};
},
methods: {
newPageEvent: function (data) {
},
},
mounted() {},
template: /*html*/ `
<!-- Desktop -->
components: {
paginator: primevue.paginator,
},
emits: ["pageUpdated"],
props: {
maxPageCount: {
type: Number,
default: 0,
},
page_size: {
type: Number,
required: true,
},
page: {
type: Number | null,
default: null,
},
},
data() {
return {
first: 0,
rowsPerPageOptions: [10, 20, 30],
};
},
watch: {
page(newValue) {
this.first = (newValue - 1) * this.$props.page_size;
},
},
methods: {
afterPageUpdated(data) {
this.$emit("pageUpdated", { ...data, page: data.page + 1 });
this.first = data.page * this.$props.page_size;
},
},
template: /*html*/ `
<!-- Desktop -->
<div class="d-none d-md-block">
<paginator v-model:rows="page_size" @page="(data)=>$emit('page',{...data, page:data.page+1})" :rows="page_size" :totalRecords="maxPageCount" :rowsPerPageOptions="[10, 20, 30]" >
</paginator>
<paginator
v-model:rows="page_size"
v-model:first="first"
@page="afterPageUpdated($event)"
:totalRecords="maxPageCount"
:rowsPerPageOptions="rowsPerPageOptions"
></paginator>
</div>
<!-- Mobile -->
<div class="d-block d-md-none">
<paginator v-model:rows="page_size" @page="(data)=>$emit('page',{...data, page:data.page+1})" :rows="page_size" :totalRecords="maxPageCount" :rowsPerPageOptions="[10, 20, 30]" template="FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink RowsPerPageDropdown">
</paginator>
<paginator
v-model:rows="page_size"
v-model:first="first"
@page="afterPageUpdated($event)"
:totalRecords="maxPageCount"
:rowsPerPageOptions="rowsPerPageOptions"
template="FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink RowsPerPageDropdown"
></paginator>
</div>
`,
};
+302 -350
View File
@@ -21,71 +21,69 @@ export default {
dms,
cms,
mergedStudent,
mergedPerson,
mergedPerson
},
props: {
searchoptions: {
type: Object,
required: true,
},
searchfunction: {
type: Function,
required: true,
},
showBtnSubmit: Boolean,
},
provide() {
return {
query: Vue.computed(() => this.lastQuery),
};
},
inject: ["isMobile"],
data: function () {
return {
searchtimer: null,
hidetimer: null,
searchsettings: {
searchstr: this.getSearchStr(),
types: this.getInitiallySelectedTypes(),
},
searchresult: [],
searchmode: "",
showresult: false,
searching: false,
error: null,
abortController: null,
props: {
searchoptions: {
type: Object,
required: true
},
searchfunction: {
type: Function,
required: true
},
showBtnSubmit: Boolean
},
provide() {
return {
query: Vue.computed(() => this.lastQuery)
};
},
data: function() {
return {
searchtimer: null,
hidetimer: null,
searchsettings: {
searchstr: this.getSearchStr(),
types: this.getInitiallySelectedTypes(),
},
searchresult: [],
searchmode: '',
showresult: false,
searching: false,
error: null,
abortController: null,
settingsDropdown: null,
lastQuery: "",
isSearchShownInMobileView: false,
};
},
lastQuery: ''
};
},
computed: {
searchTypesPlaceholder() {
if (!this.searchsettings.types.length) {
return Object.values(this.typeLabels).join(" / ");
return Object.values(this.typeLabels).join(' / ');
}
return this.searchsettings.types
.map((type) => this.typeLabels[type])
.join(" / ");
return this.searchsettings.types.map(type => this.typeLabels[type]).join(' / ');
},
types() {
if (!this.searchoptions.types) return [];
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return this.searchoptions.types;
return Object.keys(this.searchoptions.types);
},
typeLabels() {
if (!this.searchoptions.types) return {};
if (!this.searchoptions.types)
return {};
if (Array.isArray(this.searchoptions.types)) {
return this.searchoptions.types.reduce((res, type) => {
res[type] = type;
return res;
return res
}, {});
}
return this.searchoptions.types;
},
}
},
template: /*html*/ `
template: /*html*/`
<form
ref="searchform"
class="d-flex me-3"
@@ -94,91 +92,80 @@ export default {
@focusin="searchfocusin"
@focusout="searchfocusout"
>
<span
v-if="isMobile"
type="button"
data-bs-toggle="collapse"
data-bs-target=".multi-collapse"
aria-controls="header-searchbar-collapsible header-options-collapsible header-usermenu-collapsible"
aria-expanded="false"
class="d-flex flex-row align-items-center ps-3 pe-1"
>
<i v-if="isSearchShownInMobileView" class="fa-solid fa-chevron-left"></i>
<i v-else class="fa-solid fa-magnifying-glass"></i>
</span>
<div
:class="{'flex-grow-1': !isMobile, 'collapse multi-collapse collapse-horizontal': isMobile}"
id="header-searchbar-collapsible"
@[\`show.bs.collapse\`]="isSearchShownInMobileView = true"
@[\`hidden.bs.collapse\`]="isSearchShownInMobileView = false"
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:class="showresult ? 'open' : 'closed'"
>
<div
:class="{open: showresult, closed: showresult, 'px-3': isMobile}"
ref="searchbox"
class="h-100 input-group me-2 searchbar_searchbox"
:style="isMobile ? 'width: ' + getMaxWidthOfSearchbarInMobileView() : ''"
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass"></i>
</span>
<input
ref="input"
@keyup="search"
@focus="showsearchresult"
v-model="searchsettings.searchstr"
class="form-control searchbar_input"
type="search"
:placeholder="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
:aria-label="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
>
<button
v-if="searchsettings.searchstr"
type="button"
class="searchbar_input_clear btn btn-outline-secondary"
@click="clearInput"
@focusin.stop
>
<span class="input-group-text">
<i class="fa-solid fa-magnifying-glass color-white"></i>
</span>
<input
ref="input"
@keyup="search"
@focus="showsearchresult"
v-model="searchsettings.searchstr"
class="form-control searchbar_input"
type="search"
:placeholder="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
:aria-label="$p.t('search/input_search_label', { types: searchTypesPlaceholder })"
>
<button
v-if="showBtnSubmit"
type="submit"
class="btn btn-primary"
:title="$p.t('search/submit')"
:aria-label="$p.t('search/submit')"
>
<i class="fas fa-search"></i>
</button>
<button
data-bs-toggle="collapse"
data-bs-target="#searchSettings"
aria-expanded="false"
aria-controls="searchSettings"
ref="settingsbutton"
class="searchbar_setting_btn btn btn-secondary"
type="button"
:title="$p.t('search/button_filter_label')"
:aria-label="$p.t('search/button_filter_label')"
>
<i class="fas fa-filter"></i>
</button>
</div>
<div v-show="showresult"
class="searchbar_results" tabindex="-1">
<div class="searchbar_results_scroller" ref="result">
<div class="searchbar_results_wrapper" ref="results">
<div v-if="searching">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div v-else-if="this.error !== null">{{ error }}</div>
<div v-else-if="searchresult.length < 1">{{ $p.t('search/error_no_results') }}</div>
<template v-else v-for="res in searchresult">
<component
v-if="isValidRenderer(res.renderer)"
:is="res.renderer"
:mode="searchmode"
:res="res"
:actions="getActions(res)"
@actionexecuted="hideresult"
></component>
<div v-else class="searchbar-result text-danger fw-bold">{{ $p.t('search/error_unknown_type', res) }}</div>
</template>
</div>
</div>
</div>
</div>
<i class="fas fa-close"></i>
</button>
<button
v-if="showBtnSubmit"
type="submit"
class="btn btn-primary"
:title="$p.t('search/submit')"
:aria-label="$p.t('search/submit')"
>
<i class="fas fa-search"></i>
</button>
<button
data-bs-toggle="collapse"
data-bs-target="#searchSettings"
aria-expanded="false"
aria-controls="searchSettings"
ref="settingsbutton"
class="searchbar_setting_btn btn btn-secondary"
type="button"
:title="$p.t('search/button_filter_label')"
:aria-label="$p.t('search/button_filter_label')"
>
<i class="fas fa-cog"></i>
</button>
</div>
<div v-show="showresult"
class="searchbar_results" tabindex="-1">
<div class="searchbar_results_scroller" ref="result">
<div class="searchbar_results_wrapper" ref="results">
<div v-if="searching">
<i class="fas fa-spinner fa-spin fa-2x"></i>
</div>
<div v-else-if="this.error !== null">{{ error }}</div>
<div v-else-if="searchresult.length < 1">{{ $p.t('search/error_no_results') }}</div>
<template v-else v-for="res in searchresult">
<component
v-if="isValidRenderer(res.renderer)"
:is="res.renderer"
:mode="searchmode"
:res="res"
:actions="getActions(res)"
@actionexecuted="hideresult"
></component>
<div v-else class="searchbar-result text-danger fw-bold">{{ $p.t('search/error_unknown_type', res) }}</div>
</template>
</div>
</div>
</div>
<div
id="searchSettings"
@@ -220,42 +207,34 @@ export default {
</div>
</form>
`,
watch: {
"searchsettings.searchstr": function (newSearchValue) {
if (this.searchoptions.origin) {
sessionStorage.setItem(
`${this.searchoptions.origin}_searchstr`,
newSearchValue,
);
watch:{
'searchsettings.searchstr': function (newSearchValue) {
if(this.searchoptions.origin){
sessionStorage.setItem(`${this.searchoptions.origin}_searchstr`,newSearchValue);
}
},
"searchsettings.types"(newValue) {
'searchsettings.types'(newValue) {
if (Array.isArray(newValue) && newValue.length === 0) {
this.searchsettings.types = [...this.types];
}
// stores the search types in the localstorage, only if the newValue is also an array
if (Array.isArray(newValue) && this.searchoptions.origin) {
localStorage.setItem(
`${this.searchoptions.origin}_searchtypes`,
JSON.stringify(newValue),
);
localStorage.setItem(`${this.searchoptions.origin}_searchtypes`, JSON.stringify(newValue));
}
this.search();
},
},
mounted() {
}
},
mounted(){
this.settingsDropdown = new bootstrap.Collapse(this.$refs.settings, {
toggle: false,
toggle: false
});
if (!this.searchoptions.origin) {
console.warn(
"No origin defined in the searchoptions for the searchbar, please define the origin property in the searchbaroptions to allow reliable storage of searchstr and searchtypes accross applications.",
);
if (!this.searchoptions.origin){
console.warn("No origin defined in the searchoptions for the searchbar, please define the origin property in the searchbaroptions to allow reliable storage of searchstr and searchtypes accross applications.");
}
},
updated() {
if (this.showresult) {
if(this.showresult) {
Vue.nextTick(() => {
this.calcSearchResultHeight();
});
@@ -270,34 +249,32 @@ export default {
getInitiallySelectedTypes() {
let result = false;
if (this.searchoptions.origin) {
let localStorageValue = localStorage.getItem(
`${this.searchoptions.origin}_searchtypes`,
);
let localStorageValue = localStorage.getItem(`${this.searchoptions.origin}_searchtypes`);
if (localStorageValue) {
result = JSON.parse(localStorageValue);
}
}
if (result) return result;
if (!this.searchoptions.types) return [];
if (result)
return result;
if (!this.searchoptions.types)
return [];
if (Array.isArray(this.searchoptions.types))
return [...this.searchoptions.types];
return Object.keys(this.searchoptions.types);
},
getSearchStr: function () {
if (!this.searchoptions.origin) return "";
return (
sessionStorage.getItem(
`${this.searchoptions.origin}_searchstr`,
) ?? ""
);
getSearchStr: function(){
if (!this.searchoptions.origin)
return '';
return sessionStorage.getItem(`${this.searchoptions.origin}_searchstr`) ?? '';
},
checkSettingsVisibility: function (event) {
checkSettingsVisibility: function(event) {
// hides the settings collapsible if the user clicks somewhere else
if (!this.$refs.settings.contains(event.target)) {
if (!this.$refs.settings.contains(event.target))
{
this.settingsDropdown.hide();
}
},
handleShowSettings: function () {
handleShowSettings: function() {
// adds the event listener checkSettingsVisibility only when the collapsible is shown
document.addEventListener("click", this.checkSettingsVisibility);
},
@@ -305,208 +282,183 @@ export default {
// removes the event listener checkSettingsVisibility when the collapsible is hidden
document.removeEventListener("click", this.checkSettingsVisibility);
},
calcSearchResultHeight: function () {
calcSearchResultHeight: function() {
const rect = this.$refs.results.getBoundingClientRect();
if (rect.height > 0 && rect.height < window.innerHeight * 0.8) {
this.$refs.result.style.height =
Math.ceil(rect.height + 16) + "px";
if( rect.height > 0 && rect.height < (window.innerHeight * 0.8) ) {
this.$refs.result.style.height = Math.ceil(rect.height + 16) + 'px';
} else {
this.$refs.result.style.height =
Math.floor(window.innerHeight * 0.8) + "px";
this.$refs.result.style.height = Math.floor(window.innerHeight * 0.8) + 'px';
}
},
calcSearchResultExtent: function () {
if (!this.showresult) {
calcSearchResultExtent: function() {
if(!this.showresult) {
return;
}
if (
this.searchoptions?.calcheightonly === undefined ||
this.searchoptions.calcheightonly === false
) {
if(this.searchoptions?.calcheightonly === undefined
|| this.searchoptions.calcheightonly === false) {
var rect = this.$refs.searchbox.getBoundingClientRect();
this.$refs.result.style.top =
Math.floor(rect.bottom + 3) + "px";
this.$refs.result.style.right = Math.floor(rect.right) + "px";
this.$refs.result.style.width = Math.floor(rect.width) + "px";
this.$refs.result.style.top = Math.floor(rect.bottom + 3) + 'px';
this.$refs.result.style.right = Math.floor(rect.right) + 'px';
this.$refs.result.style.width = Math.floor(rect.width) + 'px';
}
this.calcSearchResultHeight();
},
search: function () {
if (this.searchoptions?.nolivesearch === true) return;
this.calcSearchResultHeight();
},
search: function() {
if(this.searchoptions?.nolivesearch === true) return;
this.abort();
if (this.searchsettings.searchstr.length >= 2) {
this.abort();
if( this.searchsettings.searchstr.length >= 2 ) {
this.calcSearchResultExtent();
this.searchtimer = setTimeout(
this.callsearchapi,
500
);
} else {
this.showresult = false;
}
},
abort() {
if (this.searchtimer !== null) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.searchresult = [];
},
callsearchapi: function() {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if(this.searchsettings.types.length === 0) {
this.error = this.$p.t('search/error_missing_type');
this.searching = false;
return;
}
if (this.abortController)
this.abortController.abort();
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal })
.then(response=>{
if (!response.data) {
this.error = this.$p.t('search/error_general');
} else {
let res = response.data.map(el => el.data ? {...el, ...JSON.parse(el.data)} : el);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = 'merged-';
let mergeKey = '';
switch (this.searchoptions.mergeResults) {
case 'student':
mergeTypes = ['student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'uid';
break;
case 'person':
mergeTypes = ['person', 'employee', 'student', 'prestudent'];
mergedType += this.searchoptions.mergeResults;
mergeKey = 'person_id';
break;
}
if (mergeTypes.length) {
res = Object.values(res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a['nomerge' + counter++] = c;
} else if (c[mergeKey] === null) {
a['nomerge' + counter++] = c;
} else if (a[c[mergeKey]] === undefined) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c]
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {})).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch(error=> {
if (error.code == "ERR_CANCELED") {
return this.retry = 0;
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t('search/error_general', error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function() {
this.search();
this.togglesettings();
},
hideresult: function() {
this.showresult = false;
window.removeEventListener('resize', this.calcSearchResultExtent);
},
showsearchresult: function() {
if(this.searchoptions?.nolivesearch === true) return;
if( this.searchsettings.searchstr.length >= 2 ) {
this.showresult = true;
window.addEventListener('resize', this.calcSearchResultExtent);
this.calcSearchResultExtent();
this.searchtimer = setTimeout(this.callsearchapi, 500);
} else {
this.showresult = false;
}
},
abort() {
if (this.searchtimer !== null) {
clearTimeout(this.searchtimer);
}
if (this.abortController) {
this.abortController.abort();
this.abortController = null;
}
this.searchresult = [];
},
callsearchapi: function () {
this.error = null;
this.searchresult.splice(0, this.searchresult.length);
this.searching = true;
this.showsearchresult();
if (this.searchsettings.types.length === 0) {
this.error = this.$p.t("search/error_missing_type");
this.searching = false;
return;
}
if (this.abortController) this.abortController.abort();
this.abortController = new AbortController();
this.searchfunction(this.searchsettings, {
timeout: 50000,
signal: this.abortController.signal,
})
.then((response) => {
if (!response.data) {
this.error = this.$p.t("search/error_general");
} else {
let res = response.data.map((el) =>
el.data ? { ...el, ...JSON.parse(el.data) } : el,
);
this.lastQuery = response.meta.searchstring;
if (this.searchoptions.mergeResults) {
let counter = 0;
let mergeTypes = [];
let mergedType = "merged-";
let mergeKey = "";
switch (this.searchoptions.mergeResults) {
case "student":
mergeTypes = ["student", "prestudent"];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "uid";
break;
case "person":
mergeTypes = [
"person",
"employee",
"student",
"prestudent",
];
mergedType +=
this.searchoptions.mergeResults;
mergeKey = "person_id";
break;
}
if (mergeTypes.length) {
res = Object.values(
res.reduce((a, c) => {
if (!mergeTypes.includes(c.renderer)) {
a["nomerge" + counter++] = c;
} else if (c[mergeKey] === null) {
a["nomerge" + counter++] = c;
} else if (
a[c[mergeKey]] === undefined
) {
a[c[mergeKey]] = {
rank: c.rank,
renderer: mergedType,
type: mergedType,
list: [c],
};
} else {
a[c[mergeKey]].list.push(c);
if (c.rank > a[c[mergeKey]].rank)
a[c[mergeKey]].rank = c.rank;
}
return a;
}, {}),
).sort((a, b) => b.rank - a.rank);
}
}
this.searchresult = res;
this.searchmode = response.meta.mode;
}
this.searching = false;
this.retry = 0;
})
.catch((error) => {
if (error.code == "ERR_CANCELED") {
return (this.retry = 0);
}
if (error.code == "ECONNABORTED" && this.retry) {
this.retry--;
return this.callsearchapi();
}
this.error = this.$p.t("search/error_general", error);
this.searching = false;
this.retry = 0;
});
},
refreshsearch: function () {
this.search();
this.togglesettings();
},
hideresult: function () {
this.showresult = false;
window.removeEventListener("resize", this.calcSearchResultExtent);
},
showsearchresult: function () {
if (this.searchoptions?.nolivesearch === true) return;
if (this.searchsettings.searchstr.length >= 2) {
this.showresult = true;
window.addEventListener("resize", this.calcSearchResultExtent);
this.calcSearchResultExtent();
}
},
searchfocusin: function (e) {
e.preventDefault();
e.stopPropagation();
if (this.hidetimer !== null) {
clearTimeout(this.hidetimer);
}
if (
this.searchsettings.searchstr.length >= 2 &&
this.searchresult.length === 0
) {
}
},
searchfocusin: function(e) {
e.preventDefault();
e.stopPropagation();
if( this.hidetimer !== null ) {
clearTimeout(this.hidetimer);
}
if (this.searchsettings.searchstr.length >= 2
&& this.searchresult.length === 0) {
this.search();
}
},
searchfocusout: function (e) {
e.preventDefault();
e.stopPropagation();
this.hidetimer = setTimeout(this.hideresult, 100);
},
dash2camelCase(string) {
return string.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
},
isValidRenderer(renderer) {
const camelCaseRenderer = this.dash2camelCase(renderer);
return Object.keys(this.$.components).includes(camelCaseRenderer);
},
},
searchfocusout: function(e) {
e.preventDefault();
e.stopPropagation();
this.hidetimer = setTimeout(
this.hideresult,
100
);
},
dash2camelCase(string) {
return string.replace(/-([a-z])/g, g => g[1].toUpperCase());
},
isValidRenderer(renderer) {
const camelCaseRenderer = this.dash2camelCase(renderer);
return Object.keys(this.$.components).includes(camelCaseRenderer);
},
getActions(res) {
let actions =
this.searchoptions.actions[this.dash2camelCase(res.renderer)];
let actions = this.searchoptions.actions[this.dash2camelCase(res.renderer)];
if (actions) {
return actions;
}
return this.searchoptions.actions[res.type];
},
getMaxWidthOfSearchbarInMobileView() {
// body width - hardcoded chevron width; necessary for accurate collapse transition transition
return (
document.querySelector("body").getBoundingClientRect().width -
27 +
"px"
);
},
},
}
}
};