Version: Arctyk ITSM v0.6.0+ Last Updated: January 2026
JavaScript Patterns¶
This page documents JavaScript patterns and practices in Arctyk ITSM.
Overview¶
Arctyk ITSM uses vanilla JavaScript (no jQuery or heavy frameworks) following modern ES6+ patterns. The codebase emphasizes:
- Module pattern for encapsulation
- Event delegation for dynamic content
- AJAX for form submissions and async operations
- Progressive enhancement for core functionality
Module Pattern¶
All JavaScript uses the module pattern (IIFE) for encapsulation and namespace management.
Basic Module Structure¶
(function () {
"use strict";
// Private variables
const API_ENDPOINT = "/api/tickets/";
// Private functions
function handleResponse(response) {
// ...
}
// Public API (if needed)
window.TicketModule = {
init: function () {
// Initialization code
},
};
// Auto-initialize on DOMContentLoaded
document.addEventListener("DOMContentLoaded", function () {
// Setup code
});
})();
Benefits¶
✅ Encapsulation - Variables don't pollute global scope
✅ Organization - Related code grouped together
✅ Testability - Easier to test isolated modules
✅ Maintenance - Clear boundaries between modules
AJAX Forms¶
Forms submit via AJAX for better UX without full page reloads.
Form Submission Pattern¶
const form = document.getElementById("comment-form");
form.addEventListener("submit", function (e) {
e.preventDefault();
const formData = new FormData(form);
const csrfToken = document.querySelector("[name=csrfmiddlewaretoken]").value;
fetch(form.action, {
method: "POST",
headers: {
"X-CSRFToken": csrfToken,
"X-Requested-With": "XMLHttpRequest",
},
body: formData,
})
.then((response) => response.json())
.then((data) => {
if (data.success) {
// Update UI with new comment
updateCommentList(data.comment);
form.reset();
} else {
// Show validation errors
displayErrors(data.errors);
}
})
.catch((error) => {
console.error("Error:", error);
showErrorMessage("Failed to submit form");
});
});
JSON Response Handling¶
function handleJsonResponse(response) {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
fetch("/api/endpoint/")
.then(handleJsonResponse)
.then((data) => {
// Process successful response
})
.catch((error) => {
// Handle error
});
Event Handling¶
Use event delegation for dynamic content that may be added/removed from the DOM.
Event Delegation Pattern¶
// ❌ Bad: Direct event listener (breaks for dynamic content)
document.querySelectorAll(".delete-btn").forEach((btn) => {
btn.addEventListener("click", handleDelete);
});
// ✅ Good: Event delegation (works for dynamic content)
document.addEventListener("click", function (e) {
if (e.target.matches(".delete-btn")) {
handleDelete(e);
}
});
Multiple Event Types¶
document.addEventListener("click", function (e) {
// Edit button clicked
if (e.target.matches(".edit-btn")) {
handleEdit(e);
}
// Delete button clicked
if (e.target.matches(".delete-btn")) {
handleDelete(e);
}
// Close modal
if (e.target.matches(".modal-close")) {
closeModal(e);
}
});
DOM Manipulation¶
Adding Elements¶
// Create element
const commentDiv = document.createElement("div");
commentDiv.className = "comment";
commentDiv.innerHTML = `
<div class="comment-header">
<span class="comment-author">${data.author}</span>
<span class="comment-timestamp">${data.timestamp}</span>
</div>
<div class="comment-body">${data.content}</div>
`;
// Add to DOM
document.getElementById("comments-list").prepend(commentDiv);
Updating Elements¶
// Update text content
document.getElementById("ticket-status").textContent = newStatus;
// Update HTML content (be careful with XSS)
const safeHTML = escapeHTML(userContent);
document.getElementById("content").innerHTML = safeHTML;
// Update attributes
const btn = document.getElementById("submit-btn");
btn.disabled = true;
btn.setAttribute("aria-busy", "true");
Removing Elements¶
// Remove single element
const element = document.getElementById("temp-message");
element.remove();
// Remove all matching elements
document.querySelectorAll(".temp-alert").forEach((el) => el.remove());
Common Patterns¶
Confirmation Dialogs¶
function confirmDelete(e) {
e.preventDefault();
if (!confirm("Are you sure you want to delete this item?")) {
return;
}
// Proceed with deletion
deleteItem(e.target.dataset.itemId);
}
Loading States¶
function showLoading(button) {
button.disabled = true;
button.dataset.originalText = button.textContent;
button.textContent = "Loading...";
button.classList.add("is-loading");
}
function hideLoading(button) {
button.disabled = false;
button.textContent = button.dataset.originalText;
button.classList.remove("is-loading");
}
Toast Notifications¶
function showToast(message, type = "info") {
const toast = document.createElement("div");
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// Auto-remove after 3 seconds
setTimeout(() => {
toast.classList.add("fade-out");
setTimeout(() => toast.remove(), 300);
}, 3000);
}
CSRF Protection¶
All AJAX POST/PUT/PATCH/DELETE requests must include the CSRF token.
function getCSRFToken() {
return document.querySelector("[name=csrfmiddlewaretoken]").value;
}
fetch("/api/endpoint/", {
method: "POST",
headers: {
"X-CSRFToken": getCSRFToken(),
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
Error Handling¶
Display Validation Errors¶
function displayErrors(errors) {
// Clear previous errors
document.querySelectorAll(".error-message").forEach((el) => el.remove());
// Display new errors
Object.entries(errors).forEach(([field, messages]) => {
const input = document.querySelector(`[name="${field}"]`);
if (input) {
const errorDiv = document.createElement("div");
errorDiv.className = "error-message text-danger";
errorDiv.textContent = messages.join(", ");
input.parentNode.appendChild(errorDiv);
}
});
}
Global Error Handler¶
window.addEventListener("error", function (e) {
console.error("JavaScript error:", e.error);
// Optionally send to error tracking service
});
Performance Best Practices¶
Debouncing¶
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Usage: Search input with 300ms debounce
const searchInput = document.getElementById("search");
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener("input", debouncedSearch);
Throttling¶
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Usage: Scroll event with 100ms throttle
window.addEventListener("scroll", throttle(handleScroll, 100));
Accessibility¶
Keyboard Navigation¶
document.addEventListener("keydown", function (e) {
// Close modal with Escape key
if (e.key === "Escape") {
closeModal();
}
// Submit form with Ctrl+Enter
if (e.ctrlKey && e.key === "Enter") {
document.getElementById("comment-form").submit();
}
});
Focus Management¶
function openModal() {
const modal = document.getElementById("modal");
modal.classList.add("show");
// Focus first interactive element
const firstInput = modal.querySelector("input, button, textarea");
if (firstInput) {
firstInput.focus();
}
}
Related Documentation¶
- Frontend Architecture - Overall frontend architecture
- Templates - Django template integration
- UI Patterns - Common UI patterns and Bootstrap usage
- Standards - Frontend - Frontend coding standards