@liedekef/ftable 1.1.42 → 1.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,8 +10,8 @@ FTable is a clean, vanilla JavaScript rewrite of the popular `jTable` plugin, de
10
10
  ✅ MIT Licensed
11
11
 
12
12
  ## Screenshots
13
- [![A screenshot of fTable](https://raw.githubusercontent.com/liedekef/ftable/master/screenshot.png)](https://github.com/liedekef/ftable/)
14
- [![Other screenshot, used in Events Made Easy](https://raw.githubusercontent.com/liedekef/ftable/master/screenshot2.png)](https://github.com/liedekef/ftable/)
13
+ [![A screenshot of fTable](https://raw.githubusercontent.com/liedekef/ftable/main/screenshot.png)](https://github.com/liedekef/ftable/)
14
+ [![Other screenshot, used in Events Made Easy](https://raw.githubusercontent.com/liedekef/ftable/main/screenshot2.png)](https://github.com/liedekef/ftable/)
15
15
 
16
16
  FTable is a clean, vanilla JavaScript rewrite of the popular `jTable` plugin, designed for modern web apps.
17
17
 
@@ -27,6 +27,9 @@ FTable is a clean, vanilla JavaScript rewrite of the popular `jTable` plugin, de
27
27
 
28
28
  ## Install
29
29
 
30
+ Use one of the releases on github or download the files [directly](https://github.com/liedekef/ftable/tree/main)
31
+ Or use npm:
32
+
30
33
  ```bash
31
34
  npm install @liedekef/ftable
32
35
  ```
package/ftable.esm.js CHANGED
@@ -199,7 +199,7 @@ class FTableDOMHelper {
199
199
 
200
200
  if (options.attributes) {
201
201
  Object.entries(options.attributes).forEach(([key, value]) => {
202
- if (value !== '')
202
+ if (value !== null)
203
203
  element.setAttribute(key, value);
204
204
  });
205
205
  }
@@ -1064,7 +1064,10 @@ class FTableFormBuilder {
1064
1064
 
1065
1065
  let input;
1066
1066
 
1067
- if (value == null || value == undefined ) {
1067
+ if (value == undefined) {
1068
+ value = null;
1069
+ }
1070
+ if (value == null && field.defaultValue ) {
1068
1071
  value = field.defaultValue;
1069
1072
  }
1070
1073
  // Auto-detect select type if options are provided
@@ -1155,7 +1158,7 @@ class FTableFormBuilder {
1155
1158
  attributes: {
1156
1159
  id: 'real-' + fieldName,
1157
1160
  type: 'hidden',
1158
- value: value || '',
1161
+ value: value,
1159
1162
  name: fieldName
1160
1163
  }
1161
1164
  });
@@ -1165,7 +1168,7 @@ class FTableFormBuilder {
1165
1168
  id: `Edit-${fieldName}`,
1166
1169
  type: 'text',
1167
1170
  'data-date': value,
1168
- placeholder: field.placeholder || '',
1171
+ placeholder: field.placeholder || null,
1169
1172
  readOnly: true
1170
1173
  };
1171
1174
  // Set any additional attributes
@@ -1219,8 +1222,8 @@ class FTableFormBuilder {
1219
1222
  const attributes = {
1220
1223
  type: inputType,
1221
1224
  id: `Edit-${fieldName}`,
1222
- placeholder: field.placeholder || '',
1223
- value: value || ''
1225
+ placeholder: field.placeholder || null,
1226
+ value: value
1224
1227
  };
1225
1228
 
1226
1229
  // extra check for name and multiple
@@ -1241,7 +1244,7 @@ class FTableFormBuilder {
1241
1244
  attributes.name = name;
1242
1245
 
1243
1246
  const input = FTableDOMHelper.create('input', {
1244
- className: field.inputClass || '',
1247
+ className: field.inputClass || null,
1245
1248
  attributes: attributes
1246
1249
  });
1247
1250
 
@@ -1263,8 +1266,8 @@ class FTableFormBuilder {
1263
1266
  type: 'text',
1264
1267
  name: fieldName,
1265
1268
  id: `Edit-${fieldName}`,
1266
- placeholder: field.placeholder || '',
1267
- value: value || '',
1269
+ placeholder: field.placeholder || null,
1270
+ value: value,
1268
1271
  list: `${fieldName}-datalist`
1269
1272
  };
1270
1273
 
@@ -1275,7 +1278,7 @@ class FTableFormBuilder {
1275
1278
  }
1276
1279
 
1277
1280
  const input = FTableDOMHelper.create('input', {
1278
- className: field.inputClass || '',
1281
+ className: field.inputClass || null,
1279
1282
  attributes: attributes
1280
1283
  });
1281
1284
 
@@ -1329,7 +1332,7 @@ class FTableFormBuilder {
1329
1332
  type: 'hidden',
1330
1333
  name: fieldName,
1331
1334
  id: `Edit-${fieldName}`,
1332
- value: value || ''
1335
+ value: value
1333
1336
  };
1334
1337
 
1335
1338
  // Apply inputAttributes
@@ -1345,7 +1348,7 @@ class FTableFormBuilder {
1345
1348
  const attributes = {
1346
1349
  name: fieldName,
1347
1350
  id: `Edit-${fieldName}`,
1348
- placeholder: field.placeholder || ''
1351
+ placeholder: field.placeholder || null
1349
1352
  };
1350
1353
 
1351
1354
  // Apply inputAttributes
@@ -1355,9 +1358,9 @@ class FTableFormBuilder {
1355
1358
  }
1356
1359
 
1357
1360
  const textarea = FTableDOMHelper.create('textarea', {
1358
- className: field.inputClass || '',
1361
+ className: field.inputClass || null,
1359
1362
  attributes: attributes,
1360
- value: value || ''
1363
+ value: value
1361
1364
  });
1362
1365
  return textarea;
1363
1366
  }
@@ -1386,7 +1389,7 @@ class FTableFormBuilder {
1386
1389
  attributes.name = name;
1387
1390
 
1388
1391
  const select = FTableDOMHelper.create('select', {
1389
- className: field.inputClass || '',
1392
+ className: field.inputClass || null,
1390
1393
  attributes: attributes
1391
1394
  });
1392
1395
 
@@ -1431,7 +1434,7 @@ class FTableFormBuilder {
1431
1434
 
1432
1435
  const radio = FTableDOMHelper.create('input', {
1433
1436
  attributes: radioAttributes,
1434
- className: field.inputClass || '',
1437
+ className: field.inputClass || null,
1435
1438
  parent: radioWrapper
1436
1439
  });
1437
1440
 
@@ -1565,8 +1568,8 @@ class FTableFormBuilder {
1565
1568
  }
1566
1569
  attributes.name = name;
1567
1570
 
1568
- const input = FTableDOMHelper.create('input', {
1569
- className: field.inputClass || '',
1571
+ return FTableDOMHelper.create('input', {
1572
+ className: field.inputClass || null,
1570
1573
  attributes: attributes
1571
1574
  });
1572
1575
  }
package/ftable.js CHANGED
@@ -200,7 +200,7 @@ class FTableDOMHelper {
200
200
 
201
201
  if (options.attributes) {
202
202
  Object.entries(options.attributes).forEach(([key, value]) => {
203
- if (value !== '')
203
+ if (value !== null)
204
204
  element.setAttribute(key, value);
205
205
  });
206
206
  }
@@ -1065,7 +1065,10 @@ class FTableFormBuilder {
1065
1065
 
1066
1066
  let input;
1067
1067
 
1068
- if (value == null || value == undefined ) {
1068
+ if (value == undefined) {
1069
+ value = null;
1070
+ }
1071
+ if (value == null && field.defaultValue ) {
1069
1072
  value = field.defaultValue;
1070
1073
  }
1071
1074
  // Auto-detect select type if options are provided
@@ -1156,7 +1159,7 @@ class FTableFormBuilder {
1156
1159
  attributes: {
1157
1160
  id: 'real-' + fieldName,
1158
1161
  type: 'hidden',
1159
- value: value || '',
1162
+ value: value,
1160
1163
  name: fieldName
1161
1164
  }
1162
1165
  });
@@ -1166,7 +1169,7 @@ class FTableFormBuilder {
1166
1169
  id: `Edit-${fieldName}`,
1167
1170
  type: 'text',
1168
1171
  'data-date': value,
1169
- placeholder: field.placeholder || '',
1172
+ placeholder: field.placeholder || null,
1170
1173
  readOnly: true
1171
1174
  };
1172
1175
  // Set any additional attributes
@@ -1220,8 +1223,8 @@ class FTableFormBuilder {
1220
1223
  const attributes = {
1221
1224
  type: inputType,
1222
1225
  id: `Edit-${fieldName}`,
1223
- placeholder: field.placeholder || '',
1224
- value: value || ''
1226
+ placeholder: field.placeholder || null,
1227
+ value: value
1225
1228
  };
1226
1229
 
1227
1230
  // extra check for name and multiple
@@ -1242,7 +1245,7 @@ class FTableFormBuilder {
1242
1245
  attributes.name = name;
1243
1246
 
1244
1247
  const input = FTableDOMHelper.create('input', {
1245
- className: field.inputClass || '',
1248
+ className: field.inputClass || null,
1246
1249
  attributes: attributes
1247
1250
  });
1248
1251
 
@@ -1264,8 +1267,8 @@ class FTableFormBuilder {
1264
1267
  type: 'text',
1265
1268
  name: fieldName,
1266
1269
  id: `Edit-${fieldName}`,
1267
- placeholder: field.placeholder || '',
1268
- value: value || '',
1270
+ placeholder: field.placeholder || null,
1271
+ value: value,
1269
1272
  list: `${fieldName}-datalist`
1270
1273
  };
1271
1274
 
@@ -1276,7 +1279,7 @@ class FTableFormBuilder {
1276
1279
  }
1277
1280
 
1278
1281
  const input = FTableDOMHelper.create('input', {
1279
- className: field.inputClass || '',
1282
+ className: field.inputClass || null,
1280
1283
  attributes: attributes
1281
1284
  });
1282
1285
 
@@ -1330,7 +1333,7 @@ class FTableFormBuilder {
1330
1333
  type: 'hidden',
1331
1334
  name: fieldName,
1332
1335
  id: `Edit-${fieldName}`,
1333
- value: value || ''
1336
+ value: value
1334
1337
  };
1335
1338
 
1336
1339
  // Apply inputAttributes
@@ -1346,7 +1349,7 @@ class FTableFormBuilder {
1346
1349
  const attributes = {
1347
1350
  name: fieldName,
1348
1351
  id: `Edit-${fieldName}`,
1349
- placeholder: field.placeholder || ''
1352
+ placeholder: field.placeholder || null
1350
1353
  };
1351
1354
 
1352
1355
  // Apply inputAttributes
@@ -1356,9 +1359,9 @@ class FTableFormBuilder {
1356
1359
  }
1357
1360
 
1358
1361
  const textarea = FTableDOMHelper.create('textarea', {
1359
- className: field.inputClass || '',
1362
+ className: field.inputClass || null,
1360
1363
  attributes: attributes,
1361
- value: value || ''
1364
+ value: value
1362
1365
  });
1363
1366
  return textarea;
1364
1367
  }
@@ -1387,7 +1390,7 @@ class FTableFormBuilder {
1387
1390
  attributes.name = name;
1388
1391
 
1389
1392
  const select = FTableDOMHelper.create('select', {
1390
- className: field.inputClass || '',
1393
+ className: field.inputClass || null,
1391
1394
  attributes: attributes
1392
1395
  });
1393
1396
 
@@ -1432,7 +1435,7 @@ class FTableFormBuilder {
1432
1435
 
1433
1436
  const radio = FTableDOMHelper.create('input', {
1434
1437
  attributes: radioAttributes,
1435
- className: field.inputClass || '',
1438
+ className: field.inputClass || null,
1436
1439
  parent: radioWrapper
1437
1440
  });
1438
1441
 
@@ -1566,8 +1569,8 @@ class FTableFormBuilder {
1566
1569
  }
1567
1570
  attributes.name = name;
1568
1571
 
1569
- const input = FTableDOMHelper.create('input', {
1570
- className: field.inputClass || '',
1572
+ return FTableDOMHelper.create('input', {
1573
+ className: field.inputClass || null,
1571
1574
  attributes: attributes
1572
1575
  });
1573
1576
  }
package/ftable.min.js CHANGED
@@ -1,4 +1,4 @@
1
- (e=>{let s={serverCommunicationError:"An error occurred while communicating to the server.",loadingMessage:"Loading records...",noDataAvailable:"No data available!",addNewRecord:"Add new record",editRecord:"Edit record",areYouSure:"Are you sure?",deleteConfirmation:"This record will be deleted. Are you sure?",yes:"Yes",no:"No",save:"Save",saving:"Saving",cancel:"Cancel",deleteText:"Delete",deleting:"Deleting",error:"An error has occured",close:"Close",cannotLoadOptionsFor:"Cannot load options for field {0}!",pagingInfo:"Showing {0}-{1} of {2}",canNotDeletedRecords:"Can not delete {0} of {1} records!",deleteProgress:"Deleting {0} of {1} records, processing...",pageSizeChangeLabel:"Row count",gotoPageLabel:"Go to page",sortingInfoPrefix:"Sorting applied: ",sortingInfoSuffix:"",ascending:"Ascending",descending:"Descending",sortingInfoNone:"No sorting applied",resetSorting:"Reset sorting",csvExport:"CSV",printTable:"🖨️ Print",cloneRecord:"Clone Record",resetTable:"Reset table",resetTableConfirm:"This will reset all columns, pagesize, sorting to their defaults. Do you want to continue?",resetSearch:"Reset"};class t{constructor(){this.cache=new Map,this.pendingRequests=new Map}generateKey(e,t){return e+"?"+Object.keys(t||{}).sort().map(e=>e+"="+t[e]).join("&")}get(e,t){e=this.generateKey(e,t);return this.cache.get(e)}set(e,t,s){e=this.generateKey(e,t);this.cache.set(e,s)}clear(e=null,t=null){if(e)if(t){t=this.generateKey(e,t);this.cache.delete(t)}else{var s,a=e.split("?")[0];for([s]of this.cache)s.startsWith(a)&&this.cache.delete(s)}else this.cache.clear()}async getOrCreate(e,t,s){let a=this.generateKey(e,t);e=this.cache.get(a);return e||(this.pendingRequests.has(a)?this.pendingRequests.get(a):(t=(async()=>{try{var e=await s();return this.cache.set(a,e),e}finally{this.pendingRequests.delete(a)}})(),this.pendingRequests.set(a,t),t))}size(){return this.cache.size}}class a{static LOG_LEVELS={DEBUG:0,INFO:1,WARN:2,ERROR:3,NONE:4};constructor(e=a.LOG_LEVELS.WARN){this.level=e}log(t,e){var s;!window.console||t<this.level||(s=Object.keys(a.LOG_LEVELS).find(e=>a.LOG_LEVELS[e]===t),console.log(`fTable ${s}: `+e))}debug(e){this.log(a.LOG_LEVELS.DEBUG,e)}info(e){this.log(a.LOG_LEVELS.INFO,e)}warn(e){this.log(a.LOG_LEVELS.WARN,e)}error(e){this.log(a.LOG_LEVELS.ERROR,e)}}class u{static create(e,t={}){let s=document.createElement(e);return t.className&&(s.className=t.className),t.style&&(s.style.cssText=t.style),t.attributes&&Object.entries(t.attributes).forEach(([e,t])=>{""!==t&&s.setAttribute(e,t)}),t.text&&(s.textContent=t.text),t.html&&(s.innerHTML=t.html),t.parent&&t.parent.appendChild(s),s}static find(e,t=document){return t.querySelector(e)}static findAll(e,t=document){return Array.from(t.querySelectorAll(e))}static addClass(e,t){e.classList.add(...t.split(" "))}static removeClass(e,t){e.classList.remove(...t.split(" "))}static toggleClass(e,t){e.classList.toggle(t)}static show(e){e.style.display=""}static hide(e){e.style.display="none"}static escapeHtml(e){if(!e)return e;let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"};return e.replace(/[&<>"']/g,e=>t[e])}}class l{static async request(e,t={}){var s={method:"GET",headers:{}},a={...s,...t};t.headers&&(a.headers={...s.headers,...t.headers});try{var i=await fetch(e,a);if(401===i.status)throw new Error("Unauthorized");if(!i.ok)throw new Error("HTTP error! status: "+i.status);var o=i.headers.get("content-type");if(o&&o.includes("application/json"))return await i.json();var r=await i.text();try{return JSON.parse(r)}catch{return{Result:"OK",Message:r}}}catch(e){throw e}}static async get(e,t={}){let a=new URL(e,window.location.href);return Object.entries(t).forEach(([e,s])=>{if(null!=s)if(Array.isArray(s)){let t=e.replace(/\[\]$/,"")+"[]";s.forEach(e=>{null!=e&&a.searchParams.append(t,e)})}else a.searchParams.append(e,s)}),this.request(a.toString(),{method:"GET",headers:{"Content-Type":"application/x-www-form-urlencoded"}})}static async post(e,t={}){e=new URL(e,window.location.href);let a=new FormData;return Object.entries(t).forEach(([e,s])=>{if(null!=s)if(Array.isArray(s)){let t=e.replace(/\[\]$/,"")+"[]";s.forEach(e=>{null!=e&&a.append(t,e)})}else a.append(e,s)}),this.request(e.toString(),{method:"POST",body:a})}}class i{constructor(e,t="localStorage"){this.prefix=e,this.method=t}set(e,t){var s,e=""+this.prefix+e;"localStorage"===this.method?localStorage.setItem(e,t):((s=new Date).setDate(s.getDate()+30),document.cookie=e+`=${t}; expires=${s.toUTCString()}; path=/`)}get(e){e=""+this.prefix+e;if("localStorage"===this.method)return localStorage.getItem(e);var t,s=e+"=";for(t of decodeURIComponent(document.cookie).split(";")){for(;" "===t.charAt(0);)t=t.substring(1);if(0===t.indexOf(s))return t.substring(s.length,t.length)}return null}remove(e){e=""+this.prefix+e;"localStorage"===this.method?localStorage.removeItem(e):document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"}generatePrefix(e,t){e=e?e+"#":"";return"ftable#"+(t=>{let s=0;if(0!==t.length)for(let e=0;e<t.length;e++){var a=t.charCodeAt(e);s=(s<<5)-s+a,s&=s}return s})(e+=t.join("$")+"#c"+t.length)}}class o{constructor(e={}){this.options={title:"Modal",content:"",buttons:[],className:"ftable-modal",parent:document.body,...e},this.overlay=null,this.modal=null,this.isOpen=!1}create(){this.overlay=u.create("div",{className:"ftable-modal-overlay",parent:this.options.parent}),this.modal=u.create("div",{className:"ftable-modal "+this.options.className,parent:this.overlay});u.create("h2",{className:"ftable-modal-header",text:this.options.title,parent:this.modal});u.create("span",{className:"ftable-modal-close",html:"&times;",parent:this.modal}).addEventListener("click",()=>this.close());var e=u.create("div",{className:"ftable-modal-body",parent:this.modal});if("string"==typeof this.options.content?e.innerHTML=this.options.content:e.appendChild(this.options.content),0<this.options.buttons.length){let s=u.create("div",{className:"ftable-modal-footer",parent:this.modal});this.options.buttons.forEach(e=>{var t=u.create("button",{className:"ftable-dialog-button "+(e.className||""),html:`<span>${e.text}</span>`,parent:s});e.onClick&&(t._originalOnClick=e.onClick,t.addEventListener("click",this._createWrappedClickHandler(t)))})}return this.options.closeOnOverlayClick&&this.overlay.addEventListener("click",e=>{e.target===this.overlay&&this.close()}),this.hide(),this}show(){return this.modal||this.create(),this.overlay.style.display="flex",this.isOpen=!0,this.modal.querySelectorAll(".ftable-dialog-button").forEach(e=>{e.disabled=!1}),this}hide(){return this.overlay&&(this.overlay.style.display="none"),this.isOpen=!1,this}close(){return this.hide(),this.options.onClose&&this.options.onClose(),this}destroy(){return this.overlay&&this.overlay.remove(),this.isOpen=!1,this}setContent(e){this.options.content=e;var t=this.modal.querySelector(".ftable-modal-body");t&&(t.innerHTML="","string"==typeof e?t.innerHTML=e:t.appendChild(e))}_createWrappedClickHandler(a){return async e=>{a.disabled=!0;try{var t,s=a._originalOnClick;"function"==typeof s&&(t=s.call(a,e))instanceof Promise&&await t}catch(e){console.error("Modal button action failed:",e)}finally{a.disabled=!1}}}}class r{constructor(e){this.options=e,this.dependencies=new Map,this.optionsCache=new t,this.originalFieldOptions=new Map,this.resolvedFieldOptions=new Map,Object.keys(this.options.fields||{}).forEach(e=>{this.resolvedFieldOptions.set(e,{})}),Object.entries(this.options.fields).forEach(([e,t])=>{this.originalFieldOptions.set(e,t.options)})}async getFieldOptions(t,s="table",e={}){var a=this.options.fields[t],i=this.originalFieldOptions.get(t);if(!i)return null;var o=this.shouldForceRefreshForContext(a,s,e),r=this.generateOptionsCacheKey(s,e);if(!o&&!e.forceRefresh){var n=this.resolvedFieldOptions.get(t)[r];if(n)return n}try{var l={...a,options:i},c=await this.resolveOptions(l,{...e},s,o);return this.resolvedFieldOptions.get(t)[r]=c}catch(e){return console.error(`Failed to resolve options for ${t} (${s}):`,e),i}}clearResolvedOptions(e=null,s=null){e?this.resolvedFieldOptions.has(e)&&(s?this.resolvedFieldOptions.get(e)[s]=null:this.resolvedFieldOptions.set(e,{table:null,create:null,edit:null})):s?this.resolvedFieldOptions.forEach((e,t)=>{this.resolvedFieldOptions.get(t)[s]=null}):this.resolvedFieldOptions.forEach((e,t)=>{this.resolvedFieldOptions.set(t,{table:null,create:null,edit:null})})}shouldForceRefreshForContext(e,t,s){return!!e.noCache&&("boolean"==typeof e.noCache?e.noCache:"function"==typeof e.noCache?e.noCache({context:t,...s}):"object"==typeof e.noCache&&!0===e.noCache[t])}generateOptionsCacheKey(e,t){let s=[e];return t.dependedValues&&Object.keys(t.dependedValues).sort().forEach(e=>{s.push(e+"="+t.dependedValues[e])}),s.join("|")}shouldIncludeField(e,t){return"create"===t?!1!==e.create&&!(!0===e.key&&!0!==e.create):"edit"!==t||!1!==e.edit}createFieldContainer(e,t,s,a){var i=u.create("div",{className:"hidden"!=t.type?"ftable-input-field-container":"",attributes:{id:"ftable-input-field-container-div-"+e}}),t=("hidden"!=t.type&&u.create("div",{className:"ftable-input-label",text:t.inputTitle||t.title,parent:i}),this.createInput(e,t,s[e],a));return i.appendChild(t),i}async createForm(e="create",t={}){this.currentFormRecord=t;var s,a,i,o,r=u.create("form",{className:`ftable-dialog-form ftable-${e}-form`});this.buildDependencyMap();for([s,a]of Object.entries(this.options.fields))this.shouldIncludeField(a,e)&&(i={...a},a.dependsOn?i.options=a.options:(o=await this.getFieldOptions(s,e,{record:t,source:e}),i.options=o),o=this.createFieldContainer(s,i,t,e),r.appendChild(o));return this.setupDependencyListeners(r),r}shouldResolveOptions(e){return e&&("function"==typeof e||"string"==typeof e)&&!Array.isArray(e)&&!("object"==typeof e&&!Array.isArray(e)&&0<Object.keys(e).length)}buildDependencyMap(){this.dependencies.clear(),Object.entries(this.options.fields).forEach(([t,s])=>{if(s.dependsOn){let e;"string"==typeof s.dependsOn&&(e=s.dependsOn.split(",").map(e=>e.trim()).filter(e=>e)).forEach(e=>{this.dependencies.has(e)||this.dependencies.set(e,[]),this.dependencies.get(e).push(t)})}})}setupDependencyListeners(s){Array.from(this.dependencies.keys()).forEach(e=>{var t=s.querySelector(`[name="${e}"]`);t&&t.addEventListener("change",()=>{this.handleDependencyChange(s,e)})}),this.handleDependencyChange(s)}async resolveOptions(e,t={},s="",a=!1){if(!e.options)return[];if(Array.isArray(e.options)||"object"==typeof e.options)return e.options;let i;t={...t,source:s,clearCache:()=>{a=!0,this.updateFieldCacheSetting(e,s,!0)}};if("function"==typeof e.options)i=await e.options(t);else{if("string"!=typeof e.options)return[];i=e.options}t=i&&"object"==typeof i&&i.url;let o=t?i.url:i;if(a=t&&void 0!==i.noCache?i.noCache:a,"string"!=typeof o)return[];if(!a)return this.optionsCache.getOrCreate(o,{},async()=>{try{var e=this.options.forcePost?await l.post(o):await l.get(o);return e.Options||e.options||e||[]}catch(e){return console.error(`Failed to load options from ${o}:`,e),[]}});try{var r=this.options.forcePost?await l.post(o):await l.get(o);return r.Options||r.options||r||[]}catch(e){return console.error(`Failed to load options from ${o}:`,e),[]}}updateFieldCacheSetting(e,t,s){e.noCache?"boolean"==typeof e.noCache?e.noCache={table:e.noCache,create:e.noCache,edit:e.noCache,[t]:s}:"object"==typeof e.noCache&&(e.noCache[t]=s):e.noCache={[t]:s}}clearOptionsCache(e=null,t=null){this.optionsCache.clear(e,t)}getFormValues(e){var t={},s=e.elements;for(let e=0;e<s.length;e++){var a=s[e],i=a.name;if(i&&!a.disabled)switch(a.type){case"checkbox":t[i]=a.checked?a.value||"1":"0";break;case"radio":a.checked&&(t[i]=a.value);break;case"select-multiple":t[i]=Array.from(a.selectedOptions).map(e=>e.value);break;default:t[i]=a.value}}return t}async handleDependencyChange(e,s=""){var a,i,o=this.getFormValues(e),r=e.classList.contains("ftable-create-form")?"create":"edit",n=this.currentFormRecord||{},l={record:n,source:r,form:e,dependedValues:o};for([a,i]of Object.entries(this.options.fields))if(i.dependsOn){if(""!==s)if(!i.dependsOn.split(",").map(e=>e.trim()).filter(e=>e).includes(s))continue;let t=e.querySelector(`[name="${a}"]`);if(t&&this.shouldIncludeField(i,r))try{"SELECT"===t.tagName?t.innerHTML='<option value="">Loading...</option>':"INPUT"===t.tagName&&t.list&&(c=document.getElementById(t.list.id))&&(c.innerHTML="");var c,d=t.value||n[a]||"",h={...l,dependsOnField:i.dependsOn,dependsOnValue:o[i.dependsOn]},p=await this.getFieldOptions(a,r,h);"SELECT"===t.tagName?this.populateSelectOptions(t,p,d):"INPUT"===t.tagName&&t.list&&(this.populateDatalistOptions(t.list,p),d)&&(t.value=d),setTimeout(()=>{t.dispatchEvent(new Event("change",{bubbles:!0}))},0)}catch(e){console.error(`Error loading options for ${a}:`,e),"SELECT"===t.tagName&&(t.innerHTML='<option value="">Error</option>')}}}parseInputAttributes(e){if("string"!=typeof e)return e||{};for(var t={},s=/(\w+)(?:=("[^"]*"|'[^']*'|\S+))?/g;null!==(i=s.exec(e));){var a=i[1],i=i[2]?i[2].replace(/^["']|["']$/g,""):"";t[a]=""===i?"true":i}return t}createInput(e,t,s,a){var i=u.create("div",{className:`ftable-input ftable-${t.type||"text"}-input`});let o;switch(null==s&&(s=t.defaultValue),!t.type&&t.options&&(t.type="select"),t.type){case"hidden":o=this.createHiddenInput(e,t,s);break;case"textarea":o=this.createTextarea(e,t,s);break;case"select":o=this.createSelect(e,t,s);break;case"checkbox":o=this.createCheckbox(e,t,s);break;case"radio":o=this.createRadioGroup(e,t,s);break;case"datalist":o=this.createDatalistInput(e,t,s);break;case"file":o=this.createFileInput(e,t,s);break;case"date":case"datetime":case"datetime-local":o=this.createDateInput(e,t,s);break;default:o=this.createTypedInput(e,t,s)}return"function"==typeof t.input?(a={field:t,record:this.currentFormRecord,inputField:o,formType:a},"string"==typeof(a=t.input(a))?i.innerHTML=a:a instanceof Node?i.appendChild(a):i.appendChild(o)):i.appendChild(o),t.explain&&u.create("div",{className:"ftable-field-explain",html:`<small>${t.explain}</small>`,parent:i}),i}createDateInput(s,a,i){if("undefined"==typeof FDatepicker)return createTypedInput(s,a,i);{let e=a.dateFormat||this.options.defaultDateFormat;var o,r=document.createElement("div"),n=u.create("input",{attributes:{id:"real-"+s,type:"hidden",value:i||"",name:s}}),i={id:"Edit-"+s,type:"text","data-date":i,placeholder:a.placeholder||"",readOnly:!0};a.inputAttributes&&(o=this.parseInputAttributes(a.inputAttributes),Object.assign(i,o));let t=u.create("input",{className:a.inputClass||"datepicker-input",attributes:i});switch(r.appendChild(n),r.appendChild(t),a.type){case"date":setTimeout(()=>{new FDatepicker(t,{format:e,altField:"real-"+s,altFormat:"Y-m-d"})},0);break;case"datetime":case"datetime-local":setTimeout(()=>{new FDatepicker(t,{format:e,timepicker:!0,altField:"real-"+s,altFormat:"Y-m-d H:i:00"})},0)}return r}}createTypedInput(e,t,s){var a,s={type:t.type||"text",id:"Edit-"+e,placeholder:t.placeholder||"",value:s||""};let i=e,o=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(s,a),void 0!==a.multiple&&!1!==a.multiple)&&(i=e+"[]"),s.name=i,u.create("input",{className:t.inputClass||"",attributes:s}));return o.addEventListener("keypress",e=>{if(13===(e.keyCode||e.which))return e.preventDefault(),o.dispatchEvent(new Event("change",{bubbles:!0})),!1}),o}createDatalistInput(e,t,s){var s={type:"text",name:e,id:"Edit-"+e,placeholder:t.placeholder||"",value:s||"",list:e+"-datalist"},a=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(s,a)),u.create("input",{className:t.inputClass||"",attributes:s})),s=u.create("datalist",{attributes:{id:e+"-datalist"}});return t.options&&this.populateDatalistOptions(s,t.options),document.body.appendChild(s),a.datalistElement=s,a}populateDatalistOptions(s,e){s.innerHTML="",Array.isArray(e)?e.forEach(e=>{u.create("option",{attributes:{value:e.Value||e.value||e},text:e.DisplayText||e.text||e,parent:s})}):"object"==typeof e&&Object.entries(e).forEach(([e,t])=>{u.create("option",{attributes:{value:e},text:t,parent:s})})}createHiddenInput(e,t,s){e={type:"hidden",name:e,id:"Edit-"+e,value:s||""};return t.inputAttributes&&(s=this.parseInputAttributes(t.inputAttributes),Object.assign(e,s)),u.create("input",{attributes:e})}createTextarea(e,t,s){var e={name:e,id:"Edit-"+e,placeholder:t.placeholder||""},a=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(e,a)),u.create("textarea",{className:t.inputClass||"",attributes:e,value:s||""}));return a}createSelect(e,t,s){var a={name:e,id:"Edit-"+e};let i=e;t.inputAttributes&&(o=this.parseInputAttributes(t.inputAttributes),Object.assign(a,o),void 0!==o.multiple&&!1!==o.multiple)&&(i=e+"[]"),a.name=i;var o=u.create("select",{className:t.inputClass||"",attributes:a});return t.options&&this.populateSelectOptions(o,t.options,s),o}createRadioGroup(o,r,n){let l=u.create("div",{className:"ftable-radio-group"});return r.options&&(Array.isArray(r.options)?r.options:"object"==typeof r.options?Object.entries(r.options).map(([e,t])=>({Value:e,DisplayText:t})):[]).forEach((e,t)=>{var s=u.create("div",{className:"ftable-radio-wrapper",parent:l}),a=o+"_"+t,i={type:"radio",name:o,id:a,value:e.Value||e.value||e},t=(r.required&&0===t&&(i.required="required"),r.disabled&&(i.disabled="disabled"),r.inputAttributes&&(t=this.parseInputAttributes(r.inputAttributes),Object.assign(i,t)),u.create("input",{attributes:i,className:r.inputClass||"",parent:s}));i.value===n&&(t.checked=!0),u.create("label",{attributes:{for:a},text:e.DisplayText||e.text||e,parent:s})}),l}createCheckbox(e,t,s){var a=u.create("div",{className:"ftable-yesno-check-wrapper"}),s=[1,"1",!0,"true"].includes(s);let i=this.options.messages.no,o=this.options.messages.yes;return t.values&&"object"==typeof t.values&&(void 0!==t.values[0]&&(i=t.values[0]),void 0!==t.values[1])&&(o=t.values[1]),u.create("input",{className:["ftable-yesno-check-input",t.inputClass||""].filter(Boolean).join(" "),attributes:{type:"checkbox",name:e,id:"Edit-"+e,value:"1"},parent:a}).checked=s,t.label?u.create("label",{className:"ftable-yesno-check-fixedlabel",attributes:{for:"Edit-"+e},text:t.label,parent:a}):u.create("label",{className:"ftable-yesno-check-text",attributes:{for:"Edit-"+e,"data-yes":o,"data-no":i},parent:a}),a}populateSelectOptions(a,e,i){a.innerHTML="",Array.isArray(e)?e.forEach(e=>{var t=void 0!==e.Value?e.Value:void 0!==e.value?e.value:e;let s=u.create("option",{attributes:{value:t},text:e.DisplayText||e.text||e,parent:a});e.Data&&"object"==typeof e.Data&&Object.entries(e.Data).forEach(([e,t])=>{s.setAttribute("data-"+e,t)}),s.value==i&&(s.selected=!0)}):"object"==typeof e&&Object.entries(e).forEach(([e,t])=>{t=u.create("option",{attributes:{value:e},text:t,parent:a});e==i&&(t.selected=!0)})}createFileInput(e,t,s){var a,i={type:"file",id:"Edit-"+e};let o=e;t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(i,a),void 0!==a.multiple&&!1!==a.multiple)&&(o=e+"[]"),i.name=o;u.create("input",{className:t.inputClass||"",attributes:i})}}class n extends class{constructor(){this.events={}}on(e,t){return this.events[e]||(this.events[e]=[]),this.events[e].push(t),this}once(t,s){let a=(...e)=>{this.off(t,a),s.apply(this,e)};return a.fn=s,this.on(t,a),this}emit(e,t={}){return this.events[e]&&this.events[e].forEach(e=>e(t)),this}off(e,t){return this.events[e]&&(this.events[e]=this.events[e].filter(e=>e!==t)),this}}{constructor(e,t={}){if(super(),this.element="string"==typeof e?document.querySelector(e):e,this.element){if(this.element.ftableInstance)return this.element.ftableInstance;this.options=this.mergeOptions(t),this.verifyOptions(),this.logger=new a(this.options.logLevel),this.userPrefs=new i("",this.options.saveUserPreferencesMethod),this.formBuilder=new r(this.options,this),this.state={records:[],totalRecordCount:0,currentPage:1,isLoading:!1,selectedRecords:new Set,sorting:[],searchQueries:{}},this.elements={},this.modals={},this.searchTimeout=null,this.lastSortEvent=null,this._recalculatedOnce=!1,this.shiftKeyDown=!1,this.lastSelectedRow=null,(this.element.ftableInstance=this).init()}}mergeOptions(e){var t={tableId:void 0,logLevel:a.LOG_LEVELS.WARN,actions:{},fields:{},forcePost:!0,closeOnOverlayClick:!0,animationsEnabled:!0,loadingAnimationDelay:1e3,defaultDateLocale:"",defaultDateFormat:"Y-m-d",saveUserPreferences:!0,saveUserPreferencesMethod:"localStorage",defaultSorting:"",tableReset:!1,paging:!1,pageList:"normal",pageSize:10,pageSizes:[10,25,50,100,250,500],gotoPageArea:"combobox",sorting:!1,multiSorting:!1,multiSortingCtrlKey:!0,selecting:!1,multiselect:!1,openChildAsAccordion:!1,toolbarsearch:!1,toolbarreset:!0,searchDebounceMs:300,listCache:3e4,messages:{...s}};return this.deepMerge(t,e)}deepMerge(e,t){var s,a={...e};for(s in t)t[s]&&"object"==typeof t[s]&&!Array.isArray(t[s])?a[s]=this.deepMerge(a[s]||{},t[s]):a[s]=t[s];return a}verifyOptions(){this.options.pageSize&&!this.options.pageSizes.includes(this.options.pageSize)&&(this.options.pageSize=this.options.pageSizes[0])}static setMessages(e){Object.assign(s,e)}init(){this.processFieldDefinitions(),this.createMainStructure(),this.setupFTableUserPreferences(),this.createTable(),this.createModals(),this.options.paging&&this.createPagingUI(),this.resolveAsyncFieldOptions().then(()=>{setTimeout(()=>{this.refreshDisplayValues()},0)}).catch(console.error),this.bindEvents(),this.updateSortingHeaders(),this.renderSortingInfo(),this.initColumnWidths()}initColumnWidths(){var e=this.columnList.filter(e=>{e=this.options.fields[e];return"hidden"!==e.visibility&&"separator"!==e.visibility});let t=e.length;e.forEach(e=>{e=this.options.fields[e];e.width=e.width||100/t+"%"})}normalizeColumnWidths(){var e=this.elements.mainContainer,t=this.columnList.map(e=>({th:this.elements.table.querySelector(`[data-field-name="${e}"]`),field:this.options.fields[e]})).filter(e=>e.th&&"hidden"!==e.field.visibility&&"separator"!==e.field.visibility);if(0!==t.length){let s=e.offsetWidth,a=0;t.forEach(e=>{var t=e.th.offsetWidth/s*100;e.field.width=t+"%",e.th.style.width=e.field.width,a+=t})}}parseDefaultSorting(e){let o=[];return e&&"string"==typeof e&&e.split(",").forEach(s=>{s=s.trim();if(s){var a=s.toUpperCase().indexOf(" DESC"),i=s.toUpperCase().indexOf(" ASC");let e="ASC",t=s;e=0<a?(t=s.slice(0,a).trim(),"DESC"):(t=(0<i?s.slice(0,i):s).trim(),"ASC");a=this.options.fields[t];a&&!1!==a.sorting&&o.push({fieldName:t,direction:e})}}),o}addEssentialCSS(){var e;document.querySelector("#ftable-essential-css")||((e=document.createElement("style")).id="ftable-essential-css",e.textContent=`
1
+ (e=>{let s={serverCommunicationError:"An error occurred while communicating to the server.",loadingMessage:"Loading records...",noDataAvailable:"No data available!",addNewRecord:"Add new record",editRecord:"Edit record",areYouSure:"Are you sure?",deleteConfirmation:"This record will be deleted. Are you sure?",yes:"Yes",no:"No",save:"Save",saving:"Saving",cancel:"Cancel",deleteText:"Delete",deleting:"Deleting",error:"An error has occured",close:"Close",cannotLoadOptionsFor:"Cannot load options for field {0}!",pagingInfo:"Showing {0}-{1} of {2}",canNotDeletedRecords:"Can not delete {0} of {1} records!",deleteProgress:"Deleting {0} of {1} records, processing...",pageSizeChangeLabel:"Row count",gotoPageLabel:"Go to page",sortingInfoPrefix:"Sorting applied: ",sortingInfoSuffix:"",ascending:"Ascending",descending:"Descending",sortingInfoNone:"No sorting applied",resetSorting:"Reset sorting",csvExport:"CSV",printTable:"🖨️ Print",cloneRecord:"Clone Record",resetTable:"Reset table",resetTableConfirm:"This will reset all columns, pagesize, sorting to their defaults. Do you want to continue?",resetSearch:"Reset"};class t{constructor(){this.cache=new Map,this.pendingRequests=new Map}generateKey(e,t){return e+"?"+Object.keys(t||{}).sort().map(e=>e+"="+t[e]).join("&")}get(e,t){e=this.generateKey(e,t);return this.cache.get(e)}set(e,t,s){e=this.generateKey(e,t);this.cache.set(e,s)}clear(e=null,t=null){if(e)if(t){t=this.generateKey(e,t);this.cache.delete(t)}else{var s,a=e.split("?")[0];for([s]of this.cache)s.startsWith(a)&&this.cache.delete(s)}else this.cache.clear()}async getOrCreate(e,t,s){let a=this.generateKey(e,t);e=this.cache.get(a);return e||(this.pendingRequests.has(a)?this.pendingRequests.get(a):(t=(async()=>{try{var e=await s();return this.cache.set(a,e),e}finally{this.pendingRequests.delete(a)}})(),this.pendingRequests.set(a,t),t))}size(){return this.cache.size}}class a{static LOG_LEVELS={DEBUG:0,INFO:1,WARN:2,ERROR:3,NONE:4};constructor(e=a.LOG_LEVELS.WARN){this.level=e}log(t,e){var s;!window.console||t<this.level||(s=Object.keys(a.LOG_LEVELS).find(e=>a.LOG_LEVELS[e]===t),console.log(`fTable ${s}: `+e))}debug(e){this.log(a.LOG_LEVELS.DEBUG,e)}info(e){this.log(a.LOG_LEVELS.INFO,e)}warn(e){this.log(a.LOG_LEVELS.WARN,e)}error(e){this.log(a.LOG_LEVELS.ERROR,e)}}class u{static create(e,t={}){let s=document.createElement(e);return t.className&&(s.className=t.className),t.style&&(s.style.cssText=t.style),t.attributes&&Object.entries(t.attributes).forEach(([e,t])=>{null!==t&&s.setAttribute(e,t)}),t.text&&(s.textContent=t.text),t.html&&(s.innerHTML=t.html),t.parent&&t.parent.appendChild(s),s}static find(e,t=document){return t.querySelector(e)}static findAll(e,t=document){return Array.from(t.querySelectorAll(e))}static addClass(e,t){e.classList.add(...t.split(" "))}static removeClass(e,t){e.classList.remove(...t.split(" "))}static toggleClass(e,t){e.classList.toggle(t)}static show(e){e.style.display=""}static hide(e){e.style.display="none"}static escapeHtml(e){if(!e)return e;let t={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#039;"};return e.replace(/[&<>"']/g,e=>t[e])}}class l{static async request(e,t={}){var s={method:"GET",headers:{}},a={...s,...t};t.headers&&(a.headers={...s.headers,...t.headers});try{var i=await fetch(e,a);if(401===i.status)throw new Error("Unauthorized");if(!i.ok)throw new Error("HTTP error! status: "+i.status);var o=i.headers.get("content-type");if(o&&o.includes("application/json"))return await i.json();var r=await i.text();try{return JSON.parse(r)}catch{return{Result:"OK",Message:r}}}catch(e){throw e}}static async get(e,t={}){let a=new URL(e,window.location.href);return Object.entries(t).forEach(([e,s])=>{if(null!=s)if(Array.isArray(s)){let t=e.replace(/\[\]$/,"")+"[]";s.forEach(e=>{null!=e&&a.searchParams.append(t,e)})}else a.searchParams.append(e,s)}),this.request(a.toString(),{method:"GET",headers:{"Content-Type":"application/x-www-form-urlencoded"}})}static async post(e,t={}){e=new URL(e,window.location.href);let a=new FormData;return Object.entries(t).forEach(([e,s])=>{if(null!=s)if(Array.isArray(s)){let t=e.replace(/\[\]$/,"")+"[]";s.forEach(e=>{null!=e&&a.append(t,e)})}else a.append(e,s)}),this.request(e.toString(),{method:"POST",body:a})}}class i{constructor(e,t="localStorage"){this.prefix=e,this.method=t}set(e,t){var s,e=""+this.prefix+e;"localStorage"===this.method?localStorage.setItem(e,t):((s=new Date).setDate(s.getDate()+30),document.cookie=e+`=${t}; expires=${s.toUTCString()}; path=/`)}get(e){e=""+this.prefix+e;if("localStorage"===this.method)return localStorage.getItem(e);var t,s=e+"=";for(t of decodeURIComponent(document.cookie).split(";")){for(;" "===t.charAt(0);)t=t.substring(1);if(0===t.indexOf(s))return t.substring(s.length,t.length)}return null}remove(e){e=""+this.prefix+e;"localStorage"===this.method?localStorage.removeItem(e):document.cookie=e+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"}generatePrefix(e,t){e=e?e+"#":"";return"ftable#"+(t=>{let s=0;if(0!==t.length)for(let e=0;e<t.length;e++){var a=t.charCodeAt(e);s=(s<<5)-s+a,s&=s}return s})(e+=t.join("$")+"#c"+t.length)}}class o{constructor(e={}){this.options={title:"Modal",content:"",buttons:[],className:"ftable-modal",parent:document.body,...e},this.overlay=null,this.modal=null,this.isOpen=!1}create(){this.overlay=u.create("div",{className:"ftable-modal-overlay",parent:this.options.parent}),this.modal=u.create("div",{className:"ftable-modal "+this.options.className,parent:this.overlay});u.create("h2",{className:"ftable-modal-header",text:this.options.title,parent:this.modal});u.create("span",{className:"ftable-modal-close",html:"&times;",parent:this.modal}).addEventListener("click",()=>this.close());var e=u.create("div",{className:"ftable-modal-body",parent:this.modal});if("string"==typeof this.options.content?e.innerHTML=this.options.content:e.appendChild(this.options.content),0<this.options.buttons.length){let s=u.create("div",{className:"ftable-modal-footer",parent:this.modal});this.options.buttons.forEach(e=>{var t=u.create("button",{className:"ftable-dialog-button "+(e.className||""),html:`<span>${e.text}</span>`,parent:s});e.onClick&&(t._originalOnClick=e.onClick,t.addEventListener("click",this._createWrappedClickHandler(t)))})}return this.options.closeOnOverlayClick&&this.overlay.addEventListener("click",e=>{e.target===this.overlay&&this.close()}),this.hide(),this}show(){return this.modal||this.create(),this.overlay.style.display="flex",this.isOpen=!0,this.modal.querySelectorAll(".ftable-dialog-button").forEach(e=>{e.disabled=!1}),this}hide(){return this.overlay&&(this.overlay.style.display="none"),this.isOpen=!1,this}close(){return this.hide(),this.options.onClose&&this.options.onClose(),this}destroy(){return this.overlay&&this.overlay.remove(),this.isOpen=!1,this}setContent(e){this.options.content=e;var t=this.modal.querySelector(".ftable-modal-body");t&&(t.innerHTML="","string"==typeof e?t.innerHTML=e:t.appendChild(e))}_createWrappedClickHandler(a){return async e=>{a.disabled=!0;try{var t,s=a._originalOnClick;"function"==typeof s&&(t=s.call(a,e))instanceof Promise&&await t}catch(e){console.error("Modal button action failed:",e)}finally{a.disabled=!1}}}}class r{constructor(e){this.options=e,this.dependencies=new Map,this.optionsCache=new t,this.originalFieldOptions=new Map,this.resolvedFieldOptions=new Map,Object.keys(this.options.fields||{}).forEach(e=>{this.resolvedFieldOptions.set(e,{})}),Object.entries(this.options.fields).forEach(([e,t])=>{this.originalFieldOptions.set(e,t.options)})}async getFieldOptions(t,s="table",e={}){var a=this.options.fields[t],i=this.originalFieldOptions.get(t);if(!i)return null;var o=this.shouldForceRefreshForContext(a,s,e),r=this.generateOptionsCacheKey(s,e);if(!o&&!e.forceRefresh){var n=this.resolvedFieldOptions.get(t)[r];if(n)return n}try{var l={...a,options:i},c=await this.resolveOptions(l,{...e},s,o);return this.resolvedFieldOptions.get(t)[r]=c}catch(e){return console.error(`Failed to resolve options for ${t} (${s}):`,e),i}}clearResolvedOptions(e=null,s=null){e?this.resolvedFieldOptions.has(e)&&(s?this.resolvedFieldOptions.get(e)[s]=null:this.resolvedFieldOptions.set(e,{table:null,create:null,edit:null})):s?this.resolvedFieldOptions.forEach((e,t)=>{this.resolvedFieldOptions.get(t)[s]=null}):this.resolvedFieldOptions.forEach((e,t)=>{this.resolvedFieldOptions.set(t,{table:null,create:null,edit:null})})}shouldForceRefreshForContext(e,t,s){return!!e.noCache&&("boolean"==typeof e.noCache?e.noCache:"function"==typeof e.noCache?e.noCache({context:t,...s}):"object"==typeof e.noCache&&!0===e.noCache[t])}generateOptionsCacheKey(e,t){let s=[e];return t.dependedValues&&Object.keys(t.dependedValues).sort().forEach(e=>{s.push(e+"="+t.dependedValues[e])}),s.join("|")}shouldIncludeField(e,t){return"create"===t?!1!==e.create&&!(!0===e.key&&!0!==e.create):"edit"!==t||!1!==e.edit}createFieldContainer(e,t,s,a){var i=u.create("div",{className:"hidden"!=t.type?"ftable-input-field-container":"",attributes:{id:"ftable-input-field-container-div-"+e}}),t=("hidden"!=t.type&&u.create("div",{className:"ftable-input-label",text:t.inputTitle||t.title,parent:i}),this.createInput(e,t,s[e],a));return i.appendChild(t),i}async createForm(e="create",t={}){this.currentFormRecord=t;var s,a,i,o,r=u.create("form",{className:`ftable-dialog-form ftable-${e}-form`});this.buildDependencyMap();for([s,a]of Object.entries(this.options.fields))this.shouldIncludeField(a,e)&&(i={...a},a.dependsOn?i.options=a.options:(o=await this.getFieldOptions(s,e,{record:t,source:e}),i.options=o),o=this.createFieldContainer(s,i,t,e),r.appendChild(o));return this.setupDependencyListeners(r),r}shouldResolveOptions(e){return e&&("function"==typeof e||"string"==typeof e)&&!Array.isArray(e)&&!("object"==typeof e&&!Array.isArray(e)&&0<Object.keys(e).length)}buildDependencyMap(){this.dependencies.clear(),Object.entries(this.options.fields).forEach(([t,s])=>{if(s.dependsOn){let e;"string"==typeof s.dependsOn&&(e=s.dependsOn.split(",").map(e=>e.trim()).filter(e=>e)).forEach(e=>{this.dependencies.has(e)||this.dependencies.set(e,[]),this.dependencies.get(e).push(t)})}})}setupDependencyListeners(s){Array.from(this.dependencies.keys()).forEach(e=>{var t=s.querySelector(`[name="${e}"]`);t&&t.addEventListener("change",()=>{this.handleDependencyChange(s,e)})}),this.handleDependencyChange(s)}async resolveOptions(e,t={},s="",a=!1){if(!e.options)return[];if(Array.isArray(e.options)||"object"==typeof e.options)return e.options;let i;t={...t,source:s,clearCache:()=>{a=!0,this.updateFieldCacheSetting(e,s,!0)}};if("function"==typeof e.options)i=await e.options(t);else{if("string"!=typeof e.options)return[];i=e.options}t=i&&"object"==typeof i&&i.url;let o=t?i.url:i;if(a=t&&void 0!==i.noCache?i.noCache:a,"string"!=typeof o)return[];if(!a)return this.optionsCache.getOrCreate(o,{},async()=>{try{var e=this.options.forcePost?await l.post(o):await l.get(o);return e.Options||e.options||e||[]}catch(e){return console.error(`Failed to load options from ${o}:`,e),[]}});try{var r=this.options.forcePost?await l.post(o):await l.get(o);return r.Options||r.options||r||[]}catch(e){return console.error(`Failed to load options from ${o}:`,e),[]}}updateFieldCacheSetting(e,t,s){e.noCache?"boolean"==typeof e.noCache?e.noCache={table:e.noCache,create:e.noCache,edit:e.noCache,[t]:s}:"object"==typeof e.noCache&&(e.noCache[t]=s):e.noCache={[t]:s}}clearOptionsCache(e=null,t=null){this.optionsCache.clear(e,t)}getFormValues(e){var t={},s=e.elements;for(let e=0;e<s.length;e++){var a=s[e],i=a.name;if(i&&!a.disabled)switch(a.type){case"checkbox":t[i]=a.checked?a.value||"1":"0";break;case"radio":a.checked&&(t[i]=a.value);break;case"select-multiple":t[i]=Array.from(a.selectedOptions).map(e=>e.value);break;default:t[i]=a.value}}return t}async handleDependencyChange(e,s=""){var a,i,o=this.getFormValues(e),r=e.classList.contains("ftable-create-form")?"create":"edit",n=this.currentFormRecord||{},l={record:n,source:r,form:e,dependedValues:o};for([a,i]of Object.entries(this.options.fields))if(i.dependsOn){if(""!==s)if(!i.dependsOn.split(",").map(e=>e.trim()).filter(e=>e).includes(s))continue;let t=e.querySelector(`[name="${a}"]`);if(t&&this.shouldIncludeField(i,r))try{"SELECT"===t.tagName?t.innerHTML='<option value="">Loading...</option>':"INPUT"===t.tagName&&t.list&&(c=document.getElementById(t.list.id))&&(c.innerHTML="");var c,d=t.value||n[a]||"",h={...l,dependsOnField:i.dependsOn,dependsOnValue:o[i.dependsOn]},p=await this.getFieldOptions(a,r,h);"SELECT"===t.tagName?this.populateSelectOptions(t,p,d):"INPUT"===t.tagName&&t.list&&(this.populateDatalistOptions(t.list,p),d)&&(t.value=d),setTimeout(()=>{t.dispatchEvent(new Event("change",{bubbles:!0}))},0)}catch(e){console.error(`Error loading options for ${a}:`,e),"SELECT"===t.tagName&&(t.innerHTML='<option value="">Error</option>')}}}parseInputAttributes(e){if("string"!=typeof e)return e||{};for(var t={},s=/(\w+)(?:=("[^"]*"|'[^']*'|\S+))?/g;null!==(i=s.exec(e));){var a=i[1],i=i[2]?i[2].replace(/^["']|["']$/g,""):"";t[a]=""===i?"true":i}return t}createInput(e,t,s,a){var i=u.create("div",{className:`ftable-input ftable-${t.type||"text"}-input`});let o;switch(null==(s=null==s?null:s)&&t.defaultValue&&(s=t.defaultValue),!t.type&&t.options&&(t.type="select"),t.type){case"hidden":o=this.createHiddenInput(e,t,s);break;case"textarea":o=this.createTextarea(e,t,s);break;case"select":o=this.createSelect(e,t,s);break;case"checkbox":o=this.createCheckbox(e,t,s);break;case"radio":o=this.createRadioGroup(e,t,s);break;case"datalist":o=this.createDatalistInput(e,t,s);break;case"file":o=this.createFileInput(e,t,s);break;case"date":case"datetime":case"datetime-local":o=this.createDateInput(e,t,s);break;default:o=this.createTypedInput(e,t,s)}return"function"==typeof t.input?(a={field:t,record:this.currentFormRecord,inputField:o,formType:a},"string"==typeof(a=t.input(a))?i.innerHTML=a:a instanceof Node?i.appendChild(a):i.appendChild(o)):i.appendChild(o),t.explain&&u.create("div",{className:"ftable-field-explain",html:`<small>${t.explain}</small>`,parent:i}),i}createDateInput(s,a,i){if("undefined"==typeof FDatepicker)return createTypedInput(s,a,i);{let e=a.dateFormat||this.options.defaultDateFormat;var o,r=document.createElement("div"),n=u.create("input",{attributes:{id:"real-"+s,type:"hidden",value:i,name:s}}),i={id:"Edit-"+s,type:"text","data-date":i,placeholder:a.placeholder||null,readOnly:!0};a.inputAttributes&&(o=this.parseInputAttributes(a.inputAttributes),Object.assign(i,o));let t=u.create("input",{className:a.inputClass||"datepicker-input",attributes:i});switch(r.appendChild(n),r.appendChild(t),a.type){case"date":setTimeout(()=>{new FDatepicker(t,{format:e,altField:"real-"+s,altFormat:"Y-m-d"})},0);break;case"datetime":case"datetime-local":setTimeout(()=>{new FDatepicker(t,{format:e,timepicker:!0,altField:"real-"+s,altFormat:"Y-m-d H:i:00"})},0)}return r}}createTypedInput(e,t,s){var a,s={type:t.type||"text",id:"Edit-"+e,placeholder:t.placeholder||null,value:s};let i=e,o=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(s,a),void 0!==a.multiple&&!1!==a.multiple)&&(i=e+"[]"),s.name=i,u.create("input",{className:t.inputClass||null,attributes:s}));return o.addEventListener("keypress",e=>{if(13===(e.keyCode||e.which))return e.preventDefault(),o.dispatchEvent(new Event("change",{bubbles:!0})),!1}),o}createDatalistInput(e,t,s){var s={type:"text",name:e,id:"Edit-"+e,placeholder:t.placeholder||null,value:s,list:e+"-datalist"},a=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(s,a)),u.create("input",{className:t.inputClass||null,attributes:s})),s=u.create("datalist",{attributes:{id:e+"-datalist"}});return t.options&&this.populateDatalistOptions(s,t.options),document.body.appendChild(s),a.datalistElement=s,a}populateDatalistOptions(s,e){s.innerHTML="",Array.isArray(e)?e.forEach(e=>{u.create("option",{attributes:{value:e.Value||e.value||e},text:e.DisplayText||e.text||e,parent:s})}):"object"==typeof e&&Object.entries(e).forEach(([e,t])=>{u.create("option",{attributes:{value:e},text:t,parent:s})})}createHiddenInput(e,t,s){e={type:"hidden",name:e,id:"Edit-"+e,value:s};return t.inputAttributes&&(s=this.parseInputAttributes(t.inputAttributes),Object.assign(e,s)),u.create("input",{attributes:e})}createTextarea(e,t,s){var e={name:e,id:"Edit-"+e,placeholder:t.placeholder||null},a=(t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(e,a)),u.create("textarea",{className:t.inputClass||null,attributes:e,value:s}));return a}createSelect(e,t,s){var a={name:e,id:"Edit-"+e};let i=e;t.inputAttributes&&(o=this.parseInputAttributes(t.inputAttributes),Object.assign(a,o),void 0!==o.multiple&&!1!==o.multiple)&&(i=e+"[]"),a.name=i;var o=u.create("select",{className:t.inputClass||null,attributes:a});return t.options&&this.populateSelectOptions(o,t.options,s),o}createRadioGroup(o,r,n){let l=u.create("div",{className:"ftable-radio-group"});return r.options&&(Array.isArray(r.options)?r.options:"object"==typeof r.options?Object.entries(r.options).map(([e,t])=>({Value:e,DisplayText:t})):[]).forEach((e,t)=>{var s=u.create("div",{className:"ftable-radio-wrapper",parent:l}),a=o+"_"+t,i={type:"radio",name:o,id:a,value:e.Value||e.value||e},t=(r.required&&0===t&&(i.required="required"),r.disabled&&(i.disabled="disabled"),r.inputAttributes&&(t=this.parseInputAttributes(r.inputAttributes),Object.assign(i,t)),u.create("input",{attributes:i,className:r.inputClass||null,parent:s}));i.value===n&&(t.checked=!0),u.create("label",{attributes:{for:a},text:e.DisplayText||e.text||e,parent:s})}),l}createCheckbox(e,t,s){var a=u.create("div",{className:"ftable-yesno-check-wrapper"}),s=[1,"1",!0,"true"].includes(s);let i=this.options.messages.no,o=this.options.messages.yes;return t.values&&"object"==typeof t.values&&(void 0!==t.values[0]&&(i=t.values[0]),void 0!==t.values[1])&&(o=t.values[1]),u.create("input",{className:["ftable-yesno-check-input",t.inputClass||""].filter(Boolean).join(" "),attributes:{type:"checkbox",name:e,id:"Edit-"+e,value:"1"},parent:a}).checked=s,t.label?u.create("label",{className:"ftable-yesno-check-fixedlabel",attributes:{for:"Edit-"+e},text:t.label,parent:a}):u.create("label",{className:"ftable-yesno-check-text",attributes:{for:"Edit-"+e,"data-yes":o,"data-no":i},parent:a}),a}populateSelectOptions(a,e,i){a.innerHTML="",Array.isArray(e)?e.forEach(e=>{var t=void 0!==e.Value?e.Value:void 0!==e.value?e.value:e;let s=u.create("option",{attributes:{value:t},text:e.DisplayText||e.text||e,parent:a});e.Data&&"object"==typeof e.Data&&Object.entries(e.Data).forEach(([e,t])=>{s.setAttribute("data-"+e,t)}),s.value==i&&(s.selected=!0)}):"object"==typeof e&&Object.entries(e).forEach(([e,t])=>{t=u.create("option",{attributes:{value:e},text:t,parent:a});e==i&&(t.selected=!0)})}createFileInput(e,t,s){var a,i={type:"file",id:"Edit-"+e};let o=e;return t.inputAttributes&&(a=this.parseInputAttributes(t.inputAttributes),Object.assign(i,a),void 0!==a.multiple&&!1!==a.multiple)&&(o=e+"[]"),i.name=o,u.create("input",{className:t.inputClass||null,attributes:i})}}class n extends class{constructor(){this.events={}}on(e,t){return this.events[e]||(this.events[e]=[]),this.events[e].push(t),this}once(t,s){let a=(...e)=>{this.off(t,a),s.apply(this,e)};return a.fn=s,this.on(t,a),this}emit(e,t={}){return this.events[e]&&this.events[e].forEach(e=>e(t)),this}off(e,t){return this.events[e]&&(this.events[e]=this.events[e].filter(e=>e!==t)),this}}{constructor(e,t={}){if(super(),this.element="string"==typeof e?document.querySelector(e):e,this.element){if(this.element.ftableInstance)return this.element.ftableInstance;this.options=this.mergeOptions(t),this.verifyOptions(),this.logger=new a(this.options.logLevel),this.userPrefs=new i("",this.options.saveUserPreferencesMethod),this.formBuilder=new r(this.options,this),this.state={records:[],totalRecordCount:0,currentPage:1,isLoading:!1,selectedRecords:new Set,sorting:[],searchQueries:{}},this.elements={},this.modals={},this.searchTimeout=null,this.lastSortEvent=null,this._recalculatedOnce=!1,this.shiftKeyDown=!1,this.lastSelectedRow=null,(this.element.ftableInstance=this).init()}}mergeOptions(e){var t={tableId:void 0,logLevel:a.LOG_LEVELS.WARN,actions:{},fields:{},forcePost:!0,closeOnOverlayClick:!0,animationsEnabled:!0,loadingAnimationDelay:1e3,defaultDateLocale:"",defaultDateFormat:"Y-m-d",saveUserPreferences:!0,saveUserPreferencesMethod:"localStorage",defaultSorting:"",tableReset:!1,paging:!1,pageList:"normal",pageSize:10,pageSizes:[10,25,50,100,250,500],gotoPageArea:"combobox",sorting:!1,multiSorting:!1,multiSortingCtrlKey:!0,selecting:!1,multiselect:!1,openChildAsAccordion:!1,toolbarsearch:!1,toolbarreset:!0,searchDebounceMs:300,listCache:3e4,messages:{...s}};return this.deepMerge(t,e)}deepMerge(e,t){var s,a={...e};for(s in t)t[s]&&"object"==typeof t[s]&&!Array.isArray(t[s])?a[s]=this.deepMerge(a[s]||{},t[s]):a[s]=t[s];return a}verifyOptions(){this.options.pageSize&&!this.options.pageSizes.includes(this.options.pageSize)&&(this.options.pageSize=this.options.pageSizes[0])}static setMessages(e){Object.assign(s,e)}init(){this.processFieldDefinitions(),this.createMainStructure(),this.setupFTableUserPreferences(),this.createTable(),this.createModals(),this.options.paging&&this.createPagingUI(),this.resolveAsyncFieldOptions().then(()=>{setTimeout(()=>{this.refreshDisplayValues()},0)}).catch(console.error),this.bindEvents(),this.updateSortingHeaders(),this.renderSortingInfo(),this.initColumnWidths()}initColumnWidths(){var e=this.columnList.filter(e=>{e=this.options.fields[e];return"hidden"!==e.visibility&&"separator"!==e.visibility});let t=e.length;e.forEach(e=>{e=this.options.fields[e];e.width=e.width||100/t+"%"})}normalizeColumnWidths(){var e=this.elements.mainContainer,t=this.columnList.map(e=>({th:this.elements.table.querySelector(`[data-field-name="${e}"]`),field:this.options.fields[e]})).filter(e=>e.th&&"hidden"!==e.field.visibility&&"separator"!==e.field.visibility);if(0!==t.length){let s=e.offsetWidth,a=0;t.forEach(e=>{var t=e.th.offsetWidth/s*100;e.field.width=t+"%",e.th.style.width=e.field.width,a+=t})}}parseDefaultSorting(e){let o=[];return e&&"string"==typeof e&&e.split(",").forEach(s=>{s=s.trim();if(s){var a=s.toUpperCase().indexOf(" DESC"),i=s.toUpperCase().indexOf(" ASC");let e="ASC",t=s;e=0<a?(t=s.slice(0,a).trim(),"DESC"):(t=(0<i?s.slice(0,i):s).trim(),"ASC");a=this.options.fields[t];a&&!1!==a.sorting&&o.push({fieldName:t,direction:e})}}),o}addEssentialCSS(){var e;document.querySelector("#ftable-essential-css")||((e=document.createElement("style")).id="ftable-essential-css",e.textContent=`
2
2
  .ftable-row-animation {
3
3
  transition: background-color 0.3s ease;
4
4
  }
package/ftable.umd.js CHANGED
@@ -200,7 +200,7 @@ class FTableDOMHelper {
200
200
 
201
201
  if (options.attributes) {
202
202
  Object.entries(options.attributes).forEach(([key, value]) => {
203
- if (value !== '')
203
+ if (value !== null)
204
204
  element.setAttribute(key, value);
205
205
  });
206
206
  }
@@ -1065,7 +1065,10 @@ class FTableFormBuilder {
1065
1065
 
1066
1066
  let input;
1067
1067
 
1068
- if (value == null || value == undefined ) {
1068
+ if (value == undefined) {
1069
+ value = null;
1070
+ }
1071
+ if (value == null && field.defaultValue ) {
1069
1072
  value = field.defaultValue;
1070
1073
  }
1071
1074
  // Auto-detect select type if options are provided
@@ -1156,7 +1159,7 @@ class FTableFormBuilder {
1156
1159
  attributes: {
1157
1160
  id: 'real-' + fieldName,
1158
1161
  type: 'hidden',
1159
- value: value || '',
1162
+ value: value,
1160
1163
  name: fieldName
1161
1164
  }
1162
1165
  });
@@ -1166,7 +1169,7 @@ class FTableFormBuilder {
1166
1169
  id: `Edit-${fieldName}`,
1167
1170
  type: 'text',
1168
1171
  'data-date': value,
1169
- placeholder: field.placeholder || '',
1172
+ placeholder: field.placeholder || null,
1170
1173
  readOnly: true
1171
1174
  };
1172
1175
  // Set any additional attributes
@@ -1220,8 +1223,8 @@ class FTableFormBuilder {
1220
1223
  const attributes = {
1221
1224
  type: inputType,
1222
1225
  id: `Edit-${fieldName}`,
1223
- placeholder: field.placeholder || '',
1224
- value: value || ''
1226
+ placeholder: field.placeholder || null,
1227
+ value: value
1225
1228
  };
1226
1229
 
1227
1230
  // extra check for name and multiple
@@ -1242,7 +1245,7 @@ class FTableFormBuilder {
1242
1245
  attributes.name = name;
1243
1246
 
1244
1247
  const input = FTableDOMHelper.create('input', {
1245
- className: field.inputClass || '',
1248
+ className: field.inputClass || null,
1246
1249
  attributes: attributes
1247
1250
  });
1248
1251
 
@@ -1264,8 +1267,8 @@ class FTableFormBuilder {
1264
1267
  type: 'text',
1265
1268
  name: fieldName,
1266
1269
  id: `Edit-${fieldName}`,
1267
- placeholder: field.placeholder || '',
1268
- value: value || '',
1270
+ placeholder: field.placeholder || null,
1271
+ value: value,
1269
1272
  list: `${fieldName}-datalist`
1270
1273
  };
1271
1274
 
@@ -1276,7 +1279,7 @@ class FTableFormBuilder {
1276
1279
  }
1277
1280
 
1278
1281
  const input = FTableDOMHelper.create('input', {
1279
- className: field.inputClass || '',
1282
+ className: field.inputClass || null,
1280
1283
  attributes: attributes
1281
1284
  });
1282
1285
 
@@ -1330,7 +1333,7 @@ class FTableFormBuilder {
1330
1333
  type: 'hidden',
1331
1334
  name: fieldName,
1332
1335
  id: `Edit-${fieldName}`,
1333
- value: value || ''
1336
+ value: value
1334
1337
  };
1335
1338
 
1336
1339
  // Apply inputAttributes
@@ -1346,7 +1349,7 @@ class FTableFormBuilder {
1346
1349
  const attributes = {
1347
1350
  name: fieldName,
1348
1351
  id: `Edit-${fieldName}`,
1349
- placeholder: field.placeholder || ''
1352
+ placeholder: field.placeholder || null
1350
1353
  };
1351
1354
 
1352
1355
  // Apply inputAttributes
@@ -1356,9 +1359,9 @@ class FTableFormBuilder {
1356
1359
  }
1357
1360
 
1358
1361
  const textarea = FTableDOMHelper.create('textarea', {
1359
- className: field.inputClass || '',
1362
+ className: field.inputClass || null,
1360
1363
  attributes: attributes,
1361
- value: value || ''
1364
+ value: value
1362
1365
  });
1363
1366
  return textarea;
1364
1367
  }
@@ -1387,7 +1390,7 @@ class FTableFormBuilder {
1387
1390
  attributes.name = name;
1388
1391
 
1389
1392
  const select = FTableDOMHelper.create('select', {
1390
- className: field.inputClass || '',
1393
+ className: field.inputClass || null,
1391
1394
  attributes: attributes
1392
1395
  });
1393
1396
 
@@ -1432,7 +1435,7 @@ class FTableFormBuilder {
1432
1435
 
1433
1436
  const radio = FTableDOMHelper.create('input', {
1434
1437
  attributes: radioAttributes,
1435
- className: field.inputClass || '',
1438
+ className: field.inputClass || null,
1436
1439
  parent: radioWrapper
1437
1440
  });
1438
1441
 
@@ -1566,8 +1569,8 @@ class FTableFormBuilder {
1566
1569
  }
1567
1570
  attributes.name = name;
1568
1571
 
1569
- const input = FTableDOMHelper.create('input', {
1570
- className: field.inputClass || '',
1572
+ return FTableDOMHelper.create('input', {
1573
+ className: field.inputClass || null,
1571
1574
  attributes: attributes
1572
1575
  });
1573
1576
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liedekef/ftable",
3
- "version": "1.1.42",
3
+ "version": "1.1.44",
4
4
  "description": "Modern, lightweight, jQuery-free CRUD table for dynamic AJAX-powered tables.",
5
5
  "main": "ftable.js",
6
6
  "module": "ftable.esm.js",