diff --git a/war/src/main/js/app.js b/war/src/main/js/app.js index 63a35226d7d4..0ac9a97cce38 100644 --- a/war/src/main/js/app.js +++ b/war/src/main/js/app.js @@ -5,6 +5,7 @@ import Tooltips from "@/components/tooltips"; import StopButtonLink from "@/components/stop-button-link"; import ConfirmationLink from "@/components/confirmation-link"; import Dialogs from "@/components/dialogs"; +import Autocomplete from "@/components/autocomplete"; Dropdowns.init(); Notifications.init(); @@ -13,3 +14,4 @@ Tooltips.init(); StopButtonLink.init(); ConfirmationLink.init(); Dialogs.init(); +Autocomplete.init(); diff --git a/war/src/main/js/components/autocomplete/index.js b/war/src/main/js/components/autocomplete/index.js new file mode 100644 index 000000000000..3f5a6367a285 --- /dev/null +++ b/war/src/main/js/components/autocomplete/index.js @@ -0,0 +1,88 @@ +import behaviorShim from "@/util/behavior-shim"; +import Utils from "@/components/dropdowns/utils"; + +function init() { + function addValue(value, item, delimiter) { + const prev = value.includes(delimiter) + ? value.substring(0, value.lastIndexOf(delimiter) + 1) + " " + : ""; + return prev + item + delimiter + " "; + } + + function validate(e) { + if (e.targetUrl) { + var method = e.getAttribute("checkMethod") || "post"; + try { + FormChecker.delayedCheck(e.targetUrl(), method, e.targetElement); + } catch (x) { + console.warn(x); + return; + } + } + } + + function convertSuggestionToItem(suggestion, e) { + const delimiter = e.getAttribute("autoCompleteDelimChar"); + return { + label: suggestion.name, + onClick: () => { + e.value = delimiter + ? addValue(e.value, suggestion.name, delimiter) + : suggestion.name; + validate(e); + e.focus(); + }, + }; + } + + function createAndShowDropdown(e, div, suggestions) { + const items = suggestions.map((s) => convertSuggestionToItem(s, e)); + if (!e.dropdown) { + Utils.generateDropdown( + div, + (instance) => { + e.dropdown = instance; + }, + true, + ); + } + e.dropdown.setContent(Utils.generateDropdownItems(items, true)); + e.dropdown.show(); + } + + function updateSuggestions(e, div) { + const text = e.value.trim(); + const delimiter = e.getAttribute("autoCompleteDelimChar"); + const word = delimiter ? text.split(delimiter).reverse()[0].trim() : text; + if (!word) { + if (e.dropdown) { + e.dropdown.hide(); + } + return; + } + const url = + e.getAttribute("autoCompleteUrl") + "?value=" + encodeURIComponent(word); + fetch(url) + .then((rsp) => (rsp.ok ? rsp.json().suggestions : {})) + .then((response) => createAndShowDropdown(e, div, response)); + } + + behaviorShim.specify( + "INPUT.auto-complete", + "input-auto-complete", + 0, + function (e) { + // form field with auto-completion support + // insert the auto-completion container + var div = document.createElement("DIV"); + e.parentNode.insertBefore(div, e.nextElementSibling); + e.style.position = "relative"; + + e.addEventListener("input", (evt) => { + updateSuggestions(e, div); + }); + }, + ); +} + +export default { init }; diff --git a/war/src/main/js/components/dropdowns/utils.js b/war/src/main/js/components/dropdowns/utils.js index 8a3677c939b1..5ef11b640c80 100644 --- a/war/src/main/js/components/dropdowns/utils.js +++ b/war/src/main/js/components/dropdowns/utils.js @@ -11,12 +11,12 @@ const SELECTED_ITEM_CLASS = "jenkins-dropdown__item--selected"; * @param element - the element to generate the dropdown for * @param callback - called to retrieve the list of dropdown items */ -function generateDropdown(element, callback) { +function generateDropdown(element, callback, immediate) { tippy( element, Object.assign({}, Templates.dropdown(), { onCreate(instance) { - instance.reference.addEventListener("mouseenter", () => { + const onload = () => { if (instance.loaded) { return; } @@ -26,7 +26,12 @@ function generateDropdown(element, callback) { }); callback(instance); - }); + }; + if (immediate) { + onload(); + } else { + instance.reference.addEventListener("mouseenter", onload); + } }, }), ); diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index 8d1a0a9e8e8a..ef331e3fb2bb 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -1310,49 +1310,6 @@ function rowvgStartEachRow(recursive, f) { }, ); - Behaviour.specify( - "INPUT.auto-complete", - "input-auto-complete", - ++p, - function (e) { - // form field with auto-completion support - // insert the auto-completion container - var div = document.createElement("DIV"); - e.parentNode.insertBefore(div, e.nextElementSibling); - e.style.position = "relative"; // or else by default it's absolutely positioned, making "width:100%" break - - var ds = new YAHOO.util.XHRDataSource(e.getAttribute("autoCompleteUrl")); - ds.responseType = YAHOO.util.XHRDataSource.TYPE_JSON; - ds.responseSchema = { - resultsList: "suggestions", - fields: ["name"], - }; - - // Instantiate the AutoComplete - var ac = new YAHOO.widget.AutoComplete(e, div, ds); - ac.generateRequest = function (query) { - return "?value=" + query; - }; - ac.autoHighlight = false; - ac.prehighlightClassName = "yui-ac-prehighlight"; - ac.animSpeed = 0; - ac.formatResult = ac.formatEscapedResult; - ac.useShadow = true; - ac.autoSnapContainer = true; - ac.delimChar = e.getAttribute("autoCompleteDelimChar"); - ac.doBeforeExpandContainer = function (textbox, container) { - // adjust the width every time we show it - container.style.width = textbox.clientWidth + "px"; - var Dom = YAHOO.util.Dom; - Dom.setXY(container, [ - Dom.getX(textbox), - Dom.getY(textbox) + textbox.offsetHeight, - ]); - return true; - }; - }, - ); - Behaviour.specify( "A.jenkins-help-button", "a-jenkins-help-button",