web-mojo 2.2.47 → 2.2.49
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/dist/admin.cjs.js +1 -1
- package/dist/admin.cjs.js.map +1 -1
- package/dist/admin.es.js +18 -13
- package/dist/admin.es.js.map +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +4 -4
- package/dist/chunks/{ChatView-BT_nD9K9.js → ChatView-BUfkeVnX.js} +14 -7
- package/dist/chunks/ChatView-BUfkeVnX.js.map +1 -0
- package/dist/chunks/ChatView-Tj1Kb9Hf.js +2 -0
- package/dist/chunks/ChatView-Tj1Kb9Hf.js.map +1 -0
- package/dist/chunks/{Collection-C_Ssejpi.js → Collection-D3K_bEJN.js} +2 -2
- package/dist/chunks/{Collection-C_Ssejpi.js.map → Collection-D3K_bEJN.js.map} +1 -1
- package/dist/chunks/{Collection-C5uDBFur.js → Collection-DpUI5_9f.js} +2 -2
- package/dist/chunks/{Collection-C5uDBFur.js.map → Collection-DpUI5_9f.js.map} +1 -1
- package/dist/chunks/{ContextMenu-leHTa126.js → ContextMenu-BSlbXHBy.js} +2 -2
- package/dist/chunks/{ContextMenu-leHTa126.js.map → ContextMenu-BSlbXHBy.js.map} +1 -1
- package/dist/chunks/{ContextMenu-4yi1YZwj.js → ContextMenu-sdxDECHa.js} +3 -3
- package/dist/chunks/{ContextMenu-4yi1YZwj.js.map → ContextMenu-sdxDECHa.js.map} +1 -1
- package/dist/chunks/{DataView-34YwqbHX.js → DataView-CACdHJV9.js} +2 -2
- package/dist/chunks/{DataView-34YwqbHX.js.map → DataView-CACdHJV9.js.map} +1 -1
- package/dist/chunks/{DataView-DNhn9WNt.js → DataView-DUOJRtqo.js} +2 -2
- package/dist/chunks/{DataView-DNhn9WNt.js.map → DataView-DUOJRtqo.js.map} +1 -1
- package/dist/chunks/{Dialog-G4K6rcmt.js → Dialog-BVDLaVFL.js} +5 -5
- package/dist/chunks/{Dialog-G4K6rcmt.js.map → Dialog-BVDLaVFL.js.map} +1 -1
- package/dist/chunks/{Dialog-DISGn8Ys.js → Dialog-BmkbDDol.js} +2 -2
- package/dist/chunks/{Dialog-DISGn8Ys.js.map → Dialog-BmkbDDol.js.map} +1 -1
- package/dist/chunks/{FormView-DezJ-BG_.js → FormView-CqiU2UkE.js} +3 -3
- package/dist/chunks/{FormView-DezJ-BG_.js.map → FormView-CqiU2UkE.js.map} +1 -1
- package/dist/chunks/{FormView-DTIezo2P.js → FormView-Cuqamyp7.js} +3 -3
- package/dist/chunks/{FormView-DTIezo2P.js.map → FormView-Cuqamyp7.js.map} +1 -1
- package/dist/chunks/{ListView-CCa2A8AN.js → ListView-B7_jgGeh.js} +2 -2
- package/dist/chunks/{ListView-CCa2A8AN.js.map → ListView-B7_jgGeh.js.map} +1 -1
- package/dist/chunks/{ListView-fAR_2dr2.js → ListView-ChN3NCAY.js} +3 -3
- package/dist/chunks/{ListView-fAR_2dr2.js.map → ListView-ChN3NCAY.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-B-f2p9Sa.js → MetricsCountryMapView-BDdS4s7L.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-B-f2p9Sa.js.map → MetricsCountryMapView-BDdS4s7L.js.map} +1 -1
- package/dist/chunks/{MetricsCountryMapView-CUOaeRfl.js → MetricsCountryMapView-Pu1VZIJT.js} +2 -2
- package/dist/chunks/{MetricsCountryMapView-CUOaeRfl.js.map → MetricsCountryMapView-Pu1VZIJT.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-DLg1F5tP.js → MetricsMiniChartWidget-BVS-pJks.js} +4 -4
- package/dist/chunks/{MetricsMiniChartWidget-DLg1F5tP.js.map → MetricsMiniChartWidget-BVS-pJks.js.map} +1 -1
- package/dist/chunks/{MetricsMiniChartWidget-B8KsG_h3.js → MetricsMiniChartWidget-ZqmLtt06.js} +2 -2
- package/dist/chunks/{MetricsMiniChartWidget-B8KsG_h3.js.map → MetricsMiniChartWidget-ZqmLtt06.js.map} +1 -1
- package/dist/chunks/{PDFViewer-cMv7WDvp.js → PDFViewer-Byo3kf7Z.js} +3 -3
- package/dist/chunks/{PDFViewer-cMv7WDvp.js.map → PDFViewer-Byo3kf7Z.js.map} +1 -1
- package/dist/chunks/{PDFViewer-D-NSWRwM.js → PDFViewer-CCW5ZvRb.js} +2 -2
- package/dist/chunks/{PDFViewer-D-NSWRwM.js.map → PDFViewer-CCW5ZvRb.js.map} +1 -1
- package/dist/chunks/Rest-BSz7rF0G.js +2 -0
- package/dist/chunks/Rest-BSz7rF0G.js.map +1 -0
- package/dist/chunks/{Rest-CtdC4cD9.js → Rest-DSzjaYYe.js} +77 -15
- package/dist/chunks/Rest-DSzjaYYe.js.map +1 -0
- package/dist/chunks/{TokenManager-D_WVmyKY.js → TokenManager-C07o8g1O.js} +2 -2
- package/dist/chunks/{TokenManager-D_WVmyKY.js.map → TokenManager-C07o8g1O.js.map} +1 -1
- package/dist/chunks/{TokenManager-DmqJ4qmb.js → TokenManager-DNwuLDw6.js} +5 -5
- package/dist/chunks/{TokenManager-DmqJ4qmb.js.map → TokenManager-DNwuLDw6.js.map} +1 -1
- package/dist/chunks/{WebApp-158Y9QSF.js → WebApp-1HetrsR0.js} +2 -2
- package/dist/chunks/{WebApp-158Y9QSF.js.map → WebApp-1HetrsR0.js.map} +1 -1
- package/dist/chunks/{WebApp-CiBl53Hl.js → WebApp-Do2IaAGx.js} +12 -12
- package/dist/chunks/{WebApp-CiBl53Hl.js.map → WebApp-Do2IaAGx.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-BGnzEIjP.js → WebSocketClient-BlBSBvYP.js} +2 -2
- package/dist/chunks/{WebSocketClient-BGnzEIjP.js.map → WebSocketClient-BlBSBvYP.js.map} +1 -1
- package/dist/chunks/{WebSocketClient-iJiqRgNy.js → WebSocketClient-BvwLDqeR.js} +2 -2
- package/dist/chunks/{WebSocketClient-iJiqRgNy.js.map → WebSocketClient-BvwLDqeR.js.map} +1 -1
- package/dist/chunks/{version-7OD8J84F.js → version-DXeLxR_3.js} +4 -4
- package/dist/chunks/{version-7OD8J84F.js.map → version-DXeLxR_3.js.map} +1 -1
- package/dist/chunks/{version-Dra0sZ9N.js → version-e7n3DAiW.js} +2 -2
- package/dist/chunks/{version-Dra0sZ9N.js.map → version-e7n3DAiW.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +6 -6
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +70 -17
- package/dist/index.es.js.map +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +5 -5
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +5 -5
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +4 -4
- package/dist/web-mojo.lite.css +154 -0
- package/dist/web-mojo.lite.iife.js +21760 -0
- package/dist/web-mojo.lite.iife.js.map +1 -0
- package/dist/web-mojo.lite.iife.min.js +1765 -0
- package/dist/web-mojo.lite.iife.min.js.map +1 -0
- package/package.json +4 -3
- package/dist/chunks/ChatView-BT_nD9K9.js.map +0 -1
- package/dist/chunks/ChatView-G_I8KNR_.js +0 -2
- package/dist/chunks/ChatView-G_I8KNR_.js.map +0 -1
- package/dist/chunks/Rest-Bd1ABsfh.js +0 -2
- package/dist/chunks/Rest-Bd1ABsfh.js.map +0 -1
- package/dist/chunks/Rest-CtdC4cD9.js.map +0 -1
|
@@ -0,0 +1,1765 @@
|
|
|
1
|
+
var MOJO=function(F){"use strict";class te{constructor(e={}){this.defaultRoute=e.defaultRoute||"home",this.routes=[],this.currentRoute=null,this.eventEmitter=e.eventEmitter||null,this.boundHandlePopState=this.handlePopState.bind(this)}start(){window.addEventListener("popstate",this.boundHandlePopState),this.handleCurrentLocation()}stop(){window.removeEventListener("popstate",this.boundHandlePopState)}addRoute(e,t){this.routes.push({pattern:this.normalizePattern(e),regex:this.patternToRegex(e),pageName:t,paramNames:this.extractParamNames(e)})}async navigate(e,t={}){const{replace:s=!1,state:i=null,trigger:a=!0}=t,{pageName:r,queryParams:n}=this.parseInput(e);a&&await this.handleRouteChange(r,n)}back(){window.history.back()}forward(){window.history.forward()}getCurrentRoute(){return this.currentRoute}getCurrentPath(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();return this.buildPublicUrl(e,t)}handlePopState(e){this.allowPopState?this.handleCurrentLocation():console.warn("PopStateEvent is not allowed")}async handleCurrentLocation(){const{pageName:e,queryParams:t}=this.parseCurrentUrl();await this.handleRouteChange(e,t)}async handleRouteChange(e,t){const s="/"+e,i=this.matchRoute(s),a=this.buildPublicUrl(e,t);return i?(this.currentRoute=i,this.eventEmitter&&this.eventEmitter.emit("route:changed",{path:a,pageName:i.pageName,params:i.params,query:t,route:i}),i):(console.log("No route matched for page:",e),this.eventEmitter&&this.eventEmitter.emit("route:notfound",{path:a}),null)}matchRoute(e){for(const t of this.routes){const s=e.match(t.regex);if(s){const i={};return t.paramNames.forEach((a,r)=>{i[a]=s[r+1]}),{...t,params:i,path:e}}}return null}parseInput(e){let t=this.defaultRoute,s={};if(!e)return{pageName:t,queryParams:s};try{if(e.includes("?")){const[i,a]=e.split("?",2),r=new URLSearchParams(a);if(r.has("page")){t=r.get("page")||this.defaultRoute;for(const[n,l]of r)n!=="page"&&(s[n]=l)}else{i.startsWith("/")?t=i.substring(1)||this.defaultRoute:t=i||this.defaultRoute;for(const[n,l]of r)s[n]=l}}else e.startsWith("/")?t=e.substring(1)||this.defaultRoute:t=e}catch(i){console.warn("Failed to parse input:",e,i),t=this.defaultRoute,s={}}return{pageName:t,queryParams:s}}parseCurrentUrl(){const e=new URLSearchParams(window.location.search),t=e.get("page")||this.defaultRoute,s={};for(const[i,a]of e)i!=="page"&&(s[i]=a);return{pageName:t,queryParams:s}}buildPublicUrl(e,t={}){const s=new URLSearchParams;return s.set("page",e),Object.entries(t).forEach(([i,a])=>{a!=null&&a!==""&&s.set(i,String(a))}),"?"+s.toString()}updateBrowserUrl(e,t,s,i){const a=new URL(window.location.origin+window.location.pathname);a.searchParams.set("page",e),Object.entries(t).forEach(([n,l])=>{l!=null&&l!==""&&a.searchParams.set(n,String(l))});const r=a.toString();s?window.history.replaceState(i,"",r):window.history.pushState(i,"",r)}patternToRegex(e){let t=e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&").replace(/\/:([^/?]+)\?/g,"(?:/([^/]+))?").replace(/:([^/]+)/g,"([^/]+)");return new RegExp(`^${t}$`)}extractParamNames(e){return(e.match(/:([^/?]+)\??/g)||[]).map(s=>s.replace(/[:?]/g,""))}normalizePattern(e){return e.startsWith("/")?e:`/${e}`}updateUrl(e={},t={}){const{replace:s=!1}=t,{pageName:i}=this.parseCurrentUrl();this.updateBrowserUrl(i,e,s)}buildUrl(e,t={}){return this.buildPublicUrl(e,t)}doRoutesMatch(e,t){if(!e||!t)return!1;const{pageName:s}=this.parseInput(e),{pageName:i}=this.parseInput(t);return s===i}}class Ee{constructor(){this.listeners={},this.onceListeners={},this.maxListeners=100,this.debugMode=!1,this.eventStats={}}on(e,t){if(typeof t!="function")throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(s=>this.on(s,t)),this):(this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].length>=this.maxListeners&&console.warn(`Max listeners (${this.maxListeners}) exceeded for event: ${e}`),this.listeners[e].push(t),this)}once(e,t){if(typeof t!="function")throw new Error("Callback must be a function");return Array.isArray(e)?(e.forEach(s=>this.once(s,t)),this):(this.onceListeners[e]||(this.onceListeners[e]=[]),this.onceListeners[e].push(t),this)}off(e,t){if(Array.isArray(e))return e.forEach(s=>this.off(s,t)),this;if(!t)return delete this.listeners[e],delete this.onceListeners[e],this;if(this.listeners[e]){const s=this.listeners[e].indexOf(t);s!==-1&&(this.listeners[e].splice(s,1),this.listeners[e].length===0&&delete this.listeners[e])}if(this.onceListeners[e]){const s=this.onceListeners[e].indexOf(t);s!==-1&&(this.onceListeners[e].splice(s,1),this.onceListeners[e].length===0&&delete this.onceListeners[e])}return this}emit(e,t){this.updateEventStats(e),this.debugMode&&console.log(`[EventBus] Emitting: ${e}`,t);const s=[];return this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]),this.debugMode&&s.length>0&&console.log(`[EventBus] ${s.length} listener(s) for '${e}'`),s.forEach(i=>{try{i(t,e)&&(e.stopPropagation&&e.stopPropagation(),e.preventDefault&&e.preventDefault())}catch(a){console.error(`Error in event listener for '${e}':`,a),this.emitError(a,e,i)}}),this}async emitAsync(e,t){const s=[];this.listeners[e]&&s.push(...this.listeners[e]),this.listeners["*"]&&s.push(...this.listeners["*"]),this.onceListeners[e]&&(s.push(...this.onceListeners[e]),delete this.onceListeners[e]),this.onceListeners["*"]&&(s.push(...this.onceListeners["*"]),delete this.onceListeners["*"]);const i=s.map(a=>new Promise(r=>{try{const n=a(t,e);r(n)}catch(n){console.error(`Error in async event listener for '${e}':`,n),this.emitError(n,e,a),r()}}));return await Promise.all(i),this}removeAllListeners(){return this.listeners={},this.onceListeners={},this}listenerCount(e){const t=this.listeners[e]?this.listeners[e].length:0,s=this.onceListeners[e]?this.onceListeners[e].length:0;return t+s}eventNames(){const e=Object.keys(this.listeners),t=Object.keys(this.onceListeners);return[...new Set([...e,...t])]}setMaxListeners(e){if(typeof e!="number"||e<0)throw new Error("Max listeners must be a non-negative number");return this.maxListeners=e,this}namespace(e){const t=s=>`${e}:${s}`;return{on:(s,i)=>this.on(t(s),i),once:(s,i)=>this.once(t(s),i),off:(s,i)=>this.off(t(s),i),emit:(s,i)=>this.emit(t(s),i),emitAsync:(s,i)=>this.emitAsync(t(s),i)}}use(e){if(typeof e!="function")throw new Error("Middleware must be a function");const t=this.emit;return this.emit=(s,i)=>{try{const a=e(s,i);if(a===!1)return this;const r=a!==void 0?a:i;return t.call(this,s,r)}catch(a){return console.error("Error in event middleware:",a),t.call(this,s,i)}},this}emitError(e,t,s){t!=="error"&&setTimeout(()=>{this.emit("error",{error:e,originalEvent:t,callback:s.toString()})},0)}waitFor(e,t=null){return new Promise((s,i)=>{let a=null;const r=()=>{a&&clearTimeout(a)},n=l=>{r(),s(l)};this.once(e,n),t&&(a=setTimeout(()=>{this.off(e,n),i(new Error(`Timeout waiting for event: ${e}`))},t))})}debug(e=!0){return this.debugMode=e,console.log(e?"[EventBus] Debug mode enabled":"[EventBus] Debug mode disabled"),this}getStats(){const e=this.eventNames(),t={totalEvents:e.length,totalListeners:0,events:{},emissions:{...this.eventStats}};return e.forEach(s=>{const i=this.listenerCount(s);t.events[s]=i,t.totalListeners+=i}),t}updateEventStats(e){this.eventStats[e]||(this.eventStats[e]={count:0,firstEmission:Date.now(),lastEmission:null}),this.eventStats[e].count++,this.eventStats[e].lastEmission=Date.now()}getEventStats(e){const t=this.eventStats[e];return t?{...t,listenerCount:this.listenerCount(e),avgEmissionsPerMinute:this.calculateEmissionRate(t)}:null}calculateEmissionRate(e){if(!e.firstEmission||!e.lastEmission)return 0;const t=e.lastEmission-e.firstEmission;if(t===0)return 0;const s=t/(1e3*60);return Math.round(e.count/s*100)/100}resetStats(){return this.eventStats={},this}getTopEvents(e=10){return Object.entries(this.eventStats).map(([t,s])=>({event:t,count:s.count,rate:this.calculateEmissionRate(s),listeners:this.listenerCount(t)})).sort((t,s)=>s.count-t.count).slice(0,e)}debugInfo(){console.group("[EventBus] Debug Information"),console.log("Debug Mode:",this.debugMode),console.log("Max Listeners:",this.maxListeners);const e=this.getStats();return console.log("Total Events:",e.totalEvents),console.log("Total Listeners:",e.totalListeners),Object.keys(this.eventStats).length>0&&console.log("Top Events:",this.getTopEvents(5)),console.groupEnd(),this}}class ke{constructor(){this.config={baseURL:"",timeout:3e4,headers:{"Content-Type":"application/json",Accept:"application/json"},trackDevice:!0,duidHeader:"X-Mojo-UID",duidTransport:"header"},this.interceptors={request:[],response:[]},this.duid=null,this.config.trackDevice&&this._initializeDuid()}_initializeDuid(){const e="mojo_device_uid";try{let t=localStorage.getItem(e);t?this.duid=t:(this.duid=this._generateDuid(),localStorage.setItem(e,this.duid))}catch(t){console.error("Could not access localStorage to get/set DUID.",t),this.duid=this._generateDuid()}}_generateDuid(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}configure(e){e.baseUrl&&(e.baseURL=e.baseUrl);const t=this.config.trackDevice;this.config={...this.config,...e,headers:{...this.config.headers,...e.headers}},this.config.trackDevice&&!t&&this._initializeDuid()}addInterceptor(e,t){this.interceptors[e]&&this.interceptors[e].push(t)}buildUrl(e){if(e.startsWith("http://")||e.startsWith("https://"))return e;const t=this.config.baseURL.endsWith("/")?this.config.baseURL.slice(0,-1):this.config.baseURL,s=e.startsWith("/")?e:`/${e}`;return`${t}${s}`}categorizeError(e,t=0){if(e.name==="TypeError"&&e.message.includes("fetch"))return{reason:"not_reachable",message:"Service is not reachable - please check your connection"};if(e.name==="AbortError")return{reason:"cancelled",message:"Request was cancelled"};if(e.name==="TimeoutError"||e.message.includes("timeout"))return{reason:"timed_out",message:"Request timed out - please try again"};if(t>=400){if(t===400)return{reason:"bad_request",message:"Invalid request data"};if(t===401)return{reason:"unauthorized",message:"Authentication required"};if(t===403)return{reason:"forbidden",message:"Access denied"};if(t===404)return{reason:"not_found",message:"Resource not found"};if(t===409)return{reason:"conflict",message:"Resource conflict"};if(t===422)return{reason:"validation_error",message:"Validation failed"};if(t===429)return{reason:"rate_limited",message:"Too many requests - please wait"};if(t>=500)return{reason:"server_error",message:"Server error - please try again later"};if(t>=400)return{reason:"client_error",message:"Request error"}}return e.message.includes("CORS")?{reason:"cors_error",message:"Cross-origin request blocked"}:e.message.includes("DNS")||e.message.includes("ENOTFOUND")?{reason:"dns_error",message:"Unable to resolve server address"}:{reason:"unknown_error",message:`Network error: ${e.message}`}}buildQueryString(e={}){const t=new URLSearchParams;Object.entries(e).forEach(([i,a])=>{a!=null&&(Array.isArray(a)?a.forEach(r=>t.append(`${i}[]`,r)):t.append(i,a))});const s=t.toString();return s?`?${s}`:""}async processRequestInterceptors(e){let t={...e};for(const s of this.interceptors.request)try{t=await s(t)}catch(i){throw console.error("Request interceptor error:",i),i}return t}async processResponseInterceptors(e,t){let s={success:e.ok,status:e.status,statusText:e.statusText,headers:Object.fromEntries(e.headers.entries()),data:null,errors:null,message:null,reason:null};try{const i=e.headers.get("content-type");if(i&&i.includes("application/json")){const a=await e.json();if(s.data=a,!e.ok){const r=this.categorizeError(new Error("HTTP Error"),e.status);s.errors=a.errors||{},s.message=a.message||r.message,s.reason=r.reason}}else if(s.data=await e.text(),!e.ok){const a=this.categorizeError(new Error("HTTP Error"),e.status);s.message=a.message,s.reason=a.reason}}catch{s.errors={parse:"Failed to parse response"},s.message="Invalid response format"}for(const i of this.interceptors.response)try{s=await i(s,t)}catch(a){console.error("Response interceptor error:",a)}return s}async request(e,t,s=null,i={},a={}){let r={method:e.toUpperCase(),url:this.buildUrl(t)+this.buildQueryString(i),headers:{...this.config.headers,...a.headers},data:s,options:{timeout:this.config.timeout,...a}};if(r=await this.processRequestInterceptors(r),this.config.trackDevice&&this.duid)if(this.config.duidTransport==="header")r.headers[this.config.duidHeader]=this.duid;else if(r.method==="GET"){const o=new URL(r.url);o.searchParams.append("duid",this.duid),r.url=o.toString()}else r.data&&typeof r.data=="object"&&!(r.data instanceof FormData)&&(r.data.duid=this.duid);const n={method:r.method,headers:r.headers},l=[];r.options.timeout&&l.push(AbortSignal.timeout(r.options.timeout)),r.options.signal&&l.push(r.options.signal),l.length>1?n.signal=AbortSignal.any?AbortSignal.any(l):l[0]:l.length===1&&(n.signal=l[0]),r.data&&["POST","PUT","PATCH"].includes(r.method)&&(r.data instanceof FormData?(n.body=r.data,delete n.headers["Content-Type"]):typeof r.data=="object"?n.body=JSON.stringify(r.data):n.body=r.data);try{const o=await fetch(r.url,n);return await this.processResponseInterceptors(o,r)}catch(o){if(o.name==="AbortError")throw o;const c=this.categorizeError(o),d={success:!1,status:0,statusText:"Network Error",headers:{},data:null,errors:{network:o.message},message:c.message,reason:c.reason},h={ok:!1,status:0,statusText:"Network Error",headers:new Headers,json:async()=>({}),text:async()=>""};return await this.processResponseInterceptors(h,r),d}}async GET(e,t={},s={}){return this.request("GET",e,null,t,s)}async POST(e,t={},s={},i={}){return this.request("POST",e,t,s,i)}async PUT(e,t={},s={},i={}){return this.request("PUT",e,t,s,i)}async PATCH(e,t={},s={},i={}){return this.request("PATCH",e,t,s,i)}async DELETE(e,t={},s={}){return this.request("DELETE",e,null,t,s)}async download(e,t={},s={}){const a={method:"GET",url:this.buildUrl(e)+this.buildQueryString(t),headers:{...this.config.headers,Accept:"*/*",...s.headers},options:{...s}};delete a.headers["Content-Type"];try{const r=await fetch(a.url,{method:a.method,headers:a.headers,signal:a.options.signal});if(!r.ok)throw new Error(`Download failed: ${r.status} ${r.statusText}`);const n=r.headers.get("content-disposition");let l=s.filename||"download";if(n){const m=n.match(/filename="?(.+)"?/);m&&m.length>1&&(l=m[1])}const o=r.body.getReader(),c=new ReadableStream({start(m){function p(){return o.read().then(({done:g,value:w})=>{if(g){m.close();return}return m.enqueue(w),p()})}return p()}}),d=await new Response(c).blob(),h=window.URL.createObjectURL(d),f=document.createElement("a");return f.style.display="none",f.href=h,f.download=l,document.body.appendChild(f),f.click(),window.URL.revokeObjectURL(h),f.remove(),{success:!0,message:"Download initiated"}}catch(r){return console.error("Download error:",r),{success:!1,message:r.message}}}async downloadBlob(e,t={},s={}){const a={method:"GET",url:this.buildUrl(e)+this.buildQueryString(t),headers:{...this.config.headers,Accept:"*/*",...s.headers},options:{...s}};delete a.headers["Content-Type"];try{const r=await fetch(a.url,{method:a.method,headers:a.headers,signal:a.options.signal});if(!r.ok)throw new Error(`Download failed: ${r.status} ${r.statusText}`);const n=await r.blob(),l=r.headers.get("content-disposition");let o=s.filename||"download";if(l){const h=l.match(/filename="?(.+)"?/);h&&h.length>1&&(o=h[1])}const c=window.URL.createObjectURL(n),d=document.createElement("a");return d.style.display="none",d.href=c,d.download=o,document.body.appendChild(d),d.click(),window.URL.revokeObjectURL(c),d.remove(),{success:!0,message:"Download initiated"}}catch(r){return console.error("Download error:",r),{success:!1,message:r.message}}}async upload(e,t,s={}){return new Promise((i,a)=>{if(!(t instanceof File)){a(new Error("Only single File objects are supported for legacy backend compatibility"));return}const r=new XMLHttpRequest;s.onProgress&&typeof s.onProgress=="function"&&(r.upload.onprogress=s.onProgress),r.onload=function(){r.status>=200&&r.status<300?i({data:r.response,status:r.status,statusText:r.statusText,xhr:r}):a(new Error(`Upload failed: ${r.status} ${r.statusText}`))},r.onerror=function(){a(new Error("Upload failed: Network error"))},r.ontimeout=function(){a(new Error("Upload failed: Timeout"))},r.open("PUT",e),r.setRequestHeader("Content-Type",t.type),s.timeout&&(r.timeout=s.timeout),r.send(t)})}async uploadMultipart(e,t,s={},i={}){const a=new FormData;if(t instanceof FileList)Array.from(t).forEach((r,n)=>{a.append(`file_${n}`,r)});else if(t instanceof File)a.append("file",t);else if(t instanceof FormData)return this.POST(e,t,{},i);return Object.entries(s).forEach(([r,n])=>{a.append(r,n)}),this.POST(e,a,{},i)}setAuthToken(e,t="Bearer"){e?this.config.headers.Authorization=`${t} ${e}`:delete this.config.headers.Authorization}clearAuth(){delete this.config.headers.Authorization}isRetryableError(e){return["not_reachable","timed_out","server_error","dns_error"].includes(e.reason)}requiresAuth(e){return e.reason==="unauthorized"}isNetworkError(e){return["not_reachable","timed_out","cancelled","cors_error","dns_error"].includes(e.reason)}getUserMessage(e){return e.message?e.message:{not_reachable:"Unable to connect to the server. Please check your internet connection.",timed_out:"The request took too long. Please try again.",cancelled:"The request was cancelled.",unauthorized:"Please log in to continue.",forbidden:"You don't have permission to perform this action.",not_found:"The requested resource was not found.",validation_error:"Please check your input and try again.",rate_limited:"Too many requests. Please wait a moment before trying again.",server_error:"Server error. Please try again later.",cors_error:"Access blocked by security policy.",dns_error:"Unable to reach the server.",unknown_error:"An unexpected error occurred."}[e.reason]||"An error occurred. Please try again."}}const z=new ke;class G{constructor(e={}){this.config=e,this.initPluginRegistry(),this.name=e.name||"MOJO App",this.version=e.version||"1.0.0",this.debug=e.debug||!1,this.container=e.container||"#app",this.layoutType=e.layout||"portal",this.layoutConfig=e.layoutConfig||{},e.sidebar&&(this.layoutConfig.sidebarConfig=e.sidebar),e.topbar&&(this.layoutConfig.topbarConfig=e.topbar),this.layout=null,this.layoutConfig.containerId=this.container||this.containerId||"#app",this.pageContainer=e.pageContainer||"#page-container",this.basePath=e.basePath||"",this.routerMode=e.routerMode||e.router?.mode||"param",this.basePath=e.basePath||e.router?.base||"",this.defaultRoute=e.defaultRoute||"home",this.session=e.session||{},this.router=null,this.navigation=e.navigation||{},this.state={currentPage:null,previousPage:null,loading:!1},this.events=new Ee,this.rest=z,e.api&&this.rest.configure(e.api),this.router=new te({mode:this.routerMode==="param"?"params":this.routerMode,basePath:this.basePath,defaultRoute:this.defaultRoute,eventEmitter:this.events}),this.events.on("route:changed",async t=>{const{pageName:s,params:i,query:a}=t;await this.showPage(s,a,i,{fromRouter:!0})}),typeof window<"u"&&(window.MOJO=window.MOJO||{},window.MOJO.router=this.router),this.setupFocusTracking(),this.pageCache=new Map,this.pageClasses=new Map,this.componentClasses=new Map,this.modelClasses=new Map,this.currentPage=null,this.isStarted=!1,window.matchUUID?window[window.matchUUID]=this:window.MOJO?window.MOJO.app=this:window.__app__=this}async start(){if(this.isStarted){console.warn("WebApp already started");return}try{this.setupPageContainer(),this.validateDefaultRoute(),await this.setupRouter(),this.isStarted=!0,this.router.allowPopState=!1,this.events.emit("app:ready",{app:this})}catch(e){throw console.error(`Failed to start ${this.name}:`,e),this.showError("Failed to start application"),e}}async setupRouter(){if(!this.router){console.error("Router not initialized");return}this.events.on("route:notfound",async e=>{console.warn(`Route not found: ${e.path}`),this._show404(e.path)}),this.router.start(),console.log(`Router started in ${this.routerMode} mode`)}setupPageContainer(){const e=typeof this.container=="string"?document.querySelector(this.container):this.container;e&&!e.querySelector("#page-container")&&(e.innerHTML='<div id="page-container"></div>'),this.pageContainer="#page-container"}registerPage(e,t,s={}){if(typeof e!="string"||!e)return console.error("registerPage: pageName must be a non-empty string"),this;if(typeof t!="function")return console.error("registerPage: PageClass must be a constructor function"),this;if(s.containerId||(s.containerId=this.pageContainer),this.pageClasses.set(e,{PageClass:t,constructorOptions:s}),this.router){let i=s.route||`/${e}`;i.startsWith("/")||(i=`/${i}`),s.route=i,this.router.addRoute(i,e)}return this}getPage(e){return this.pageCache.get(e)}getPagePermissions(e){if(this.pageCache.has(e))return this.pageCache.get(e).permissions;const t=this.pageClasses.get(e);if(!t)return null;const{PageClass:s,constructorOptions:i}=t;return i?i.permissions:null}getOrCreatePage(e){if(this.pageCache.has(e))return this.pageCache.get(e);const t=this.pageClasses.get(e);if(!t)return console.error(`Page not registered: ${e}`),null;const{PageClass:s,constructorOptions:i}=t;try{const a={pageName:e,...i,app:this},r=new s(a);return i.route&&(r.route=i.route),this.pageCache.set(e,r),console.log(`Created page: ${e} with route: ${r.route}`),r}catch(a){return console.error(`Failed to create page ${e}:`,a),null}}async showPage(e,t={},s={},i={}){const{fromRouter:a=!1,replace:r=!1,force:n=!1}=i;try{let l,o;typeof e=="string"?(o=e,l=this.getOrCreatePage(e)):e&&typeof e=="object"&&(l=e,o=e.pageName);const c=this.currentPage;if(!l){this._show404(o,s,t,a);return}if(this.events.emit("page:showing",{page:l,pageName:l.pageName,params:s,query:t,fromRouter:a}),!l.canEnter()){this._showDeniedPage(l,s,t,a);return}c&&c!==l&&await this._exitOldPage(c),await l.onParams(s,t),c!==l&&await l.onEnter(),l.syncUrl(),this.events.emit("page:show",{page:l,pageName:l.pageName,params:s,query:t,fromRouter:a}),await l.render(),this.currentPage=l,console.log(`✅ Showing page: ${l.pageName}`,{query:t,params:s})}catch(l){console.error("Error in showPage:",l),this.showError(`Failed to load page: ${l.message}`),e!=="error"&&await this.showPage("error",{},{error:l,originalPage:e},{fromRouter:a})}}async _show404(e,t,s,i){const a=this.getOrCreatePage("404");a&&(a.setInfo&&a.setInfo(e),await this._exitOldPage(this.currentPage),await a.render(),this.currentPage=a,this.events.emit("page:404",{page:null,pageName:e,params:t,query:s,fromRouter:i}))}async _showDeniedPage(e,t,s,i){const a=this.getOrCreatePage("denied");a.setDeniedPage&&a.setDeniedPage(e),await this._exitOldPage(this.currentPage),await a.render(),this.currentPage=a,this.events.emit("page:denied",{page:e,pageName:e.pageName,params:t,query:s,fromRouter:i})}async _exitOldPage(e){if(e)try{await e.onExit(),await e.unmount(),this.events.emit("page:hide",{page:e})}catch(t){console.error(`Error exiting page ${e.pageName}:`,t)}}async navigate(e,t={},s={}){if(!this.router){console.error("Router not initialized");return}let i=e;if(Object.keys(t).length>0){const a=new URLSearchParams(t).toString();i+=(e.includes("?")?"&":"?")+a}return await this.router.navigate(i,s)}async navigateToDefault(e={}){return await this.showPage(this.defaultRoute,{},{},e)}back(){this.router?this.router.back():console.warn("Router not initialized")}forward(){this.router?this.router.forward():console.warn("Router not initialized")}getCurrentPage(){return this.currentPage}getPageContainer(){return this.layout&&this.layout.getPageContainer?this.layout.getPageContainer():typeof this.pageContainer=="string"?document.querySelector(this.pageContainer):this.pageContainer}async showError(e){try{await(await Promise.resolve().then(()=>L)).default.alert(e,"Error",{size:"md",class:"text-danger"})}catch(t){this.events.emit("notification",{message:e,type:"error"}),typeof window<"u"&&window?.console&&console.error("[WebApp] showError fallback:",t),typeof window<"u"&&alert(`Error: ${e}`)}}async showSuccess(e){try{await(await Promise.resolve().then(()=>L)).default.alert(e,"Success",{size:"md",class:"text-success"})}catch(t){this.events.emit("notification",{message:e,type:"success"}),typeof window<"u"&&window?.console&&console.warn("[WebApp] showSuccess fallback:",t),typeof window<"u"&&alert(`Success: ${e}`)}}async showInfo(e){try{await(await Promise.resolve().then(()=>L)).default.alert(e,"Information",{size:"md",class:"text-info"})}catch(t){this.events.emit("notification",{message:e,type:"info"}),typeof window<"u"&&window?.console&&console.info("[WebApp] showInfo fallback:",t),typeof window<"u"&&alert(`Info: ${e}`)}}async showWarning(e){try{await(await Promise.resolve().then(()=>L)).default.alert(e,"Warning",{size:"md",class:"text-warning"})}catch(t){this.events.emit("notification",{message:e,type:"warning"}),typeof window<"u"&&window?.console&&console.warn("[WebApp] showWarning fallback:",t),typeof window<"u"&&alert(`Warning: ${e}`)}}showNotification(e,t="info"){this.events.emit("notification",{message:e,type:t})}async showLoading(e={}){typeof e=="string"&&(e={message:e});try{(await Promise.resolve().then(()=>L)).default.showBusy(e)}catch(t){typeof window<"u"&&window?.console&&console.warn("[WebApp] showLoading fallback:",t,e),this.events.emit("notification",{message:e.message||"Loading...",type:"info"})}}async hideLoading(){try{(await Promise.resolve().then(()=>L)).default.hideBusy()}catch(e){typeof window<"u"&&window?.console&&console.warn("[WebApp] hideLoading fallback:",e)}}async showModelForm(e={}){try{return await(await Promise.resolve().then(()=>L)).default.showModelForm(e)}catch(t){throw typeof window<"u"&&window?.console&&console.error("[WebApp] showModelForm failed:",t),t}}async showForm(e={}){try{return await(await Promise.resolve().then(()=>L)).default.showForm(e)}catch(t){throw typeof window<"u"&&window?.console&&console.error("[WebApp] showForm failed:",t),t}}async showDialog(e={}){try{return await(await Promise.resolve().then(()=>L)).default.showDialog(e)}catch(t){throw typeof window<"u"&&window?.console&&console.error("[WebApp] showDialog failed:",t),t}}async confirm(e,t="Confirm",s={}){return await(await Promise.resolve().then(()=>L)).default.confirm(e,t,s)}setupFocusTracking(){if(typeof window>"u")return;this.isFocused=!document.hidden;const e=()=>{const i=this.isFocused;this.isFocused=!document.hidden,i!==this.isFocused&&(this.isFocused?this.events.emit("browser:focus"):this.events.emit("browser:blur"))},t=()=>{this.isFocused||(this.isFocused=!0,this.events.emit("browser:focus"))},s=()=>{this.isFocused&&(this.isFocused=!1,this.events.emit("browser:blur"))};document.addEventListener("visibilitychange",e),window.addEventListener("focus",t),window.addEventListener("blur",s),this._focusHandlers={visibilitychange:e,focus:t,blur:s}}setupErrorHandling(){window.addEventListener("error",e=>{console.error("Global error:",e.error),this.debug&&this.showError(`Error: ${e.error?.message||"Unknown error"}`)}),window.addEventListener("unhandledrejection",e=>{console.error("Unhandled promise rejection:",e.reason),this.debug&&this.showError(`Promise rejected: ${e.reason?.message||"Unknown error"}`)})}escapeHtml(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}getState(e){return e?this.state[e]:this.state}setState(e){const t={...this.state};Object.assign(this.state,e),this.events.emit("state:changed",{oldState:t,newState:this.state,updates:e})}registerComponent(e,t){this.componentClasses.set(e,t)}getComponent(e){return this.componentClasses.get(e)}registerModel(e,t){this.modelClasses.set(e,t)}getModel(e){return this.modelClasses.get(e)}setupRest(){this.rest=z,z.configure(this.api)}async destroy(){console.log("Destroying WebApp..."),this.router&&this.router.stop(),this._focusHandlers&&typeof window<"u"&&(document.removeEventListener("visibilitychange",this._focusHandlers.visibilitychange),window.removeEventListener("focus",this._focusHandlers.focus),window.removeEventListener("blur",this._focusHandlers.blur));const e=Array.from(this.pageCache.values());if(await Promise.allSettled(e.map(async t=>{try{t.destroy&&await t.destroy()}catch(s){console.error("Error destroying page:",s)}})),this.layout&&this.layout.destroy)try{await this.layout.destroy()}catch(t){console.error("Error destroying layout:",t)}this.pageCache.clear(),this.pageClasses.clear(),this.componentClasses.clear(),this.modelClasses.clear(),typeof window<"u"&&window.MOJO&&delete window.MOJO.router,this.isStarted=!1,console.log(`✨ ${this.name} destroyed`)}buildPagePath(e,t,s){let i=e.route||`/${e.pageName.toLowerCase()}`;if(Object.keys(t).forEach(a=>{(typeof t[a]=="string"||typeof t[a]=="number")&&(i=i.replace(`:${a}`,t[a]))}),s&&Object.keys(s).length>0){const a=new URLSearchParams(s).toString();i+=(i.includes("?")?"&":"?")+a}return i}validateDefaultRoute(){this.pageClasses.has(this.defaultRoute)?console.log(`✅ Default route '${this.defaultRoute}' is registered`):(console.warn(`⚠️ Default route '${this.defaultRoute}' is not registered!`),console.warn(` Please register a page: app.registerPage('${this.defaultRoute}', YourPageClass);`),console.warn(" Or change default route: new WebApp({ defaultRoute: 'your-page' });"))}findFallbackPage(){const e=["404","error","denied"];for(const[t]of this.pageClasses.entries())if(!e.includes(t))return t;return null}static create(e={}){return new G(e)}initPluginRegistry(){typeof window<"u"&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.app=this)}static registerPlugin(e,t){typeof window<"u"&&(window.MOJO||(window.MOJO={}),window.MOJO.plugins||(window.MOJO.plugins={}),window.MOJO.plugins[e]=t,console.debug(`MOJO Plugin registered: ${e}`))}}const Ae="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2NlZDRkYSI+PHBhdGggZD0iTTEyIDEyYzIuMjEgMCA0LTEuNzkgNC00cy0xLjc5LTQtNC00LTQgMS43OS00IDQgMS43OSA0IDQgNHptMCAyYy0yLjY3IDAtOCAxLjM0LTggNHYyaDE2di0yYzAtMi42Ni01LjMzLTQtOC00eiIvPjwvc3ZnPg==";class Ie{constructor(){this.formatters=new Map,this.registerBuiltInFormatters()}escapeHtml(e){if(e==null)return"";const t={"&":"&","<":"<",">":">",'"':""","'":"'"};return String(e).replace(/[&<>"']/g,s=>t[s])}registerBuiltInFormatters(){this.register("date",this.date.bind(this)),this.register("time",this.time.bind(this)),this.register("datetime",this.datetime.bind(this)),this.register("datetime_tz",this.datetime_tz.bind(this)),this.register("datatime_tz",this.datetime_tz.bind(this)),this.register("date_range",this.date_range.bind(this)),this.register("datetime_range",this.datetime_range.bind(this)),this.register("relative",this.relative.bind(this)),this.register("fromNow",this.relative.bind(this)),this.register("relative_short",this.relative_short.bind(this)),this.register("iso",this.iso.bind(this)),this.register("epoch",e=>{if(e==null||e==="")return e;const t=parseFloat(e);return isNaN(t)?e:t*1e3}),this.register("number",this.number.bind(this)),this.register("currency",this.currency.bind(this)),this.register("percent",this.percent.bind(this)),this.register("filesize",this.filesize.bind(this)),this.register("ordinal",this.ordinal.bind(this)),this.register("compact",this.compact.bind(this)),this.register("add",this.add.bind(this)),this.register("subtract",this.subtract.bind(this)),this.register("multiply",this.multiply.bind(this)),this.register("divide",this.divide.bind(this)),this.register("sub",this.subtract.bind(this)),this.register("mult",this.multiply.bind(this)),this.register("div",this.divide.bind(this)),this.register("uppercase",e=>String(e).toUpperCase()),this.register("lowercase",e=>String(e).toLowerCase()),this.register("upper",e=>String(e).toUpperCase()),this.register("lower",e=>String(e).toLowerCase()),this.register("capitalize",this.capitalize.bind(this)),this.register("caps",this.capitalize.bind(this)),this.register("replace",this.replace.bind(this)),this.register("truncate",this.truncate.bind(this)),this.register("truncate_middle",this.truncate_middle.bind(this)),this.register("truncate_front",this.truncate_front.bind(this)),this.register("slug",this.slug.bind(this)),this.register("initials",this.initials.bind(this)),this.register("mask",this.mask.bind(this)),this.register("hex",this.hex.bind(this)),this.register("tohex",this.hex.bind(this)),this.register("unhex",this.unhex.bind(this)),this.register("fromhex",this.unhex.bind(this)),this.register("email",this.email.bind(this)),this.register("phone",this.phone.bind(this)),this.register("url",this.url.bind(this)),this.register("badge",this.badge.bind(this)),this.register("badgeClass",this.badgeClass.bind(this)),this.register("status",this.status.bind(this)),this.register("status_text",this.status_text.bind(this)),this.register("status_icon",this.status_icon.bind(this)),this.register("boolean",this.boolean.bind(this)),this.register("bool",this.boolean.bind(this)),this.register("yesno",e=>this.boolean(e,"Yes","No")),this.register("yesnoicon",this.yesnoicon.bind(this)),this.register("icon",this.icon.bind(this)),this.register("avatar",this.avatar.bind(this)),this.register("image",this.image.bind(this)),this.register("tooltip",this.tooltip.bind(this)),this.register("linkify",this.linkify.bind(this)),this.register("clipboard",this.clipboard.bind(this)),this.register("default",this.default.bind(this)),this.register("equals",this.equals.bind(this)),this.register("json",this.json.bind(this)),this.register("raw",e=>e),this.register("custom",(e,t)=>typeof t=="function"?t(e):e),this.register("iter",this.iter.bind(this)),this.register("keys",e=>e&&typeof e=="object"&&!Array.isArray(e)?Object.keys(e):null),this.register("values",e=>e&&typeof e=="object"&&!Array.isArray(e)?Object.values(e):null),this.register("plural",this.plural.bind(this)),this.register("list",this.formatList.bind(this)),this.register("duration",this.duration.bind(this)),this.register("hash",this.hash.bind(this)),this.register("stripHtml",this.stripHtml.bind(this)),this.register("highlight",this.highlight.bind(this)),this.register("nl2br",this.nl2br.bind(this)),this.register("code",this.code.bind(this)),this.register("pre",e=>`<pre class="bg-light p-2 rounded border">${this.escapeHtml(String(e))}</pre>`)}relative_short(e){return this.relative(e,!0)}linkify(e,t={}){if(e==null)return"";const s=String(e),i=this.escapeHtml(s),a={urls:!0,emails:!0,target:"_blank",rel:"noopener noreferrer"},r=t&&typeof t=="object"?{...a,...t}:a;let n=i;if(r.urls!==!1){const l=/(^|\s)((?:https?:\/\/|www\.)[^\s<]+)/gi;n=n.replace(l,(o,c,d)=>{const h=d.startsWith("www.")?`https://${d}`:d;return`${c}<a href="${h}" target="${r.target}" rel="${r.rel}">${d}</a>`})}if(r.emails!==!1){const l=/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;n=n.replace(l,o=>`<a href="mailto:${o}">${o}</a>`)}return n}clipboard(e,t="text"){if(e==null)return"";const s=String(e),i=this.escapeHtml(s),a=t!=="icon-only",r=`
|
|
2
|
+
<button type="button"
|
|
3
|
+
class="btn btn-sm btn-outline-secondary ms-1 p-0 border-0 bg-transparent"
|
|
4
|
+
title="Copy"
|
|
5
|
+
data-bs-toggle="tooltip"
|
|
6
|
+
data-action="copy-to-clipboard"
|
|
7
|
+
data-clipboard="${i}">
|
|
8
|
+
<i class="bi bi-clipboard"></i>
|
|
9
|
+
</button>`.trim();return`
|
|
10
|
+
<span class="mojo-clipboard d-inline-flex align-items-center">
|
|
11
|
+
${a?`<span class="font-monospace">${i}</span>`:""}
|
|
12
|
+
${r}
|
|
13
|
+
</span>
|
|
14
|
+
`}nl2br(e){return e==null?"":this.escapeHtml(String(e)).replace(/\r\n|\r|\n/g,"<br>")}code(e,t=""){if(e==null)return"";const s=t?`language-${this.escapeHtml(String(t))}`:"",i=this.escapeHtml(String(e));return`<pre class="bg-light p-2 rounded border"><code class="${s}">${i}</code></pre>`}register(e,t){if(typeof t!="function")throw new Error(`Formatter must be a function, got ${typeof t}`);return this.formatters.set(e.toLowerCase(),t),this}apply(e,t,...s){try{const i=this.formatters.get(e.toLowerCase());return i?i(t,...s):(console.warn(`Formatter '${e}' not found`),t)}catch(i){return console.error(`Error in formatter '${e}':`,i),t}}pipe(e,t,s=null){return t?this.parsePipeString(t,s).reduce((a,r)=>this.apply(r.name,a,...r.args),e):e}parsePipeString(e,t=null){const s=[],i=e.split("|").map(a=>a.trim());for(const a of i){const r=this.parseFormatter(a,t);r&&s.push(r)}return s}parseFormatter(e,t=null){const s=e.match(/^([a-zA-Z_]\w*)\s*\((.*)\)$/);if(s){const[,a,r]=s,n=r?this.parseArguments(r,t):[];return{name:a,args:n}}const i=e.match(/^([a-zA-Z_]\w*)(?::(.+))?$/);if(i){const[,a,r]=i,n=r?this.parseColonArguments(r,t):[];return{name:a,args:n}}return null}parseArguments(e,t=null){const s=[];let i="",a=!1,r=null,n=0;for(let l=0;l<e.length;l++){const o=e[l];!a&&(o==='"'||o==="'")?(a=!0,r=o,i+=o):a&&o===r&&e[l-1]!=="\\"?(a=!1,r=null,i+=o):!a&&o==="{"?(n++,i+=o):!a&&o==="}"?(n--,i+=o):!a&&n===0&&o===","?(s.push(this.parseValue(i.trim(),t)),i=""):i+=o}return i.trim()&&s.push(this.parseValue(i.trim(),t)),s}parseColonArguments(e,t=null){const s=[];let i="",a=!1,r=null;for(let n=0;n<e.length;n++){const l=e[n];!a&&(l==='"'||l==="'")?(a=!0,r=l,i+=l):a&&l===r&&e[n-1]!=="\\"?(a=!1,r=null,i+=l):!a&&l===":"?(s.push(this.parseValue(i.trim(),t)),i=""):i+=l}return i.trim()&&s.push(this.parseValue(i.trim(),t)),s}parseValue(e,t=null){if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1);if(e==="true")return!0;if(e==="false")return!1;if(e==="null")return null;if(e!=="undefined"){if(!isNaN(e)&&e!=="")return Number(e);if(e.startsWith("{")&&e.endsWith("}"))try{return JSON.parse(e)}catch{}if(t&&this.isIdentifier(e)){if(!e.includes(".")&&Object.prototype.hasOwnProperty.call(t,e))return t[e];if(t.get&&typeof t.get=="function"){const s=t.get(e);if(s!==void 0)return s}if(t.getContextValue&&typeof t.getContextValue=="function"){const s=t.getContextValue(e);if(s!==void 0)return s}if(e.includes(".")){const s=window.MOJOUtils||(typeof require<"u"?require("./MOJOUtils.js").default:null);if(s){const i=s.getNestedValue(t,e);if(i!==void 0)return i}}}return e}}isIdentifier(e){return/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(e)}date(e,t="MM/DD/YYYY"){if(!e)return"";e=this.normalizeEpoch(e);let s;if(e instanceof Date)s=e;else if(typeof e=="string")if(/^\d{4}-\d{2}-\d{2}$/.test(e)){const[n,l,o]=e.split("-").map(Number);s=new Date(n,l-1,o)}else s=new Date(e);else s=new Date(e);if(isNaN(s.getTime()))return String(e);const i={YYYY:s.getFullYear(),YY:String(s.getFullYear()).slice(-2),MMMM:s.toLocaleDateString("en-US",{month:"long"}),MMM:s.toLocaleDateString("en-US",{month:"short"}),MM:String(s.getMonth()+1).padStart(2,"0"),M:s.getMonth()+1,dddd:s.toLocaleDateString("en-US",{weekday:"long"}),ddd:s.toLocaleDateString("en-US",{weekday:"short"}),DD:String(s.getDate()).padStart(2,"0"),D:s.getDate()};let a=t;const r=new RegExp(`(${Object.keys(i).join("|")})`,"g");return a=a.replace(r,n=>i[n]||n),a}time(e,t="HH:mm:ss"){if(!e)return"";e=this.normalizeEpoch(e);const s=e instanceof Date?e:new Date(e);if(isNaN(s.getTime()))return String(e);const i=s.getHours(),a={HH:String(i).padStart(2,"0"),H:i,hh:String(i%12||12).padStart(2,"0"),h:i%12||12,mm:String(s.getMinutes()).padStart(2,"0"),m:s.getMinutes(),ss:String(s.getSeconds()).padStart(2,"0"),s:s.getSeconds(),A:i>=12?"PM":"AM",a:i>=12?"pm":"am"};let r=t;const n=Object.keys(a).sort((l,o)=>o.length-l.length);for(const l of n)r=r.replace(new RegExp(l,"g"),a[l]);return r}datetime(e,t="MM/DD/YYYY",s="HH:mm:ss"){e=this.normalizeEpoch(e);const i=this.date(e,t),a=this.time(e,s);return i&&a?`${i} ${a}`:""}datetime_tz(e,t="MM/DD/YYYY",s="HH:mm:ss",i={}){if(!e)return"";e=this.normalizeEpoch(e);const a=e instanceof Date?e:new Date(e);if(isNaN(a.getTime()))return String(e);const r=i&&i.locale||"en-US",n=i&&i.timeZone?i.timeZone:void 0,l=()=>{let I="";try{const j=new Intl.DateTimeFormat(r,{hour:"2-digit",minute:"2-digit",timeZoneName:"short",...n?{timeZone:n}:{}}).formatToParts(a).find(V=>V.type==="timeZoneName");if(I=j?j.value:"",I&&/^GMT[+-]/i.test(I))try{const R=new Intl.DateTimeFormat(r,{timeStyle:"short",timeZoneName:"short",...n?{timeZone:n}:{}}).formatToParts(a).find(Ke=>Ke.type==="timeZoneName");R&&R.value&&!/^GMT[+-]/i.test(R.value)&&(I=R.value)}catch{}if(I&&/\s/.test(I)){const V=I.split(/\s+/).map(R=>R[0]).join("").toUpperCase();V.length>=2&&V.length<=4&&(I=V)}}catch{I=""}return I};if(!n){const I=this.date(a,t),O=this.time(a,s),j=l();return I&&O?`${I} ${O} ${j}`.trim():""}const o=new Intl.DateTimeFormat(r,{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(a),c=I=>{const O=o.find(j=>j.type===I);return O?O.value:""},d=c("year"),h=c("month"),f=c("day"),m=c("hour"),p=c("minute"),g=c("second"),w=h?String(parseInt(h,10)):"",y=f?String(parseInt(f,10)):"",b=m?String(parseInt(m,10)):"",C=m?parseInt(m,10)%12||12:"",$=m?parseInt(m,10)>=12?"PM":"AM":"",S=$?$.toLowerCase():"",T=new Intl.DateTimeFormat(r,{timeZone:n,month:"long"}).format(a),_=new Intl.DateTimeFormat(r,{timeZone:n,month:"short"}).format(a),P=new Intl.DateTimeFormat(r,{timeZone:n,weekday:"long"}).format(a),ee=new Intl.DateTimeFormat(r,{timeZone:n,weekday:"short"}).format(a),pe={YYYY:d,YY:d?d.slice(-2):"",MMMM:T,MMM:_,MM:h,M:w,dddd:P,ddd:ee,DD:f,D:y},ge={HH:m,H:b,hh:C!==""?String(C).padStart(2,"0"):"",h:C!==""?String(C):"",mm:p,m:p?String(parseInt(p,10)):"",ss:g,s:g?String(parseInt(g,10)):"",A:$,a:S},A=(I,O)=>{if(!I)return"";const j=new RegExp(`(${Object.keys(O).sort((V,R)=>R.length-V.length).join("|")})`,"g");return I.replace(j,V=>O[V]??V)},N=A(t,pe),De=A(s,ge),Ge=l();return N&&De?`${N} ${De} ${Ge}`.trim():""}normalizeEpoch(e){if(typeof e!="number"&&(e=Number(e)),isNaN(e))return"";if(e<1e11)return e*1e3;if(e>1e12&&e<1e13)return e;throw new Error("Value doesn't look like epoch seconds or ms")}date_range(e,t=null,s="MM/DD/YYYY"){if(!e)return"";const i=t||new Date,a=this.date(e,s),r=this.date(i,s);return!a||!r?"":`${a} - ${r}`}datetime_range(e,t=null,s="MM/DD/YYYY",i="HH:mm"){if(!e)return"";const a=t||new Date,r=this.datetime(e,s,i),n=this.datetime(a,s,i);return!r||!n?"":`${r} - ${n}`}relative(e,t=!1){if(!e)return"";e=this.normalizeEpoch(e);const s=e instanceof Date?e:new Date(e);if(isNaN(s.getTime()))return String(e);const a=s-new Date,r=Math.abs(a),n=Math.floor(r/1e3),l=Math.floor(n/60),o=Math.floor(l/60),c=Math.floor(o/24),d=a>0;if(t)return c>365?Math.floor(c/365)+"y":c>30?Math.floor(c/30)+"mo":c>7?Math.floor(c/7)+"w":c>0?c+"d":o>0?o+"h":l>0?l+"m":"now";if(c>365){const h=Math.floor(c/365),f=d?"in ":"",m=d?"":" ago";return f+h+" year"+(h>1?"s":"")+m}if(c>30){const h=Math.floor(c/30),f=d?"in ":"",m=d?"":" ago";return f+h+" month"+(h>1?"s":"")+m}if(c>7){const h=Math.floor(c/7),f=d?"in ":"",m=d?"":" ago";return f+h+" week"+(h>1?"s":"")+m}if(c===1)return d?"tomorrow":"yesterday";if(c>0){const h=d?"in ":"",f=d?"":" ago";return h+c+" days"+f}if(o>0){const h=d?"in ":"",f=d?"":" ago";return h+o+" hour"+(o>1?"s":"")+f}if(l>0){const h=d?"in ":"",f=d?"":" ago";return h+l+" minute"+(l>1?"s":"")+f}if(n>30){const h=d?"in ":"",f=d?"":" ago";return h+n+" seconds"+f}return"just now"}iso(e,t=!1){if(!e)return"";e=this.normalizeEpoch(e);const s=e instanceof Date?e:new Date(e);return isNaN(s.getTime())?String(e):t?s.toISOString().split("T")[0]:s.toISOString()}number(e,t=2,s="en-US"){const i=parseFloat(e);return isNaN(i)?String(e):i.toLocaleString(s,{minimumFractionDigits:t,maximumFractionDigits:t})}currency(e,t="$",s=2){const i=parseInt(e);if(isNaN(i))return String(e);const a=Math.abs(i).toString(),r=i<0?"-":"";let n,l;a.length<=2?(n="0",l=a.padStart(2,"0")):(n=a.slice(0,-2),l=a.slice(-2)),n=n.replace(/\B(?=(\d{3})+(?!\d))/g,",");let o;if(s===0)parseInt(l)>=50&&(n=(parseInt(n.replace(/,/g,""))+1).toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")),o=n;else if(s===2)o=`${n}.${l}`;else{const c=l.slice(0,s).padEnd(s,"0");o=`${n}.${c}`}return r+t+o}percent(e,t=0,s=!0){const i=parseFloat(e);if(isNaN(i))return String(e);const a=s?i*100:i;return this.number(a,t)+"%"}filesize(e,t=!1,s=1){const i=parseInt(e);if(isNaN(i))return String(e);const a=t?["B","KiB","MiB","GiB","TiB"]:["B","KB","MB","GB","TB"],r=t?1024:1e3;let n=i,l=0;for(;n>=r&&l<a.length-1;)n/=r,l++;const o=l===0?0:s;return`${n.toFixed(o)} ${a[l]}`}ordinal(e,t=!1){const s=parseInt(e);if(isNaN(s))return String(e);const i=s%10,a=s%100;let r="th";return i===1&&a!==11?r="st":i===2&&a!==12?r="nd":i===3&&a!==13&&(r="rd"),t?r:s+r}compact(e,t=1){const s=parseFloat(e);if(isNaN(s))return String(e);const i=Math.abs(s),a=s<0?"-":"";return i>=1e9?a+(i/1e9).toFixed(t)+"B":i>=1e6?a+(i/1e6).toFixed(t)+"M":i>=1e3?a+(i/1e3).toFixed(t)+"K":String(s)}add(e,t){const s=parseFloat(e),i=parseFloat(t);return isNaN(s)||isNaN(i)?e:s+i}subtract(e,t){const s=parseFloat(e),i=parseFloat(t);return isNaN(s)||isNaN(i)?e:s-i}multiply(e,t){const s=parseFloat(e),i=parseFloat(t);return isNaN(s)||isNaN(i)?e:s*i}divide(e,t){const s=parseFloat(e),i=parseFloat(t);return isNaN(s)||isNaN(i)||i===0?e:s/i}capitalize(e,t=!0){const s=String(e);return s?t?s.replace(/\b\w/g,i=>i.toUpperCase()):s.charAt(0).toUpperCase()+s.slice(1):""}replace(e,t,s="",i="g"){if(e==null)return"";const a=String(e);if(t==null||t==="")return a;if(t instanceof RegExp)return a.replace(t,String(s));const r=String(t),n=r.match(/^\/(.+)\/([a-z]*)$/i);if(n){const[,l,o]=n;try{return a.replace(new RegExp(l,o),String(s))}catch{}}return String(i).includes("g")?a.split(r).join(String(s)):a.replace(r,String(s))}truncate(e,t=50,s="..."){const i=String(e);return i.length<=t?i:i.substring(0,t)+s}truncate_front(e,t=8,s="..."){const i=String(e);return i.length<=t?i:`${s}${i.slice(-t)}`}truncate_middle(e,t=8,s="***"){const i=String(e);if(i.length<=t)return i;const a=Math.floor(t/2),r=i.substring(0,a),n=i.substring(i.length-a);return`${r}${s}${n}`}slug(e,t="-"){return String(e).toLowerCase().replace(/[^\w\s-]/g,"").replace(/\s+/g,t).replace(new RegExp(`${t}+`,"g"),t).replace(new RegExp(`^${t}|${t}$`,"g"),"")}initials(e,t=2){return String(e).split(/\s+/).filter(a=>a.length>0).slice(0,t).map(a=>a.charAt(0).toUpperCase()).join("")}mask(e,t="*",s=4){const i=String(e);if(i.length<=s)return i;const a=t.repeat(Math.max(0,i.length-s)),r=i.slice(-s);return a+r}email(e,t={}){const s=String(e).trim();if(!s)return"";if(t.link===!1)return s;const i=t.subject?`?subject=${encodeURIComponent(t.subject)}`:"",a=t.body?`&body=${encodeURIComponent(t.body)}`:"",r=t.class?` class="${t.class}"`:"";return`<a href="mailto:${s}${i}${a}"${r}>${s}</a>`}phone(e,t="US",s=!0){let i=String(e).replace(/\D/g,""),a=i;return t==="US"&&(i.length===10?a=`(${i.slice(0,3)}) ${i.slice(3,6)}-${i.slice(6)}`:i.length===11&&i[0]==="1"&&(a=`+1 (${i.slice(1,4)}) ${i.slice(4,7)}-${i.slice(7)}`)),s?`<a href="tel:${i}">${a}</a>`:a}url(e,t=null,s=!0){let i=String(e).trim();return i?(/^https?:\/\//.test(i)||(i="https://"+i),`<a href="${i}"${s?' target="_blank"':""}${s?' rel="noopener noreferrer"':""}>${t||i}</a>`):""}badge(e,t="auto"){if(Array.isArray(e))return e.map(r=>this.badge(r,t)).join(" ");const s=String(e),i=t==="auto"?this.inferBadgeType(s):t;return`<span class="badge ${i?`bg-${i}`:"bg-secondary"}">${s}</span>`}badgeClass(e,t="auto"){const s=String(e),i=t==="auto"?this.inferBadgeType(s):t;return i?`bg-${i}`:"bg-secondary"}inferBadgeType(e){const t=e.toLowerCase();return["active","success","complete","completed","approved","done","true","on","yes"].includes(t)?"success":["error","failed","rejected","deleted","cancelled","false","off","no","declined"].includes(t)?"danger":["warning","pending","review","processing","uploading"].includes(t)?"warning":["info","new","draft"].includes(t)?"info":(["inactive","disabled","archived","suspended"].includes(t),"secondary")}status(e){return this._status(e)}status_icon(e){return this._status(e,{},{},!1,!0)}status_text(e){return this._status(e,{},{},!0,!1)}_status(e,t={},s={},i=!1,a=!1){const r=String(e).toLowerCase(),n={active:"bi bi-check-circle-fill",approved:"bi bi-check-circle-fill",declined:"bi bi-x-circle-fill",inactive:"bi bi-pause-circle-fill",pending:"bi bi-clock-fill",success:"bi bi-check-circle-fill",error:"bi bi-exclamation-triangle-fill",warning:"bi bi-exclamation-triangle-fill"},l={active:"success",approved:"success",declined:"danger",inactive:"secondary",pending:"warning",success:"success",error:"danger",warning:"warning"},o=t[r]||n[r]||"",c=s[r]||l[r]||"secondary";let d="";!i&&o&&(d=`<i class="${o}"></i>`);let h="";return a||(h=e),`<span class="text-${c}">${d}${d?" ":""}${h}</span>`}boolean(e,t="True",s="False",i=!1){const a=e?t:s;return i?`<span class="text-${e?"success":"danger"}">${a}</span>`:a}icon(e,t={}){const s=String(e).toLowerCase(),i=t[s]||"";return i?`<i class="${i}"></i>`:""}yesnoicon(e,t="bi bi-check-circle-fill text-success",s="bi bi-x-circle-fill text-danger"){return e?`<i class="${t}"></i>`:`<i class="${s}"></i>`}image(e,t="thumbnail",s="img-fluid",i=""){const a=this._extractImageUrl(e,t);return a?`<img src="${a}" class="${s}" alt="${i}" />`:""}avatar(e,t="md",s="rounded-circle",i=""){const a=this._extractImageUrl(e,"square_sm")||Ae,r={xs:"width: 1.5rem; height: 1.5rem;",sm:"width: 2rem; height: 2rem;",md:"width: 3rem; height: 3rem;",lg:"width: 4rem; height: 4rem;",xl:"width: 5rem; height: 5rem;"},n=r[t]||r.md,o=`object-fit-cover ${s}`.trim();return`<img src="${a}" class="${o}" style="${n}" alt="${i}" />`}tooltip(e,t="",s="top",i=""){if(e==null)return"";const a=String(e),r=i==="html"?t:this.escapeHtml(t);return`<span data-bs-toggle="tooltip" data-bs-placement="${s}" ${i==="html"?'data-bs-html="true"':""} data-bs-title="${r}">${a}</span>`}_extractImageUrl(e,t="thumbnail"){if(!e)return null;if(typeof e=="string")return e;if(typeof e=="object"){if(e.attributes&&(e=e.attributes),t==="thumbnail"&&e.thumbnail&&typeof e.thumbnail=="string")return e.thumbnail;if(e.renditions&&typeof e.renditions=="object"){const s=e.renditions[t];if(s&&s.url)return s.url;const i=Object.values(e.renditions);if(i.length>0&&i[0].url)return i[0].url}if(e.url)return e.url}return null}default(e,t=""){return e==null||e===""?t:e}equals(e,t,s,i=""){return e==t?s:i}plural(e,t,s=null,i=!0){if(e==null||t===null||t===void 0)return i?`${e} ${t}`:t||"";const a=parseInt(e);if(isNaN(a))return i?`${e} ${t}`:t||"";const r=Math.abs(a)===1?t:s||t+"s";return i?`${a} ${r}`:r}formatList(e,t={}){if(!Array.isArray(e))return String(e);const{conjunction:s="and",limit:i=null,moreText:a="others"}=t;if(e.length===0)return"";if(e.length===1)return String(e[0]);let r=e.slice(),n=!1;if(i&&e.length>i&&(r=e.slice(0,i),n=!0),n){const l=e.length-i;return`${r.join(", ")}, ${s} ${l} ${a}`}return r.length===2?`${r[0]} ${s} ${r[1]}`:`${r.slice(0,-1).join(", ")}, ${s} ${r[r.length-1]}`}duration(e,t="ms",s=!1,i=2){if(e==null)return"";const a=parseFloat(e);if(isNaN(a))return String(e);let r;switch(t){case"s":case"sec":case"seconds":r=a*1e3;break;case"m":case"min":case"minutes":r=a*6e4;break;case"h":case"hr":case"hours":r=a*36e5;break;case"d":case"day":case"days":r=a*864e5;break;case"ms":case"milliseconds":default:r=a}const n=[{name:"day",short:"d",value:864e5},{name:"hour",short:"h",value:36e5},{name:"minute",short:"m",value:6e4},{name:"second",short:"s",value:1e3}];if(r===0)return s?"0s":"0 seconds";const l=Math.abs(r),o=r<0?"-":"",c=[];let d=l;for(const h of n)if(d>=h.value){const f=Math.floor(d/h.value);d=d%h.value;const m=s?h.short:f===1?h.name:h.name+"s";if(c.push(s?`${f}${m}`:`${f} ${m}`),c.length>=i)break}return c.length===0?s?`${Math.round(l)}ms`:`${Math.round(l)} milliseconds`:o+(s?c.join(""):c.join(" "))}hash(e,t=8,s="",i="..."){if(e==null)return"";const a=String(e);return a.length<=t?s+a:s+a.substring(0,t)+i}stripHtml(e){return e==null?"":String(e).replace(/<[^>]*>/g,"")}highlight(e,t,s="highlight"){if(e==null||!t)return String(e||"");const i=String(t).replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),a=new RegExp(`(${i})`,"gi");return String(e).replace(a,`<mark class="${s}">$1</mark>`)}hex(e,t=!1,s=!1){if(e==null)return"";let i="";const a=r=>Array.from(r).map(n=>n.toString(16).padStart(2,"0")).join("");if(typeof e=="number"){let r=Math.abs(Math.trunc(e)).toString(16);r.length%2&&(r="0"+r),i=r}else if(e instanceof Uint8Array)i=a(e);else if(e instanceof ArrayBuffer)i=a(new Uint8Array(e));else if(Array.isArray(e)&&e.every(r=>typeof r=="number"))i=a(Uint8Array.from(e.map(r=>r&255)));else{const n=new TextEncoder().encode(String(e));i=a(n)}return t&&(i=i.toUpperCase()),(s?"0x":"")+i}unhex(e){if(e==null)return"";let t=String(e).trim();if((t.startsWith("0x")||t.startsWith("0X"))&&(t=t.slice(2)),t=t.replace(/\s+/g,""),t.length===0)return"";t.length%2!==0&&(t="0"+t);const s=new Uint8Array(t.length/2);for(let i=0;i<t.length;i+=2){const a=parseInt(t.slice(i,i+2),16);if(Number.isNaN(a))return String(e);s[i/2]=a}try{return new TextDecoder().decode(s)}catch{let a="";for(const r of s)a+=String.fromCharCode(r);return a}}json(e,t=2){try{return JSON.stringify(e,null,t)}catch{return String(e)}}has(e){return this.formatters.has(e.toLowerCase())}unregister(e){return this.formatters.delete(e.toLowerCase())}listFormatters(){return Array.from(this.formatters.keys()).sort()}iter(e){return e==null?[]:Array.isArray(e)?e:typeof e=="object"?Object.entries(e).map(([t,s])=>({key:t,value:s})):[{key:"0",value:e}]}}const M=new Ie;window.dataFormatter=M;class x{static getContextData(e,t){if(!t||e==null)return;let s=t,i="",a=0,r=-1;for(let l=0;l<t.length;l++){const o=t[l];if(o==="(")a++;else if(o===")")a--;else if(o==="|"&&a===0){r=l;break}}r>-1&&(s=t.substring(0,r).trim(),i=t.substring(r+1).trim());const n=this.getNestedValue(e,s);return i?M.pipe(n,i,e):n}static getNestedValue(e,t){if(!t||e==null)return;if(!t.includes(".")){if(t in e){const a=e[t];return typeof a=="function"?a.call(e):a}return}const s=t.split(".");let i=e;for(let a=0;a<s.length;a++){const r=s[a];if(i==null)return;if(a===0)if(i.hasOwnProperty(r)){const n=i[r];typeof n=="function"?i=n.call(e):i=n}else return;else{if(i&&typeof i.getContextValue=="function"){const n=s.slice(a).join(".");return i.getContextValue(n)}if(Array.isArray(i)&&!isNaN(r))i=i[parseInt(r)];else if(i.hasOwnProperty(r))i=i[r];else if(typeof i[r]=="function")i=i[r].call(i);else return}}return i}static isNullOrUndefined(e){return e==null}static deepClone(e){if(e===null||typeof e!="object")return e;if(e instanceof Date)return new Date(e.getTime());if(e instanceof Array)return e.map(t=>this.deepClone(t));if(e instanceof Object){const t={};for(const s in e)e.hasOwnProperty(s)&&(t[s]=this.deepClone(e[s]));return t}}static deepMerge(e,...t){if(!t.length)return e;const s=t.shift();if(this.isObject(e)&&this.isObject(s))for(const i in s)this.isObject(s[i])?(e[i]||Object.assign(e,{[i]:{}}),this.deepMerge(e[i],s[i])):Object.assign(e,{[i]:s[i]});return this.deepMerge(e,...t)}static isObject(e){return e&&typeof e=="object"&&!Array.isArray(e)}static debounce(e,t){let s;return function(...a){const r=()=>{clearTimeout(s),e(...a)};clearTimeout(s),s=setTimeout(r,t)}}static throttle(e,t){let s;return function(...i){s||(e.apply(this,i),s=!0,setTimeout(()=>s=!1,t))}}static generateId(e=""){const t=Date.now().toString(36),s=Math.random().toString(36).substr(2,9);return e?`${e}_${t}_${s}`:`${t}_${s}`}static escapeHtml(e){const t={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(e).replace(/[&<>"'`=\/]/g,s=>t[s])}static checkPasswordStrength(e){if(!e||typeof e!="string")return{score:0,strength:"invalid",feedback:["Password must be a non-empty string"],details:{length:0,hasLowercase:!1,hasUppercase:!1,hasNumbers:!1,hasSpecialChars:!1,hasCommonPatterns:!1,isCommonPassword:!1}};const t=[],s={length:e.length,hasLowercase:/[a-z]/.test(e),hasUppercase:/[A-Z]/.test(e),hasNumbers:/[0-9]/.test(e),hasSpecialChars:/[^a-zA-Z0-9]/.test(e),hasCommonPatterns:!1,isCommonPassword:!1};let i=0;e.length<6?t.push("Password should be at least 6 characters long"):e.length<8?(i+=1,t.push("Consider using at least 8 characters for better security")):e.length<12?i+=3:i+=4,s.hasLowercase?i+=1:t.push("Include lowercase letters"),s.hasUppercase?i+=1:t.push("Include uppercase letters"),s.hasNumbers?i+=1:t.push("Include numbers"),s.hasSpecialChars?i+=2:t.push("Include special characters (!@#$%^&* etc.)");const a=[/123/,/abc/i,/qwerty/i,/asdf/i,/(.)\1{2,}/,/password/i,/admin/i,/user/i,/login/i];for(const l of a)if(l.test(e)){s.hasCommonPatterns=!0,i-=1,t.push("Avoid common patterns and dictionary words");break}["123456","password","123456789","12345678","12345","1234567","1234567890","qwerty","abc123","111111","123123","admin","letmein","welcome","monkey","password123","123qwe","qwerty123","000000","dragon","sunshine","princess","azerty","1234","iloveyou","trustno1","superman","shadow","master","jennifer"].includes(e.toLowerCase())&&(s.isCommonPassword=!0,i=Math.max(0,i-3),t.push("This password is too common and easily guessed"));let n;return i<2?n="very-weak":i<4?n="weak":i<6?n="fair":i<8?n="good":n="strong",i>=7&&t.length===0?t.push("Strong password! Consider using a password manager."):i>=5&&t.length<=1&&t.push("Good password strength. Consider adding more variety."),{score:Math.max(0,i),strength:n,feedback:t,details:s}}static generatePassword(e={}){const s={...{length:12,includeLowercase:!0,includeUppercase:!0,includeNumbers:!0,includeSpecialChars:!0,customChars:"",excludeAmbiguous:!1},...e};if(s.length<4)throw new Error("Password length must be at least 4 characters");let i="abcdefghijklmnopqrstuvwxyz",a="ABCDEFGHIJKLMNOPQRSTUVWXYZ",r="0123456789",n="!@#$%^&*()_+-=[]{}|;:,.<>?";s.excludeAmbiguous&&(i=i.replace(/[il]/g,""),a=a.replace(/[IOL]/g,""),r=r.replace(/[01]/g,""),n=n.replace(/[|]/g,""));let l="";const o=[];if(s.customChars?l=s.customChars:(s.includeLowercase&&(l+=i,o.push(i[Math.floor(Math.random()*i.length)])),s.includeUppercase&&(l+=a,o.push(a[Math.floor(Math.random()*a.length)])),s.includeNumbers&&(l+=r,o.push(r[Math.floor(Math.random()*r.length)])),s.includeSpecialChars&&(l+=n,o.push(n[Math.floor(Math.random()*n.length)]))),!l)throw new Error("No character types selected for password generation");let c="";for(const d of o)c+=d;for(let d=c.length;d<s.length;d++)c+=l[Math.floor(Math.random()*l.length)];return c.split("").sort(()=>Math.random()-.5).join("")}static parseQueryString(e){const t={},s=new URLSearchParams(e);for(const[i,a]of s.entries())t[i]=a;return t}static toQueryString(e){return new URLSearchParams(e).toString()}static wrapData(e,t=null,s=3){return!e||typeof e!="object"||e instanceof Date||e instanceof RegExp||e instanceof Error||s<=0||typeof e.getContextValue=="function"?e:Array.isArray(e)?e.map(i=>i&&typeof i=="object"&&!i.getContextValue?new se(i,t):i):new se(e,t)}}class se{constructor(e,t=null){if(Object.defineProperty(this,"_data",{value:e,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(this,"_rootContext",{value:t,writable:!1,enumerable:!1,configurable:!1}),e&&typeof e=="object"){for(const s in e)if(e.hasOwnProperty(s)){const i=e[s];this[s]=x.wrapData(i,t)}}}getContextValue(e){let t=e,s="",i=0,a=-1;for(let n=0;n<e.length;n++){const l=e[n];if(l==="(")i++;else if(l===")")i--;else if(l==="|"&&i===0){a=n;break}}a>-1&&(t=e.substring(0,a).trim(),s=e.substring(a+1).trim());let r;return t in this&&t!=="_data"&&t!=="_rootContext"?r=this[t]:r=x.getNestedValue(this._data,t),s&&r!==void 0?M.pipe(r,s,this._rootContext||this._data):r}has(e){return this._data&&this._data.hasOwnProperty(e)}toJSON(){return this._data}}x.DataWrapper=se,typeof window<"u"&&(window.utils=x);const Te=Object.prototype.toString,B=Array.isArray||function(u){return Te.call(u)==="[object Array]"},U=function(u){return typeof u=="function"},be=function(u){return u!==null&&typeof u=="object"};function Me(){return typeof window>"u"?null:window.MOJO?.dataFormatter?window.MOJO.dataFormatter:window.dataFormatter?window.dataFormatter:null}const ye=function(u){const e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(u).replace(/[&<>"'`=\/]/g,function(t){return e[t]})};class we{constructor(e){this.string=e,this.tail=e,this.pos=0}eos(){return this.tail===""}scan(e){const t=this.tail.match(e);if(!t||t.index!==0)return"";const s=t[0];return this.tail=this.tail.substring(s.length),this.pos+=s.length,s}scanUntil(e){const t=this.tail.search(e);let s;switch(t){case-1:s=this.tail,this.tail="";break;case 0:s="";break;default:s=this.tail.substring(0,t),this.tail=this.tail.substring(t)}return this.pos+=s.length,s}}class K{constructor(e,t){this.view=e,this.cache={".":this.view},this.parent=t,this.view?._cacheId||this.view&&typeof this.view=="object"&&(this.view._cacheId=Math.random().toString(36).substring(2))}push(e){return new K(e,this)}lookup(e){if(this.renderCache&&this.view?._cacheId){const i=`${this.view._cacheId}:${e}`;if(this.renderCache.has(i))return this.renderCache.get(i)}if(e===".")return this.view;if(e&&e.startsWith(".")){let i=e.substring(1),a=!1,r=null;const n=i.indexOf("|");if(n!==-1&&(r=i.substring(n+1).trim(),i=i.substring(0,n).trim()),i.endsWith("|iter")&&(i=i.substring(0,i.length-5),a=!0),this.view&&typeof this.view=="object"){let l;if(typeof this.view.getContextValue=="function")try{l=this.view.getContextValue(i),l!==void 0&&U(l)&&(l=l.call(this.view))}catch{l=void 0}if(l===void 0&&i in this.view&&(l=this.view[i],U(l)&&(l=l.call(this.view))),r&&l!==void 0)try{const o=Me();o&&typeof o.pipe=="function"&&(l=o.pipe(l,r,this))}catch{}return B(l)?a?l:l.length>0:be(l)?a?Object.entries(l).map(([o,c])=>({key:o,value:c})):Object.keys(l).length>0:l}return}const t=this.cache;let s;if(t.hasOwnProperty(e))s=t[e];else{let i=this,a,r,n,l=!1;for(;i;){if(i.view&&typeof i.view.getContextValue=="function")try{a=i.view.getContextValue(e),a!==void 0&&(l=!0)}catch{l=!1}if(!l)if(e.indexOf(".")>0)for(a=i.view,r=e.split("."),n=0;a!=null&&n<r.length;)if(a&&typeof a.getContextValue=="function"&&n<r.length)try{const o=r.slice(n).join(".");a=a.getContextValue(o),n=r.length,a!==void 0&&(l=!0)}catch{n===r.length-1&&(l=ie(a,r[n])||ve(a,r[n])),a=a[r[n++]]}else n===r.length-1&&(l=ie(a,r[n])||ve(a,r[n])),a=a[r[n++]];else a=i.view[e],l=ie(i.view,e);if(l){s=a;break}i=i.parent}t[e]=s}if(U(s)&&(s=s.call(this.view)),this.renderCache&&this.view?._cacheId){const i=`${this.view._cacheId}:${e}`;this.renderCache.set(i,s)}return s}}function ie(u,e){return u!=null&&typeof u=="object"&&e in u}function ve(u,e){return u!=null&&typeof u!="object"&&u.hasOwnProperty&&u.hasOwnProperty(e)}class Ce{constructor(){this.templateCache=new Map}clearCache(){this.templateCache.clear()}parse(e,t){t=t||["{{","}}"];const s=e+":"+t.join(":");let i=this.templateCache.get(s);return i==null&&(i=this.parseTemplate(e,t),this.templateCache.set(s,i)),i}parseTemplate(e,t){if(!e)return[];const s=t[0],i=t[1],a=new we(e),r=[];let n,l,o,c,d;const h=new RegExp(ae(s)+"\\s*"),f=new RegExp("\\s*"+ae(i)),m=new RegExp("\\s*"+ae("}"+i));for(;!a.eos();){if(n=a.pos,o=a.scanUntil(h),o)for(let p=0;p<o.length;++p)c=o.charAt(p),c===`
|
|
15
|
+
`?r.push(["text",c]):r.push(["text",c]);if(!a.scan(h))break;if(l=a.scan(/[#^\/>{&=!]/),l||(l="name"),a.scan(/\s*/),l==="="?(o=a.scanUntil(/\s*=/),a.scan(/\s*=/),a.scanUntil(f)):l==="{"?(o=a.scanUntil(m),a.scan(m),l="&"):o=a.scanUntil(f),a.scan(f),l==="#"||l==="^")d=[l,o,n,a.pos],r.push(d);else if(l==="/"){let p;for(let g=r.length-1;g>=0;--g)if((r[g][0]==="#"||r[g][0]==="^")&&r[g][1]===o){p=r[g];break}p&&p.length===4&&p.push(a.pos),r.push([l,o,n,a.pos])}else r.push([l,o,n,a.pos])}return this.nestSections(this.squashTokens(r))}squashTokens(e){const t=[];let s,i;for(let a=0;a<e.length;++a)s=e[a],s&&(s[0]==="text"&&i&&i[0]==="text"?(i[1]+=s[1],i[3]=s[3]):(t.push(s),i=s));return t}nestSections(e){const t=[];let s=t;const i=[];for(let a=0;a<e.length;++a){const r=e[a];switch(r[0]){case"#":case"^":const n=[r[0],r[1],r[2],r[3],[],r[4]||null];s.push(n),i.push(n),s=n[4];break;case"/":const l=i.pop();l&&(l[5]=r[2],s=i.length>0?i[i.length-1][4]:t);break;default:s.push(r)}}return t}render(e,t,s,i){const a=this.getConfigTags(i)||["{{","}}"],r=this.parse(e,a),n=new Map;return this.renderTokens(r,new K(t),s,e,i,n)}renderTokens(e,t,s,i,a,r){r&&!t.renderCache&&(t.renderCache=r);let n="";for(let l=0;l<e.length;++l){const o=e[l];let c;switch(o[0]){case"#":if(c=t.lookup(o[1]),!c)continue;const d=o[4];if(!d||!B(d)){console.warn(`MUSTACHE WARNING - Section ${o[1]} has no child tokens:`,o);continue}if(B(c))for(let h=0;h<c.length;++h){const f=t.push(c[h]);t.renderCache&&(f.renderCache=t.renderCache);const m=this.renderTokens(d,f,s,i,a,r);n+=m}else if(typeof c=="object"||typeof c=="string"||typeof c=="number"){const h=t.push(c);t.renderCache&&(h.renderCache=t.renderCache),n+=this.renderTokens(d,h,s,i,a,r)}else if(U(c)){const h=i==null?null:i.slice(o[3],o[5]);c=c.call(t.view,h,f=>this.render(f,t.view,s,a)),c!=null&&(n+=c)}else c&&(n+=this.renderTokens(d,t,s,i,a,r));break;case"^":if(c=t.lookup(o[1]),!c||B(c)&&c.length===0){const h=o[4];h&&B(h)&&(n+=this.renderTokens(h,t,s,i,a,r))}break;case">":if(!s)continue;c=U(s)?s(o[1]):s[o[1]],c!=null&&(n+=this.render(c,t.view,s,a));break;case"&":c=t.lookup(o[1]),c!=null&&(n+=c);break;case"name":c=t.lookup(o[1]),c!=null&&(n+=ye(c));break;case"text":n+=o[1];break}}return n}getConfigTags(e){return be(e)&&B(e.tags)?e.tags:null}}function ae(u){return u.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}const re=new Ce,E={name:"MOJO Mustache",version:"1.0.0",tags:["{{","}}"],Scanner:we,Context:K,Writer:Ce,escape:ye,clearCache(){return re.clearCache()},parse(u,e){return re.parse(u,e)},render(u,e,t,s){if(typeof u!="string")throw new TypeError('Invalid template! Template should be a "string"');return e&&typeof e=="object"&&!e.getContextValue&&typeof e.toJSON!="function"&&(e=x.wrapData(e)),re.render(u,e,t,s)}};class Ve{constructor(e){this.view=e,this.domListeners=[],this.debounceTimers=new Map}bind(e){if(this.unbind(),!e)return;const t=async n=>{const l=n.target.closest("[data-action]");if(l&&this.shouldHandle(l,n)){const c=l.getAttribute("data-action");if(this.hideTooltip(l),await this.dispatch(c,n,l)){n.preventDefault(),n.stopPropagation(),n.handledByChild=!0;return}}const o=n.target.closest("a[href], [data-page]");if(o&&!o.hasAttribute("data-action")&&this.shouldHandle(o,n)){if(n.ctrlKey||n.metaKey||n.shiftKey||n.button===1)return;if(o.tagName==="A"){const c=o.getAttribute("href");if(c&&c!=="#"&&!c.startsWith("#")&&(this.view.isExternalLink(c)||o.hasAttribute("data-external")))return}this.hideTooltip(o),n.preventDefault(),n.stopPropagation(),n.handledByChild=!0,o.hasAttribute("data-page")?await this.view.handlePageNavigation(o):await this.view.handleHrefNavigation(o)}},s=n=>{const l=n.target.closest("[data-change-action]");if(!l||!this.shouldHandle(l,n))return;const o=l.getAttribute("data-change-action");this.dispatchChange(o,n,l).then(c=>{c&&(n.stopPropagation(),n.handledByChild=!0)})},i=n=>{const l=n.target.closest("[data-change-action]");if(!l||!this.shouldHandle(l,n)||!n.target.matches('[data-filter="live-search"]'))return;const c=l.getAttribute("data-change-action"),d=parseInt(l.getAttribute("data-filter-debounce"))||300,h=`${c}-${l.getAttribute("data-container")||"default"}`;this.debounceTimers.has(h)&&clearTimeout(this.debounceTimers.get(h));const f=setTimeout(()=>{this.debounceTimers.delete(h),this.dispatchChange(c,n,l).then(m=>{m&&(n.stopPropagation(),n.handledByChild=!0)})},d);this.debounceTimers.set(h,f)},a=n=>{if(n.target.matches('[data-filter="search"]'))return;const l=n.target.closest("[data-keydown-action]")||n.target.closest("[data-change-action]");if(!l||!this.shouldHandle(l,n))return;let o=["Enter"];if(l.getAttribute("data-change-keys")&&(o=l.getAttribute("data-change-keys").split(",").map(c=>c.trim())),o.includes("*")||o.includes(n.key)){const c=l.getAttribute("data-keydown-action")||l.getAttribute("data-change-action");this.dispatch(c,n,l).then(d=>{d&&(n.preventDefault(),n.stopPropagation(),n.handledByChild=!0)})}},r=n=>{const l=n.target.closest("form[data-action]");if(!l||!this.shouldHandle(l,n))return;n.preventDefault();const o=l.getAttribute("data-action");this.dispatch(o,n,l)};e.addEventListener("click",t),e.addEventListener("change",s),e.addEventListener("input",i),e.addEventListener("keydown",a),e.addEventListener("submit",r),this.domListeners.push({el:e,type:"click",fn:t},{el:e,type:"change",fn:s},{el:e,type:"input",fn:i},{el:e,type:"keydown",fn:a},{el:e,type:"submit",fn:r})}unbind(){for(const{el:e,type:t,fn:s}of this.domListeners)e.removeEventListener(t,s);this.domListeners=[];for(const e of this.debounceTimers.values())clearTimeout(e);this.debounceTimers.clear()}hideDropdown(e){const s=e.closest(".dropdown-menu").closest(".dropdown");if(!s)return;const i=s.querySelector('[data-bs-toggle="dropdown"]');i&&window.bootstrap?.Dropdown&&window.bootstrap.Dropdown.getInstance(i)?.hide()}hideTooltip(e){if(e&&window.bootstrap?.Tooltip){const t=window.bootstrap.Tooltip.getInstance(e);t&&t.dispose()}}hideAllTooltips(){window.bootstrap?.Tooltip&&document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(t=>{const s=window.bootstrap.Tooltip.getInstance(t);s&&s.hide()})}async dispatch(e,t,s){const i=this.view,a=o=>o.includes("-")?o.split("-").map(c=>c[0].toUpperCase()+c.slice(1)).join(""):o[0].toUpperCase()+o.slice(1),r=`handleAction${a(e)}`;if(typeof i[r]=="function")try{return t.preventDefault(),await i[r](t,s),!0}catch(o){return console.error(`Error in action ${e}:`,o),i.handleActionError(e,o,t,s),!0}const n=`onAction${a(e)}`;if(typeof i[n]=="function")try{return await i[n](t,s)?(!!s.closest(".dropdown-menu")&&this.hideDropdown(s),t.preventDefault(),t.stopPropagation(),!0):!1}catch(o){return console.error(`Error in action ${e}:`,o),i.handleActionError(e,o,t,s),!0}const l=`onPassThruAction${a(e)}`;if(typeof i[l]=="function")try{return await i[l](t,s),!1}catch(o){return console.error(`Error in action ${e}:`,o),i.handleActionError(e,o,t,s),!0}if(typeof i.onActionDefault=="function")try{return await i.onActionDefault(e,t,s)}catch(o){return console.error(`Error in default action handler for ${e}:`,o),i.handleActionError(e,o,t,s),!0}return i.emit?.(`action:${e}`,{action:e,event:t,element:s}),!1}async dispatchChange(e,t,s){const i=this.view,r=`onChange${(n=>n.includes("-")?n.split("-").map(l=>l[0].toUpperCase()+l.slice(1)).join(""):n[0].toUpperCase()+n.slice(1))(e)}`;if(typeof i[r]=="function")try{return await i[r](t,s),!0}catch(n){return console.error(`Error in onChange ${e}:`,n),i.handleActionError?.(e,n,t,s),!0}return await this.dispatch(e,t,s)}shouldHandle(e,t){return!!(this.owns(e)||this.contains(e)&&!t.handledByChild)}owns(e){const t=this.view.element;if(!t||!t.contains(e))return!1;for(const s of Object.values(this.view.children))if(s.element&&s.element.contains(e))return!1;return!0}contains(e){return!!this.view.element&&this.view.element.contains(e)}}const ne={on(u,e,t){this._listeners||(this._listeners={}),this._listeners[u]||(this._listeners[u]=[]);const s={callback:e,context:t,fn:t?e.bind(t):e};return this._listeners[u].push(s),this},off(u,e,t){return!this._listeners||!this._listeners[u]?this:(e?(this._listeners[u]=this._listeners[u].filter(s=>s.callback!==e||arguments.length===3&&s.context!==t),this._listeners[u].length===0&&delete this._listeners[u]):delete this._listeners[u],this)},once(u,e,t){const s=(...i)=>{this.off(u,s),(t?e.bind(t):e).apply(t||this,i)};return this.on(u,s),this},emit(u,...e){if(!this._listeners||!this._listeners[u])return this;const t=this._listeners[u].slice();for(const s of t)try{s.fn.apply(s.context||this,e)}catch(i){console&&console.error&&console.error(`Error in ${u} event handler:`,i)}return this}};typeof window<"u"&&(window.Mustache=E);class v{constructor(e={}){this.tagName=e.tagName??"div",this.className=e.className??"mojo-view",this.style=e.style??null,this.id=e.id??v._genId(),this.containerId=e.containerId??null,this.container=e.container??null,typeof this.container=="string"&&(this.containerId=this.container,this.container=null),this.parent=e.parent??null,this.children=e.children??{},this.template=e.template||e.templateUrl||"",this.data=e.data??{},this.isRendering=!1,this.lastRenderTime=0,this.mounted=!1,this.debug=e.debug??!1,this.app=e.app??null,this.cacheTemplate=e.cacheTemplate??!0,this.enableTooltips=e.enableTooltips??!1,this.options={...e},this.element=this._ensureElement(),this.events=new Ve(this),e.model&&this.setModel(e.model)}async onInit(){}async onInitView(){this.initialized||(this.initialized=!0,await this.onInit())}async onBeforeRender(){}async onAfterRender(){}async onBeforeMount(){}async onAfterMount(){}async onBeforeUnmount(){}async onAfterUnmount(){}async onBeforeDestroy(){}async onAfterDestroy(){}setModel(e={}){let t=e!==this.model;if(!t)return this;this.model&&this.model.off&&this.model.off("change",this._onModelChange,this),this.model=e,this.model&&this.model.on&&this.model.on("change",this._onModelChange,this);for(const s in this.children){const i=this.children[s];i&&typeof i.setModel=="function"&&i.setModel(e)}return t&&this._onModelChange(),this}_onModelChange(){this.isMounted()&&this.render()}setTemplate(e){return this.template=e??"",this}addChild(e,t){try{if(!e||typeof e!="object")return this;t&&((t.containerId||t.container)&&(e.containerId=t.containerId||t.container),t.id&&(e.id=t.id)),e.parent=this,this.getApp()&&(e.app=this.app),this.children[e.id]=e}catch(s){v._warn("addChild error",s)}return e}removeChild(e){try{const t=typeof e=="string"?e:e&&e.id;if(!t)return this;const s=this.children[t];s&&(Promise.resolve(s.destroy()).catch(i=>v._warn("removeChild destroy error",i)),delete this.children[t])}catch(t){v._warn("removeChild error",t)}return this}getChild(e){return this.children[e]}async updateData(e,t=!1){return Object.assign(this.data,e),t&&this.isMounted()&&await this.render(),this}toggleClass(e,t){return t===void 0&&(t=!this.element.classList.contains(e)),this.element.classList.toggle(e,t),this}addClass(e){return this.element.classList.add(e),this}setClass(e){return this.element.className=e,this}removeClass(e){return this.element.classList.remove(e),this}canRender(){if(this.isRendering)return!1;const e=Date.now();if(this.options.renderCooldown>0&&e-this.lastRenderTime<this.options.renderCooldown)return v._warn(`View ${this.id}: Render called too quickly, cooldown active`),!1;if(this.options.noAppend&&this.parent)if(this.parent.contains(this.containerId||this.container)){if(this.containerId&&!document.getElementById(this.containerId))return!1;if(this.container&&!document.contains(this.container))return!1}else return!1;return!0}async render(e=!0,t=null){const s=Date.now();if(!this.canRender())return this;this.isRendering=!0,this.lastRenderTime=s;try{this.initialized||await this.onInitView(),this.unbindEvents(),await this.onBeforeRender(),this.getViewData&&(this.data=await this.getViewData());const i=await this.renderTemplate();this.element.innerHTML=i,e&&!this.isMounted()&&await this.mount(t),await this._renderChildren(),await this.onAfterRender(),this.bindEvents()}catch(i){v._warn(`Render error in ${this.id}`,i)}finally{this.isRendering=!1}return this}async _renderChildren(){for(const e in this.children){const t=this.children[e];t&&(t.parent=this,await Promise.resolve(t.render()).catch(s=>v._warn(`Child render error (${e})`,s)))}}async _unmountChildren(){for(const e in this.children){const t=this.children[e];t&&t.unbindEvents()}}isMounted(){return this.element?.isConnected}getChildElementById(e,t=null){const s=e.startsWith("#")?e.substring(1):e;return t?t.querySelector(`#${s}`):this.element.querySelector(`#${s}`)}getChildElement(e){if(e.startsWith("#"))return this.getChildElementById(e);let t=this.element?.querySelector(`[data-container="${e}"]`);return t||this.getChildElementById(e)}getContainer(){return this.replaceById?this.parent?this.parent.getChildElementById(this.id):null:this.containerId?this.parent?this.parent.getChildElement(this.containerId):this.getChildElementById(this.containerId,document.body):null}async mount(e=null){if(await this.onBeforeMount(),e||(e=this.getContainer()),this.containerId&&!e){console.error(`Container not found for ${this.containerId}`);return}e&&this.replaceById?e.replaceWith(this.element):e?e.replaceChildren(this.element):!this.containerId&&this.parent?this.parent.element.appendChild(this.element):!this.containerId&&!this.parent&&this.options.allowAppendToBody?(console.log("APPENDING TO BODY!!!!"),document.body.appendChild(this.element)):console.error(`Container not found for ${this.containerId}`),await this.onAfterMount(),this.mounted=!0}async unmount(){!this.element||!this.element.parentNode||(await this.onBeforeUnmount(),await this._unmountChildren(),this.element.parentNode&&this.element.parentNode.removeChild(this.element),this.events.unbind(),await this.onAfterUnmount(),this.mounted=!1)}async destroy(){try{this.events.unbind();for(const e in this.children){const t=this.children[e];t&&await Promise.resolve(t.destroy()).catch(s=>v._warn(`Child destroy error (${e})`,s))}this.mounted=!1,this.element&&this.element.parentNode&&(await this.onBeforeDestroy(),this.element.parentNode&&this.element.parentNode.removeChild(this.element),await this.onAfterDestroy())}catch(e){v._warn(`Destroy error in ${this.id}`,e)}}_ensureElement(){try{if(this.element&&this.element.tagName?.toLowerCase()===this.tagName)return this._syncAttrs(),this.element;const e=document.createElement(this.tagName);return this.element=e,this.el=e,this._syncAttrs(),e}catch(e){v._warn("ensureElement error",e);const t=document.createElement("div");return t.id=this.id||v._genId(),t}}_syncAttrs(){try{if(!this.element)return;this.id&&(this.element.id=this.id),this.element.className=this.className||"",this.style==null?this.element.removeAttribute("style"):this.element.style.cssText=String(this.style)}catch(e){v._warn("_syncAttrs error",e)}}bindEvents(){this.events.bind(this.element),this.enableTooltips&&this.initializeTooltips()}unbindEvents(){this.events.unbind(),this.enableTooltips&&this.disposeTooltips()}async renderTemplate(){const e=await this.getTemplate();if(!e)return"";const t=this.getPartials();return E.render(e,this,t)}renderTemplateString(e,t,s){return E.render(e,t,s)}getPartials(){return{}}async getTemplate(){if(this._templateCache&&this.cacheTemplate)return this._templateCache;const e=this.template||this.templateUrl;if(!e)throw new Error("Template not found");let t="";if(typeof e=="string")if(e.includes("<")||e.includes("{"))t=e;else try{let s=e;this.app||(this.app=this.getApp()),this.app&&this.app.basePath&&!s.startsWith("/")&&!s.startsWith("http://")&&!s.startsWith("https://")&&(s=`${this.app.basePath.endsWith("/")?this.app.basePath.slice(0,-1):this.app.basePath}/${s}`);const i=await fetch(s);if(!i.ok)throw new Error(`HTTP ${i.status}: ${i.statusText}`);t=await i.text()}catch(s){v._warn(`Failed to load template from ${e}: ${s}`),this.showError?.(`Failed to load template from ${e}: ${s.message}`)}else typeof e=="function"&&(t=await this.template(this.data,this.state));return this.cacheTemplate&&t&&(this._templateCache=t),t}getContextValue(e){const t=x.getContextData(this,e);return e&&e.startsWith("data.")&&t&&typeof t=="object"?x.wrapData(t,this):e&&e.startsWith("model.")&&e!=="model"&&t&&typeof t=="object"&&typeof t.getContextValue!="function"?x.wrapData(t,null):t}async handlePageNavigation(e){const t=e.getAttribute("data-page"),s=e.getAttribute("data-params");let i={};if(s)try{i=JSON.parse(s)}catch{console.warn("Invalid JSON in data-params:",s)}const a=this.getApp();if(a){a.showPage(t,i);return}const r=this.findRouter();r&&typeof r.navigateToPage=="function"?await r.navigateToPage(t,i):console.error(`No router found for page navigation to '${t}'`)}async handleHrefNavigation(e){const t=e.getAttribute("href");if(this.isExternalLink(t)||e.hasAttribute("data-external"))return;const s=this.findRouter();if(s){if(s.options&&s.options.mode==="param"&&t.startsWith("?")){const a="/"+t;await s.navigate(a);return}if(s.options&&s.options.mode==="hash"&&t.startsWith("#")){await s.navigate(t);return}const i=this.hrefToRoutePath(t);await s.navigate(i)}else console.warn("No router found for navigation, using default behavior"),window.location.href=t}isExternalLink(e){return e?e.startsWith("/")&&this.getApp()?!e.startsWith(this.findRouter().basePath):e.startsWith("#")||e.startsWith("mailto:")||e.startsWith("tel:")||e.startsWith("http://")||e.startsWith("https://")||e.startsWith("//"):!0}hrefToRoutePath(e){if(e.startsWith("/")){const t=this.findRouter();if(t&&t.options&&t.options.base){const s=t.options.base;if(e.startsWith(s))return e.substring(s.length)||"/"}return e}return e.startsWith("./")?e.substring(2):e}findRouter(){return this.getApp(),this.app?.router||null}getApp(){if(this.app)return this.app;const e=[window.__app__,window.MOJO?.app,window.APP,window.app,window.WebApp,window.matchUUID?window[window.matchUUID]():window[window.matchUUID]];return this.app=e.find(t=>t&&typeof t.showPage=="function")||null,this.app}handleActionError(e,t,s,i){this.showError(`Action '${e}' failed: ${t}`,s,i)}escapeHtml(e){if(typeof e!="string")return e;const t=document.createElement("div");return t.textContent=e,t.innerHTML}contains(e){if(typeof e=="string"){if(!this.element)return!1;e=document.getElementById(e)}return e?this.element.contains(e):!1}initializeTooltips(){if(!this.element||!window.bootstrap?.Tooltip)return;this.disposeTooltips(),[...this.element.querySelectorAll('[data-bs-toggle="tooltip"]')].map(t=>{const s=t.getAttribute("data-tooltip-theme"),i=t.getAttribute("data-tooltip-size");let a="";s&&(a+=`tooltip-${s} `),i&&(a+=`tooltip-${i}`);const r={},n=a.trim();return n&&(r.customClass=n),new window.bootstrap.Tooltip(t,r)})}disposeTooltips(){if(!this.element||!window.bootstrap?.Tooltip)return;this.element.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(t=>{const s=window.bootstrap.Tooltip.getInstance(t);s&&s.dispose()})}async showError(e){console.error(`View ${this.id} error:`,e);const t=this.getApp?this.getApp():this.app||null;if(t&&typeof t.showError=="function"){await t.showError(e);return}alert(`Error: ${e}`)}async showSuccess(e){this.debug&&console.log(`View ${this.id} success:`,e);const t=this.getApp?this.getApp():this.app||null;if(t&&typeof t.showSuccess=="function"){await t.showSuccess(e);return}alert(`Success: ${e}`)}async showInfo(e){console.info(`View ${this.id} info:`,e);const t=this.getApp?this.getApp():this.app||null;if(t&&typeof t.showInfo=="function"){await t.showInfo(e);return}alert(`Info: ${e}`)}async showWarning(e){console.warn(`View ${this.id} warning:`,e);const t=this.getApp?this.getApp():this.app||null;if(t&&typeof t.showWarning=="function"){await t.showWarning(e);return}alert(`Warning: ${e}`)}async onActionCopyToClipboard(e,t){try{const i=(t?.closest("[data-clipboard]")||t)?.getAttribute("data-clipboard")||"";if(!i)return!0;if(navigator.clipboard&&window.isSecureContext)await navigator.clipboard.writeText(i);else{const n=document.createElement("textarea");n.value=i,document.body.appendChild(n),n.select(),document.execCommand("copy"),document.body.removeChild(n)}const a=t.querySelector("i"),r=a&&a.className;return a&&(a.className="bi bi-check",setTimeout(()=>{a.className=r},1e3)),!0}catch(s){return console.warn("Copy to clipboard failed:",s),!0}}static _genId(){return`view_${Math.random().toString(36).substr(2,9)}`}static _warn(e,t){try{t?console.warn(`[View] ${e}:`,t):console.warn(`[View] ${e}`)}catch{}}}Object.assign(v.prototype,ne);class Y extends v{constructor(e={}){e.tagName=e.tagName||"main",e.className=e.className||"mojo-page";const t=e.pageName||"";t&&!e.id&&(e.id="page_"+t.toLowerCase().replace(/\s+/g,"_")),super(e),this.pageName=e.pageName||this.constructor.pageName||"",this.route=e.route||this.constructor.route||"",this.title=e.title||this.pageName||"",!this.id&&this.constructor.pageName&&!e.pageName&&(this.id="page_"+this.constructor.pageName.toLowerCase().replace(/\s+/g,"_")),this.pageIcon=e.icon||e.pageIcon||this.constructor.pageIcon||"bi bi-file-text",this.displayName=e.displayName||this.constructor.displayName||this.pageName||"",this.pageDescription=e.pageDescription||this.constructor.pageDescription||"",this.params={},this.query={},this.matched=!1,this.isActive=!1,this.pageOptions={title:e.title||this.pageName||"Untitled Page",description:e.description||"",requiresAuth:e.requiresAuth||!1,...e.pageOptions},this.savedState=null,console.log(`Page ${this.pageName} constructed with route: ${this.route}`)}async onParams(e={},t={}){this.params=e,this.query=t}canEnter(){if(this.options.permissions){const e=this.getApp().activeUser;if(!e||!e.hasPermission(this.options.permissions))return!1}return!(this.options.requiresGroup&&!this.getApp().activeGroup)}async onEnter(){this.isActive=!0,await this.onInitView(),this.savedState&&(this.restoreState(this.savedState),this.savedState=null),this.pageOptions&&this.pageOptions.title&&typeof document<"u"&&(document.title=this.pageOptions.title),this.emit("activated",{page:this.getMetadata()}),console.log(`Page ${this.pageName} entered`)}async onExit(){this.savedState=this.captureState(),this.isActive=!1,this.emit("deactivated",{page:this.getMetadata()}),console.log(`Page ${this.pageName} exiting`)}getMetadata(){return{name:this.pageName,displayName:this.displayName||this.pageName,icon:this.pageIcon,description:this.pageDescription,route:this.route,isActive:this.isActive}}async onActionDefault(e){console.log(`Default action '${e}' triggered on page: ${this.pageName}`)}async makeActive(){this.getApp().showPage(this)}async onActionNavigate(e,t){e.preventDefault();const s=t.dataset.page;this.getApp().showPage(s)}captureState(){return this.element?{scrollTop:this.element.scrollTop,formData:this.captureFormData(),custom:this.captureCustomState()}:null}restoreState(e){!e||!this.element||(this.element.scrollTop=e.scrollTop||0,this.restoreFormData(e.formData),e.custom&&this.restoreCustomState(e.custom))}captureFormData(){const e={};return this.element&&this.element.querySelectorAll("input, select, textarea").forEach(t=>{t.name&&(t.type==="checkbox"?e[t.name]=t.checked:t.type==="radio"?t.checked&&(e[t.name]=t.value):e[t.name]=t.value)}),e}restoreFormData(e){!e||!this.element||Object.entries(e).forEach(([t,s])=>{const i=this.element.querySelector(`[name="${t}"]`);if(i)if(i.type==="checkbox")i.checked=s;else if(i.type==="radio"){const a=this.element.querySelector(`[name="${t}"][value="${s}"]`);a&&(a.checked=!0)}else i.value=s})}captureCustomState(){return{}}restoreCustomState(e){}setMeta(e={}){if(!(typeof document>"u")){if(e.title&&(document.title=e.title,this.pageOptions.title=e.title),e.description){let t=document.querySelector('meta[name="description"]');t||(t=document.createElement("meta"),t.name="description",document.head.appendChild(t)),t.content=e.description,this.pageOptions.description=e.description}Object.entries(e).forEach(([t,s])=>{if(t!=="title"&&t!=="description"){let i=document.querySelector(`meta[name="${t}"]`);i||(i=document.createElement("meta"),i.name=t,document.head.appendChild(i)),i.content=s}})}}showError(e){if(super.showError(e),this.element){const t=document.createElement("div");t.className="alert alert-danger alert-dismissible fade show",t.innerHTML=`
|
|
16
|
+
${e}
|
|
17
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
18
|
+
`,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}showSuccess(e){if(super.showSuccess(e),this.element){const t=document.createElement("div");t.className="alert alert-success alert-dismissible fade show",t.innerHTML=`
|
|
19
|
+
${e}
|
|
20
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
21
|
+
`,this.element.insertBefore(t,this.element.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},3e3)}}async onBeforeRender(){await super.onBeforeRender(),this.setMeta({title:this.pageOptions.title,description:this.pageOptions.description})}async onAfterMount(){await super.onAfterMount(),typeof document<"u"&&this.pageName&&document.body.classList.add(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}async onBeforeDestroy(){await super.onBeforeDestroy(),typeof document<"u"&&this.pageName&&document.body.classList.remove(`page-${this.pageName.toLowerCase().replace(/\s+/g,"-")}`)}navigate(e,t={},s={}){if(this.app&&this.app.router)return this.app.router.navigate(e,s);if(typeof window<"u"&&window.MOJO?.router)return window.MOJO.router.navigate(e,s);console.error("No router available for navigation")}getRoute(){if(this.route){let e=this.route;return typeof e=="string"&&e.startsWith("/")&&(e=e.substring(1)),e}return this.pageName}syncUrl(e=!0){this.updateBrowserUrl(this.query,!1,!1)}updateBrowserUrl(e=null,t=!1,s=!1){this.getApp(),this.app.router.updateBrowserUrl(this.getRoute(),e,t,s)}static define(e){class t extends Y{constructor(i={}){super({...e,...i})}}return t.template=e.template,t.pageName=e.pageName,t.route=e.route,t}}class Q{constructor(e={},t={}){this.endpoint=t.endpoint||this.constructor.endpoint||"",this.id=e.id||null,this.attributes={...e},this._=this.attributes,this.originalAttributes={...e},this.errors={},this.loading=!1,this.rest=z,this.options={idAttribute:"id",timestamps:!0,...t}}getContextValue(e){return this.get(e)}get(e){return!e.includes(".")&&!e.includes("|")&&this[e]!==void 0?typeof this[e]=="function"?this[e]():this[e]:x.getContextData(this.attributes,e)}set(e,t,s={}){const i=JSON.parse(JSON.stringify(this.attributes));let a=!1;if(e!=null){if(typeof e=="object"){for(const[r,n]of Object.entries(e))a=this._setNestedAttribute(r,n)||a;e.id!==void 0&&(this.id=e.id)}else e==="id"?(this.id=t,a=!0):a=this._setNestedAttribute(e,t);if(a&&!s.silent)if(this.emit("change",this),typeof e=="string")this.emit(`change:${e}`,t,this);else for(const[r,n]of Object.entries(e)){const l=this._getNestedValue(r);JSON.stringify(this._getNestedValue(r,i))!==JSON.stringify(l)&&this.emit(`change:${r}`,l,this)}}}_setNestedAttribute(e,t){if(!e.includes(".")){const o=this.attributes[e];return this.attributes[e]=t,this[e]=t,o!==t}const s=e.split("."),i=s[0];(!this.attributes[i]||typeof this.attributes[i]!="object")&&(this.attributes[i]={}),(!this[i]||typeof this[i]!="object")&&(this[i]={});const a=this._getNestedValue(e);let r=this.attributes[i],n=this[i];for(let o=1;o<s.length-1;o++){const c=s[o];(!r[c]||typeof r[c]!="object")&&(r[c]={}),(!n[c]||typeof n[c]!="object")&&(n[c]={}),r=r[c],n=n[c]}const l=s[s.length-1];return r[l]=t,n[l]=t,JSON.stringify(a)!==JSON.stringify(t)}_getNestedValue(e,t=this.attributes){if(!e.includes("."))return t[e];const s=e.split(".");let i=t;for(const a of s){if(i==null||typeof i!="object")return;i=i[a]}return i}getData(){return this.attributes}getId(){return this.id}async fetch(e={}){let t=e.url;if(!t){const r=e.id||this.getId();if(!r&&this.options.requiresId!==!1)throw new Error("Model: ID is required for fetching");t=this.buildUrl(r)}const s=JSON.stringify({url:t,params:e.params});if(e.debounceMs&&e.debounceMs>0)return this._debouncedFetch(s,e);if(this.currentRequest&&this.currentRequestKey!==s&&(console.info("Model: Cancelling previous request for new parameters"),this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===s)return console.info("Model: Duplicate request in progress, returning existing promise"),this.currentRequest;const i=Date.now();if(this.lastFetchTime&&i-this.lastFetchTime<100)return console.info("Model: Rate limited, skipping fetch"),this;this.loading=!0,this.errors={},this.lastFetchTime=i,this.currentRequestKey=s,this.abortController=new AbortController,this.currentRequest=this._performFetch(t,e,this.abortController);try{return await this.currentRequest}catch(r){if(r.name==="AbortError")return console.info("Model: Request was cancelled"),this;throw r}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _debouncedFetch(e,t){return this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((s,i)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const a=await this.fetch({...t,debounceMs:0});s(a)}catch(a){i(a)}},t.debounceMs)})}async _performFetch(e,t,s){try{t.graph&&(!t.params||!t.params.graph)&&(t.params||(t.params={}),t.params.graph=t.graph);const i=await this.rest.GET(e,t.params,{signal:s.signal});return i.success?i.data.status?(this.originalAttributes={...this.attributes},i.data.data&&this.set(i.data.data),this.errors={}):this.errors=i.data:this.errors=i.errors||{},i}catch(i){if(i.name==="AbortError")throw console.info("Model: Fetch was cancelled"),i;return this.errors={fetch:i.message},{success:!1,error:i.message,status:i.status||500}}finally{this.loading=!1}}async save(e,t={}){const s=!this.id,i=s?"POST":"PUT",a=s?this.buildUrl():this.buildUrl(this.id);this.loading=!0,this.errors={};try{const r=await this.rest[i](a,e,t.params);return r.success?r.data.status?(this.originalAttributes={...this.attributes},this.set(r.data.data),this.errors={}):this.errors=r.data:this.errors=r.errors||{},r}catch(r){return{success:!1,error:r.message,status:r.status||500}}finally{this.loading=!1}}async destroy(e={}){if(!this.id)return this.errors={destroy:"Cannot destroy model without ID"},{success:!1,error:"Cannot destroy model without ID",status:400};const t=this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest.DELETE(t,e.params);return s.success?(this.attributes={},this.originalAttributes={},this.id=null,this.errors={}):this.errors=s.errors||{},s}catch(s){return this.errors={destroy:s.message},{success:!1,error:s.message,status:s.status||500}}finally{this.loading=!1}}isDirty(){return JSON.stringify(this.attributes)!==JSON.stringify(this.originalAttributes)}getChangedAttributes(){const e={};for(const[t,s]of Object.entries(this.attributes))this.originalAttributes[t]!==s&&(e[t]=s);return e}reset(){this.attributes={...this.originalAttributes},this._=this.attributes,this.errors={}}buildUrl(e=null){let t=this.endpoint;return e&&(t=t.endsWith("/")?`${t}${e}`:`${t}/${e}`),t}toJSON(){return{id:this.id,...this.attributes}}validate(){if(this.errors={},this.constructor.validations)for(const[e,t]of Object.entries(this.constructor.validations))this.validateField(e,t);return Object.keys(this.errors).length===0}validateField(e,t){const s=this.get(e),i=Array.isArray(t)?t:[t];for(const a of i)if(typeof a=="function"){const r=a(s,this);if(r!==!0){this.errors[e]=r||`${e} is invalid`;break}}else if(typeof a=="object"){if(a.required&&(s==null||s==="")){this.errors[e]=a.message||`${e} is required`;break}if(a.minLength&&s&&s.length<a.minLength){this.errors[e]=a.message||`${e} must be at least ${a.minLength} characters`;break}if(a.maxLength&&s&&s.length>a.maxLength){this.errors[e]=a.message||`${e} must be no more than ${a.maxLength} characters`;break}if(a.pattern&&s&&!a.pattern.test(s)){this.errors[e]=a.message||`${e} format is invalid`;break}}}static async find(e,t={}){const s=new this({},t);return await s.fetch({id:e,...t}),s}static create(e={},t={}){return new this(e,t)}cancel(){return this.currentRequest&&this.abortController?(console.info("Model: Manually cancelling active request"),this.abortController.abort(),!0):this.debouncedFetchTimeout?(clearTimeout(this.debouncedFetchTimeout),this.debouncedFetchTimeout=null,!0):!1}isFetching(){return!!this.currentRequest}async showError(e){await Dialog.alert(e,"Error",{size:"md",class:"text-danger"})}}Object.assign(Q.prototype,ne);class W{constructor(e={},t=null){if(Array.isArray(e)?(t=e,e=t||{}):t=t||e.data||[],this.ModelClass=e.ModelClass||Q,this.models=[],this.loading=!1,this.errors={},this.meta={},this.rest=z,t&&this.add(t),this.params={start:0,size:e.size||10,...e.params},this.endpoint=e.endpoint||this.ModelClass.endpoint||"",!this.endpoint){let s=new this.ModelClass;this.endpoint=s.endpoint}this.restEnabled=!!this.endpoint,e.restEnabled!==void 0&&(this.restEnabled=e.restEnabled),this.options={parse:!0,reset:!0,preloaded:!1,...e}}getModelName(){return this.ModelClass.name}async fetch(e={}){const t=JSON.stringify({...this.params,...e});if(this.currentRequest&&this.currentRequestKey!==t&&(console.info("Collection: Cancelling previous request for new parameters"),this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===t)return console.info("Collection: Duplicate request in progress, returning existing promise"),this.currentRequest;const s=Date.now();if(this.options.rateLimiting&&this.lastFetchTime&&s-this.lastFetchTime<100)return console.info("Collection: Rate limited, skipping fetch"),{success:!0,message:"Rate limited, skipping fetch",data:{data:this.toJSON()}};if(!this.restEnabled)return console.info("Collection: REST disabled, skipping fetch"),{success:!0,message:"REST disabled, skipping fetch",data:{data:this.toJSON()}};if(this.options.preloaded&&this.models.length>0)return console.info("Collection: Using preloaded data, skipping fetch"),{success:!0,message:"Using preloaded data, skipping fetch",data:{data:this.toJSON()}};const a=this.buildUrl();this.loading=!0,this.errors={},this.lastFetchTime=s,this.currentRequestKey=t,this.abortController=new AbortController,this.currentRequest=this._performFetch(a,e,this.abortController);try{return await this.currentRequest}catch(r){return r.name==="AbortError"?(console.info("Collection: Request was cancelled"),{success:!1,error:"Request cancelled",status:0}):{success:!1,error:r.message,status:r.status||500}}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _performFetch(e,t,s){const i={...this.params,...t};console.log("Fetching collection data from",e,i);try{this.emit("fetch:start");const a=await this.rest.GET(e,i,{signal:s.signal});if(a.success&&a.data.status){const r=this.options.parse?this.parse(a):a.data;(this.options.reset||t.reset!==!1)&&this.reset(),this.add(r,{silent:t.silent}),this.errors={},this.emit("fetch:success")}else a.data&&a.data.error?(this.errors=a.data,this.emit("fetch:error",{message:a.data.error,error:a.data})):(this.errors=a.errors||{},this.emit("fetch:error",{error:a.errors}));return a}catch(a){return a.name==="AbortError"?(console.info("Collection: Fetch was cancelled"),{success:!1,error:"Request cancelled",status:0}):(this.errors={fetch:a.message},this.emit("fetch:error",{message:a.message,error:a}),{success:!1,error:a.message,status:a.status||500})}finally{this.loading=!1,this.emit("fetch:end")}}async updateParams(e,t=!1,s=0){return await this.setParams({...this.params,...e},t,s)}async setParams(e,t=!1,s=0){return this.params=e,t&&this.restEnabled?s>0?(this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((i,a)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const r=await this.fetch();i(r)}catch(r){a(r)}},s)})):this.fetch():Promise.resolve(this)}async fetchOne(e,t={}){if(!e)return console.warn("Collection: fetchOne requires an ID"),null;if(!this.restEnabled)return console.info("Collection: REST disabled, cannot fetch single item"),null;try{const s=new this.ModelClass({id:e},{endpoint:this.endpoint,collection:this}),i=await s.fetch(t);if(i.success){if(t.addToCollection===!0){const a=this.get(s.id);a?t.merge!==!1&&a.set(s.attributes):this.add(s,{silent:t.silent})}return s}else return console.warn("Collection: fetchOne failed -",i.error||"Unknown error"),null}catch(s){return console.error("Collection: fetchOne error -",s.message),null}}async download(e="json",t={}){if(!this.restEnabled)return console.warn("Collection: REST is not enabled, cannot download from remote."),{success:!1,message:"Remote downloads are not enabled for this collection."};const s=this.buildUrl(),i={...this.params};delete i.start,delete i.size,i.download_format=e;const a=`export-${this.getModelName().toLowerCase()}`,r=this._buildDateRangeSuffix(i),n=`${a}${r}.${e}`,o={json:"application/json",csv:"text/csv"}[e]||"*/*";return i.filename=n,this.rest.download(s,i,{...t,filename:n,headers:{Accept:o}})}_buildDateRangeSuffix(e={}){const t=e.dr_start,s=e.dr_end;if(!t&&!s)return"";const i=n=>n?String(n).replace(/[^\dA-Za-z_-]/g,"-"):"",a=[],r=e.dr_field||"daterange";return a.push(i(r)),t&&a.push(`from-${i(e.dr_start)}`),s&&a.push(`to-${i(e.dr_end)}`),`-${a.filter(Boolean).join("-")}`}parse(e){return e.data&&Array.isArray(e.data.data)?(this.meta={size:e.data.size||10,start:e.data.start||0,count:e.data.count||0,status:e.data.status,graph:e.data.graph,...e.meta},e.data.data):Array.isArray(e.data)?e.data:Array.isArray(e)?e:[e]}add(e,t={}){const s=Array.isArray(e)?e:[e],i=[];for(const a of s){let r;a instanceof this.ModelClass?r=a:r=new this.ModelClass(a,{endpoint:this.endpoint,collection:this});const n=this.models.findIndex(l=>l.id===r.id);n!==-1?t.merge!==!1&&this.models[n].set(r.attributes):(this.models.push(r),i.push(r))}return!t.silent&&i.length>0&&(this.emit("add",{models:i,collection:this}),this.emit("update",{collection:this})),i}remove(e,t={}){const s=Array.isArray(e)?e:[e],i=[];for(const a of s){let r=-1;if(typeof a=="string"||typeof a=="number"?r=this.models.findIndex(n=>n.id==a):r=this.models.indexOf(a),r!==-1){const n=this.models.splice(r,1)[0];i.push(n)}}return!t.silent&&i.length>0&&(this.emit("remove",{models:i,collection:this}),this.emit("update",{collection:this})),i}reset(e=null,t={}){const s=[...this.models];return this.models=[],e&&this.add(e,{silent:!0,...t}),t.silent||this.emit("reset",{collection:this,previousModels:s}),this}get(e){return this.models.find(t=>t.id==e)}at(e){return this.models[e]}length(){return this.models.length}isEmpty(){return this.models.length===0}where(e){return typeof e=="function"?this.models.filter(e):typeof e=="object"?this.models.filter(t=>Object.entries(e).every(([s,i])=>t.get(s)===i)):[]}findWhere(e){const t=this.where(e);return t.length>0?t[0]:void 0}forEach(e,t){if(typeof e!="function")throw new TypeError("Callback must be a function");return this.models.forEach((s,i)=>{e.call(t,s,i,this)}),this}sort(e,t={}){if(typeof e=="string"){const s=e;e=(i,a)=>{const r=i.get(s),n=a.get(s);return r<n?-1:r>n?1:0}}return this.models.sort(e),t.silent||this.trigger("sort",{collection:this}),this}toJSON(){return this.models.map(e=>e.toJSON())}cancel(){return this.currentRequest&&this.abortController?(console.info("Collection: Manually cancelling active request"),this.abortController.abort(),!0):!1}isFetching(){return!!this.currentRequest}buildUrl(){return this.endpoint}*[Symbol.iterator](){for(const e of this.models)yield e}static fromArray(e,t=[],s={}){const i=new this(e,s);return i.add(t,{silent:!0}),i}}Object.assign(W.prototype,ne);class q{static _plugins=[];static _renderers=new Map;static register(e){return!e||typeof e!="object"?(console.warn("[FormPlugins] register called with invalid plugin:",e),()=>{}):!e.id||typeof e.id!="string"?(console.warn('[FormPlugins] plugin must have a unique string "id"',e),()=>{}):(this.unregister(e.id),e.fieldTypes&&typeof e.fieldTypes=="object"&&Object.entries(e.fieldTypes).forEach(([t,s])=>{typeof s=="function"?this._renderers.set(t,{renderer:s,pluginId:e.id}):console.warn(`[FormPlugins] renderer for type "${t}" is not a function`)}),this._plugins.push(e),()=>this.unregister(e.id))}static unregister(e){if(e){this._plugins=this._plugins.filter(t=>t.id!==e);for(const[t,s]of this._renderers.entries())s?.pluginId===e&&this._renderers.delete(t)}}static getRenderer(e){return this._renderers.get(e)?.renderer||null}static hasRenderer(e){return this._renderers.has(e)}static getPlugins(){return[...this._plugins]}static _invoke(e,t,...s){const i=e?.[t];if(typeof i=="function")try{return i.apply(e,s)}catch(a){console.error(`[FormPlugins] ${t} error from plugin "${e.id}":`,a)}}static onFormBuilderInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormBuilderInit",e))}static onFormViewInit(e){this._plugins.forEach(t=>this._invoke(t,"onFormViewInit",e))}static onFormViewAfterRender(e){this._plugins.forEach(t=>this._invoke(t,"onAfterRender",e))}static onFieldInit(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldInit",e,t,s))}static onFieldChange(e,t,s){this._plugins.forEach(i=>this._invoke(i,"onFieldChange",e,t,s))}}class J{constructor(e={}){this.fields=e.fields||[],this.structureOnly=e.structureOnly||!1,this.fields.forEach(t=>{t.cols&&!t.columns?(t.columns=t.cols,delete t.cols):t.columns||(t.columns=12),t.type==="group"&&t.fields&&t.fields.forEach(s=>{s.cols&&!s.columns?(s.columns=s.cols,delete s.cols):s.columns||(s.columns=12)})}),this.options={formClass:"needs-validation",formMethod:"POST",formAction:"",groupClass:"row mb-3",fieldWrapper:"",labelClass:"form-label",inputClass:"form-control",errorClass:"invalid-feedback",helpClass:"form-text",submitButton:!1,resetButton:!1,...e.options},this.buttons=e.buttons||[],this.data=e.data||{},this.errors=e.errors||{},this.initializeTemplates()}initializeTemplates(){q.onFormBuilderInit?.(this),this.templates={input:`
|
|
22
|
+
<div class="mojo-form-control">
|
|
23
|
+
{{#label}}
|
|
24
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
25
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
26
|
+
</label>
|
|
27
|
+
{{/label}}
|
|
28
|
+
{{#showCopy}}
|
|
29
|
+
<div class="input-group">
|
|
30
|
+
<input type="{{type}}" id="{{fieldId}}" name="{{name}}"
|
|
31
|
+
class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
32
|
+
value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
33
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
34
|
+
{{#readonly}}readonly{{/readonly}} {{{attrs}}}>
|
|
35
|
+
<button class="btn btn-outline-secondary" type="button" data-action="copy-to-clipboard" data-target="{{fieldId}}" title="Copy to clipboard">
|
|
36
|
+
<i class="bi bi-clipboard"></i>
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
{{/showCopy}}
|
|
40
|
+
{{^showCopy}}
|
|
41
|
+
<input type="{{type}}" id="{{fieldId}}" name="{{name}}"
|
|
42
|
+
class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
43
|
+
value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
44
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
45
|
+
{{#readonly}}readonly{{/readonly}} {{{attrs}}}>
|
|
46
|
+
{{/showCopy}}
|
|
47
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
48
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
49
|
+
</div>
|
|
50
|
+
`,password:`
|
|
51
|
+
<div class="mojo-form-control">
|
|
52
|
+
{{#label}}
|
|
53
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
54
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
55
|
+
</label>
|
|
56
|
+
{{/label}}
|
|
57
|
+
<div class="input-group">
|
|
58
|
+
<input type="password" id="{{fieldId}}" name="{{name}}"
|
|
59
|
+
class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
60
|
+
value="{{fieldValue}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
61
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
62
|
+
{{#readonly}}readonly{{/readonly}}
|
|
63
|
+
data-field-type="password" {{{attrs}}}>
|
|
64
|
+
{{#showToggle}}
|
|
65
|
+
<button type="button" class="btn btn-outline-secondary"
|
|
66
|
+
data-action="toggle-password"
|
|
67
|
+
data-target="{{fieldId}}"
|
|
68
|
+
aria-label="Show password" aria-pressed="false">
|
|
69
|
+
<i class="bi bi-eye"></i>
|
|
70
|
+
</button>
|
|
71
|
+
{{/showToggle}}
|
|
72
|
+
</div>
|
|
73
|
+
{{#strengthMeter}}
|
|
74
|
+
<div class="mt-2">
|
|
75
|
+
<div class="progress" style="height: 4px;">
|
|
76
|
+
<div class="progress-bar bg-secondary" role="progressbar"
|
|
77
|
+
style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
|
|
78
|
+
id="{{fieldId}}_strength_bar"></div>
|
|
79
|
+
</div>
|
|
80
|
+
<small class="{{helpClass}}" id="{{fieldId}}_strength_text">Strength</small>
|
|
81
|
+
</div>
|
|
82
|
+
{{/strengthMeter}}
|
|
83
|
+
{{#capsLockWarning}}
|
|
84
|
+
<div class="{{helpClass}} text-warning d-none" id="{{fieldId}}_caps_warning">
|
|
85
|
+
Caps Lock is on
|
|
86
|
+
</div>
|
|
87
|
+
{{/capsLockWarning}}
|
|
88
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
89
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
90
|
+
</div>
|
|
91
|
+
`,textarea:`
|
|
92
|
+
<div class="mojo-form-control">
|
|
93
|
+
{{#label}}
|
|
94
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
95
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
96
|
+
</label>
|
|
97
|
+
{{/label}}
|
|
98
|
+
{{#showCopy}}
|
|
99
|
+
<div class="position-relative">
|
|
100
|
+
<textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
101
|
+
rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
102
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
103
|
+
{{#readonly}}readonly{{/readonly}} {{{attrs}}}>{{fieldValue}}</textarea>
|
|
104
|
+
<button class="btn btn-sm btn-outline-secondary position-absolute top-0 end-0 m-2" type="button" data-action="copy-to-clipboard" data-target="{{fieldId}}" title="Copy to clipboard">
|
|
105
|
+
<i class="bi bi-clipboard"></i>
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
{{/showCopy}}
|
|
109
|
+
{{^showCopy}}
|
|
110
|
+
<textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
111
|
+
rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
112
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
113
|
+
{{#readonly}}readonly{{/readonly}} {{{attrs}}}>{{fieldValue}}</textarea>
|
|
114
|
+
{{/showCopy}}
|
|
115
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
116
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
117
|
+
</div>
|
|
118
|
+
`,htmlpreview:`
|
|
119
|
+
<div class="mojo-form-control">
|
|
120
|
+
{{#label}}
|
|
121
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
122
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
123
|
+
</label>
|
|
124
|
+
{{/label}}
|
|
125
|
+
<div class="position-relative">
|
|
126
|
+
<textarea id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
127
|
+
rows="{{rows}}" {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
|
|
128
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
129
|
+
{{#readonly}}readonly{{/readonly}}
|
|
130
|
+
data-field-type="htmlpreview" {{{attrs}}}>{{fieldValue}}</textarea>
|
|
131
|
+
<div class="position-absolute d-flex gap-1" style="top: 8px; right: 8px; z-index: 10;">
|
|
132
|
+
{{#showCopy}}
|
|
133
|
+
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
134
|
+
data-action="copy-to-clipboard"
|
|
135
|
+
data-target="{{fieldId}}"
|
|
136
|
+
title="Copy to clipboard"
|
|
137
|
+
aria-label="Copy to clipboard">
|
|
138
|
+
<i class="bi bi-clipboard"></i>
|
|
139
|
+
</button>
|
|
140
|
+
{{/showCopy}}
|
|
141
|
+
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
142
|
+
data-action="preview-html"
|
|
143
|
+
data-target="{{fieldId}}"
|
|
144
|
+
title="Preview HTML"
|
|
145
|
+
aria-label="Preview HTML">
|
|
146
|
+
<i class="bi bi-eye"></i>
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
151
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
152
|
+
</div>
|
|
153
|
+
`,select:`
|
|
154
|
+
<div class="mojo-form-control">
|
|
155
|
+
{{#label}}
|
|
156
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
157
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
158
|
+
</label>
|
|
159
|
+
{{/label}}
|
|
160
|
+
{{#searchInput}}{{{searchInput}}}{{/searchInput}}
|
|
161
|
+
<select id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
162
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
163
|
+
{{#multiple}}multiple{{/multiple}} {{{attrs}}}>
|
|
164
|
+
{{{optionsHTML}}}
|
|
165
|
+
</select>
|
|
166
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
167
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
168
|
+
</div>
|
|
169
|
+
`,checkbox:`
|
|
170
|
+
<div class="mojo-form-control">
|
|
171
|
+
<div class="form-check {{fieldClass}}">
|
|
172
|
+
<input type="checkbox" id="{{fieldId}}" name="{{name}}"
|
|
173
|
+
class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"
|
|
174
|
+
{{#checked}}checked{{/checked}} {{#required}}required{{/required}}
|
|
175
|
+
{{#disabled}}disabled{{/disabled}} {{{attrs}}}>
|
|
176
|
+
<label class="form-check-label" for="{{fieldId}}">{{label}}</label>
|
|
177
|
+
</div>
|
|
178
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
179
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
`,switch:`
|
|
183
|
+
<div class="mojo-form-control">
|
|
184
|
+
<div class="form-check form-switch {{sizeClass}} {{fieldClass}}">
|
|
185
|
+
<input type="checkbox" id="{{fieldId}}" name="{{name}}" role="switch"
|
|
186
|
+
class="form-check-input{{#error}} is-invalid{{/error}}" value="{{value}}"
|
|
187
|
+
{{#checked}}checked{{/checked}} {{#required}}required{{/required}}
|
|
188
|
+
{{#disabled}}disabled{{/disabled}} data-change-action="toggle-switch"
|
|
189
|
+
data-field="{{name}}" {{{attrs}}}>
|
|
190
|
+
<label class="form-check-label" for="{{fieldId}}">{{label}}</label>
|
|
191
|
+
</div>
|
|
192
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
193
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
194
|
+
</div>
|
|
195
|
+
`,image:`
|
|
196
|
+
<div class="mojo-form-control">
|
|
197
|
+
{{#label}}
|
|
198
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
199
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
200
|
+
</label>
|
|
201
|
+
{{/label}}
|
|
202
|
+
<div class="image-field-container {{containerClass}}" id="{{dropZoneId}}" style="width: {{width}}px; height: {{height}}px;">
|
|
203
|
+
<input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}} d-none{{#error}} is-invalid{{/error}}"
|
|
204
|
+
accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
205
|
+
data-change-action="image-selected" data-field="{{name}}" {{{attrs}}}>
|
|
206
|
+
<div class="image-drop-zone{{#allowDrop}} droppable{{/allowDrop}}" data-action="click-image-upload"
|
|
207
|
+
data-field-id="{{fieldId}}" data-field="{{name}}" style="width: 100%; height: 100%; cursor: {{cursor}};">
|
|
208
|
+
{{#imageUrl}}
|
|
209
|
+
<img id="{{previewId}}" src="{{imageUrl}}" alt="Preview" class="img-thumbnail w-100 h-100" style="object-fit: cover;">
|
|
210
|
+
{{#showRemove}}
|
|
211
|
+
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"
|
|
212
|
+
data-action="remove-image" data-field-id="{{fieldId}}" data-field="{{name}}" style="opacity: 0.8;">
|
|
213
|
+
<i class="bi bi-x"></i>
|
|
214
|
+
</button>
|
|
215
|
+
{{/showRemove}}
|
|
216
|
+
{{/imageUrl}}
|
|
217
|
+
{{^imageUrl}}
|
|
218
|
+
<div class="d-flex flex-column align-items-center justify-content-center h-100 text-muted border border-2 border-dashed">
|
|
219
|
+
<i class="bi bi-image fs-1 mb-2"></i>
|
|
220
|
+
<small class="text-center px-2">{{placeholderText}}</small>
|
|
221
|
+
</div>
|
|
222
|
+
{{/imageUrl}}
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
226
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
227
|
+
</div>
|
|
228
|
+
`,range:`
|
|
229
|
+
<div class="mojo-form-control">
|
|
230
|
+
{{#label}}
|
|
231
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
232
|
+
{{label}}: <span id="{{fieldId}}_value">{{fieldValue}}</span>
|
|
233
|
+
</label>
|
|
234
|
+
{{/label}}
|
|
235
|
+
<input type="range" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
236
|
+
min="{{min}}" max="{{max}}" step="{{step}}" value="{{fieldValue}}" {{#disabled}}disabled{{/disabled}}
|
|
237
|
+
data-change-action="range-changed" data-target="{{fieldId}}_value" {{{attrs}}}>
|
|
238
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
239
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
240
|
+
</div>
|
|
241
|
+
`,file:`
|
|
242
|
+
<div class="mojo-form-control">
|
|
243
|
+
{{#label}}
|
|
244
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
245
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
246
|
+
</label>
|
|
247
|
+
{{/label}}
|
|
248
|
+
<input type="file" id="{{fieldId}}" name="{{name}}" class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
249
|
+
accept="{{accept}}" {{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
250
|
+
{{#multiple}}multiple{{/multiple}} data-change-action="file-selected" {{{attrs}}}>
|
|
251
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
252
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
253
|
+
</div>
|
|
254
|
+
`,radio:`
|
|
255
|
+
<div class="mojo-form-control">
|
|
256
|
+
{{#label}}<div class="{{labelClass}}">{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}</div>{{/label}}
|
|
257
|
+
{{#options}}
|
|
258
|
+
<div class="form-check">
|
|
259
|
+
<input type="radio" id="{{fieldId}}_{{value}}" name="{{name}}" value="{{value}}"
|
|
260
|
+
class="form-check-input{{#error}} is-invalid{{/error}}" {{#checked}}checked{{/checked}}
|
|
261
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
262
|
+
{{{attrs}}}>
|
|
263
|
+
<label class="form-check-label" for="{{fieldId}}_{{value}}">{{text}}</label>
|
|
264
|
+
</div>
|
|
265
|
+
{{/options}}
|
|
266
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
267
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
268
|
+
</div>
|
|
269
|
+
`,button:`
|
|
270
|
+
<button type="button" {{#name}}name="{{name}}"{{/name}} class="btn {{fieldClass}}"
|
|
271
|
+
{{#action}}data-action="{{action}}"{{/action}} {{#disabled}}disabled{{/disabled}} {{{attrs}}}>
|
|
272
|
+
{{label}}
|
|
273
|
+
</button>
|
|
274
|
+
`,divider:`
|
|
275
|
+
<hr class="{{dividerClass}}">
|
|
276
|
+
`,html:`
|
|
277
|
+
<div class="form-html {{fieldClass}}">{{{html}}}</div>
|
|
278
|
+
`,header:`
|
|
279
|
+
<h{{level}} {{#id}}id="{{id}}"{{/id}} class="text-primary mb-3 {{fieldClass}}">{{text}}</h{{level}}>
|
|
280
|
+
`,hidden:`
|
|
281
|
+
<input type="hidden" id="{{fieldId}}" name="{{name}}" value="{{fieldValue}}">
|
|
282
|
+
`,checklistdropdown:`
|
|
283
|
+
<div class="dropdown">
|
|
284
|
+
<button class="{{buttonClass}}" type="button" data-bs-toggle="dropdown">
|
|
285
|
+
<i class="{{buttonIcon}} me-1"></i> {{buttonText}}
|
|
286
|
+
</button>
|
|
287
|
+
<div class="{{dropdownClass}}" style="min-width: {{minWidth}};">
|
|
288
|
+
{{#options}}
|
|
289
|
+
<div class="form-check">
|
|
290
|
+
<input class="form-check-input" type="checkbox"
|
|
291
|
+
value="{{value}}"
|
|
292
|
+
id="{{id}}"
|
|
293
|
+
{{#checked}}checked{{/checked}}
|
|
294
|
+
data-change-action="update-checklist"
|
|
295
|
+
data-field="{{fieldName}}">
|
|
296
|
+
<label class="form-check-label" for="{{id}}">
|
|
297
|
+
{{label}}
|
|
298
|
+
</label>
|
|
299
|
+
</div>
|
|
300
|
+
{{/options}}
|
|
301
|
+
<hr class="my-2">
|
|
302
|
+
<button class="btn btn-sm btn-primary w-100" data-action="apply-filter">
|
|
303
|
+
Apply
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
`,buttongroup:`
|
|
308
|
+
<div class="btn-group btn-group-{{size}}" role="group">
|
|
309
|
+
{{#options}}
|
|
310
|
+
<button type="button" class="{{buttonClass}} {{#active}}active{{/active}}"
|
|
311
|
+
{{^action}}
|
|
312
|
+
data-action="select-button-option"
|
|
313
|
+
{{/action}}
|
|
314
|
+
{{#action}}
|
|
315
|
+
data-action='{{action}}'
|
|
316
|
+
{{/action}}
|
|
317
|
+
data-field="{{fieldName}}"
|
|
318
|
+
data-value="{{value}}">
|
|
319
|
+
{{#icon}}<i class="{{icon}} me-1"></i> {{/icon}} {{label}}
|
|
320
|
+
</button>
|
|
321
|
+
{{/options}}
|
|
322
|
+
</div>
|
|
323
|
+
`,toolbarform:`
|
|
324
|
+
<div class="mojo-toolbar-form">
|
|
325
|
+
<div class="row g-2 align-items-center">
|
|
326
|
+
{{#fields}}
|
|
327
|
+
<div class="{{containerClass}}">
|
|
328
|
+
{{#label}}<label class="form-label-sm mb-1">{{label}}</label>{{/label}}
|
|
329
|
+
{{{fieldHtml}}}
|
|
330
|
+
</div>
|
|
331
|
+
{{/fields}}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
`,color:`
|
|
335
|
+
<div class="mojo-form-control">
|
|
336
|
+
{{#label}}
|
|
337
|
+
<label for="{{fieldId}}" class="{{labelClass}}">
|
|
338
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
339
|
+
</label>
|
|
340
|
+
{{/label}}
|
|
341
|
+
<div class="d-flex align-items-center gap-2">
|
|
342
|
+
<input type="color" id="{{fieldId}}" name="{{name}}"
|
|
343
|
+
class="{{inputClass}}{{#error}} is-invalid{{/error}}"
|
|
344
|
+
value="{{fieldValue}}"
|
|
345
|
+
{{#required}}required{{/required}} {{#disabled}}disabled{{/disabled}}
|
|
346
|
+
{{#readonly}}readonly{{/readonly}} {{{attrs}}}>
|
|
347
|
+
<button type="button" class="btn-sm text-muted border-0 bg-transparent p-1"
|
|
348
|
+
data-action="clear-color" data-field="{{name}}"
|
|
349
|
+
title="Clear color">
|
|
350
|
+
<i class="bi bi-x fs-5"></i>
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
353
|
+
{{#help}}<div class="{{helpClass}}">{{help}}</div>{{/help}}
|
|
354
|
+
{{#error}}<div class="{{errorClass}}">{{error}}</div>{{/error}}
|
|
355
|
+
</div>
|
|
356
|
+
`}}buildFormHTML(){const e=this.buildFieldsHTML(),t=this.buildButtonsHTML();return`
|
|
357
|
+
<form class="${this.options.formClass}" novalidate>
|
|
358
|
+
${e}
|
|
359
|
+
${t}
|
|
360
|
+
</form>
|
|
361
|
+
`}isAutoSizingField(e){return!e.columns||e.columns==="auto"||e.columns===""}buildFieldsHTML(){const e=[];let t=0;for(;t<this.fields.length;){const s=this.fields[t];if(s.columns=s.columns||s.cols,s.type==="group"){const i=[s];let a=s.columns||12;typeof a=="object"&&a!==null&&(a=a.md||a.sm||a.xs||12);let r=t+1;for(;r<this.fields.length&&this.fields[r].type==="group"&&a<12;){const l=this.fields[r];let o=l.columns||12;if(typeof o=="object"&&o!==null&&(o=o.md||o.sm||o.xs||12),a+o<=12)i.push(l),a+=o,r++;else break}let n=i.length>1;if(i.length===1&&s.columns){let l=s.columns;typeof l=="object"&&l!==null&&(l=l.md||l.sm||l.xs||12),n=n||l<12}if(n){const l=i.map(o=>this.buildGroupHTML(o)).join("");e.push(`<div class="row">${l}</div>`)}else e.push(this.buildGroupHTML(s));t=r}else if(s.columns&&s.columns<12){const i=[s];let a=s.columns||12;typeof a=="object"&&a!==null&&(a=a.md||a.sm||a.xs||12);let r=t+1;for(;r<this.fields.length&&this.fields[r].columns&&a<12;){const l=this.fields[r];let o=l.columns||12;if(typeof o=="object"&&o!==null&&(o=o.md||o.sm||o.xs||12),a+o<=12)i.push(l),a+=o,r++;else break}const n=i.map(l=>this.buildFieldHTML(l)).join("");e.push(`<div class="row">${n}</div>`),t=r}else if(this.isAutoSizingField(s)){const i=[s];let a=t+1;for(;a<this.fields.length;){const r=this.fields[a];if(this.isAutoSizingField(r))i.push(r),a++;else break}if(i.length>1){const r=i.map(n=>this.buildFieldHTML(n)).join("");e.push(`<div class="row">${r}</div>`)}else e.push(`<div class="row">${this.buildFieldHTML(s)}</div>`);t=a}else e.push(this.buildFieldHTML(s)),t++}return e.join("")}buildGroupHTML(e){const{columns:t=12,title:s,fields:i=[],class:a="",titleClass:r="h6 mb-3",responsive:n={}}=e;let l=[];if(typeof t=="object"&&t!==null){if(t.xs&&l.push(`col-${t.xs}`),t.sm&&l.push(`col-sm-${t.sm}`),t.md&&l.push(`col-md-${t.md}`),t.lg&&l.push(`col-lg-${t.lg}`),t.xl&&l.push(`col-xl-${t.xl}`),t.xxl&&l.push(`col-xxl-${t.xxl}`),!t.md&&(t.xs||t.sm)){const d=t.sm||t.xs;l.push(`col-md-${d}`)}l.length===0&&l.push("col-md-12")}else l.push(`col-md-${t}`);n.xs&&l.push(`col-${n.xs}`),n.sm&&l.push(`col-sm-${n.sm}`),n.lg&&l.push(`col-lg-${n.lg}`),n.xl&&l.push(`col-xl-${n.xl}`);const o=l.join(" "),c=i.map(d=>d.type==="group"?this.buildGroupHTML(d):this.buildFieldHTML(d)).join("");return`
|
|
362
|
+
<div class="${o}">
|
|
363
|
+
<div class="mojo-form-group ${a}">
|
|
364
|
+
${s?`<div class="${r}">${this.escapeHtml(s)}</div>`:""}
|
|
365
|
+
<div class="row">
|
|
366
|
+
${c}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
`}buildFieldHTML(e){const{type:t,columns:s,class:i=""}=e;let a="";const r=q&&typeof q.getRenderer=="function"?q.getRenderer(t):null;if(typeof r=="function")try{const l=r(this,e);l!=null&&(a=String(l))}catch(l){console.error("FormPlugins custom renderer error:",l)}if(!a)switch(t){case"text":a=this.renderTextField(e);break;case"email":a=this.renderEmailField(e);break;case"password":a=this.renderPasswordField(e);break;case"number":a=this.renderNumberField(e);break;case"tel":a=this.renderTelField(e);break;case"url":a=this.renderUrlField(e);break;case"search":a=this.renderSearchField(e);break;case"hex":a=this.renderHexField(e);break;case"textarea":a=this.renderTextareaField(e);break;case"htmlpreview":a=this.renderHtmlPreviewField(e);break;case"json":a=this.renderJsonField(e);break;case"select":a=this.renderSelectField(e);break;case"multiselect":a=this.renderMultiSelectField(e);break;case"checkbox":a=this.renderCheckboxField(e);break;case"toggle":case"switch":a=this.renderSwitchField(e);break;case"radio":a=this.renderRadioField(e);break;case"date":a=this.renderDateField(e);break;case"datetime":a=this.renderDateTimeField(e);break;case"time":a=this.renderTimeField(e);break;case"file":a=this.renderFileField(e);break;case"image":a=this.renderImageField(e);break;case"color":a=this.renderColorField(e);break;case"range":a=this.renderRangeField(e);break;case"hidden":a=this.renderHiddenField(e);break;case"button":a=this.renderButton(e);break;case"divider":a=this.renderDivider(e);break;case"html":a=this.renderHtmlField(e);break;case"heading":case"header":a=this.renderHeaderField(e);break;case"tag":case"tags":a=this.renderTagField(e);break;case"collection":a=this.renderCollectionField(e);break;case"collectionmultiselect":case"collection-multiselect":a=this.renderCollectionMultiSelectField(e);break;case"datepicker":a=this.renderDatePickerField(e);break;case"daterange":a=this.renderDateRangeField(e);break;case"checklistdropdown":a=this.renderChecklistDropdownField(e);break;case"buttongroup":a=this.renderButtonGroupField(e);break;case"combo":case"combobox":case"autocomplete":a=this.renderComboField(e);break;case"tabset":a=this.renderTabsetField(e);break;default:console.warn(`Unknown field type: ${t}`),a=this.renderTextField(e)}let n;return this.isAutoSizingField(e)?n=`col ${i}`.trim():n=`col-${s} ${i}`.trim(),`<div class="${n}">${a}</div>`}getFieldId(e){return e?`field_${e.replace(/[.\s\[\]]/g,"_")}`:`field_${Math.random().toString(36).substr(2,9)}`}renderTextField(e){return this.renderInputField(e,"text")}renderEmailField(e){return this.renderInputField(e,"email")}renderPasswordField(e){const t=e.passwordUsage||"current",s=t==="new"||t==="new-password"?"new-password":"current-password",i={...e.attributes||{},autocomplete:e.attributes&&e.attributes.autocomplete||s};return this.renderInputField({...e,showToggle:e.showToggle!==!1,attributes:i},"password")}renderNumberField(e){const{min:t,max:s,step:i=1,...a}=e,r=[];return t!==void 0&&r.push(`min="${t}"`),s!==void 0&&r.push(`max="${s}"`),i!==void 0&&r.push(`step="${i}"`),this.renderInputField({...a,attributes:{...a.attributes,...r.reduce((n,l)=>{const[o,c]=l.split("=");return n[o]=c.replace(/"/g,""),n},{})}},"number")}renderTelField(e){return this.renderInputField(e,"tel")}renderUrlField(e){return this.renderInputField(e,"url")}renderSearchField(e){const t={...e,attributes:{"data-filter":"live-search","data-change-action":"filter-search","data-filter-debounce":e.debounce||"300",...e.attributes}};return this.renderInputField(t,"search")}renderHexField(e){const{hexType:t="color",allowPrefix:s=!0,minLength:i,maxLength:a,...r}=e;let n,l,o,c,d;switch(t){case"color":n=s?"^#?[0-9A-Fa-f]{6}$":"^[0-9A-Fa-f]{6}$",l=6,o=s?7:6,c=s?"#FF0000":"FF0000",d=d||"Enter a valid hex color (e.g., "+c+")";break;case"color-short":n=s?"^#?[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$":"^[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$",l=s?4:3,o=s?7:6,c=s?"#F00 or #FF0000":"F00 or FF0000",d=d||"Enter a valid hex color (3 or 6 digits)";break;case"string":n="^[0-9A-Fa-f]+$",l=i||1,o=a||64,c="ABCDEF123456",d=d||"Only hexadecimal characters (0-9, A-F) allowed";break;default:n=s?"^#?[0-9A-Fa-f]+$":"^[0-9A-Fa-f]+$",l=i||1,o=a||64,c=s?"#ABCDEF or ABCDEF":"ABCDEF",d=d||"Enter hexadecimal characters only"}const h={...r,pattern:n,minLength:l,maxLength:o,placeholder:r.placeholder||c,help:r.help||d,attributes:{"data-hex-type":t,"data-allow-prefix":s,style:"text-transform: uppercase;",...r.attributes}};return this.renderInputField(h,"text")}renderInputField(e,t="text"){const{name:s,label:i,value:a="",placeholder:r="",required:n=!1,disabled:l=!1,readonly:o=!1,class:c="",attributes:d={},help:h=e.helpText||e.help||""}=e,f=`${this.options.inputClass} ${c}`.trim(),m=this.errors[s],p=this.getFieldValue(s)??a,g=Object.entries(d).map(([b,C])=>`${b}="${this.escapeHtml(C)}"`).join(" "),w=this.getFieldId(s),y={labelClass:this.options.labelClass,inputClass:f,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:w,name:s,type:t,fieldValue:this.escapeHtml(p),label:i?this.escapeHtml(i):null,placeholder:r?this.escapeHtml(r):null,help:h?this.escapeHtml(h):null,error:m?this.escapeHtml(m):null,required:n,disabled:l,readonly:o,attrs:g,showCopy:!!e.showCopy};if(t==="password"&&(e.showToggle||e.strengthMeter||e.capsLockWarning)){const b={...y,showToggle:!!e.showToggle,strengthMeter:!!e.strengthMeter,capsLockWarning:!!e.capsLockWarning};return E.render(this.templates.password,b)}if(t==="password"){const b={...y,showToggle:e.showToggle!==!1,strengthMeter:!!e.strengthMeter,capsLockWarning:!!e.capsLockWarning};return E.render(this.templates.password,b)}return E.render(this.templates.input,y)}renderTextareaField(e){const{name:t,label:s,value:i="",placeholder:a="",required:r=!1,disabled:n=!1,readonly:l=!1,rows:o=3,cols:c,class:d="",attributes:h={},help:f=e.helpText||e.help||""}=e,m=`${this.options.inputClass} ${d}`.trim(),p=this.errors[t],g=this.getFieldValue(t)??i,w=Object.entries(h).map(([C,$])=>`${C}="${this.escapeHtml($)}"`).join(" "),y=this.getFieldId(t),b={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:y,name:t,fieldValue:g,label:s?this.escapeHtml(s):null,placeholder:a?this.escapeHtml(a):null,help:f?this.escapeHtml(f):null,error:p?this.escapeHtml(p):null,rows:o||3,required:r,disabled:n,readonly:l,showCopy:!!e.showCopy,attrs:w};return E.render(this.templates.textarea,b)}renderHtmlPreviewField(e){const{name:t,label:s,value:i="",placeholder:a="",required:r=!1,disabled:n=!1,readonly:l=!1,rows:o=5,class:c="",attributes:d={},help:h=e.helpText||e.help||""}=e,f=`${this.options.inputClass} ${c}`.trim(),m=this.errors[t],p=this.getFieldValue(t)??i,g=Object.entries(d).map(([b,C])=>`${b}="${this.escapeHtml(C)}"`).join(" "),w=this.getFieldId(t),y={labelClass:this.options.labelClass,inputClass:f,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:w,name:t,fieldValue:p,label:s?this.escapeHtml(s):null,placeholder:a?this.escapeHtml(a):null,help:h?this.escapeHtml(h):null,error:m?this.escapeHtml(m):null,rows:o||5,required:r,disabled:n,readonly:l,attrs:g,showCopy:!!e.showCopy};return E.render(this.templates.htmlpreview,y)}renderJsonField(e){const{name:t,label:s,placeholder:i="",required:a=!1,disabled:r=!1,readonly:n=!1,rows:l=3,class:o="",attributes:c={},help:d=e.helpText||e.help||""}=e,h=this.getFieldValue(e.name)??e.value??{};let f=h;if(typeof h=="object"&&h!==null)try{f=JSON.stringify(h,null,2)}catch{f="{}"}else typeof h!="string"&&(f=String(h));const m=`${this.options.inputClass} ${o}`.trim(),p=this.errors[t],g=this.getFieldId(t),w=Object.entries({...c,"data-field-type":"json"}).map(([b,C])=>`${b}="${this.escapeHtml(C)}"`).join(" "),y={labelClass:this.options.labelClass,inputClass:m,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:t,fieldValue:f,label:s?this.escapeHtml(s):null,placeholder:i?this.escapeHtml(i):null,help:d?this.escapeHtml(d):null,error:p?this.escapeHtml(p):null,rows:l||3,required:a,disabled:r,readonly:n,attrs:w};return E.render(this.templates.textarea,y)}renderSelectField(e){const{name:t,label:s,options:i=[],value:a="",required:r=!1,disabled:n=!1,multiple:l=!1,searchable:o=!1,class:c="",attributes:d={},help:h=e.helpText||e.help||"",start:f=e.start,end:m=e.end,step:p=e.step,format:g=e.format,prefix:w=e.prefix,suffix:y=e.suffix}=e,b=`form-select ${c}`.trim(),C=this.errors[t],S=this.getFieldValue(t)??a,T=Object.entries(d).map(([A,N])=>`${A}="${this.escapeHtml(N)}"`).join(" "),_=this.getFieldId(t);let P=[...i];if(f!==void 0&&m!==void 0){const A=p!==void 0?p:1,N=this.generateSelectOptions(f,m,A,{format:g,prefix:w,suffix:y});P=[...P,...N]}let ee="";Array.isArray(P)&&(ee=P.map(A=>{if(typeof A=="string"){const N=A===S?"selected":"";return`<option value="${this.escapeHtml(A)}" ${N}>${this.escapeHtml(A)}</option>`}else if(A&&typeof A=="object"){const N=A.value==S?"selected":"";return`<option value="${this.escapeHtml(A.value)}" ${N}>${this.escapeHtml(A.label||A.text||A.value)}</option>`}return""}).join(""));const pe=o?`
|
|
371
|
+
<input type="text"
|
|
372
|
+
class="form-control form-control-sm mb-2"
|
|
373
|
+
placeholder="Search options..."
|
|
374
|
+
data-filter="live-search"
|
|
375
|
+
data-change-action="filter-select-options"
|
|
376
|
+
data-target="${_}">
|
|
377
|
+
`:"",ge={labelClass:this.options.labelClass,inputClass:b,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:_,name:t,label:s?this.escapeHtml(s):null,help:h?this.escapeHtml(h):null,error:C?this.escapeHtml(C):null,searchInput:o?pe:null,optionsHTML:ee,required:r,disabled:n,multiple:l,attrs:T};return E.render(this.templates.select,ge)}renderMultiSelectField(e){const{name:t,label:s,options:i=[],value:a=[],required:r=!1,disabled:n=!1,maxHeight:l=300,help:o=e.helpText||e.help||""}=e,c=e.placeholder||e.placeHolder||"Select...";this.getFieldId(t);const d=this.errors[t],h=e.value??this.getFieldValue(t)??a;return`
|
|
378
|
+
<div class="multiselect-placeholder"
|
|
379
|
+
data-field-name="${t}"
|
|
380
|
+
data-field-type="multiselect"
|
|
381
|
+
data-field-config='${JSON.stringify({name:t,value:h,placeholder:c,maxHeight:l,disabled:n,required:r})}'>
|
|
382
|
+
<input type="hidden" name="${t}" value="${this.escapeHtml(JSON.stringify(h))}">
|
|
383
|
+
<select class="form-select${d?" is-invalid":""}"
|
|
384
|
+
multiple
|
|
385
|
+
${n?"disabled":""}
|
|
386
|
+
${r?"required":""}>
|
|
387
|
+
${i.map(f=>{const m=typeof f=="string"?f:f.value,p=typeof f=="string"?f:f.label||f.value,g=Array.isArray(h)&&h.includes(m)?"selected":"";return`<option value="${this.escapeHtml(m)}" ${g}>${this.escapeHtml(p)}</option>`}).join("")}
|
|
388
|
+
</select>
|
|
389
|
+
<small class="form-text text-muted">This will be enhanced with MultiSelectDropdown component</small>
|
|
390
|
+
</div>
|
|
391
|
+
`}renderCheckboxField(e){const{name:t,label:s,value:i=!1,required:a=!1,disabled:r=!1,class:n="",attributes:l={},help:o=e.helpText||e.help||""}=e,c=this.errors[t],d=this.getFieldValue(t)??i,h=d===!0||d==="true"||d==="1",f=Object.entries(l).map(([g,w])=>`${g}="${this.escapeHtml(w)}"`).join(" "),m=this.getFieldId(t),p={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:t,label:this.escapeHtml(s),help:o?this.escapeHtml(o):null,error:c?this.escapeHtml(c):null,value:this.escapeHtml(i),fieldClass:n,checked:h,required:a,disabled:r,attrs:f};return E.render(this.templates.checkbox,p)}renderSwitchField(e){const{name:t,label:s,value:i=!1,required:a=!1,disabled:r=!1,size:n="md",class:l="",attributes:o={},help:c=e.helpText||e.help||""}=e,d=this.errors[t],h=this.getFieldValue(t)??i,f=h===!0||h==="true"||h==="1",m=Object.entries(o).map(([y,b])=>`${y}="${this.escapeHtml(b)}"`).join(" "),p=this.getFieldId(t),g=n!=="md"?`form-switch-${n}`:"",w={helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:p,name:t,label:this.escapeHtml(s),help:c?this.escapeHtml(c):null,error:d?this.escapeHtml(d):null,value:this.escapeHtml(i),sizeClass:g,fieldClass:l,checked:f,required:a,disabled:r,attrs:m};return E.render(this.templates.switch,w)}renderRadioField(e){const{name:t,label:s,options:i=[],value:a="",disabled:r=!1,inline:n=!1,class:l="",attributes:o={},help:c=e.helpText||e.help||""}=e,d=this.errors[t],h=this.getFieldValue(t)??a,f=Object.entries(o).map(([p,g])=>`${p}="${this.escapeHtml(g)}"`).join(" ");let m="";return Array.isArray(i)&&(m=i.map((p,g)=>{const w=`${t}_${g}`,y=typeof p=="string"?p:p.value,b=typeof p=="string"?p:p.label||p.text||p.value,C=y===h?"checked":"";return`
|
|
392
|
+
<div class="form-check ${n?"form-check-inline":""}">
|
|
393
|
+
<input
|
|
394
|
+
type="radio"
|
|
395
|
+
id="${w}"
|
|
396
|
+
name="${t}"
|
|
397
|
+
class="form-check-input ${d?"is-invalid":""}"
|
|
398
|
+
value="${this.escapeHtml(y)}"
|
|
399
|
+
${C}
|
|
400
|
+
${r?"disabled":""}
|
|
401
|
+
|
|
402
|
+
${f}
|
|
403
|
+
>
|
|
404
|
+
<label class="form-check-label" for="${w}">
|
|
405
|
+
${this.escapeHtml(b)}
|
|
406
|
+
</label>
|
|
407
|
+
</div>
|
|
408
|
+
`}).join("")),`
|
|
409
|
+
<div class="mojo-form-control">
|
|
410
|
+
${s?`<fieldset>
|
|
411
|
+
<legend class="${this.options.labelClass}">${this.escapeHtml(s)}</legend>
|
|
412
|
+
<div class="${l}">
|
|
413
|
+
${m}
|
|
414
|
+
</div>
|
|
415
|
+
</fieldset>`:`<div class="${l}">${m}</div>`}
|
|
416
|
+
${c?`<div class="${this.options.helpClass}">${this.escapeHtml(c)}</div>`:""}
|
|
417
|
+
${d?`<div class="${this.options.errorClass}">${this.escapeHtml(d)}</div>`:""}
|
|
418
|
+
</div>
|
|
419
|
+
`}renderDateField(e){return this.renderInputField(e,"date")}renderDateTimeField(e){return this.renderInputField(e,"datetime-local")}renderTimeField(e){return this.renderInputField(e,"time")}renderFileField(e){const{name:t,label:s,required:i=!1,disabled:a=!1,multiple:r=!1,accept:n="*/*",class:l="",attributes:o={},help:c=e.helpText||e.help||""}=e,d=`${this.options.inputClass} ${l}`.trim(),h=this.errors[t],f=Object.entries(o).map(([g,w])=>`${g}="${this.escapeHtml(w)}"`).join(" "),m=this.getFieldId(t),p={labelClass:this.options.labelClass,inputClass:d,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:m,name:t,label:s?this.escapeHtml(s):null,help:c?this.escapeHtml(c):null,error:h?this.escapeHtml(h):null,accept:n,required:i,disabled:a,multiple:r,attrs:f};return E.render(this.templates.file,p)}renderImageField(e){const{name:t,label:s,required:i=!1,disabled:a=!1,accept:r="image/*",class:n="",attributes:l={},help:o=e.helpText||e.help||"",size:c="md",allowDrop:d=!0,placeholder:h="Drop image here or click to upload"}=e,f=`${this.options.inputClass} ${n}`.trim(),m=this.errors[t],p=this.getFieldId(t),g=`${p}_dropzone`,w=`${p}_preview`,y={xs:{width:48,height:48,containerClass:"image-field-xs"},sm:{width:96,height:96,containerClass:"image-field-sm"},md:{width:150,height:150,containerClass:"image-field-md"},lg:{width:200,height:200,containerClass:"image-field-lg"},xl:{width:300,height:300,containerClass:"image-field-xl"}},b=y[c]||y.md,C=Object.entries(l).map(([_,P])=>`${_}="${this.escapeHtml(P)}"`).join(" "),$=this.getFieldValue(t),S=this.extractImageUrl($,c),T={labelClass:this.options.labelClass,inputClass:f,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:p,name:t,label:s?this.escapeHtml(s):null,help:o?this.escapeHtml(o):null,error:m?this.escapeHtml(m):null,dropZoneId:g,previewId:w,containerClass:b.containerClass,width:b.width,height:b.height,accept:r,imageUrl:S,placeholderText:a?"No image":this.escapeHtml(h),cursor:a?"default":"pointer",allowDrop:d,showRemove:!a,required:i,disabled:a,attrs:C};return E.render(this.templates.image,T)}extractImageUrl(e,t="md"){if(!e)return null;if(typeof e=="string")return e;if(typeof e=="object"&&e.url){if(e.renditions){const s={xs:["thumbnail_sm","thumbnail","square_sm"],sm:["thumbnail","thumbnail_sm","square_sm"],md:["thumbnail_md","thumbnail","thumbnail_lg"],lg:["thumbnail_lg","thumbnail_md","thumbnail"],xl:["original","thumbnail_lg"]},i=s[t]||s.md;for(const a of i)if(e.renditions[a]&&e.renditions[a].url)return e.renditions[a].url}return e.url}return null}renderColorField(e){const{name:t,label:s,value:i="",placeholder:a="",required:r=!1,disabled:n=!1,readonly:l=!1,class:o="",attributes:c={},help:d=e.helpText||e.help||""}=e,h=`${this.options.inputClass} ${o}`.trim(),f=this.errors[t],m=this.getFieldValue(t)??i,p=Object.entries(c).map(([y,b])=>`${y}="${this.escapeHtml(b)}"`).join(" "),g=this.getFieldId(t),w={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:t,fieldValue:this.escapeHtml(m),label:s?this.escapeHtml(s):null,placeholder:a?this.escapeHtml(a):null,help:d?this.escapeHtml(d):null,error:f?this.escapeHtml(f):null,required:r,disabled:n,readonly:l,attrs:p};return E.render(this.templates.color,w)}renderRangeField(e){const{name:t,label:s,min:i=0,max:a=100,step:r=1,value:n=i,disabled:l=!1,class:o="",attributes:c={},help:d=e.helpText||e.help||""}=e,h=`${this.options.inputClass} ${o}`.trim(),f=this.errors[t],m=this.getFieldValue(t)??n,p=Object.entries(c).map(([y,b])=>`${y}="${this.escapeHtml(b)}"`).join(" "),g=this.getFieldId(t),w={labelClass:this.options.labelClass,inputClass:h,helpClass:this.options.helpClass,errorClass:this.options.errorClass,fieldId:g,name:t,label:s?this.escapeHtml(s):null,help:d?this.escapeHtml(d):null,error:f?this.escapeHtml(f):null,min:i,max:a,step:r,fieldValue:m,disabled:l,attrs:p};return E.render(this.templates.range,w)}renderHiddenField(e){const{name:t,value:s=""}=e,i=this.getFieldValue(t)??s;return`<input type="hidden" name="${t}" value="${this.escapeHtml(i)}">`}renderButton(e){const{name:t="",label:s="Button",type:i="button",action:a="",class:r="btn-secondary",disabled:n=!1,attributes:l={}}=e;let o=a;o||(i==="submit"?o="submit-form":i==="reset"&&(o="reset-form"));const c=Object.entries(l).map(([d,h])=>`${d}="${this.escapeHtml(h)}"`).join(" ");return`
|
|
420
|
+
<button
|
|
421
|
+
type="button"
|
|
422
|
+
${t?`name="${t}"`:""}
|
|
423
|
+
class="btn ${r}"
|
|
424
|
+
${o?`data-action="${o}"`:""}
|
|
425
|
+
${n?"disabled":""}
|
|
426
|
+
${c}
|
|
427
|
+
>
|
|
428
|
+
${this.escapeHtml(s)}
|
|
429
|
+
</button>
|
|
430
|
+
`}renderDivider(e){const{label:t="",class:s=""}=e;return`
|
|
431
|
+
<div class="form-divider ${s}">
|
|
432
|
+
<hr>
|
|
433
|
+
${t?`<div class="form-divider-label">${this.escapeHtml(t)}</div>`:""}
|
|
434
|
+
</div>
|
|
435
|
+
`}renderHtmlField(e){const{html:t="",class:s=""}=e;return`
|
|
436
|
+
<div class="form-html ${s}">
|
|
437
|
+
${t}
|
|
438
|
+
</div>
|
|
439
|
+
`}renderHeaderField(e){const{text:t="",level:s=3,class:i="",id:a=""}=e,r=Math.max(1,Math.min(6,parseInt(s))),n=a?` id="${this.escapeHtml(a)}"`:"",l=i?` class="${this.escapeHtml(i)}"`:"";return`<h${r}${n}${l}>${this.escapeHtml(t)}</h${r}>`}buildButtonsHTML(){if(!this.options.submitButton&&!this.options.resetButton&&!this.buttons.length)return"";let e="";if(this.buttons.forEach(t=>{e+=this.renderButton(t)+" "}),this.options.submitButton){let t="Submit";typeof this.options.submitButton=="string"?t=this.options.submitButton:this.options.submitButton===!0&&(t="Submit"),e+=`<button type="submit" class="btn btn-primary me-2" data-action="submit-form">${t}</button>`}if(this.options.resetButton){let t="Reset";typeof this.options.resetButton=="string"?t=this.options.resetButton:this.options.resetButton===!0&&(t="Reset"),e+=`<button type="button" class="btn btn-secondary" data-action="reset-form">${t}</button>`}return e?`
|
|
440
|
+
<div class="form-actions mt-3">
|
|
441
|
+
${e}
|
|
442
|
+
</div>
|
|
443
|
+
`:""}getFieldValue(e){return this.structureOnly?"":x.getContextData(this.data,e)}renderTagField(e){const{name:t,label:s,value:i="",placeholder:a="Add tags...",required:r=!1,disabled:n=!1,readonly:l=!1,maxTags:o=50,allowDuplicates:c=!1,separator:d=",",help:h=e.helpText||e.help||""}=e,f=this.getFieldId(t),m=this.errors[t],p=this.getFieldValue(t)??i;return`
|
|
444
|
+
<div class="mojo-form-control">
|
|
445
|
+
${s?`<label for="${f}" class="${this.options.labelClass}">${this.escapeHtml(s)}${r?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
446
|
+
<div class="tag-input-placeholder"
|
|
447
|
+
data-field-name="${t}"
|
|
448
|
+
data-field-type="tag"
|
|
449
|
+
data-field-config='${JSON.stringify({name:t,value:p,placeholder:a,maxTags:o,allowDuplicates:c,separator:d,disabled:n,readonly:l,required:r})}'>
|
|
450
|
+
<input type="text"
|
|
451
|
+
id="${f}"
|
|
452
|
+
name="${t}_display"
|
|
453
|
+
class="${this.options.inputClass}${m?" is-invalid":""}"
|
|
454
|
+
placeholder="${this.escapeHtml(a)}"
|
|
455
|
+
${n?"disabled":""}
|
|
456
|
+
${l?"readonly":""}
|
|
457
|
+
|
|
458
|
+
<input type="hidden" name="${t}" value="${this.escapeHtml(p)}">
|
|
459
|
+
<small class="form-text text-muted">This will be enhanced with TagInput component</small>
|
|
460
|
+
</div>
|
|
461
|
+
${h?`<div class="${this.options.helpClass}">${this.escapeHtml(h)}</div>`:""}
|
|
462
|
+
${m?`<div class="${this.options.errorClass}">${this.escapeHtml(m)}</div>`:""}
|
|
463
|
+
</div>
|
|
464
|
+
`}renderCollectionField(e){const{name:t,label:s,value:i="",placeholder:a="Search...",required:r=!1,disabled:n=!1,readonly:l=!1,Collection:o,labelField:c="name",valueField:d="id",maxItems:h=10,emptyFetch:f=!1,debounceMs:m=300,requiresActiveGroup:p=!1,help:g=e.helpText||e.help||""}=e,w=this.getFieldId(t),y=this.errors[t],b=this.getFieldValue(t)??i;return`
|
|
465
|
+
<div class="mojo-form-control">
|
|
466
|
+
${s?`<label for="${w}" class="${this.options.labelClass}">${this.escapeHtml(s)}${r?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
467
|
+
<div class="collection-select-placeholder"
|
|
468
|
+
data-field-name="${t}"
|
|
469
|
+
data-field-type="collection"
|
|
470
|
+
data-field-config='${JSON.stringify({name:t,value:b,placeholder:a,labelField:c,valueField:d,maxItems:h,emptyFetch:f,debounceMs:m,disabled:n,readonly:l,required:r,requiresActiveGroup:p})}'>
|
|
471
|
+
<input type="text"
|
|
472
|
+
id="${w}"
|
|
473
|
+
name="${t}_display"
|
|
474
|
+
class="${this.options.inputClass}${y?" is-invalid":""}"
|
|
475
|
+
placeholder="${this.escapeHtml(a)}"
|
|
476
|
+
${n?"disabled":""}
|
|
477
|
+
${l?"readonly":""}
|
|
478
|
+
|
|
479
|
+
<input type="hidden" name="${t}" value="${this.escapeHtml(b)}">
|
|
480
|
+
<small class="form-text text-muted">This will be enhanced with CollectionSelect component</small>
|
|
481
|
+
</div>
|
|
482
|
+
${g?`<div class="${this.options.helpClass}">${this.escapeHtml(g)}</div>`:""}
|
|
483
|
+
${y?`<div class="${this.options.errorClass}">${this.escapeHtml(y)}</div>`:""}
|
|
484
|
+
</div>
|
|
485
|
+
`}renderCollectionMultiSelectField(e){const{name:t,label:s,value:i=[],required:a=!1,disabled:r=!1,Collection:n,collectionParams:l={},labelField:o="name",valueField:c="id",excludeIds:d=[],ignoreIds:h=[],size:f=8,maxHeight:m=null,showSelectAll:p=!0,enableSearch:g=!1,searchPlaceholder:w="Search...",searchDebounce:y=400,requiresActiveGroup:b=!1,help:C=e.helpText||e.help||""}=e;this.getFieldId(t);const $=this.errors[t],S=this.getFieldValue(t)??i;return`
|
|
486
|
+
<div class="mojo-form-control">
|
|
487
|
+
${s?`<label class="${this.options.labelClass}">${this.escapeHtml(s)}${a?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
488
|
+
<div class="collection-multiselect-placeholder"
|
|
489
|
+
data-field-name="${t}"
|
|
490
|
+
data-field-type="collectionmultiselect"
|
|
491
|
+
data-field-config='${JSON.stringify({name:t,value:S,labelField:o,valueField:c,excludeIds:d,ignoreIds:h,size:f,maxHeight:m,showSelectAll:p,enableSearch:g,searchPlaceholder:w,searchDebounce:y,disabled:r,required:a,requiresActiveGroup:b})}'>
|
|
492
|
+
<input type="hidden" name="${t}" value="${this.escapeHtml(JSON.stringify(S))}">
|
|
493
|
+
<small class="form-text text-muted">This will be enhanced with CollectionMultiSelect component</small>
|
|
494
|
+
</div>
|
|
495
|
+
${C?`<div class="${this.options.helpClass}">${this.escapeHtml(C)}</div>`:""}
|
|
496
|
+
${$?`<div class="${this.options.errorClass}">${this.escapeHtml($)}</div>`:""}
|
|
497
|
+
</div>
|
|
498
|
+
`}renderDatePickerField(e){const{name:t,label:s,value:i="",placeholder:a="Select date...",required:r=!1,disabled:n=!1,readonly:l=!1,min:o=null,max:c=null,format:d="YYYY-MM-DD",displayFormat:h="MMM DD, YYYY",help:f=e.helpText||e.help||""}=e,m=this.getFieldId(t),p=this.errors[t],g=this.getFieldValue(t)??i;return`
|
|
499
|
+
<div class="mojo-form-control">
|
|
500
|
+
${s?`<label for="${m}" class="${this.options.labelClass}">${this.escapeHtml(s)}${r?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
501
|
+
<div class="date-picker-placeholder"
|
|
502
|
+
data-field-name="${t}"
|
|
503
|
+
data-field-type="datepicker"
|
|
504
|
+
data-field-config='${JSON.stringify({name:t,value:g,placeholder:a,min:o,max:c,format:d,displayFormat:h,disabled:n,readonly:l,required:r})}'>
|
|
505
|
+
<input type="date"
|
|
506
|
+
id="${m}"
|
|
507
|
+
name="${t}"
|
|
508
|
+
class="${this.options.inputClass}${p?" is-invalid":""}"
|
|
509
|
+
value="${this.escapeHtml(g)}"
|
|
510
|
+
placeholder="${this.escapeHtml(a)}"
|
|
511
|
+
${o?`min="${o}"`:""}
|
|
512
|
+
${c?`max="${c}"`:""}
|
|
513
|
+
${n?"disabled":""}
|
|
514
|
+
${l?"readonly":""}
|
|
515
|
+
${r?"required":""}
|
|
516
|
+
|
|
517
|
+
<small class="form-text text-muted">This will be enhanced with Easepick DatePicker</small>
|
|
518
|
+
</div>
|
|
519
|
+
${f?`<div class="${this.options.helpClass}">${this.escapeHtml(f)}</div>`:""}
|
|
520
|
+
${p?`<div class="${this.options.errorClass}">${this.escapeHtml(p)}</div>`:""}
|
|
521
|
+
</div>
|
|
522
|
+
`}renderDateRangeField(e){const{name:t,startName:s,endName:i,fieldName:a,label:r,startDate:n="",endDate:l="",placeholder:o="Select date range...",required:c=!1,disabled:d=!1,readonly:h=!1,min:f=null,max:m=null,format:p="YYYY-MM-DD",displayFormat:g="MMM DD, YYYY",outputFormat:w="date",separator:y=" - ",help:b=e.helpText||e.help||""}=e,C=this.getFieldId(t||s||"daterange"),$=this.errors[t],S=s||(t?t+"_start":""),T=i||(t?t+"_end":""),_=this.getFieldValue(S)||n,P=this.getFieldValue(T)||l;return`
|
|
523
|
+
<div class="mojo-form-control">
|
|
524
|
+
${r?`<label for="${C}" class="${this.options.labelClass}">${this.escapeHtml(r)}${c?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
525
|
+
<div class="date-range-picker-placeholder"
|
|
526
|
+
data-field-name="${t||s||"daterange"}"
|
|
527
|
+
data-field-type="daterange"
|
|
528
|
+
data-field-config='${JSON.stringify({name:t,startName:s,endName:i,fieldName:a,startDate:_,endDate:P,placeholder:o,min:f,max:m,format:p,displayFormat:g,outputFormat:w,separator:y,disabled:d,readonly:h,required:c})}'>
|
|
529
|
+
<div class="row g-2">
|
|
530
|
+
<div class="col">
|
|
531
|
+
<input type="date"
|
|
532
|
+
id="${C}_start"
|
|
533
|
+
name="${t}_start"
|
|
534
|
+
class="${this.options.inputClass}${$?" is-invalid":""}"
|
|
535
|
+
value="${this.escapeHtml(_)}"
|
|
536
|
+
placeholder="Start date..."
|
|
537
|
+
${f?`min="${f}"`:""}
|
|
538
|
+
${m?`max="${m}"`:""}
|
|
539
|
+
${d?"disabled":""}
|
|
540
|
+
${h?"readonly":""}
|
|
541
|
+
${c?"required":""}
|
|
542
|
+
|
|
543
|
+
</div>
|
|
544
|
+
<div class="col-auto d-flex align-items-center">
|
|
545
|
+
<span class="text-muted">${this.escapeHtml(y.trim())}</span>
|
|
546
|
+
</div>
|
|
547
|
+
<div class="col">
|
|
548
|
+
<input type="date"
|
|
549
|
+
id="${C}_end"
|
|
550
|
+
name="${t}_end"
|
|
551
|
+
class="${this.options.inputClass}${$?" is-invalid":""}"
|
|
552
|
+
value="${this.escapeHtml(P)}"
|
|
553
|
+
placeholder="End date..."
|
|
554
|
+
${f?`min="${f}"`:""}
|
|
555
|
+
${m?`max="${m}"`:""}
|
|
556
|
+
${d?"disabled":""}
|
|
557
|
+
${h?"readonly":""}
|
|
558
|
+
${c?"required":""}
|
|
559
|
+
|
|
560
|
+
</div>
|
|
561
|
+
</div>
|
|
562
|
+
<small class="form-text text-muted">This will be enhanced with Easepick DateRangePicker</small>
|
|
563
|
+
</div>
|
|
564
|
+
${b?`<div class="${this.options.helpClass}">${this.escapeHtml(b)}</div>`:""}
|
|
565
|
+
${$?`<div class="${this.options.errorClass}">${this.escapeHtml($)}</div>`:""}
|
|
566
|
+
</div>
|
|
567
|
+
`}renderChecklistDropdownField(e){const t=this.getFieldId(e.name),s=this.getFieldValue(e.name)??[],i={fieldId:t,fieldName:e.name,buttonText:e.buttonText||"Select Options",buttonIcon:e.buttonIcon||"bi-chevron-down",buttonClass:e.buttonClass||"btn btn-outline-secondary btn-sm dropdown-toggle",dropdownClass:e.dropdownClass||"dropdown-menu p-2",minWidth:e.minWidth||"200px",options:e.options.map(a=>({value:a.value,label:a.label,id:`${e.name}-${a.value}`,checked:s.includes(a.value)}))};return E.render(this.templates.checklistdropdown,i)}renderButtonGroupField(e){const t=this.getFieldId(e.name),s=this.getFieldValue(e.name)??e.value,i={fieldId:t,fieldName:e.name,size:e.size||"sm",variant:e.variant||"outline-primary",options:e.options.map(a=>({value:a.value,label:a.label,action:a.action,active:a.value===s,buttonClass:this.getButtonClass(a.value===s,e.variant)}))};return E.render(this.templates.buttongroup,i)}getButtonClass(e,t="outline-primary"){return e?`btn btn-${t.replace("outline-","")}`:`btn btn-${t}`}renderComboField(e){const{name:t,label:s,value:i="",required:a=!1,disabled:r=!1,maxHeight:n=300,help:l=e.helpText||e.help||""}=e,o=e.placeholder||e.placeHolder||"Type or select...",c=e.allowCustom!==!1;this.getFieldId(t);const d=this.errors[t],h=e.value??this.getFieldValue(t)??i;return`
|
|
568
|
+
<div class="mojo-form-control">
|
|
569
|
+
${s?`<label class="${this.options.labelClass}">${this.escapeHtml(s)}${a?'<span class="text-danger">*</span>':""}</label>`:""}
|
|
570
|
+
<div class="combobox-placeholder"
|
|
571
|
+
data-field-name="${t}"
|
|
572
|
+
data-field-type="combobox"
|
|
573
|
+
data-field-config='${JSON.stringify({name:t,value:h,placeholder:o,maxHeight:n,allowCustom:c,disabled:r,required:a})}'>
|
|
574
|
+
<input type="text"
|
|
575
|
+
class="form-control${d?" is-invalid":""}"
|
|
576
|
+
value="${this.escapeHtml(h)}"
|
|
577
|
+
placeholder="${this.escapeHtml(o)}"
|
|
578
|
+
${r?"disabled":""}
|
|
579
|
+
${a?"required":""}>
|
|
580
|
+
<small class="form-text text-muted">This will be enhanced with ComboBox component</small>
|
|
581
|
+
</div>
|
|
582
|
+
${l?`<div class="${this.options.helpClass}">${this.escapeHtml(l)}</div>`:""}
|
|
583
|
+
${d?`<div class="${this.options.errorClass}">${this.escapeHtml(d)}</div>`:""}
|
|
584
|
+
</div>
|
|
585
|
+
`}renderTabsetField(e){const{tabs:t=[],name:s=`tabset-${Date.now()}`,navClass:i="nav nav-tabs mb-3",contentClass:a="tab-content"}=e,r=String(s).toLowerCase().replace(/[^a-z0-9]/g,"-"),n=t.map((o,c)=>{const d=`${r}-pane-${c}`,h=c===0;return`
|
|
586
|
+
<li class="nav-item" role="presentation">
|
|
587
|
+
<button class="nav-link ${h?"active":""}"
|
|
588
|
+
id="${d}-tab"
|
|
589
|
+
data-bs-toggle="tab"
|
|
590
|
+
data-bs-target="#${d}"
|
|
591
|
+
type="button"
|
|
592
|
+
role="tab"
|
|
593
|
+
aria-controls="${d}"
|
|
594
|
+
aria-selected="${h}">
|
|
595
|
+
${this.escapeHtml(o.label||`Tab ${c+1}`)}
|
|
596
|
+
</button>
|
|
597
|
+
</li>
|
|
598
|
+
`}).join(""),l=t.map((o,c)=>{const d=`${r}-pane-${c}`,h=c===0,f=(o.fields||[]).map(m=>m.type==="group"?this.buildGroupHTML(m):this.buildFieldHTML(m)).join("");return`
|
|
599
|
+
<div class="tab-pane fade ${h?"show active":""}"
|
|
600
|
+
id="${d}"
|
|
601
|
+
role="tabpanel"
|
|
602
|
+
aria-labelledby="${d}-tab"
|
|
603
|
+
data-tab-index="${c}">
|
|
604
|
+
<div class="row">
|
|
605
|
+
${f}
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
`}).join("");return`
|
|
609
|
+
<div class="mojo-form-tabset">
|
|
610
|
+
<ul class="${i}" role="tablist">
|
|
611
|
+
${n}
|
|
612
|
+
</ul>
|
|
613
|
+
<div class="${a}">
|
|
614
|
+
${l}
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
`}generateSelectOptions(e,t,s=1,i={}){const{format:a,prefix:r="",suffix:n=""}=i,l=[],o=e<=t?Math.abs(s):-Math.abs(s);for(let c=e;e<=t?c<=t:c>=t;c+=o){let d=String(c);if(typeof a=="function")d=a(c);else if(a==="padded"||a==="pad"){const h=String(Math.max(Math.abs(e),Math.abs(t))).length;d=String(c).padStart(h,"0")}else a==="ordinal"&&(d=this.formatOrdinal(c));d=`${r}${d}${n}`,l.push({value:c,label:d})}return l}formatOrdinal(e){const t=e%10,s=e%100;return t===1&&s!==11?e+"st":t===2&&s!==12?e+"nd":t===3&&s!==13?e+"rd":e+"th"}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}}const Le={enableFileDrop(u={}){if(this._fileDropConfig={acceptedTypes:u.acceptedTypes||["*/*"],maxFileSize:u.maxFileSize||10*1024*1024,dropZoneSelector:u.dropZoneSelector||null,visualFeedback:u.visualFeedback!==!1,multiple:u.multiple||!1,validateOnDrop:u.validateOnDrop!==!1,dragOverClass:u.dragOverClass||"drag-over",dragActiveClass:u.dragActiveClass||"drag-active"},this._fileDropState={isDragActive:!1,dragCounter:0},this._boundFileDropHandlers={dragEnter:this._onFileDropDragEnter.bind(this),dragOver:this._onFileDropDragOver.bind(this),dragLeave:this._onFileDropDragLeave.bind(this),drop:this._onFileDropDrop.bind(this),preventDefault:this._onFileDropPreventDefault.bind(this)},this.element)this._setupFileDropListeners();else{const t=this.onAfterRender.bind(this);this.onAfterRender=async()=>{await t(),this._setupFileDropListeners()}}const e=this.onBeforeDestroy.bind(this);this.onBeforeDestroy=async()=>{this._cleanupFileDropListeners(),await e()}},_setupFileDropListeners(){if(!this._fileDropConfig)return;const u=this._getFileDropZone();if(!u){console.warn("FileDropMixin: Drop zone not found");return}this._fileDropZone=u,u.addEventListener("dragenter",this._boundFileDropHandlers.dragEnter),u.addEventListener("dragover",this._boundFileDropHandlers.dragOver),u.addEventListener("dragleave",this._boundFileDropHandlers.dragLeave),u.addEventListener("drop",this._boundFileDropHandlers.drop),["dragenter","dragover","dragleave","drop"].forEach(e=>{document.addEventListener(e,this._boundFileDropHandlers.preventDefault)})},_cleanupFileDropListeners(){this._boundFileDropHandlers&&(this._fileDropZone&&(this._fileDropZone.removeEventListener("dragenter",this._boundFileDropHandlers.dragEnter),this._fileDropZone.removeEventListener("dragover",this._boundFileDropHandlers.dragOver),this._fileDropZone.removeEventListener("dragleave",this._boundFileDropHandlers.dragLeave),this._fileDropZone.removeEventListener("drop",this._boundFileDropHandlers.drop)),["dragenter","dragover","dragleave","drop"].forEach(u=>{document.removeEventListener(u,this._boundFileDropHandlers.preventDefault)}),this._fileDropZone=null,this._boundFileDropHandlers=null,this._fileDropConfig=null,this._fileDropState=null)},_getFileDropZone(){return this._fileDropConfig.dropZoneSelector?this.element.querySelector(this._fileDropConfig.dropZoneSelector):this.element},_onFileDropPreventDefault(u){u.preventDefault(),u.stopPropagation()},_onFileDropDragEnter(u){this._onFileDropPreventDefault(u),this._fileDropState.dragCounter++,this._fileDropState.isDragActive||(this._fileDropState.isDragActive=!0,this._applyFileDropVisualFeedback(!0))},_onFileDropDragOver(u){this._onFileDropPreventDefault(u),u.dataTransfer.dropEffect="copy"},_onFileDropDragLeave(u){this._onFileDropPreventDefault(u),this._fileDropState.dragCounter--,this._fileDropState.dragCounter<=0&&(this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1))},async _onFileDropDrop(u){this._onFileDropPreventDefault(u),this._fileDropState.isDragActive=!1,this._fileDropState.dragCounter=0,this._applyFileDropVisualFeedback(!1);const e=Array.from(u.dataTransfer.files);if(e.length===0)return;const t=this._fileDropConfig.multiple?e:[e[0]];let s={valid:!0,errors:[]};if(this._fileDropConfig.validateOnDrop&&(s=this._validateFileDropFiles(t),!s.valid)){typeof this.onFileDropError=="function"&&await this.onFileDropError(new Error(s.errors.join(", ")),u,t);return}if(typeof this.onFileDrop=="function")try{await this.onFileDrop(t,u,s)}catch(i){typeof this.onFileDropError=="function"?await this.onFileDropError(i,u,t):console.error("FileDropMixin: Error in onFileDrop callback:",i)}else console.warn("FileDropMixin: No onFileDrop method found on view")},_applyFileDropVisualFeedback(u){if(!this._fileDropConfig.visualFeedback||!this._fileDropZone)return;const{dragOverClass:e,dragActiveClass:t}=this._fileDropConfig;u?this._fileDropZone.classList.add(e,t):this._fileDropZone.classList.remove(e,t)},_validateFileDropFiles(u){const e=[],t=this._fileDropConfig;for(const s of u){if(!this._isFileDropTypeAccepted(s.type)){e.push(`File type "${s.type}" is not accepted for file "${s.name}"`);continue}s.size>t.maxFileSize&&e.push(`File "${s.name}" (${this._formatFileDropSize(s.size)}) exceeds maximum size (${this._formatFileDropSize(t.maxFileSize)})`)}return{valid:e.length===0,errors:e}},_isFileDropTypeAccepted(u){const{acceptedTypes:e}=this._fileDropConfig;return e.includes("*/*")?!0:e.some(t=>{if(t===u)return!0;if(t.endsWith("/*")){const s=t.split("/")[0];return u.startsWith(s+"/")}return!1})},_formatFileDropSize(u){if(u===0)return"0 Bytes";const e=1024,t=["Bytes","KB","MB","GB"],s=Math.floor(Math.log(u)/Math.log(e));return parseFloat((u/Math.pow(e,s)).toFixed(2))+" "+t[s]}};function _e(u){Object.assign(u.prototype,Le)}class le extends v{constructor(e={}){const{name:t,value:s="",placeholder:i="Add tags...",maxTags:a=50,allowDuplicates:r=!1,separator:n=",",trimTags:l=!0,minLength:o=1,maxLength:c=50,disabled:d=!1,readonly:h=!1,class:f="",tagClass:m="badge bg-primary",inputClass:p="form-control",...g}=e;super({tagName:"div",className:`tag-input-view ${f}`,...g}),this.name=t,this.placeholder=i,this.maxTags=a,this.allowDuplicates=r,this.separator=n,this.trimTags=l,this.minLength=o,this.maxLength=c,this.disabled=d,this.readonly=h,this.tagClass=m,this.inputClass=p,this.tags=[],this.focusedTagIndex=-1,s&&(this.tags=this.parseTagString(s))}async renderTemplate(){const e=this.renderTags(),t=this.renderHiddenInput(),s=this.renderInput();return`
|
|
618
|
+
<div class="tag-input-container">
|
|
619
|
+
<div class="tag-input-wrapper border rounded p-2"
|
|
620
|
+
data-action="focus-input"
|
|
621
|
+
tabindex="0"
|
|
622
|
+
role="combobox"
|
|
623
|
+
aria-expanded="false"
|
|
624
|
+
aria-label="Tag input">
|
|
625
|
+
<div class="tags-container d-flex flex-wrap gap-1 mb-2">
|
|
626
|
+
${e}
|
|
627
|
+
</div>
|
|
628
|
+
${s}
|
|
629
|
+
</div>
|
|
630
|
+
${t}
|
|
631
|
+
<div class="tag-input-feedback small text-muted mt-1">
|
|
632
|
+
<span class="tag-count">${this.tags.length}</span>/${this.maxTags} tags
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
`}renderTags(){return this.tags.map((e,t)=>`
|
|
636
|
+
<span class="${this.tagClass} tag-item"
|
|
637
|
+
data-tag-index="${t}"
|
|
638
|
+
tabindex="0"
|
|
639
|
+
role="button"
|
|
640
|
+
aria-label="Tag: ${this.escapeHtml(e)}. Press Delete or Backspace to remove.">
|
|
641
|
+
<span class="tag-text">${this.escapeHtml(e)}</span>
|
|
642
|
+
${!this.readonly&&!this.disabled?`
|
|
643
|
+
<i class="bi bi-x tag-remove ms-1"
|
|
644
|
+
data-action="remove-tag"
|
|
645
|
+
data-tag-index="${t}"
|
|
646
|
+
aria-label="Remove tag"></i>
|
|
647
|
+
`:""}
|
|
648
|
+
</span>
|
|
649
|
+
`).join("")}renderInput(){return this.readonly?"":`
|
|
650
|
+
<input type="text"
|
|
651
|
+
class="${this.inputClass} tag-input-field border-0 p-0"
|
|
652
|
+
placeholder="${this.escapeHtml(this.placeholder)}"
|
|
653
|
+
${this.disabled?"disabled":""}
|
|
654
|
+
data-change-action="input-change"
|
|
655
|
+
style="outline: none; box-shadow: none; min-width: 120px;"
|
|
656
|
+
autocomplete="off">
|
|
657
|
+
`}renderHiddenInput(){return this.name?`
|
|
658
|
+
<input type="hidden"
|
|
659
|
+
name="${this.name}"
|
|
660
|
+
value="${this.escapeHtml(this.getTagString())}"
|
|
661
|
+
class="tag-input-hidden">
|
|
662
|
+
`:""}async onAfterRender(){await super.onAfterRender(),this.updateTagCount()}async onActionFocusInput(e,t){this.focus()}focus(){const e=this.element.querySelector(".tag-input-field");e&&!this.disabled&&e.focus(),this.focusedTagIndex=-1}async onActionRemoveTag(e,t){e.stopPropagation();const s=parseInt(t.getAttribute("data-tag-index"));s>=0&&s<this.tags.length&&await this.removeTag(s)}async onChangeInputChange(e,t){const s=t.value,i=s.slice(-1);if(i===this.separator||i===`
|
|
663
|
+
`){e.preventDefault();const a=s.slice(0,-1);a.trim()&&(await this.addTag(a),t.value="");return}}bindEvents(){this.__bnd_keydown||(this.__bnd_keydown=this.handleInputKeydown.bind(this)),this.element.addEventListener("keydown",this.__bnd_keydown),this.events.bind(this.element)}unbindEvents(){this.__bnd_keydown&&this.element.removeEventListener("keydown",this.__bnd_keydown),this.events.unbind()}handleInputKeydown(e){const t=e.target,s=t.value||"";switch(e.key){case"Enter":case"Tab":case",":s.trim()&&(e.preventDefault(),this.addTag(s),t.value="");break;case"Backspace":s===""&&this.tags.length>0&&(e.preventDefault(),this.focusedTagIndex>=0?(this.removeTag(this.focusedTagIndex),this.focusedTagIndex==0?this.focus():this.focusTag(this.focusedTagIndex-1)):this.removeTag(this.tags.length-1));break;case"ArrowLeft":if(s===""&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const i=this.focusedTagIndex-1;i>=0?this.focusTag(i):this.focus()}else this.focusTag(this.tags.length-1);break;case"ArrowRight":if(s===""&&this.tags.length>0)if(e.preventDefault(),this.focusedTagIndex>=0){const i=this.focusedTagIndex+1;i<this.tags.length?this.focusTag(i):this.focus()}else this.focusTag(0);break;case"Escape":t.value="",t.blur();break}}async addTag(e){if(this.readonly||this.disabled)return!1;const t=this.trimTags?e.trim():e;return this.isValidTag(t)?!this.allowDuplicates&&this.tags.includes(t)?(this.showTagError(`Tag "${t}" already exists`),!1):this.tags.length>=this.maxTags?(this.showTagError(`Maximum ${this.maxTags} tags allowed`),!1):(this.tags.push(t),await this.updateDisplay(),this.emit("tag:added",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),!0):!1}async removeTag(e){if(this.readonly||this.disabled)return!1;if(e>=0&&e<this.tags.length){const t=this.tags[e];return this.tags.splice(e,1),await this.updateDisplay(),this.emit("tag:removed",{tag:t,tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags}),!0}return!1}async removeTagByValue(e){const t=this.tags.indexOf(e);return t>=0?await this.removeTag(t):!1}async clearTags(){if(this.readonly||this.disabled)return!1;const e=[...this.tags];return this.tags=[],await this.updateDisplay(),this.emit("tags:cleared",{oldTags:e}),this.emit("change",{value:"",tags:[]}),!0}async setTags(e){let t=[];Array.isArray(e)?t=e:typeof e=="string"&&(t=this.parseTagString(e)),t=t.filter(s=>this.isValidTag(s)).slice(0,this.maxTags),this.allowDuplicates||(t=[...new Set(t)]),this.tags=t,await this.updateDisplay(),this.emit("tags:set",{tags:this.tags}),this.emit("change",{value:this.getTagString(),tags:this.tags})}isValidTag(e){return!(typeof e!="string"||e.length<this.minLength||e.length>this.maxLength||e.trim()==="")}parseTagString(e){return e?e.split(this.separator).map(t=>this.trimTags?t.trim():t).filter(t=>t.length>0):[]}getTagString(){return this.tags.join(this.separator)}getTags(){return[...this.tags]}focusTag(e){const t=this.element.querySelectorAll(".tag-item");t[e]&&(this.focusedTagIndex=e,console.log(`Focused tag index: ${e}`),t[e].focus())}async updateDisplay(){const e=this.element.querySelector(".tags-container");e&&(e.innerHTML=this.renderTags());const t=this.element.querySelector(".tag-input-hidden");t&&(t.value=this.getTagString()),this.updateTagCount()}updateTagCount(){const e=this.element.querySelector(".tag-count");e&&(e.textContent=this.tags.length)}showTagError(e){let t=this.element.querySelector(".tag-error");if(!t){t=document.createElement("div"),t.className="tag-error small text-danger mt-1";const s=this.element.querySelector(".tag-input-feedback");s&&s.parentNode.insertBefore(t,s.nextSibling)}t.textContent=e,setTimeout(()=>{t.parentNode&&t.remove()},3e3)}setEnabled(e){this.disabled=!e;const t=this.element.querySelector(".tag-input-field");t&&(t.disabled=this.disabled);const s=this.element.querySelector(".tag-input-wrapper");s&&s.classList.toggle("disabled",this.disabled)}setReadonly(e){this.readonly=e;const t=this.element.querySelector(".tag-input-field");t&&(t.style.display=e?"none":""),this.element.querySelectorAll(".tag-remove").forEach(i=>{i.style.display=e?"none":""})}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}getFormValue(){return this.getTagString()}async setFormValue(e){await this.setTags(e)}static create(e={}){return new le(e)}}class Pe extends v{constructor(e={}){super({tagName:"div",className:"collection-dropdown-view dropdown-menu show w-100 position-absolute",style:"max-height: 250px; overflow-y: auto; z-index: 1000;",template:`
|
|
664
|
+
{{#data.loading}}
|
|
665
|
+
<div class="dropdown-item text-center">
|
|
666
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
667
|
+
<span class="visually-hidden">Loading...</span>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
{{/data.loading}}
|
|
671
|
+
|
|
672
|
+
{{^data.loading}}
|
|
673
|
+
{{#data.items}}
|
|
674
|
+
<button type="button"
|
|
675
|
+
class="dropdown-item {{#isSelected}}active{{/isSelected}} {{#isFocused}}bg-light{{/isFocused}}"
|
|
676
|
+
data-action="select-item"
|
|
677
|
+
data-value="{{valueField}}"
|
|
678
|
+
data-label="{{labelField}}"
|
|
679
|
+
data-index="{{index}}">
|
|
680
|
+
{{labelField}}
|
|
681
|
+
</button>
|
|
682
|
+
{{/data.items}}
|
|
683
|
+
|
|
684
|
+
{{#data.showNoResults}}
|
|
685
|
+
<div class="dropdown-item text-muted">
|
|
686
|
+
{{#data.hasSearched}}No results found{{/data.hasSearched}}
|
|
687
|
+
{{^data.hasSearched}}Start typing to search...{{/data.hasSearched}}
|
|
688
|
+
</div>
|
|
689
|
+
{{/data.showNoResults}}
|
|
690
|
+
{{/data.loading}}
|
|
691
|
+
`,...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.selectedValue=e.selectedValue||"",this.loading=e.loading||!1,this.hasSearched=e.hasSearched||!1,this.focusedIndex=e.focusedIndex||-1}async getViewData(){const e=this.collection?this.collection.toJSON().map((t,s)=>{const i=x.getNestedValue(t,this.labelField),a=x.getNestedValue(t,this.valueField);return{...t,labelField:i,valueField:a,isSelected:a==this.selectedValue,isFocused:s===this.focusedIndex,index:s}}):[];return{loading:this.loading,hasSearched:this.hasSearched,showNoResults:!this.loading&&this.hasSearched&&e.length===0,items:e}}async handleActionSelectItem(e,t){e.preventDefault();const s=t.getAttribute("data-value"),i=t.getAttribute("data-label");this.emit("item-selected",{value:s,label:i})}updateState(e){Object.assign(this,e)}updateFocusedItem(e){this.focusedIndex=e,this.element?.querySelectorAll('.dropdown-item[data-action="select-item"]')?.forEach((s,i)=>{s.classList.toggle("bg-light",i===this.focusedIndex)})}getItemCount(){return this.collection?this.collection.length():0}getFocusedItem(){return this.focusedIndex>=0&&this.collection&&this.collection.toJSON()[this.focusedIndex]||null}}class Ne extends v{constructor(e={}){super({className:"collection-select-view",template:`
|
|
692
|
+
<div class="position-relative">
|
|
693
|
+
<input type="text"
|
|
694
|
+
class="form-control {{#data.hasError}}is-invalid{{/data.hasError}} {{#data.showClear}}pe-5{{/data.showClear}}"
|
|
695
|
+
placeholder="{{data.placeholder}}"
|
|
696
|
+
value="{{data.displayValue}}"
|
|
697
|
+
autocomplete="off" />
|
|
698
|
+
|
|
699
|
+
<input type="hidden"
|
|
700
|
+
name="{{data.name}}"
|
|
701
|
+
value="{{data.selectedValue}}" />
|
|
702
|
+
|
|
703
|
+
{{#data.showClear}}
|
|
704
|
+
<button type="button"
|
|
705
|
+
class="btn btn-link position-absolute top-50 end-0 translate-middle-y me-2 p-0 border-0"
|
|
706
|
+
style="z-index: 10; color: #6c757d;"
|
|
707
|
+
data-action="clear-selection"
|
|
708
|
+
title="Clear selection">
|
|
709
|
+
<i class="bi bi-x-circle"></i>
|
|
710
|
+
</button>
|
|
711
|
+
{{/data.showClear}}
|
|
712
|
+
|
|
713
|
+
<div class="dropdown-container"></div>
|
|
714
|
+
|
|
715
|
+
{{#data.hasError}}
|
|
716
|
+
<div class="invalid-feedback">{{data.errorMessage}}</div>
|
|
717
|
+
{{/data.hasError}}
|
|
718
|
+
</div>
|
|
719
|
+
`,...e}),this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.maxItems=e.maxItems||10,this.placeholder=e.placeholder||"Search...",this.debounceMs=e.debounceMs||400,this.name=e.name||"collection_select",this.emptyFetch=e.emptyFetch!==!1,this.requiresActiveGroup=e.requiresActiveGroup||!1,this.selectedValue=e.value||"0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.loading=!1,this.hasSearched=!1,this.focusedIndex=-1,this.hasError=!1,this.errorMessage="",this.selectedValue&&typeof this.selectedValue=="object"&&(this.selectedLabel=x.getNestedValue(this.selectedValue,this.labelField)||"",this.selectedValue=x.getNestedValue(this.selectedValue,this.valueField)||"0"),this.searchTimer=null,this.dropdownView=null,this.defaultParams={},this.defaultParamsOption=e.defaultParams||null,this.handleDocumentClick=this.handleDocumentClick.bind(this),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleInputEvents=this.handleInputEvents.bind(this)}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.defaultParams={...this.collection.params},this.collection.params.size=this.maxItems,this.defaultParams.size=this.maxItems,this.defaultParamsOption){const e=typeof this.defaultParamsOption=="function"?this.defaultParamsOption():this.defaultParamsOption;e&&typeof e=="object"&&(Object.assign(this.defaultParams,e),Object.assign(this.collection.params,e))}if(this.requiresActiveGroup){const e=this.getApp();e&&e.activeGroup&&e.activeGroup.id&&(this.collection.params.group=e.activeGroup.id,this.defaultParams.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.showDropdown=!0,this.updateDropdown()}),this.collection.on("fetch:end",()=>{this.loading=!1,this.showDropdown=!0,this.updateDropdown()}),this.selectedValue&&this.loadSelectedItem(),this.emptyFetch&&this.collection.isEmpty()&&this.performInitialFetch()}async performInitialFetch(){if(this.collection)try{const e={...this.defaultParams};delete e.search,await this.collection.updateParams(e,!0)}catch(e){console.error("Initial fetch error:",e)}}async loadSelectedItem(){try{if(!this.selectedValue||this.selectedValue=="0"||this.selectedLabel)return;const e=this.collection?.get(this.selectedValue);if(e){this.selectedLabel=this.getFieldValue(e,this.labelField),this.render(!1);return}let t=await this.collection.fetchOne(this.selectedValue);t&&(this.selectedLabel=this.getFieldValue(t,this.labelField)||`${t.constructor.name} #${t.id}`,this.render(!1))}catch(e){console.error("Error loading selected item:",e)}}async getViewData(){let e="";return this.showDropdown&&this.hasSearched?e=this.searchValue:this.selectedValue&&this.selectedLabel&&(e=this.selectedLabel),{name:this.name,placeholder:this.placeholder,displayValue:e,selectedValue:this.selectedValue,showClear:!!(this.selectedValue&&this.selectedValue!=="0"&&this.selectedLabel),hasError:this.hasError,errorMessage:this.errorMessage}}async onAfterRender(){await super.onAfterRender();const e=this.getInput();e&&(e.addEventListener("input",this.handleInputEvents),e.addEventListener("focus",this.handleInputEvents),e.addEventListener("keydown",this.handleKeyDown)),document.addEventListener("click",this.handleDocumentClick),this.createDropdownView()}async onBeforeDestroy(){await super.onBeforeDestroy();const e=this.getInput();e&&(e.removeEventListener("input",this.handleInputEvents),e.removeEventListener("focus",this.handleInputEvents),e.removeEventListener("keydown",this.handleKeyDown)),document.removeEventListener("click",this.handleDocumentClick),this.searchTimer&&clearTimeout(this.searchTimer),this.dropdownView&&this.dropdownView.destroy()}createDropdownView(){this.dropdownView&&this.dropdownView.destroy(),this.dropdownView=new Pe({collection:this.collection,labelField:this.labelField,valueField:this.valueField,selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.dropdownView.on("item-selected",e=>{this.selectItem(e.value,e.label)})}async handleInputEvents(e){const t=e.target;e.type==="focus"?(this.showDropdown=!0,!this.hasSearched&&this.emptyFetch&&this.collection?.isEmpty()&&this.performInitialFetch(),this.updateDropdown()):e.type==="input"&&(this.searchValue=t.value,this.showDropdown=!0,this.hasSearched=!0,this.focusedIndex=-1,this.searchValue!==this.selectedLabel&&(this.selectedValue="0",this.selectedLabel="",this.emit("change",{value:"0",label:""})),this.searchTimer&&clearTimeout(this.searchTimer),this.searchTimer=setTimeout(()=>{this.performSearch()},this.debounceMs),this.updateDropdown())}async handleActionClearSelection(e,t){e.preventDefault(),e.stopPropagation(),this.clearSelection()}clearSelection(){this.selectedValue="0",this.selectedLabel="",this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const e=this.getInput();e&&(e.value="",e.focus());const t=this.getHiddenInput();t&&(t.value="0"),this.updateDropdown(),this.render(),this.emit("change",{value:"0",label:""})}async performSearch(){if(this.collection)try{const e={...this.defaultParams};this.searchValue&&this.searchValue.trim()&&(e.search=this.searchValue.trim()),await this.collection.updateParams(e,!0)}catch(e){console.error("Search error:",e),this.loading=!1,this.updateDropdown()}}updateDropdown(){if(this.dropdownView)if(this.dropdownView.updateState({selectedValue:this.selectedValue,loading:this.loading,hasSearched:this.hasSearched,focusedIndex:this.focusedIndex}),this.showDropdown)if(this.dropdownView.isMounted())this.dropdownView.render();else{const e=this.element?.querySelector(".dropdown-container");e&&this.dropdownView.render(!0,e)}else this.dropdownView.isMounted()&&(this.dropdownView.destroy(),this.createDropdownView())}selectItem(e,t){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.showDropdown=!1,this.hasError=!1,this.focusedIndex=-1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e),this.updateDropdown(),this.emit("change",{value:e,label:t})}handleDocumentClick(e){this.element?.contains(e.target)||(this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown())}handleKeyDown(e){if(!this.showDropdown||!this.collection)return;const t=this.dropdownView?.getItemCount()||0;switch(e.key){case"ArrowDown":e.preventDefault(),this.focusedIndex=Math.min(this.focusedIndex+1,t-1),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"ArrowUp":e.preventDefault(),this.focusedIndex=Math.max(this.focusedIndex-1,0),this.dropdownView?.updateFocusedItem(this.focusedIndex);break;case"Enter":{e.preventDefault();const s=this.dropdownView?.getFocusedItem();s&&this.selectItem(s[this.valueField],s[this.labelField]);break}case"Escape":e.preventDefault(),this.showDropdown=!1,this.focusedIndex=-1,this.updateDropdown();break}}getInput(){return this.element?.querySelector('input[type="text"]')}getHiddenInput(){return this.element?.querySelector('input[type="hidden"]')}setValue(e,t=""){this.selectedValue=e,this.selectedLabel=t,this.searchValue="",this.hasError=!1,this.hasSearched=!1;const s=this.getInput();s&&(s.value=t);const i=this.getHiddenInput();i&&(i.value=e)}getValue(){return this.selectedValue===0||this.selectedValue==="0"?null:this.selectedValue}getLabel(){return this.selectedLabel}setError(e){this.hasError=!0,this.errorMessage=e,this.render()}clearError(){this.hasError=!1,this.errorMessage="",this.render()}focus(){const e=this.getInput();e&&e.focus()}getFormValue(){return this.selectedValue===0||this.selectedValue==="0"?null:this.selectedValue}setFormValue(e){let t=e,s="";t&&typeof t=="object"&&(s=x.getNestedValue(t,this.labelField)||"",t=x.getNestedValue(t,this.valueField)),t=t||"0",t!=this.selectedValue&&(this.selectedValue=t,this.selectedLabel=s,this.searchValue="",this.hasSearched=!1,this.showDropdown=!1,this.hasError=!1,this.selectedValue&&this.selectedValue!=="0"?(this.selectedLabel||(this.selectedLabel=`${this.collection.getModelName()} #${this.selectedValue}`),this.loadSelectedItem()):this.render())}getFieldValue(e,t){if(!(!e||!t)){if(typeof e.get=="function"){const s=e.get(t);return s===void 0&&t.includes(".")?x.getNestedValue(e,t):s}return x.getNestedValue(e,t)}}}class Oe extends v{constructor(e={}){super({tagName:"div",className:"collection-multiselect-search",template:`
|
|
720
|
+
<input type="text"
|
|
721
|
+
class="form-control form-control-sm mb-2"
|
|
722
|
+
placeholder="{{placeholder}}"
|
|
723
|
+
data-change-action="search"
|
|
724
|
+
data-filter="live-search"
|
|
725
|
+
data-filter-debounce="{{debounce}}" />
|
|
726
|
+
`,...e}),this.placeholder=e.placeholder||"Search...",this.debounce=e.debounce||400}async onChangeSearch(e,t){const s=t.value.trim();this.emit("search",s)}getValue(){return this.element?.querySelector("input")?.value||""}clear(){const e=this.element?.querySelector("input");e&&(e.value="")}}class He extends v{constructor(e={}){const s=!!e.customItemTemplate?"{{{customContent}}}":'<span {{#disabled}}class="text-muted"{{/disabled}}>{{label}}</span>';super({tagName:"div",className:"collection-multiselect-items",template:`
|
|
727
|
+
{{#loading}}
|
|
728
|
+
<div class="text-center py-3">
|
|
729
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
730
|
+
<span class="visually-hidden">Loading...</span>
|
|
731
|
+
</div>
|
|
732
|
+
</div>
|
|
733
|
+
{{/loading}}
|
|
734
|
+
|
|
735
|
+
{{^loading}}
|
|
736
|
+
{{#items.length}}
|
|
737
|
+
{{#showSelectAll}}
|
|
738
|
+
<div class="collection-multiselect-actions d-flex justify-content-between align-items-center mb-2 py-1">
|
|
739
|
+
<button type="button"
|
|
740
|
+
class="btn btn-link btn-sm text-decoration-none p-0 {{#allSelected}}text-muted{{/allSelected}}"
|
|
741
|
+
data-action="select-all"
|
|
742
|
+
{{#allSelected}}disabled{{/allSelected}}>
|
|
743
|
+
<i class="bi bi-check-square me-1"></i>
|
|
744
|
+
SELECT {{#unselectedCount}}({{unselectedCount}}){{/unselectedCount}}
|
|
745
|
+
</button>
|
|
746
|
+
<button type="button"
|
|
747
|
+
class="btn btn-link btn-sm text-decoration-none p-0 {{#noneSelected}}text-muted{{/noneSelected}}"
|
|
748
|
+
data-action="deselect-all"
|
|
749
|
+
{{#noneSelected}}disabled{{/noneSelected}}>
|
|
750
|
+
DESELECT {{#selectedCount}}({{selectedCount}}){{/selectedCount}}
|
|
751
|
+
<i class="bi bi-square ms-1"></i>
|
|
752
|
+
</button>
|
|
753
|
+
</div>
|
|
754
|
+
{{/showSelectAll}}
|
|
755
|
+
|
|
756
|
+
<div class="collection-multiselect-list border rounded"
|
|
757
|
+
style="max-height: {{maxHeight}}px; overflow-y: auto;">
|
|
758
|
+
{{#items}}
|
|
759
|
+
<div class="collection-multiselect-item d-flex align-items-center py-2 px-3 {{^disabled}}clickable{{/disabled}}"
|
|
760
|
+
data-action="{{^disabled}}toggle{{/disabled}}"
|
|
761
|
+
data-value="{{value}}"
|
|
762
|
+
data-index="{{index}}">
|
|
763
|
+
<i class="bi {{#selected}}bi-check-square-fill text-primary{{/selected}}{{^selected}}bi-square{{/selected}} me-2"
|
|
764
|
+
style="font-size: 1.1rem;"></i>
|
|
765
|
+
${s}
|
|
766
|
+
</div>
|
|
767
|
+
{{/items}}
|
|
768
|
+
</div>
|
|
769
|
+
{{/items.length}}
|
|
770
|
+
|
|
771
|
+
{{^items.length}}
|
|
772
|
+
<div class="collection-multiselect-empty text-muted text-center py-4 border rounded">
|
|
773
|
+
<i class="bi bi-inbox fs-3 d-block mb-2 opacity-50"></i>
|
|
774
|
+
<div>No items available</div>
|
|
775
|
+
</div>
|
|
776
|
+
{{/^items.length}}
|
|
777
|
+
{{/loading}}
|
|
778
|
+
`,...e}),this.items=e.items||[],this.loading=e.loading||!1,this.maxHeight=e.maxHeight||336,this.showSelectAll=e.showSelectAll!==!1,this.selectedCount=e.selectedCount||0,this.totalCount=e.totalCount||0,this.unselectedCount=e.unselectedCount||0,this.allSelected=e.allSelected||!1,this.noneSelected=e.noneSelected||!0,this.customItemTemplate=e.customItemTemplate||null,this.lastClickedIndex=-1}handleActionToggle(e,t){const s=t.getAttribute("data-value"),i=parseInt(t.getAttribute("data-index"),10);this.emit("toggle",{value:s,index:i,shiftKey:e.shiftKey,element:t}),this.lastClickedIndex=i}updateItemCheckbox(e,t){const s=e.querySelector("i.bi");s&&(t?(s.classList.remove("bi-square"),s.classList.add("bi-check-square-fill","text-primary")):(s.classList.remove("bi-check-square-fill","text-primary"),s.classList.add("bi-square")))}updateActionButtons(){const e=this.element?.querySelector('[data-action="select-all"]'),t=this.element?.querySelector('[data-action="deselect-all"]');e&&(e.querySelector("span"),this.allSelected?(e.classList.add("text-muted"),e.disabled=!0):(e.classList.remove("text-muted"),e.disabled=!1)),t&&(this.noneSelected?(t.classList.add("text-muted"),t.disabled=!0):(t.classList.remove("text-muted"),t.disabled=!1))}async handleActionSelectAll(e){e.preventDefault(),this.emit("select-all")}async handleActionDeselectAll(e){e.preventDefault(),this.emit("deselect-all")}updateState(e){Object.assign(this,e)}}class qe extends v{constructor(e={}){super({tagName:"div",className:"collection-multiselect-view",template:`
|
|
779
|
+
<div class="mojo-form-control">
|
|
780
|
+
{{#label}}
|
|
781
|
+
<label class="form-label">
|
|
782
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
783
|
+
</label>
|
|
784
|
+
{{/label}}
|
|
785
|
+
|
|
786
|
+
<div class="collection-multiselect-search-container"></div>
|
|
787
|
+
<div class="collection-multiselect-list-container"></div>
|
|
788
|
+
|
|
789
|
+
{{#help}}
|
|
790
|
+
<div class="form-text">{{help}}</div>
|
|
791
|
+
{{/help}}
|
|
792
|
+
{{#error}}
|
|
793
|
+
<div class="invalid-feedback d-block">{{error}}</div>
|
|
794
|
+
{{/error}}
|
|
795
|
+
</div>
|
|
796
|
+
`,...e}),this.name=e.name||"collection_multiselect",this.label=e.label||"",this.help=e.help||"",this.error=e.error||"",this.required=e.required||!1,this.disabled=e.disabled||!1,this.collection=e.collection,this.labelField=e.labelField||"name",this.valueField=e.valueField||"id",this.excludeIds=e.excludeIds||[],this.ignoreIds=e.ignoreIds||[],this.itemTemplate=e.itemTemplate||null,this.collectionParams=e.collectionParams||{},this.defaultParamsOption=e.defaultParams||null,this.baseParams={},this.requiresActiveGroup=e.requiresActiveGroup||!1,this.size=e.size||8,this.maxHeight=e.maxHeight||this.size*42,this.showSelectAll=e.showSelectAll!==!1,this.enableSearch=e.enableSearch||!1,this.searchPlaceholder=e.searchPlaceholder||"Search...",this.searchDebounce=e.searchDebounce||400,this.selectedValues=Array.isArray(e.value)?e.value:[],this.loading=!1,this.items=[],this.searchView=null,this.listView=null}onInit(){this.collection&&this.setupCollection()}setupCollection(){if(this.baseParams={...this.collection.params},Object.keys(this.collectionParams).length>0&&(Object.assign(this.baseParams,this.collectionParams),Object.assign(this.collection.params,this.collectionParams)),this.defaultParamsOption){const e=typeof this.defaultParamsOption=="function"?this.defaultParamsOption():this.defaultParamsOption;e&&(Object.assign(this.baseParams,e),Object.assign(this.collection.params,e))}if(this.requiresActiveGroup){const e=this.getApp();e?.activeGroup?.id&&(this.baseParams.group=e.activeGroup.id,this.collection.params.group=e.activeGroup.id)}this.collection.on("fetch:start",()=>{this.loading=!0,this.updateListView()}),this.collection.on("fetch:end",()=>{this.loading=!1,this.buildItems(),this.updateListView()}),this.collection.isEmpty()||this.buildItems()}async onAfterRender(){await super.onAfterRender(),this.enableSearch&&this.createSearchView(),this.createListView(),this.collection?.isEmpty()&&this.collection.fetch()}createSearchView(){const e=this.element?.querySelector(".collection-multiselect-search-container");e&&(this.searchView=new Oe({placeholder:this.searchPlaceholder,debounce:this.searchDebounce}),this.searchView.on("search",t=>{this.handleSearch(t)}),this.searchView.render(!0,e))}createListView(){const e=this.element?.querySelector(".collection-multiselect-list-container");if(!e)return;const t=this.selectedValues.length,s=this.items.length,i=s-t;this.listView=new He({items:this.items,loading:this.loading,maxHeight:this.maxHeight,showSelectAll:this.showSelectAll,selectedCount:t,totalCount:s,unselectedCount:i,allSelected:t===s&&s>0,noneSelected:t===0,customItemTemplate:this.itemTemplate}),this.listView.on("toggle",a=>{this.handleToggle(a)}),this.listView.on("select-all",()=>{this.selectAll()}),this.listView.on("deselect-all",()=>{this.deselectAll()}),this.listView.render(!0,e)}updateListView(){if(this.listView){const e=this.selectedValues.length,t=this.items.length,s=t-e;this.listView.updateState({items:this.items,loading:this.loading,selectedCount:e,totalCount:t,unselectedCount:s,allSelected:e===t&&t>0,noneSelected:e===0}),this.listView.render(!1)}}buildItems(){const e=this.collection.models.filter(t=>{const s=this.getFieldValue(t,this.valueField);return!(s==null||this.excludeIds.includes(s)||this.ignoreIds.some(i=>i==s))});this.items=e.map((t,s)=>{const i=t.toJSON?t.toJSON():t,a=this.getFieldValue(t,this.valueField),r={label:this.getFieldValue(t,this.labelField),value:a,index:s,selected:this.selectedValues.some(n=>n==a),disabled:this.disabled,model:i};return this.itemTemplate&&(r.customContent=this.renderItemTemplate(r)),r})}renderItemTemplate(e){if(!this.itemTemplate)return"";try{return this.renderTemplateString(this.itemTemplate,e)}catch(t){return console.error("Error rendering item template:",t),e.label}}getFieldValue(e,t){if(!(!e||!t))return typeof e.get=="function"?e.get(t)??x.getNestedValue(e,t):x.getNestedValue(e,t)}handleSearch(e){const t={...this.baseParams};e&&(t.search=e),this.collection.updateParams(t,!0)}handleToggle({value:e,index:t,shiftKey:s,element:i}){if(s&&this.listView.lastClickedIndex>=0){const a=Math.min(this.listView.lastClickedIndex,t),r=Math.max(this.listView.lastClickedIndex,t),n=!this.items[t].selected;for(let l=a;l<=r;l++){const o=this.items[l];o.disabled||(n?this.selectedValues.includes(o.value)||this.selectedValues.push(o.value):this.selectedValues=this.selectedValues.filter(c=>c!=o.value),o.selected=n)}this.updateListView()}else{const a=this.items[t];a.selected?(this.selectedValues=this.selectedValues.filter(r=>r!=e),a.selected=!1):(this.selectedValues.push(e),a.selected=!0),i&&this.listView&&(this.listView.updateItemCheckbox(i,a.selected),this.listView.selectedCount=this.selectedValues.length,this.listView.unselectedCount=this.items.length-this.selectedValues.length,this.listView.allSelected=this.selectedValues.length===this.items.length&&this.items.length>0,this.listView.noneSelected=this.selectedValues.length===0,this.listView.updateActionButtons())}this.emit("change",{value:this.selectedValues,name:this.name})}selectAll(){this.selectedValues=this.items.filter(e=>!e.disabled).map(e=>e.value),this.items.forEach(e=>{e.disabled||(e.selected=!0)}),this.updateListView(),this.emit("change",{value:this.selectedValues,name:this.name})}deselectAll(){this.selectedValues=[],this.items.forEach(e=>e.selected=!1),this.updateListView(),this.emit("change",{value:this.selectedValues,name:this.name})}async onBeforeDestroy(){await super.onBeforeDestroy(),this.searchView&&this.searchView.destroy(),this.listView&&this.listView.destroy()}getValue(){return this.selectedValues}setValue(e){this.selectedValues=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}setExcludeIds(e){this.excludeIds=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}setIgnoreIds(e){this.ignoreIds=Array.isArray(e)?e:[],this.buildItems(),this.updateListView()}async refresh(){await this.collection.fetch()}getFormValue(){return this.selectedValues}setFormValue(e){this.setValue(e)}}class je extends v{constructor(e={}){super({tagName:"div",className:"multiselect-items",template:`
|
|
797
|
+
{{#items.length}}
|
|
798
|
+
<div class="multiselect-list" style="max-height: {{maxHeight}}px; overflow-y: auto;">
|
|
799
|
+
{{#items}}
|
|
800
|
+
<div class="multiselect-item form-check px-3 py-2"
|
|
801
|
+
data-action="toggle"
|
|
802
|
+
data-value="{{value}}"
|
|
803
|
+
data-index="{{index}}">
|
|
804
|
+
<input type="checkbox"
|
|
805
|
+
class="form-check-input"
|
|
806
|
+
id="{{id}}"
|
|
807
|
+
{{#selected}}checked{{/selected}}
|
|
808
|
+
{{#disabled}}disabled{{/disabled}}>
|
|
809
|
+
<label class="form-check-label w-100" for="{{id}}">
|
|
810
|
+
{{label}}
|
|
811
|
+
</label>
|
|
812
|
+
</div>
|
|
813
|
+
{{/items}}
|
|
814
|
+
</div>
|
|
815
|
+
<div class="multiselect-footer border-top p-2">
|
|
816
|
+
<button type="button" class="btn btn-sm btn-primary w-100" data-action="close-dropdown">
|
|
817
|
+
Done
|
|
818
|
+
</button>
|
|
819
|
+
</div>
|
|
820
|
+
{{/items.length}}
|
|
821
|
+
|
|
822
|
+
{{^items.length}}
|
|
823
|
+
<div class="multiselect-empty text-muted text-center py-3">
|
|
824
|
+
<small>No options available</small>
|
|
825
|
+
</div>
|
|
826
|
+
{{/items.length}}
|
|
827
|
+
`,...e}),this.items=e.items||[],this.maxHeight=e.maxHeight||300}handleActionToggle(e,t){const s=t.getAttribute("data-value"),i=parseInt(t.getAttribute("data-index"),10),a=this.items[i];if(!a||a.disabled)return;a.selected=!a.selected;const r=t.querySelector('input[type="checkbox"]');r&&(r.checked=a.selected),this.emit("toggle",{value:s,index:i,selected:a.selected})}handleActionCloseDropdown(e,t){this.emit("close-dropdown")}getValue(){return this.items.filter(e=>e.selected).map(e=>e.value)}setValue(e){const t=new Set(Array.isArray(e)?e:[e]);this.items.forEach(s=>{s.selected=t.has(s.value)}),this.render(!1)}updateItems(e){this.items=e,this.render(!1)}}class Re extends v{constructor(e={}){super({tagName:"div",className:"multiselect-dropdown",template:`
|
|
828
|
+
<div class="mojo-form-control">
|
|
829
|
+
{{#label}}
|
|
830
|
+
<label class="form-label">
|
|
831
|
+
{{label}}{{#required}}<span class="text-danger">*</span>{{/required}}
|
|
832
|
+
</label>
|
|
833
|
+
{{/label}}
|
|
834
|
+
|
|
835
|
+
<div class="dropdown w-100">
|
|
836
|
+
<button class="btn btn-outline-secondary dropdown-toggle w-100 text-start d-flex justify-content-between align-items-center"
|
|
837
|
+
type="button"
|
|
838
|
+
data-bs-toggle="dropdown"
|
|
839
|
+
aria-expanded="false"
|
|
840
|
+
{{#disabled}}disabled{{/disabled}}>
|
|
841
|
+
<span class="multiselect-button-text">{{buttonText}}</span>
|
|
842
|
+
<i class="bi bi-chevron-down"></i>
|
|
843
|
+
</button>
|
|
844
|
+
<div class="dropdown-menu w-100" data-bs-auto-close="outside" data-container="items"></div>
|
|
845
|
+
</div>
|
|
846
|
+
|
|
847
|
+
{{#help}}
|
|
848
|
+
<div class="form-text">{{help}}</div>
|
|
849
|
+
{{/help}}
|
|
850
|
+
{{#error}}
|
|
851
|
+
<div class="invalid-feedback d-block">{{error}}</div>
|
|
852
|
+
{{/error}}
|
|
853
|
+
</div>
|
|
854
|
+
`,...e}),this.name=e.name||"multiselect",this.label=e.label||"",this.help=e.help||"",this.error=e.error||"",this.required=e.required||!1,this.disabled=e.disabled||!1,this.placeholder=e.placeholder||e.placeHolder||"Select...",this.maxHeight=e.maxHeight||300,this.showSelectedLabels=e.showSelectedLabels!==!1,this.maxLabelsToShow=e.maxLabelsToShow||3,this.options=e.options||[],this.selectedValues=Array.isArray(e.value)?e.value:[],this.buttonText=this.computeButtonText(),this.listView=null}computeButtonText(){const e=this.selectedValues.length;return e===0?this.placeholder||"Select...":this.showSelectedLabels&&e<=this.maxLabelsToShow?this.selectedValues.map(s=>{const i=this.options.find(a=>(typeof a=="string"?a:a.value)===s);return typeof i=="string"?i:i?.label||i?.value||s}).join(", "):`${e} selected`}async onAfterRender(){await super.onAfterRender(),this.createListView()}createListView(){const e=this.element?.querySelector('[data-container="items"]');if(!e)return;const t=this.options.map((s,i)=>{const a=typeof s=="string"?s:s.value,r=typeof s=="string"?s:s.label||s.text||s.value,n=typeof s=="object"?s.disabled:!1;return{id:`${this.name}_${i}`,value:a,label:r,index:i,selected:this.selectedValues.includes(a),disabled:n}});this.listView=new je({items:t,maxHeight:this.maxHeight}),this.listView.on("toggle",s=>{this.handleToggle(s)}),this.listView.on("close-dropdown",()=>{this.closeDropdown()}),this.listView.render(!0,e)}closeDropdown(){const e=this.element?.querySelector(".dropdown-toggle");if(e&&window.bootstrap?.Dropdown){const t=window.bootstrap.Dropdown.getInstance(e);t&&t.hide()}}handleToggle(e){const{value:t,selected:s}=e;s?this.selectedValues.includes(t)||this.selectedValues.push(t):this.selectedValues=this.selectedValues.filter(i=>i!==t),this.updateButtonText(),this.emit("change",{value:this.selectedValues,name:this.name})}updateButtonText(){const e=this.element?.querySelector(".multiselect-button-text");if(!e)return;const t=this.selectedValues.length;this.buttonText=this.computeButtonText(),e.textContent=this.buttonText,t===0?e.classList.add("text-muted"):e.classList.remove("text-muted")}getValue(){return this.selectedValues}setValue(e){this.selectedValues=Array.isArray(e)?e:e?[e]:[],this.listView&&this.listView.setValue(this.selectedValues),this.updateButtonText()}setOptions(e){if(this.options=e,this.listView){const t=this.options.map((s,i)=>{const a=typeof s=="string"?s:s.value,r=typeof s=="string"?s:s.label||s.text||s.value,n=typeof s=="object"?s.disabled:!1;return{id:`${this.name}_${i}`,value:a,label:r,index:i,selected:this.selectedValues.includes(a),disabled:n}});this.listView.updateItems(t)}}clear(){this.setValue([])}getFormValue(){return this.getValue()}setFormValue(e){this.setValue(e)}async onBeforeDestroy(){await super.onBeforeDestroy(),this.listView&&this.listView.destroy()}}class oe extends v{constructor(e={}){const{name:t,value:s="",format:i="YYYY-MM-DD",displayFormat:a="MMM DD, YYYY",min:r=null,max:n=null,placeholder:l="Select date...",disabled:o=!1,readonly:c=!1,required:d=!1,class:h="",inputClass:f="form-control",autoApply:m=!0,inline:p=!1,...g}=e;super({tagName:"div",className:`date-picker-view ${h}`,...g}),this.name=t,this.format=i,this.displayFormat=a,this.min=r,this.max=n,this.placeholder=l,this.disabled=o,this.readonly=c,this.required=d,this.inputClass=f,this.autoApply=m,this.inline=p,this.currentValue=s,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if(typeof window<"u"&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date input:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick){e();return}const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.useNative?"date":"text",s=this.formatValueForInput(this.currentValue);return`
|
|
855
|
+
<div class="date-picker-container">
|
|
856
|
+
<input
|
|
857
|
+
type="${t}"
|
|
858
|
+
id="${e}"
|
|
859
|
+
name="${this.name||""}"
|
|
860
|
+
class="${this.inputClass}${this.hasError()?" is-invalid":""}"
|
|
861
|
+
value="${this.escapeHtml(s)}"
|
|
862
|
+
placeholder="${this.escapeHtml(this.placeholder)}"
|
|
863
|
+
${this.min?`min="${this.min}"`:""}
|
|
864
|
+
${this.max?`max="${this.max}"`:""}
|
|
865
|
+
${this.disabled?"disabled":""}
|
|
866
|
+
${this.readonly?"readonly":""}
|
|
867
|
+
${this.required?"required":""}
|
|
868
|
+
autocomplete="off"
|
|
869
|
+
data-change-action="date-changed"
|
|
870
|
+
/>
|
|
871
|
+
<div class="date-picker-feedback"></div>
|
|
872
|
+
</div>
|
|
873
|
+
`}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(!(!e||!window.easepick))try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=s=>{s.on("select",i=>{const a=i.detail.date;this.handleDateChange(a?this.formatDate(a,this.format):"")}),s.on("clear",()=>{this.handleDateChange("")}),s.on("show",()=>{this.emit("picker:show")}),s.on("hide",()=>{this.emit("picker:hide")})},this.picker=new window.easepick.create(t),this.currentValue&&this.picker.setDate(this.currentValue)}catch(t){console.error("Failed to initialize Easepick:",t),this.useNative=!0,this.initializeNativeFallback()}}initializeNativeFallback(){const e=this.getInputElement();e&&(e.type="date",this.currentValue&&(e.value=this.formatDate(this.currentValue,"YYYY-MM-DD")))}async onChangeDateChanged(e,t,s){const i=s.value;this.handleDateChange(i)}handleDateChange(e){const t=this.currentValue;this.currentValue=e,this.updateHiddenInput(),t!==e&&(this.emit("change",{value:e,formatted:this.formatValueForDisplay(e),oldValue:t}),this.emit("date:changed",{value:e,oldValue:t}))}formatDate(e,t=this.format){if(!e)return"";const s=new Date(e);if(isNaN(s.getTime()))return"";const i=s.getFullYear(),a=String(s.getMonth()+1).padStart(2,"0"),r=String(s.getDate()).padStart(2,"0");switch(t){case"YYYY-MM-DD":return`${i}-${a}-${r}`;case"MM/DD/YYYY":return`${a}/${r}/${i}`;case"DD/MM/YYYY":return`${r}/${a}/${i}`;case"MMM DD, YYYY":return`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][s.getMonth()]} ${r}, ${i}`;default:return`${i}-${a}-${r}`}}formatValueForInput(e){return e?this.useNative?this.formatDate(e,"YYYY-MM-DD"):e:""}formatValueForDisplay(e){return e?this.formatDate(e,this.displayFormat):""}getInputId(){return this.name?`datepicker_${this.name}_${Date.now()}`:`datepicker_${Date.now()}`}getInputElement(){return this.element?.querySelector("input")}updateHiddenInput(){}hasError(){return!1}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setValue(e){if(this.currentValue=e,this.picker&&this.easepickLoaded)this.picker.setDate(e||null);else{const t=this.getInputElement();t&&(t.value=this.formatValueForInput(e))}this.emit("value:set",{value:e})}getValue(){return this.currentValue}getFormattedValue(e=this.displayFormat){return this.formatDate(this.currentValue,e)}clear(){this.setValue("")}setMin(e){if(this.min=e,this.picker&&this.easepickLoaded)this.picker.options.minDate=new Date(e);else{const t=this.getInputElement();t&&(t.min=e)}}setMax(e){if(this.max=e,this.picker&&this.easepickLoaded)this.picker.options.maxDate=new Date(e);else{const t=this.getInputElement();t&&(t.max=e)}}setEnabled(e){this.disabled=!e;const t=this.getInputElement();t&&(t.disabled=this.disabled),this.picker&&this.easepickLoaded&&this.disabled&&this.picker.hide()}setReadonly(e){this.readonly=e;const t=this.getInputElement();t&&(t.readonly=e)}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getValue()}async setFormValue(e){this.setValue(e)}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new oe(e)}}class ce extends v{constructor(e={}){const{name:t,startName:s,endName:i,fieldName:a,startDate:r="",endDate:n="",format:l="YYYY-MM-DD",displayFormat:o="MMM DD, YYYY",outputFormat:c="date",min:d=null,max:h=null,placeholder:f="Select date range...",startPlaceholder:m="Start date...",endPlaceholder:p="End date...",disabled:g=!1,readonly:w=!1,required:y=!1,class:b="",inputClass:C="form-control",autoApply:$=!0,inline:S=!1,separator:T=" - ",..._}=e;super({tagName:"div",className:`date-range-picker-view ${b}`,..._}),this.name=t,this.startName=s,this.endName=i,this.fieldName=a,this.format=l,this.displayFormat=o,this.outputFormat=c,this.min=d,this.max=h,this.placeholder=f,this.startPlaceholder=m,this.endPlaceholder=p,this.disabled=g,this.readonly=w,this.required=y,this.inputClass=C,this.autoApply=$,this.inline=S,this.separator=T,this.currentStartDate=r,this.currentEndDate=n,this.picker=null,this.useNative=!1,this.easepickLoaded=!1,this.checkEasepickAvailability()}async checkEasepickAvailability(){if(typeof window<"u"&&window.easepick)return this.easepickLoaded=!0,!0;try{return await this.loadEasepick(),this.easepickLoaded=!0,!0}catch(e){return console.warn("Easepick failed to load, falling back to native date inputs:",e),this.useNative=!0,!1}}async loadEasepick(){return new Promise((e,t)=>{if(window.easepick){e();return}const s=document.createElement("link");s.rel="stylesheet",s.href="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",document.head.appendChild(s);const i=document.createElement("script");i.src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js",i.onload=()=>{window.easepick?e():t(new Error("Easepick not available after loading"))},i.onerror=()=>t(new Error("Failed to load Easepick script")),document.head.appendChild(i)})}async renderTemplate(){const e=this.getInputId(),t=this.getDisplayValue();if(this.useNative)return this.renderNativeTemplate(e);const s=this.startName||(this.name?`${this.name}_start`:""),i=this.endName||(this.name?`${this.name}_end`:""),a=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",r=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";return`
|
|
874
|
+
<div class="date-range-picker-container">
|
|
875
|
+
<input
|
|
876
|
+
type="text"
|
|
877
|
+
id="${e}"
|
|
878
|
+
${this.name?`name="${this.name}"`:""}
|
|
879
|
+
class="${this.inputClass} date-range-picker-input${this.hasError()?" is-invalid":""}"
|
|
880
|
+
value="${this.escapeHtml(t)}"
|
|
881
|
+
placeholder="${this.escapeHtml(this.placeholder)}"
|
|
882
|
+
${this.disabled?"disabled":""}
|
|
883
|
+
${this.readonly?"readonly":""}
|
|
884
|
+
${this.required?"required":""}
|
|
885
|
+
autocomplete="off"
|
|
886
|
+
data-change-action="range-changed"
|
|
887
|
+
style="background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22><path fill=%22%236c757d%22 fill-rule=%22evenodd%22 d=%22M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z%22/></svg>'); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; padding-right: 2.25rem; cursor: pointer;"
|
|
888
|
+
/>
|
|
889
|
+
|
|
890
|
+
<!-- Hidden inputs for form submission -->
|
|
891
|
+
${s?`<input type="hidden" name="${s}" value="${this.escapeHtml(a)}" />`:""}
|
|
892
|
+
${i?`<input type="hidden" name="${i}" value="${this.escapeHtml(r)}" />`:""}
|
|
893
|
+
${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}
|
|
894
|
+
|
|
895
|
+
<div class="date-range-picker-feedback"></div>
|
|
896
|
+
</div>
|
|
897
|
+
`}renderNativeTemplate(e){return`
|
|
898
|
+
<div class="date-range-picker-container date-range-native">
|
|
899
|
+
<div class="row g-2">
|
|
900
|
+
<div class="col">
|
|
901
|
+
<input
|
|
902
|
+
type="date"
|
|
903
|
+
id="${e}_start"
|
|
904
|
+
name="${this.name}_start"
|
|
905
|
+
class="${this.inputClass}${this.hasError()?" is-invalid":""}"
|
|
906
|
+
value="${this.escapeHtml(this.formatDate(this.currentStartDate,"YYYY-MM-DD"))}"
|
|
907
|
+
placeholder="${this.escapeHtml(this.startPlaceholder)}"
|
|
908
|
+
${this.min?`min="${this.min}"`:""}
|
|
909
|
+
${this.max?`max="${this.max}"`:""}
|
|
910
|
+
${this.disabled?"disabled":""}
|
|
911
|
+
${this.readonly?"readonly":""}
|
|
912
|
+
${this.required?"required":""}
|
|
913
|
+
data-change-action="start-date-changed"
|
|
914
|
+
/>
|
|
915
|
+
</div>
|
|
916
|
+
<div class="col-auto d-flex align-items-center">
|
|
917
|
+
<span class="text-muted">${this.escapeHtml(this.separator.trim())}</span>
|
|
918
|
+
</div>
|
|
919
|
+
<div class="col">
|
|
920
|
+
<input
|
|
921
|
+
type="date"
|
|
922
|
+
id="${e}_end"
|
|
923
|
+
name="${this.name}_end"
|
|
924
|
+
class="${this.inputClass}${this.hasError()?" is-invalid":""}"
|
|
925
|
+
value="${this.escapeHtml(this.formatDate(this.currentEndDate,"YYYY-MM-DD"))}"
|
|
926
|
+
placeholder="${this.escapeHtml(this.endPlaceholder)}"
|
|
927
|
+
${this.min?`min="${this.min}"`:""}
|
|
928
|
+
${this.max?`max="${this.max}"`:""}
|
|
929
|
+
${this.disabled?"disabled":""}
|
|
930
|
+
${this.readonly?"readonly":""}
|
|
931
|
+
${this.required?"required":""}
|
|
932
|
+
data-change-action="end-date-changed"
|
|
933
|
+
/>
|
|
934
|
+
</div>
|
|
935
|
+
</div>
|
|
936
|
+
|
|
937
|
+
<!-- Hidden input for combined value -->
|
|
938
|
+
<input type="hidden" name="${this.name}" value="${this.escapeHtml(this.getCombinedValue())}" />
|
|
939
|
+
${this.fieldName?`<input type="hidden" name="${this.fieldName}" value="${this.escapeHtml(this.name||"")}" />`:""}
|
|
940
|
+
|
|
941
|
+
<div class="date-range-picker-feedback"></div>
|
|
942
|
+
</div>
|
|
943
|
+
`}async onAfterRender(){await super.onAfterRender(),this.easepickLoaded&&!this.useNative?await this.initializeEasepick():this.initializeNativeFallback()}async initializeEasepick(){const e=this.getInputElement();if(!(!e||!window.easepick))try{const t={element:e,css:["https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css"],format:this.displayFormat,lang:"en-US",autoApply:this.autoApply,inline:this.inline,readonly:this.readonly,zIndex:9999,plugins:["RangePlugin"],RangePlugin:{tooltip:!0,locale:{one:"day",other:"days"}}};this.min&&(t.minDate=new Date(this.min)),this.max&&(t.maxDate=new Date(this.max)),t.setup=s=>{s.on("select",i=>{const{start:a,end:r}=i.detail,n=a?this.normalizeDateFromPicker(a):"",l=r?this.normalizeDateFromPicker(r):"";this.handleRangeChange(n,l)}),s.on("clear",()=>{this.handleRangeChange("","")}),s.on("show",()=>{this.emit("picker:show")}),s.on("hide",()=>{this.emit("picker:hide")}),s.on("ready",()=>{this.applyInitialRange(s)})},this.picker=new window.easepick.create(t),this.applyInitialRange(this.picker)}catch(t){console.error("Failed to initialize Easepick range picker:",t),this.useNative=!0,await this.render(),this.initializeNativeFallback()}}initializeNativeFallback(){this.updateConstraints()}async onChangeRangeChanged(e,t,s){}async onChangeStartDateChanged(e,t,s){const i=s.value;this.handleRangeChange(i,this.currentEndDate),this.updateConstraints()}async onChangeEndDateChanged(e,t,s){const i=s.value;this.handleRangeChange(this.currentStartDate,i),this.updateConstraints()}handleRangeChange(e,t){const s=this.currentStartDate,i=this.currentEndDate;this.currentStartDate=e,this.currentEndDate=t,this.updateHiddenInputs(),(s!==e||i!==t)&&(this.emit("change",{startDate:e,endDate:t,combined:this.getCombinedValue(),formatted:this.getDisplayValue(),oldStartDate:s,oldEndDate:i}),this.emit("range:changed",{startDate:e,endDate:t,oldStartDate:s,oldEndDate:i}))}updateConstraints(){if(!this.useNative)return;const e=this.element?.querySelector(`[name="${this.name}_start"]`),t=this.element?.querySelector(`[name="${this.name}_end"]`);e&&t&&(this.currentStartDate&&(t.min=this.currentStartDate),this.currentEndDate&&(e.max=this.currentEndDate))}normalizeDateFromPicker(e){if(!e)return"";if(typeof e.toJSDate=="function"){const t=e.toJSDate();return this.formatDate(t,this.format)}if(typeof e.getFullYear=="function"){const t=e.getFullYear(),s=String(e.getMonth()+1).padStart(2,"0"),i=String(e.getDate()).padStart(2,"0");switch(this.format){case"YYYY-MM-DD":return`${t}-${s}-${i}`;case"MM/DD/YYYY":return`${s}/${i}/${t}`;case"DD/MM/YYYY":return`${i}/${s}/${t}`;default:return`${t}-${s}-${i}`}}return this.formatDate(e,this.format)}formatDate(e,t=this.format){if(!e)return"";let s,i,a,r;if(typeof e=="string"&&/^\d{4}-\d{2}-\d{2}$/.test(e)){const n=e.split("-");s=parseInt(n[0]),i=String(parseInt(n[1])).padStart(2,"0"),a=String(parseInt(n[2])).padStart(2,"0")}else{if(e instanceof Date?r=e:r=new Date(e),isNaN(r.getTime()))return"";s=r.getFullYear(),i=String(r.getMonth()+1).padStart(2,"0"),a=String(r.getDate()).padStart(2,"0")}switch(t){case"YYYY-MM-DD":return`${s}-${i}-${a}`;case"MM/DD/YYYY":return`${i}/${a}/${s}`;case"DD/MM/YYYY":return`${a}/${i}/${s}`;case"MMM DD, YYYY":{const n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],l=parseInt(i)-1;return`${n[l]} ${a}, ${s}`}default:return`${s}-${i}-${a}`}}formatForOutput(e){if(!e)return"";if(typeof e=="string"&&/^\d{4}-\d{2}-\d{2}$/.test(e))switch(this.outputFormat){case"epoch":const s=e.split("-"),i=new Date(parseInt(s[0]),parseInt(s[1])-1,parseInt(s[2]));return Math.floor(i.getTime()/1e3).toString();case"iso":return new Date(e+"T00:00:00").toISOString();case"date":default:return e}const t=new Date(e);if(isNaN(t.getTime()))return"";switch(this.outputFormat){case"epoch":return Math.floor(t.getTime()/1e3).toString();case"iso":return t.toISOString();case"date":default:return this.formatDate(e,this.format)}}getDisplayValue(){if(!this.currentStartDate&&!this.currentEndDate)return"";const e=this.currentStartDate?this.formatDate(this.currentStartDate,this.displayFormat):"",t=this.currentEndDate?this.formatDate(this.currentEndDate,this.displayFormat):"";return e&&t?`${e}${this.separator}${t}`:e||t||""}getCombinedValue(){return!this.currentStartDate&&!this.currentEndDate?"":JSON.stringify({start:this.currentStartDate,end:this.currentEndDate})}getInputId(){return this.name?`daterange_${this.name}_${Date.now()}`:`daterange_${Date.now()}`}getInputElement(){return this.element?.querySelector('input[type="text"], input[name="'+this.name+'"]')}updateHiddenInputs(){const e=this.startName||(this.name?`${this.name}_start`:""),t=this.endName||(this.name?`${this.name}_end`:""),s=e?this.element?.querySelector(`[name="${e}"]`):null,i=t?this.element?.querySelector(`[name="${t}"]`):null,a=this.name?this.element?.querySelector(`[name="${this.name}"]`):null,r=this.fieldName?this.element?.querySelector(`[name="${this.fieldName}"]`):null,n=this.currentStartDate?this.formatForOutput(this.currentStartDate):"",l=this.currentEndDate?this.formatForOutput(this.currentEndDate):"";s&&(s.value=n),i&&(i.value=l),a&&(a.value=this.getDisplayValue()),r&&(r.value=this.name||"")}hasError(){return this.currentStartDate&&this.currentEndDate?new Date(this.currentEndDate)<new Date(this.currentStartDate):!1}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}setRange(e,t){if(this.currentStartDate=e,this.currentEndDate=t,this.picker&&this.easepickLoaded){const s=this.normalizeDateValue(e),i=this.normalizeDateValue(t);this.picker.setDateRange(s||null,i||null)}else if(this.useNative){const s=this.element?.querySelector(`[name="${this.name}_start"]`),i=this.element?.querySelector(`[name="${this.name}_end"]`);s&&(s.value=this.formatDate(e,"YYYY-MM-DD")),i&&(i.value=this.formatDate(t,"YYYY-MM-DD"))}else{const s=this.getInputElement();s&&(s.value=this.getDisplayValue())}this.updateHiddenInputs(),this.emit("range:set",{startDate:e,endDate:t})}applyInitialRange(e){if(!e||!(this.currentStartDate||this.currentEndDate))return;const t=this.normalizeDateValue(this.currentStartDate),s=this.normalizeDateValue(this.currentEndDate);(t||s)&&e.setDateRange(t||null,s||null)}normalizeDateValue(e){if(!e&&e!==0)return null;if(e instanceof Date)return isNaN(e)?null:e;const t=String(e).trim();if(!t)return null;if(/^\d{4}-\d{2}-\d{2}$/.test(t)){const[i,a,r]=t.split("-").map(Number),n=new Date(i,a-1,r);return isNaN(n)?null:n}if(/^-?\d+$/.test(t)){const i=Number(t),a=t.length<=10?i*1e3:i,r=new Date(a);return isNaN(r)?null:r}const s=new Date(t);return isNaN(s)?null:s}getRange(){return{start:this.currentStartDate,end:this.currentEndDate,combined:this.getCombinedValue()}}clear(){this.setRange("","")}setStartDate(e){this.setRange(e,this.currentEndDate)}setEndDate(e){this.setRange(this.currentStartDate,e)}getStartDate(){return this.currentStartDate}getEndDate(){return this.currentEndDate}setEnabled(e){this.disabled=!e,this.element?.querySelectorAll("input")?.forEach(s=>{s.disabled=this.disabled})}setReadonly(e){this.readonly=e,this.element?.querySelectorAll('input:not([type="hidden"])')?.forEach(s=>{s.readonly=e})}focus(){const e=this.getInputElement();e&&e.focus()}show(){this.picker&&this.easepickLoaded&&this.picker.show()}hide(){this.picker&&this.easepickLoaded&&this.picker.hide()}getFormValue(){return this.getRange()}async setFormValue(e){if(typeof e=="string")try{const t=JSON.parse(e);this.setRange(t.start,t.end)}catch{this.setRange(e,"")}else e&&typeof e=="object"&&this.setRange(e.start||"",e.end||"")}async onBeforeDestroy(){if(this.picker&&this.easepickLoaded)try{this.picker.destroy()}catch(e){console.warn("Error destroying Easepick range picker instance:",e)}this.picker=null,await super.onBeforeDestroy()}static create(e={}){return new ce(e)}}class de extends v{constructor(e={}){const{name:t,value:s="",placeholder:i="Select or type...",options:a=[],allowCustom:r=!0,showDescription:n=!0,minChars:l=0,maxSuggestions:o=10,disabled:c=!1,readonly:d=!1,required:h=!1,class:f="",inputClass:m="form-control",onSelect:p=null,onChange:g=null,...w}=e;super({tagName:"div",className:`combo-input ${f}`,...w}),this.name=t,this.placeholder=i,this.options=this.normalizeOptions(a),this.allowCustom=r,this.showDescription=n,this.minChars=l,this.maxSuggestions=o,this.disabled=c,this.readonly=d,this.required=h,this.inputClass=m,this.onSelectCallback=p,this.onChangeCallback=g,this.currentValue=s,this.inputValue=this.getDisplayValue(s),this.filteredOptions=[],this.highlightedIndex=-1,this.isOpen=!1,this.selectedOption=this.findOptionByValue(s)}normalizeOptions(e){return Array.isArray(e)?e.map(t=>typeof t=="string"?{value:t,label:t}:typeof t=="object"&&t.value!==void 0?{value:t.value,label:t.label||String(t.value),description:t.description||t.label||"",meta:t.meta||{}}:null).filter(t=>t!==null):[]}findOptionByValue(e){return this.options.find(t=>t.value===e)||null}getDisplayValue(e){const t=this.findOptionByValue(e);return t?t.label:e}async renderTemplate(){return`
|
|
944
|
+
<div class="combo-input-container position-relative">
|
|
945
|
+
<div class="input-wrapper position-relative">
|
|
946
|
+
${this.renderInput()}
|
|
947
|
+
${this.renderDropdownToggle()}
|
|
948
|
+
</div>
|
|
949
|
+
${this.renderHiddenInput()}
|
|
950
|
+
${this.renderDropdown()}
|
|
951
|
+
</div>
|
|
952
|
+
`}renderInput(){return`
|
|
953
|
+
<input type="text"
|
|
954
|
+
class="${this.inputClass} combo-input-field"
|
|
955
|
+
placeholder="${this.escapeHtml(this.placeholder)}"
|
|
956
|
+
value="${this.escapeHtml(this.inputValue)}"
|
|
957
|
+
${this.disabled?"disabled":""}
|
|
958
|
+
${this.readonly?"readonly":""}
|
|
959
|
+
${this.required?"required":""}
|
|
960
|
+
data-change-action="input-change"
|
|
961
|
+
data-action="input-keydown"
|
|
962
|
+
autocomplete="off"
|
|
963
|
+
role="combobox"
|
|
964
|
+
aria-expanded="${this.isOpen}"
|
|
965
|
+
aria-autocomplete="list"
|
|
966
|
+
aria-controls="combo-dropdown-${this.cid}">
|
|
967
|
+
`}renderDropdownToggle(){return this.readonly||this.disabled?"":`
|
|
968
|
+
<button type="button"
|
|
969
|
+
class="btn btn-sm combo-toggle position-absolute top-50 end-0 translate-middle-y border-0"
|
|
970
|
+
data-action="toggle-dropdown"
|
|
971
|
+
tabindex="-1"
|
|
972
|
+
aria-label="Toggle dropdown"
|
|
973
|
+
style="padding: 0.25rem 0.5rem;">
|
|
974
|
+
<i class="bi bi-chevron-down"></i>
|
|
975
|
+
</button>
|
|
976
|
+
`}renderHiddenInput(){return this.name?`
|
|
977
|
+
<input type="hidden"
|
|
978
|
+
name="${this.name}"
|
|
979
|
+
value="${this.escapeHtml(this.currentValue)}"
|
|
980
|
+
class="combo-input-hidden">
|
|
981
|
+
`:""}renderDropdown(){return`
|
|
982
|
+
<div id="combo-dropdown-${this.cid}"
|
|
983
|
+
class="combo-dropdown dropdown-menu position-absolute w-100 ${this.isOpen?"show":""}"
|
|
984
|
+
role="listbox"
|
|
985
|
+
style="max-height: 300px; overflow-y: auto; z-index: 1050;">
|
|
986
|
+
${this.renderDropdownContent()}
|
|
987
|
+
</div>
|
|
988
|
+
`}renderDropdownContent(){return this.filteredOptions.length===0?this.renderNoResults():this.filteredOptions.slice(0,this.maxSuggestions).map((e,t)=>this.renderOption(e,t)).join("")}renderOption(e,t){const s=t===this.highlightedIndex,i=e.value===this.currentValue;return`
|
|
989
|
+
<div class="dropdown-item combo-option ${s?"active":""} ${i?"selected":""}"
|
|
990
|
+
data-action="select-option"
|
|
991
|
+
data-option-index="${t}"
|
|
992
|
+
role="option"
|
|
993
|
+
aria-selected="${i}"
|
|
994
|
+
style="cursor: pointer;">
|
|
995
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
996
|
+
<div class="flex-grow-1">
|
|
997
|
+
<div class="combo-option-label fw-semibold">${this.highlightMatch(e.label)}</div>
|
|
998
|
+
${this.showDescription&&e.description?`
|
|
999
|
+
<div class="combo-option-description small text-muted">${this.escapeHtml(e.description)}</div>
|
|
1000
|
+
`:""}
|
|
1001
|
+
</div>
|
|
1002
|
+
${i?'<i class="bi bi-check text-primary ms-2"></i>':""}
|
|
1003
|
+
</div>
|
|
1004
|
+
</div>
|
|
1005
|
+
`}renderNoResults(){return this.allowCustom&&this.inputValue.length>=this.minChars?`
|
|
1006
|
+
<div class="dropdown-item-text text-muted small">
|
|
1007
|
+
<i class="bi bi-info-circle me-1"></i>
|
|
1008
|
+
${this.inputValue?"No matches found. Press Enter to use custom value.":"Start typing to see suggestions..."}
|
|
1009
|
+
</div>
|
|
1010
|
+
`:`
|
|
1011
|
+
<div class="dropdown-item-text text-muted small">
|
|
1012
|
+
<i class="bi bi-search me-1"></i>
|
|
1013
|
+
No matching options found.
|
|
1014
|
+
</div>
|
|
1015
|
+
`}highlightMatch(e){if(!this.inputValue)return this.escapeHtml(e);const t=this.escapeHtml(e),s=new RegExp(`(${this.escapeRegex(this.inputValue)})`,"gi");return t.replace(s,'<mark class="bg-warning bg-opacity-25">$1</mark>')}async onAfterRender(){await super.onAfterRender(),this.updateFilteredOptions(),this.handleOutsideClick=e=>{this.element&&!this.element.contains(e.target)&&this.closeDropdown()},document.addEventListener("click",this.handleOutsideClick)}async onChangeInputChange(e,t){this.inputValue=t.value,this.updateFilteredOptions(),this.inputValue.length>=this.minChars?this.openDropdown():this.closeDropdown(),this.highlightedIndex=-1,await this.updateDropdownDisplay()}async onActionInputKeydown(e,t){switch(e.key){case"ArrowDown":e.preventDefault(),this.isOpen?this.highlightNext():this.openDropdown(),await this.updateDropdownDisplay();break;case"ArrowUp":e.preventDefault(),this.isOpen&&(this.highlightPrevious(),await this.updateDropdownDisplay());break;case"Enter":e.preventDefault(),this.isOpen&&this.highlightedIndex>=0?await this.selectHighlightedOption():this.allowCustom&&this.inputValue&&await this.selectCustomValue(this.inputValue);break;case"Escape":e.preventDefault(),this.closeDropdown();const s=this.element.querySelector(".combo-input-field");s&&(s.value=this.getDisplayValue(this.currentValue),this.inputValue=s.value);break;case"Tab":this.isOpen&&this.closeDropdown();break}}async onActionToggleDropdown(e,t){if(e.preventDefault(),e.stopPropagation(),this.isOpen)this.closeDropdown();else{this.inputValue="";const s=this.element.querySelector(".combo-input-field");s&&(s.value="",s.focus()),this.updateFilteredOptions(),this.openDropdown(),await this.updateDropdownDisplay()}}async onActionSelectOption(e,t){e.preventDefault(),e.stopPropagation();const s=parseInt(t.getAttribute("data-option-index"));s>=0&&s<this.filteredOptions.length&&await this.selectOption(this.filteredOptions[s])}openDropdown(){this.isOpen=!0;const e=this.element?.querySelector(".combo-dropdown");e&&e.classList.add("show");const t=this.element?.querySelector(".combo-input-field");t&&t.setAttribute("aria-expanded","true")}closeDropdown(){this.isOpen=!1,this.highlightedIndex=-1;const e=this.element?.querySelector(".combo-dropdown");e&&e.classList.remove("show");const t=this.element?.querySelector(".combo-input-field");t&&t.setAttribute("aria-expanded","false")}updateFilteredOptions(){const e=this.inputValue.toLowerCase().trim();if(!e){this.filteredOptions=[...this.options];return}this.filteredOptions=this.options.filter(t=>{const s=t.label.toLowerCase().includes(e),i=String(t.value).toLowerCase().includes(e),a=t.description?.toLowerCase().includes(e);return s||i||a}),this.filteredOptions.sort((t,s)=>{const i=t.label.toLowerCase()===e,a=s.label.toLowerCase()===e;if(i&&!a)return-1;if(!i&&a)return 1;const r=t.label.toLowerCase().startsWith(e),n=s.label.toLowerCase().startsWith(e);return r&&!n?-1:!r&&n?1:0})}async updateDropdownDisplay(){const e=this.element?.querySelector(".combo-dropdown");if(e&&(e.innerHTML=this.renderDropdownContent(),this.highlightedIndex>=0)){const t=e.querySelector(".combo-option.active");t&&t.scrollIntoView({block:"nearest"})}}highlightNext(){this.filteredOptions.length!==0&&(this.highlightedIndex=(this.highlightedIndex+1)%Math.min(this.filteredOptions.length,this.maxSuggestions))}highlightPrevious(){this.filteredOptions.length!==0&&(this.highlightedIndex=this.highlightedIndex<=0?Math.min(this.filteredOptions.length,this.maxSuggestions)-1:this.highlightedIndex-1)}async selectHighlightedOption(){this.highlightedIndex>=0&&this.highlightedIndex<this.filteredOptions.length&&await this.selectOption(this.filteredOptions[this.highlightedIndex])}async selectOption(e){this.currentValue=e.value,this.inputValue=e.label,this.selectedOption=e;const t=this.element?.querySelector(".combo-input-field");t&&(t.value=e.label);const s=this.element?.querySelector(".combo-input-hidden");s&&(s.value=e.value),this.closeDropdown(),this.emit("select",{option:e,value:e.value,meta:e.meta}),this.emit("change",{value:e.value,option:e,meta:e.meta}),typeof this.onSelectCallback=="function"&&this.onSelectCallback(e),typeof this.onChangeCallback=="function"&&this.onChangeCallback(e.value)}async selectCustomValue(e){if(!this.allowCustom)return;this.currentValue=e,this.inputValue=e,this.selectedOption=null;const t=this.element?.querySelector(".combo-input-hidden");t&&(t.value=e),this.closeDropdown(),this.emit("custom",{value:e}),this.emit("change",{value:e,custom:!0}),typeof this.onChangeCallback=="function"&&this.onChangeCallback(e)}getValue(){return this.currentValue}async setValue(e){this.currentValue=e,this.selectedOption=this.findOptionByValue(e),this.inputValue=this.getDisplayValue(e);const t=this.element?.querySelector(".combo-input-field");t&&(t.value=this.inputValue);const s=this.element?.querySelector(".combo-input-hidden");s&&(s.value=e),this.updateFilteredOptions()}getSelectedOption(){return this.selectedOption}async setOptions(e){this.options=this.normalizeOptions(e),this.updateFilteredOptions(),this.isOpen&&await this.updateDropdownDisplay()}setEnabled(e){this.disabled=!e;const t=this.element?.querySelector(".combo-input-field");t&&(t.disabled=this.disabled);const s=this.element?.querySelector(".combo-toggle");s&&(s.disabled=this.disabled)}setReadonly(e){this.readonly=e;const t=this.element?.querySelector(".combo-input-field");t&&(e?t.setAttribute("readonly",""):t.removeAttribute("readonly"))}focus(){const e=this.element?.querySelector(".combo-input-field");e&&e.focus()}async clear(){await this.setValue(""),this.inputValue="";const e=this.element?.querySelector(".combo-input-field");e&&(e.value=""),this.emit("clear")}getFormValue(){return this.allowCustom&&this.inputValue&&this.inputValue!==this.getDisplayValue(this.currentValue)?this.inputValue:this.currentValue}async setFormValue(e){await this.setValue(e)}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=String(e),t.innerHTML}escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async onBeforeDestroy(){this.handleOutsideClick&&document.removeEventListener("click",this.handleOutsideClick),await super.onBeforeDestroy()}static create(e={}){return new de(e)}}class ze extends v{constructor(e={}){super(e),this.name=e.name||"combo",this.placeholder=e.placeholder||e.placeHolder||"Type or select...",this.value=e.value||"",this.options=(e.options||[]).map(t=>typeof t=="string"?{label:t,value:t}:typeof t=="object"&&t!==null?t:{label:t,value:t}),this.allowCustom=e.allowCustom!==!1,this.disabled=e.disabled||!1,this.required=e.required||!1,this.maxHeight=e.maxHeight||300,this.filteredOptions=[...this.options],this.highlightedIndex=-1,this.isOpen=!1,this.template=`
|
|
1016
|
+
<div class="combobox-container">
|
|
1017
|
+
<div class="input-group">
|
|
1018
|
+
<input type="text"
|
|
1019
|
+
class="form-control combobox-input"
|
|
1020
|
+
placeholder="{{placeholder}}"
|
|
1021
|
+
value="{{value}}"
|
|
1022
|
+
{{#disabled}}disabled{{/disabled}}
|
|
1023
|
+
{{#required}}required{{/required}}
|
|
1024
|
+
data-action="combobox-input"
|
|
1025
|
+
autocomplete="off">
|
|
1026
|
+
<button class="btn btn-outline-secondary combobox-toggle"
|
|
1027
|
+
type="button"
|
|
1028
|
+
data-action="combobox-toggle"
|
|
1029
|
+
{{#disabled}}disabled{{/disabled}}>
|
|
1030
|
+
<i class="bi bi-chevron-down"></i>
|
|
1031
|
+
</button>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div class="dropdown-menu combobox-dropdown"
|
|
1034
|
+
style="max-height: {{maxHeight}}px; overflow-y: auto; width: 100%;">
|
|
1035
|
+
<div data-region="dropdown-items"></div>
|
|
1036
|
+
{{^allowCustom}}
|
|
1037
|
+
<div class="combobox-no-match dropdown-item text-muted" style="display: none;">
|
|
1038
|
+
No matches found
|
|
1039
|
+
</div>
|
|
1040
|
+
{{/allowCustom}}
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
`,this.itemTemplate=`
|
|
1044
|
+
{{#items}}
|
|
1045
|
+
<button type="button"
|
|
1046
|
+
class="dropdown-item combobox-item {{#highlighted}}active{{/highlighted}}"
|
|
1047
|
+
data-action="select-item"
|
|
1048
|
+
data-value="{{value}}"
|
|
1049
|
+
data-index="{{index}}">
|
|
1050
|
+
{{label}}
|
|
1051
|
+
</button>
|
|
1052
|
+
{{/items}}
|
|
1053
|
+
`}async onInit(){await super.onInit()}async onAfterRender(){if(await super.onAfterRender(),this.input=this.element.querySelector(".combobox-input"),this.dropdown=this.element.querySelector(".combobox-dropdown"),this.dropdownItems=this.element.querySelector('[data-region="dropdown-items"]'),this.noMatchDiv=this.element.querySelector(".combobox-no-match"),this.value&&this.input){const e=this.options.find(t=>t.value===this.value);e?this.input.value=e.label||e.value:this.allowCustom&&(this.input.value=this.value)}this.renderItems(),this.setupEventListeners()}setupEventListeners(){this.input.addEventListener("focus",()=>this.openDropdown()),this.input.addEventListener("input",e=>this.handleInput(e)),this.input.addEventListener("keydown",e=>this.handleKeydown(e)),document.addEventListener("click",e=>{this.element.contains(e.target)||this.closeDropdown()})}handleInput(e){const t=e.target.value.toLowerCase();this.filteredOptions=this.options.filter(s=>(s.label||s.value).toLowerCase().includes(t)),this.highlightedIndex=-1,this.renderItems(),this.openDropdown(),!this.allowCustom&&this.noMatchDiv&&(this.noMatchDiv.style.display=this.filteredOptions.length===0?"block":"none"),this.value=e.target.value,this.emit("change",{value:this.value})}handleKeydown(e){if(!this.isOpen&&(e.key==="ArrowDown"||e.key==="ArrowUp")){this.openDropdown(),e.preventDefault();return}if(this.isOpen)switch(e.key){case"ArrowDown":e.preventDefault(),this.highlightedIndex=Math.min(this.highlightedIndex+1,this.filteredOptions.length-1),this.renderItems(),this.scrollToHighlighted();break;case"ArrowUp":e.preventDefault(),this.highlightedIndex=Math.max(this.highlightedIndex-1,-1),this.renderItems(),this.scrollToHighlighted();break;case"Enter":e.preventDefault(),this.highlightedIndex>=0&&this.selectItem(this.filteredOptions[this.highlightedIndex]);break;case"Escape":e.preventDefault(),this.closeDropdown();break;case"Tab":this.closeDropdown();break}}scrollToHighlighted(){if(this.highlightedIndex<0)return;const t=this.dropdownItems.querySelectorAll(".combobox-item")[this.highlightedIndex];t&&t.scrollIntoView({block:"nearest"})}openDropdown(){this.disabled||this.isOpen||(this.isOpen=!0,this.dropdown.classList.add("show"),this.input.value===""&&(this.filteredOptions=[...this.options],this.renderItems()))}closeDropdown(){this.isOpen&&(this.isOpen=!1,this.dropdown.classList.remove("show"),this.highlightedIndex=-1,this.allowCustom||!this.options.find(t=>t.value===this.input.value||t.label===this.input.value)&&this.input.value!==""&&(this.input.value=this.value))}selectItem(e){const t=e.value,s=e.label||e.value;this.input.value=s,this.value=t,this.closeDropdown(),this.filteredOptions=[...this.options],this.highlightedIndex=-1,this.emit("change",{value:this.value,label:s})}renderItems(){const e=this.filteredOptions.map((s,i)=>({value:s.value,label:s.label||s.value,index:i,highlighted:i===this.highlightedIndex})),t=E.render(this.itemTemplate,{items:e});this.dropdownItems.innerHTML=t}async onActionComboboxInput(e,t){}async onActionComboboxToggle(e,t){this.isOpen?this.closeDropdown():(this.input.focus(),this.openDropdown())}async onActionSelectItem(e,t){const s=t.getAttribute("data-value"),i=this.options.find(a=>a.value===s);i&&this.selectItem(i)}getValue(){return this.value}setValue(e){if(this.value=e,!this.input)return;const t=this.options.find(s=>s.value===e);t?this.input.value=t.label||t.value:this.allowCustom&&(this.input.value=e)}setFormValue(e){this.setValue(e)}getTemplateData(){return{placeholder:this.placeholder,value:this.input?this.input.value:this.value,disabled:this.disabled,required:this.required,maxHeight:this.maxHeight,allowCustom:this.allowCustom}}}class H extends v{constructor(e={}){const{formConfig:t=e.config,fields:s,model:i=null,data:a={},defaults:r=null,errors:n={},fileHandling:l="base64",autosaveModelField:o=!1,...c}=e;super({tagName:"div",className:"form-view",...c}),q.onFormViewInit?.(this),this.model=i,this.defaults=r||a,this._originalData=a,this.errors=n,this.loading=!1,this.fileHandling=l,this.autosaveModelField=o,this.customComponents=new Map,this.fieldStatusManagers=new Map,this.saveTimeouts=new Map,this.pendingSaveFields=new Map,this.batchSaveTimeout=null,this.isSaving=!1,this.data=this.prepareFormData(),this.formConfig=t||{fields:s||[]},this.formBuilder=new J({...this.getFormConfig(),data:this.data,errors:n})}prepareFormData(){const e={...this.defaults};if(this.model)if(this.model.attributes&&typeof this.model.attributes=="object")Object.assign(e,this.model.attributes);else if(typeof this.model.toJSON=="function"){const t=this.model.toJSON();Object.assign(e,t)}else typeof this.model=="object"&&this.model.constructor===Object&&Object.assign(e,this.model);return this._originalData&&Object.assign(e,this._originalData),e}getFormConfig(){const e={...this.formConfig},t=this.getApp();return this.formConfig.fields&&Array.isArray(this.formConfig.fields)?e.fields=this.formConfig.fields.filter(s=>s.permissions?t.activeUser?.hasPermission(s.permissions):!0):e.fields=[],e}async renderTemplate(){return this.formBuilder.buildFormHTML()}async onAfterRender(){await super.onAfterRender(),this.data=this.prepareFormData(),this.populateFormValues(),this.initializeFormComponents(),this.initializeChangeHandlers();const e=this.getFormElement();e&&e.addEventListener("submit",t=>(t.preventDefault(),!1)),q.onFormViewAfterRender?.(this)}populateFormValues(){if(!(!this.element||!this.formConfig?.fields)){this._isPopulating=!0;try{this.formConfig.fields.forEach(e=>{this.populateFieldRecursive(e)})}finally{this._isPopulating=!1}}}populateFieldRecursive(e){e.type==="group"&&e.fields?e.fields.forEach(t=>{this.populateFieldRecursive(t)}):e.type==="tabset"&&e.tabs?e.tabs.forEach(t=>{t.fields&&Array.isArray(t.fields)&&t.fields.forEach(s=>{this.populateFieldRecursive(s)})}):this.populateFieldValue(e)}populateFieldValue(e){if(!e.name||!this.element)return;const t=this.element.querySelector(`[name="${e.name}"]`);if(!t)return;const s=x.getContextData(this.data,e.name);s!=null&&this.setFieldValue(t,e,s)}initializeFormComponents(){this.initializeImageFields(),this.initializeCustomComponents(),this.initializeTagInputs(),this.initializeMultiSelectDropdowns(),this.initializeComboBoxes(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.initializePasswordFields()}initializeImageFields(){this.element.querySelectorAll(".image-drop-zone.droppable").length>0&&this.enableFileDrop({acceptedTypes:["image/*"],maxFileSize:10485760,multiple:!1,dropZoneSelector:".image-drop-zone.droppable",visualFeedback:!0,dragOverClass:"drag-over",dragActiveClass:"drag-active"})}initializeCustomComponents(){this.initializeTagInputs(),this.initializeCollectionSelects(),this.initializeCollectionMultiSelects(),this.initializeDatePickers(),this.initializeDateRangePickers(),this.initializeComboInputs();try{const t=(s=[])=>{s.forEach(i=>{if(i&&i.type==="group"&&Array.isArray(i.fields))t(i.fields);else if(i&&i.name){const a=this.element.querySelector(`[name="${i.name}"], #${i.id||i.name}`);a&&q.onFieldInit?.(this,a,i)}})};t(this.formConfig?.fields||[])}catch(t){console.warn("FormPlugins.onFieldInit error:",t)}this.element.querySelectorAll("[data-component]").forEach(t=>{t.getAttribute("data-component"),t.getAttribute("data-field")})}initializeChangeHandlers(){if(!this.element)return;const e=this.element.querySelectorAll("input:not([data-action]), select:not([data-action]), textarea:not([data-action])");console.log("FormView: initializeChangeHandlers - found",e.length,"inputs"),e.forEach(t=>{console.log("FormView: Processing input:",t.type,t.name,t.getAttribute("data-change-action")),!(t.hasAttribute("data-component")||t.classList.contains("form-check-input"))&&(t.addEventListener("change",s=>{if(this._isPopulating)return;const i=t.name;if(i){let a=t.value;if(t.type==="checkbox")a=t.checked;else if(t.type==="radio"){if(!t.checked)return}else if(t.multiple&&t.selectedOptions)a=Array.from(t.selectedOptions).map(r=>r.value);else if(t.type==="file"){const r=t.getAttribute("data-change-action");if(r==="image-selected"){this.onChangeImageSelected(s,t);return}else if(r==="file-selected"){this.onChangeFileSelected(s,t);return}}this.handleFieldChange(i,a)}}),(t.type==="text"||t.type==="email"||t.type==="url"||t.tagName==="TEXTAREA")&&t.addEventListener("input",s=>{if(this._isPopulating)return;const i=t.name;i&&this.handleFieldChange(i,t.value)}))})}initializeTagInputs(){this.element.querySelectorAll('[data-field-type="tag"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=new le({...a,containerId:null});r.render(!0,t),this.customComponents.set(s,r),r.on("change",n=>{this.handleFieldChange(s,n.value)})}catch{}})}initializeMultiSelectDropdowns(){this.element.querySelectorAll('[data-field-type="multiselect"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=this.getFormFieldConfig(s);if(!r)return;const n=new Re({...a,options:r.options||[],placeholder:r.placeholder||a.placeholder||"Select...",label:r.label,containerId:null});let l=a.value??x.getContextData(this.data,s);l&&n.setFormValue(l),n.render(!0,t),this.customComponents.set(s,n),n.on("change",o=>{this.handleFieldChange(s,o.value)})}catch(s){console.error("MultiSelectDropdown initialization failed:",s)}})}initializeComboBoxes(){this.element.querySelectorAll('[data-field-type="combobox"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=this.getFormFieldConfig(s);if(!r)return;const n=new ze({...a,options:r.options||[],placeholder:r.placeholder||a.placeholder||"Type or select...",containerId:null});let l=a.value??x.getContextData(this.data,s);l&&n.setFormValue(l),n.render(!0,t),this.customComponents.set(s,n),n.on("change",o=>{this.handleFieldChange(s,o.value)})}catch(s){console.error("ComboBox initialization failed:",s)}})}initializeCollectionSelects(){this.element.querySelectorAll('[data-field-type="collection"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=this.getFormFieldConfig(s);if(!r||!r.Collection)return;const n=new r.Collection;r.collectionParams&&(n.params={...n.params,...r.collectionParams});const l=new Ne({...a,collection:n,defaultParams:r.defaultParams||null,containerId:null});let o=x.getContextData(this.data,s);o&&l.setFormValue(o),l.render(!0,t),this.customComponents.set(s,l),l.on("change",c=>{this.handleFieldChange(s,c.value)})}catch{}})}initializeCollectionMultiSelects(){this.element.querySelectorAll('[data-field-type="collectionmultiselect"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=this.getFormFieldConfig(s);if(!r||!r.Collection)return;const n=new r.Collection;r.collectionParams&&(n.params={...n.params,...r.collectionParams});const l=new qe({...a,collection:n,defaultParams:r.defaultParams||null,itemTemplate:r.itemTemplate||null,containerId:null});let o=x.getContextData(this.data,s);o&&l.setFormValue(o),l.render(!0,t),this.customComponents.set(s,l),l.on("change",c=>{this.handleFieldChange(s,c.value)})}catch(s){console.error("CollectionMultiSelect initialization failed:",s)}})}initializeDatePickers(){this.element.querySelectorAll('[data-field-type="datepicker"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=new oe({...a,containerId:null});r.render(!0,t),this.customComponents.set(s,r),r.on("change",n=>{this.handleFieldChange(s,n.value)})}catch{}})}initializeDateRangePickers(){this.element.querySelectorAll('[data-field-type="daterange"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=new ce({...a,containerId:null});r.render(!0,t),this.customComponents.set(s,r),r.on("change",n=>{this.handleFieldChange(s,n.combined)})}catch{}})}initializeComboInputs(){this.element.querySelectorAll('[data-field-type="combo"]').forEach(t=>{try{const s=t.getAttribute("data-field-name"),i=t.getAttribute("data-field-config"),a=JSON.parse(i),r=new de({...a,containerId:null});let n=x.getContextData(this.data,s);n&&r.setValue(n),r.render(!0,t),this.customComponents.set(s,r),r.on("change",l=>{this.handleFieldChange(s,l.value)}),r.on("select",l=>{this.emit("field:select",{field:s,value:l.value,option:l.option,meta:l.meta})})}catch(s){console.error("ComboInput initialization failed:",s)}})}handleFieldChange(e,t){this._isPopulating||(this.data[e]=t,this.autosaveModelField&&this.model?this.handleFieldSave(e,t):this.model&&this.options.allowModelChange&&(this._isFormDrivenChange=!0,this.model.set(e,t)),this.emit("field:change",{field:e,value:t}),q.onFieldChange?.(this,e,t))}async handleFieldSave(e,t){if(!this.model)return;this.pendingSaveFields.set(e,t),this.getFieldStatusManager(e).showStatus("saving"),this.batchSaveTimeout&&clearTimeout(this.batchSaveTimeout),this.batchSaveTimeout=setTimeout(async()=>{await this.executeBatchSave()},300)}async executeBatchSave(){if(this.isSaving||this.pendingSaveFields.size===0)return;const e=Object.fromEntries(this.pendingSaveFields),t=Array.from(this.pendingSaveFields.keys());try{if(this.isSaving=!0,this.pendingSaveFields.clear(),this.batchSaveTimeout=null,this._isFormDrivenChange=!0,typeof this.model.save=="function"){const s=await this.model.save(e);if(!s||!s.success||s.data&&!s.data.status){const i=s?.data?.error||s?.error||s?.message||"Save failed";this.getApp()?.toast?.error(i),this.revertFields(t),t.forEach(a=>{this.getFieldStatusManager(a).showStatus("error",{message:i})});return}}else Object.entries(e).forEach(([s,i])=>{this.model.set(s,i)});t.forEach(s=>{this.getFieldStatusManager(s).showStatus("saved")})}catch(s){console.error("Batch save error:",s),this.getApp()?.toast?.error(s.message||"An error occurred while saving"),this.revertFields(t),t.forEach(i=>{this.getFieldStatusManager(i).showStatus("error",{message:s.message})})}finally{this.isSaving=!1}}revertFields(e){if(!this.model)return;const t=this._isPopulating;this._isPopulating=!0;try{e.forEach(s=>{const i=this.model.get(s);this.data[s]=i;const a=this.element?.querySelector(`[name="${s}"]`);if(a){const r=this.getFormFieldConfig(s);r?this.setFieldValue(a,r,i):a.type==="checkbox"?a.checked=!!i:a.value=i??""}})}finally{this._isPopulating=t}}getFieldStatusManager(e){if(!this.fieldStatusManagers.has(e)){const t=this.element.querySelector(`[name="${e}"]`);if(t){const s=new Be(t);this.fieldStatusManagers.set(e,s)}}return this.fieldStatusManagers.get(e)}refreshForm(){this.data=this.prepareFormData(),this.element&&this.populateFormValues()}getChangeReason(e,t){if(e instanceof File)return e.size===0||e.name===""||e.name==="blob"?"empty file, no change":`file upload: ${e.name}, ${e.size} bytes`;if(typeof e=="string"&&e.startsWith("data:image/"))return"base64 image upload";if(typeof e=="boolean"||typeof t=="boolean"){const s=!!e;return`boolean: ${t==null?!1:!!t} → ${s}`}return e==null||String(e).trim(),t==null||String(t).trim(),t==null?"was null/undefined, now has value":e==null?"was value, now null/undefined":"text content changed"}setFormData(e){this._originalData={...this._originalData,...e},this.refreshForm()}async onActionSubmitForm(e,t){e.preventDefault();const s=await this.handleSubmit();s.success?(this.data=s.data,this.emit("submit",{data:s.data,result:s.result,form:this,event:e}),!this.model&&this.formConfig.onSubmit&&typeof this.formConfig.onSubmit=="function"&&await this.formConfig.onSubmit(s.data,this)):this.emit("error",{error:s.error,result:s,form:this})}async onActionResetForm(e,t){const s=this.getFormElement();s&&(s.reset(),this.data={},this.clearAllErrors(),this.emit("reset",{form:this,event:e}))}async onActionClickImageUpload(e,t){console.log("FormView: onActionClickImageUpload called"),console.log("FormView: element:",t);const s=t.getAttribute("data-field-id");if(console.log("FormView: fieldId:",s),!s){console.error("FormView: No fieldId attribute found");return}const i=this.element.querySelector(`#${s}`);console.log("FormView: fileInput:",i),i&&!i.disabled?(i.click(),console.log("FormView: fileInput.click() called")):i?console.log("FormView: fileInput is disabled"):console.error("FormView: fileInput not found for fieldId:",s)}async onActionRemoveImage(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",i.dispatchEvent(new Event("change",{bubbles:!0}))),delete this.data[s],this.emit("change",{field:s,value:null,form:this}),await this.updateField(s)}async onActionClearColor(e,t){const s=t.getAttribute("data-field");if(!s)return;const i=this.element.querySelector(`input[name="${s}"]`);i&&(i.value="",this.handleFieldChange(s,""),await this.updateField(s))}async onActionPreviewHtml(e,t){e.preventDefault();const s=t.getAttribute("data-target");if(!s)return;const i=this.element.querySelector(`#${s}`);if(!i)return;const a=i.value||"";(await Promise.resolve().then(()=>L)).default.showHtmlPreview({html:a,title:"HTML Preview"})}async onActionSelectButtonOption(e,t){const s=t.getAttribute("data-field"),i=t.getAttribute("data-value");if(!s||!i)return;this.data[s]=i;const a=t.closest(".btn-group");a&&(a.querySelectorAll("button").forEach(n=>{n.classList.remove("active"),n.classList.add("btn-outline-primary"),n.classList.remove("btn-primary")}),t.classList.add("active"),t.classList.remove("btn-outline-primary"),t.classList.add("btn-primary")),this.emit("field:changed",{field:s,value:i,form:this}),this.emit("change",{field:s,value:i,form:this}),this.emit("form:changed",await this.getFormData())}async onActionApplyFilter(e,t){const s=t.closest(".dropdown"),i=s?.querySelectorAll('input[type="checkbox"]');if(!i||i.length===0)return;const a=i[0].getAttribute("data-field");if(!a)return;const r=[];i.forEach(l=>{l.checked&&r.push(l.value)}),this.data[a]=r;const n=s.querySelector('[data-bs-toggle="dropdown"]');n&&window.bootstrap?.Dropdown&&window.bootstrap.Dropdown.getInstance(n)?.hide(),this.emit("field:changed",{field:a,value:r,form:this}),this.emit("change",{field:a,value:r,form:this}),this.emit("form:changed",await this.getFormData())}async onChangeValidateField(e,t){const s=t.name;if(s){const i=t.value;this.handleFieldChange(s,i),this.validateField(s)}}async onChangeToggleSwitch(e,t){const s=t.getAttribute("data-field");if(s){const i=t.checked;this.handleFieldChange(s,i),this.emit("switch:toggle",{field:s,checked:i,form:this})}}async onChangeImageSelected(e,t){console.log("FormView: onChangeImageSelected called"),console.log("FormView: element:",t),console.log("FormView: element.files:",t.files);const s=t.getAttribute("data-field"),i=t.files[0];if(console.log("FormView: fieldName:",s),console.log("FormView: file:",i),s&&i){console.log("FormView: fieldName and file exist, processing...");const a=this.findFieldConfig(s);console.log("FormView: fieldConfig:",a);const r=URL.createObjectURL(i);if(console.log("FormView: previewUrl created:",r),a&&a.imageSize){console.log("FormView: Image cropping is required, imageSize:",a.imageSize);try{const n=window.MOJO?.plugins?.ImageCropView;if(console.log("FormView: ImageCropView available?",!!n),!n){console.log("FormView: ImageCropView not available, falling back to normal handling"),this.data[s]=i,await this.updateImagePreview(s,r),this.emit("image:selected",{field:s,file:i,form:this});return}const l=await n.showDialog(r,{title:`Crop ${a.label||s}`,cropAndScale:a.imageSize,size:"lg"});if(l.action==="crop"&&l.data){const c=await(await fetch(l.data)).blob(),d=new File([c],i.name,{type:i.type||"image/png"});this.data[s]=d,await this.updateImagePreview(s,l.data),this.emit("image:selected",{field:s,file:d,originalFile:i,cropped:!0,cropData:l.cropData,form:this}),this.emit("change",{field:s,value:d,form:this})}else t.value=""}catch(n){console.error("FormView: Error during image cropping:",n),console.log("FormView: Falling back to normal image handling after error"),this.data[s]=i,await this.updateImagePreview(s,r),this.emit("image:selected",{field:s,file:i,form:this}),this.emit("change",{field:s,value:i,form:this})}}else console.log("FormView: Normal image handling (no cropping)"),this.data[s]=i,console.log("FormView: File stored in this.data["+s+"]"),await this.updateImagePreview(s,r),console.log("FormView: updateImagePreview completed"),this.emit("image:selected",{field:s,file:i,form:this}),console.log("FormView: image:selected event emitted")}else console.log("FormView: Missing fieldName or file - not processing")}async onChangeFileSelected(e,t){const s=Array.from(t.files);t.multiple?this.data[t.name]=t.files:this.data[t.name]=s[0]||null,this.emit("file:selected",{field:t.name,files:s,form:this}),this.emit("change",{field:t.name,value:s,form:this})}async onChangeRangeChanged(e,t){const s=t.name,i=t.value,a=t.getAttribute("data-target");if(a){const r=this.element.querySelector(`#${a}`);r&&(r.textContent=i)}s&&this.handleFieldChange(s,i),this.emit("range:changed",{field:s,value:i,form:this})}async onChangeFilterSearch(e,t){const s=t.value;this.emit("search",{query:s,field:t.name,form:this})}async onChangeFilterSelectOptions(e,t){const s=t.value.toLowerCase(),i=t.getAttribute("data-target"),a=i?this.element.querySelector(`#${i}`):null;a&&a.querySelectorAll("option").forEach(n=>{const l=n.textContent.toLowerCase();n.style.display=l.includes(s)?"":"none"})}async onFileDrop(e,t,s){const i=t.target.closest(".image-drop-zone");if(!i)return;const a=i.getAttribute("data-field");if(!a)return;const r=e[0],n=this.element.querySelector(`input[name="${a}"]`);if(n){const o=new DataTransfer;o.items.add(r),n.files=o.files,n.dispatchEvent(new Event("change",{bubbles:!0}))}this.data[a]=r;const l=URL.createObjectURL(r);await this.updateImagePreview(a,l),this.emit("image:dropped",{field:a,file:r,form:this})}async onFileDropError(e,t,s){this.showError(`File upload error: ${e.message}`),this.emit("file:error",{error:e,files:s,form:this})}getFormElement(){return this.element?this.element.querySelector("form"):null}getFormFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if(i.fields&&Array.isArray(i.fields)){const a=t(i.fields);if(a)return a}}return null};return t(this.formConfig.fields||[])}async getFormData(){const e=this.getFormElement();if(!e)return this.fileHandling==="multipart"?new FormData:{};if(this.fileHandling==="multipart"){const t=new FormData(e);for(const[s,i]of Object.entries(this.data))if(i instanceof File)t.set(s,i);else if(i instanceof FileList)for(let a=0;a<i.length;a++)t.append(`${s}[${a}]`,i[a]);return t}else{const t=new FormData(e),s={};for(const[n,l]of t.entries())s[n]?(Array.isArray(s[n])||(s[n]=[s[n]]),s[n].push(l)):s[n]=l;e.querySelectorAll('input[type="checkbox"]').forEach(n=>{s[n.name]=n.checked}),e.querySelectorAll('input[type="number"]').forEach(n=>{if(n.name&&s[n.name]!==void 0&&s[n.name]!==""){const l=Number(s[n.name]);isNaN(l)||(s[n.name]=l)}}),this.formConfig.fields?.forEach(n=>{if(n.type==="select"&&n.name&&s[n.name]!==void 0){const l=this.getFormFieldConfig(n.name);if(l?.options&&Array.isArray(l.options)&&l.options.every(c=>{const d=typeof c=="object"?c.value:c;return d===""||!isNaN(Number(d))})&&s[n.name]!==""){const c=Number(s[n.name]);isNaN(c)||(s[n.name]=c)}}}),e.querySelectorAll('[data-field-type="json"]').forEach(n=>{try{s[n.name]=JSON.parse(n.value)}catch{s[n.name]=n.value}}),this.customComponents.forEach((n,l)=>{n.getFormValue?s[l]=n.getFormValue():n.getValue&&(s[l]=n.getValue())});for(const[n,l]of Object.entries(this.data))if(l instanceof File)try{s[n]=await this.fileToBase64(l)}catch{s[n]=null}else if(l instanceof FileList){const o=[];for(let c=0;c<l.length;c++)try{o.push(await this.fileToBase64(l[c]))}catch{o.push(null)}s[n]=o}return s}}_onModelChange(){this.isSaving||(this.data=this.prepareFormData(),this.isMounted()&&(this._isFormDrivenChange||this.syncFormWithModel(),this._isFormDrivenChange=!1))}syncFormWithModel(){!this.model||!this.element||this.formDataMatchesModelData(this.data)||this.populateFormValues()}formDataMatchesModelData(e){if(!this.formConfig?.fields||!this.element)return!0;for(const t of this.formConfig.fields)if(t.type==="group"&&t.fields){for(const s of t.fields)if(!this.fieldValueMatchesModel(s,e))return!1}else if(!this.fieldValueMatchesModel(t,e))return!1;return!0}fieldValueMatchesModel(e,t){if(!e.name)return!0;const s=this.element.querySelector(`[name="${e.name}"]`);if(!s)return!0;const i=this.getFieldCurrentValue(s,e),a=x.getContextData(t,e.name);return this.valuesAreDifferent(i,a)===!1}getFieldCurrentValue(e,t){switch(t.type){case"checkbox":case"toggle":case"switch":return e.checked;case"radio":const s=this.element.querySelector(`[name="${t.name}"]:checked`);return s?s.value:"";case"select":return e.multiple?Array.from(e.selectedOptions).map(i=>i.value):e.value;case"file":case"image":return null;case"json":try{return e.value?JSON.parse(e.value):null}catch{return e.value}default:return e.value}}setFieldValue(e,t,s){switch(t.type){case"checkbox":case"toggle":case"switch":e.checked=!!s;break;case"radio":const i=this.element.querySelector(`[name="${t.name}"][value="${s}"]`);i&&(i.checked=!0);break;case"select":e.multiple&&Array.isArray(s)?Array.from(e.options).forEach(a=>{a.selected=s.includes(a.value)}):e.value=s??"";break;case"file":case"image":break;case"json":if(typeof s=="object"&&s!==null)try{e.value=JSON.stringify(s,null,2)}catch{e.value="{}"}else typeof s=="string"?e.value=s:e.value=String(s||"");break;default:e.value=s||"";break}e.dispatchEvent(new Event("change",{bubbles:!0}))}setDefaults(e){this.defaults={...this.defaults,...e},this.refreshForm()}async handleSubmit(){try{const e=await this.getFormData();if(this.formConfig.validateOnSubmit!==!1&&!this.validate())return this.focusFirstError(),{success:!1,data:e,error:"Form validation failed"};if(this.model&&typeof this.model.save=="function"){const t=await this.saveModel(e);if(t&&t.success!==!1)return{success:!0,data:e,result:t};{const s=t?.message||t?.error||"Save failed. Please try again.";return{success:!1,data:e,result:t,error:s}}}else return e}catch(e){return{success:!1,error:e.message||"An error occurred while submitting the form"}}}async saveModel(e=null){if(!this.model||typeof this.model.save!="function")throw new Error("No model available for saving");e||(e=await this.getFormData());const t=this.getChangedData(e);if(!t||Object.keys(t).length===0)return{success:!0,message:"No changes to save",data:e};try{return this._isFormDrivenChange=!0,await this.model.save(t)}catch(s){throw s}}getChangedData(e){if(!this.model)return e;const t=this.getOriginalModelData();let s;return e instanceof FormData?s=this.getChangedFormData(e,t):s=this.getChangedObjectData(e,t),s}getOriginalModelData(){return this.model.attributes?this.model.attributes:typeof this.model.toJSON=="function"?this.model.toJSON():{}}getChangedFormData(e,t){const s=new FormData;let i=!1;for(const[a,r]of e.entries())if(r instanceof File)r.size===0||r.name===""||r.name==="blob"||(s.set(a,r),i=!0);else{const n=t[a];r!==n&&r!==String(n)&&(s.set(a,r),i=!0)}return i?s:null}getChangedObjectData(e,t){const s={};let i=!1;const a=new Set([...Object.keys(t),...Object.keys(e)]),r=(n,l)=>l.split(".").reduce((o,c)=>o&&typeof o=="object"?o[c]:void 0,n);for(const n of a){const l=this.findFieldConfig(n);if(!l)continue;const o=e[n],c=r(t,n),d=l.type||"text";this.valuesAreDifferent(o,c,d,l)&&(s[n]=o,i=!0)}return i?s:null}valuesAreDifferent(e,t,s="text",i={}){if(e instanceof File)return e.size>0&&e.name!==""&&e.name!=="blob";if(typeof e=="string"&&e.startsWith("data:image/"))return!0;if(s==="collection"&&typeof t=="object"&&t!==null&&t!==void 0&&typeof e=="string"){if(e==="0")return t!==null;const n=i.valueField||"id";if(t[n]==e)return!1}if(s==="switch"||s==="checkbox"||s==="toggle")return!!e!==!!t;const a=e==null?"":String(e).trim(),r=t==null?"":String(t).trim();return a!==r}validate(){const e=this.getFormElement();if(!e)return!1;const t=e.checkValidity();return t||e.classList.add("was-validated"),t}validateField(e){const t=this.getFormElement();if(!t)return!1;const s=t.elements[e];if(!s)return!1;const i=s.checkValidity();return i?(s.classList.remove("is-invalid"),s.classList.add("is-valid"),delete this.errors[e]):(s.classList.remove("is-valid"),s.classList.add("is-invalid"),this.errors[e]=s.validationMessage),i}focusFirstError(){const e=this.getFormElement();if(!e)return;const t=e.querySelector(":invalid");if(!t)return;const s=t.closest(".tab-pane");if(s&&!s.classList.contains("active")){const i=s.id,a=e.querySelector(`[role="tab"][aria-controls="${i}"], [data-bs-target="#${i}"]`);if(a){const r=window.bootstrap?.Tab?.getOrCreateInstance?window.bootstrap.Tab.getOrCreateInstance(a):null;r&&typeof r.show=="function"?r.show():(e.querySelectorAll('[role="tab"].nav-link').forEach(o=>{const c=o===a;o.classList.toggle("active",c),o.setAttribute("aria-selected",c?"true":"false")}),e.querySelectorAll(".tab-pane").forEach(o=>o.classList.remove("show","active")),s.classList.add("show","active"))}}t.focus(),t.scrollIntoView({behavior:"smooth",block:"center"})}clearAllErrors(){const e=this.getFormElement();if(!e)return;this.errors={},e.classList.remove("was-validated"),e.querySelectorAll(".is-invalid").forEach(i=>i.classList.remove("is-invalid")),e.querySelectorAll(".is-valid").forEach(i=>i.classList.remove("is-valid"))}setLoading(e){this.loading=e;const t=this.getFormElement();if(!t)return;const s=t.querySelectorAll("input, select, textarea, button"),i=t.querySelector('button[type="submit"]');if(e)s.forEach(a=>a.disabled=!0),i&&(i.innerHTML='<span class="spinner-border spinner-border-sm me-2"></span>Loading...');else if(s.forEach(a=>a.disabled=!1),i){const a=this.formConfig.options?.submitButton||"Submit";i.innerHTML=typeof a=="string"?a:"Submit"}}showError(e){if(this.emit("error",{message:e,form:this}),this.element){this.element.querySelectorAll(".alert").forEach(i=>i.remove());const s=document.createElement("div");s.className="alert alert-danger alert-dismissible fade show",s.innerHTML=`
|
|
1054
|
+
${e}
|
|
1055
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
1056
|
+
`,this.element.insertBefore(s,this.element.firstChild),setTimeout(()=>{s.parentNode&&s.remove()},5e3)}}async updateField(e){this.formBuilder=new J({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async updateImagePreview(e,t){const s=this.element.querySelector(`[data-field="${e}"].image-drop-zone`);if(!s)return;let i=s.querySelector("img");const a=s.querySelector(".bi-image")?.parentElement,r=s.getAttribute("data-field-id");if(t){if(i)i.src=t;else{const n=`${r}_preview`;s.innerHTML=`
|
|
1057
|
+
<img id="${n}"
|
|
1058
|
+
src="${t}"
|
|
1059
|
+
alt="Preview"
|
|
1060
|
+
class="img-thumbnail w-100 h-100"
|
|
1061
|
+
style="object-fit: cover;">
|
|
1062
|
+
<button type="button"
|
|
1063
|
+
class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1"
|
|
1064
|
+
data-action="remove-image"
|
|
1065
|
+
data-field-id="${r}"
|
|
1066
|
+
data-field="${e}"
|
|
1067
|
+
style="opacity: 0.8;">
|
|
1068
|
+
<i class="bi bi-x"></i>
|
|
1069
|
+
</button>
|
|
1070
|
+
`}a&&(a.style.display="none")}}findFieldConfig(e){const t=s=>{for(const i of s){if(i.name===e)return i;if(i.fields&&Array.isArray(i.fields)){const a=t(i.fields);if(a)return a}if(i.tabs&&Array.isArray(i.tabs)){for(const a of i.tabs)if(a.fields&&Array.isArray(a.fields)){const r=t(a.fields);if(r)return r}}}return null};return t(this.formConfig.fields||[])}async fileToBase64(e){return new Promise((t,s)=>{const i=new FileReader;i.onload=()=>t(i.result),i.onerror=s,i.readAsDataURL(e)})}hasFiles(e){if(e instanceof FormData){for(const[t,s]of e.entries())if(s instanceof File)return!0;return!1}else{for(const t of Object.values(e))if(t instanceof File||Array.isArray(t)&&t.some(s=>s instanceof File))return!0;return!1}}reset(){const e=this.getFormElement();e&&e.reset(),this.data={},this.errors={},this.clearAllErrors(),this.emit("reset",{form:this})}async updateConfig(e){this.formConfig={...this.formConfig,...e},this.formBuilder=new J({...this.getFormConfig(),data:this.data,errors:this.errors}),await this.render()}async onBeforeDestroy(){const e=[];for(const t of this.customComponents.values())t.destroy&&e.push(t.destroy());await Promise.all(e),this.customComponents.clear(),Object.values(this.data).forEach(t=>{typeof t=="string"&&t.startsWith("blob:")&&URL.revokeObjectURL(t)}),await super.onBeforeDestroy()}initializePasswordFields(){if(!this.element)return;this.element.querySelectorAll('input[data-field-type="password"], input[type="password"]').forEach(t=>{this.updatePasswordStrengthUI(t);const s=a=>{this.updatePasswordStrengthUI(a.target)};t.addEventListener("input",s);const i=a=>{if(typeof a.getModifierState=="function"){const r=a.getModifierState("CapsLock");this.updateCapsLockWarning(t,!!r)}};t.addEventListener("keydown",i),t.addEventListener("keyup",i),this.updateCapsLockWarning(t,!1)})}async onActionTogglePassword(e,t){e.preventDefault();const s=t.getAttribute("data-target");let i=null;if(s&&(i=this.element.querySelector("#"+s)),!i){const n=t.closest(".input-group");n&&(i=n.querySelector('input[type="password"], input[data-field-type="password"], input[type="text"]'))}if(!i)return;const a=i.type==="password";i.type=a?"text":"password",t.setAttribute("aria-pressed",a?"true":"false"),t.setAttribute("aria-label",a?"Hide password":"Show password");const r=t.querySelector("i");r&&(r.classList.toggle("bi-eye",!a),r.classList.toggle("bi-eye-slash",a)),i.focus();try{const n=i.value?.length??0;i.setSelectionRange(n,n)}catch{}}async onActionCopyToClipboard(e,t){e.preventDefault();const s=t.getAttribute("data-target");if(!s)return;const i=this.element.querySelector("#"+s);if(i)try{await navigator.clipboard.writeText(i.value);const a=t.querySelector("i");if(a){const r=a.className;a.className="bi bi-check2",t.classList.add("btn-success"),t.classList.remove("btn-outline-secondary"),setTimeout(()=>{a.className=r,t.classList.remove("btn-success"),t.classList.add("btn-outline-secondary")},1500)}this.app&&this.app.toast&&this.app.toast.success("Copied to clipboard!")}catch(a){console.error("Failed to copy to clipboard:",a),this.app&&this.app.toast&&this.app.toast.error("Failed to copy to clipboard")}}computePasswordStrength(e=""){const t=e.length;let s=0;t>=6&&s++,t>=8&&s++,t>=12&&s++;const i=/[a-z]/.test(e),a=/[A-Z]/.test(e),r=/\d/.test(e),n=/[^A-Za-z0-9]/.test(e),l=[i,a,r,n].filter(Boolean).length;return l>=2&&s++,l>=3&&s++,s=Math.max(0,Math.min(4,s)),[{percent:0,label:"Too short",barClass:"bg-secondary"},{percent:25,label:"Weak",barClass:"bg-danger"},{percent:50,label:"Fair",barClass:"bg-warning"},{percent:75,label:"Good",barClass:"bg-info"},{percent:100,label:"Strong",barClass:"bg-success"}][s]}updatePasswordStrengthUI(e){if(!e||!e.id)return;const t=this.element.querySelector(`#${e.id}_strength_bar`),s=this.element.querySelector(`#${e.id}_strength_text`);if(!t&&!s)return;const{percent:i,label:a,barClass:r}=this.computePasswordStrength(e.value||"");t&&(t.className=`progress-bar ${r}`,t.style.width=`${i}%`,t.setAttribute("aria-valuenow",String(i))),s&&(s.textContent=a)}updateCapsLockWarning(e,t){if(!e||!e.id)return;const s=this.element.querySelector(`#${e.id}_caps_warning`);s&&(t?s.classList.remove("d-none"):s.classList.add("d-none"))}}class Be{constructor(e){this.fieldElement=e,this.statusContainer=this.findOrCreateStatusContainer(),this.timeouts=new Map}findOrCreateStatusContainer(){let e=this.fieldElement.parentElement.querySelector(".field-status-label-inline");if(!e){const t=this.findFieldLabel();t&&(e=t.querySelector(".field-status-label-inline"))}return e||(e=this.createStatusContainer()),e}createStatusContainer(){const e=this.getFieldType();this.getPlacementStrategy(e);const t=document.createElement("div");return this.createLabelInlineContainer(t)}getFieldType(){const e=this.fieldElement.tagName.toLowerCase(),t=this.fieldElement.type?.toLowerCase(),s=this.fieldElement.className;return t==="checkbox"||s.includes("form-check-input")||s.includes("form-switch")?"toggle":e==="select"?"select":e==="textarea"?"textarea":"input"}getPlacementStrategy(e){return"label-inline"}createLabelInlineContainer(e){e.className="field-status-label-inline",e.innerHTML=this.getStatusHTML();const t=this.findFieldLabel();return t?t.appendChild(e):this.fieldElement.parentElement.appendChild(e),e}findFieldLabel(){if(this.fieldElement.id){const s=document.querySelector(`label[for="${this.fieldElement.id}"]`);if(s)return s}const e=this.fieldElement.parentElement.querySelector("label");if(e)return e;const t=this.fieldElement.closest("label");return t||null}createInputOverlayContainer(e){e.className="field-status-overlay",e.innerHTML=this.getStatusHTML();const t=this.fieldElement.parentElement;return getComputedStyle(t).position==="static"&&(t.style.position="relative"),t.appendChild(e),e}createFullOverlayContainer(e){e.className="field-status-full-overlay d-none",e.innerHTML=`
|
|
1071
|
+
<div class="saving-indicator">
|
|
1072
|
+
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
|
1073
|
+
<span class="visually-hidden">Saving...</span>
|
|
1074
|
+
</div>
|
|
1075
|
+
<span class="ms-2">Saving...</span>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="success-indicator d-none">
|
|
1078
|
+
<i class="bi bi-check-circle text-success"></i>
|
|
1079
|
+
<span class="ms-2">Saved</span>
|
|
1080
|
+
</div>
|
|
1081
|
+
<div class="error-indicator d-none">
|
|
1082
|
+
<i class="bi bi-exclamation-circle text-danger"></i>
|
|
1083
|
+
<span class="ms-2">Error saving</span>
|
|
1084
|
+
</div>
|
|
1085
|
+
`;const t=this.fieldElement.parentElement;return getComputedStyle(t).position==="static"&&(t.style.position="relative"),t.appendChild(e),e}getStatusHTML(){return`
|
|
1086
|
+
<div class="spinner-border spinner-border-sm text-primary d-none" data-status="saving" role="status">
|
|
1087
|
+
<span class="visually-hidden">Saving...</span>
|
|
1088
|
+
</div>
|
|
1089
|
+
<i class="bi bi-check-circle text-success d-none" data-status="saved"></i>
|
|
1090
|
+
<i class="bi bi-exclamation-circle text-danger d-none" data-status="error"></i>
|
|
1091
|
+
`}showStatus(e,t={}){this.clearTimeout(e),this.showStandardStatus(e,t)}showStandardStatus(e,t={}){this.hideAllStatuses();const s=this.statusContainer.querySelector(`[data-status="${e}"]`);s&&(s.classList.remove("d-none"),s.classList.add("d-inline-block","show"),e==="saved"?this.setTimeout(e,()=>this.hideStatus(e),2500):e==="error"&&(t.message&&(s.title=t.message),this.setTimeout(e,()=>this.hideStatus(e),6e3)))}showFullOverlayStatus(e,t={}){this.statusContainer.querySelectorAll(".saving-indicator, .success-indicator, .error-indicator").forEach(a=>a.classList.add("d-none")),this.statusContainer.classList.remove("d-none");let i;switch(e){case"saving":i=".saving-indicator";break;case"saved":i=".success-indicator",this.setTimeout(e,()=>this.hideStatus(e),2500);break;case"error":if(i=".error-indicator",t.message){const a=this.statusContainer.querySelector(".error-indicator span");a&&(a.textContent=t.message)}this.setTimeout(e,()=>this.hideStatus(e),6e3);break}if(i){const a=this.statusContainer.querySelector(i);a&&a.classList.remove("d-none")}}hideStatus(e){const t=this.statusContainer.querySelector(`[data-status="${e}"]`);t&&(t.classList.remove("show"),t.classList.add("hide"),setTimeout(()=>{t.classList.add("d-none"),t.classList.remove("d-inline-block","hide"),t.title=""},300))}hideAllStatuses(){this.statusContainer.querySelectorAll("[data-status]").forEach(t=>{t.classList.add("d-none"),t.classList.remove("d-inline-block","show","hide"),t.title=""})}setTimeout(e,t,s){const i=setTimeout(t,s);this.timeouts.set(e,i)}clearTimeout(e){this.timeouts.has(e)&&(clearTimeout(this.timeouts.get(e)),this.timeouts.delete(e))}destroy(){this.timeouts.forEach(e=>clearTimeout(e)),this.timeouts.clear()}}_e(H);const xe=Object.freeze(Object.defineProperty({__proto__:null,FormView:H,default:H},Symbol.toStringTag,{value:"Module"}));class Ue extends Y{constructor(e={}){super({title:"Form Page",description:"A page for submitting forms",icon:"form",fields:[],template:'<div data-container="form-view-container"></div>',className:"form-page container-sm",...e})}async onInit(){await super.onInit(),this.formView=new H({containerId:"form-view-container",fields:this.options.fields,autosaveModelField:!0}),this.addChild(this.formView),this.getApp().activeGroup&&this.formView.setModel(this.getApp().activeGroup)}async onEnter(){await super.onEnter(),this.formView&&await this.recreateFormView()}async onGroupChange(e){this.formView&&await this.recreateFormView()}async recreateFormView(){this.formView&&(await this.formView.destroy(),this.removeChild(this.formView)),this.formView=new H({containerId:"form-view-container",fields:this.options.fields,autosaveModelField:!0}),this.addChild(this.formView),this.getApp().activeGroup&&this.formView.setModel(this.getApp().activeGroup)}}let k=class D extends v{static _openDialogs=[];static _baseZIndex={backdrop:1050,modal:1055};static getFullscreenAwareZIndex(){return document.querySelector(".table-fullscreen")?{backdrop:10040,modal:10050}:this._baseZIndex}static _busyIndicator=null;static _busyCounter=0;static _busyTimeout=null;static fixAllBackdropStacking(){const e=document.querySelectorAll(".modal-backdrop"),t=D._openDialogs;if(e.length===0||t.length===0)return;const s=[...t].sort((i,a)=>(i._dialogZIndex||0)-(a._dialogZIndex||0));e.forEach((i,a)=>{if(a<s.length){const n=s[a]._dialogZIndex-5;i.style.zIndex=n;const o=document.querySelector(".table-fullscreen")||document.body;i.parentNode!==o&&o.appendChild(i)}})}static updateAllBackdropStacking(){D.fixAllBackdropStacking()}static showBusy(e={}){const{timeout:t=3e4,message:s="Loading..."}=e;if(this._busyCounter++,this._busyCounter===1){if(this._busyTimeout&&clearTimeout(this._busyTimeout),!this._busyIndicator){const r=this.getFullscreenAwareZIndex().modal+1e3;this._busyIndicator=document.createElement("div"),this._busyIndicator.className="mojo-busy-indicator",this._busyIndicator.innerHTML=`
|
|
1092
|
+
<div class="mojo-busy-spinner">
|
|
1093
|
+
<div class="spinner-border text-light" role="status">
|
|
1094
|
+
<span class="visually-hidden">Loading...</span>
|
|
1095
|
+
</div>
|
|
1096
|
+
<p class="mojo-busy-message mt-3 text-light">${s}</p>
|
|
1097
|
+
</div>
|
|
1098
|
+
<style>
|
|
1099
|
+
.mojo-busy-indicator {
|
|
1100
|
+
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
|
1101
|
+
background-color: rgba(0, 0, 0, 0.5); z-index: ${r};
|
|
1102
|
+
display: flex; align-items: center; justify-content: center;
|
|
1103
|
+
opacity: 0; transition: opacity 0.15s linear;
|
|
1104
|
+
}
|
|
1105
|
+
.mojo-busy-indicator.show { opacity: 1; }
|
|
1106
|
+
.mojo-busy-spinner .spinner-border { width: 3rem; height: 3rem; }
|
|
1107
|
+
</style>
|
|
1108
|
+
`,document.body.appendChild(this._busyIndicator)}const i=this._busyIndicator.querySelector(".mojo-busy-message");i&&(i.textContent=s),setTimeout(()=>this._busyIndicator.classList.add("show"),10),this._busyTimeout=setTimeout(()=>{console.error("Busy indicator timed out."),this.hideBusy(!0),this.alert({title:"Operation Timed Out",message:"The operation took too long. Please check your connection and try again.",type:"danger"})},t)}}static hideBusy(e=!1){e?this._busyCounter=0:this._busyCounter--,this._busyCounter<=0&&(this._busyCounter=0,this._busyTimeout&&(clearTimeout(this._busyTimeout),this._busyTimeout=null),this._busyIndicator&&(this._busyIndicator.classList.remove("show"),setTimeout(()=>{this._busyIndicator&&this._busyCounter===0&&(this._busyIndicator.remove(),this._busyIndicator=null)},150)))}constructor(e={}){const t=e.id||`modal-${Date.now()}`;super({...e,id:t,tagName:"div",className:`modal ${e.fade!==!1?"fade":""} ${e.className||""}`,attributes:{tabindex:"-1","aria-hidden":"true","aria-labelledby":e.labelledBy||`${t}-label`,"aria-describedby":e.describedBy||null,...e.attributes}}),this.modalId=t,this.title=e.title||"",this.titleId=`${this.modalId}-label`,this.size=e.size||"",this.centered=e.centered!==void 0?e.centered:!1,this.scrollable=e.scrollable!==void 0?e.scrollable:!1,this.autoSize=e.autoSize||e.size==="auto",this.backdrop=e.backdrop!==void 0?e.backdrop:!0,this.keyboard=e.keyboard!==void 0?e.keyboard:!0,this.focus=e.focus!==void 0?e.focus:!0,this.header=e.header!==void 0?e.header:!0,this.headerContent=e.headerContent||null,this.headerView=null,this.closeButton=e.closeButton!==void 0?e.closeButton:!0,this.contextMenu=e.contextMenu||null,this._processHeaderContent(this.headerContent),this.body=e.body||e.content||"",this.bodyView=null,this.bodyClass=e.bodyClass||"",this.noBodyPadding=e.noBodyPadding||!1,this.minWidth=e.minWidth||300,this.minHeight=e.minHeight||200,e.maxHeight&&(this.maxHeight=e.maxHeight),this.maxWidthPercent=e.maxWidthPercent||.9,this.maxHeightPercent=e.maxHeightPercent||.8,this._processBodyContent(this.body),this.footer=e.footer||null,this.footerView=null,this.footerClass=e.footerClass||"",this._processFooterContent(this.footer),this.buttons=e.buttons||null,this.onShow=e.onShow||null,this.onShown=e.onShown||null,this.onHide=e.onHide||null,this.onHidden=e.onHidden||null,this.onHidePrevented=e.onHidePrevented||null,this.autoShow=e.autoShow!==void 0?e.autoShow:!1,this.modal=null,this.relatedTarget=e.relatedTarget||null}_processBodyContent(e){if(e&&e.render)this.bodyView=e,this.body="",this.addChild(this.bodyView);else if(typeof e=="function")try{const t=e();t instanceof v?(this.bodyView=t,this.body="",this.addChild(this.bodyView)):t instanceof Promise?(this.bodyPromise=t,this.body='<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>'):this.body=t}catch(t){console.error("Error processing body function:",t),this.body=e}else this.body=e}_processHeaderContent(e){if(e instanceof v)this.headerView=e,this.headerContent=null,this.addChild(this.headerView);else if(typeof e=="function")try{const t=e();t instanceof v?(this.headerView=t,this.headerContent=null,this.addChild(this.headerView)):t instanceof Promise?(this.headerPromise=t,this.headerContent='<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>'):this.headerContent=t}catch(t){console.error("Error processing headerContent function:",t),this.headerContent=e}else this.headerContent=e}_processFooterContent(e){if(e instanceof v)this.footerView=e,this.footer=null,this.addChild(this.footerView);else if(typeof e=="function")try{const t=e();t instanceof v?(this.footerView=t,this.footer=null,this.addChild(this.footerView)):t instanceof Promise?(this.footerPromise=t,this.footer='<div class="text-center"><div class="spinner-border spinner-border-sm"></div></div>'):this.footer=t}catch(t){console.error("Error processing footer function:",t),this.footer=e}else this.footer=e}async getTemplate(){const e=["modal-dialog"];return this.size&&this.size!=="auto"&&(this.size.startsWith("fullscreen")?e.push(`modal-${this.size}`):["sm","lg","xl","xxl"].includes(this.size)&&(e.push(`modal-${this.size}`),["lg","xl","xxl"].includes(this.size)&&e.push("modal-fullscreen-sm-down"))),this.centered&&e.push("modal-dialog-centered"),this.scrollable&&(this.maxHeight?e.push("overflow-hidden"):e.push("modal-dialog-scrollable")),`
|
|
1109
|
+
<div class="${e.join(" ")}">
|
|
1110
|
+
<div class="modal-content">
|
|
1111
|
+
${await this.buildHeader()}
|
|
1112
|
+
${await this.buildBody()}
|
|
1113
|
+
${await this.buildFooter()}
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
`}async buildHeader(){if(!this.header)return"";if(this.headerView)return this.headerView.replaceById=!0,`<div class="modal-header" data-view-container="header">
|
|
1117
|
+
<!-- View will be mounted here -->
|
|
1118
|
+
<div id="${this.headerView.id}"></div>
|
|
1119
|
+
</div>`;if(this.headerContent)return`<div class="modal-header">${this.headerContent}</div>`;let e="";return this.contextMenu&&this.contextMenu.items&&this.contextMenu.items.length>0?e=await this.buildContextMenu():this.closeButton&&(e='<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>'),`
|
|
1120
|
+
<div class="modal-header">
|
|
1121
|
+
${this.title?`<h5 class="modal-title" id="${this.titleId}">${this.title}</h5>`:""}
|
|
1122
|
+
${e}
|
|
1123
|
+
</div>
|
|
1124
|
+
`}async buildContextMenu(){const e=await this.filterContextMenuItems();if(e.length===0)return this.closeButton?'<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>':"";const t=this.contextMenu.icon||"bi-three-dots-vertical",s=this.contextMenu.buttonClass||"btn btn-link p-1 mojo-modal-context-menu-btn",i=e.map(a=>{if(a.type==="divider")return'<li><hr class="dropdown-divider"></li>';const r=a.icon?`<i class="${a.icon} me-2"></i>`:"",n=a.label||"";if(a.href)return`<li><a class="dropdown-item" href="${a.href}"${a.target?` target="${a.target}"`:""}>${r}${n}</a></li>`;if(a.action){const l=Object.keys(a).filter(o=>o.startsWith("data-")).map(o=>`${o}="${a[o]}"`).join(" ");return`<li><a class="dropdown-item" data-action="${a.action}" ${l}>${r}${n}</a></li>`}return""}).join("");return`
|
|
1125
|
+
<div class="dropdown">
|
|
1126
|
+
<button class="${s}" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
1127
|
+
<i class="${t}"></i>
|
|
1128
|
+
</button>
|
|
1129
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
1130
|
+
${i}
|
|
1131
|
+
</ul>
|
|
1132
|
+
</div>
|
|
1133
|
+
`}async filterContextMenuItems(){if(!this.contextMenu||!this.contextMenu.items)return[];const e=[];for(const t of this.contextMenu.items){if(t.type==="divider"){e.push(t);continue}if(t.permissions)try{const s=this.getApp?.();let i=null;if(s&&(i=s.activeUser||s.getState?.("activeUser")),!i&&typeof window<"u"&&window.getApp)try{i=window.getApp()?.activeUser}catch{}if(i&&i.hasPermission){if(!i.hasPermission(t.permissions))continue}else continue}catch(s){console.warn("Error checking permissions for context menu item:",s);continue}e.push(t)}return e}async buildBody(){return this.bodyView?(this.bodyView.replaceById=!0,`<div class="${this.noBodyPadding?`modal-body p-0 ${this.bodyClass}`:`modal-body ${this.bodyClass}`}" data-view-container="body">
|
|
1134
|
+
<!-- View will be mounted here -->
|
|
1135
|
+
<div id="${this.bodyView.id}"></div>
|
|
1136
|
+
</div>`):!this.body&&this.body!==""?"":`
|
|
1137
|
+
<div class="${this.noBodyPadding?`modal-body p-0 ${this.bodyClass}`:`modal-body ${this.bodyClass}`}">
|
|
1138
|
+
${this.body}
|
|
1139
|
+
</div>
|
|
1140
|
+
`}async buildFooter(){if(this.footerView)return`<div class="modal-footer ${this.footerClass}" data-view-container="footer"></div>`;if(this.footer!==null&&typeof this.footer=="string")return`<div class="modal-footer ${this.footerClass}">${this.footer}</div>`;if(this.buttons&&this.buttons.length>0){const e=this.buttons.map(t=>{const s=t.dismiss?'data-bs-dismiss="modal"':"",i=t.action?`data-action="${t.action}"`:"",a=t.id?`id="${t.id}"`:"",r=t.disabled?"disabled":"";return`
|
|
1141
|
+
<button type="${t.type||"button"}"
|
|
1142
|
+
class="btn ${t.class||"btn-secondary"}"
|
|
1143
|
+
${a}
|
|
1144
|
+
${s}
|
|
1145
|
+
${i}
|
|
1146
|
+
${r}>
|
|
1147
|
+
${t.icon?`<i class="bi ${t.icon} me-1"></i>`:""}
|
|
1148
|
+
${t.text||"Button"}
|
|
1149
|
+
</button>
|
|
1150
|
+
`}).join("");return`<div class="modal-footer ${this.footerClass}">${e}</div>`}return""}async mount(e=null){if(this.mounted||this.destroyed)return;if(!this.element)throw new Error("Cannot mount dialog without element");return await this.onBeforeMount(),(document.querySelector(".table-fullscreen")||document.body).appendChild(this.element),this.bindEvents(),this.mounted=!0,await this.onAfterMount(),this.emit("mounted",{view:this}),this}async onAfterRender(){if(await super.onAfterRender(),window.Prism&&this.element&&this.element.querySelectorAll("pre code").length>0&&window.Prism.highlightAllUnder(this.element),this.autoSize)this.setupAutoSizing();else if(this.maxHeight){const e=this.element.querySelector(".modal-body");e&&(e.style.maxHeight=`${this.maxHeight}px`)}}async onAfterMount(){await super.onAfterMount(),typeof window<"u"&&window.bootstrap&&window.bootstrap.Modal&&(this.backdrop==="static"&&this.element.setAttribute("data-bs-backdrop","static"),this.keyboard||this.element.setAttribute("data-bs-keyboard","false"),this.modal=new window.bootstrap.Modal(this.element,{backdrop:this.backdrop,keyboard:this.keyboard,focus:this.focus}),this.bindBootstrapEvents(),this.autoShow&&this.show(this.relatedTarget))}setupAutoSizing(){this.element&&(this.element.addEventListener("shown.bs.modal",()=>{this.applyAutoSizing()},{once:!0}),setTimeout(()=>{this.isShown()&&this.applyAutoSizing()},100))}applyAutoSizing(){if(this.element)try{const e=this.element.querySelector(".modal-dialog"),t=this.element.querySelector(".modal-content"),s=this.element.querySelector(".modal-body");if(!e||!t||!s){console.warn("Dialog auto-sizing: Required elements not found");return}if(this.bodyView&&!this.bodyView.element){setTimeout(()=>this.applyAutoSizing(),50);return}const i={dialogMaxWidth:e.style.maxWidth,dialogWidth:e.style.width,contentWidth:t.style.width,contentMaxHeight:t.style.maxHeight,hadScrollableClass:e.classList.contains("modal-dialog-scrollable")};e.style.maxWidth="none",e.style.width="auto",t.style.width="auto",t.style.maxHeight="none",t.offsetHeight;const a=t.getBoundingClientRect(),r=40,n=Math.min(window.innerWidth*this.maxWidthPercent,window.innerWidth-r);let l=Math.min(window.innerHeight*this.maxHeightPercent,window.innerHeight-r),o=Math.max(this.minWidth,Math.ceil(a.width+20)),c=Math.max(this.minHeight,Math.ceil(a.height));this.maxHeight&&(l=Math.min(this.maxHeight,l),e.style.maxHeight=`${l}px`),o=Math.min(o,n);const d=a.height>l;e.style.maxWidth=`${o}px`,e.style.width=`${o}px`,d&&(e.classList.contains("modal-dialog-scrollable")||e.classList.add("modal-dialog-scrollable"),t.style.maxHeight=`${l}px`,c=l),this.autoSizedWidth=o,this.autoSizedHeight=c,this._originalStyles=i}catch(e){console.error("Error in dialog auto-sizing:",e),this.element.querySelector(".modal-dialog").style.maxWidth=""}}resetAutoSizing(){if(!(!this.autoSize||!this._originalStyles||!this.element))try{const e=this.element.querySelector(".modal-dialog"),t=this.element.querySelector(".modal-content"),s=this.element.querySelector(".modal-body");e&&t&&s&&(e.style.maxWidth=this._originalStyles.dialogMaxWidth||"",e.style.width=this._originalStyles.dialogWidth||"",t.style.width=this._originalStyles.contentWidth||"",t.style.maxHeight=this._originalStyles.contentMaxHeight||"",!this._originalStyles.hadScrollableClass&&e.classList.contains("modal-dialog-scrollable")&&e.classList.remove("modal-dialog-scrollable"),delete this.autoSizedWidth,delete this.autoSizedHeight,delete this._originalStyles)}catch(e){console.error("Error resetting dialog auto-sizing:",e)}}bindBootstrapEvents(){this.element.addEventListener("show.bs.modal",e=>{const t=D._openDialogs.length,i=D.getFullscreenAwareZIndex().modal+t*20;this.element.style.zIndex=i,this._dialogZIndex=i,this._backdropZIndex=i-10,D._openDialogs.push(this),this.onShow&&this.onShow(e),this.emit("show",{dialog:this,relatedTarget:e.relatedTarget})}),this.element.addEventListener("shown.bs.modal",e=>{if(setTimeout(()=>{D.fixAllBackdropStacking()},50),this.onShown&&this.onShown(e),this.emit("shown",{dialog:this,relatedTarget:e.relatedTarget}),this.focus){const t=this.element.querySelector('input:not([type="hidden"]), textarea, select');t&&t.focus()}}),this.element.addEventListener("hide.bs.modal",e=>{const t=this.element.querySelector(":focus");if(t&&t.blur(),this.onHide&&this.onHide(e)===!1){e.preventDefault();return}this.emit("hide",{dialog:this})}),this.element.addEventListener("hidden.bs.modal",e=>{const t=D._openDialogs.indexOf(this);t>-1&&D._openDialogs.splice(t,1),D._openDialogs.length>0&&(document.body.classList.add("modal-open"),setTimeout(()=>{D.fixAllBackdropStacking()},50)),this.previousFocus&&document.body.contains(this.previousFocus)&&this.previousFocus.focus(),this.onHidden&&this.onHidden(e),this.emit("hidden",{dialog:this})}),this.element.addEventListener("hidePrevented.bs.modal",e=>{this.onHidePrevented&&this.onHidePrevented(e),this.emit("hidePrevented",{dialog:this})})}show(e=null){this.previousFocus=document.activeElement,window.lastDialog=this,this.modal&&this.modal.show(e)}hide(){const e=this.element?.querySelector(":focus");e&&e.blur(),this.modal&&this.modal.hide()}toggle(e=null){this.modal&&this.modal.toggle(e)}async destroy(){if(this.modal){const e=this.element?.querySelector(":focus");e&&e.blur(),this.modal.dispose(),this.modal=null}this.previousFocus&&document.body.contains(this.previousFocus)&&(this.previousFocus.focus(),this.previousFocus=null),this.autoSize&&this.resetAutoSizing(),await super.destroy()}handleUpdate(){this.modal&&this.modal.handleUpdate()}async setContent(e){if(e instanceof v){this.bodyView&&(await this.bodyView.destroy(),this.removeChild(this.bodyView)),this.bodyView=e,this.body="",this.addChild(this.bodyView);const t=this.element?.querySelector(".modal-body");t&&(t.innerHTML="",await this.bodyView.render(t))}else{this.body=e;const t=this.element?.querySelector(".modal-body");t&&(t.innerHTML=e)}this.handleUpdate()}setTitle(e){this.title=e;const t=this.element?.querySelector(".modal-title");t&&(t.textContent=e)}setLoading(e=!0,t="Loading..."){const s=this.element?.querySelector(".modal-body");s&&(e?s.innerHTML=`
|
|
1151
|
+
<div class="text-center py-4">
|
|
1152
|
+
<div class="spinner-border text-primary mb-3" role="status">
|
|
1153
|
+
<span class="visually-hidden">Loading...</span>
|
|
1154
|
+
</div>
|
|
1155
|
+
<p>${t}</p>
|
|
1156
|
+
</div>
|
|
1157
|
+
`:this.bodyView&&s.replaceChildren(this.bodyView.element))}async onBeforeDestroy(){this.headerView&&await this.headerView.destroy(),this.bodyView&&await this.bodyView.destroy(),this.footerView&&await this.footerView.destroy(),await super.onBeforeDestroy(),this.modal&&(this.modal.dispose(),this.modal=null)}static async showCode(e={}){const t=new D({title:e.title||"Source Code",size:e.size||"lg",scrollable:!0,body:D.formatCode(e.code,e.language),buttons:[{text:"Copy to Clipboard",class:"btn-primary",icon:"bi-clipboard",action:"copy"},{text:"Close",class:"btn-secondary",dismiss:!0}]});t.on("action:copy",async()=>{if(navigator.clipboard)try{await navigator.clipboard.writeText(e.code),t.showCopySuccess()}catch(a){console.error("Failed to copy:",a)}});const i=document.querySelector(".table-fullscreen")||document.body;return await t.render(!0,i),window.Prism&&t.element&&window.Prism.highlightAllUnder(t.element),t.show(),t.on("hidden",()=>{t.destroy(),t.element.remove()}),t}static formatCode(e,t="javascript"){let s;window.Prism&&window.Prism.languages[t]?s=window.Prism.highlight(e,window.Prism.languages[t],t):s=e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");const i=window.Prism?`language-${t}`:"",a=`
|
|
1158
|
+
max-height: 60vh;
|
|
1159
|
+
overflow-y: auto;
|
|
1160
|
+
background: #1e1e1e;
|
|
1161
|
+
color: #d4d4d4;
|
|
1162
|
+
padding: 1.25rem;
|
|
1163
|
+
border-radius: 0.5rem;
|
|
1164
|
+
margin: 0;
|
|
1165
|
+
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
|
|
1166
|
+
font-size: 0.9rem;
|
|
1167
|
+
line-height: 1.6;
|
|
1168
|
+
border: 1px solid #2d2d30;
|
|
1169
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
|
1170
|
+
`.replace(/\s+/g," ").trim();return`
|
|
1171
|
+
<style>
|
|
1172
|
+
/* Custom Prism theme overrides for Dialog */
|
|
1173
|
+
.dialog-code-block .token.comment { color: #6a9955; }
|
|
1174
|
+
.dialog-code-block .token.string { color: #ce9178; }
|
|
1175
|
+
.dialog-code-block .token.keyword { color: #569cd6; }
|
|
1176
|
+
.dialog-code-block .token.function { color: #dcdcaa; }
|
|
1177
|
+
.dialog-code-block .token.number { color: #b5cea8; }
|
|
1178
|
+
.dialog-code-block .token.operator { color: #d4d4d4; }
|
|
1179
|
+
.dialog-code-block .token.class-name { color: #4ec9b0; }
|
|
1180
|
+
.dialog-code-block .token.punctuation { color: #d4d4d4; }
|
|
1181
|
+
.dialog-code-block .token.boolean { color: #569cd6; }
|
|
1182
|
+
.dialog-code-block .token.property { color: #9cdcfe; }
|
|
1183
|
+
.dialog-code-block .token.tag { color: #569cd6; }
|
|
1184
|
+
.dialog-code-block .token.attr-name { color: #9cdcfe; }
|
|
1185
|
+
.dialog-code-block .token.attr-value { color: #ce9178; }
|
|
1186
|
+
.dialog-code-block ::selection { background: #264f78; }
|
|
1187
|
+
</style>
|
|
1188
|
+
<pre class="${i} dialog-code-block" style="${a}">
|
|
1189
|
+
<code class="${i}" style="color: inherit; background: transparent; text-shadow: none;">${s}</code>
|
|
1190
|
+
</pre>
|
|
1191
|
+
`}static highlightCodeBlocks(e=document){window.Prism&&window.Prism.highlightAllUnder&&window.Prism.highlightAllUnder(e)}showCopySuccess(){const e=this.element.querySelector('[data-action="copy"]');if(e){const t=e.innerHTML;e.innerHTML='<i class="bi bi-check me-1"></i>Copied!',e.classList.remove("btn-primary"),e.classList.add("btn-success"),e.disabled=!0,setTimeout(()=>{e.innerHTML=t,e.classList.remove("btn-success"),e.classList.add("btn-primary"),e.disabled=!1},2e3)}}static async showHtmlPreview(e={}){const t=e.html||e.content||"",s=e.title||"HTML Preview",i=e.size||"lg",r=`
|
|
1192
|
+
<div class="html-preview-container">
|
|
1193
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
1194
|
+
<small class="text-muted">Preview (sandboxed)</small>
|
|
1195
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" data-action="refresh-preview">
|
|
1196
|
+
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
1197
|
+
</button>
|
|
1198
|
+
</div>
|
|
1199
|
+
<iframe
|
|
1200
|
+
id="html-preview-frame"
|
|
1201
|
+
class="border rounded w-100"
|
|
1202
|
+
style="height: ${e.height||500}px; background: white;"
|
|
1203
|
+
sandbox="allow-same-origin"
|
|
1204
|
+
frameborder="0"
|
|
1205
|
+
></iframe>
|
|
1206
|
+
</div>
|
|
1207
|
+
`,n=new D({title:s,size:i,scrollable:!1,body:r,buttons:[{text:"Close",class:"btn-secondary",dismiss:!0}]});n.on("action:refresh-preview",async d=>{const h=n.element.querySelector("#html-preview-frame");if(!h)return;const f=h.contentDocument||h.contentWindow.document;f.open(),f.write(t),f.close()});const o=document.querySelector(".table-fullscreen")||document.body;await n.render(!0,o);const c=n.element.querySelector("#html-preview-frame");if(c){const d=c.contentDocument||c.contentWindow.document;d.open(),d.write(t),d.close()}return n.show(),n.on("hidden",()=>{n.destroy(),n.element.remove()}),n}static async showDialog(e={}){if(typeof e=="string"){const f=arguments[0],m=arguments[1]||"Alert";e={...arguments[2]||{},body:f,title:m}}const{title:t="Dialog",content:s,body:i=s||"",size:a="md",centered:r=!0,buttons:n=[{text:"OK",class:"btn-primary",value:!0}],rejectOnDismiss:l=!1,...o}=e,c=new D({title:t,body:i,size:a,centered:r,buttons:n,...o}),h=document.querySelector(".table-fullscreen")||document.body;return await c.render(!0,h),new Promise((f,m)=>{let p=!1;c.element.querySelectorAll(".modal-footer button").forEach((w,y)=>{const b=n[y];b&&w.addEventListener("click",async C=>{if(p)return;const $=b.value!==void 0?b.value:b.action??y;if(typeof b.handler=="function")try{const S=await b.handler({dialog:c,button:b,index:y,event:C});if(S===null||S===!1)return;const T=S===!0||S===void 0?$:S;p=!0,b.dismiss||c.hide(),f(T)}catch(S){console.error("Dialog button handler error:",S);return}else p=!0,b.dismiss||c.hide(),f($)})}),c.on("hidden",()=>{p||(p=!0,l?m(new Error("Dialog dismissed")):f(null)),setTimeout(()=>{c.destroy(),c.element.remove()},100)}),c.show()})}static async alert(e={}){typeof e=="string"&&(e={message:e,title:"Alert"});const{message:t="",title:s="Alert",type:i="info",...a}=e;let r="",n="";switch(i){case"success":r='<i class="bi bi-check-circle-fill text-success me-2"></i>',n="text-success";break;case"warning":r='<i class="bi bi-exclamation-triangle-fill text-warning me-2"></i>',n="text-warning";break;case"danger":case"error":r='<i class="bi bi-x-circle-fill text-danger me-2"></i>',n="text-danger";break;default:r='<i class="bi bi-info-circle-fill text-info me-2"></i>',n="text-info"}return D.showDialog({title:`<span class="${n}">${r}${s}</span>`,body:`<p>${t}</p>`,size:"sm",centered:!0,buttons:[{text:"OK",class:"btn-primary",value:!0}],...a})}static async confirm(e,t="Confirm",s={}){typeof e=="object"&&(s=e,e=s.message,t=s.title||t);const i=new D({title:t,body:`<p>${e}</p>`,size:s.size||"sm",centered:!0,backdrop:"static",buttons:[{text:s.cancelText||"Cancel",class:"btn-secondary",dismiss:!0,action:"cancel"},{text:s.confirmText||"Confirm",class:s.confirmClass||"btn-primary",action:"confirm"}],...s}),r=document.querySelector(".table-fullscreen")||document.body;return await i.render(!0,r),i.show(),new Promise(n=>{let l=!1;i.on("action:confirm",()=>{l=!0,i.hide()}),i.on("hidden",()=>{i.destroy(),i.element.remove(),n(l)})})}static async prompt(e,t="Input",s={}){const i=`prompt-input-${Date.now()}`,a=s.defaultValue||"",r=s.inputType||"text",n=s.placeholder||"",l=new D({title:t,body:`
|
|
1208
|
+
<p>${e}</p>
|
|
1209
|
+
<input type="${r}"
|
|
1210
|
+
class="form-control"
|
|
1211
|
+
id="${i}"
|
|
1212
|
+
value="${a}"
|
|
1213
|
+
placeholder="${n}">
|
|
1214
|
+
`,size:s.size||"sm",centered:!0,backdrop:"static",buttons:[{text:"Cancel",class:"btn-secondary",dismiss:!0},{text:"OK",class:"btn-primary",action:"ok"}],...s}),c=document.querySelector(".table-fullscreen")||document.body;return await l.render(!0,c),l.show(),l.on("shown",()=>{const d=l.element.querySelector(`#${i}`);d&&(d.focus(),d.select())}),new Promise(d=>{let h=null;l.on("action:ok",()=>{const f=l.element.querySelector(`#${i}`);h=f?f.value:null,l.hide()}),l.on("hidden",()=>{l.destroy(),l.element.remove(),d(h)})})}getModal(){return this.modal}isShown(){return this.element?.classList.contains("show")||!1}static async showForm(e={}){const{title:t="Form",formConfig:s={},size:i="md",centered:a=!0,submitText:r="Submit",cancelText:n="Cancel",...l}=e,o=(await Promise.resolve().then(()=>xe)).default,c=new o({fileHandling:e.fileHandling||"base64",data:e.data,defaults:e.defaults,model:e.model,formConfig:{fields:s.fields||e.fields,...s,submitButton:!1,resetButton:!1}}),d=new D({title:t,body:c,size:i,centered:a,buttons:[{text:n,class:"btn-secondary",action:"cancel"},{text:r,class:"btn-primary",action:"submit"}],...l}),f=document.querySelector(".table-fullscreen")||document.body;return await d.render(!0,f),d.show(),new Promise(m=>{let p=!1;d.on("action:submit",async()=>{if(!p){if(!c.validate()){c.focusFirstError();return}if(e.autoSave&&e.model){d.setLoading(!0);const g=await c.saveModel();if(!g.success){d.setLoading(!1),d.render(),d.getApp().toast.error(g.message);return}p=!0,d.hide(),m(g)}try{const g=await c.getFormData();p=!0,d.hide(),m(g)}catch(g){console.error("Error collecting form data:",g),c.showError("Error collecting form data")}}}),d.on("action:cancel",()=>{p||(p=!0,d.hide(),m(null))}),d.on("hidden",()=>{p||(p=!0,m(null)),setTimeout(()=>{c.destroy(),d.destroy()},100)})})}static async showModelForm(e={}){const{title:t="Edit",formConfig:s={},size:i="md",centered:a=!0,submitText:r="Save",cancelText:n="Cancel",model:l,fields:o,...c}=e;if(!l)throw new Error("showModelForm requires a model");const d=(await Promise.resolve().then(()=>xe)).default,h=new d({fileHandling:e.fileHandling||"base64",model:l,data:e.data,defaults:e.defaults,formConfig:{fields:o||s.fields||[],...s,submitButton:!1,resetButton:!1}}),f=new D({title:t,body:h,size:i,centered:a,buttons:[{text:n,class:"btn-secondary",action:"cancel"},{text:r,class:"btn-primary",action:"submit"}],...c}),p=document.querySelector(".table-fullscreen")||document.body;return await f.render(!0,p),f.show(),new Promise(g=>{let w=!1;f.on("action:submit",async()=>{if(!w){f.setLoading(!0,"Saving...");try{const y=await h.handleSubmit();if(y.success)w=!0,f.hide(),g(y);else{f.setLoading(!1);let b=y.error;y.data&&y.data.error&&(b=y.data.error),f.getApp().toast.error(b)}}catch(y){console.error("Error saving form:",y),await f.setContent(h),h.showError(y.message||"An error occurred while saving")}}}),f.on("action:cancel",()=>{w||(w=!0,f.hide(),g(null))}),f.on("hidden",()=>{w||(w=!0,g(null)),setTimeout(()=>{h.destroy(),f.destroy()},100)})})}static async showData(e={}){const{title:t="Data View",data:s={},model:i=null,fields:a=[],columns:r=2,responsive:n=!0,showEmptyValues:l=!1,emptyValueText:o="—",size:c="lg",centered:d=!0,closeText:h="Close",...f}=e,m=(await Promise.resolve().then(()=>Ze)).default,p=new m({data:s,model:i,fields:a,columns:r,responsive:n,showEmptyValues:l,emptyValueText:o}),g=new D({title:t,body:p,size:c,centered:d,buttons:[{text:h,class:"btn-secondary",value:"close"}],...f}),y=document.querySelector(".table-fullscreen")||document.body;return await g.render(!0,y),g.show(),new Promise(b=>{let C=!1;const $=g.element.querySelector(".modal-footer button"),S=()=>{C||(C=!0,g.hide(),b(!0))};$?.addEventListener("click",S),g.on("hidden",()=>{C||(C=!0,b(!0)),setTimeout(()=>{p.destroy(),g.destroy(),g.element.remove()},100)}),p.on("field:click",T=>{g.emit("dataview:field:click",T)}),p.on("error",T=>{g.emit("dataview:error",T)})})}};k.showConfirm=k.confirm,k.showError=k.alert;const L=Object.freeze(Object.defineProperty({__proto__:null,default:k},Symbol.toStringTag,{value:"Module"}));class $e extends v{constructor(e={}){super({template:"progress-view-template",...e}),this.filename=e.filename||"Unknown file",this.filesize=e.filesize||0,this.filesizeFormatted=M.pipe(this.filesize,"filesize"),this.progress=0,this.percentage=0,this.loaded=0,this.total=this.filesize,this.loadedFormatted="0 B",this.totalFormatted=this.filesizeFormatted,this.status="Starting upload...",this.showCancel=e.showCancel!==!1,this.onCancel=e.onCancel||null,this.cancelled=!1,this.completed=!1}getTemplate(){return`
|
|
1215
|
+
<div class="progress-view">
|
|
1216
|
+
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
1217
|
+
<div class="flex-grow-1 min-width-0">
|
|
1218
|
+
<div class="fw-medium text-truncate" title="{{filename}}">
|
|
1219
|
+
<i class="bi bi-file-earmark me-1"></i>
|
|
1220
|
+
{{filename}}
|
|
1221
|
+
</div>
|
|
1222
|
+
<small class="text-muted">{{status}}</small>
|
|
1223
|
+
</div>
|
|
1224
|
+
{{#showCancel}}
|
|
1225
|
+
<button type="button"
|
|
1226
|
+
class="btn btn-sm btn-outline-secondary ms-2"
|
|
1227
|
+
data-action="cancel"
|
|
1228
|
+
{{#cancelled}}disabled{{/cancelled}}>
|
|
1229
|
+
<i class="bi bi-x"></i>
|
|
1230
|
+
</button>
|
|
1231
|
+
{{/showCancel}}
|
|
1232
|
+
</div>
|
|
1233
|
+
|
|
1234
|
+
<div class="progress mb-2" style="height: 8px;">
|
|
1235
|
+
<div class="progress-bar"
|
|
1236
|
+
role="progressbar"
|
|
1237
|
+
style="width: {{percentage}}%"
|
|
1238
|
+
aria-valuenow="{{percentage}}"
|
|
1239
|
+
aria-valuemin="0"
|
|
1240
|
+
aria-valuemax="100">
|
|
1241
|
+
</div>
|
|
1242
|
+
</div>
|
|
1243
|
+
|
|
1244
|
+
<div class="d-flex justify-content-between">
|
|
1245
|
+
<small class="text-muted">
|
|
1246
|
+
{{loadedFormatted}} / {{totalFormatted}}
|
|
1247
|
+
</small>
|
|
1248
|
+
<small class="text-muted">
|
|
1249
|
+
{{percentage}}%
|
|
1250
|
+
</small>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
`}updateProgress(e){this.cancelled||this.completed||(this.progress=e.progress,this.percentage=e.percentage,this.loaded=e.loaded,this.total=e.total||this.filesize,this.loadedFormatted=M.pipe(this.loaded,"filesize"),this.totalFormatted=M.pipe(this.total,"filesize"),this.percentage<100?this.status=`Uploading... ${this.percentage}%`:this.status="Finalizing upload...",this.render())}markCompleted(e="Upload completed!"){this.completed=!0,this.progress=1,this.percentage=100,this.status=e,this.render()}markFailed(e="Upload failed"){this.status=e,this.render()}markCancelled(){this.cancelled=!0,this.status="Upload cancelled",this.render()}async onActionCancel(e,t,s){if(!(this.cancelled||this.completed)&&(s.disabled=!0,this.markCancelled(),this.emit("cancel"),typeof this.onCancel=="function"))try{await this.onCancel()}catch(i){console.error("Error in cancel callback:",i)}}setFilename(e){this.filename=e,this.render()}setFilesize(e){this.filesize=e,this.filesizeFormatted=M.pipe(e,"filesize"),this.total=e,this.totalFormatted=this.filesizeFormatted,this.render()}getPercentage(){return this.percentage}isCompleted(){return this.completed}isCancelled(){return this.cancelled}getStats(){return{filename:this.filename,filesize:this.filesize,progress:this.progress,percentage:this.percentage,loaded:this.loaded,total:this.total,cancelled:this.cancelled,completed:this.completed,status:this.status}}}class X extends v{constructor(e={}){super({className:"list-view-item",...e}),this.selected=!1,this.index=e.index??0,this.listView=e.listView??null,this.template||(this.template=`
|
|
1254
|
+
<div class="list-item-content" data-action="select">
|
|
1255
|
+
{{#model}}
|
|
1256
|
+
{{#id}}<span class="item-id">{{id}}</span>{{/id}}
|
|
1257
|
+
{{#name}}<span class="item-name">{{name}}</span>{{/name}}
|
|
1258
|
+
{{#title}}<span class="item-title">{{title}}</span>{{/title}}
|
|
1259
|
+
{{#label}}<span class="item-label">{{label}}</span>{{/label}}
|
|
1260
|
+
{{#description}}<p class="item-description">{{description}}</p>{{/description}}
|
|
1261
|
+
{{/model}}
|
|
1262
|
+
{{^model}}
|
|
1263
|
+
<span class="item-empty">No data</span>
|
|
1264
|
+
{{/model}}
|
|
1265
|
+
</div>
|
|
1266
|
+
`)}async onActionSelect(e,t){e.stopPropagation(),this.selected?this.deselect():this.select()}select(){this.selected||(this.selected=!0,this.addClass("selected"),this.emit("item:select",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:select",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}))}deselect(){this.selected&&(this.selected=!1,this.removeClass("selected"),this.emit("item:deselect",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:deselect",{item:this,model:this.model,index:this.index,data:this.model?.toJSON?this.model.toJSON():this.model}))}async onActionDefault(e,t,s){this.emit("item:click",{item:this,model:this.model,index:this.index,action:e,data:this.model?.toJSON?this.model.toJSON():this.model}),this.listView&&this.listView.emit("item:click",{item:this,model:this.model,index:this.index,action:e,data:this.model?.toJSON?this.model.toJSON():this.model})}setIndex(e){return this.index=e,this.element.setAttribute("data-index",e),this}setSelected(e){return e?this.select():this.deselect(),this}async destroy(){this.listView=null,await super.destroy()}}class he extends v{constructor(e={}){super({className:"list-view",template:`
|
|
1267
|
+
<div class="list-view-container">
|
|
1268
|
+
{{#loading}}
|
|
1269
|
+
<div class="list-loading">
|
|
1270
|
+
<div class="spinner-border spinner-border-sm" role="status">
|
|
1271
|
+
<span class="visually-hidden">Loading...</span>
|
|
1272
|
+
</div>
|
|
1273
|
+
Loading...
|
|
1274
|
+
</div>
|
|
1275
|
+
{{/loading}}
|
|
1276
|
+
{{^loading}}
|
|
1277
|
+
{{#isEmpty}}
|
|
1278
|
+
<div class="list-empty">
|
|
1279
|
+
{{emptyMessage}}
|
|
1280
|
+
</div>
|
|
1281
|
+
{{/isEmpty}}
|
|
1282
|
+
{{^isEmpty}}
|
|
1283
|
+
<div class="list-items" data-container="items"></div>
|
|
1284
|
+
{{/isEmpty}}
|
|
1285
|
+
{{/loading}}
|
|
1286
|
+
</div>
|
|
1287
|
+
`,...e}),this.collection=null,this.itemViews=new Map,this.selectedItems=new Set,this.itemTemplate=e.itemTemplate||null,this.itemClass=e.itemClass||X,this.selectionMode=e.selectionMode||"none",this.emptyMessage=e.emptyMessage||"No items to display",this.loading=!1,this.isEmpty=!0}async onInit(){this._initCollection(this.options.collection||this.options.Collection)}_initCollection(e){if(!e){console.log("Collection not provided");return}if(e instanceof W)this.setCollection(e);else if(typeof e=="function"){const t=new e;this.setCollection(t)}else if(Array.isArray(e)){const t=new W(null,{},e);this.setCollection(t)}}setCollection(e){return this.collection===e?this:(this.collection&&(this.collection.off("add",this._onModelsAdded,this),this.collection.off("remove",this._onModelsRemoved,this),this.collection.off("reset",this._onCollectionReset,this),this.collection.off("fetch:start",this._onFetchStart,this),this.collection.off("fetch:end",this._onFetchEnd,this)),this.collection=e,this.options.defaultQuery&&!this.options.collectionParams&&(this.collection.params={...this.collection.params,...this.options.defaultQuery}),this.options.collectionParams&&(this.collection.params={...this.collection.params,...this.options.collectionParams}),this.collection&&(this.collection.on("add",this._onModelsAdded,this),this.collection.on("remove",this._onModelsRemoved,this),this.collection.on("reset",this._onCollectionReset,this),this.collection.on("fetch:start",this._onFetchStart,this),this.collection.on("fetch:end",this._onFetchEnd,this),this._buildItems()),this)}async _renderChildren(){await super._renderChildren();const e=this.getChildElement("items");e&&this.forEachItem((t,s)=>{e.appendChild(t.element),t.render(!1)})}_buildItems(){if(this._clearItems(),!this.collection||this.collection.isEmpty()){this.isEmpty=!0,this.emit("list:empty");return}this.isEmpty=!1,this.collection.forEach((e,t)=>{this._createItemView(e,t)}),this.emit("list:loaded",{count:this.collection.length()}),this.isMounted()&&this.render()}_createItemView(e,t){if(this.itemViews.has(e.id))return;const s=new this.itemClass({model:e,index:t,listView:this,template:this.itemTemplate});return this.itemViews.set(e.id,s),s.on("item:select",this._onItemSelect.bind(this)),s.on("item:deselect",this._onItemDeselect.bind(this)),s}_clearItems(){this.forEachItem(e=>{this.removeChild(e.id)}),this.itemViews.clear(),this.selectedItems.clear()}_onModelsAdded(e){const{models:t}=e;t.forEach(s=>{const i=this.collection.models.indexOf(s);this._createItemView(s,i)}),this.isEmpty=this.collection.isEmpty(),!this.loading&&this.isMounted()&&this.render()}_onModelsRemoved(e){const{models:t}=e;t.forEach(s=>{const i=this.itemViews.get(s.id);i&&(this.removeChild(i.id),this.itemViews.delete(s.id),this.selectedItems.delete(s.id))}),this.isEmpty=this.collection.isEmpty(),!this.loading&&this.isMounted()&&this.render(),this.isEmpty&&this.emit("list:empty")}_onCollectionReset(e){this._buildItems()}_onFetchStart(){this.loading=!0,this.isMounted()&&this.render()}_onFetchEnd(){this.loading=!1,this.isMounted()&&this.render()}_onItemSelect(e){const{model:t,item:s}=e;if(this.selectionMode==="none"){s.deselect();return}this.selectionMode==="single"&&(this.itemViews.forEach((i,a)=>{a!==t.id&&i.selected&&i.deselect()}),this.selectedItems.clear()),this.selectedItems.add(t.id),this.emit("selection:change",{selected:Array.from(this.selectedItems),item:s,model:t})}_onItemDeselect(e){const{model:t}=e;this.selectedItems.delete(t.id),this.emit("selection:change",{selected:Array.from(this.selectedItems),item:e.item,model:t})}getSelectedItems(){const e=[];return this.selectedItems.forEach(t=>{const s=this.itemViews.get(t);s&&e.push({view:s,model:s.model,data:s.model?.toJSON?s.model.toJSON():s.model})}),e}forEachItem(e,t){if(typeof e!="function")throw new TypeError("Callback must be a function");let s=0;return this.itemViews.forEach((i,a)=>{e.call(t,i,i.model,s++)}),this}clearSelection(){this.forEachItem(e=>{e.selected&&e.deselect()}),this.selectedItems.clear(),this.emit("selection:change",{selected:[]})}selectItem(e){const t=this.itemViews.get(e);return t&&t.select(),this}deselectItem(e){const t=this.itemViews.get(e);return t&&t.deselect(),this}setItemTemplate(e,t=!1){return this.itemTemplate=e,t&&this.itemViews.size>0&&this.forEachItem(s=>{s.setTemplate(e),s.isMounted()&&s.render()}),this}async onAfterMount(){await super.onAfterMount(),this.collection&&(this.options.fetchOnMount||!this.collection.lastFetchTime)&&this.collection.fetch()}async refresh(){if(this.collection&&this.collection.restEnabled)return await this.collection.fetch();this._buildItems()}async destroy(){this.collection&&(this.collection.off("add",this._onModelsAdded,this),this.collection.off("remove",this._onModelsRemoved,this),this.collection.off("reset",this._onCollectionReset,this),this.collection.off("fetch:start",this._onFetchStart,this),this.collection.off("fetch:end",this._onFetchEnd,this)),this._clearItems(),await super.destroy()}}class ue extends X{constructor(e={}){super({tagName:"tr",className:"table-row",enableTooltips:!0,...e}),this.columns=e.columns||[],this.actions=e.actions||null,this.contextMenu=e.contextMenu||null,this.batchActions=e.batchActions||null,this.tableView=e.tableView||e.listView||null,this.editingCells=new Set,this.template=this.buildRowTemplate()}getResponsiveClasses(e){if(!e)return"";const t=["sm","md","lg","xl","xxl"];if(typeof e=="string")return t.includes(e)?`d-none d-${e}-table-cell`:(console.warn(`Invalid visibility breakpoint: ${e}. Valid options are: ${t.join(", ")}`),"");if(typeof e=="object"){const s=[];if(e.hide){if(!t.includes(e.hide))return console.warn(`Invalid hide breakpoint: ${e.hide}. Valid options are: ${t.join(", ")}`),"";s.push(`d-table-cell d-${e.hide}-none`)}if(e.show){if(!t.includes(e.show))return console.warn(`Invalid show breakpoint: ${e.show}. Valid options are: ${t.join(", ")}`),"";e.hide?s.push(`d-${e.show}-table-cell`):s.push(`d-none d-${e.show}-table-cell`)}return s.join(" ")}return""}buildRowTemplate(){let e="";return this.tableView&&this.tableView.isSelectable()&&(e+=`
|
|
1288
|
+
<td style="padding: 0;">
|
|
1289
|
+
<div class="mojo-select-cell {{#selected}}selected{{/selected}}"
|
|
1290
|
+
data-action="select">
|
|
1291
|
+
<div class="mojo-checkbox">
|
|
1292
|
+
<i class="bi bi-check"></i>
|
|
1293
|
+
</div>
|
|
1294
|
+
</div>
|
|
1295
|
+
</td>
|
|
1296
|
+
`),this.columns.forEach(t=>{const s=t.class||t.className||"",i=this.getResponsiveClasses(t.visibility),a=t.editable?"editable-cell":"",r=[s,i,a].filter(o=>o).join(" "),n=this.buildCellTemplate(t);let l=t.action;!l&&t.editable?l="edit-cell":!l&&this.tableView.rowAction&&(l=this.tableView.rowAction),l?e+=`<td class="${r}" data-action="${l}" data-column="${t.key}">${n}</td>`:e+=`<td class="${r}" data-column="${t.key}">${n}</td>`}),this.actions?e+=this.buildActionsTemplate():this.contextMenu&&(e+=this.buildContextMenuTemplate()),e}buildCellTemplate(e){const t=`model.${e.key}`,s=e.formatter||e.format;if(s){if(typeof s=="string")return`{{{${t}|${s}}}}`;if(typeof s=="function")return`<span data-formatter="${e.key}">{{${t}}}</span>`}return e.template?e.template:e.editable?`<span class="cell-content" data-field="${e.key}">{{{${t}}}}</span>`:`{{{${t}}}}`}buildActionsTemplate(){return!this.actions||this.actions.length===0?"":`<td><div class="btn-group btn-group-sm">${this.actions.map(t=>{if(typeof t=="string")switch(t){case"view":return`
|
|
1297
|
+
<button class="btn btn-sm btn-outline-primary"
|
|
1298
|
+
data-action="view"
|
|
1299
|
+
title="View">
|
|
1300
|
+
<i class="bi bi-eye"></i>
|
|
1301
|
+
</button>
|
|
1302
|
+
`;case"edit":return`
|
|
1303
|
+
<button class="btn btn-sm btn-outline-secondary"
|
|
1304
|
+
data-action="edit"
|
|
1305
|
+
title="Edit">
|
|
1306
|
+
<i class="bi bi-pencil"></i>
|
|
1307
|
+
</button>
|
|
1308
|
+
`;case"delete":return`
|
|
1309
|
+
<button class="btn btn-sm btn-outline-danger"
|
|
1310
|
+
data-action="delete"
|
|
1311
|
+
title="Delete">
|
|
1312
|
+
<i class="bi bi-trash"></i>
|
|
1313
|
+
</button>
|
|
1314
|
+
`;default:return""}else if(typeof t=="object")return`
|
|
1315
|
+
<button class="btn btn-sm ${t.class||"btn-outline-primary"}"
|
|
1316
|
+
data-id="${this.model.id}"
|
|
1317
|
+
data-action="${t.action}"
|
|
1318
|
+
title="${t.label||""}">
|
|
1319
|
+
${t.icon?`<i class="${t.icon}"></i>`:""}
|
|
1320
|
+
${t.label&&!t.icon?t.label:""}
|
|
1321
|
+
</button>
|
|
1322
|
+
`;return""}).join("")}</div></td>`}buildContextMenuTemplate(){return!this.contextMenu||this.contextMenu.length===0?"":`
|
|
1323
|
+
<td class="text-end" style="width: 1px;">
|
|
1324
|
+
<div class="dropdown">
|
|
1325
|
+
<button class="btn btn-sm btn-link border-0"
|
|
1326
|
+
type="button"
|
|
1327
|
+
data-bs-toggle="dropdown"
|
|
1328
|
+
aria-expanded="false"
|
|
1329
|
+
style="color: #6c757d;">
|
|
1330
|
+
<i class="bi bi-three-dots-vertical"></i>
|
|
1331
|
+
</button>
|
|
1332
|
+
<ul class="dropdown-menu dropdown-menu-end shadow-sm">
|
|
1333
|
+
${this.buildContextMenuItems()}
|
|
1334
|
+
</ul>
|
|
1335
|
+
</div>
|
|
1336
|
+
</td>
|
|
1337
|
+
`}buildContextMenuItems(){return this.contextMenu.map(e=>{if(e.separator||e.divider)return'<li><hr class="dropdown-divider"></li>';let t="dropdown-item";return(e.action==="delete"||e.danger)&&(t+=" text-danger"),e.disabled&&(t+=" disabled"),`
|
|
1338
|
+
<li>
|
|
1339
|
+
<a class="${t}" href="#"
|
|
1340
|
+
data-id="{{model.id}}"
|
|
1341
|
+
data-action="${e.action}"
|
|
1342
|
+
${e.disabled?'aria-disabled="true" tabindex="-1"':""}>
|
|
1343
|
+
${e.icon?`<i class="${e.icon} me-2"></i>`:""}
|
|
1344
|
+
${e.label}
|
|
1345
|
+
</a>
|
|
1346
|
+
</li>
|
|
1347
|
+
`}).join("")}async onAfterRender(){await super.onAfterRender(),this.columns.forEach(t=>{if(t.formatter&&typeof t.formatter=="function"){const s=this.element.querySelector(`[data-formatter="${t.key}"]`);if(s){const i=this.model.get?this.model.get(t.key):this.model[t.key],a={value:i,row:this.model,model:this.model,column:t,table:this.tableView,index:this.index};try{s.innerHTML=t.formatter(i,a)}catch(r){console.error(`Error formatting cell for column ${t.key}:`,r)}}}}),this.selected&&this.element.classList.add("selected");const e=this.model.get?this.model.get("id"):this.model.id;e&&this.element.setAttribute("data-id",e)}async onActionEditCell(e,t){e.stopPropagation();const s=t.getAttribute("data-column"),i=this.columns.find(a=>a.key===s);!i||!i.editable||this.editingCells.has(s)||await this.enterEditMode(s,i,t)}async onActionRowClick(e,t){e.target.closest(".btn-group")||e.target.closest(".dropdown")||e.target.closest(".cell-editor")||(this.emit("row:click",{row:this,model:this.model,column:t.getAttribute("data-column"),event:e}),this.tableView&&this.tableView.emit("row:click",{row:this,model:this.model,column:t.getAttribute("data-column"),event:e}))}async onActionView(e,t){e.stopPropagation(),this.emit("row:view",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:view",{row:this,model:this.model,event:e})}async onActionEdit(e,t){return e.stopPropagation(),this.emit("row:edit",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:edit",{row:this,model:this.model,event:e}),!0}async onActionDelete(e,t){e.stopPropagation(),this.emit("row:delete",{row:this,model:this.model,event:e}),this.tableView&&this.tableView.emit("row:delete",{row:this,model:this.model,event:e})}async enterEditMode(e,t,s){const i=s.querySelector(".cell-content");if(!i)return;this.editingCells.add(e);const a=this.model.get?this.model.get(e):this.model[e],r=this.createCellEditor(t,a),n=i.innerHTML;i.style.display="none";const l=document.createElement("div");l.className="cell-editor",l.innerHTML=r,s.appendChild(l);const o=l.querySelector("input, select, .form-check-input");o&&(o.focus(),(o.type==="text"||o.type==="textarea")&&o.select()),l.dataset.originalContent=n,l.dataset.columnKey=e,this.setupEditorEvents(l,e,t),this.emit("cell:edit",{row:this,model:this.model,column:e,originalValue:a})}createCellEditor(e,t){const s=e.editableOptions||{};switch(s.type){case"select":return this.createSelectEditor(s,t);case"switch":case"checkbox":return this.createSwitchEditor(s,t);case"textarea":return this.createTextareaEditor(s,t);default:return this.createTextEditor(s,t)}}createTextEditor(e,t){const s=e.placeholder||"";return`
|
|
1348
|
+
<div class="d-flex gap-1 align-items-center">
|
|
1349
|
+
<input type="${e.inputType||"text"}"
|
|
1350
|
+
class="form-control form-control-sm cell-input"
|
|
1351
|
+
value="${this.escapeHtml(t||"")}"
|
|
1352
|
+
placeholder="${s}">
|
|
1353
|
+
<button type="button" class="btn btn-sm btn-success cell-save" title="Save">
|
|
1354
|
+
<i class="bi bi-check"></i>
|
|
1355
|
+
</button>
|
|
1356
|
+
<button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
|
|
1357
|
+
<i class="bi bi-x"></i>
|
|
1358
|
+
</button>
|
|
1359
|
+
</div>
|
|
1360
|
+
`}createTextareaEditor(e,t){const s=e.placeholder||"";return`
|
|
1361
|
+
<div class="d-flex gap-1">
|
|
1362
|
+
<textarea class="form-control form-control-sm cell-input"
|
|
1363
|
+
rows="${e.rows||2}"
|
|
1364
|
+
placeholder="${s}">${this.escapeHtml(t||"")}</textarea>
|
|
1365
|
+
<div class="d-flex flex-column gap-1">
|
|
1366
|
+
<button type="button" class="btn btn-sm btn-success cell-save" title="Save">
|
|
1367
|
+
<i class="bi bi-check"></i>
|
|
1368
|
+
</button>
|
|
1369
|
+
<button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
|
|
1370
|
+
<i class="bi bi-x"></i>
|
|
1371
|
+
</button>
|
|
1372
|
+
</div>
|
|
1373
|
+
</div>
|
|
1374
|
+
`}createSelectEditor(e,t){const s=e.options||[];let i="";return s.forEach(a=>{if(typeof a=="string")i+=`<option value="${a}" ${a===t?"selected":""}>${a}</option>`;else if(typeof a=="object"&&a.value!==void 0){const r=a.value===t?"selected":"";i+=`<option value="${a.value}" ${r}>${a.label||a.value}</option>`}}),`
|
|
1375
|
+
<div class="d-flex gap-1 align-items-center">
|
|
1376
|
+
<select class="form-select form-select-sm cell-input">
|
|
1377
|
+
${i}
|
|
1378
|
+
</select>
|
|
1379
|
+
<button type="button" class="btn btn-sm btn-success cell-save" title="Save">
|
|
1380
|
+
<i class="bi bi-check"></i>
|
|
1381
|
+
</button>
|
|
1382
|
+
<button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
|
|
1383
|
+
<i class="bi bi-x"></i>
|
|
1384
|
+
</button>
|
|
1385
|
+
</div>
|
|
1386
|
+
`}createSwitchEditor(e,t){const s=t?"checked":"";return`
|
|
1387
|
+
<div class="d-flex gap-2 align-items-center">
|
|
1388
|
+
<div class="form-check ${e.type==="switch"?"form-switch":""}">
|
|
1389
|
+
<input class="form-check-input cell-input" type="checkbox" ${s}>
|
|
1390
|
+
</div>
|
|
1391
|
+
<div class="d-flex gap-1">
|
|
1392
|
+
<button type="button" class="btn btn-sm btn-success cell-save" title="Save">
|
|
1393
|
+
<i class="bi bi-check"></i>
|
|
1394
|
+
</button>
|
|
1395
|
+
<button type="button" class="btn btn-sm btn-outline-secondary cell-cancel" title="Cancel">
|
|
1396
|
+
<i class="bi bi-x"></i>
|
|
1397
|
+
</button>
|
|
1398
|
+
</div>
|
|
1399
|
+
</div>
|
|
1400
|
+
`}setupEditorEvents(e,t,s){const i=e.querySelector(".cell-input"),a=e.querySelector(".cell-save"),r=e.querySelector(".cell-cancel");i&&(i.type==="text"||i.type==="email"||i.type==="number")&&i.addEventListener("keydown",n=>{n.key==="Enter"?(n.preventDefault(),this.saveCellEdit(e,t,s)):n.key==="Escape"&&(n.preventDefault(),this.cancelCellEdit(e,t))}),i&&(i.type==="checkbox"||i.tagName==="SELECT")&&s.autoSave!==!1&&i.addEventListener("change",()=>{this.saveCellEdit(e,t,s)}),a?.addEventListener("click",()=>{this.saveCellEdit(e,t,s)}),r?.addEventListener("click",()=>{this.cancelCellEdit(e,t)})}async saveCellEdit(e,t,s){const i=e.querySelector(".cell-input");if(!i)return;let a;i.type==="checkbox"?a=i.checked:(i.tagName,a=i.value);const r=this.model.get?this.model.get(t):this.model[t];try{this.model.save?await this.model.save({[t]:a}):this.model[t]=a,this.exitEditMode(e,t,a),this.emit("cell:save",{row:this,model:this.model,column:t,oldValue:r,newValue:a})}catch(n){console.error("Failed to save cell edit:",n),this.emit("cell:save:error",{row:this,model:this.model,column:t,oldValue:r,newValue:a,error:n}),e.classList.add("saving-error"),setTimeout(()=>e.classList.remove("saving-error"),3e3)}}cancelCellEdit(e,t){const s=e.dataset.originalContent;this.exitEditMode(e,t,null,s),this.emit("cell:cancel",{row:this,model:this.model,column:t})}exitEditMode(e,t,s=null,i=null){const r=e.closest("td").querySelector(".cell-content");if(r){if(s!==null){const n=this.columns.find(o=>o.key===t);let l=s;n&&n.formatter&&typeof n.formatter=="string"&&(l=M.pipe(s,n.formatter)),r.innerHTML=this.escapeHtml(l)}else i&&(r.innerHTML=i);r.style.display=""}e.remove(),this.editingCells.delete(t)}escapeHtml(e){if(e==null)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}select(){super.select(),this.addClass("selected");const e=this.element?.querySelector(".mojo-select-cell");e&&e.classList.add("selected")}deselect(){super.deselect(),this.removeClass("selected");const e=this.element?.querySelector(".mojo-select-cell");e&&e.classList.remove("selected")}}const Se={exact:{display:"is",description:"Exact match"},in:{display:"in",description:"Match any of the values (comma-separated)"},not:{display:"is not",description:"Does not match"},not_in:{display:"not in",description:"Does not match any of the values"},gt:{display:">",description:"Greater than"},gte:{display:">=",description:"Greater than or equal to"},lt:{display:"<",description:"Less than"},lte:{display:"<=",description:"Less than or equal to"},contains:{display:"contains",description:"Contains substring (case-sensitive)"},icontains:{display:"contains",description:"Contains substring (case-insensitive)"},startswith:{display:"starts with",description:"Starts with substring (case-sensitive)"},istartswith:{display:"starts with",description:"Starts with substring (case-insensitive)"},endswith:{display:"ends with",description:"Ends with substring (case-sensitive)"},iendswith:{display:"ends with",description:"Ends with substring (case-insensitive)"},isnull:{display:u=>u==="true"||u===!0?"is null":"is not null",description:"Check if value is null or not"},range:{display:"between",description:"Between two values (comma-separated)"}};function Z(u){if(!u||typeof u!="string")return{field:u,lookup:null};const e=u.split("__");if(e.length===1)return{field:u,lookup:null};const t=e[e.length-1];return Se[t]?{field:e.slice(0,-1).join("__"),lookup:t}:{field:u,lookup:null}}function Ye(u,e,t){if(!u||e===null||e===void 0)return"";const{field:s,lookup:i}=Z(u),a=Se[i];if(e&&typeof e=="object"&&!Array.isArray(e)){const n=e.start!==void 0&&e.start!==null&&e.start!=="",l=e.end!==void 0&&e.end!==null&&e.end!=="";return n||l?n&&l?`${t} between '${e.start}' and '${e.end}'`:n?`${t} from '${e.start}'`:`${t} until '${e.end}'`:`${t} is '${JSON.stringify(e)}'`}const r=Array.isArray(e)?e.join(","):String(e);if(!i||i==="exact")return`${t} is '${r}'`;if(i==="in"||i==="not_in"){const n=r.split(",").map(o=>o.trim()).filter(o=>o);if(n.length===0)return`${t} ${a.display}`;const l=n.map(o=>`'${o}'`).join(", ");return`${t} ${a.display} ${l}`}if(i==="range"){const n=r.split(",").map(l=>l.trim()).filter(l=>l);return n.length===2?`${t} between '${n[0]}' and '${n[1]}'`:`${t} ${a.display} '${r}'`}if(i==="isnull"){const n=typeof a.display=="function"?a.display(r):a.display;return`${t} ${n}`}return a?`${t} ${a.display} '${r}'`:`${t} is '${r}'`}class Fe extends he{constructor(e={}){const t={className:"table-view-component",itemClass:e.itemClass||ue,selectionMode:e.selectable?"multiple":"none",emptyMessage:e.emptyMessage||"No data available",addButtonIcon:e.addButtonIcon||"bi bi-plus-circle",...e};super(t),this.isFullscreen=!1,this.columns=e.columns||[],this.actions=e.actions||null,this.contextMenu=e.contextMenu||null,this.batchActions=e.batchActions||null,this.searchable=e.searchable!==!1,this.sortable=e.sortable!==!1,this.filterable=e.filterable!==!1,this.paginated=e.paginated!==!1,this.clickAction=e.clickAction||"view",this.itemView=e.itemView,this.addForm=e.addForm,this.editForm=e.editForm,this.deleteTemplate=e.deleteTemplate,this.formDialogConfig=e.formDialogConfig||{},this.viewDialogOptions=e.viewDialogOptions||{},this.exportOptions=e.exportOptions||null,this.options.showExport&&!this.exportOptions&&(this.exportOptions=[{format:"csv",label:"Export as CSV",icon:"bi bi-file-earmark-spreadsheet"},{format:"json",label:"Export as JSON",icon:"bi bi-file-earmark-code"}]),this.exportSource=e.exportSource||"remote",this.filters={},this.additionalFilters=e.filters||[],this.hideActivePills=e.hideActivePills||!1,this.hideActivePillNames=e.hideActivePillNames||[],this.rowAction=e.rowAction||"row-click",this.batchBarLocation=e.batchBarLocation||"bottom",this.options.addButtonLabel=e.addButtonLabel||"Add",this.toolbarButtons=e.toolbarButtons||[],this.tableOptions={striped:!0,bordered:!1,hover:!0,responsive:!1,size:null,...e.tableOptions},this.searchPlacement=e.searchPlacement||"toolbar",this.searchPlaceholder=e.searchPlaceholder||"Search...",this.initializeColumns(),this.extractColumnFilters(),this.footerTotalColumns=this.columns.filter(s=>s.footer_total===!0),this.hasFooterTotals=this.footerTotalColumns.length>0,this.template=this.buildTableTemplate(),this.setupCollectionListeners()}setupCollectionListeners(){this.hasFooterTotals&&this.collection&&this.collection.on("reset add remove change",()=>{this.updateFooterTotals()})}initializeColumns(){this.columns.forEach(e=>{!e.key&&e.name&&(e.key=e.name),!e.label&&!e.title&&(e.label=e.key.charAt(0).toUpperCase()+e.key.slice(1))})}getResponsiveClasses(e){if(!e)return"";const t=["sm","md","lg","xl","xxl"];if(typeof e=="string")return t.includes(e)?`d-none d-${e}-table-cell`:(console.warn(`Invalid visibility breakpoint: ${e}. Valid options are: ${t.join(", ")}`),"");if(typeof e=="object"){const s=[];if(e.hide){if(!t.includes(e.hide))return console.warn(`Invalid hide breakpoint: ${e.hide}. Valid options are: ${t.join(", ")}`),"";s.push(`d-table-cell d-${e.hide}-none`)}if(e.show){if(!t.includes(e.show))return console.warn(`Invalid show breakpoint: ${e.show}. Valid options are: ${t.join(", ")}`),"";e.hide?s.push(`d-${e.show}-table-cell`):s.push(`d-none d-${e.show}-table-cell`)}return s.join(" ")}return""}parseColumnKey(e){const t=e.split("|");return{fieldKey:t[0],formatter:t[1]||null}}updateFooterTotals(){if(!this.hasFooterTotals||!this.element)return;const e=this.calculateFooterTotals();console.log("Updating footer totals in DOM:",e);let t=0;this.columns.forEach(s=>{if(s.footer_total){const i=`col_${t}`,a=this.element.querySelector(`[data-total-column="${i}"]`);if(a&&e[i]){const r=this.parseColumnKey(s.key).formatter||s.formatter;let n;r&&typeof r=="string"?n=this.formatValue(e[i].value,r):n=e[i].value,a.textContent=n}t++}})}formatValue(e,t){try{return M.pipe(e,t)}catch(s){return console.warn("Error formatting value:",s),e}}calculateFooterTotals(){if(!this.hasFooterTotals||!this.collection||this.collection.length===0)return{};const e={};return this.footerTotalColumns.forEach((t,s)=>{const{fieldKey:i,formatter:a}=this.parseColumnKey(t.key);let r=0;this.collection.forEach(l=>{const o=l.get?l.get(i):l[i],c=parseFloat(o)||0;r+=c}),console.log(`Footer total for ${t.key}: ${r} (from ${this.collection.length} items)`);const n=`col_${s}`;e[n]={value:r,formatter:a||t.formatter,fieldKey:i,originalKey:t.key}}),e}extractColumnFilters(){this.filters={},this.columns.forEach(e=>{if(e.filter){const{fieldKey:t}=this.parseColumnKey(e.key);this.filters[t]=e.filter}})}isSelectable(){return this.batchActions&&this.batchActions.length>0&&this.selectionMode=="multiple"}buildTableTemplate(){const e=this.batchBarLocation==="top"?this.buildBatchActionsPanel():"",t=this.batchBarLocation==="bottom"?this.buildBatchActionsPanel():"";return`
|
|
1401
|
+
<div class="mojo-table-wrapper">
|
|
1402
|
+
${this.buildToolbarTemplate()}
|
|
1403
|
+
${e}
|
|
1404
|
+
<div class="table-container"${(()=>{const s=this.tableOptions&&this.tableOptions.fontSize!=null?this.tableOptions.fontSize:this.options&&this.options.fontSize,i=s==="sm"?"0.9rem":s==="xs"?"0.8rem":s?String(s):null;return i?` style="font-size: ${i};"`:""})()}>
|
|
1405
|
+
{{#loading}}
|
|
1406
|
+
<div class="mojo-table-loading d-flex justify-content-center align-items-center py-5">
|
|
1407
|
+
<div class="spinner-border" role="status">
|
|
1408
|
+
<span class="visually-hidden">Loading...</span>
|
|
1409
|
+
</div>
|
|
1410
|
+
</div>
|
|
1411
|
+
{{/loading}}
|
|
1412
|
+
{{^loading}}
|
|
1413
|
+
{{#isEmpty}}
|
|
1414
|
+
<div class="table-empty text-center py-5">
|
|
1415
|
+
<i class="bi bi-inbox fa-2x mb-2 text-muted"></i>
|
|
1416
|
+
<p class="text-muted">{{emptyMessage}}</p>
|
|
1417
|
+
</div>
|
|
1418
|
+
{{/isEmpty}}
|
|
1419
|
+
{{^isEmpty}}
|
|
1420
|
+
<table class="${this.buildTableClasses()}">
|
|
1421
|
+
${this.buildTableHeaderTemplate()}
|
|
1422
|
+
<tbody data-container="items"></tbody>
|
|
1423
|
+
${this.hasFooterTotals?this.buildTableFooterTemplate():""}
|
|
1424
|
+
</table>
|
|
1425
|
+
{{/isEmpty}}
|
|
1426
|
+
{{/loading}}
|
|
1427
|
+
</div>
|
|
1428
|
+
${t}
|
|
1429
|
+
${this.buildPaginationTemplate()}
|
|
1430
|
+
</div>
|
|
1431
|
+
`}buildTableClasses(){let e=["table"];return this.tableOptions.striped&&e.push("table-striped"),this.tableOptions.bordered&&e.push("table-bordered"),this.tableOptions.hover&&e.push("table-hover"),this.tableOptions.responsive&&e.push("table-responsive"),this.tableOptions.background&&e.push(`table-${this.tableOptions.background}`),this.tableOptions.size==="sm"&&e.push("table-sm"),this.tableOptions.size==="lg"&&e.push("table-lg"),e.join(" ")}buildToolbarTemplate(){return!this.searchable&&!this.filterable?"":`
|
|
1432
|
+
<div class="table-action-buttons mb-3">
|
|
1433
|
+
<div class="d-flex align-items-center gap-2">
|
|
1434
|
+
${this.buildActionButtonsTemplate()}
|
|
1435
|
+
${this.filterable?this.buildFilterDropdownTemplate():""}
|
|
1436
|
+
${this.searchable&&this.searchPlacement==="toolbar"?this.buildSearchTemplate():""}
|
|
1437
|
+
|
|
1438
|
+
</div>
|
|
1439
|
+
<div data-container="filter-pills"></div>
|
|
1440
|
+
</div>
|
|
1441
|
+
`}buildActionButtonsTemplate(){let e=[];if(e.push(`
|
|
1442
|
+
<button class="btn btn-sm btn-outline-secondary btn-refresh"
|
|
1443
|
+
data-action="refresh"
|
|
1444
|
+
title="Refresh">
|
|
1445
|
+
<i class="bi bi-arrow-clockwise"></i>
|
|
1446
|
+
</button>
|
|
1447
|
+
`),this.isFullscreenSupported()&&e.push(`
|
|
1448
|
+
<button class="btn btn-sm btn-outline-secondary btn-fullscreen"
|
|
1449
|
+
data-action="toggle-fullscreen"
|
|
1450
|
+
title="Toggle Fullscreen">
|
|
1451
|
+
<i class="bi bi-fullscreen"></i>
|
|
1452
|
+
</button>
|
|
1453
|
+
`),this.options.showAdd&&e.push(`
|
|
1454
|
+
<button class="btn btn-sm btn-success btn-add"
|
|
1455
|
+
data-action="add"
|
|
1456
|
+
title="${this.options.addButtonLabel}">
|
|
1457
|
+
<i class="${this.options.addButtonIcon} me-1"></i>
|
|
1458
|
+
<span class="d-none d-lg-inline">${this.options.addButtonLabel}</span>
|
|
1459
|
+
</button>
|
|
1460
|
+
`),this.options.showExport)if(this.exportOptions&&this.exportOptions.length>1){const t=this.exportOptions.map(s=>`
|
|
1461
|
+
<li>
|
|
1462
|
+
<a class="dropdown-item" href="#" data-action="export" data-format="${s.format}">
|
|
1463
|
+
<i class="${s.icon||"bi bi-file-earmark-arrow-down"} me-2"></i>${s.label}
|
|
1464
|
+
</a>
|
|
1465
|
+
</li>
|
|
1466
|
+
`).join("");e.push(`
|
|
1467
|
+
<div class="dropdown">
|
|
1468
|
+
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
|
|
1469
|
+
data-bs-toggle="dropdown" aria-expanded="false" title="Export">
|
|
1470
|
+
<i class="bi bi-download me-1"></i>
|
|
1471
|
+
<span class="d-none d-lg-inline">Export</span>
|
|
1472
|
+
</button>
|
|
1473
|
+
<ul class="dropdown-menu">
|
|
1474
|
+
${t}
|
|
1475
|
+
</ul>
|
|
1476
|
+
</div>
|
|
1477
|
+
`)}else{const t=this.exportOptions&&this.exportOptions.length===1?this.exportOptions[0].format:"json";e.push(`
|
|
1478
|
+
<button class="btn btn-sm btn-outline-secondary btn-export"
|
|
1479
|
+
data-action="export"
|
|
1480
|
+
data-format="${t}"
|
|
1481
|
+
title="Export">
|
|
1482
|
+
<i class="bi bi-download me-1"></i>
|
|
1483
|
+
<span class="d-none d-lg-inline">Export</span>
|
|
1484
|
+
</button>
|
|
1485
|
+
`)}return this.toolbarButtons&&this.toolbarButtons.length>0&&this.toolbarButtons.forEach((t,s)=>{const{label:i="Button",icon:a="",action:r="",handler:n=null,variant:l="outline-secondary",title:o=i,className:c="",permissions:d=null}=t;if(d&&!this.checkPermissions(d))return;const h=a?`<i class="${a} me-1"></i>`:"",f=`<span class="d-none d-lg-inline">${i}</span>`;let m="";n?m=`data-action="custom-toolbar-button" data-button-index="${s}"`:r&&(m=`data-action="${r}"`);const p=`btn btn-sm btn-${l} ${c}`.trim();e.push(`
|
|
1486
|
+
<button class="${p}"
|
|
1487
|
+
${m}
|
|
1488
|
+
title="${o}">
|
|
1489
|
+
${h}${f}
|
|
1490
|
+
</button>
|
|
1491
|
+
`)}),e.join("")}buildSearchTemplate(){return`
|
|
1492
|
+
<div class="flex-grow-1" style="max-width: 400px;">
|
|
1493
|
+
<div class="input-group input-group-sm">
|
|
1494
|
+
<span class="input-group-text">
|
|
1495
|
+
<i class="bi bi-search"></i>
|
|
1496
|
+
</span>
|
|
1497
|
+
<input type="search"
|
|
1498
|
+
class="form-control"
|
|
1499
|
+
placeholder="{{searchPlaceholder}}"
|
|
1500
|
+
data-filter="search"
|
|
1501
|
+
data-change-action="apply-search"
|
|
1502
|
+
value="{{collection.params.search}}"
|
|
1503
|
+
aria-label="Search">
|
|
1504
|
+
{{#searchValue}}
|
|
1505
|
+
<button class="btn btn-outline-secondary" type="button"
|
|
1506
|
+
data-action="clear-search"
|
|
1507
|
+
title="Clear search">
|
|
1508
|
+
<i class="bi bi-x"></i>
|
|
1509
|
+
</button>
|
|
1510
|
+
{{/searchValue}}
|
|
1511
|
+
</div>
|
|
1512
|
+
</div>
|
|
1513
|
+
`}buildFilterDropdownTemplate(){return this.filters&&Object.keys(this.filters).length>0||this.additionalFilters&&this.additionalFilters.length>0?`
|
|
1514
|
+
<div class="dropdown">
|
|
1515
|
+
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
|
|
1516
|
+
data-bs-toggle="dropdown" aria-expanded="false">
|
|
1517
|
+
<i class="bi bi-filter me-1"></i>
|
|
1518
|
+
<span class="d-none d-lg-inline">Add Filter</span>
|
|
1519
|
+
</button>
|
|
1520
|
+
<div class="dropdown-menu" style="min-width: 250px;">
|
|
1521
|
+
${this.buildFilterList()}
|
|
1522
|
+
</div>
|
|
1523
|
+
</div>
|
|
1524
|
+
`:""}buildFilterList(){const e=this.getAllAvailableFilters(),t=this.getActiveFilters();return e.length===0?'<div class="dropdown-item-text text-muted">No filters available</div>':`
|
|
1525
|
+
${e.map(i=>{const a=t.hasOwnProperty(i.key),r=a?"active":"",n=this.getFilterIcon(i.type||i.config?.type);return`
|
|
1526
|
+
<button class="dropdown-item ${r}"
|
|
1527
|
+
data-action="add-filter"
|
|
1528
|
+
data-filter-key="${i.key}">
|
|
1529
|
+
<i class="bi bi-${n} me-2"></i>
|
|
1530
|
+
${i.label}
|
|
1531
|
+
${a?'<i class="bi bi-check-circle ms-auto"></i>':""}
|
|
1532
|
+
</button>
|
|
1533
|
+
`}).join("")}
|
|
1534
|
+
${Object.keys(t).length>0?`
|
|
1535
|
+
<div class="dropdown-divider"></div>
|
|
1536
|
+
<button class="dropdown-item text-danger" data-action="clear-all-filters">
|
|
1537
|
+
<i class="bi bi-x-circle me-2"></i>Clear All Filters
|
|
1538
|
+
</button>
|
|
1539
|
+
`:""}
|
|
1540
|
+
`}updateFilterPills(){const e=this.element?.querySelector('[data-container="filter-pills"]');if(!e)return;this.getActiveFilters();const t=this.buildActivePills();e.innerHTML=t}updateSearchInputs(e){const t=this.element?.querySelectorAll('[data-filter="search"]');t&&t.forEach(s=>{s.value=e||""})}buildActivePills(){if(this.hideActivePills)return"";const e=this.getActiveFilters(),t=e.search&&e.search.toString().trim()!=="";let s=Object.entries(e).filter(([n,l])=>l&&l.toString().trim()!==""&&n!=="search");if(this.hideActivePillNames&&this.hideActivePillNames.length>0&&(s=s.filter(([n])=>!this.hideActivePillNames.includes(n))),s.length===0&&!t)return"";const i=s.map(([n,l])=>{const{field:o}=Z(n),c=this.getFilterLabel(o),d=Ye(n,l,c);return`
|
|
1541
|
+
<span class="badge bg-primary me-1 mb-1 py-1 px-2 position-relative" style="font-size: 0.75rem;">
|
|
1542
|
+
<i class="bi bi-filter me-1" style="font-size: 0.65rem;"></i>
|
|
1543
|
+
|
|
1544
|
+
<button type="button" class="btn btn-link text-white p-0 ms-1"
|
|
1545
|
+
style="font-size: 0.65rem; line-height: 1;"
|
|
1546
|
+
data-action="edit-filter"
|
|
1547
|
+
data-filter="${n}"
|
|
1548
|
+
title="Edit filter">
|
|
1549
|
+
${d}
|
|
1550
|
+
</button>
|
|
1551
|
+
|
|
1552
|
+
<button type="button" class="btn-close btn-close-white ms-1"
|
|
1553
|
+
style="font-size: 0.6rem; width: 0.5rem; height: 0.5rem;"
|
|
1554
|
+
data-action="remove-filter"
|
|
1555
|
+
data-filter="${n}"
|
|
1556
|
+
title="Remove filter">
|
|
1557
|
+
</button>
|
|
1558
|
+
</span>
|
|
1559
|
+
`}).join(""),r=s.length>1||s.length>0&&t||s.length===0&&t?`
|
|
1560
|
+
<button class="btn btn-sm btn-outline-secondary mb-1 py-0 px-2" style="font-size: 0.75rem;" data-action="clear-all-filters">
|
|
1561
|
+
<i class="bi bi-x-circle me-1" style="font-size: 0.7rem;"></i>
|
|
1562
|
+
<small>Clear All</small>
|
|
1563
|
+
</button>
|
|
1564
|
+
`:"";return`
|
|
1565
|
+
<div class="row mt-2">
|
|
1566
|
+
<div class="col-12">
|
|
1567
|
+
<div class="d-flex flex-wrap align-items-center">
|
|
1568
|
+
${i}
|
|
1569
|
+
${r}
|
|
1570
|
+
</div>
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
`}buildTableHeaderTemplate(){let e="";return this.isSelectable()&&(e+=`
|
|
1574
|
+
<th style="width: 40px; padding: 0;">
|
|
1575
|
+
<div class="mojo-select-all-cell" data-action="select-all">
|
|
1576
|
+
<div class="mojo-checkbox">
|
|
1577
|
+
<i class="bi bi-check"></i>
|
|
1578
|
+
</div>
|
|
1579
|
+
</div>
|
|
1580
|
+
</th>
|
|
1581
|
+
`),this.columns.forEach(t=>{const{fieldKey:s}=this.parseColumnKey(t.key),i=this.sortable&&t.sortable!==!1,a=this.getSortBy()===s?this.getSortDirection():null,r=this.getSortIcon(a),n=t.label||t.title||s,l=this.getResponsiveClasses(t.visibility),o=i?`
|
|
1582
|
+
<div class="dropdown d-inline-block ms-2">
|
|
1583
|
+
<button class="btn btn-sm btn-link p-0 text-decoration-none" type="button"
|
|
1584
|
+
data-bs-toggle="dropdown" aria-expanded="false"
|
|
1585
|
+
data-column="${s}">
|
|
1586
|
+
${r}
|
|
1587
|
+
</button>
|
|
1588
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
1589
|
+
<li><a class="dropdown-item ${a==="asc"?"active":""}"
|
|
1590
|
+
data-action="sort" data-field="${s}" data-direction="asc">
|
|
1591
|
+
<i class="bi bi-sort-alpha-down me-2"></i>Sort A-Z
|
|
1592
|
+
</a></li>
|
|
1593
|
+
<li><a class="dropdown-item ${a==="desc"?"active":""}"
|
|
1594
|
+
data-action="sort" data-field="${s}" data-direction="desc">
|
|
1595
|
+
<i class="bi bi-sort-alpha-down-alt me-2"></i>Sort Z-A
|
|
1596
|
+
</a></li>
|
|
1597
|
+
<li><a class="dropdown-item ${a===null?"active":""}"
|
|
1598
|
+
data-action="sort" data-field="${s}" data-direction="none">
|
|
1599
|
+
<i class="bi bi-x-circle me-2"></i>No Sort
|
|
1600
|
+
</a></li>
|
|
1601
|
+
</ul>
|
|
1602
|
+
</div>
|
|
1603
|
+
`:"";e+=`
|
|
1604
|
+
<th class="${i?"sortable":""} ${l}">
|
|
1605
|
+
<div class="d-flex align-items-center">
|
|
1606
|
+
<span>${n}</span>
|
|
1607
|
+
${o}
|
|
1608
|
+
</div>
|
|
1609
|
+
</th>
|
|
1610
|
+
`}),this.actions?e+="<th>Actions</th>":this.contextMenu&&(e+='<th style="width: 1px;"></th>'),`
|
|
1611
|
+
<thead>
|
|
1612
|
+
<tr>
|
|
1613
|
+
${e}
|
|
1614
|
+
</tr>
|
|
1615
|
+
</thead>
|
|
1616
|
+
`}buildTableFooterTemplate(){let e="";this.isSelectable()&&(e+="<td></td>");let t=0;return this.columns.forEach((s,i)=>{const a=this.getResponsiveClasses(s.visibility);if(s.footer_total){const r=`col_${t}`,n=this.parseColumnKey(s.key).formatter||s.formatter;let l;n&&typeof n=="string"?l=`{{{footerTotals.${r}.value|${n}}}}`:l=`{{footerTotals.${r}.value}}`,e+=`<td class="table-footer-total ${a}" data-total-column="${r}">${l}</td>`,t++}else i===0?e+=`<td class="table-footer-label ${a}"><strong>Totals</strong></td>`:e+=`<td class="${a}"></td>`}),(this.actions||this.contextMenu)&&(e+="<td></td>"),`
|
|
1617
|
+
<tfoot>
|
|
1618
|
+
<tr class="table-totals-row">
|
|
1619
|
+
${e}
|
|
1620
|
+
</tr>
|
|
1621
|
+
</tfoot>
|
|
1622
|
+
`}buildBatchActionsPanel(){if(!this.batchActions||this.batchActions.length===0)return"";if(this.batchBarLocation==="top"){let e="";return this.batchActions.forEach(t=>{e+=`
|
|
1623
|
+
<button class="btn btn-sm btn-outline-secondary" data-action="batch-${t.action}" title="${t.label}">
|
|
1624
|
+
<i class="${t.icon} me-1"></i>
|
|
1625
|
+
<span class="d-none d-lg-inline">${t.label}</span>
|
|
1626
|
+
</button>
|
|
1627
|
+
`}),`
|
|
1628
|
+
<div class="batch-actions-panel-top alert alert-info d-none mb-3" role="alert">
|
|
1629
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
1630
|
+
<div class="d-flex align-items-center">
|
|
1631
|
+
<strong class="me-2">
|
|
1632
|
+
<span class="batch-select-count">0</span> ${this.options.batchPanelTitle||"items"} selected
|
|
1633
|
+
</strong>
|
|
1634
|
+
</div>
|
|
1635
|
+
<div class="d-flex gap-2 align-items-center">
|
|
1636
|
+
${e}
|
|
1637
|
+
<button class="btn btn-sm btn-outline-secondary" data-action="clear-selection" title="Clear Selection">
|
|
1638
|
+
<i class="bi bi-x-circle me-1"></i>
|
|
1639
|
+
<span class="d-none d-lg-inline">Clear</span>
|
|
1640
|
+
</button>
|
|
1641
|
+
</div>
|
|
1642
|
+
</div>
|
|
1643
|
+
</div>
|
|
1644
|
+
`}else{let e="";return this.batchActions.forEach(t=>{e+=`
|
|
1645
|
+
<div class="batch-select-action text-center px-2" data-action="batch-${t.action}">
|
|
1646
|
+
<div class="batch-action-icon fs-3">
|
|
1647
|
+
<i class="${t.icon}"></i>
|
|
1648
|
+
</div>
|
|
1649
|
+
<div class="batch-action-title small">${t.label}</div>
|
|
1650
|
+
</div>
|
|
1651
|
+
`}),`
|
|
1652
|
+
<div class="batch-actions-panel rounded-start rounded-end" style="display: none;">
|
|
1653
|
+
<div class="batch-select-panel rounded-start rounded-end">
|
|
1654
|
+
<div class="row g-0">
|
|
1655
|
+
<div class="col-auto">
|
|
1656
|
+
<div class="batch-select-count rounded-start">0</div>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div class="col">
|
|
1659
|
+
<div class="ps-2 batch-select-title">${this.options.batchPanelTitle||"Rows"}</div>
|
|
1660
|
+
</div>
|
|
1661
|
+
<div class="col">
|
|
1662
|
+
<div class="batch-select-actions d-flex justify-content-end">
|
|
1663
|
+
${e}
|
|
1664
|
+
</div>
|
|
1665
|
+
</div>
|
|
1666
|
+
<div class="col-auto">
|
|
1667
|
+
<div class="batch-select-end rounded-end"></div>
|
|
1668
|
+
</div>
|
|
1669
|
+
</div>
|
|
1670
|
+
</div>
|
|
1671
|
+
</div>
|
|
1672
|
+
`}}buildPaginationTemplate(){return this.paginated?`
|
|
1673
|
+
<div class="table-status-bar mt-3">
|
|
1674
|
+
<div class="d-flex flex-column flex-lg-row justify-content-center justify-content-lg-between align-items-center gap-3">
|
|
1675
|
+
<div class="d-flex flex-column flex-sm-row align-items-center gap-2 gap-sm-3 text-center text-lg-start">
|
|
1676
|
+
<span class="text-muted">
|
|
1677
|
+
Showing <span data-value="start">0</span> to <span data-value="end">0</span>
|
|
1678
|
+
of <span data-value="total">0</span> entries
|
|
1679
|
+
</span>
|
|
1680
|
+
<div class="d-flex align-items-center">
|
|
1681
|
+
<label class="form-label me-2 mb-0">Show:</label>
|
|
1682
|
+
<select class="form-select form-select-sm" style="width: auto;" data-change-action="page-size">
|
|
1683
|
+
<option value="5">5</option>
|
|
1684
|
+
<option value="10">10</option>
|
|
1685
|
+
<option value="25">25</option>
|
|
1686
|
+
<option value="50">50</option>
|
|
1687
|
+
<option value="100">100</option>
|
|
1688
|
+
</select>
|
|
1689
|
+
</div>
|
|
1690
|
+
</div>
|
|
1691
|
+
<nav aria-label="Table pagination">
|
|
1692
|
+
<ul class="pagination pagination-sm mb-0 justify-content-center" data-container="pagination">
|
|
1693
|
+
<!-- Pagination will be rendered here -->
|
|
1694
|
+
</ul>
|
|
1695
|
+
</nav>
|
|
1696
|
+
</div>
|
|
1697
|
+
</div>
|
|
1698
|
+
`:""}_createItemView(e,t){const s=new this.itemClass({model:e,index:t,listView:this,tableView:this,template:this.itemTemplate,columns:this.columns,actions:this.actions,contextMenu:this.contextMenu,batchActions:this.batchActions,containerId:"items"});return this.itemViews.set(e.id,s),s.on("item:select",i=>{this._onItemSelect(i),this.updateBatchActionsPanel()}),s.on("item:deselect",i=>{this._onItemDeselect(i),this.updateBatchActionsPanel()}),s.on("row:click",this._onRowClick.bind(this)),s.on("row:view",this._onRowView.bind(this)),s.on("row:edit",this._onRowEdit.bind(this)),s.on("row:delete",this._onRowDelete.bind(this)),s.on("cell:edit",this._onCellEdit.bind(this)),s.on("cell:save",this._onCellSave.bind(this)),s.on("cell:cancel",this._onCellCancel.bind(this)),s}async onMounted(){await super.onMounted();const e=this.getActiveFilters();this.collection&&Object.keys(e).length>0&&this.updateFilterPills(),this.setupSearchClearListener()}setupSearchClearListener(){if(!this.element)return;this.element.querySelectorAll('input[type="search"][data-filter="search"]').forEach(t=>{t.addEventListener("input",s=>{s.target.value===""&&this.getActiveFilters().search&&this.onActionClearSearch(s,s.target)})})}_onRowClick(e){if(this.emit("row:click",e),this.options.onRowClick)return this.options.onRowClick(e.model,e.event);this.clickAction==="view"?this._onRowView(e):this.clickAction==="edit"&&this._onRowEdit(e)}getModelClass(e){return this.collection?.ModelClass?this.collection.ModelClass:this.collection?.model?this.collection.model:e?.constructor?e.constructor:null}getModelName(e){const t=this.getModelClass(e);return t&&(t.MODEL_NAME||t.name.replace(/Model$/,""))||"Item"}getItemViewClass(e){if(this.itemView)return this.itemView;const t=this.getModelClass(e);return t?.VIEW_CLASS?t.VIEW_CLASS:null}getAddFormConfig(e){return this.addForm||e?.ADD_FORM||this.editForm||e?.EDIT_FORM}getEditFormConfig(e){return this.editForm||e?.EDIT_FORM||this.addForm||e?.ADD_FORM}getFormDialogConfig(e){return{...e?.FORM_DIALOG_CONFIG,...this.formDialogConfig}}renderTemplateString(e,t){return e?E.render(e,t):""}async _onRowView(e){if(this.emit("row:view",e),this.options.onItemView){await this.options.onItemView(e.model,e.event);return}const t=this.getItemViewClass(e.model);if(t){const s=new t({model:e.model});await k.showDialog({header:!1,body:s,size:"lg",centered:!1,...this.getFormDialogConfig(this.getModelClass(e.model)),...this.viewDialogOptions})}else await k.showData({title:`View ${this.getModelName(e.model)} #${e.model.id}`,model:e.model})}async _onRowEdit(e){if(this.emit("row:edit",e),this.options.onItemEdit){await this.options.onItemEdit(e.model,e.event);return}const t=this.getModelClass(e.model);let s=this.getEditFormConfig(t);if(s){s.fields||(s={title:`Edit ${this.getModelName(e.model)}`,fields:s});const i=await k.showModelForm({model:e.model,...s,...this.getFormDialogConfig(t)});if(!i)return;if(!i.success||!i?.result?.data.status){k.showError(i?.result?.data?.error||i?.result?.message||"An error occurred");return}}else{const i=await k.showDialog({title:`Edit ${this.getModelName(e.model)} #${e.model.id}`,body:new H({model:e.model,fields:this.options.formFields||[]})});if(i){const a=await e.model.save(i);if(!a.data?.status){k.showError(a.data.error||"An error occurred");return}await this.refresh()}}}async _onRowDelete(e){if(this.emit("row:delete",e),this.options.onItemDelete){await this.options.onItemDelete(e.model,e.event);return}const t=this.getModelClass(e.model),s=this.deleteTemplate||t?.DELETE_TEMPLATE||'Are you sure you want to delete this {{name||"item"}}?',i=this.renderTemplateString(s,e.model);await k.confirm({message:i||"Are you sure you want to delete this item?",title:"Confirm Delete",confirmText:"Delete",confirmClass:"btn-danger"})&&(await e.model.destroy(),this.collection.fetch())}_onCellEdit(e){this.emit("cell:edit",e)}async _onCellSave(e){this.emit("cell:save",e)}_onCellCancel(e){this.emit("cell:cancel",e)}isFullscreenSupported(){return!!(document.fullscreenEnabled||document.mozFullScreenEnabled||document.webkitFullscreenEnabled||document.msFullscreenEnabled)}async onActionToggleFullscreen(e,t){this.isFullscreen?await this.exitFullscreen():await this.enterFullscreen()}async enterFullscreen(){try{this.element.requestFullscreen?await this.element.requestFullscreen():this.element.mozRequestFullScreen?await this.element.mozRequestFullScreen():this.element.webkitRequestFullscreen?await this.element.webkitRequestFullscreen():this.element.msRequestFullscreen&&await this.element.msRequestFullscreen(),this.isFullscreen=!0,this.element.classList.add("table-fullscreen"),this.updateFullscreenButton(),this.setupFullscreenListeners(),this.emit("table:fullscreen:enter")}catch(e){console.warn("Could not enter fullscreen:",e)}}async exitFullscreen(){try{document.exitFullscreen?await document.exitFullscreen():document.mozCancelFullScreen?await document.mozCancelFullScreen():document.webkitExitFullscreen?await document.webkitExitFullscreen():document.msExitFullscreen&&await document.msExitFullscreen(),this.isFullscreen=!1,this.element.classList.remove("table-fullscreen"),this.updateFullscreenButton(),this.emit("table:fullscreen:exit")}catch(e){console.warn("Could not exit fullscreen:",e)}}updateFullscreenButton(){const e=this.element?.querySelector(".btn-fullscreen"),t=e?.querySelector("i");e&&t&&(this.isFullscreen?(t.className="bi bi-fullscreen-exit",e.title="Exit Fullscreen"):(t.className="bi bi-fullscreen",e.title="Enter Fullscreen"))}setupFullscreenListeners(){if(this._fullscreenHandler)return;const e=()=>{!!!(document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement)&&this.isFullscreen&&(this.isFullscreen=!1,this.element.classList.remove("table-fullscreen"),this.updateFullscreenButton(),this.emit("table:fullscreen:exit"))};document.addEventListener("fullscreenchange",e),document.addEventListener("mozfullscreenchange",e),document.addEventListener("webkitfullscreenchange",e),document.addEventListener("msfullscreenchange",e),this._fullscreenHandler=e}cleanupFullscreenListeners(){this._fullscreenHandler&&(document.removeEventListener("fullscreenchange",this._fullscreenHandler),document.removeEventListener("mozfullscreenchange",this._fullscreenHandler),document.removeEventListener("webkitfullscreenchange",this._fullscreenHandler),document.removeEventListener("msfullscreenchange",this._fullscreenHandler),this._fullscreenHandler=null)}destroy(){this.cleanupFullscreenListeners(),super.destroy()}async onActionRefresh(e,t){await this.refresh()}async onActionAdd(e,t){if(this.options.onAdd){this.emit("table:add",{event:e}),await this.options.onAdd(e);return}this.emit("table:add",{event:e});const s=this.getModelClass();if(!s){console.warn("Cannot determine Model class for add operation");return}let i=this.getAddFormConfig(s);if(i){const a=new s;i.fields||(i={title:`Add ${this.getModelName()}`,fields:i});const r=await k.showForm({model:a,...i,...this.getFormDialogConfig(s)});if(r){this.options.addRequiresActiveGroup&&(r.group=this.getApp().activeGroup.id),this.options.addRequiresActiveUser&&(r.user=this.getApp().activeUser.id),this.options.addFormDefaults&&Object.assign(r,this.options.addFormDefaults);const n=await a.save(r);if(!n?.data.status){k.showError(n?.data.error||"An error occurred");return}this.collection&&this.collection.add(a),await this.refresh()}}else{const a=new s,r=await k.showDialog({title:`Add ${this.getModelName()}`,body:new H({model:a,fields:this.options.formFields||[]})});if(r){const n=await a.save(r);if(!n?.data.status){k.showError(n.data.error||"An error occurred");return}this.collection&&this.collection.add(a),await this.refresh()}}}async onActionExport(e,t){const s=t.getAttribute("data-format")||"json";this.emit("table:export",{format:s,source:this.exportSource,event:e}),this.exportSource==="remote"?this.collection?await this.collection.download(s):console.warn("TableView: Cannot export from remote without a collection."):this.options.onExport?await this.options.onExport(this.collection?.toJSON()||[],s):console.warn("TableView: onExport handler not implemented for local export.")}async onActionApplySearch(e,t){const s=t.value.trim();this.collection&&(this.setFilter("search",s),this.collection.params.start=0,this.collection.restEnabled?await this.collection.fetch():this.render()),this.updateFilterPills(),this.emit("table:search",{searchTerm:s,event:e}),this.emit("params-changed")}async onActionClearSearch(e,t){this.setFilter("search",null),this.collection&&(this.collection.params.start=0,this.collection.restEnabled&&await this.collection.fetch()),await this.render(),this.updateFilterPills(),this.emit("table:search",{searchTerm:"",event:e}),this.emit("params-changed")}getSortBy(){const e=this.collection?.params?.sort;return e?e.startsWith("-")?e.slice(1):e:null}getSortDirection(){const e=this.collection?.params?.sort;return e&&e.startsWith("-")?"desc":"asc"}getSortIcon(e){return e==="asc"?'<i class="bi bi-sort-alpha-down text-primary"></i>':e==="desc"?'<i class="bi bi-sort-alpha-down-alt text-primary"></i>':'<i class="bi bi-three-dots-vertical text-muted"></i>'}async onActionSort(e,t){e.preventDefault();const s=t.getAttribute("data-field"),i=t.getAttribute("data-direction");if(this.collection){let a;if(i==="none"?a=void 0:i==="desc"?a=`-${s}`:a=s,this.collection.setParams({...this.collection.params,sort:a,start:0}),this.collection.restEnabled)await this.collection.fetch();else{if(a){const r=a.startsWith("-"),n=r?a.slice(1):a;this.collection.sort((l,o)=>{const c=l.get(n),d=o.get(n);return c<d?r?1:-1:c>d?r?-1:1:0})}this.render()}}this.updateSortIcons(),this.emit("table:sort",{field:s,event:e}),this.emit("params-changed")}updateSortIcons(){if(!this.element)return;const e=this.getSortBy(),t=this.getSortDirection();this.columns.forEach(s=>{if(this.sortable&&s.sortable!==!1){const{fieldKey:i}=this.parseColumnKey(s.key),a=this.element.querySelector(`[data-bs-toggle="dropdown"][data-column="${i}"]`);if(a){const r=e===i,n=this.getSortIcon(r?t:null);a.innerHTML=n;const l=a.nextElementSibling;if(l){const o=l.querySelector(`[data-field="${i}"][data-direction="asc"]`),c=l.querySelector(`[data-field="${i}"][data-direction="desc"]`),d=l.querySelector(`[data-field="${i}"][data-direction="none"]`);o&&o.classList.toggle("active",r&&t==="asc"),c&&c.classList.toggle("active",r&&t==="desc"),d&&d.classList.toggle("active",!r||e!==i)}}}})}async onActionSelectAll(e,t){e.stopPropagation();const s=this.itemViews.size>0&&Array.from(this.itemViews.values()).every(a=>a.selected);s?this.clearSelection():this.forEachItem(a=>{a.selected||a.select()});const i=this.element?.querySelector(".mojo-select-all-cell");i&&i.classList.toggle("selected",!s),this.updateBatchActionsPanel()}async onBeforeRender(){this.searchValue=this.getActiveFilters().search||"",this.footerTotals=this.calculateFooterTotals()}async onAfterRender(){if(await super.onAfterRender(),this.hasFooterTotals&&this.updateFooterTotals(),this.paginated&&this.collection){const e=this.collection.meta?.count||this.collection.length(),t=this.collection.params?.start||0,s=this.collection.params?.size||10,i=Math.min(t+s,e),a=this.element.querySelector('[data-value="start"]'),r=this.element.querySelector('[data-value="end"]'),n=this.element.querySelector('[data-value="total"]');a&&(a.textContent=t+1),r&&(r.textContent=i),n&&(n.textContent=e);const l=this.element.querySelector('[data-change-action="page-size"]');l&&(l.value=s),this.renderPagination()}this.updateSortIcons(),this.updateFilterPills(),this.setupSearchClearListener()}renderPagination(){const e=this.element.querySelector('[data-container="pagination"]');if(!e||!this.collection)return;const t=this.collection.meta?.count||this.collection.length(),s=this.collection.params?.size||10,i=this.collection.params?.start||0,a=Math.floor(i/s)+1,r=Math.ceil(t/s);if(r<=1){e.innerHTML="";return}const n=a>1?a-1:r,l=a<r?a+1:1,o=[];o.push(`
|
|
1699
|
+
<li class="page-item">
|
|
1700
|
+
<a class="page-link" href="#" data-action="page" data-page="${n}">
|
|
1701
|
+
<i class="bi bi-chevron-left"></i>
|
|
1702
|
+
</a>
|
|
1703
|
+
</li>
|
|
1704
|
+
`);const c=1,d=new Set([1,r]);for(let m=a-c;m<=a+c;m++)m>=1&&m<=r&&d.add(m);const h=Array.from(d).sort((m,p)=>m-p);let f=0;for(const m of h)f&&m-f>1&&o.push(`
|
|
1705
|
+
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
1706
|
+
`),o.push(`
|
|
1707
|
+
<li class="page-item ${m===a?"active":""}">
|
|
1708
|
+
<a class="page-link" href="#" data-action="page" data-page="${m}">${m}</a>
|
|
1709
|
+
</li>
|
|
1710
|
+
`),f=m;o.push(`
|
|
1711
|
+
<li class="page-item">
|
|
1712
|
+
<a class="page-link" href="#" data-action="page" data-page="${l}">
|
|
1713
|
+
<i class="bi bi-chevron-right"></i>
|
|
1714
|
+
</a>
|
|
1715
|
+
</li>
|
|
1716
|
+
`),e.innerHTML=o.join("")}async onActionPage(e,t){e.preventDefault();const s=parseInt(t.getAttribute("data-page"),10),i=this.collection.params?.size||10,a=this.collection.meta?.count||this.collection.length(),r=Math.max(1,Math.ceil(a/i));let n=isNaN(s)?1:s;n<1&&(n=r),n>r&&(n=1),this.collection.setParams({...this.collection.params,start:(n-1)*i}),this.collection.restEnabled?await this.collection.fetch():this.render(),this.emit("table:page",{page:n,event:e}),this.emit("params-changed")}async onChangePageSize(e,t){const s=parseInt(t.value);this.collection&&(this.collection.setParams({...this.collection.params,start:0,size:s}),this.collection.restEnabled&&await this.collection.fetch(),this.render()),this.emit("table:pagesize",{size:s,event:e}),this.emit("params-changed")}getActiveFilters(){if(!this.collection?.params)return{};const{start:e,size:t,sort:s,...i}=this.collection.params,a={},r=new Set;return this.getAllAvailableFilters().forEach(l=>{if(l.config.type==="daterange"){const o=l.key,c=l.config.startName||"dr_start",d=l.config.endName||"dr_end",h=l.config.fieldName||"dr_field";i[h]===o&&(i[c]||i[d])&&(a[o]={start:i[c]||"",end:i[d]||""},r.add(c),r.add(d),r.add(h))}}),Object.keys(i).forEach(l=>{r.has(l)||(a[l]=i[l])}),Object.keys(a).forEach(l=>{if(a.hasOwnProperty(l)){const o=`${l}__in`;a.hasOwnProperty(o)&&(delete a[l],a[o]=a[o])}}),a}setFilter(e,t){if(!this.collection)return;const s=this.getFilterConfig(e);if(s&&s.type==="daterange"){const i=s.startName||"dr_start",a=s.endName||"dr_end",r=s.fieldName||"dr_field";delete this.collection.params[i],delete this.collection.params[a],delete this.collection.params[r],t&&typeof t=="object"&&(t.start||t.end)&&(t.start&&(this.collection.params[i]=t.start),t.end&&(this.collection.params[a]=t.end),this.collection.params[r]=e)}else{const{field:i,lookup:a}=Z(e);if(delete this.collection.params[e],delete this.collection.params[i],delete this.collection.params[`${i}__in`],!t||Array.isArray(t)&&t.length===0)return;Array.isArray(t)?t.length===1?this.collection.params[i]=t[0]:this.collection.params[`${i}__in`]=t.join(","):this.collection.params[e]=t}}getAllAvailableFilters(){const e=[];return this.columns.forEach(t=>{if(t.filter){const{fieldKey:s}=this.parseColumnKey(t.key);e.push({key:s,label:t.filter.label||t.label||s,type:t.filter.type,config:t.filter})}}),this.additionalFilters&&Array.isArray(this.additionalFilters)&&this.additionalFilters.forEach(t=>{e.push({key:t.name||t.key,label:t.label,type:t.type,config:t})}),e}getFilterConfig(e){const t=this.columns.find(s=>{const{fieldKey:i}=this.parseColumnKey(s.key);return i===e});if(t&&t.filter)return t.filter;if(this.additionalFilters&&Array.isArray(this.additionalFilters)){const s=this.additionalFilters.find(i=>(i.name||i.key)===e);if(s)return s}return null}getFilterLabel(e){if(e==="search")return"Search";const t=this.filters[e];if(t&&t.label)return t.label;const s=this.additionalFilters.find(i=>(i.name||i.key)===e);return s&&s.label?s.label:e.charAt(0).toUpperCase()+e.slice(1)}getFilterDisplayValue(e,t){if(e==="search")return`"${t}"`;const s=this.filters[e]||this.additionalFilters.find(i=>(i.name||i.key)===e);if(s&&s.type==="daterange"&&typeof t=="object"){const i=t.start||"",a=t.end||"";return`${i} to ${a}`}if(s&&s.type==="select"&&s.options){if(typeof s.options[0]=="object"){const i=s.options.find(a=>a.value===t);return i?i.label:t}return t}return t}getFilterIcon(e){return{text:"search",select:"funnel",date:"calendar",daterange:"calendar-range",number:"123",boolean:"toggle-on"}[e]||"filter"}async onActionAddFilter(e,t){const s=t.getAttribute("data-filter-key"),i=this.getFilterConfig(s),a=this.getActiveFilters()[s];if(!i){console.warn("No filter config found for key:",s);return}const r=await k.showForm({title:`${a!==void 0&&a!==""?"Edit":"Add"} ${this.getFilterLabel(s)} Filter`,size:"md",fields:[this.buildFilterDialogField(i,a,s)]});if(r){const n=this.extractFilterValue(i,r);this.setFilter(s,n),await this.applyFilters()}}buildFilterDialogField(e,t,s){const i={name:"filter_value",label:e.label,value:t,...e,placeholder:e.placeholder||e.placeHolder};if(e.type==="daterange"){if(i.startName=i.startName||"dr_start",i.endName=i.endName||"dr_end",i.fieldName=i.fieldName||"dr_field",i.format=i.format||"YYYY-MM-DD",i.displayFormat=i.displayFormat||"MMM DD, YYYY",i.separator=i.separator||" to ",i.label=i.label||"Date Range",t&&typeof t=="object"){const a=r=>{if(!r&&r!==0)return"";if(r instanceof Date&&!isNaN(r))return r.toISOString().slice(0,10);const n=String(r).trim();if(!n)return"";if(/^-?\d+$/.test(n)){const o=Number(n),c=n.length<=10?o*1e3:o,d=new Date(c);if(!isNaN(d))return d.toISOString().slice(0,10)}const l=new Date(n);return isNaN(l)?n:l.toISOString().slice(0,10)};i.startDate=a(t.start||t.from||t.begin||""),i.endDate=a(t.end||t.to||t.finish||"")}}else if(e.type==="multiselect"){let a=[];t&&(Array.isArray(t)?a=t:typeof t=="string"&&(a=t.split(",").map(r=>r.trim()).filter(r=>r))),i.value=a,!i.placeholder&&!i.placeHolder&&(e.placeholder||e.placeHolder?i.placeholder=e.placeholder||e.placeHolder:e.label&&(i.placeholder=`Select ${e.label}...`))}return i}extractFilterValue(e,t){if(e.type==="daterange"){const s=e.startName||"dr_start",i=e.endName||"dr_end";return{start:t[s],end:t[i]}}return e.type==="multiselect",t.filter_value}async applyFilters(){if(this.collection&&(this.collection.params.start=0),this.collection?.restEnabled)try{await this.collection.fetch(),this.render()}catch(e){console.error("Failed to fetch filtered data:",e),this.render()}else this.render();this.updateFilterPills(),this.emit("params-changed")}async onActionEditFilter(e,t){const s=t.getAttribute("data-filter"),{field:i}=Z(s);let a=this.getFilterConfig(i)||this.getFilterConfig(s);const r=this.getActiveFilters(),n=r[s]||r[i];if(!a){console.warn("No filter config found for key:",s,"or field:",i);return}const l={filter_value:n};if(a.type==="daterange"&&n&&typeof n=="object"){const c=a.startName||"dr_start",d=a.endName||"dr_end";l[c]=n.start||"",l[d]=n.end||""}const o=await k.showForm({title:`Edit ${this.getFilterLabel(i)} Filter`,size:"md",data:l,fields:[this.buildFilterDialogField(a,n,i)]});if(o){const c=this.extractFilterValue(a,o);this.setFilter(s,c),await this.applyFilters()}}async onActionRemoveFilter(e,t){const s=t.getAttribute("data-filter"),{field:i}=Z(s);this.setFilter(s,null),s==="search"&&this.updateSearchInputs(""),this.collection.restEnabled&&await this.collection.fetch(),this.render(),this.updateFilterPills(),this.emit("filter:remove",{key:s,field:i}),this.emit("params-changed")}async onActionClearAllFilters(e,t){if(!this.collection)return;const{start:s,size:i,sort:a}=this.collection.params;this.collection.params={start:s,size:i},a&&(this.collection.params.sort=a),this.updateSearchInputs(""),this.collection.restEnabled&&await this.collection.fetch(),this.render(),this.updateFilterPills(),this.emit("filters:clear"),this.emit("params-changed")}updateBatchActionsPanel(){if(!this.batchActions||this.batchActions.length===0)return;const e=this.getSelectedItems().length;if(this.batchBarLocation==="top"){const s=this.element?.querySelector(".batch-actions-panel-top"),i=this.element?.querySelector(".batch-select-count");s&&i&&(i.textContent=e,e>0?s.classList.remove("d-none"):s.classList.add("d-none"))}else{const s=this.element?.querySelector(".batch-actions-panel"),i=this.element?.querySelector(".batch-select-count");s&&i&&(i.textContent=e,s.style.display=e>0?"block":"none")}const t=this.element?.querySelector(".mojo-select-all-cell");if(t){const s=this.itemViews.size>0&&Array.from(this.itemViews.values()).every(r=>r.selected),i=Array.from(this.itemViews.values()).some(r=>r.selected);t.classList.toggle("selected",s),t.classList.toggle("indeterminate",!s&&i);const a=t.querySelector("i");a&&(a.className=!s&&i?"bi bi-dash":"bi bi-check")}}async onActionBatch(e,t){const s=t.getAttribute("data-action").replace("batch-",""),i=this.getSelectedItems();this.emit("batch:action",{action:s,items:i,event:e})}async onActionClearSelection(e,t){this.clearSelection(),this.updateBatchActionsPanel()}async onActionCustomToolbarButton(e,t){const s=parseInt(t.getAttribute("data-button-index"),10),i=this.toolbarButtons[s];i&&typeof i.handler=="function"&&await i.handler.call(this,e,t)}}function We(){return typeof window>"u"?null:((!window.MOJO||typeof window.MOJO!="object")&&(window.MOJO={}),window.MOJO)}function Je(u){return u.__lite&&u.__lite.version||(u.WebApp=G,u.View=v,u.Page=Y,u.Router=te,u.Model=Q,u.Collection=W,u.Rest=z,u.FormBuilder=J,u.FormView=H,u.Dialog=k,u.ProgressView=$e,u.ListView=he,u.ListViewItem=X,u.TableView=Fe,u.TableRow=ue,u.DataFormatter=M,u.MOJOUtils=x,u.__lite={version:"dev",build:"web-mojo.lite"},u.mount=async function(t,s){if(!t)throw new Error("MOJO.mount(view, container) requires a view");const i=typeof s=="string"?document.querySelector(s):s;if(!i)throw new Error("MOJO.mount(view, container) container not found");if(typeof t.render!="function"||typeof t.mount!="function")throw new Error("MOJO.mount expects a View instance with render() and mount() methods");return await t.render(),await t.mount(i),t}),u}const fe=We();fe&&Je(fe);class me extends v{constructor(e={}){const{data:t,model:s,fields:i,columns:a,responsive:r,showEmptyValues:n,emptyValueText:l,...o}=e;super({tagName:"div",className:"data-view",...o}),this.data=t||{},this.fields=i||[],this.model=s||null,this.model&&(this.data=this.model),this.dataViewOptions={columns:a||2,responsive:r!==!1,showEmptyValues:n||!1,emptyValueText:l||"—",rowClass:"row g-3",itemClass:"data-view-item",labelClass:"data-view-label fw-semibold text-muted small text-uppercase",valueClass:"data-view-value"}}async onBeforeRender(){this.fields.length===0&&this.getData()&&this.generateFieldsFromData()}async renderTemplate(){const e=this.buildItemsHTML();return`
|
|
1717
|
+
<div class="${this.dataViewOptions.rowClass}">
|
|
1718
|
+
${e}
|
|
1719
|
+
</div>
|
|
1720
|
+
`}generateFieldsFromData(){const e=this.getData();e&&typeof e=="object"&&(this.fields=Object.keys(e).map(t=>{const s=e[t],i=this.inferFieldType(s,t),a=this.inferFormatter(s,t,i);return{name:t,label:this.formatLabel(t),type:i,format:a,formatter:a}}))}formatLabel(e){return e.replace(/([A-Z])/g," $1").replace(/[_-]/g," ").replace(/\b\w/g,t=>t.toUpperCase()).trim()}inferFieldType(e,t=""){if(e==null)return"text";const s=t.toLowerCase(),i=typeof e;if(s.includes("date")||s.includes("time")||s.includes("created")||s.includes("updated")||s.includes("modified")||s.includes("last_login")||s.includes("expires")||s.includes("last_activity"))return"datetime";if(s.includes("email")||s.includes("mail"))return"email";if(s.includes("url")||s.includes("link")||s.includes("website")||s.includes("homepage"))return"url";if(s.includes("phone")||s.includes("tel")||s.includes("mobile")||s.includes("cell"))return"phone";if(s.includes("price")||s.includes("cost")||s.includes("amount")||s.includes("fee")||s.includes("salary")||s.includes("revenue"))return"currency";if(s.includes("size")||s.includes("bytes"))return"filesize";if(s.includes("percent")||s.includes("rate")||s.includes("ratio")&&i==="number")return"percent";if(i==="boolean")return"boolean";if(i==="number")return"number";if(i==="object")return Array.isArray(e)?"array":e&&e.renditions?"file":this.shouldUseDataView(e,s)?"dataview":"object";if(i==="string"){if(e.includes("@")&&e.includes("."))return"email";if(e.match(/^\d{4}-\d{2}-\d{2}/))return"date";if(e.match(/^https?:\/\//))return"url";if(e.match(/^\+?[\d\s\-\(\)]+$/))return"phone"}return"text"}inferFormatter(e,t,s){const i=t.toLowerCase(),a=[];switch(s){case"datetime":i.includes("time")&&!i.includes("date")?a.push("time"):i.includes("relative")||i.includes("ago")||i.includes("last_")?a.push("relative"):i.includes("created")||i.includes("updated")||i.includes("modified")?a.push('date("MMM D, YYYY")'):a.push('date("MMMM D, YYYY")');break;case"date":i.includes("birth")||i.includes("dob")?a.push('date("MMMM D, YYYY")'):a.push('date("MMM D, YYYY")');break;case"email":break;case"url":break;case"phone":a.push("phone");break;case"currency":a.push("currency"),i.includes("eur")||i.includes("euro")?a[a.length-1]='currency("EUR")':(i.includes("gbp")||i.includes("pound"))&&(a[a.length-1]='currency("GBP")');break;case"filesize":a.push("filesize");break;case"percent":a.push("percent");break;case"number":if(typeof e=="number")if(i.includes("count")||i.includes("total")||i.includes("followers")||i.includes("views"))e>=1e3?a.push("compact"):a.push("number");else if(i.includes("score")||i.includes("rating"))a.push("number"),e%1!==0&&(a[a.length-1]="number(1)");else{if(i.includes("version")||i.includes("id"))return null;a.push("number")}break;case"boolean":break;case"text":typeof e=="string"&&(i.includes("description")||i.includes("content")||i.includes("body")?e.length>200?a.push("truncate(200)"):e.length>100&&a.push("truncate(100)"):i.includes("summary")||i.includes("excerpt")?e.length>150&&a.push("truncate(150)"):i.includes("name")||i.includes("title")||i.includes("label")?(a.push("capitalize"),e.length>50&&a.unshift("truncate(50)")):i.includes("slug")||i.includes("handle")||i.includes("username")?a.push("slug"):i.includes("code")||i.includes("token")||i.includes("key")?e.length>20&&a.push("mask"):e.length>100&&a.push("truncate(100)"));break;case"array":case"object":break;case"dataview":break;default:typeof e=="string"&&e.length>100&&a.push("truncate(100)");break}return a.length>0?a.join("|"):null}shouldUseDataView(e,t){if(!e||typeof e!="object"||Array.isArray(e))return!1;const s=["permissions","perms","access","rights","settings","config","configuration","options","profile","info","details","data","metadata","meta","attributes","props","preferences","prefs","user_data","contact","address","location","stats","statistics","metrics","counts"];if(window.utils&&window.utils.isObject(e)&&e.id)return!0;if(s.some(a=>t.includes(a))){const a=Object.keys(e);if(a.length>=2&&a.length<=20&&!a.some(n=>typeof e[n]=="object"&&e[n]!==null&&!Array.isArray(e[n])&&Object.keys(e[n]).length>3))return!0}return!1}getData(){return this.model&&this.model.attributes?{...this.model.attributes}:this.data||{}}getFieldValue(e){let t,s=e.name||e.key,i=e.format||e.formatter;if(!s)return null;if(s&&s.includes("|")){const a=s.split("|");s=a[0].trim(),i||(i=a.slice(1).join("|").trim())}if(this.model&&typeof this.model.get=="function"?t=this.model.get(s):t=this.getData()[s],i&&(t=M.pipe(t,i)),t==null||t==="")return this.dataViewOptions.showEmptyValues?this.dataViewOptions.emptyValueText:null;if(e.template){const a=this.model?this.model:this.data;return this.renderTemplateString(e.template,a)}return t}renderTemplateString(e,t){return!e||!t?"":e.replace(/\{\{([^}]+)\}\}/g,(s,i)=>{const a=i.trim();let r;const n=a.split("|"),l=n[0],o=n.slice(1).join("|");return this.model&&typeof this.model.get=="function"?r=this.model.get(l):r=l.split(".").reduce((c,d)=>c?c[d]:void 0,t),o&&(r=M.pipe(r,o)),r??""})}getColumnClasses(e){let t=this.getColumnSizeClasses(e);return e.justify=="right"?t+=" d-flex justify-content-end":e.justify=="center"&&(t+=" d-flex justify-content-center"),t}getColumnSizeClasses(e){if(e.type==="array"||e.type==="object"||e.type==="dataview")return"col-12";const t=e.columns||e.colSize||e.cols||Math.floor(12/this.dataViewOptions.columns);return this.dataViewOptions.responsive?`col-12 col-md-${t}`:`col-${t}`}buildItemsHTML(){return this.fields.map(e=>this.buildItemHTML(e)).filter(Boolean).join("")}buildItemHTML(e){const t=this.getFieldValue(e);if(t===null&&!this.dataViewOptions.showEmptyValues)return"";const s=e.label||this.formatLabel(e.name);return`
|
|
1721
|
+
<div class="${this.getColumnClasses(e)}">
|
|
1722
|
+
<div class="${this.dataViewOptions.itemClass} ${e.className}" data-field="${e.name}">
|
|
1723
|
+
${this.buildLabelHTML(s,e)}
|
|
1724
|
+
${this.buildValueHTML(t,e)}
|
|
1725
|
+
</div>
|
|
1726
|
+
</div>
|
|
1727
|
+
`}buildLabelHTML(e,t){return`<div class="${t.labelClass||this.dataViewOptions.labelClass}">${this.escapeHtml(e)}:</div>`}buildValueHTML(e,t){const s=t.valueClass||this.dataViewOptions.valueClass,i=this.formatDisplayValue(e,t);return`<div class="${s}">${i}</div>`}formatDisplayValue(e,t){if(e==null)return this.dataViewOptions.emptyValueText;if(t.template||t.format||t.formatter)return String(e);const i=this.getData()[t.name];switch(t.type){case"boolean":return i?'<span class="badge bg-success">Yes</span>':'<span class="badge bg-secondary">No</span>';case"email":const a=String(e);return`<a href="mailto:${this.escapeHtml(a)}" class="text-decoration-none">${this.escapeHtml(a)}</a>`;case"url":const r=String(e);return`<a href="${this.escapeHtml(r)}" target="_blank" rel="noopener" class="text-decoration-none">${this.escapeHtml(r)} <i class="bi bi-box-arrow-up-right"></i></a>`;case"array":case"object":return this.formatAsJson(i);case"dataview":return this.formatAsDataView(i,t);case"phone":const n=String(e);return`<a href="tel:${n.replace(/[^\d\+]/g,"")}" class="text-decoration-none">${this.escapeHtml(n)}</a>`;default:return this.escapeHtml(String(e))}}formatAsJson(e){try{const t=JSON.stringify(e,null,2),s=this.escapeHtml(t),i=t.split(`
|
|
1728
|
+
`).length,a=i>10||t.length>500,r=`json-${Math.random().toString(36).substr(2,9)}`;if(a){const n=JSON.stringify(e).substring(0,100)+(JSON.stringify(e).length>100?"...":""),l=this.escapeHtml(n);return`
|
|
1729
|
+
<div class="json-container">
|
|
1730
|
+
<div class="d-flex align-items-center justify-content-between mb-1">
|
|
1731
|
+
<small class="text-muted">${Array.isArray(e)?"Array":"Object"} (${i} lines)</small>
|
|
1732
|
+
<div class="btn-group btn-group-sm" role="group">
|
|
1733
|
+
<button type="button" class="btn btn-outline-secondary btn-sm json-toggle" data-bs-toggle="collapse" data-bs-target="#${r}" aria-expanded="false">
|
|
1734
|
+
<i class="bi bi-eye"></i> Show
|
|
1735
|
+
</button>
|
|
1736
|
+
<button type="button" class="btn btn-outline-secondary btn-sm json-copy" data-json='${this.escapeHtml(t)}' title="Copy JSON">
|
|
1737
|
+
<i class="bi bi-clipboard"></i>
|
|
1738
|
+
</button>
|
|
1739
|
+
</div>
|
|
1740
|
+
</div>
|
|
1741
|
+
<div class="json-preview bg-light p-2 rounded small border" style="font-family: 'Courier New', monospace;">
|
|
1742
|
+
<code class="text-muted">${l}</code>
|
|
1743
|
+
</div>
|
|
1744
|
+
<div class="collapse mt-2" id="${r}">
|
|
1745
|
+
<pre class="json-display p-3 rounded small mb-0" style="max-height: 400px; overflow-y: auto; white-space: pre-wrap; font-family: 'Courier New', monospace;"><code>${this.syntaxHighlightJson(s)}</code></pre>
|
|
1746
|
+
</div>
|
|
1747
|
+
</div>
|
|
1748
|
+
`}else return`
|
|
1749
|
+
<div class="json-container">
|
|
1750
|
+
<div class="d-flex align-items-center justify-content-between mb-1">
|
|
1751
|
+
<small class="text-muted">${Array.isArray(e)?"Array":"Object"}</small>
|
|
1752
|
+
<button type="button" class="btn btn-outline-secondary btn-sm json-copy" data-json='${this.escapeHtml(t)}' title="Copy JSON">
|
|
1753
|
+
<i class="bi bi-clipboard"></i>
|
|
1754
|
+
</button>
|
|
1755
|
+
</div>
|
|
1756
|
+
<pre class="json-display bg-light p-2 rounded small mb-0 border" style="white-space: pre-wrap; font-family: 'Courier New', monospace;"><code>${this.syntaxHighlightJson(s)}</code></pre>
|
|
1757
|
+
</div>
|
|
1758
|
+
`}catch{return`<span class="text-muted fst-italic">[Object: ${typeof e}] - Cannot display as JSON</span>`}}syntaxHighlightJson(e){return e.replace(/("([^"\\]|\\.)*")\s*:/g,'<span style="color: #0969da;">$1</span>:').replace(/:\s*("([^"\\]|\\.)*")/g,': <span style="color: #0a3069;">$1</span>').replace(/:\s*(true|false)/g,': <span style="color: #8250df;">$1</span>').replace(/:\s*(null)/g,': <span style="color: #656d76;">$1</span>').replace(/:\s*(-?\d+\.?\d*)/g,': <span style="color: #0550ae;">$1</span>')}bindEvents(){super.bindEvents(),this.element&&this.element.addEventListener("click",e=>{const t=e.target.closest("[data-field]");if(t){const s=t.dataset.field,i=this.fields.find(a=>a.name===s);this.emit("field:click",{field:i,fieldName:s,element:t,event:e})}e.target.closest(".json-copy")&&(e.preventDefault(),e.stopPropagation(),this.handleJsonCopy(e.target.closest(".json-copy"))),e.target.closest(".json-toggle")&&this.handleJsonToggle(e.target.closest(".json-toggle"))})}handleJsonCopy(e){const t=e.getAttribute("data-json");if(t)try{if(navigator.clipboard&&window.isSecureContext)navigator.clipboard.writeText(t).then(()=>{this.showCopyFeedback(e)});else{const s=document.createElement("textarea");s.value=t,document.body.appendChild(s),s.select(),document.execCommand("copy"),document.body.removeChild(s),this.showCopyFeedback(e)}}catch(s){console.warn("Failed to copy JSON:",s)}}handleJsonToggle(e){const t=e.querySelector("i"),s=e.getAttribute("aria-expanded")==="true";setTimeout(()=>{s?(t.className="bi bi-eye-slash",e.innerHTML='<i class="bi bi-eye-slash"></i> Hide'):(t.className="bi bi-eye",e.innerHTML='<i class="bi bi-eye"></i> Show')},10)}showCopyFeedback(e){const t=e.querySelector("i").className,s=e.querySelector("i");s.className="bi bi-check text-success",e.classList.add("btn-success"),e.classList.remove("btn-outline-secondary"),setTimeout(()=>{s.className=t,e.classList.remove("btn-success"),e.classList.add("btn-outline-secondary")},1e3)}formatAsDataView(e,t){if(!e||typeof e!="object")return'<span class="text-muted fst-italic">No data available</span>';try{const s=new this.constructor({data:e,columns:t.dataViewColumns||2,showEmptyValues:t.showEmptyValues??!0,emptyValueText:t.emptyValueText||"Not set",...t.dataViewOptions||{}});s.onInit(),s.generateFieldsFromData();const i=s.buildItemsHTML();return`
|
|
1759
|
+
<div class="nested-dataview border rounded p-3 bg-light">
|
|
1760
|
+
<div class="${s.dataViewOptions.rowClass}">
|
|
1761
|
+
${i}
|
|
1762
|
+
</div>
|
|
1763
|
+
</div>
|
|
1764
|
+
`}catch(s){return console.error("Error creating nested DataView:",s),'<span class="text-danger">Error displaying nested data</span>'}}async updateData(e){return this.data=e,this.model&&typeof this.model.set=="function"&&this.model.set(e),this.fields.length>0&&!this.options.fields&&(this.fields=[]),await this.render(),this.emit("data:updated",{data:e}),this}async updateFields(e){return this.fields=e,await this.render(),this.emit("fields:updated",{fields:e}),this}async updateConfig(e){return this.dataViewOptions={...this.dataViewOptions,...e},await this.render(),this.emit("config:updated",{options:this.dataViewOptions}),this}async refresh(){if(this.model&&typeof this.model.fetch=="function")try{await this.model.fetch(),this.emit("data:refreshed",{model:this.model})}catch(e){throw this.emit("error",{error:e,message:"Failed to refresh data"}),e}return this}getCurrentData(){return this.getData()}getField(e){return this.fields.find(t=>t.name===e)||null}setFieldFormat(e,t){const s=this.getField(e);return s?(s.format=t,s.formatter=t):this.fields.push({name:e,label:this.formatLabel(e),type:this.inferFieldType(this.getData()[e],e),format:t,formatter:t}),this}addFormatPipe(e,t){const s=this.getField(e);return s&&(s.format?(s.format+=`|${t}`,s.formatter=s.format):(s.format=t,s.formatter=t)),this}clearFieldFormat(e){const t=this.getField(e);if(t){const s=this.getData(),i=this.inferFormatter(s[e],e,t.type);t.format=i,t.formatter=i}return this}getFormattedValue(e,t=null){const s=this.getField(e);if(!s)return null;const i=t!==null?t:this.getData()[e],a=s.format||s.formatter;return a&&i!=null?M.pipe(i,a):i}setFieldFormats(e){return Object.entries(e).forEach(([t,s])=>{this.setFieldFormat(t,s)}),this}getFieldFormats(){const e={};return this.fields.forEach(t=>{const s=t.format||t.formatter;s&&(e[t.name]=s)}),e}onInit(){super.onInit(),this.model&&typeof this.model.on=="function"&&this.model.on("change",()=>{this.render()})}static create(e={}){return new me(e)}}const Ze=Object.freeze(Object.defineProperty({__proto__:null,default:me},Symbol.toStringTag,{value:"Module"}));return F.Collection=W,F.DataFormatter=M,F.Dialog=k,F.FormBuilder=J,F.FormPage=Ue,F.FormView=H,F.ListView=he,F.ListViewItem=X,F.MOJOUtils=x,F.Model=Q,F.Page=Y,F.ProgressView=$e,F.Rest=z,F.Router=te,F.TableRow=ue,F.TableView=Fe,F.View=v,F.WebApp=G,F.default=fe,Object.defineProperties(F,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}}),F}({});
|
|
1765
|
+
//# sourceMappingURL=web-mojo.lite.iife.min.js.map
|