diff --git a/components/ILIAS/Form/resources/autocomplete/dist/legacyAutocomplete.js b/components/ILIAS/Form/resources/autocomplete/dist/legacyAutocomplete.js index 800d1eecc811..4d334f33cfb8 100644 --- a/components/ILIAS/Form/resources/autocomplete/dist/legacyAutocomplete.js +++ b/components/ILIAS/Form/resources/autocomplete/dist/legacyAutocomplete.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(e){"use strict";const t="more",n=500;let o,i;function r(e,t,n){const o=document.createElement("li");return o.tabIndex=0,o.textContent=e,o.dataset.value=t,void 0!==n&&(o.dataset.id=n),o}function a(e){"UL"===e.nextElementSibling?.nodeName&&e.nextElementSibling.remove()}function l(){"number"==typeof i&&(window.clearTimeout(i),i=void 0)}async function u(e,n,i){try{const{signal:l}=o,u=await fetch(e,{signal:l});if(!u.ok)throw new Error(`Response status: ${u.status}`);const s=await u.json(),m=function(e){if(void 0===e.items)return e;const t=[];return Object.entries(e.items).forEach((([e,n])=>{t[e]=n})),t}(s);if(0===m.length)return void a(n);const g=document.createElement("ul");if(g.style.left=`${n.offsetLeft}px`,g.style.minWidth=`${n.offsetWidth}px`,g.classList.add("c-form__autocomplete"),m.forEach((e=>{n.value!==e.value&&n.value.includes(e.value)||g.appendChild(r(e.label,e.value,e.id))})),s.hasMoreResults&&g.appendChild(r(i.moreText,t)),0===g.children.length)return;g.addEventListener("keydown",(e=>{d(e,i)})),g.addEventListener("click",(e=>{c(e,i)}));const p=document.activeElement.dataset.value;a(n),n.parentNode.insertBefore(g,n.nextElementSibling),void 0!==p&&n.parentNode.querySelector(`[data-value="${p}"]`).focus()}catch(e){}}function d(e,t){"Enter"===e.key&&"LI"===e.target.nodeName&&(e.preventDefault(),c(e,t)),"ArrowDown"===e.key&&(e.stopImmediatePropagation(),e.preventDefault(),"UL"===e.target.nextElementSibling?.nodeName&&e.target.nextElementSibling.firstElementChild.focus(),"LI"===e.target.nodeName&&null!==e.target.nextElementSibling&&e.target.nextElementSibling.focus()),"ArrowUp"===e.key&&"LI"===e.target.nodeName&&(e.stopImmediatePropagation(),e.preventDefault(),null===e.target.previousElementSibling?e.target.parentElement.previousElementSibling.focus():e.target.previousElementSibling.focus())}function s(e,t){return null===t?e.trim():e.split(t).at(-1).trim()}function c(e,n){o.abort(),o=new AbortController;let{value:i}=e.target.dataset;if(i!==t){if(null!==n.delimiter){const t=e.target.parentNode.previousElementSibling.value.split(n.delimiter);let o="";t.length>1&&(o=`${t.slice(0,-1).join(`${n.delimiter} `)}${n.delimiter} `),i=`${o}${i}${n.delimiter} `}e.target.parentNode.previousElementSibling.value=i,e.target.parentNode.previousElementSibling.focus(),e.target.parentNode.remove(),n.submitOnSelection&&"id"in e.target.dataset&&(window.location.href=`${n.submitUrl}&selected_id=${encodeURIComponent(e.target.dataset.id)}`)}else{const t=s(e.target.parentNode.previousElementSibling.value,n.delimiter);u(`${n.dataSource}&term=${encodeURIComponent(t)}&fetchall=1`,e.target.parentNode.previousElementSibling,n)}}function m(e,t){o=new AbortController,function(e){const t=document.createAttribute("role");t.value="status",e.setAttributeNode(t);const n=document.createAttribute("aria-relevant");n.value="additions",e.setAttributeNode(n)}(e.parentElement),e.addEventListener("keydown",(e=>{d(e,t)})),e.addEventListener("keyup",(e=>{!function(e,t){if(void 0===e.key||"Tab"===e.key||"ArrowDown"===e.key||"ArrowUp"===e.key)return;if(e.target.value.length{u(`${t.dataSource}&term=${encodeURIComponent(o)}`,e.target,t)}),n)}(e,t)}))}e.LegacyForm=e.LegacyForm||{},e.LegacyForm.autocomplete=e.LegacyForm.autocomplete||{},e.LegacyForm.autocomplete.init=(e,t)=>m(e,t)}(il); +!function(e){"use strict";const t="more",n=500,o="c-form__autocomplete";let r,a;function i(e,t,n){const o=document.createElement("li");return o.tabIndex=0,o.textContent=e,o.dataset.value=t,void 0!==n&&(o.dataset.id=n),o}function l(e,t){t.appendTo?document.querySelector(t.appendTo)?.querySelector(`.${o}`)?.remove():e.parentNode.querySelector(`.${o}`)?.remove()}function u(){"number"==typeof a&&(window.clearTimeout(a),a=void 0)}async function d(e,n,a){try{const{signal:u}=r,d=await fetch(e,{signal:u});if(!d.ok)throw new Error(`Response status: ${d.status}`);const s=await d.json(),p=function(e){if(void 0===e.items)return e;const t=[];return Object.entries(e.items).forEach((([e,n])=>{t[e]=n})),t}(s);if(0===p.length)return void l(n,a);const f=document.createElement("ul");if("string"!=typeof a.appendTo&&(f.style.left=`${n.offsetLeft}px`),f.style.minWidth=`${n.offsetWidth}px`,f.classList.add(o),p.forEach((e=>{n.value!==e.value&&n.value.includes(e.value)||f.appendChild(i(e.label||e.value,e.value,e.id))})),s.hasMoreResults&&f.appendChild(i(a.moreText,t)),0===f.children.length)return;f.addEventListener("keydown",(e=>{c(n,e,a)})),f.addEventListener("click",(e=>{m(n,e,a)}));const v=document.activeElement.dataset.value;l(n,a),a.appendTo?(document.querySelector(a.appendTo).appendChild(f),void 0!==v&&document.querySelector(a.appendTo).querySelector(`[data-value="${v}"]`).focus()):(n.parentNode.insertBefore(f,n.nextElementSibling),void 0!==v&&n.parentNode.querySelector(`[data-value="${v}"]`).focus()),a.open&&a.open(f)}catch(e){}}function c(e,t,n){"Enter"===t.key&&"LI"===t.target.nodeName&&(t.preventDefault(),m(e,t,n)),"ArrowDown"===t.key&&(t.stopImmediatePropagation(),t.preventDefault(),t.target===e&&(n.appendTo?document.querySelector(n.appendTo).querySelector(`.${o}`).firstElementChild.focus():t.target.parentNode.querySelector(`.${o}`)?.firstElementChild?.focus()),"LI"===t.target.nodeName&&null!==t.target.nextElementSibling&&t.target.nextElementSibling.focus()),"ArrowUp"===t.key&&"LI"===t.target.nodeName&&(t.stopImmediatePropagation(),t.preventDefault(),null===t.target.previousElementSibling?e.focus():t.target.previousElementSibling.focus())}function s(e,t){return null===t?e.trim():e.split(t).at(-1).trim()}function m(e,n,o){r.abort(),r=new AbortController;let{value:a}=n.target.dataset;if(a!==t){if(null!==o.delimiter){const t=e.value.split(o.delimiter);let n="";t.length>1&&(n=`${t.slice(0,-1).join(`${o.delimiter} `)}${o.delimiter} `),a=`${n}${a}${o.delimiter} `}e.value=a,e.focus(),n.target.parentNode.remove(),o.submitOnSelection&&"id"in n.target.dataset&&(window.location.href=`${o.submitUrl}&selected_id=${encodeURIComponent(n.target.dataset.id)}`)}else{const t=s(e.value,o.delimiter);d(`${o.dataSource}&term=${encodeURIComponent(t)}&fetchall=1`,e,o)}}function p(e,t){r=new AbortController,function(e){const t=document.createAttribute("role");t.value="status",e.setAttributeNode(t);const n=document.createAttribute("aria-relevant");n.value="additions",e.setAttributeNode(n)}(e.parentElement),e.addEventListener("keydown",(n=>{c(e,n,t)})),e.addEventListener("keyup",(e=>{!function(e,t){if(void 0===e.key||"Tab"===e.key||"ArrowDown"===e.key||"ArrowUp"===e.key)return;if(e.target.value.length{d(`${t.dataSource}&term=${encodeURIComponent(o)}`,e.target,t)}),n)}(e,t)}))}e.LegacyForm=e.LegacyForm||{},e.LegacyForm.autocomplete=e.LegacyForm.autocomplete||{},e.LegacyForm.autocomplete.init=(e,t)=>p(e,t)}(il); diff --git a/components/ILIAS/Form/resources/autocomplete/src/autocompleteHandler.js b/components/ILIAS/Form/resources/autocomplete/src/autocompleteHandler.js index 142eb0498e00..bb3f6d079344 100644 --- a/components/ILIAS/Form/resources/autocomplete/src/autocompleteHandler.js +++ b/components/ILIAS/Form/resources/autocomplete/src/autocompleteHandler.js @@ -15,6 +15,7 @@ const moreValue = 'more'; const triggerTimeout = 500; +const listCssClass = 'c-form__autocomplete'; /** * @@ -79,11 +80,14 @@ function buildListElement(label, value, id) { /** * @param {HTMLElement} inputField + * @param {Object} config * @returns {void} */ -function removeList(inputField) { - if (inputField.nextElementSibling?.nodeName === 'UL') { - inputField.nextElementSibling.remove(); +function removeList(inputField, config) { + if (config.appendTo) { + document.querySelector(config.appendTo)?.querySelector(`.${listCssClass}`)?.remove(); + } else { + inputField.parentNode.querySelector(`.${listCssClass}`)?.remove(); } } @@ -113,19 +117,21 @@ async function fetchListItemsAndBuildSelector(fullUrl, inputField, config) { const items = buildItems(responseJson); if (items.length === 0) { - removeList(inputField); + removeList(inputField, config); return; } const list = document.createElement('ul'); - list.style.left = `${inputField.offsetLeft}px`; + if (typeof config.appendTo !== 'string') { + list.style.left = `${inputField.offsetLeft}px`; + } list.style.minWidth = `${inputField.offsetWidth}px`; - list.classList.add('c-form__autocomplete'); + list.classList.add(listCssClass); items.forEach((elem) => { if (inputField.value !== elem.value && inputField.value.includes(elem.value)) { return; } - list.appendChild(buildListElement(elem.label, elem.value, elem.id)); + list.appendChild(buildListElement(elem.label || elem.value, elem.value, elem.id)); }); if (responseJson.hasMoreResults) { list.appendChild(buildListElement(config.moreText, moreValue)); @@ -133,35 +139,54 @@ async function fetchListItemsAndBuildSelector(fullUrl, inputField, config) { if (list.children.length === 0) { return; } - list.addEventListener('keydown', (e) => { keyHandler(e, config); }); - list.addEventListener('click', (e) => { onSelectHandler(e, config); }); + list.addEventListener('keydown', (e) => { keyHandler(inputField, e, config); }); + list.addEventListener('click', (e) => { onSelectHandler(inputField, e, config); }); const activeElementValue = document.activeElement.dataset.value; - removeList(inputField); - inputField.parentNode.insertBefore(list, inputField.nextElementSibling); - if (typeof activeElementValue !== 'undefined') { - inputField.parentNode.querySelector(`[data-value="${activeElementValue}"]`).focus(); + removeList(inputField, config); + if (config.appendTo) { + document.querySelector(config.appendTo).appendChild(list); + if (typeof activeElementValue !== 'undefined') { + document.querySelector(config.appendTo) + .querySelector(`[data-value="${activeElementValue}"]`) + .focus(); + } + } else { + inputField.parentNode.insertBefore(list, inputField.nextElementSibling); + if (typeof activeElementValue !== 'undefined') { + inputField.parentNode.querySelector(`[data-value="${activeElementValue}"]`).focus(); + } + } + if (config.open) { + config.open(list); } } catch (e) { - // nothing to do } } /** + * @param {HTMLInputElement} inputField * @param {Event} e * @param {Object} config * @returns {void} */ -function keyHandler(e, config) { +function keyHandler(inputField, e, config) { if (e.key === 'Enter' && e.target.nodeName === 'LI') { e.preventDefault(); - onSelectHandler(e, config); + onSelectHandler(inputField, e, config); } if (e.key === 'ArrowDown') { e.stopImmediatePropagation(); e.preventDefault(); - if (e.target.nextElementSibling?.nodeName === 'UL') { - e.target.nextElementSibling.firstElementChild.focus(); + if (e.target === inputField) { + if (config.appendTo) { + document.querySelector(config.appendTo) + .querySelector(`.${listCssClass}`) + .firstElementChild + .focus(); + } else { + e.target.parentNode.querySelector(`.${listCssClass}`)?.firstElementChild?.focus(); + } } if (e.target.nodeName === 'LI' && e.target.nextElementSibling !== null) { @@ -172,8 +197,9 @@ function keyHandler(e, config) { if (e.key === 'ArrowUp' && e.target.nodeName === 'LI') { e.stopImmediatePropagation(); e.preventDefault(); + if (e.target.previousElementSibling === null) { - e.target.parentElement.previousElementSibling.focus(); + inputField.focus(); } else { e.target.previousElementSibling.focus(); } @@ -193,7 +219,7 @@ function onChangeHandler(e, config) { if (e.target.value.length < config.autocompleteLength) { clearTimeout(); - removeList(e.target); + removeList(e.target, config); return; } @@ -227,28 +253,30 @@ function getTermFromSelectedValue(value, delimiter) { } /** + * @param {HTMLInputElement} inputField * @param {Event} e * @param {Object} config * @returns {void} */ -function onSelectHandler(e, config) { +function onSelectHandler(inputField, e, config) { controller.abort(); controller = new AbortController(); let { value } = e.target.dataset; if (value === moreValue) { const term = getTermFromSelectedValue( - e.target.parentNode.previousElementSibling.value, + inputField.value, config.delimiter, ); fetchListItemsAndBuildSelector( `${config.dataSource}&term=${encodeURIComponent(term)}&fetchall=1`, - e.target.parentNode.previousElementSibling, + inputField, config, ); return; } + if (config.delimiter !== null) { - const currentValueArray = e.target.parentNode.previousElementSibling.value + const currentValueArray = inputField.value .split(config.delimiter); let currentValue = ''; if (currentValueArray.length > 1) { @@ -256,8 +284,8 @@ function onSelectHandler(e, config) { } value = `${currentValue}${value}${config.delimiter} `; } - e.target.parentNode.previousElementSibling.value = value; - e.target.parentNode.previousElementSibling.focus(); + inputField.value = value; + inputField.focus(); e.target.parentNode.remove(); if (config.submitOnSelection && 'id' in e.target.dataset) { @@ -268,6 +296,6 @@ function onSelectHandler(e, config) { export default function autocompleteHandler(autocompleteInput, config) { controller = new AbortController(); setAccessibilityAttributesToContainer(autocompleteInput.parentElement); - autocompleteInput.addEventListener('keydown', (e) => { keyHandler(e, config); }); + autocompleteInput.addEventListener('keydown', (e) => { keyHandler(autocompleteInput, e, config); }); autocompleteInput.addEventListener('keyup', (e) => { onChangeHandler(e, config); }); } diff --git a/components/ILIAS/Search/classes/class.ilMainMenuSearchGUI.php b/components/ILIAS/Search/classes/class.ilMainMenuSearchGUI.php index 78f5f6636858..43b007d8006b 100755 --- a/components/ILIAS/Search/classes/class.ilMainMenuSearchGUI.php +++ b/components/ILIAS/Search/classes/class.ilMainMenuSearchGUI.php @@ -36,6 +36,7 @@ class ilMainMenuSearchGUI protected ilTree $tree; protected ilCtrl $ctrl; protected ilObjUser $user; + protected ilGlobalTemplateInterface $global_template; private GlobalHttpState $http; private Factory $refinery; @@ -56,7 +57,7 @@ public function __construct() $this->http = $DIC->http(); $this->refinery = $DIC->refinery(); - $DIC->ui()->mainTemplate()->addJavascript('assets/js/SearchMainMenu.js'); + $this->global_template = $DIC->ui()->mainTemplate(); $this->initRefIdFromQuery(); } @@ -76,6 +77,9 @@ public function getHTML(): string { iljQueryUtil::initjQuery(); + $this->global_template->addJavaScript('assets/js/legacyAutocomplete.js', true, 3); + $this->global_template->addJavascript('assets/js/SearchMainMenu.js'); + $this->tpl = new ilTemplate('tpl.main_menu_search.html', true, true, 'components/ILIAS/Search'); if ($this->user->getId() != ANONYMOUS_USER_ID) { $this->tpl->setVariable('LABEL_SEARCH_OPTIONS', $this->lng->txt("label_search_options")); diff --git a/components/ILIAS/Search/resources/SearchMainMenu.js b/components/ILIAS/Search/resources/SearchMainMenu.js index c9b893ab0f30..258538b1ad04 100755 --- a/components/ILIAS/Search/resources/SearchMainMenu.js +++ b/components/ILIAS/Search/resources/SearchMainMenu.js @@ -1,3 +1,18 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + /* eslint-env jquery */ il.SearchMainMenu = { acDatasource: 'ilias.php?baseClass=ilSearchControllerGUI&cmd=autoComplete', @@ -5,7 +20,7 @@ il.SearchMainMenu = { init() { // we must bind the blur event before the autocomplete item is added this.suppressBlur(); - this.initAutocomplete(); + this.initAutocomplete(`${this.acDatasource}&search_type=4`); this.initChange(); }, @@ -16,46 +31,40 @@ il.SearchMainMenu = { ); }, - initAutocomplete() { - $('#main_menu_search').autocomplete({ - source: `${this.acDatasource}&search_type=4`, - appendTo: '#mm_search_menu_ac', - open() { - $('.ui-autocomplete').position({ - my: 'left top', - at: 'left top', - of: $('#mm_search_menu_ac'), - }); - }, - minLength: 3, + /** + * @param {string} dataSource + */ + initAutocomplete(dataSource) { + const autocomplete = document.querySelector('#main_menu_search'); + const target = document.getElementById('mm_search_menu_ac'); + + il.LegacyForm.autocomplete.init(autocomplete, { + delimiter: null, + dataSource, + submitOnSelection: false, + autocompleteLength: 3, + submitUrl: null, + moreText: null, + appendTo: `#${target.id}`, }); }, initChange() { $("#ilMMSearchMenu input[type='radio']").change(() => { - /* close current search */ - $('#main_menu_search').autocomplete('close'); - $('#main_menu_search').autocomplete('enable'); - - /* append search type */ - const checkedInput = $('input[name=root_id]:checked', '#mm_search_form'); - const typeVal = checkedInput.val(); + /* disabled autocomplete */ + const originalInput = document.querySelector('#main_menu_search'); + const autocomplete = originalInput.cloneNode(true); // clone attributes, value, etc. + originalInput.replaceWith(autocomplete); // replace the original inpu /* disable autocomplete for search at current position */ + const checkedInput = $('input[name=root_id]:checked', '#mm_search_form'); if (checkedInput[0].id === 'ilmmsc') { - $('#main_menu_search').autocomplete('disable'); return; } - $('#main_menu_search').autocomplete( - 'option', - { - source: `${this.acDatasource}&search_type=${typeVal}`, - }, - ); + const typeVal = checkedInput.val(); - /* start new search */ - $('#main_menu_search').autocomplete('search'); + this.initAutocomplete(`${this.acDatasource}&search_type=${typeVal}`); }); }, }; diff --git a/components/ILIAS/Search/templates/default/tpl.main_menu_search.html b/components/ILIAS/Search/templates/default/tpl.main_menu_search.html index 05fcc4b3ee28..d931a1b4da3c 100755 --- a/components/ILIAS/Search/templates/default/tpl.main_menu_search.html +++ b/components/ILIAS/Search/templates/default/tpl.main_menu_search.html @@ -1,7 +1,7 @@