diff --git a/public/js/apps/Cis.js b/public/js/apps/Cis.js
index c88a47a35..39fde01a2 100644
--- a/public/js/apps/Cis.js
+++ b/public/js/apps/Cis.js
@@ -136,6 +136,18 @@ const app = Vue.createApp({
}
};
},
+ computed: {
+ isMobile() {
+ const smallScreen = window.matchMedia("(max-width: 767px)").matches;
+ const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
+ return smallScreen;// && touchCapable;
+ },
+ },
+ provide() {
+ return {
+ isMobile: this.isMobile
+ }
+ },
methods: {
searchfunction: function(searchsettings) {
return this.$api.call(ApiSearchbar.searchCis(searchsettings));
diff --git a/public/js/apps/Dashboard/Fhc.js b/public/js/apps/Dashboard/Fhc.js
index 140c76402..7b7d91fb7 100644
--- a/public/js/apps/Dashboard/Fhc.js
+++ b/public/js/apps/Dashboard/Fhc.js
@@ -233,18 +233,10 @@ const app = Vue.createApp({
renderers: null,
}),
components: {},
- computed: {
- isMobile() {
- const smallScreen = window.matchMedia("(max-width: 767px)").matches;
- const touchCapable = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;
- return smallScreen;// && touchCapable;
- }
- },
provide() {
return { // provide injectable & watchable language property
language: Vue.computed(() => this.$p.user_language),
renderers: Vue.computed(() => this.renderers),
- isMobile: this.isMobile
}
},
methods: {
diff --git a/public/js/components/Cis/Menu.js b/public/js/components/Cis/Menu.js
index f073d5a38..f2f73de26 100644
--- a/public/js/components/Cis/Menu.js
+++ b/public/js/components/Cis/Menu.js
@@ -28,8 +28,10 @@ export default {
urlMatchRankings:[],
navUserDropdown:null,
menuOpen:true,
+ isSearchShownInMobileView: false,
};
},
+ inject: ["isMobile"],
provide(){
return{
setActiveEntry: this.setActiveEntry,
@@ -58,7 +60,10 @@ export default {
},
site_url(){
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router;
- }
+ },
+ areHeaderOptionsShown() {
+ return !this.isSearchShownInMobileView || !this.isMobile;
+ },
},
methods: {
fetchMenu() {
@@ -112,10 +117,21 @@ export default {
});
},
template: /*html*/`
-
-
+
+
+
+
+
+
diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js
index d6c93198e..498d4d964 100644
--- a/public/js/components/searchbar/searchbar.js
+++ b/public/js/components/searchbar/searchbar.js
@@ -21,69 +21,75 @@ 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)
- };
- },
- 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,
+ },
+ emits: ["isSearchShownInMobileViewUpdated"],
+ 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,
settingsDropdown: null,
- lastQuery: ''
- };
- },
+ lastQuery: "",
+ isSearchShownInMobileView: false,
+ };
+ },
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;
- }
+ },
+ isSearchShown() {
+ return !this.isMobile ? true : this.isSearchShownInMobileView;
+ },
},
- template: /*html*/`
+ template: /*html*/ `
`,
- 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();
});
@@ -249,32 +270,34 @@ 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);
},
@@ -282,183 +305,212 @@ 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.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.abort();
+ if (this.searchsettings.searchstr.length >= 2) {
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) {
+ 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
+ ) {
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];
- }
- }
+ },
+ expandSearch() {
+ this.isSearchShownInMobileView = true;
+ this.$emit("isSearchShownInMobileViewUpdated", {
+ isSearchShownInMobileView: true,
+ });
+ },
+ minimizeSearch() {
+ this.isSearchShownInMobileView = false;
+ this.$emit("isSearchShownInMobileViewUpdated", {
+ isSearchShownInMobileView: false,
+ });
+ },
+ },
};