Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replace yui autocomplete with plain javascript in search-box #9231

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
</a>

<div id="search-box-completion" data-search-url="${searchURL}" />
<st:adjunct includes="jenkins.views.JenkinsHeader.search-box" />
</div>
</form>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
(function () {
var element = document.getElementById("search-box-completion");
if (element) {
createSearchBox(element.getAttribute("data-search-url"));
}
})();
2 changes: 2 additions & 0 deletions war/src/main/js/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Dropdowns from "@/components/dropdowns";
import Notifications from "@/components/notifications";
import SearchBar from "@/components/search-bar";
import SearchBox from "@/components/search-box";
import Tooltips from "@/components/tooltips";
import StopButtonLink from "@/components/stop-button-link";
import ConfirmationLink from "@/components/confirmation-link";
Expand All @@ -9,6 +10,7 @@ import Dialogs from "@/components/dialogs";
Dropdowns.init();
Notifications.init();
SearchBar.init();
SearchBox.init();
Tooltips.init();
StopButtonLink.init();
ConfirmationLink.init();
Expand Down
204 changes: 204 additions & 0 deletions war/src/main/js/components/search-box/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { getStyle } from "@/util/dom";

function init() {
const input = document.getElementById("search-box");
const sizer = document.getElementById("search-box-sizer");
const comp = document.getElementById("search-box-completion");
if (!comp) {
return;
}

window.addEventListener("load", () => {
// copy font style of box to sizer
var ds = sizer.style;
ds.fontFamily = getStyle(input, "fontFamily");
ds.fontSize = getStyle(input, "fontSize");
ds.fontStyle = getStyle(input, "fontStyle");
ds.fontWeight = getStyle(input, "fontWeight");
});

const searchURL = comp.getAttribute("data-search-url");

const debounce = 300;
const maxResults = 25;
const highlightClass = "ac-highlight";
let req,
suggestions,
selected = -1,
visible = false,
currentValue;

comp.style.top = input.offsetHeight + 2 + "px";

input.autocomplete = "off";

["input", "keydown", "blur", "focus"].forEach((ev) =>
input.addEventListener(ev, handleEvent),
);

const ul = document.createElement("ul");
ul.classList.add("jenkins-hidden");
comp.appendChild(ul);

function handleEvent(event) {
if (event.type === "focus") {
return;
}

if (event.type === "keydown" && handleKey(event)) {
return;
}

if (input.value === "") {
hide();
currentValue = null;
return;
}

if (event.type === "blur") {
hide();
return;
}

if (input.value === currentValue && visible) {
return;
}

currentValue = input.value;

clearTimeout(req);
req = setTimeout(search, debounce);
}

function handleKey(event) {
if (!visible) {
if (event.code === "Enter" && !input.value) {
event.preventDefault();
}
return ["Enter", "Escape", "Tab"].includes(event.code);
}

switch (event.code) {
case "ArrowUp":
return nav(-1, event);
case "ArrowDown":
return nav(1, event);
case "Tab":
if (selected >= 0) {
event.preventDefault();
select(selected);
}
hide();
return true;
case "Enter":
if (selected >= 0) {
event.preventDefault();
select(selected);
}
hide();
return true;
case "Escape":
hide();
return true;
}
return false;
}

function search() {
if (!input.value) {
return;
}

const params = new URLSearchParams({ query: input.value });
let url = searchURL + "suggest?" + params;

fetch(url).then((rsp) => {
if (rsp.ok) {
rsp.json().then((data) => {
const list = [];
data.suggestions.forEach((item, i) => {
if (i < maxResults) {
list.push(item.name);
}
});
suggestions = list;
if (!suggestions.length) {
hide();
return;
}

showSuggestions();
});
}
});
}

function nav(direction, event) {
event.preventDefault();
const lastSelected = ul.querySelector(`.${highlightClass}`);
if (lastSelected) {
lastSelected.classList.remove(highlightClass);
}
selected = (selected + direction + suggestions.length) % suggestions.length;
ul.querySelector(`:nth-child(${selected + 1})`).classList.add(
highlightClass,
);
return true;
}

function select(i) {
input.value = currentValue = suggestions[i];
}

function showSuggestions() {
ul.innerHTML = "";
suggestions.forEach((item, i) => {
const li = document.createElement("li");
li.innerText = item;
if (i == selected) {
li.classList.add(highlightClass);
}
li.addEventListener("mousedown", () => select(i));
li.addEventListener("mouseover", () => {
ul.querySelectorAll("li").forEach((item) => {
item.classList.remove(highlightClass);
});
li.classList.add(highlightClass);
selected = i;
});
li.addEventListener("mouseout", () => {
li.classList.remove(highlightClass);
});
ul.appendChild(li);
});
ul.classList.remove("jenkins-hidden");
visible = true;
}

function hide() {
suggestions = [];
selected = -1;
if (visible) {
ul.classList.add("jenkins-hidden");
visible = false;
updatePos();
}
}

function updatePos() {
sizer.innerHTML = escapeHTML(input.value);
var cssWidth,
offsetWidth = sizer.offsetWidth;
if (offsetWidth > 0) {
cssWidth = offsetWidth + "px";
} else {
// sizer hidden on small screen, make sure resizing looks OK
cssWidth = getStyle(sizer, "minWidth");
}
input.style.width = comp.style.width = "calc(85px + " + cssWidth + ")";
}

input.addEventListener("input", updatePos);
}

export default { init };
12 changes: 12 additions & 0 deletions war/src/main/js/util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ export function toId(string) {
.replace(/[\W_]+/g, "-")
.toLowerCase();
}

export function getStyle(e, a) {
if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView
.getComputedStyle(e, null)
.getPropertyValue(a.replace(/([A-Z])/g, "-$1"));
}
if (e.currentStyle) {
return e.currentStyle[a];
}
return null;
}
14 changes: 12 additions & 2 deletions war/src/main/scss/components/_page-header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,20 @@ a.page-header__brand-link {

#search-box-completion {
text-align: left;
width: 25em;
position: absolute;
z-index: 1000;
min-width: 30em;
}

#search-box-completion ul {
padding: 0.75rem 0;
width: 100%;
margin: 0;
list-style: none;
overflow: hidden;
background-color: var(--input-color);
box-shadow: var(--dropdown-box-shadow);
border: none;
border-radius: var(--form-input-border-radius);
}

#search-box-completion li {
Expand All @@ -186,6 +190,12 @@ a.page-header__brand-link {
line-height: var(--line-height-base);
overflow: hidden;
text-overflow: ellipsis;
cursor: default;

&.ac-highlight {
background: var(--search-box-completion-bg);
font-weight: bold;
}
}

#search-box-sizer {
Expand Down
Loading