Implmented custom autocomplete for customer field

This commit is contained in:
2026-03-18 21:55:55 -04:00
parent 460bcd466f
commit b367687113
8 changed files with 135 additions and 15 deletions

View File

@@ -0,0 +1,102 @@
(function () {
// Helper: escape HTML for safety
function escapeHtml(str) {
return $("<div>").text(str).html();
}
// Helper: highlight all occurrences of term (case-insensitive)
function highlightTerm(text, term) {
if (!term) return text;
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp("(" + escapedTerm + ")", "ig");
return text.replace(regex, "<strong>$1</strong>");
}
window.initCustomerAutocomplete = function(context) {
let scope = context || document;
$(scope).find(".customer-name").each(function() {
if ($(this).data("autocomplete-initialized")) return;
$(this).data("autocomplete-initialized", true);
let $input = $(this);
let ac = $input.autocomplete({
appendTo: "body", // crucial for Redmine positioning
minLength: 2,
source: function(request, response) {
$.getJSON("/customers/autocomplete", { q: request.term })
.done(function(data) {
response(data.map(function(item) {
// combine secondary info
let secondary = [];
if (item.phone_number) secondary.push(item.phone_number);
if (item.mobile_phone_number) secondary.push(item.mobile_phone_number);
let meta = secondary.length ? " (" + secondary.join(" • ") + ")" : "";
// escape HTML to avoid XSS
let safeText = escapeHtml(item.name + meta);
return {
label: item.name + meta, // plain fallback
value: item.name, // goes into input
id: item.id,
html: highlightTerm(safeText, request.term)
};
}));
})
.fail(function() {
response([]);
});
},
select: function(event, ui) {
$input.val(ui.item.value); // visible text
$("#issue_customer_id").val(ui.item.id); // hidden ID
// trigger Redmine form update safely
setTimeout(function() {
$("#issue_customer_id").trigger("change");
}, 0);
return false;
},
change: function(event, ui) {
// clear hidden field if no valid selection
if (!ui.item && !$input.val()) {
$("#issue_customer_id").val("");
}
}
});
// Render item HTML for highlight
ac.autocomplete("instance")._renderItem = function(ul, item) {
return $("<li>")
.append($("<div>").html(item.html))
.appendTo(ul);
};
});
};
// Re-init after Redmine AJAX form updates
$(document).on("ajaxComplete", function() {
if (window.initCustomerAutocomplete) {
window.initCustomerAutocomplete(document);
}
});
// Init on page load
$(document).ready(function() {
window.initCustomerAutocomplete(document);
});
// Also init on Turbo/Redmine load events
document.addEventListener("turbo:load", function() {
window.initCustomerAutocomplete(document);
});
})();

View File

@@ -0,0 +1,5 @@
/* Keep Redmine default look, just enhance metadata */
.ui-autocomplete .autocomplete-meta {
color: #888;
font-size: 0.9em;
}