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 } 63 }; 64 65 // Add body for POST, PUT, PATCH requests 66 if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) { 67 if (useFormData) { 68 // Use FormData 69 const formData = new FormData(); 70 71 // Handle different data types 72 if (data instanceof FormData) { 73 // If data is already FormData, use it directly 74 config.body = data; 75 } else if (typeof data === 'object') { 76 // Convert object to FormData 77 Object.keys(data).forEach(key => { 78 const value = data[key]; 79 if (value instanceof File || value instanceof Blob) { 80 formData.append(key, value); 81 } else if (Array.isArray(value)) { 82 // Handle arrays 83 value.forEach((item, index) => { 84 if (typeof item === 'object' && !(item instanceof File)) { 85 formData.append(`${key}[${index}]`, JSON.stringify(item)); 86 } else { 87 formData.append(`${key}[${index}]`, item); 88 } 89 }); 90 } else if (typeof value === 'object' && value !== null) { 91 formData.append(key, JSON.stringify(value)); 92 } else { 93 formData.append(key, value); 94 } 95 }); 96 config.body = formData; 97 } 98 // Don't set Content-Type header for FormData, let browser set it 99 } else { 100 // Use JSON 101 config.headers['Content-Type'] = 'application/json'; 102 config.body = JSON.stringify(data); 103 } 104 } 105 106 // Add query parameters for GET requests 107 let finalUrl = BASE_URL + url; 108 if (data && method.toUpperCase() === 'GET') { 109 const queryString = new URLSearchParams(data).toString(); 110 finalUrl += queryString ? `?${queryString}` : ''; 111 } 112 113 const response = await fetch(finalUrl, config); 114 115 const metaTitle = response.headers.get('X-Title'); 116 117 if (metaTitle) { 118 document.title = metaTitle; 119 } 120 121 const metaTags = response.headers.get('X-Meta-Tag'); 122 123 if (lastMeta != metaTags) { 124 if (metaTags) { 125 deleteMetaTags() 126 updateMetaTags(metaTags); 127 } else { 128 deleteMetaTags() 129 } 130 lastMeta = metaTags; 131 } 132 133 // Check content type before parsing 134 const contentType = response.headers.get('content-type'); 135 // console.log(contentType); 136 let responseData = await response.text(); 137 138 if (contentType && contentType.includes('application/json')) { 139 responseData = JSON.parse(responseData); 140 } 141 142 if (response.status >= 400) { 143 defaultErrorHandler(responseData); 144 if (onError) { 145 onError(responseData); 146 } 147 } 148 149 else if (onSuccess) { 150 onSuccess(responseData); 151 } 152 153 154 } catch (error) { 155 // Network or parsing error 156 const errorResponse = { 157 error: { 158 code: 500, 159 message: 'Network error atau server tidak merespons' 160 }, 161 data: { 162 success: false 163 }, 164 metadata: [] 165 }; 166 167 if (onError) { 168 onError(errorResponse); 169 } else { 170 defaultErrorHandler(errorResponse); 171 } 172 173 console.error('Network Error:', error); 174 return errorResponse; 175 } 176 }; 177 178 // Public API methods 179 return { 180 get: (url, query = null, options = {}) => {
181 return makeRequest('GET', url, query, options); 182 }, 183 184 post: (url, body = null, options = {}) => { 185 return makeRequest('POST', url, body, options); 186 }, 187 188 put: (url, body = null, options = {}) => { 189 return makeRequest('PUT', url, body, options); 190 }, 191 192 patch: (url, body = null, options = {}) => { 193 return makeRequest('PATCH', url, body, options); 194 }, 195 196 delete: (url, options = {}) => { 197 return makeRequest('DELETE', url, null, options); 198 }, 199 200 // JSON-specific methods (for backward compatibility or when you need JSON) 201 postJson: (url, body = null, options = {}) => { 202 return makeRequest('POST', url, body, { ...options, useFormData: false }); 203 }, 204 205 putJson: (url, body = null, options = {}) => { 206 return makeRequest('PUT', url, body, { ...options, useFormData: false }); 207 }, 208 209 patchJson: (url, body = null, options = {}) => { 210 return makeRequest('PATCH', url, body, { ...options, useFormData: false }); 211 }, 212 213 // Utility methods 214 clearErrors: clearErrorMessages, 215 216 setBaseUrl: (url) => { 217 BASE_URL = url; 218 } 219 }; 220})(); 221 222// Usage Examples: 223 224// Basic usage with form-data (default) 225// api.post('/change-password', { 226// password: 'newPassword', 227// confirmPassword: 'wrongPassword' 228// }); 229 230// With file upload 231// const fileInput = document.getElementById('avatar'); 232// api.post('/upload-profile', { 233// name: 'John Doe', 234// email: '[email protected]', 235// avatar: fileInput.files[0] // File object 236// }); 237 238// With FormData object directly 239// const formData = new FormData(); 240// formData.append('username', 'johndoe'); 241// formData.append('file', fileInput.files[0]); 242// api.post('/upload', formData); 243 244// Force JSON format (when you need JSON instead of form-data) 245// api.postJson('/api-endpoint', { 246// data: 'value' 247// }); 248 249// Or use options to override 250// api.post('/api-endpoint', { 251// data: 'value' 252// }, { 253// useFormData: false // This will use JSON 254// }); 255 256// Usage with custom success callback 257// api.post('/change-password', { 258// password: 'newPassword', 259// confirmPassword: 'newPassword' 260// }, { 261// onSuccess: (response) => { 262// alert('Password berhasil diubah!'); 263// window.location.href = '/dashboard'; 264// } 265// }); 266 267// Usage with custom error handling 268// api.get('/users', null, { 269// onError: (response) => { 270// console.log('Custom error handling:', response); 271// // Your custom error logic here 272// } 273// }); 274 275// Usage with custom headers (for authentication) 276// api.post('/protected-endpoint', { data: 'value' }, { 277// headers: { 278// 'Authorization': 'Bearer ' + localStorage.getItem('token') 279// }, 280// onSuccess: (response) => { 281// console.log('Success:', response); 282// } 283// });
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.