+1
-1
@@ -1,1 +0,1 @@ | ||
| "use strict";module.exports=class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);if(this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.features.modalConfig.deletable)){const e=this.modalElement.querySelector(".anygrid-btn-delete");e&&e.addEventListener("click",(()=>{this._handleDeleteRecord()}))}}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,50)}; /* Slightly darker */\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];return"number"===t.type&&(a=parseFloat(a),r=parseFloat(r)),n?a-r:r-a}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}; | ||
| "use strict";module.exports=class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.modalElement.querySelector(".anygrid-btn-delete").addEventListener("click",(()=>{this._handleDeleteRecord()})))}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,25)};\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}darkenColor(e,t){const n=parseInt(e.slice(1),16),i=t<0?0:255,a=t<0?-1*t:t,r=n>>16,s=n>>8&255,o=255&n;return"#"+(16777216+65536*(Math.round((i-r)*a)+r)+256*(Math.round((i-s)*a)+s)+(Math.round((i-o)*a)+o)).toString(16).slice(1)}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];if(null==a&&(a=""),null==r&&(r=""),"number"===t.type)return a=parseFloat(a)||0,r=parseFloat(r)||0,n?a-r:r-a;if("string"==typeof a&&"string"==typeof r)return n?a.localeCompare(r,void 0,{sensitivity:"base"}):r.localeCompare(a,void 0,{sensitivity:"base"});const s=String(a),o=String(r);return n?s.localeCompare(o,void 0,{sensitivity:"base"}):o.localeCompare(s,void 0,{sensitivity:"base"})}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}; |
@@ -1,1 +0,1 @@ | ||
| var AnyGrid=function(){"use strict";return class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);if(this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.features.modalConfig.deletable)){const e=this.modalElement.querySelector(".anygrid-btn-delete");e&&e.addEventListener("click",(()=>{this._handleDeleteRecord()}))}}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,50)}; /* Slightly darker */\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];return"number"===t.type&&(a=parseFloat(a),r=parseFloat(r)),n?a-r:r-a}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}}(); | ||
| var AnyGrid=function(){"use strict";return class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.modalElement.querySelector(".anygrid-btn-delete").addEventListener("click",(()=>{this._handleDeleteRecord()})))}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,25)};\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}darkenColor(e,t){const n=parseInt(e.slice(1),16),i=t<0?0:255,a=t<0?-1*t:t,r=n>>16,s=n>>8&255,o=255&n;return"#"+(16777216+65536*(Math.round((i-r)*a)+r)+256*(Math.round((i-s)*a)+s)+(Math.round((i-o)*a)+o)).toString(16).slice(1)}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];if(null==a&&(a=""),null==r&&(r=""),"number"===t.type)return a=parseFloat(a)||0,r=parseFloat(r)||0,n?a-r:r-a;if("string"==typeof a&&"string"==typeof r)return n?a.localeCompare(r,void 0,{sensitivity:"base"}):r.localeCompare(a,void 0,{sensitivity:"base"});const s=String(a),o=String(r);return n?s.localeCompare(o,void 0,{sensitivity:"base"}):o.localeCompare(s,void 0,{sensitivity:"base"})}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}}(); |
+1
-1
@@ -1,1 +0,1 @@ | ||
| class e{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);if(this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.features.modalConfig.deletable)){const e=this.modalElement.querySelector(".anygrid-btn-delete");e&&e.addEventListener("click",(()=>{this._handleDeleteRecord()}))}}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,50)}; /* Slightly darker */\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];return"number"===t.type&&(a=parseFloat(a),r=parseFloat(r)),n?a-r:r-a}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}export{e as default}; | ||
| class e{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.modalElement.querySelector(".anygrid-btn-delete").addEventListener("click",(()=>{this._handleDeleteRecord()})))}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,25)};\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}darkenColor(e,t){const n=parseInt(e.slice(1),16),i=t<0?0:255,a=t<0?-1*t:t,r=n>>16,s=n>>8&255,o=255&n;return"#"+(16777216+65536*(Math.round((i-r)*a)+r)+256*(Math.round((i-s)*a)+s)+(Math.round((i-o)*a)+o)).toString(16).slice(1)}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];if(null==a&&(a=""),null==r&&(r=""),"number"===t.type)return a=parseFloat(a)||0,r=parseFloat(r)||0,n?a-r:r-a;if("string"==typeof a&&"string"==typeof r)return n?a.localeCompare(r,void 0,{sensitivity:"base"}):r.localeCompare(a,void 0,{sensitivity:"base"});const s=String(a),o=String(r);return n?s.localeCompare(o,void 0,{sensitivity:"base"}):o.localeCompare(s,void 0,{sensitivity:"base"})}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}export{e as default}; |
+1
-1
@@ -1,1 +0,1 @@ | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).AnyGrid=t()}(this,(function(){"use strict";return class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);if(this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.features.modalConfig.deletable)){const e=this.modalElement.querySelector(".anygrid-btn-delete");e&&e.addEventListener("click",(()=>{this._handleDeleteRecord()}))}}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,50)}; /* Slightly darker */\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];return"number"===t.type&&(a=parseFloat(a),r=parseFloat(r)),n?a-r:r-a}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}})); | ||
| !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).AnyGrid=t()}(this,(function(){"use strict";return class{constructor(e,t,n={}){this.data=e,this.dataApiEndPoint=n.dataApiEndPoint||null,this.totalRecords=e.length,this.columns=t,this.itemsPerPage=n.initialItemsPerPage||10,this.currentPage=1,this.tbody=null,this.searchInput=null,this.paginationContainer=null,this.filteredData=this.data,this.sortingOrder={},this.dataTableId=this.generateUniqueId("anygrid-datatable"),this.paginationContainerId=this.generateUniqueId("anygrid-pagination"),this.searchInputId=this.generateUniqueId("search-input"),this.itemsPerPageId=this.generateUniqueId("items-per-page"),this.gridContainerId=n.gridContainerId||"anygrid";const i={search:!0,sort:!0,actions:!0,pagination:!0,itemsPerPage:!0,dynamicHeaders:!0,mode:"datagrid",theme:"default",initialItemsPerPage:10,gridModal:!1,modalConfig:{editable:!1,nonEditableFields:["id"],deletable:!1,animation:"fade",closeOnOutsideClick:!0,confirmDelete:!0,confirmEdit:!0}};if(this.features={...i,...n,modalConfig:{...i.modalConfig,...n.modalConfig||{}}},this.initializeDataGrid(),n.themeColor)this.applyDynamicTheme(n.themeColor,this.gridContainerId);else if(this.features.theme){let e=this.features.theme;"dark"===e&&(e="default"),this.applyTheme(e,this.gridContainerId)}else this.applyTheme("dark",this.gridContainerId);this.features.search&&(this.searchInput=document.getElementById(`${this.searchInputId}`),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this._editState={originalRecord:null,pendingChanges:{}},this.features.gridModal&&(this._initModalStructure(),this._setupRowClickHandlers(),this.modalElement.querySelector(".anygrid-btn-delete").addEventListener("click",(()=>{this._handleDeleteRecord()})))}applyDynamicTheme(e,t){const n=document.getElementById(t);if(!n)return void console.error(`Grid container with ID '${t}' not found.`);Array.from(n.classList).filter((e=>e.endsWith("-theme"))).forEach((e=>n.classList.remove(e))),n.classList.add("dynamic-theme");const i=this.hexToRgb(e),a=document.createElement("style");a.id="anygrid-dynamic-theme";const r=document.getElementById("anygrid-dynamic-theme");r&&r.remove();const s=`\n .dynamic-theme {\n --background-dark: #ededeb; /* Keep light theme value */\n --background-light: #f9f9f9; /* Keep light theme value */\n --text-light: #333333; /* Keep light theme value */\n --border-color: #cccccc; /* Keep light theme value */\n --input-background: #ffffff; /* Keep light theme value */\n --input-background-disabled: #e0e0e0; /* Keep light theme value */\n --label-color: ${e}; /* Use theme color */\n --radio-checkbox-accent: ${e}; /* Use theme color */\n --button-background: ${this.lightenColor(e,20)}; /* Very light version of theme color */\n --button-background-hover: ${this.lightenColor(e,25)};\n --edit-background: #e91e63; /* Keep light theme value (red) */\n --delete-background: #dc3545; /* Keep light theme value (red) */\n --text-contrast: #ffffff; /* Keep light theme value */\n --shadow-color: rgba(79, 77, 77, 0.1); /* Keep light theme value */\n --primary-color: ${e}; /* Use theme color */\n --primary-color-rgb: ${i};\n --hover-bg: ${this.lightenColor(e,90)}; /* Very subtle hover effect */\n \n /* Additional variables for consistency */\n --focus-shadow: 0 0 0 0.2rem ${this.hexToRgb(e,.25)};\n --link-color: ${e};\n --link-hover-color: ${this.lightenColor(e,30)};\n }\n `;a.textContent=s,document.head.appendChild(a)}lightenColor(e,t){const n=parseInt(e.replace("#",""),16),i=Math.round(2.55*t),a=(n>>16)+i,r=(n>>8&255)+i,s=(255&n)+i;return`#${(16777216+65536*(a<255?a<1?0:a:255)+256*(r<255?r<1?0:r:255)+(s<255?s<1?0:s:255)).toString(16).slice(1)}`}darkenColor(e,t){const n=parseInt(e.slice(1),16),i=t<0?0:255,a=t<0?-1*t:t,r=n>>16,s=n>>8&255,o=255&n;return"#"+(16777216+65536*(Math.round((i-r)*a)+r)+256*(Math.round((i-s)*a)+s)+(Math.round((i-o)*a)+o)).toString(16).slice(1)}hexToRgb(e,t=1){const n=parseInt(e.slice(1,3),16),i=parseInt(e.slice(3,5),16),a=parseInt(e.slice(5,7),16);return 1===t?`rgb(${n}, ${i}, ${a})`:`rgba(${n}, ${i}, ${a}, ${t})`}_initModalStructure(){this.modalElement=document.createElement("div"),this.modalElement.className=`anygrid-modal ${this.features.modalConfig.animation}`,this.modalElement.style.display="none",this.modalElement.innerHTML=`\n <div class="modal-content">\n <button class="modal-close">×</button>\n <div class="modal-body"></div>\n ${this.features.modalConfig.deletable?'<div class="modal-footer"><button class="anygrid-btn-delete">Delete</button></div>':""}\n </div>\n <div class="modal-backdrop"></div>\n `,document.body.appendChild(this.modalElement),this.modalElement.querySelector(".modal-close").addEventListener("click",(()=>{this._hideModal()})),this.features.modalConfig.closeOnOutsideClick&&this.modalElement.querySelector(".modal-backdrop").addEventListener("click",(()=>{this._hideModal()}))}_setupRowClickHandlers(){this.tbody?(this.tbody.addEventListener("click",(e=>{const t=e.target.closest("tr");if(!t)return;const n=t.dataset.rowIndex||Array.from(this.tbody.children).indexOf(t),i=this.filteredData[n];i&&this._showModalWithData(i)})),this.tbody.style.cursor="pointer"):console.warn("Tbody not found - check initializeGrid()")}_showModalWithData(e){if(!this.modalElement)return;this.currentRecord=e;const t=this.modalElement.querySelector(".modal-body");this.features.modalConfig.editable?(t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field" data-field="${e}">\n <strong>${e}:</strong>\n <span class="field-value">${t}</span>\n </div>\n `)).join(""),this._setupClickToEdit()):t.innerHTML=Object.entries(e).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span>${t}</span>\n </div>\n `)).join(""),this.features.modalConfig.editable&&this._setupModalFooter(),this._editState.originalRecord={...e},this._editState.pendingChanges={},this.modalElement.style.display="block",document.body.style.overflow="hidden"}_setupClickToEdit(){const e=this.modalElement.querySelectorAll(".record-field"),t=["id","uuid","created_at","updated_at",...this.features.modalConfig.nonEditableFields||[]];e.forEach((e=>{const n=e.dataset.field,i=!t.includes(n);if(!i)return e.classList.add("non-editable"),void(e.style.cursor="not-allowed");e.style.cursor="pointer",e.addEventListener("click",(t=>{if(!i||"BUTTON"===t.target.tagName||"INPUT"===t.target.tagName)return;const a=e.querySelector(".field-value"),r=a.textContent;a.innerHTML=`\n <input type="text" value="${r}" \n data-field="${n}"\n class="field-input">\n `;const s=a.querySelector("input");s.focus(),s.select();const o=()=>this._saveFieldEdit(e,n,s);s.addEventListener("blur",o),s.addEventListener("keyup",(e=>{"Enter"===e.key&&o()}))}))}))}_setupModalFooter(){let e=this.modalElement.querySelector(".modal-footer");e||(e=document.createElement("div"),e.className="modal-footer",this.modalElement.querySelector(".modal-content").appendChild(e));const t=e.querySelector(".btn-save");if(t&&t.remove(),this.features.modalConfig.editable){const t=document.createElement("button");t.className="btn-save",t.textContent="Save All Changes",t.addEventListener("click",(()=>this._saveAllEdits())),e.prepend(t)}if(this.features.modalConfig.deletable&&!e.querySelector(".anygrid-btn-delete")){const t=document.createElement("button");t.className="anygrid-btn-delete",t.textContent="Delete",t.addEventListener("click",(()=>this._handleDelete())),e.appendChild(t)}}_saveFieldEdit(e,t,n){const i=n.value;if(e.querySelector(".field-value").textContent=i,this.currentRecord[t]=i,this.features.modalConfig.confirmEdit){const t=document.createElement("span");t.className="edit-confirmation",t.textContent="✓ Saved",e.appendChild(t),setTimeout((()=>t.remove()),2e3)}}_saveAllEdits(){const e={};this.modalElement.querySelectorAll(".record-field").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value").textContent;e[n]=i,this.currentRecord[n]=i})),console.log("All changes saved:",e),this.features.modalConfig.confirmEdit&&alert("All changes saved successfully!")}_hideModal(){this.modalElement&&(this.modalElement.style.display="none",document.body.style.overflow="",this.currentRecord=null)}generateUniqueId(e){return`${e}-${Math.random().toString(36).substring(2,7)}`}initializeDataGrid(){const e=document.getElementById(this.gridContainerId);if(e){const t=[5,10,20,50,100],n=this.features.itemsPerPage?t.map((e=>`\n <option value="${e}" ${e===this.itemsPerPage?"selected":""}>${e}</option>\n `)).join(""):"",i=this.features.csvExport?`\n <button id="export-csv-${this.gridContainerId}" class="anygrid-export-csv">Export CSV</button>\n `:"",a=this.features.excelExport?`\n <button id="export-excel-${this.gridContainerId}" class="anygrid-export-excel">Export EXCEL</button>\n `:"",r=`\n <div class="search-container"> \n ${this.features.search?`<input type="text" id="${this.searchInputId}" class="anygrid-search-input" placeholder="Search...">`:""}\n ${this.features.itemsPerPage?`<select id="${this.itemsPerPageId}" class="items-per-page">${n}</select>`:""}\n ${i} ${a}\n </div>\n \n <table class="anygrid-table" id="${this.dataTableId}">\n <thead>\n <tr></tr>\n </thead>\n <tbody></tbody>\n </table>\n ${this.features.pagination?`<div id="${this.paginationContainerId}" class="anygrid-pagination"></div>`:""}\n `,s=document.createElement("template");s.innerHTML=r.trim();const o=s.content.cloneNode(!0);if(e.appendChild(o),this.features.csvExport){document.getElementById(`export-csv-${this.gridContainerId}`).addEventListener("click",this.exportToCSV.bind(this))}if(this.features.excelExport){document.getElementById(`export-excel-${this.gridContainerId}`).addEventListener("click",this.exportToExcel.bind(this))}if(this.features.itemsPerPage){const e=document.getElementById(`${this.itemsPerPageId}`);e.value=this.itemsPerPage,e.addEventListener("change",(e=>{this.itemsPerPage=parseInt(e.target.value),this.currentPage=1,this.renderData(),this.updatePagination()}))}this.features.search&&(this.searchInput=document.getElementById(this.searchInputId),this.searchInput.addEventListener("input",this.searchTable.bind(this))),this.tbody=document.querySelector(`#${this.dataTableId} tbody`),this.paginationContainer=document.getElementById(`${this.paginationContainerId}`),this.renderData(this.filteredData),this.updatePagination()}}renderData(){Array.isArray(this.filteredData)||(console.warn("filteredData is not an array - resetting to empty array"),this.filteredData=[]),this.tbody.innerHTML="";const e=document.querySelector(`#${this.dataTableId} thead tr`);if(!e)return void console.error("Header row not found!");let t,n;if(e.innerHTML="",this.features.dynamicHeaders&&this.columns.forEach(((t,n)=>{if(!t||t.hidden)return;const i=document.createElement("th");if(i.textContent=t.label||t.header||`Column ${n}`,t.joinedColumns)Array.isArray(t.joinedColumns);else if(this.features.sort&&t.sortable){i.dataset.index=n,"function"==typeof this.sortTable&&i.addEventListener("click",(()=>this.sortTable(n)));const e=document.createElement("span");e.innerHTML=" ⇅",e.style.fontSize="1.1em",e.style.marginLeft="0.2em",e.className="anygrid-column-sortable",i.appendChild(e)}e.appendChild(i)})),this.features.pagination){const e=Math.max(1,parseInt(this.itemsPerPage)||10);t=(Math.max(1,this.currentPage)-1)*e,n=Math.min(t+e,this.filteredData.length)}else t=0,n=this.filteredData.length;this.filteredData.slice(Math.max(0,t),Math.min(this.filteredData.length,n)).forEach(((e,t)=>{if(!e||"object"!=typeof e)return;const n=document.createElement("tr");this.columns.forEach((t=>{if(t&&!t.hidden)if(t.joinedColumns&&Array.isArray(t.joinedColumns)){const i=document.createElement("td"),a=t.joinedColumns.map((t=>e[t])).join(" ");i.textContent=a,n.appendChild(i)}else{const i=document.createElement("td");let a=e[t.name];if(i.setAttribute("data-id",t.name),"id"===t.name&&n.setAttribute("data-id",a),null==a&&(a=""),t.render)try{"string"==typeof t.render?i.innerHTML=t.render.replace(`{${t.name}}`,a):"function"==typeof t.render&&(i.innerHTML=t.render(a,e))}catch(e){console.error("Error in column render:",e),i.textContent=a}else i.textContent=a;n.appendChild(i)}})),this.tbody.appendChild(n)})),this.features.pagination&&this.updatePagination()}updatePagination(){if(this.features.pagination){const e=this.itemsPerPage,t=Math.ceil(this.filteredData.length/e),n=Math.max(1,this.currentPage-5),i=Math.min(n+9,t);this.paginationContainer.innerHTML="";const a=document.createElement("div");a.classList.add("pagination-wrapper");const r=(this.currentPage-1)*e+1,s=Math.min(this.currentPage*e,this.totalRecords),o=this.totalRecords,d=document.createElement("div");d.classList.add("pagination-info"),d.textContent=`Showing ${r} to ${s} of ${o} records`;const l=document.createElement("div");l.classList.add("pagination-buttons");for(let e=n;e<=i;e++){const t=document.createElement("button");t.textContent=e,t.classList.add("pagination-button"),e===this.currentPage&&t.classList.add("active"),t.onclick=()=>{this.currentPage=e,this.renderData()},l.appendChild(t)}a.appendChild(d),a.appendChild(l),this.paginationContainer.appendChild(a)}}sortTable(e){if(this.features.sort){const t=this.columns[e],n="asc"!==this.sortingOrder[t.name],i=[...this.filteredData].sort(((e,i)=>{let a=e[t.name],r=i[t.name];if(null==a&&(a=""),null==r&&(r=""),"number"===t.type)return a=parseFloat(a)||0,r=parseFloat(r)||0,n?a-r:r-a;if("string"==typeof a&&"string"==typeof r)return n?a.localeCompare(r,void 0,{sensitivity:"base"}):r.localeCompare(a,void 0,{sensitivity:"base"});const s=String(a),o=String(r);return n?s.localeCompare(o,void 0,{sensitivity:"base"}):o.localeCompare(s,void 0,{sensitivity:"base"})}));this.filteredData=i,this.sortingOrder[t.name]=n?"asc":"desc",this.renderData()}}searchTable(){const e=this.searchInput.value.toLowerCase();this.filteredData=this.data.filter((t=>this.columns.some((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];return null==i&&(i=""),"string"!=typeof i&&(i=String(i)),i.toLowerCase().includes(e)})))),this.currentPage=1,this.renderData()}applyTheme(e,t){const n=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((e=>e.href.includes("anyGrid.css")));n?fetch(n.href).then((e=>e.text())).then((n=>{const i=n.match(new RegExp(`\\.${e}-theme\\s*{([^}]*)}`,"i"));if(!i)return void console.error(`Theme rules for '${e}' not found in the stylesheet.`);const a=i[1].trim(),r=document.getElementById(t);if(r){r.classList.add(`${e}-theme`);const n=document.createElement("style");n.textContent=`\n #${t} {\n ${a}\n }\n `,r.parentNode.insertBefore(n,r),console.log(`Applied '${e}' theme to grid container: ${t}`)}else console.error(`Grid container with ID '${t}' not found.`)})).catch((e=>{console.error("Error loading the stylesheet:",e)})):console.error("Stylesheet referencing 'anyGrid.css' not found!")}exportToCSV(e){const t=e.target.id.replace("export-csv-","");if(this.features.csvExport){const e=this.columns.map((e=>e.label||e.header)).join(","),n=this.filteredData.map((e=>this.columns.map((t=>{let n=t.joinedColumns?t.joinedColumns.map((t=>e[t])).join(" "):e[t.name];return`"${String(n).replace(/"/g,'""')}"`})).join(","))).join("\n"),i=encodeURI(`data:text/csv;charset=utf-8,${e}\n${n}`),a=document.createElement("a");a.setAttribute("href",i),a.setAttribute("download",`data-${t}.csv`),a.click()}}exportToExcel(e){const t=e.target.id.replace("export-excel-","");if(this.features.excelExport){let e='\n <xml version="1.0" encoding="UTF-8"?>\n <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:o="urn:schemas-microsoft-com:office:office"\n xmlns:x="urn:schemas-microsoft-com:office:excel"\n xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"\n xmlns:html="http://www.w3.org/TR/REC-html40">\n <Worksheet ss:Name="Sheet1">\n <Table>\n ';e+="<Row>",this.columns.forEach((t=>{e+=`<Cell><Data ss:Type="String">${t.label||t.header}</Data></Cell>`})),e+="</Row>",this.filteredData.forEach((t=>{e+="<Row>",this.columns.forEach((n=>{let i=n.joinedColumns?n.joinedColumns.map((e=>t[e])).join(" "):t[n.name];const a=isNaN(i)?"String":"Number";e+=`<Cell><Data ss:Type="${a}">${i}</Data></Cell>`})),e+="</Row>"})),e+="\n </Table>\n </Worksheet>\n </Workbook>\n ";const n=new Blob([e],{type:"application/vnd.ms-excel"}),i=URL.createObjectURL(n),a=document.createElement("a");a.href=i,a.setAttribute("download",`data-${t}.xls`),a.click()}}_getReadOnlyContent(e){return Object.entries(e).filter((([e])=>!e.startsWith("_"))).map((([e,t])=>`\n <div class="record-field">\n <strong>${e}:</strong>\n <span class="field-value">${null!==t?t:"null"}</span>\n </div>\n `)).join("")}_refreshModalContent(e){const t=this.modalElement.querySelector(".modal-body");if(!t)return;null!==t.querySelector("input")?this.columns.forEach((n=>{const i=t.querySelector(`input[data-field="${n.field}"]`);i&&(i.value=e[n.field]??"")})):t.innerHTML=this._getReadOnlyContent(e)}async _saveAllEdits(){if(this.currentRecord?.id)try{const e=this._getChangedFields();if(0===Object.keys(e).length)return void this._showStatusMessage("No changes detected","info");const t=await this._recordUpdateApi({recordId:this.currentRecord.id,...e});this._refreshUIAfterEdit(t),this._showSuccessMessage()}catch(e){this._showErrorMessage(e.message)}else this._showStatusMessage("No record loaded","error")}_getFieldValue(e){if("INPUT"===e.tagName)switch(e.type){case"checkbox":case"radio":return e.checked;case"number":return e.value?Number(e.value):null;case"date":return e.valueAsDate;default:return e.value}return"SELECT"===e.tagName?e.multiple?Array.from(e.selectedOptions).map((e=>e.value)):e.value:e.textContent}_getChangedFields(){const e={};return this.modalElement.querySelectorAll(".record-field[data-field]").forEach((t=>{const n=t.dataset.field,i=t.querySelector(".field-value");if(!i)return;const a=this._extractCleanValue(i.textContent);a!==this._editState.originalRecord[n]&&(e[n]=a)})),e}_extractCleanValue(e){if(!e)return null;const t=e.split(":").pop().replace("✓ Saved","").trim();if("null"===t)return null;if(e.includes("metadata:"))try{return t?JSON.parse(t):null}catch{return t}return t}async _recordUpdateApi(e){if(!e.recordId)throw new Error("recordId is required");const t=`${this.dataApiEndPoint}/${e.recordId}`,n={...e};delete n.recordId;const i=this.features?.modalConfig?.nonEditableFields||[];Object.keys(n).forEach((e=>{i.includes(e)?delete n[e]:n[e]=this._cleanPayloadValue(n[e])})),console.debug("API Payload:",n);try{this._showLoadingState(!0);const e=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!e.ok){const t=await e.json().catch((()=>({})));throw new Error(t.message||`HTTP ${e.status}`)}return await e.json()}catch(e){throw console.error("API Error:",e),e}finally{this._showLoadingState(!1)}}_cleanPayloadValue(e){return null==e||"null"===e?null:isNaN(e)||""===e?e:Number(e)}_refreshUIAfterEdit(e){this.currentRecord=e,console.log("current Update Record",JSON.stringify(e)),this._updateDataObject(),this.renderData(),this._showModalWithData(e),this._highlightUpdatedRow(e.id)}_updateDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&(this.data[e]={...this.data[e],...this.currentRecord})}_deleteFromDataObject(){const e=this.data.findIndex((e=>e.id===this.currentRecord.id));-1!==e&&this.data.splice(e,1)}_highlightUpdatedRow(e){if(!this.tbody||!e)return;const t=this.tbody.querySelector(`tr[data-id="${String(e)}"]`);t&&(t.classList.remove("row-updated"),t.offsetWidth,t.classList.add("row-updated"),setTimeout((()=>{t.classList.remove("row-updated")}),1e4))}_showLoadingState(e){const t=this.modalElement.querySelector(".modal-footer");if(t)if(e){const e=document.createElement("div");e.className="modal-status loading",e.textContent="Saving...",t.appendChild(e)}else t.querySelector(".modal-status.loading")?.remove()}_showSuccessMessage(){const e=this.modalElement.querySelector(".modal-footer"),t=document.createElement("div");t.className="modal-status success",t.textContent="✓ Saved successfully",e.appendChild(t),setTimeout((()=>t.remove()),3e3)}_showErrorMessage(e){const t=this.modalElement.querySelector(".modal-footer"),n=document.createElement("div");n.className="modal-status error",n.innerHTML=`✗ Error: ${e} <button class="btn-retry">Retry</button>`,t.appendChild(n),n.querySelector(".btn-retry").addEventListener("click",(()=>{this._saveAllEdits()}))}async _handleDeleteRecord(){if(this.currentRecord?.id&&(!this.features.modalConfig.confirmDelete||confirm(`Delete record ${this.currentRecord.id}?`)))try{const e=this._getRowElement(this.currentRecord.id);e&&(e.classList.add("row-deleting"),await new Promise((e=>setTimeout(e,300)))),await this._deleteRecordApi(this.currentRecord.id),this._deleteFromDataObject();const t=this.modalElement.querySelector(".modal-body");t&&(t.innerHTML=`\n <div class="delete-confirmation">\n ✓ Record ${this.currentRecord.id} deleted\n </div>\n `),setTimeout((()=>{this._hideModal(),this.renderData()}),1500)}catch(e){const t=this._getRowElement(this.currentRecord.id);t&&t.classList.remove("row-deleting");const n=this.modalElement.querySelector(".modal-body");n&&(n.innerHTML=`\n <div class="delete-error">\n ✗ Delete failed: ${e.message}\n <button class="retry-btn">Retry</button>\n </div>\n `,n.querySelector(".retry-btn").addEventListener("click",(()=>{this._handleDeleteRecord()})))}}async _deleteRecordApi(e){if(!this.dataApiEndPoint)throw new Error("API endpoint not configured");const t=await fetch(`${this.dataApiEndPoint}/${e}`,{method:"DELETE"});if(!t.ok)throw new Error(`HTTP ${t.status}`)}_getRowElement(e){return this.tbody?.querySelector(`tr[data-id="${e}"]`)}}})); |
+1
-1
| { | ||
| "name": "anygridjs", | ||
| "version": "1.0.14", | ||
| "version": "1.0.15", | ||
| "description": "Lightweight, headless, framework agnostic, feature rich, paginated, extensible, sortable, searchable data tables in plain js. Open-source & MIT licensed.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+35
-1
@@ -1,3 +0,14 @@ | ||
| # AnyGrid Documentation | ||
| # AnyGrid Data Grid Documentation | ||
| [](https://www.npmjs.com/package/anygridjs) | ||
| [](https://www.npmjs.com/package/anygridjs) | ||
| [](https://github.com/Gugulethu-Nyoni/anygridjs/stargazers) | ||
| [](https://github.com/Gugulethu-Nyoni/anygridjs/issues) | ||
| <a href="https://github.com/Gugulethu-Nyoni/anygridjs/blob/main/LICENSE"> | ||
| <img alt="anygridjs uses the MIT license" src="https://img.shields.io/github/license/Gugulethu-Nyoni/anygridjs" style="max-width: 100%;"> | ||
| </a> | ||
| [](https://github.com/Gugulethu-Nyoni/anygridjs/actions) | ||
| <img src="https://github.com/Gugulethu-Nyoni/anygrid/blob/main/images/anygridjs-datagrid-js-datatable-example.png" alt="AnyGrid Example"> | ||
@@ -9,3 +20,26 @@ | ||
| ## Table of Contents | ||
| - [AnyGrid Data Grid Documentation](#anygrid-data-grid-documentation) | ||
| - [Feature Overview](#feature-overview) | ||
| - [Data Handling & Display](#data-handling--display) | ||
| - [Customization & Theming](#customization--theming) | ||
| - [Interactivity & Modals](#interactivity--modals) | ||
| - [Integration & Deployment](#integration--deployment) | ||
| - [Why Choose AnyGrid?](#why-choose-anygrid) | ||
| - [Installation & Usage](#installation--usage) | ||
| - [Installation](#installation) | ||
| - [Usage](#usage) | ||
| - [The Data Object](#the-data-object) | ||
| - [Column Definition](#column-definition) | ||
| - [Default Features](#default-features) | ||
| - [Styling (Optional)](#styling-optional) | ||
| - [Color Themes](#color-themes) | ||
| - [API Integration with smQL](#api-integration-with-smql) | ||
| - [Advanced Usage](#advanced-usage) | ||
| - [Using Custom Containers](#using-custom-containers) | ||
| - [Contribute](#contribute) | ||
| - [License](#license) | ||
| - [Additional Resources](#additional-resources) | ||
| ## Feature Overview | ||
@@ -12,0 +46,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
108375
3.56%333
2.46%513
7.1%