-
Notifications
You must be signed in to change notification settings - Fork 150
Expand file tree
/
Copy pathloading-manager.js
More file actions
147 lines (122 loc) · 3.66 KB
/
Copy pathloading-manager.js
File metadata and controls
147 lines (122 loc) · 3.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* LoadingManager - Manages loading states for containers and buttons
* Part of User Readiness Week 1, Day 3 Implementation
*/
class LoadingManager {
constructor() {
this.activeLoadings = new Map();
this.init();
}
init() {
console.log('✅ LoadingManager initialized');
}
/**
* Show loading overlay on a container
* @param {HTMLElement|string} target - Container element or selector
* @param {string} message - Loading message to display
*/
show(target, message = 'Cargando...') {
const container = typeof target === 'string' ? document.querySelector(target) : target;
if (!container) {
console.warn('⚠️ LoadingManager: Container not found', target);
return;
}
// Prevent duplicate overlays
if (this.activeLoadings.has(container)) {
return;
}
// Create overlay
const overlay = document.createElement('div');
overlay.className = 'loading-overlay';
overlay.innerHTML = `
<div class="loading-content">
<div class="spinner"></div>
<p class="loading-message">${this.escapeHtml(message)}</p>
</div>
`;
// Add to container
container.style.position = 'relative';
container.appendChild(overlay);
// Track active loading
this.activeLoadings.set(container, overlay);
// Accessibility
container.setAttribute('aria-busy', 'true');
}
/**
* Hide loading overlay from a container
* @param {HTMLElement|string} target - Container element or selector
*/
hide(target) {
const container = typeof target === 'string' ? document.querySelector(target) : target;
if (!container) {
return;
}
const overlay = this.activeLoadings.get(container);
if (overlay && overlay.parentElement) {
overlay.remove();
}
this.activeLoadings.delete(container);
container.setAttribute('aria-busy', 'false');
}
/**
* Show loading state on a button
* @param {HTMLElement|string} button - Button element or selector
* @param {string} message - Optional loading text
*/
showButton(button, message = null) {
const btn = typeof button === 'string' ? document.querySelector(button) : button;
if (!btn) {
console.warn('⚠️ LoadingManager: Button not found', button);
return;
}
// Store original content
if (!btn.dataset.originalContent) {
btn.dataset.originalContent = btn.innerHTML;
}
// Apply loading state
btn.classList.add('btn-loading');
btn.disabled = true;
if (message) {
btn.innerHTML = `<span class="spinner spinner-sm"></span> ${this.escapeHtml(message)}`;
}
}
/**
* Hide loading state from a button
* @param {HTMLElement|string} button - Button element or selector
*/
hideButton(button) {
const btn = typeof button === 'string' ? document.querySelector(button) : button;
if (!btn) {
return;
}
// Restore original content
if (btn.dataset.originalContent) {
btn.innerHTML = btn.dataset.originalContent;
delete btn.dataset.originalContent;
}
// Remove loading state
btn.classList.remove('btn-loading');
btn.disabled = false;
}
/**
* Hide all active loadings (cleanup)
*/
hideAll() {
this.activeLoadings.forEach((overlay, container) => {
this.hide(container);
});
}
/**
* Escape HTML to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped text
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Initialize global instance
window.loadingManager = new LoadingManager();
console.log('✅ LoadingManager loaded and available globally');