function CR_formdataappend(v1,v2) { if (v1 == ""){return v2;}; if (v2 == ""){return v1;}; return v1+"&"+v2; } function CR_objectToFormData(obj) { var formstring = ""; for(var property in obj) { if(obj.hasOwnProperty(property)) { // if the property is an object, but not a File, // use recursivity. if(typeof obj[property] === 'object') { formstring = CR_formdataappend(formstring,CR_objectToFormData(obj[property])); } else { // if it's a string or a File object formstring = CR_formdataappend(formstring,property+"="+obj[property]); } } } return formstring; }; function CR_post(link,postdata,callback=null,callbackdata=null) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { if (callback !== null) callback(this.responseText,callbackdata); } }; xmlhttp.open("POST", link, true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); if (postdata && typeof postdata === 'object') { xmlhttp.send(CR_objectToFormData(postdata)); } else xmlhttp.send(postdata); }; var cr_tables=[]; cr_tables['update_delay']=2000; var cr_table_log=false; var cr_modal, cr_modal_text; function cr_cookies_get() { return document.cookie.split(';').map(function split(e) { var tmp = e.trim().split('='); return tmp; }); } function cr_cookies_index(needle, cks=null) { if ( cks == null ) cks = cr_cookies_get(); let keys = cks.map(function primul(e) {return e[0]}); return keys.indexOf(needle); } function table_parse_config(name,cfg) { cr_tables[name]=cfg; cr_tables[name]["columns_visible"]=new Array(); cr_tables[name]["columns_sort"]=new Array(); cr_tables[name]["columns_filter"]=new Array(); cr_tables[name]["TotalRecordCount"]=0; cr_tables[name]["pageNo"]=1; if (cr_tables[name]['actions']['insert']) { if (cr_tables[name]['insert_label']) cr_tables[name]['insert_label']= ' '+cr_tables[name]['insert_label']; else cr_tables[name]['insert_label']= ' Add line'; } if (cr_tables[name]["paging"]) { if (isNaN(cr_tables[name]["pageSize"])) cr_tables[name]["pageSize"]=50; if ((!cr_tables[name]["pageSizes"]) || (cr_tables[name]["pageSizes"].constructor !== Array)) cr_tables[name]["pageSizes"]=[25,50,100,200,500,1000]; } for(var propt in cr_tables[name]["columns"]) { if (! cr_tables[name]["columns"][propt].hasOwnProperty("id")) delete cr_tables[name]["columns"][propt]; else { if (!(cr_tables[name]["columns"][propt]["show"]===false)) cr_tables[name]["columns_visible"].push(propt); if (cr_tables[name]["columns"][propt]["key"]===true) cr_tables[name]["key"]=cr_tables[name]["columns"][propt]["id"]; } } } function table_create(name,divid,cfg) { var cookies_array = cr_cookies_get(); table_parse_config(name,cfg); cr_tables[name]['divid']=document.getElementById(divid); cr_tables[name]['divid'].classList.add('cr_table_div'); var tbl = document.createElement('table'); cr_tables[name]['tbl']=tbl; tbl.classList.add("cr_table"); // table header name var th = tbl.createTHead(); var tr = th.insertRow(); var td = tr.insertCell(); if (cr_tables[name]['actions']['print']) td.innerHTML = cr_tables[name]['title'] + "
"; else td.innerHTML = cr_tables[name]['title']; td.classList.add('cr_table_title'); td.setAttribute('colSpan', cr_tables[name]["columns_visible"].length); if (cr_tables[name]['actions']['insert']) { var addrow=document.createElement("div"); addrow.style.float = "right" addrow.style.cursor='copy' addrow.innerHTML=cr_tables[name]['insert_label']; addrow.setAttribute("onclick", "cr_click_add_row('" + name + "')"); td.appendChild(addrow); } // if we have filtering, need to create the modal window if (cr_tables[name]["filtering"] && (! cr_modal)) { cr_modal=document.createElement("div"); cr_modal.className="modal"; var cd=document.createElement("div"); cd.className="modal-content"; cr_modal.appendChild(cd); var cls=document.createElement("span"); cls.className="close"; cls.innerHTML="×"; cls.setAttribute("onclick", "cr_close_filter_button()"); cd.appendChild(cls); var pg=document.createElement("p"); pg.innerHTML="Filter:"; cd.appendChild(pg); cr_modal_filterdiv = document.createElement("div"); cd.appendChild(cr_modal_filterdiv); var okbtn = document.createElement("input"); okbtn.setAttribute("id", "cr_modal_btn"); okbtn.setAttribute("type", "button"); okbtn.setAttribute("name", "ok" ); okbtn.setAttribute("value", "Apply Filter" ); okbtn.setAttribute("onclick", "cr_close_filter_button()"); cd.appendChild(okbtn); document.body.appendChild(cr_modal); } // table header columns var tr = th.insertRow(); for(var j = 0; j < cr_tables[name]["columns_visible"].length; j++) { var col=cr_tables[name]["columns"][cr_tables[name]["columns_visible"][j]]; var td = tr.insertCell(); td.appendChild(document.createTextNode(col["title"])); if (col.width) td.style.width=col.width; var thbtnsdiv=document.createElement("div"); //add the sorting buttons on header row if (cr_tables[name]["sorting"] && (col["sorting"]===true)) { var tdata="\u21F3"; if (col["sort"]=='asc') tdata="\u21E7"; else if (col["sort"]=='desc') { tdata="\u21E9";} if ( (tempindex = cr_cookies_index(`cr_table_sort_${name}_${col["id"]}`,cookies_array))>-1) tdata=cookies_array[tempindex][1]; var btsort = document.createElement("button"); btsort.name = "btnsort"+col["id"]; btsort.dataset['cid']=col["id"]; btsort.value = cr_tables[name]["columns_visible"][j]; btsort.setAttribute("onclick", "cr_click_sorting_button('" + name + "', this)"); btsort.innerHTML=tdata; cr_tables[name]["columns_sort"].push(btsort); thbtnsdiv.appendChild(btsort); } //add the filter buttons on top of header row if (cr_tables[name]["filtering"] && (col["filter"]===true)) { var btnfilter = document.createElement("button"); btnfilter.name = "btnfilter"+col["id"]; btnfilter.dataset['cid']=col["id"]; btnfilter.dataset['search']=''; if ( (tempindex = cr_cookies_index(`cr_table_filter_${name}_${col["id"]}`,cookies_array))>-1) btnfilter.dataset['search']=cookies_array[tempindex][1]; btnfilter["FT"] = "IN"; if (!col["filtertype"]) col["filtertype"]="search"; if ( (col["filtertype"]=="search") || (col["filtertype"]=="date")) { fltr = document.createElement("input"); fltr.setAttribute("type", col["filtertype"]); fltr.setAttribute("name", "cr_modal" ); fltr.value=btnfilter.dataset['search']; if (btnfilter.dataset['search'] !='') btnfilter.style.background="red"; fltr.addEventListener("keyup", function(event) {event.preventDefault(); if (event.keyCode === 13) {cr_close_filter_button();} }); btnfilter['filtru']=fltr; btnfilter.dataset['type']=col["filtertype"]; } if (col["filtertype"]=="checkbox") { fltrdiv = document.createElement("div"); for(i=0; i`; } else if ( cr_tables[name]["columns"][columnid]['textbox']) { new_tbody += ``; } //if it is an object - drop down list we create the element else if ( typeof(cr_tables[name]["columns"][columnid]['values'])=="object" ) { var opts=cr_tables[name]["columns"][columnid]['values']; new_tbody += ``; } else {//else we create the edit as an input string new_tbody += ``; } } else {//the cell is not editable so we ad it as an paragraph new_tbody += `

${datashow}

`; } } new_tbody += ""; } if (cr_tables[name]["grandtotal"]) { new_tbody += `

TOTAL:

`; new_tbody += `

0

`; } if (data["Updates"]) cr_table_ajax_update_process_data(data["Updates"]); } //console.log(new_tbody); //clear any rows tbl.innerHTML=new_tbody; } //if an element was changed we reset the update request back to the timeout value function cr_table_input_change(name,element) { element.style.background="#fdd"; tmid = setTimeout(cr_table_do_ajax_update, cr_tables['update_delay'], name, element); if (element.dataset.timer>0) { clearTimeout(element.dataset.timer); } element.dataset.timer=tmid; } //function called with the return data from an ajax update request to update values in table function cr_table_ajax_update_process_data(data) { for(var dr = 0; dr < data.length; dr++) { var ky = Object.keys(data[dr])[0]; var ppp=document.getElementById(ky); switch (ppp.tagName) { case "P": ppp.textContent=data[dr][ky]; break; case "INPUT": ppp.value=data[dr][ky]; break; } } } //function called to update the cell on server by ajax request function cr_table_do_ajax_update(name, element) { if (cr_table_log) document.getElementById("log").appendChild(document.createTextNode(element.dataset.key + " ~ " + element.name + " ~ " + element.value + "\n")); element.style.background=""; element.dataset.timer=0; var http = new XMLHttpRequest(); // xmlhttp.open("POST", cr_tables[name]["actions"]["update"], true); var url = cr_tables[name]["actions"]["update"]; if ((element.tagName=="INPUT") && (element.type == "checkbox")) element.value=( element.checked ? 1 : 0); var params = "ky="+element.dataset.key + "&cl=" + element.name + "&nv=" + element.value; http.open("POST", url, true); //Send the proper header information along with the request http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http.onreadystatechange = function() {//Call a function when the state changes. if(http.readyState == 4 && http.status == 200) { var rspns = JSON.parse(http.responseText); if (rspns.Updates) cr_table_ajax_update_process_data(rspns.Updates); if (cr_table_log) document.getElementById("log").appendChild(document.createTextNode(http.responseText + "\n")); } } http.send(params); } //reload data from ajax provider function cr_table_reload_fromserver(name) { var xmlhttp = new XMLHttpRequest(); var postvar = 'u=cri'; xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var myObj = JSON.parse(this.responseText); cr_table_update(name,myObj); cr_update_nav_div_buttons(name); } }; if (cr_tables[name]["paging"]) { postvar += "&rangepage="+cr_tables[name]["currentPage"] postvar += "&rangesize="+cr_tables[name]["pageSize"]; } if (cr_tables[name]["sorting"]) { for(var dr = 0; dr < cr_tables[name]["columns_sort"].length; dr++) { if (cr_tables[name]["columns_sort"][dr].innerHTML=="\u21E7") postvar += "&sort[]=A"+cr_tables[name]["columns_sort"][dr].dataset['cid']; if (cr_tables[name]["columns_sort"][dr].innerHTML=="\u21E9") postvar += "&sort[]=D"+cr_tables[name]["columns_sort"][dr].dataset['cid']; } } if (cr_tables[name]["filtering"]) { for(var dr = 0; dr < cr_tables[name]["columns_filter"].length; dr++) { if (cr_tables[name]["columns_filter"][dr].dataset['search']!="") { postvar += "&filter[]=FLTR"+cr_tables[name]["columns_filter"][dr].dataset['cid']; postvar += "&FLTR"+ cr_tables[name]["columns_filter"][dr].dataset['cid']+"="+cr_tables[name]["columns_filter"][dr].dataset['search']; } } } xmlhttp.open("POST", cr_tables[name]["actions"]["list"], true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(postvar); } function cr_click_add_row(name) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var myObj = JSON.parse(this.responseText); cr_table_update(name,myObj); cr_tables[name]["navupdaterequired"]=true; cr_update_nav_div_buttons(name); } }; var postvar="rangestart="+(cr_tables[name]["pageNo"]-1)*cr_tables[name]["pageSize"]; postvar += "&rangesize="+cr_tables[name]["pageSize"]; cr_addrow_input_vals(cr_tables[name]["columns"]).then(result => { if (result !== false) { // console.log("User input:", result); postvar += "&" + new URLSearchParams(result).toString(); xmlhttp.open("POST", cr_tables[name]["actions"]["insert"], true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(postvar); } }); } //------------------ page navigation buttons and drop downs--------------------- //function called when navigation buttons are pressed function cr_click_nav_buttons(name,btn) { cr_tables[name]["currentPage"]=btn; cr_tables[name]["navupdaterequired"]=true; cr_table_reload_fromserver(name); } function cr_click_nav_pagesize(name,newsize=-1) { if (newsize>0) cr_tables[name]["pageSize"]=newsize; cr_tables[name]["navupdaterequired"]=true; cr_table_reload_fromserver(name); } function cr_update_nav_div_buttons(name) { if (cr_tables[name]["paging"] && cr_tables[name]["navupdaterequired"]) { let nopages = ~~((cr_tables[name]["TotalRecordCount"]-1)/cr_tables[name]["pageSize"])+1; let options = Array(); let currentpage=cr_tables[name]["currentPage"]; options.push(currentpage); options.push(currentpage-1,currentpage+1); options.push(currentpage-2,currentpage+2); options.push(currentpage-10,currentpage+10); options.push(currentpage-100,currentpage+100); options.push(currentpage-1000,currentpage+1000); options.push(currentpage-10000,currentpage+10000); options.push(currentpage-100000,currentpage+100000); options.push(1,nopages); options = options.filter(item => (item > 0) && (item <= nopages)).sort((a, b) => a - b).filter((v, i, a) => a.indexOf(v) === i); var stringBuilder = ["Page: "]; for(var i = 0; i < options.length; i++) { var val = ``; stringBuilder.push(val); } stringBuilder.push(`
Lines/page: `); for(var i = 0; i < cr_tables[name]["pageSizes"].length; i++) { var val = ``; stringBuilder.push(val); } cr_tables[name]["navDiv"].innerHTML=stringBuilder.join(''); cr_tables[name]["navupdaterequired"]=false; } } //-------------- sorting buttons ------------------------------- function cr_click_sorting_button(name,btn) { var btntext=btn.innerHTML; var tdata; if (btntext=="\u21F3") tdata="\u21E7"; if (btntext=="\u21E7") tdata="\u21E9"; if (btntext=="\u21E9") tdata="\u21F3"; btn.innerHTML = tdata; cr_tables[name]["navupdaterequired"]=true; cr_table_reload_fromserver(name); document.cookie = `cr_table_sort_${name}_${btn.dataset.cid}=${tdata}`; } //-------------- filter buttons ------------------------------- function cr_click_filter_button(name,btn) { cr_modal_filterdiv.innerHTML=""; cr_modal_filterdiv.append(btn['filtru']); cr_modal.dataset['index']=btn.dataset['index']; cr_modal.dataset['name']=name; cr_modal.style.display="block"; btn['filtru'].focus(); } function cr_close_filter_button() { var name=cr_modal.dataset['name']; var btn=cr_tables[name]["columns_filter"][cr_modal.dataset['index']]; cr_modal.style.display="none"; switch (btn.FT) { case "IN": if (btn.filtru.value != btn.dataset.search) { btn.dataset.search = btn.filtru.value; cr_tables[name]["navupdaterequired"]=true; cr_table_reload_fromserver(name); } break; case "BC": list = []; for (var i = 0; i< btn.filtru.children.length; i++) { if (btn.filtru.children[i].checked) list.push(btn.filtru.children[i].value); } if (list.join() != btn.dataset.search) { btn.dataset.search = list.join(); cr_tables[name]["navupdaterequired"]=true; cr_table_reload_fromserver(name); } break; } if (btn.dataset.search !='') btn.style.background="red"; else btn.style.background=""; document.cookie = `cr_table_filter_${name}_${btn.dataset.cid}=${btn.dataset.search}`; } function cr_click_print(name) { var xmlhttp = new XMLHttpRequest(); var postvar = ''; if (cr_tables[name]["sorting"]) { for(var dr = 0; dr < cr_tables[name]["columns_sort"].length; dr++) { if (cr_tables[name]["columns_sort"][dr].innerHTML=="\u21E7") postvar += "&sort[]=A"+cr_tables[name]["columns_sort"][dr].dataset['cid']; if (cr_tables[name]["columns_sort"][dr].innerHTML=="\u21E9") postvar += "&sort[]=D"+cr_tables[name]["columns_sort"][dr].dataset['cid']; } } if (cr_tables[name]["filtering"]) { for(var dr = 0; dr < cr_tables[name]["columns_filter"].length; dr++) { if (cr_tables[name]["columns_filter"][dr].dataset['search']!="") { postvar += "&filter[]=FLTR"+cr_tables[name]["columns_filter"][dr].dataset['cid']; postvar += "&FLTR"+ cr_tables[name]["columns_filter"][dr].dataset['cid']+"="+cr_tables[name]["columns_filter"][dr].dataset['search']; } } } xmlhttp.open("POST", cr_tables[name]["actions"]["print"], true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.responseType = 'blob'; xmlhttp.onload = function () { var blob = this.response; var contentDispo = this.getResponseHeader('Content-Disposition'); var fileName = contentDispo.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1]; fileName = fileName.replace(/"/g,""); saveBlob(blob, fileName); } xmlhttp.send(postvar); } function cr_addrow_input_vals(columns) { // Filter columns with 'create' set to true const createColumns = columns.filter(column => column.create); if (createColumns.length === 0) { return Promise.resolve({nocols: true}); // No columns to ask for input } // Create modal elements const modal = document.createElement('div'); modal.style.position = 'fixed'; modal.style.top = '0'; modal.style.left = '0'; modal.style.width = '100%'; modal.style.height = '100%'; modal.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modal.style.display = 'flex'; modal.style.justifyContent = 'center'; modal.style.alignItems = 'center'; modal.style.zIndex = '1000'; const modalContent = document.createElement('div'); modalContent.style.backgroundColor = 'white'; modalContent.style.padding = '20px'; modalContent.style.borderRadius = '10px'; modalContent.style.maxWidth = '600px'; modalContent.style.width = '100%'; const title = document.createElement('h3'); title.textContent = 'Please input details:'; title.classList = 'cr_table_title'; modalContent.appendChild(title); // Create input fields for each column const form = document.createElement('form'); createColumns.forEach(column => { const label = document.createElement('label'); label.textContent = column.title + ': '; label.classList = 'jtable-data-row'; const input = document.createElement('input'); input.type = 'text'; input.classList = 'jtable-data-row'; input.id = column.id; form.appendChild(label); form.appendChild(input); form.appendChild(document.createElement('br')); form.appendChild(document.createElement('br')); }); modalContent.appendChild(form); // Create Ok and Cancel buttons const buttonsDiv = document.createElement('div'); const okButton = document.createElement('button'); okButton.textContent = 'OK'; const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; buttonsDiv.appendChild(okButton); buttonsDiv.appendChild(cancelButton); modalContent.appendChild(buttonsDiv); // Append modal to the body modal.appendChild(modalContent); document.body.appendChild(modal); // Handle the OK and Cancel button clicks return new Promise((resolve) => { okButton.addEventListener('click', () => { const result = {}; let allFilled = true; createColumns.forEach(column => { const inputValue = document.getElementById(column.id).value.trim(); if (inputValue === '') { allFilled = false; } result[column.id] = inputValue; }); if (allFilled) { document.body.removeChild(modal); // Close the modal resolve(result); // Return user input values } else { alert("Please fill all required fields."); } }); cancelButton.addEventListener('click', () => { document.body.removeChild(modal); // Close the modal resolve(false); // User canceled }); }); } function saveBlob(blob, fileName) { var a = document.createElement('a'); a.href = window.URL.createObjectURL(blob); a.download = fileName; a.dispatchEvent(new MouseEvent('click')); } class DynamicTable { constructor(name, divid, cfg) { this.name = name; this.divid = document.getElementById(divid); this.cfg = cfg; this.tableData = {}; this.modal = null; this.modalFilterDiv = null; this.updateDelay = 2000; this.logEnabled = false; this.updateTimer = null; // Initialize table structure this.initTable(); } initTable() { // Validate input parameters if (!this.divid) { throw new Error('Invalid container element'); } // Set up configuration this.parseConfig(this.cfg); this.createTableStructure(); this.loadData(); } parseConfig(cfg) { this.tableData = { columns_visible: [], columns_sort: [], columns_filter: [], TotalRecordCount: 0, pageNo: 1, key: null }; // Initialize default values if (cfg.paging) { this.tableData.pageSize = isNaN(cfg.pageSize) ? 50 : parseInt(cfg.pageSize); this.tableData.pageSizes = cfg.pageSizes || [25, 50, 100, 200, 500, 1000]; } // Process columns for (const colId in cfg.columns) { const col = cfg.columns[colId]; if (!col.hasOwnProperty("id")) continue; if (!(col.show === false)) { this.tableData.columns_visible.push(colId); } if (col.key === true) { this.tableData.key = col.id; } } // Set insert label with security if (cfg.actions?.insert && cfg.insert_label) { this.tableData.insert_label = ` ${cfg.insert_label}`; } else if (cfg.actions?.insert) { this.tableData.insert_label = ' Add line'; } } createTableStructure() { // Create main table element const tbl = document.createElement('table'); tbl.classList.add("cr_table"); // Set up the header this.createHeader(tbl); // Set up columns and filter buttons this.createColumnHeaders(tbl); // Set up footer navigation if paging is enabled if (this.cfg.paging) { this.createFooter(tbl); } // Create table body const tbdy = document.createElement("tbody"); tbl.appendChild(tbdy); // Store references this.tableData.tbl = tbl; this.tableData.tbdy = tbdy; this.divid.classList.add('cr_table_div'); this.divid.appendChild(tbl); } createHeader(tbl) { const thead = document.createElement("thead"); const tr = document.createElement("tr"); // Create title cell const tdTitle = document.createElement("td"); if (this.cfg.actions?.print) { tdTitle.innerHTML = `${this.escapeHtml(this.cfg.title)} `; } else { tdTitle.innerHTML = this.escapeHtml(this.cfg.title); } tdTitle.classList.add('cr_table_title'); tdTitle.setAttribute('colSpan', this.tableData.columns_visible.length); // Add insert button if needed if (this.cfg.actions?.insert) { const addRowBtn = document.createElement("div"); addRowBtn.style.float = "right"; addRowBtn.style.cursor = 'copy'; addRowBtn.innerHTML = this.tableData.insert_label; // Use event listener instead of inline onclick for security addRowBtn.addEventListener('click', (e) => { e.preventDefault(); DynamicTable.prototype.addRowClick(this.name); }); tdTitle.appendChild(addRowBtn); } tr.appendChild(tdTitle); thead.appendChild(tr); tbl.appendChild(thead); } createColumnHeaders(tbl) { const thead = tbl.querySelector("thead"); const row = document.createElement("tr"); // Process each visible column for (let i = 0; i < this.tableData.columns_visible.length; i++) { const colId = this.tableData.columns_visible[i]; const col = this.cfg.columns[colId]; const th = document.createElement("th"); // Add title text with security const textNode = document.createTextNode(this.escapeHtml(col.title)); th.appendChild(textNode); // Set column width if specified if (col.width) { th.style.width = col.width; } // Create sorting buttons when enabled const sortDiv = this.createSortButton(col, colId); if (sortDiv) { th.appendChild(sortDiv); } // Create filter buttons when enabled const filterDiv = this.createFilterButton(col, colId); if (filterDiv) { th.appendChild(filterDiv); } row.appendChild(th); } thead.appendChild(row); } createSortButton(col, colId) { if (!this.cfg.sorting || !col.sorting) return null; const sortBtn = document.createElement("button"); sortBtn.name = "btnsort" + col.id; sortBtn.dataset.cid = col.id; sortBtn.value = colId; // Get current sort state from cookies or default let sortState = "\u21F3"; if (col.sort === 'asc') { sortState = "\u21E7"; } else if (col.sort === 'desc') { sortState = "\u21E9"; } // Check cookie for previous state const cookieKey = `cr_table_sort_${this.name}_${col.id}`; const cookieValue = this.getCookieValue(cookieKey); if (cookieValue) { sortState = cookieValue; } sortBtn.innerHTML = sortState; // Add click event listener instead of inline onclick sortBtn.addEventListener('click', (e) => { e.preventDefault(); DynamicTable.prototype.sortButtonClick(this.name, e.currentTarget); }); this.tableData.columns_sort.push(sortBtn); return sortBtn; } createFilterButton(col, colId) { if (!this.cfg.filtering || !col.filter) return null; const filterBtn = document.createElement("button"); filterBtn.name = "btnfilter" + col.id; filterBtn.dataset.cid = col.id; filterBtn.dataset.search = ''; // Check for cookie value const cookieKey = `cr_table_filter_${this.name}_${col.id}`; const cookieValue = this.getCookieValue(cookieKey); if (cookieValue) { filterBtn.dataset.search = cookieValue; } filterBtn.FT = col.filtertype ? col.filtertype : 'search'; filterBtn.dataset.type = col.filtertype || 'search'; // Create input/filter elements based on type let filterElement; if (col.filtertype === "checkbox") { const filterDiv = document.createElement("div"); for (let i = 0; i < col.filtervalues.length; i++) { const cb = document.createElement("input"); cb.setAttribute("type", "checkbox"); cb.setAttribute("name", "cr_modal"); cb.setAttribute("value", col.filtervalues[i].id); if (col.filtervalues[i].checked) { cb.setAttribute("checked", true); } const textNode = document.createTextNode(col.filtervalues[i].txt); // Create a text node filterDiv.appendChild(textNode); filterDiv.appendChild(cb); } filterElement = filterDiv; } else { filterElement = this.createFilterInputElement(col, colId); } filterBtn.filtru = filterElement; filterBtn.dataset.V = ''; filterBtn.innerHTML = "🔍"; // Add event listener instead of inline onclick filterBtn.addEventListener('click', (e) => { e.preventDefault(); DynamicTable.prototype.filterButtonClick(this.name, e.currentTarget); }); this.tableData.columns_filter.push(filterBtn); return filterBtn; } createFilterInputElement(col, colId) { const filterInput = document.createElement("input"); filterInput.setAttribute("type", col.filtertype || "search"); filterInput.setAttribute("name", "cr_modal"); // Set value from cookie if available const cookieKey = `cr_table_filter_${this.name}_${col.id}`; const cookieValue = this.getCookieValue(cookieKey); filterInput.value = cookieValue || ''; // Add keyup event for Enter press filterInput.addEventListener("keyup", (e) => { e.preventDefault(); if (e.keyCode === 13) { DynamicTable.prototype.closeFilterButtonClick(); } }); return filterInput; } createFooter(tbl) { if (!this.cfg.paging) return; const tfoot = document.createElement("tfoot"); const row = document.createElement("tr"); const td = document.createElement("td"); // Create navigation div const navDiv = document.createElement("div"); navDiv.id = `cr_nav_${this.name}`; td.appendChild(navDiv); this.tableData.navDiv = navDiv; this.tableData.currentPage = 1; this.tableData.navupdaterequired = true; // Make footer span whole table width td.setAttribute('colSpan', this.tableData.columns_visible.length); row.appendChild(td); tfoot.appendChild(row); tbl.appendChild(tfoot); } updateTable(name, data) { const tbody = this.tableData.tbl.querySelector("tbody"); // Clear existing rows while (tbody.firstChild) { tbody.removeChild(tbody.firstChild); } if (this.tableData.TotalRecordCount !== parseInt(data["TotalRecordCount"])) { this.tableData.TotalRecordCount = parseInt(data["TotalRecordCount"]); DynamicTable.prototype.updateNavButtons(this.name); } // Process records const records = data["Records"]; for (let dr = 0; dr < records.length; dr++) { const tr = document.createElement("tr"); const key = records[dr][this.tableData.key]; // Create cells for each column for (let j = 0; j < this.tableData.columns_visible.length; j++) { const colId = this.tableData.columns_visible[j]; const dataId = this.cfg.columns[colId]['id']; const dataValue = records[dr][dataId]; const td = document.createElement("td"); // Check if editable const columnConfig = this.cfg.columns[colId]; if (columnConfig.edit) { // Handle checkbox input if (columnConfig.checkbox) { const checkboxInput = this.createCheckbox(dataId, key, dataValue); td.appendChild(checkboxInput); } // Handle textbox input else if (columnConfig.textbox) { const textArea = this.createTextArea(dataId, key, dataValue); td.appendChild(textArea); } // Handle select dropdown else if (typeof(columnConfig.values) === "object") { const selectElement = this.createSelect(dataId, key, columnConfig.values, dataValue); td.appendChild(selectElement); } // Default to text input else { const textInput = this.createTextInput(dataId, key, dataValue); td.appendChild(textInput); } } // Non-editable cell else { const paragraph = document.createElement("p"); paragraph.setAttribute("id", `${dataId}-${key}`); paragraph.innerHTML = this.escapeHtml(dataValue); td.appendChild(paragraph); } tr.appendChild(td); } tbody.appendChild(tr); } // Add grand total if needed if (this.cfg.grandtotal) { const totalRow = document.createElement("tr"); totalRow.style.background = "wheat"; totalRow.style.color = "red"; totalRow.style.fontSize = "16px"; const colSpanTd = document.createElement("td"); colSpanTd.setAttribute('colSpan', this.tableData.columns_visible.length - 1); colSpanTd.style.textAlign = "right"; const pTotal = document.createElement("p"); pTotal.innerHTML = "TOTAL:"; colSpanTd.appendChild(pTotal); totalRow.appendChild(colSpanTd); const grandTotalTd = document.createElement("td"); const pGrandTotal = document.createElement("p"); pGrandTotal.setAttribute("id", "grandtotal"); pGrandTotal.innerHTML = "0"; grandTotalTd.appendChild(pGrandTotal); totalRow.appendChild(grandTotalTd); tbody.appendChild(totalRow); } if (data["Updates"]) { this.processAjaxUpdateData(data["Updates"]); } } createCheckbox(id, key, value) { const checkbox = document.createElement("input"); checkbox.setAttribute("type", "checkbox"); checkbox.setAttribute("id", `${id}-${key}`); checkbox.setAttribute("name", id); checkbox.setAttribute("data-key", key); checkbox.value = value; if (value === "1" || value === true) { checkbox.checked = true; } // Add event listener instead of inline onchange checkbox.addEventListener('change', (e) => { e.preventDefault(); DynamicTable.prototype.inputChange(this.name, e.currentTarget); }); return checkbox; } createTextArea(id, key, value) { const textArea = document.createElement("textarea"); textArea.setAttribute("id", `${id}-${key}`); textArea.setAttribute("name", id); textArea.setAttribute("data-key", key); textArea.setAttribute("onchange", `DynamicTable.prototype.inputChange('${this.name}', this)`); // For compatibility textArea.style.width = "calc(100% - 5px)"; textArea.style.height = "calc(100% - 2px)"; textArea.value = value; return textArea; } createSelect(id, key, values, selectedValue) { const selectElement = document.createElement("select"); selectElement.setAttribute("id", `${id}-${key}`); selectElement.setAttribute("name", id); selectElement.setAttribute("data-key", key); for (let o in values) { const option = document.createElement("option"); option.value = o; option.text = values[o]; if (o === selectedValue) { option.selected = true; } selectElement.appendChild(option); } // Add event listener selectElement.addEventListener('change', (e) => { e.preventDefault(); DynamicTable.prototype.inputChange(this.name, e.currentTarget); }); return selectElement; } createTextInput(id, key, value) { const textInput = document.createElement("input"); textInput.setAttribute("type", "text"); textInput.setAttribute("id", `${id}-${key}`); textInput.setAttribute("name", id); textInput.setAttribute("data-key", key); textInput.setAttribute("value", value); // Add event listener textInput.addEventListener('input', (e) => { e.preventDefault(); DynamicTable.prototype.inputChange(this.name, e.currentTarget); }); return textInput; } processAjaxUpdateData(data) { for (let dr = 0; dr < data.length; dr++) { const key = Object.keys(data[dr])[0]; const element = document.getElementById(key); if (element) { switch (element.tagName) { case "P": element.textContent = data[dr][key]; break; case "INPUT": element.value = data[dr][key]; break; } } } } inputChange(name, element) { // Highlight the changed cell element.style.background = "#fdd"; const timerId = setTimeout(() => { this.sendAjaxUpdate(element); }, 2000); // Delay to prevent too many requests if (element.dataset.timer) { clearTimeout(element.dataset.timer); } element.dataset.timer = timerId; } sendAjaxUpdate(element) { if (this.tableLog) { console.log(`${element.dataset.key} ~ ${element.name} ~ ${element.value}`); } // Remove highlight element.style.background = ""; element.dataset.timer = 0; const http = new XMLHttpRequest(); let url = this.cfg.actions.update; if (element.tagName === "INPUT" && element.type === "checkbox") { element.value = element.checked ? "1" : "0"; } const params = `ky=${element.dataset.key}&cl=${element.name}&nv=${encodeURIComponent(element.value)}`; http.open("POST", url, true); http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200) { try { const response = JSON.parse(http.responseText); if (response.Updates) { this.processAjaxUpdateData(response.Updates); } if (this.tableLog) { console.log(http.responseText); } } catch (e) { console.error("Failed to parse AJAX response:", e); } } }.bind(this); // Bind 'this' context http.send(params); } reloadFromServer(name) { const xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { try { const data = JSON.parse(xmlhttp.responseText); this.updateTable(name, data); DynamicTable.prototype.updateNavButtons(this.name); } catch (e) { console.error("Failed to parse server response:", e); } } }.bind(this); // Bind 'this' context let postvar = "u=cri"; if (this.cfg.paging) { postvar += `&rangepage=${this.tableData.currentPage}`; postvar += `&rangesize=${this.tableData.pageSize}`; } if (this.cfg.sorting) { for (let i = 0; i < this.tableData.columns_sort.length; i++) { const btn = this.tableData.columns_sort[i]; if (btn.innerHTML === "\u21E7") postvar += `&sort[]=A${btn.dataset.cid}`; else if (btn.innerHTML === "\u21E9") postvar += `&sort[]=D${btn.dataset.cid}`; } } if (this.cfg.filtering) { for (let i = 0; i < this.tableData.columns_filter.length; i++) { const filterBtn = this.tableData.columns_filter[i]; if (filterBtn.dataset.search !== "") { postvar += `&filter[]=FLTR${filterBtn.dataset.cid}`; postvar += `&FLTR${filterBtn.dataset.cid}=${encodeURIComponent(filterBtn.dataset.search)}`; } } } xmlhttp.open("POST", this.cfg.actions.list, true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xmlhttp.send(postvar); } updateNavButtons(name) { if (!this.tableData.paging || !this.tableData.navupdaterequired) return; const totalPages = Math.ceil(this.tableData.TotalRecordCount / this.tableData.pageSize); let options = [1, totalPages]; // Add current and nearby page numbers for (let i = -2; i <= 2; i++) { if (this.tableData.currentPage + i > 0 && this.tableData.currentPage + i <= totalPages) { options.push(this.tableData.currentPage + i); } } // Add special page numbers const specialPages = [1, 10, 100, 1000, 10000, 100000]; for (let i = 0; i < specialPages.length; i++) { if (specialPages[i] <= totalPages) { options.push(specialPages[i]); } } // Remove duplicates and sort const uniqueOptions = Array.from(new Set(options)).sort((a, b) => a - b); let stringBuilder = ["Page: "]; for (let i = 0; i < uniqueOptions.length; i++) { const disabled = uniqueOptions[i] === this.tableData.currentPage ? 'disabled' : ''; const btn = ``; stringBuilder.push(btn); } stringBuilder.push(`
Lines/page: `); for (let i = 0; i < this.tableData.pageSizes.length; i++) { const disabled = this.tableData.pageSizes[i] === this.tableData.pageSize ? 'disabled' : ''; const btn = ``; stringBuilder.push(btn); } // Update the navDiv with new buttons this.tableData.navDiv.innerHTML = stringBuilder.join(''); this.tableData.navupdaterequired = false; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } getCookieValue(key) { const name = key + "="; const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(';'); for(let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); } if (c.indexOf(name) === 0) { return c.substring(name.length, c.length); } } return ""; } } // Other functions like click handlers, printing, etc. remain largely unchanged // Simplified version of cr_table_update for clarity in debugging function debug_cr_table_update(name,data) { console.log("Updating table:", name); console.log("Received data:", data); // Display the first few records if available if (data.Records && data.Records.length > 0) { console.log("First record example:", data.Records[0]); } } // Helper function to validate and sanitize input before sending updates function sanitizeInput(inputValue) { // Basic sanitization - remove potentially dangerous characters for HTML attributes or content return inputValue.replace(//g, '>'); } function showlogout() { if (confirm("Are you sure you want to leave?")) { CR_post("/main/logout.php",'',function() {window.location.href="/main/index.html";}) } }; //showlogout function chg_pass() { $.msgBox({ type: "prompt", title: "Change Password", inputs: [{ header: "Current Password", type: "password", name: "cp" }, { header: "New Password (at least 5 characters)", type: "password", name: "p1" }, { header: "Confirm New Password", type: "password", name: "p2" }], content: "Are you sure you want to change your password?", buttons: [{ value: "Yes" }, { value: "No"}], success: function (result, values) { if (result=="Yes") { if(values[0]["value"] == '') { alert("You need to provide your current Password"); return false; } if(values[1]["value"].length < 5 ) { alert("Please enter New Password, minimum 5 chars"); return false; } if(values[1]["value"] != values[2]["value"]) { alert("New Password and Confirm New Password should be same"); return false; } $.ajax({ type: "POST", data: "oldpass=" + values[0]["value"] + "&newpass=" + values[1]["value"], url: "/ajax/main/chpasswd_do.php", cache: false}).done(function(msg) { alert(msg); }); } } }); }; //showlogout class EnhanancedSelect extends HTMLElement { constructor() { super(); this.value = 666; this.innerHTML += `
Select..
`; const selected = this.querySelector(".selected"); const optionsContainer = this.querySelector(".options-container"); const searchBox = this.querySelector(".select-box input"); selected.addEventListener("click", (e) => { optionsContainer.style.display="block"; searchBox.style.display="block"; searchBox.value = ""; searchBox.focus(); selected.classList.add("active"); }); this._datalist = this.querySelector('datalist'); this._allowedValues = []; if ( this._datalist.options.length > 0 ) { var options = ''; for (const option of this._datalist.options) { options += '
'+ option.innerHTML +'
'; this._allowedValues.push(option.value); if (option.hasAttribute('selected')){ //TODO in caz de valoare implicita, trebuie procesat selected.innerHTML = option.innerHTML; this.value = option.id; } } optionsContainer.innerHTML = options; } this.optionsList = this.querySelectorAll(".option"); // this.optionsList.addEventListener('click', this.handleOptionSelect.bind(this)); this.optionsList.forEach(o => { o.addEventListener("click", () => { selected.innerHTML = o.innerHTML; this.value = o.id; optionsContainer.style.display="none"; searchBox.style.display="none"; this.dispatchEvent(new CustomEvent('change', { detail: this.value })); }); }); searchBox.optionsList = this.optionsList; searchBox.addEventListener("keyup", function(e) { let searchTerm=e.target.value; searchTerm = searchTerm.toLowerCase(); e.currentTarget.optionsList.forEach(option => { let label = option.innerText.toLowerCase(); if (label.indexOf(searchTerm) != -1) { option.style.display = "block"; } else { option.style.display = "none"; } }); }); searchBox.addEventListener("focusout", function(e) { searchBox.style.display="none"; selected.classList.remove("active"); setTimeout(() => { optionsContainer.style.display="none"; }, 500); }); this._ajaxenable=false; if (this.hasAttribute('data-source')) { this._ajaxsource = this.getAttribute('data-source'); if (this.hasAttribute('data-filter')) this._ajaxfilter = this.getAttribute('data-filter'); else this._ajaxfilter = ''; this._ajaxenable=true; }; this._allowuserinput=false; if (this.hasAttribute('data-userinput')) { this._allowuserinput=true; } } } window.customElements.define('enhanced-select', EnhanancedSelect); const grid_no_links=4, points_subdivision=50; var width, height, canvas, ctx, points, points_subdivisionY=50; // Main initGrid(); addListeners(); drawGrid(); function initGrid() { canvas = document.getElementById('grid_bg'); height = window.innerHeight width = window.innerWidth canvas.width = width; canvas.height = height; ctx = canvas.getContext('2d'); points_subdivisionY=Math.round(points_subdivision*height/width); // create points points = []; for(var x = 0; x < points_subdivision; x++) { for(var y = 0; y < points_subdivisionY; y++) { var px = x/points_subdivision + Math.random()/points_subdivision; var py = y/points_subdivisionY + Math.random()/points_subdivisionY; var p = {x: px, originX: px, y: py, originY: py }; points.push(p); } } // for each point find the required closest pointsto draw lines for(var i = 0; i < points.length; i++) { var closest = []; var p1 = points[i]; for(var j = 0; j < points.length; j++) { var p2 = points[j] if(!(p1 == p2)) { var placed = false; for(var k = 0; k < grid_no_links; k++) { if(!placed) { if(closest[k] == undefined) { closest[k] = p2; placed = true; } } } for(var k = 0; k < grid_no_links; k++) { if(!placed) { if(getDistance(p1, p2) < getDistance(p1, closest[k])) { closest[k] = p2; placed = true; } } } } } p1.closest = closest; } // assign a circle to each point for(var i in points) { var c = new Circle(points[i], 2+Math.random()*2, 'rgba(255,255,255,0.3)'); points[i].circle = c; points[i].active = Math.random()/3; points[i].circle.active = Math.random()/2; } } function addListeners() { window.addEventListener('resize', resize); } function resize() { width = window.innerWidth; height = window.innerHeight; canvas.width = width; canvas.height = height; drawGrid(); } function drawGrid() { ctx.clearRect(0,0,width,height); for(var i in points) { drawLines(points[i]); points[i].circle.draw(); } } function drawLines(p) { if(!p.active) return; for(var i in p.closest) { ctx.beginPath(); ctx.moveTo(p.x*width, p.y*height); ctx.lineTo(p.closest[i].x*width, p.closest[i].y*height); ctx.strokeStyle = 'rgba(156,217,249,'+ p.active+')'; ctx.stroke(); } } function Circle(pos,rad,color) { var _this = this; // constructor (function() { _this.pos = pos || null; _this.radius = rad || null; _this.color = color || null; })(); this.draw = function() { if(!_this.active) return; ctx.beginPath(); ctx.arc(_this.pos.x*width, _this.pos.y*height, _this.radius, 0, 2 * Math.PI, false); ctx.fillStyle = 'rgba(156,217,249,'+ _this.active+')'; ctx.fill(); }; } function getDistance(p1, p2) { return Math.pow(p1.x - p2.x, 2) + Math.pow((p1.y - p2.y)*height/width, 2); } class MultiInput extends HTMLElement { constructor() { super(); // This is a hack :^(. // ::slotted(input)::-webkit-calendar-picker-indicator doesn't work in any browser. // ::slotted() with ::after doesn't work in Safari. this.innerHTML += ``; this._shadowRoot = this.attachShadow({mode: 'open'}); this._shadowRoot.innerHTML = ` `; const input = document.createElement('input') input.type = "hidden"; input.classList.add("hidden-input"); this.appendChild(input); this._forminput=input; input.value = "[]"; if (this.hasAttribute('data-name')){ input.name = this.getAttribute('data-name'); } this._ajaxenable=false; if (this.hasAttribute('data-source')) { this._ajaxsource = this.getAttribute('data-source'); if (this.hasAttribute('data-filter')) this._ajaxfilter = this.getAttribute('data-filter'); else this._ajaxfilter = ''; this._ajaxenable=true; }; this._allowuserinput=false; if (this.hasAttribute('data-userinput')) { this._allowuserinput=true; } this._datalist = this.querySelector('datalist'); this._allowedValues = []; for (const option of this._datalist.options) { this._allowedValues.push(option.value); if (option.hasAttribute('selected')){ const item = document.createElement('div'); item.classList.add('item'); item.textContent = option.value; this.insertBefore(item, this._input); } } this._forminput.value = this.getValues(); this._input = this.querySelector('input'); this._input.onblur = this._handleBlur.bind(this); this._input.oninput = this._handleInput.bind(this); this._input.onkeydown = (event) => { this._handleKeydown(event); }; this._input.onkeyup = (event) => { this._handleKeyup(event); }; this._allowDuplicates = this.hasAttribute('allow-duplicates'); }; _ajaxcallback(data,multiinputobject){ multiinputobject._allowedValues = []; while (multiinputobject._datalist.firstChild && !multiinputobject._datalist.firstChild.remove()); var obj = JSON.parse( data ); for( let prop in obj ){ multiinputobject._allowedValues.push(obj[prop]); const option = document.createElement('option'); option.value = obj[prop]; multiinputobject._datalist.appendChild(option); } }; //called when input is 2 or more characters and we have an ajax address configured _ajaxupdateallowedvalues() { const xmlhttp = new XMLHttpRequest(); const callback = this._ajaxcallback; const multiinputobject = this; xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { callback(this.responseText,multiinputobject); } }; xmlhttp.open("POST", this._ajaxsource, true); xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); var searchstring = this._ajaxfilter; if (searchstring != '') searchstring += '=' + this._input.value xmlhttp.send(searchstring); }; // Called by _handleKeydown() when the value of the input is an allowed value. _addItem(value) { if (value =='') return; this._input.value = ''; const item = document.createElement('div'); item.classList.add('item'); item.textContent = value; this.insertBefore(item, this._input); item.onclick = () => { this._deleteItem(item); }; // Remove value from datalist options and from _allowedValues array. // Value is added back if an item is deleted (see _deleteItem()). if (!this._allowDuplicates) { for (const option of this._datalist.options) { if (option.value === value) { option.remove(); }; } this._allowedValues = this._allowedValues.filter((item) => item !== value); } this._forminput.value = this.getValues(); } // Called when the × icon is tapped/clicked or // by _handleKeydown() when Backspace is entered. _deleteItem(item) { const value = item.textContent; item.remove(); // If duplicates aren't allowed, value is removed (in _addItem()) // as a datalist option and from the _allowedValues array. // So — need to add it back here. if (!this._allowDuplicates) { const option = document.createElement('option'); option.value = value; // Insert as first option seems reasonable... this._datalist.insertBefore(option, this._datalist.firstChild); this._allowedValues.push(value); } } // Avoid stray text remaining in the input element that's not in a div.item. _handleBlur() { if (this._allowuserinput) { const option = document.createElement('option'); option.value = this._input.value; // Insert as first option seems reasonable... this._datalist.insertBefore(option, this._datalist.firstChild); this._allowedValues.push(this._input.value); this._addItem(this._input.value); } this._input.value = ''; } // Called when input text changes, // either by entering text or selecting a datalist option. _handleInput() { // Add a div.item, but only if the current value // of the input is an allowed value const value = this._input.value; if (this._allowedValues.includes(value)) { this._addItem(value); } } // Called when text is entered or keys pressed in the input element. _handleKeydown(event) { const itemToDelete = event.target.previousElementSibling; const value = this._input.value; // On Backspace, delete the div.item to the left of the input if (value ==='' && event.key === 'Backspace' && itemToDelete) { this._deleteItem(itemToDelete); // Add a div.item, but only if the current value // of the input is an allowed value } else if (this._allowedValues.includes(value)) { this._addItem(value); }; } // Called when text is entered or keys pressed in the input element. _handleKeyup(event) { const value = this._input.value; if (this._ajaxenable && value.length>1) { this._ajaxupdateallowedvalues(); } } // Public method for getting item values as an array. getValues() { const values = []; const items = this.querySelectorAll('.item'); for (const item of items) { values.push(item.textContent); } return values; } } window.customElements.define('multi-input', MultiInput);