1var lastMeta = ""; 2 3// Global API utility functions 4window.api = (() => { 5 const BASE_URL = ''; // Set your base URL here if needed 6 7 // Default error handler that looks for error spans 8 const defaultErrorHandler = (response) => { 9 // Clear all existing error messages first 10 clearErrorMessages(); 11 if (response.metadata && response.metadata.validation) { 12 // Handle validation errors 13 Object.keys(response.metadata.validation).forEach(field => { 14 const errorSpan = document.getElementById(`error-${field}`); 15 console.log(errorSpan); 16 if (errorSpan && response.metadata.validation[field].length > 0) { 17 errorSpan.textContent = response.metadata.validation[field][0]; 18 } 19 }); 20 } else if (response.error && response.error.message) { 21 // Handle general error message 22 console.error('API Error:', response.error.message); 23 24 // Look for a general error container 25 const generalErrorSpan = document.getElementById('error-general') || 26 document.getElementById('error-message'); 27 if (generalErrorSpan) { 28 generalErrorSpan.textContent = response.error.message; 29 } 30 } 31 }; 32 33 // Function to clear all error messages 34 const clearErrorMessages = () => { 35 const errorSpans = document.querySelectorAll('[id^="error-"]'); 36 errorSpans.forEach(span => { 37 span.textContent = ''; 38 }); 39 }; 40 41 // Main request function 42 const makeRequest = async (method, url, data = null, options = {}) => { 43 const { 44 onSuccess = null, 45 onError = null, 46 headers = {}, 47 clearErrors = true, 48 useFormData = true // Default to form-data 49 } = options; 50 51 // Clear existing errors if requested 52 if (clearErrors) { 53 clearErrorMessages(); 54 } 55 56 try { 57 const config = { 58 method: method.toUpperCase(), 59 headers: { 60 ...headers, 61 'Accept': 'application/json', 62 'X-Requested-With': 'XMLHttpRequest', 63 } 64 }; 65 66 // Add body for POST, PUT, PATCH requests 67 if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) { 68 if (useFormData) { 69 // Use FormData 70 const formData = new FormData(); 71 72 // Handle different data types 73 if (data instanceof FormData) { 74 // If data is already FormData, use it directly 75 config.body = data; 76 } else if (typeof data === 'object') { 77 // Convert object to FormData 78 Object.keys(data).forEach(key => { 79 const value = data[key]; 80 if (value instanceof File || value instanceof Blob) { 81 formData.append(key, value); 82 } else if (Array.isArray(value)) { 83 // Handle arrays 84 value.forEach((item, index) => { 85 if (typeof item === 'object' && !(item instanceof File)) { 86 formData.append(`${key}[${index}]`, JSON.stringify(item)); 87 } else { 88 formData.append(`${key}[${index}]`, item); 89 } 90 }); 91 } else if (typeof value === 'object' && value !== null) { 92 formData.append(key, JSON.stringify(value)); 93 } else { 94 formData.append(key, value); 95 } 96 }); 97 config.body = formData; 98 } 99 // Don't set Content-Type header for FormData, let browser set it 100 } else { 101 // Use JSON 102 config.headers['Content-Type'] = 'application/json'; 103 config.body = JSON.stringify(data); 104 } 105 } 106 107 // Add query parameters for GET requests 108 let finalUrl = BASE_URL + url; 109 if (data && method.toUpperCase() === 'GET') { 110 const queryString = new URLSearchParams(data).toString(); 111 finalUrl += queryString ? `?${queryString}` : ''; 112 } 113 114 const response = await fetch(finalUrl, config); 115 116 const metaTitle = response.headers.get('X-Title'); 117 118 if (metaTitle) { 119 document.title = metaTitle; 120 } 121 122 const metaTags = response.headers.get('X-Meta-Tag'); 123 124 if (metaTags && lastMeta != metaTags) { 125 if (metaTags) { 126 deleteMetaTags() 127 updateMetaTags(metaTags); 128 } else { 129 deleteMetaTags() 130 } 131 lastMeta = metaTags; 132 } 133 134 // Check content type before parsing 135 const contentType = response.headers.get('content-type'); 136 // console.log(contentType); 137 let responseData = await response.text(); 138 139 if (contentType && contentType.includes('application/json')) { 140 responseData = JSON.parse(responseData); 141 } 142 143 if (response.status >= 400) { 144 defaultErrorHandler(responseData); 145 if (onError) { 146 onError(responseData); 147 } 148 } 149 150 else if (onSuccess) { 151 onSuccess(responseData); 152 } 153 154 155 } catch (error) { 156 // Network or parsing error 157 const errorResponse = { 158 error: { 159 code: 500, 160 message: 'Network error atau server tidak merespons' 161 }, 162 data: { 163 success: false 164 }, 165 metadata: [] 166 }; 167 168 if (onError) { 169 onError(errorResponse); 170 } else { 171 defaultErrorHandler(errorResponse); 172 } 173 174 console.error('Network Error:', error); 175 return errorResponse; 176 } 177 }; 178 179 // Public API methods 180 return { 181 get: (url, query = null, options = {}) => {
182 return makeRequest('GET', url, query, options); 183 }, 184 185 post: (url, body = null, options = {}) => { 186 return makeRequest('POST', url, body, options); 187 }, 188 189 put: (url, body = null, options = {}) => { 190 return makeRequest('PUT', url, body, options); 191 }, 192 193 patch: (url, body = null, options = {}) => { 194 return makeRequest('PATCH', url, body, options); 195 }, 196 197 delete: (url, options = {}) => { 198 return makeRequest('DELETE', url, null, options); 199 }, 200 201 // JSON-specific methods (for backward compatibility or when you need JSON) 202 postJson: (url, body = null, options = {}) => { 203 return makeRequest('POST', url, body, { ...options, useFormData: false }); 204 }, 205 206 putJson: (url, body = null, options = {}) => { 207 return makeRequest('PUT', url, body, { ...options, useFormData: false }); 208 }, 209 210 patchJson: (url, body = null, options = {}) => { 211 return makeRequest('PATCH', url, body, { ...options, useFormData: false }); 212 }, 213 214 // Utility methods 215 clearErrors: clearErrorMessages, 216 217 setBaseUrl: (url) => { 218 BASE_URL = url; 219 } 220 }; 221})(); 222 223// Usage Examples: 224 225// Basic usage with form-data (default) 226// api.post('/change-password', { 227// password: 'newPassword', 228// confirmPassword: 'wrongPassword' 229// }); 230 231// With file upload 232// const fileInput = document.getElementById('avatar'); 233// api.post('/upload-profile', { 234// name: 'John Doe', 235// email: '[email protected]', 236// avatar: fileInput.files[0] // File object 237// }); 238 239// With FormData object directly 240// const formData = new FormData(); 241// formData.append('username', 'johndoe'); 242// formData.append('file', fileInput.files[0]); 243// api.post('/upload', formData); 244 245// Force JSON format (when you need JSON instead of form-data) 246// api.postJson('/api-endpoint', { 247// data: 'value' 248// }); 249 250// Or use options to override 251// api.post('/api-endpoint', { 252// data: 'value' 253// }, { 254// useFormData: false // This will use JSON 255// }); 256 257// Usage with custom success callback 258// api.post('/change-password', { 259// password: 'newPassword', 260// confirmPassword: 'newPassword' 261// }, { 262// onSuccess: (response) => { 263// alert('Password berhasil diubah!'); 264// window.location.href = '/dashboard'; 265// } 266// }); 267 268// Usage with custom error handling 269// api.get('/users', null, { 270// onError: (response) => { 271// console.log('Custom error handling:', response); 272// // Your custom error logic here 273// } 274// }); 275 276// Usage with custom headers (for authentication) 277// api.post('/protected-endpoint', { data: 'value' }, { 278// headers: { 279// 'Authorization': 'Bearer ' + localStorage.getItem('token') 280// }, 281// onSuccess: (response) => { 282// console.log('Success:', response); 283// } 284// });
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.