Skip to content

Commit

Permalink
replace yui autocomplete with plain javascript in sarch-box
Browse files Browse the repository at this point in the history
The search box in the header is now implemented in plain javascript and
no longer using yui autocomplete.

Minor adjustments to the styling
- box-shadow is now the same as the one for dropdowns
- background of the suggestion list is the same as the background of the
  search input
  • Loading branch information
mawinter69 committed May 3, 2024
1 parent 05ac985 commit 47cc257
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 8 deletions.
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
202 changes: 202 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,202 @@
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);

const dataSrc = async (val) => {
let url = searchURL + "suggest?query=" + val;
const result = await fetch(url);
const data = await result.json();
const list = [];
data.suggestions.forEach((item, i) => {
if (i < maxResults) {
list.push(item.name);
}
});

return list;
};

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;
}

async function search() {
if (!input.value) {
return;
}
suggestions = await dataSrc(input.value);
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.minWidth = "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;
}
13 changes: 12 additions & 1 deletion war/src/main/scss/components/_page-header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,21 @@ a.page-header__brand-link {

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

#search-box-completion ul {
padding: 0.75rem 0;
min-width: 30em;
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 +191,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

0 comments on commit 47cc257

Please sign in to comment.