web-mojo 2.2.14 → 2.2.15
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 +22 -14
- 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 +1 -1
- package/dist/chunks/ChatView-BJK6SF8T.js +2 -0
- package/dist/chunks/ChatView-BJK6SF8T.js.map +1 -0
- package/dist/chunks/{ChatView-eFzjsHBL.js → ChatView-BPVE1u2i.js} +18 -4
- package/dist/chunks/ChatView-BPVE1u2i.js.map +1 -0
- package/dist/chunks/{Collection-BlP54kxB.js → Collection-BQWznmwY.js} +2 -2
- package/dist/chunks/{Collection-BlP54kxB.js.map → Collection-BQWznmwY.js.map} +1 -1
- package/dist/chunks/{Collection-CTkDG1NZ.js → Collection-zmb3xHhH.js} +27 -3
- package/dist/chunks/{Collection-CTkDG1NZ.js.map → Collection-zmb3xHhH.js.map} +1 -1
- package/dist/chunks/{ContextMenu-Capwv7d-.js → ContextMenu-DPjJuxpq.js} +2 -2
- package/dist/chunks/{ContextMenu-Capwv7d-.js.map → ContextMenu-DPjJuxpq.js.map} +1 -1
- package/dist/chunks/{ContextMenu-BH5SaDXX.js → ContextMenu-NNHmt1iq.js} +2 -2
- package/dist/chunks/{ContextMenu-BH5SaDXX.js.map → ContextMenu-NNHmt1iq.js.map} +1 -1
- package/dist/chunks/{ListView-CNkYumcc.js → ListView-BBZw7GUS.js} +2 -2
- package/dist/chunks/{ListView-CNkYumcc.js.map → ListView-BBZw7GUS.js.map} +1 -1
- package/dist/chunks/{ListView-O9AO02Rf.js → ListView-DHQyUB3V.js} +2 -2
- package/dist/chunks/{ListView-O9AO02Rf.js.map → ListView-DHQyUB3V.js.map} +1 -1
- package/dist/chunks/{TokenManager-CCfcK4aA.js → TokenManager-Bi5eDY8Y.js} +2 -2
- package/dist/chunks/{TokenManager-CCfcK4aA.js.map → TokenManager-Bi5eDY8Y.js.map} +1 -1
- package/dist/chunks/{TokenManager-CBXqj6Iw.js → TokenManager-DFWJ-cuN.js} +3 -3
- package/dist/chunks/{TokenManager-CBXqj6Iw.js.map → TokenManager-DFWJ-cuN.js.map} +1 -1
- package/dist/chunks/{version-DnlcM3tJ.js → version-BzRzH4mJ.js} +2 -2
- package/dist/chunks/{version-DnlcM3tJ.js.map → version-BzRzH4mJ.js.map} +1 -1
- package/dist/chunks/{version-DCTYSNWj.js → version-S1OYxk1c.js} +4 -4
- package/dist/chunks/{version-DCTYSNWj.js.map → version-S1OYxk1c.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +4 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +8 -8
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/dist/map.cjs.js +1 -1
- package/dist/map.es.js +1 -1
- package/dist/timeline.cjs.js +1 -1
- package/dist/timeline.es.js +2 -2
- package/package.json +1 -1
- package/dist/chunks/ChatView-DGulpthL.js +0 -2
- package/dist/chunks/ChatView-DGulpthL.js.map +0 -1
- package/dist/chunks/ChatView-eFzjsHBL.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const t=require("./Rest-P-KCJpjB.js");class Model{constructor(e={},s={}){this.endpoint=s.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=t.rest,this.options={idAttribute:"id",timestamps:!0,...s}}getContextValue(t){return this.get(t)}get(e){return e.includes(".")||e.includes("|")||void 0===this[e]?t.MOJOUtils.getContextData(this.attributes,e):"function"==typeof this[e]?this[e]():this[e]}set(t,e,s={}){const r=JSON.parse(JSON.stringify(this.attributes));let i=!1;if(null!=t){if("object"==typeof t){for(const[e,s]of Object.entries(t))i=this._setNestedAttribute(e,s)||i;void 0!==t.id&&(this.id=t.id)}else"id"===t?(this.id=e,i=!0):i=this._setNestedAttribute(t,e);if(i&&!s.silent)if(this.emit("change",this),"string"==typeof t)this.emit(`change:${t}`,e,this);else for(const[e,s]of Object.entries(t)){const t=this._getNestedValue(e);JSON.stringify(this._getNestedValue(e,r))!==JSON.stringify(t)&&this.emit(`change:${e}`,t,this)}}}_setNestedAttribute(t,e){if(!t.includes(".")){const s=this.attributes[t];return this.attributes[t]=e,this[t]=e,s!==e}const s=t.split("."),r=s[0];this.attributes[r]&&"object"==typeof this.attributes[r]||(this.attributes[r]={}),this[r]&&"object"==typeof this[r]||(this[r]={});const i=this._getNestedValue(t);let o=this.attributes[r],n=this[r];for(let h=1;h<s.length-1;h++){const t=s[h];o[t]&&"object"==typeof o[t]||(o[t]={}),n[t]&&"object"==typeof n[t]||(n[t]={}),o=o[t],n=n[t]}const a=s[s.length-1];return o[a]=e,n[a]=e,JSON.stringify(i)!==JSON.stringify(e)}_getNestedValue(t,e=this.attributes){if(!t.includes("."))return e[t];const s=t.split(".");let r=e;for(const i of s){if(null==r||"object"!=typeof r)return;r=r[i]}return r}getData(){return this.attributes}getId(){return this.id}async fetch(t={}){let e=t.url;if(!e){const s=t.id||this.getId();if(!s&&!1!==this.options.requiresId)throw new Error("Model: ID is required for fetching");e=this.buildUrl(s)}const s=JSON.stringify({url:e,params:t.params});if(t.debounceMs&&t.debounceMs>0)return this._debouncedFetch(s,t);if(this.currentRequest&&this.currentRequestKey!==s&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===s)return this.currentRequest;const r=Date.now();if(this.lastFetchTime&&r-this.lastFetchTime<100)return this;this.loading=!0,this.errors={},this.lastFetchTime=r,this.currentRequestKey=s,this.abortController=new AbortController,this.currentRequest=this._performFetch(e,t,this.abortController);try{return await this.currentRequest}catch(i){if("AbortError"===i.name)return this;throw i}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _debouncedFetch(t,e){return this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((t,s)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const s=await this.fetch({...e,debounceMs:0});t(s)}catch(r){s(r)}},e.debounceMs)})}async _performFetch(t,e,s){try{!e.graph||e.params&&e.params.graph||(e.params||(e.params={}),e.params.graph=e.graph);const r=await this.rest.GET(t,e.params,{signal:s.signal});return r.success?r.data.status?(this.originalAttributes={...this.attributes},r.data.data&&this.set(r.data.data),this.errors={}):this.errors=r.data:this.errors=r.errors||{},r}catch(r){if("AbortError"===r.name)throw r;return this.errors={fetch:r.message},{success:!1,error:r.message,status:r.status||500}}finally{this.loading=!1}}async save(t,e={}){const s=!this.id,r=s?"POST":"PUT",i=s?this.buildUrl():this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest[r](i,t,e.params);return s.success?s.data.status?(this.originalAttributes={...this.attributes},this.set(s.data.data),this.errors={}):this.errors=s.data:this.errors=s.errors||{},s}catch(o){return{success:!1,error:o.message,status:o.status||500}}finally{this.loading=!1}}async destroy(t={}){if(!this.id)return this.errors={destroy:"Cannot destroy model without ID"},{success:!1,error:"Cannot destroy model without ID",status:400};const e=this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest.DELETE(e,t.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 t={};for(const[e,s]of Object.entries(this.attributes))this.originalAttributes[e]!==s&&(t[e]=s);return t}reset(){this.attributes={...this.originalAttributes},this._=this.attributes,this.errors={}}buildUrl(t=null){let e=this.endpoint;return t&&(e=e.endsWith("/")?`${e}${t}`:`${e}/${t}`),e}toJSON(){return{id:this.id,...this.attributes}}validate(){if(this.errors={},this.constructor.validations)for(const[t,e]of Object.entries(this.constructor.validations))this.validateField(t,e);return 0===Object.keys(this.errors).length}validateField(t,e){const s=this.get(t),r=Array.isArray(e)?e:[e];for(const i of r)if("function"==typeof i){const e=i(s,this);if(!0!==e){this.errors[t]=e||`${t} is invalid`;break}}else if("object"==typeof i){if(i.required&&(null==s||""===s)){this.errors[t]=i.message||`${t} is required`;break}if(i.minLength&&s&&s.length<i.minLength){this.errors[t]=i.message||`${t} must be at least ${i.minLength} characters`;break}if(i.maxLength&&s&&s.length>i.maxLength){this.errors[t]=i.message||`${t} must be no more than ${i.maxLength} characters`;break}if(i.pattern&&s&&!i.pattern.test(s)){this.errors[t]=i.message||`${t} format is invalid`;break}}}static async find(t,e={}){const s=new this({},e);return await s.fetch({id:t,...e}),s}static create(t={},e={}){return new this(t,e)}cancel(){return this.currentRequest&&this.abortController?(this.abortController.abort(),!0):!!this.debouncedFetchTimeout&&(clearTimeout(this.debouncedFetchTimeout),this.debouncedFetchTimeout=null,!0)}isFetching(){return!!this.currentRequest}async showError(t){await Dialog.alert(t,"Error",{size:"md",class:"text-danger"})}}Object.assign(Model.prototype,t.EventEmitter);class Collection{constructor(e={},s=null){if(Array.isArray(e)?e=(s=e)||{}:s=s||e.data||[],this.ModelClass=e.ModelClass||Model,this.models=[],this.loading=!1,this.errors={},this.meta={},this.rest=t.rest,s&&this.add(s),this.params={start:0,size:e.size||10,...e.params},this.endpoint=e.endpoint||this.ModelClass.endpoint||"",!this.endpoint){let t=new this.ModelClass;this.endpoint=t.endpoint}this.restEnabled=!!this.endpoint,void 0!==e.restEnabled&&(this.restEnabled=e.restEnabled),this.options={parse:!0,reset:!0,preloaded:!1,...e}}getModelName(){return this.ModelClass.name}async fetch(t={}){const e=JSON.stringify({...this.params,...t});if(this.currentRequest&&this.currentRequestKey!==e&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===e)return this.currentRequest;const s=Date.now();if(this.options.rateLimiting&&this.lastFetchTime&&s-this.lastFetchTime<100)return{success:!0,message:"Rate limited, skipping fetch",data:{data:this.toJSON()}};if(!this.restEnabled)return{success:!0,message:"REST disabled, skipping fetch",data:{data:this.toJSON()}};if(this.options.preloaded&&this.models.length>0)return{success:!0,message:"Using preloaded data, skipping fetch",data:{data:this.toJSON()}};const r=this.buildUrl();this.loading=!0,this.errors={},this.lastFetchTime=s,this.currentRequestKey=e,this.abortController=new AbortController,this.currentRequest=this._performFetch(r,t,this.abortController);try{return await this.currentRequest}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:{success:!1,error:i.message,status:i.status||500}}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _performFetch(t,e,s){const r={...this.params,...e};try{this.emit("fetch:start");const i=await this.rest.GET(t,r,{signal:s.signal});if(i.success&&i.data.status){const t=this.options.parse?this.parse(i):i.data;(this.options.reset||!1!==e.reset)&&this.reset(),this.add(t,{silent:e.silent}),this.errors={},this.emit("fetch:success")}else i.data&&i.data.error?(this.errors=i.data,this.emit("fetch:error",{message:i.data.error,error:i.data})):(this.errors=i.errors||{},this.emit("fetch:error",{error:i.errors}));return i}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:(this.errors={fetch:i.message},this.emit("fetch:error",{message:i.message,error:i}),{success:!1,error:i.message,status:i.status||500})}finally{this.loading=!1,this.emit("fetch:end")}}async updateParams(t,e=!1,s=0){return await this.setParams({...this.params,...t},e,s)}async setParams(t,e=!1,s=0){return this.params=t,e&&this.restEnabled?s>0?(this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((t,e)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const e=await this.fetch();t(e)}catch(s){e(s)}},s)})):this.fetch():Promise.resolve(this)}async fetchOne(t,e={}){if(!t)return console.warn("Collection: fetchOne requires an ID"),null;if(!this.restEnabled)return null;try{const s=new this.ModelClass({id:t},{endpoint:this.endpoint,collection:this}),r=await s.fetch(e);if(r.success){if(!0===e.addToCollection){const t=this.get(s.id);t?!1!==e.merge&&t.set(s.attributes):this.add(s,{silent:e.silent})}return s}return console.warn("Collection: fetchOne failed -",r.error||"Unknown error"),null}catch(s){return console.error("Collection: fetchOne error -",s.message),null}}async download(t="json",e={}){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(),r={...this.params};delete r.start,delete r.size,r.download_format=t;const i=`export-${this.getModelName().toLowerCase()}.${t}`,o={json:"application/json",csv:"text/csv"}[t]||"*/*";return this.rest.download(s,r,{...e,filename:i,headers:{Accept:o}})}parse(t){return t.data&&Array.isArray(t.data.data)?(this.meta={size:t.data.size||10,start:t.data.start||0,count:t.data.count||0,status:t.data.status,graph:t.data.graph,...t.meta},t.data.data):Array.isArray(t.data)?t.data:Array.isArray(t)?t:[t]}add(t,e={}){const s=Array.isArray(t)?t:[t],r=[];for(const i of s){let t;t=i instanceof this.ModelClass?i:new this.ModelClass(i,{endpoint:this.endpoint,collection:this});const s=this.models.findIndex(e=>e.id===t.id);-1!==s?!1!==e.merge&&this.models[s].set(t.attributes):(this.models.push(t),r.push(t))}return!e.silent&&r.length>0&&(this.emit("add",{models:r,collection:this}),this.emit("update",{collection:this})),r}remove(t,e={}){const s=Array.isArray(t)?t:[t],r=[];for(const i of s){let t=-1;if(t="string"==typeof i||"number"==typeof i?this.models.findIndex(t=>t.id==i):this.models.indexOf(i),-1!==t){const e=this.models.splice(t,1)[0];r.push(e)}}return!e.silent&&r.length>0&&(this.emit("remove",{models:r,collection:this}),this.emit("update",{collection:this})),r}reset(t=null,e={}){const s=[...this.models];return this.models=[],t&&this.add(t,{silent:!0,...e}),e.silent||this.emit("reset",{collection:this,previousModels:s}),this}get(t){return this.models.find(e=>e.id==t)}at(t){return this.models[t]}length(){return this.models.length}isEmpty(){return 0===this.models.length}where(t){return"function"==typeof t?this.models.filter(t):"object"==typeof t?this.models.filter(e=>Object.entries(t).every(([t,s])=>e.get(t)===s)):[]}findWhere(t){const e=this.where(t);return e.length>0?e[0]:void 0}forEach(t,e){if("function"!=typeof t)throw new TypeError("Callback must be a function");return this.models.forEach((s,r)=>{t.call(e,s,r,this)}),this}sort(t,e={}){if("string"==typeof t){const e=t;t=(t,s)=>{const r=t.get(e),i=s.get(e);return r<i?-1:r>i?1:0}}return this.models.sort(t),e.silent||this.trigger("sort",{collection:this}),this}toJSON(){return this.models.map(t=>t.toJSON())}cancel(){return!(!this.currentRequest||!this.abortController||(this.abortController.abort(),0))}isFetching(){return!!this.currentRequest}buildUrl(){return this.endpoint}*[Symbol.iterator](){for(const t of this.models)yield t}static fromArray(t,e=[],s={}){const r=new this(t,s);return r.add(e,{silent:!0}),r}}Object.assign(Collection.prototype,t.EventEmitter),exports.Collection=Collection,exports.Model=Model;
|
|
2
|
-
//# sourceMappingURL=Collection-
|
|
1
|
+
"use strict";const t=require("./Rest-P-KCJpjB.js");class Model{constructor(e={},s={}){this.endpoint=s.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=t.rest,this.options={idAttribute:"id",timestamps:!0,...s}}getContextValue(t){return this.get(t)}get(e){return e.includes(".")||e.includes("|")||void 0===this[e]?t.MOJOUtils.getContextData(this.attributes,e):"function"==typeof this[e]?this[e]():this[e]}set(t,e,s={}){const r=JSON.parse(JSON.stringify(this.attributes));let i=!1;if(null!=t){if("object"==typeof t){for(const[e,s]of Object.entries(t))i=this._setNestedAttribute(e,s)||i;void 0!==t.id&&(this.id=t.id)}else"id"===t?(this.id=e,i=!0):i=this._setNestedAttribute(t,e);if(i&&!s.silent)if(this.emit("change",this),"string"==typeof t)this.emit(`change:${t}`,e,this);else for(const[e,s]of Object.entries(t)){const t=this._getNestedValue(e);JSON.stringify(this._getNestedValue(e,r))!==JSON.stringify(t)&&this.emit(`change:${e}`,t,this)}}}_setNestedAttribute(t,e){if(!t.includes(".")){const s=this.attributes[t];return this.attributes[t]=e,this[t]=e,s!==e}const s=t.split("."),r=s[0];this.attributes[r]&&"object"==typeof this.attributes[r]||(this.attributes[r]={}),this[r]&&"object"==typeof this[r]||(this[r]={});const i=this._getNestedValue(t);let n=this.attributes[r],o=this[r];for(let h=1;h<s.length-1;h++){const t=s[h];n[t]&&"object"==typeof n[t]||(n[t]={}),o[t]&&"object"==typeof o[t]||(o[t]={}),n=n[t],o=o[t]}const a=s[s.length-1];return n[a]=e,o[a]=e,JSON.stringify(i)!==JSON.stringify(e)}_getNestedValue(t,e=this.attributes){if(!t.includes("."))return e[t];const s=t.split(".");let r=e;for(const i of s){if(null==r||"object"!=typeof r)return;r=r[i]}return r}getData(){return this.attributes}getId(){return this.id}async fetch(t={}){let e=t.url;if(!e){const s=t.id||this.getId();if(!s&&!1!==this.options.requiresId)throw new Error("Model: ID is required for fetching");e=this.buildUrl(s)}const s=JSON.stringify({url:e,params:t.params});if(t.debounceMs&&t.debounceMs>0)return this._debouncedFetch(s,t);if(this.currentRequest&&this.currentRequestKey!==s&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===s)return this.currentRequest;const r=Date.now();if(this.lastFetchTime&&r-this.lastFetchTime<100)return this;this.loading=!0,this.errors={},this.lastFetchTime=r,this.currentRequestKey=s,this.abortController=new AbortController,this.currentRequest=this._performFetch(e,t,this.abortController);try{return await this.currentRequest}catch(i){if("AbortError"===i.name)return this;throw i}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _debouncedFetch(t,e){return this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((t,s)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const s=await this.fetch({...e,debounceMs:0});t(s)}catch(r){s(r)}},e.debounceMs)})}async _performFetch(t,e,s){try{!e.graph||e.params&&e.params.graph||(e.params||(e.params={}),e.params.graph=e.graph);const r=await this.rest.GET(t,e.params,{signal:s.signal});return r.success?r.data.status?(this.originalAttributes={...this.attributes},r.data.data&&this.set(r.data.data),this.errors={}):this.errors=r.data:this.errors=r.errors||{},r}catch(r){if("AbortError"===r.name)throw r;return this.errors={fetch:r.message},{success:!1,error:r.message,status:r.status||500}}finally{this.loading=!1}}async save(t,e={}){const s=!this.id,r=s?"POST":"PUT",i=s?this.buildUrl():this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest[r](i,t,e.params);return s.success?s.data.status?(this.originalAttributes={...this.attributes},this.set(s.data.data),this.errors={}):this.errors=s.data:this.errors=s.errors||{},s}catch(n){return{success:!1,error:n.message,status:n.status||500}}finally{this.loading=!1}}async destroy(t={}){if(!this.id)return this.errors={destroy:"Cannot destroy model without ID"},{success:!1,error:"Cannot destroy model without ID",status:400};const e=this.buildUrl(this.id);this.loading=!0,this.errors={};try{const s=await this.rest.DELETE(e,t.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 t={};for(const[e,s]of Object.entries(this.attributes))this.originalAttributes[e]!==s&&(t[e]=s);return t}reset(){this.attributes={...this.originalAttributes},this._=this.attributes,this.errors={}}buildUrl(t=null){let e=this.endpoint;return t&&(e=e.endsWith("/")?`${e}${t}`:`${e}/${t}`),e}toJSON(){return{id:this.id,...this.attributes}}validate(){if(this.errors={},this.constructor.validations)for(const[t,e]of Object.entries(this.constructor.validations))this.validateField(t,e);return 0===Object.keys(this.errors).length}validateField(t,e){const s=this.get(t),r=Array.isArray(e)?e:[e];for(const i of r)if("function"==typeof i){const e=i(s,this);if(!0!==e){this.errors[t]=e||`${t} is invalid`;break}}else if("object"==typeof i){if(i.required&&(null==s||""===s)){this.errors[t]=i.message||`${t} is required`;break}if(i.minLength&&s&&s.length<i.minLength){this.errors[t]=i.message||`${t} must be at least ${i.minLength} characters`;break}if(i.maxLength&&s&&s.length>i.maxLength){this.errors[t]=i.message||`${t} must be no more than ${i.maxLength} characters`;break}if(i.pattern&&s&&!i.pattern.test(s)){this.errors[t]=i.message||`${t} format is invalid`;break}}}static async find(t,e={}){const s=new this({},e);return await s.fetch({id:t,...e}),s}static create(t={},e={}){return new this(t,e)}cancel(){return this.currentRequest&&this.abortController?(this.abortController.abort(),!0):!!this.debouncedFetchTimeout&&(clearTimeout(this.debouncedFetchTimeout),this.debouncedFetchTimeout=null,!0)}isFetching(){return!!this.currentRequest}async showError(t){await Dialog.alert(t,"Error",{size:"md",class:"text-danger"})}}Object.assign(Model.prototype,t.EventEmitter);class Collection{constructor(e={},s=null){if(Array.isArray(e)?e=(s=e)||{}:s=s||e.data||[],this.ModelClass=e.ModelClass||Model,this.models=[],this.loading=!1,this.errors={},this.meta={},this.rest=t.rest,s&&this.add(s),this.params={start:0,size:e.size||10,...e.params},this.endpoint=e.endpoint||this.ModelClass.endpoint||"",!this.endpoint){let t=new this.ModelClass;this.endpoint=t.endpoint}this.restEnabled=!!this.endpoint,void 0!==e.restEnabled&&(this.restEnabled=e.restEnabled),this.options={parse:!0,reset:!0,preloaded:!1,...e}}getModelName(){return this.ModelClass.name}async fetch(t={}){const e=JSON.stringify({...this.params,...t});if(this.currentRequest&&this.currentRequestKey!==e&&(this.abortController?.abort(),this.currentRequest=null),this.currentRequest&&this.currentRequestKey===e)return this.currentRequest;const s=Date.now();if(this.options.rateLimiting&&this.lastFetchTime&&s-this.lastFetchTime<100)return{success:!0,message:"Rate limited, skipping fetch",data:{data:this.toJSON()}};if(!this.restEnabled)return{success:!0,message:"REST disabled, skipping fetch",data:{data:this.toJSON()}};if(this.options.preloaded&&this.models.length>0)return{success:!0,message:"Using preloaded data, skipping fetch",data:{data:this.toJSON()}};const r=this.buildUrl();this.loading=!0,this.errors={},this.lastFetchTime=s,this.currentRequestKey=e,this.abortController=new AbortController,this.currentRequest=this._performFetch(r,t,this.abortController);try{return await this.currentRequest}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:{success:!1,error:i.message,status:i.status||500}}finally{this.currentRequest=null,this.currentRequestKey=null,this.abortController=null}}async _performFetch(t,e,s){const r={...this.params,...e};try{this.emit("fetch:start");const i=await this.rest.GET(t,r,{signal:s.signal});if(i.success&&i.data.status){const t=this.options.parse?this.parse(i):i.data;(this.options.reset||!1!==e.reset)&&this.reset(),this.add(t,{silent:e.silent}),this.errors={},this.emit("fetch:success")}else i.data&&i.data.error?(this.errors=i.data,this.emit("fetch:error",{message:i.data.error,error:i.data})):(this.errors=i.errors||{},this.emit("fetch:error",{error:i.errors}));return i}catch(i){return"AbortError"===i.name?{success:!1,error:"Request cancelled",status:0}:(this.errors={fetch:i.message},this.emit("fetch:error",{message:i.message,error:i}),{success:!1,error:i.message,status:i.status||500})}finally{this.loading=!1,this.emit("fetch:end")}}async updateParams(t,e=!1,s=0){return await this.setParams({...this.params,...t},e,s)}async setParams(t,e=!1,s=0){return this.params=t,e&&this.restEnabled?s>0?(this.debouncedFetchTimeout&&clearTimeout(this.debouncedFetchTimeout),this.cancel(),new Promise((t,e)=>{this.debouncedFetchTimeout=setTimeout(async()=>{try{const e=await this.fetch();t(e)}catch(s){e(s)}},s)})):this.fetch():Promise.resolve(this)}async fetchOne(t,e={}){if(!t)return console.warn("Collection: fetchOne requires an ID"),null;if(!this.restEnabled)return null;try{const s=new this.ModelClass({id:t},{endpoint:this.endpoint,collection:this}),r=await s.fetch(e);if(r.success){if(!0===e.addToCollection){const t=this.get(s.id);t?!1!==e.merge&&t.set(s.attributes):this.add(s,{silent:e.silent})}return s}return console.warn("Collection: fetchOne failed -",r.error||"Unknown error"),null}catch(s){return console.error("Collection: fetchOne error -",s.message),null}}async download(t="json",e={}){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(),r={...this.params};delete r.start,delete r.size,r.download_format=t;const i=`export-${this.getModelName().toLowerCase()}${this._buildDateRangeSuffix(r)}.${t}`,n={json:"application/json",csv:"text/csv"}[t]||"*/*";return r.filename=i,this.rest.download(s,r,{...e,filename:i,headers:{Accept:n}})}_buildDateRangeSuffix(t={}){const e=t.dr_start,s=t.dr_end;if(!e&&!s)return"";const r=t=>t?String(t).replace(/[^\dA-Za-z_-]/g,"-"):"",i=[],n=t.dr_field||"daterange";return i.push(r(n)),e&&i.push(`from-${r(t.dr_start)}`),s&&i.push(`to-${r(t.dr_end)}`),`-${i.filter(Boolean).join("-")}`}parse(t){return t.data&&Array.isArray(t.data.data)?(this.meta={size:t.data.size||10,start:t.data.start||0,count:t.data.count||0,status:t.data.status,graph:t.data.graph,...t.meta},t.data.data):Array.isArray(t.data)?t.data:Array.isArray(t)?t:[t]}add(t,e={}){const s=Array.isArray(t)?t:[t],r=[];for(const i of s){let t;t=i instanceof this.ModelClass?i:new this.ModelClass(i,{endpoint:this.endpoint,collection:this});const s=this.models.findIndex(e=>e.id===t.id);-1!==s?!1!==e.merge&&this.models[s].set(t.attributes):(this.models.push(t),r.push(t))}return!e.silent&&r.length>0&&(this.emit("add",{models:r,collection:this}),this.emit("update",{collection:this})),r}remove(t,e={}){const s=Array.isArray(t)?t:[t],r=[];for(const i of s){let t=-1;if(t="string"==typeof i||"number"==typeof i?this.models.findIndex(t=>t.id==i):this.models.indexOf(i),-1!==t){const e=this.models.splice(t,1)[0];r.push(e)}}return!e.silent&&r.length>0&&(this.emit("remove",{models:r,collection:this}),this.emit("update",{collection:this})),r}reset(t=null,e={}){const s=[...this.models];return this.models=[],t&&this.add(t,{silent:!0,...e}),e.silent||this.emit("reset",{collection:this,previousModels:s}),this}get(t){return this.models.find(e=>e.id==t)}at(t){return this.models[t]}length(){return this.models.length}isEmpty(){return 0===this.models.length}where(t){return"function"==typeof t?this.models.filter(t):"object"==typeof t?this.models.filter(e=>Object.entries(t).every(([t,s])=>e.get(t)===s)):[]}findWhere(t){const e=this.where(t);return e.length>0?e[0]:void 0}forEach(t,e){if("function"!=typeof t)throw new TypeError("Callback must be a function");return this.models.forEach((s,r)=>{t.call(e,s,r,this)}),this}sort(t,e={}){if("string"==typeof t){const e=t;t=(t,s)=>{const r=t.get(e),i=s.get(e);return r<i?-1:r>i?1:0}}return this.models.sort(t),e.silent||this.trigger("sort",{collection:this}),this}toJSON(){return this.models.map(t=>t.toJSON())}cancel(){return!(!this.currentRequest||!this.abortController||(this.abortController.abort(),0))}isFetching(){return!!this.currentRequest}buildUrl(){return this.endpoint}*[Symbol.iterator](){for(const t of this.models)yield t}static fromArray(t,e=[],s={}){const r=new this(t,s);return r.add(e,{silent:!0}),r}}Object.assign(Collection.prototype,t.EventEmitter),exports.Collection=Collection,exports.Model=Model;
|
|
2
|
+
//# sourceMappingURL=Collection-BQWznmwY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collection-BlP54kxB.js","sources":["../../src/core/Model.js","../../src/core/Collection.js"],"sourcesContent":["/**\n * Model - Base class for models with REST API support\n * Provides CRUD operations for API resources with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits 'change' events when data is modified via set()\n * Emits 'change:attributeName' for specific attribute changes\n *\n * Standard Events:\n * - 'change' - Emitted when any model data changes\n * - 'change:fieldName' - Emitted when specific field changes\n *\n * @example\n * const user = new User({ name: 'John', email: 'john@example.com' });\n *\n * // Listen for any changes\n * user.on('change', (model) => {\n * console.log('User model changed');\n * view.render();\n * });\n *\n * // Listen for specific field changes\n * user.on('change:name', (newName, model) => {\n * console.log('Name changed to:', newName);\n * });\n *\n * // Trigger events by changing data\n * user.set('name', 'Jane'); // Emits 'change' and 'change:name'\n * user.set({ name: 'Bob', email: 'bob@example.com' }); // Emits 'change' and individual field events\n */\n\nimport MOJOUtils from '@core/utils/MOJOUtils.js';\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport rest from '@core/Rest.js';\n\nclass Model {\n constructor(data = {}, options = {}) {\n this.endpoint = options.endpoint || this.constructor.endpoint || '';\n this.id = data.id || null;\n this.attributes = { ...data };\n this._ = this.attributes;\n this.originalAttributes = { ...data };\n this.errors = {};\n this.loading = false;\n this.rest = rest;\n\n // Event system via EventEmitter mixin (applied to prototype)\n\n // Configuration options\n this.options = {\n idAttribute: 'id',\n timestamps: true,\n ...options\n };\n }\n\n getContextValue(key) {\n return this.get(key);\n }\n\n /**\n * Get attribute value with support for dot notation and pipe formatting\n * @param {string} key - Attribute key with optional pipes (e.g., \"name|uppercase\")\n * @returns {*} Attribute value, possibly formatted\n */\n get(key) {\n // Check if key exists as an instance field first (for 'id', 'endpoint', etc.)\n if (!key.includes('.') && !key.includes('|') && this[key] !== undefined) {\n // If it's a function, call it and return the result\n if (typeof this[key] === 'function') {\n return this[key]();\n }\n return this[key];\n }\n\n // Use MOJOUtils for all attribute access with pipes and dot notation\n return MOJOUtils.getContextData(this.attributes, key);\n }\n\n /**\n * Set attribute value(s)\n * @param {string|object} key - Attribute key or object of key-value pairs\n * @param {*} value - Attribute value (if key is string)\n * @param {object} options - Options (silent: true to not trigger change event)\n */\n set(key, value, options = {}) {\n const previousAttributes = JSON.parse(JSON.stringify(this.attributes)); // Deep copy\n let hasChanged = false;\n if (key === undefined || key === null) return;\n\n if (typeof key === 'object') {\n // Set multiple attributes\n for (const [attrKey, attrValue] of Object.entries(key)) {\n hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;\n }\n if (key.id !== undefined) {\n this.id = key.id;\n }\n } else {\n // Set single attribute\n if (key === 'id') {\n this.id = value;\n hasChanged = true;\n } else {\n hasChanged = this._setNestedAttribute(key, value);\n }\n }\n\n // Trigger change event if data changed and not silent\n if (hasChanged && !options.silent) {\n this.emit('change', this);\n\n // Trigger specific attribute change events\n if (typeof key === 'string') {\n this.emit(`change:${key}`, value, this);\n } else {\n for (const [attr, val] of Object.entries(key)) {\n // Get the final value that was actually set (after nested expansion)\n const finalValue = this._getNestedValue(attr);\n if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {\n this.emit(`change:${attr}`, finalValue, this);\n }\n }\n }\n }\n }\n\n /**\n * Set a nested attribute using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {*} value - Value to set\n * @returns {boolean} - Whether the value changed\n */\n _setNestedAttribute(key, value) {\n if (!key.includes('.')) {\n // Simple attribute\n const oldValue = this.attributes[key];\n this.attributes[key] = value;\n this[key] = value;\n return oldValue !== value;\n }\n\n // Nested attribute with dot notation\n const keys = key.split('.');\n const topLevelKey = keys[0];\n\n // Ensure the top-level object exists\n if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== 'object') {\n this.attributes[topLevelKey] = {};\n }\n if (!this[topLevelKey] || typeof this[topLevelKey] !== 'object') {\n this[topLevelKey] = {};\n }\n\n // Get the old value for comparison\n const oldValue = this._getNestedValue(key);\n\n // Navigate to the nested location and set the value\n let attrTarget = this.attributes[topLevelKey];\n let instanceTarget = this[topLevelKey];\n\n for (let i = 1; i < keys.length - 1; i++) {\n const currentKey = keys[i];\n\n if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== 'object') {\n attrTarget[currentKey] = {};\n }\n if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== 'object') {\n instanceTarget[currentKey] = {};\n }\n\n attrTarget = attrTarget[currentKey];\n instanceTarget = instanceTarget[currentKey];\n }\n\n // Set the final value\n const finalKey = keys[keys.length - 1];\n attrTarget[finalKey] = value;\n instanceTarget[finalKey] = value;\n\n return JSON.stringify(oldValue) !== JSON.stringify(value);\n }\n\n /**\n * Get a nested value using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {object} source - Source object (defaults to this.attributes)\n * @returns {*} - The nested value\n */\n _getNestedValue(key, source = this.attributes) {\n if (!key.includes('.')) {\n return source[key];\n }\n\n const keys = key.split('.');\n let current = source;\n\n for (const k of keys) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n current = current[k];\n }\n\n return current;\n }\n\n getData() {\n return this.attributes;\n }\n\n getId() {\n return this.id;\n }\n\n /**\n * Fetch model data from API with request deduplication and cancellation\n * @param {object} options - Request options\n * @param {number} options.debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(options = {}) {\n let url = options.url;\n if (!url) {\n const id = options.id || this.getId();\n if (!id && this.options.requiresId !== false) {\n throw new Error('Model: ID is required for fetching');\n }\n url = this.buildUrl(id);\n }\n const requestKey = JSON.stringify({url, params: options.params});\n\n // Handle debounced fetch\n if (options.debounceMs && options.debounceMs > 0) {\n return this._debouncedFetch(requestKey, options);\n }\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Model: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Model: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Model: Rate limited, skipping fetch');\n return this;\n }\n\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, options, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Model: Request was cancelled');\n return this;\n }\n throw error;\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Handle debounced fetch requests\n * @param {string} requestKey - Unique key for this request\n * @param {object} options - Fetch options\n * @returns {Promise} Promise that resolves with REST response\n */\n async _debouncedFetch(requestKey, options) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch({ ...options, debounceMs: 0 });\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, options.debounceMs);\n });\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} options - Request options\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, options, abortController) {\n try {\n if (options.graph && (!options.params || !options.params.graph)) {\n if (!options.params) options.params = {};\n options.params.graph = options.graph;\n }\n const response = await this.rest.GET(url, options.params, {\n signal: abortController.signal\n });\n\n if (response.success) {\n if (response.data.status) {\n this.originalAttributes = { ...this.attributes };\n if (response.data.data) this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Model: Fetch was cancelled');\n throw error;\n }\n\n this.errors = { fetch: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Save model to API (create or update)\n * @param {object} data - Data to save to the model\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async save(data, options = {}) {\n const isNew = !this.id;\n const method = isNew ? 'POST' : 'PUT';\n const url = isNew ? this.buildUrl() : this.buildUrl(this.id);\n\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest[method](url, data, options.params);\n\n if (response.success) {\n if (response.data.status) {\n // Update model on success\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response; // Always return the full response\n\n } catch (error) {\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n\n /**\n * Delete model from API\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async destroy(options = {}) {\n if (!this.id) {\n this.errors = { destroy: 'Cannot destroy model without ID' };\n return {\n success: false,\n error: 'Cannot destroy model without ID',\n status: 400\n };\n }\n\n const url = this.buildUrl(this.id);\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest.DELETE(url, options.params);\n\n if (response.success) {\n // Clear model data on success\n this.attributes = {};\n this.originalAttributes = {};\n this.id = null;\n this.errors = {};\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n\n } catch (error) {\n this.errors = { destroy: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Check if model has been modified\n * @returns {boolean} True if model has unsaved changes\n */\n isDirty() {\n return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);\n }\n\n /**\n * Get attributes that have changed since last save\n * @returns {object} Object containing only changed attributes\n */\n getChangedAttributes() {\n const changed = {};\n\n for (const [key, value] of Object.entries(this.attributes)) {\n if (this.originalAttributes[key] !== value) {\n changed[key] = value;\n }\n }\n\n return changed;\n }\n\n /**\n * Reset model to original state\n */\n reset() {\n this.attributes = { ...this.originalAttributes };\n this._ = this.attributes;\n this.errors = {};\n }\n\n /**\n * Build URL for API requests\n * @param {string|number} id - Optional ID to append to URL\n * @returns {string} Complete API URL\n */\n buildUrl(id = null) {\n let url = this.endpoint;\n if (id) {\n url = url.endsWith('/') ? `${url}${id}` : `${url}/${id}`;\n }\n return url;\n }\n\n /**\n * Convert model to JSON\n * @returns {object} Model attributes as plain object\n */\n toJSON() {\n return {\n id: this.id,\n ...this.attributes\n };\n }\n\n /**\n * Validate model attributes\n * @returns {boolean} True if valid, false if validation errors exist\n */\n validate() {\n this.errors = {};\n\n // Override in subclasses for custom validation\n if (this.constructor.validations) {\n for (const [field, rules] of Object.entries(this.constructor.validations)) {\n this.validateField(field, rules);\n }\n }\n\n return Object.keys(this.errors).length === 0;\n }\n\n /**\n * Validate a single field\n * @param {string} field - Field name\n * @param {object|array} rules - Validation rules\n */\n validateField(field, rules) {\n const value = this.get(field);\n const rulesArray = Array.isArray(rules) ? rules : [rules];\n\n for (const rule of rulesArray) {\n if (typeof rule === 'function') {\n const result = rule(value, this);\n if (result !== true) {\n this.errors[field] = result || `${field} is invalid`;\n break;\n }\n } else if (typeof rule === 'object') {\n if (rule.required && (value === undefined || value === null || value === '')) {\n this.errors[field] = rule.message || `${field} is required`;\n break;\n }\n if (rule.minLength && value && value.length < rule.minLength) {\n this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;\n break;\n }\n if (rule.maxLength && value && value.length > rule.maxLength) {\n this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;\n break;\n }\n if (rule.pattern && value && !rule.pattern.test(value)) {\n this.errors[field] = rule.message || `${field} format is invalid`;\n break;\n }\n }\n }\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Static method to create and fetch a model by ID\n * @param {string|number} id - Model ID\n * @param {object} options - Options\n * @returns {Promise<RestModel>} Promise that resolves with fetched model\n */\n static async find(id, options = {}) {\n const model = new this({}, options);\n await model.fetch({ id, ...options });\n return model;\n }\n\n /**\n * Static method to create a new model with data\n * @param {object} data - Model data\n * @param {object} options - Options\n * @returns {RestModel} New model instance\n */\n static create(data = {}, options = {}) {\n return new this(data, options);\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Model: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n\n // Cancel debounced fetch if exists\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n this.debouncedFetchTimeout = null;\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if model has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n async showError(message) {\n await Dialog.alert(message, 'Error', {\n size: 'md',\n class: 'text-danger'\n });\n }\n}\n\nObject.assign(Model.prototype, EventEmitter);\n\nexport default Model;\n","/**\n * Collection - Class for managing arrays of Model instances\n * Provides methods for fetching and managing collections of models with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits events when collection is modified\n *\n * Standard Events:\n * - 'add' - Emitted when models are added to the collection\n * - 'remove' - Emitted when models are removed from the collection\n * - 'update' - Emitted when collection is modified (after add/remove)\n * - 'reset' - Emitted when collection is reset (all models replaced)\n *\n * @example\n * const users = new UserCollection();\n *\n * // Listen for collection changes\n * users.on('add', ({ models, collection }) => {\n * console.log('Added', models.length, 'users');\n * updateUI();\n * });\n *\n * users.on('remove', ({ models, collection }) => {\n * console.log('Removed', models.length, 'users');\n * updateUI();\n * });\n *\n * // Add models - triggers 'add' and 'update' events\n * users.add([\n * new User({ name: 'John' }),\n * new User({ name: 'Jane' })\n * ]);\n *\n * Usage Examples:\n *\n * // Preloaded Data (no REST fetching)\n * const collection = new MyCollection({ preloaded: true });\n * collection.add(new MyModel({...}));\n * // collection.fetch() will be skipped if data already exists\n *\n * // REST Data (fetch from API)\n * const collection = new MyCollection({ preloaded: false }); // default\n * await collection.fetch(); // Will make API call\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Collection {\n constructor(options = {}, data = null) {\n // Handle case where first argument is data instead of ModelClass\n if (Array.isArray(options)) {\n // First argument is an array, treat it as data\n data = options;\n options = data || {};\n } else {\n data = data || options.data || [];\n }\n this.ModelClass = options.ModelClass || Model;\n this.models = [];\n this.loading = false;\n this.errors = {};\n this.meta = {};\n this.rest = rest;\n if (data) {\n this.add(data);\n }\n\n // Initialize params with defaults - single source of truth for query state\n this.params = {\n start: 0,\n size: options.size || 10,\n ...options.params\n };\n\n // Set up endpoint\n this.endpoint = options.endpoint || this.ModelClass.endpoint || '';\n if (!this.endpoint) {\n let tmp = new this.ModelClass();\n this.endpoint = tmp.endpoint;\n }\n\n // Automatic REST detection based on endpoint\n this.restEnabled = this.endpoint ? true : false;\n\n // Allow explicit override\n if (options.restEnabled !== undefined) {\n this.restEnabled = options.restEnabled;\n }\n\n // Configuration\n this.options = {\n parse: true,\n reset: true,\n preloaded: false,\n ...options\n };\n\n // Event system via EventEmitter mixin (applied to prototype)\n }\n\n getModelName() {\n return this.ModelClass.name;\n }\n\n /**\n * Fetch collection data from API\n * @param {object} additionalParams - Additional parameters to merge for this fetch only\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(additionalParams = {}) {\n const requestKey = JSON.stringify({ ...this.params, ...additionalParams });\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Collection: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Collection: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.options.rateLimiting && this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Collection: Rate limited, skipping fetch');\n return { success: true, message: 'Rate limited, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if not REST enabled\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, skipping fetch');\n return { success: true, message: 'REST disabled, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if preloaded is true and we already have data\n if (this.options.preloaded && this.models.length > 0) {\n console.info('Collection: Using preloaded data, skipping fetch');\n return { success: true, message: 'Using preloaded data, skipping fetch', data: { data: this.toJSON() } };\n }\n\n const url = this.buildUrl();\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, additionalParams, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Collection: Request was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} additionalParams - Additional parameters\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, additionalParams, abortController) {\n const fetchParams = { ...this.params, ...additionalParams };\n console.log('Fetching collection data from', url, fetchParams);\n try {\n this.emit(\"fetch:start\");\n const response = await this.rest.GET(url, fetchParams, {\n signal: abortController.signal\n });\n\n if (response.success && response.data.status) {\n const data = this.options.parse ? this.parse(response) : response.data;\n\n if (this.options.reset || additionalParams.reset !== false) {\n this.reset();\n }\n\n this.add(data, { silent: additionalParams.silent });\n this.errors = {};\n this.emit(\"fetch:success\");\n } else {\n if (response.data && response.data.error) {\n this.errors = response.data;\n this.emit(\"fetch:error\", { message: response.data.error, error: response.data });\n } else {\n this.errors = response.errors || {};\n this.emit(\"fetch:error\", { error: response.errors });\n }\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Collection: Fetch was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n\n this.errors = { fetch: error.message };\n this.emit(\"fetch:error\", { message: error.message, error: error });\n\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n this.emit(\"fetch:end\");\n }\n }\n\n /**\n * Update collection parameters and optionally fetch new data\n * @param {object} newParams - Parameters to update\n * @param {boolean} autoFetch - Whether to automatically fetch after updating params\n * @param {number} debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false\n */\n async updateParams(newParams, autoFetch = false, debounceMs = 0) {\n return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);\n }\n\n async setParams(newParams, autoFetch = false, debounceMs = 0) {\n this.params = newParams;\n if (autoFetch && this.restEnabled) {\n if (debounceMs > 0) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch();\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, debounceMs);\n });\n } else {\n // For immediate fetches, the fetch method will handle cancellation\n return this.fetch();\n }\n }\n\n return Promise.resolve(this);\n }\n\n /**\n * Fetch a single model by ID\n * @param {string|number} id - Model ID to fetch\n * @param {object} options - Additional fetch options\n * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found\n */\n async fetchOne(id, options = {}) {\n if (!id) {\n console.warn('Collection: fetchOne requires an ID');\n return null;\n }\n\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, cannot fetch single item');\n return null;\n }\n\n try {\n // Create model instance with the ID and use its fetch method\n const model = new this.ModelClass({ id }, {\n endpoint: this.endpoint,\n collection: this\n });\n\n const response = await model.fetch(options);\n\n if (response.success) {\n // Optionally add to collection if not already present\n if (options.addToCollection === true) {\n const existingModel = this.get(model.id);\n if (!existingModel) {\n this.add(model, { silent: options.silent });\n } else if (options.merge !== false) {\n existingModel.set(model.attributes);\n }\n }\n\n return model;\n } else {\n console.warn('Collection: fetchOne failed -', response.error || 'Unknown error');\n return null;\n }\n } catch (error) {\n console.error('Collection: fetchOne error -', error.message);\n return null;\n }\n }\n\n /**\n * Download collection data in a specified format\n * @param {string} format - The format for the download (e.g., 'csv', 'json')\n * @param {object} options - Download options\n * @returns {Promise} Promise that resolves with the download result\n */\n async download(format = 'json', options = {}) {\n if (!this.restEnabled) {\n console.warn('Collection: REST is not enabled, cannot download from remote.');\n // Here we could implement local data export in the future.\n return { success: false, message: 'Remote downloads are not enabled for this collection.' };\n }\n\n const url = this.buildUrl();\n const downloadParams = { ...this.params };\n\n // Remove pagination params for full export\n delete downloadParams.start;\n delete downloadParams.size;\n\n // Add format param\n downloadParams.download_format = format;\n\n // Provide a default filename and content type\n const filename = `export-${this.getModelName().toLowerCase()}.${format}`;\n const contentTypes = {\n json: 'application/json',\n csv: 'text/csv'\n };\n const acceptHeader = contentTypes[format] || '*/*';\n\n return this.rest.download(url, downloadParams, {\n ...options,\n filename,\n headers: { 'Accept': acceptHeader }\n });\n }\n\n /**\n * Parse response data - override in subclasses for custom parsing\n * @param {object} response - API response\n * @returns {array} Array of model data objects\n */\n parse(response) {\n // Handle standard paginated responses with size/start/count\n if (response.data && Array.isArray(response.data.data)) {\n this.meta = {\n size: response.data.size || 10,\n start: response.data.start || 0,\n count: response.data.count || 0,\n status: response.data.status,\n graph: response.data.graph,\n ...response.meta\n };\n return response.data.data;\n }\n\n // Handle direct array responses\n if (Array.isArray(response.data)) {\n return response.data;\n }\n\n // Fallback - assume response itself is the data array\n return Array.isArray(response) ? response : [response];\n }\n\n /**\n * Add model(s) to the collection\n * @param {object|array} data - Model data or array of model data\n * @param {object} options - Options for adding models\n */\n add(data, options = {}) {\n const modelsData = Array.isArray(data) ? data : [data];\n const addedModels = [];\n\n for (const modelData of modelsData) {\n let model;\n\n if (modelData instanceof this.ModelClass) {\n model = modelData;\n } else {\n model = new this.ModelClass(modelData, {\n endpoint: this.endpoint,\n collection: this\n });\n }\n\n // Check for duplicates\n const existingIndex = this.models.findIndex(m => m.id === model.id);\n if (existingIndex !== -1) {\n if (options.merge !== false) {\n // Update existing model\n this.models[existingIndex].set(model.attributes);\n }\n } else {\n // Add new model\n this.models.push(model);\n addedModels.push(model);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && addedModels.length > 0) {\n this.emit('add', { models: addedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return addedModels;\n }\n\n /**\n * Remove model(s) from the collection\n * @param {Model|array|string|number} models - Model(s) to remove or ID(s)\n * @param {object} options - Options\n */\n remove(models, options = {}) {\n const modelsToRemove = Array.isArray(models) ? models : [models];\n const removedModels = [];\n\n for (const model of modelsToRemove) {\n let index = -1;\n\n if (typeof model === 'string' || typeof model === 'number') {\n // Remove by ID\n index = this.models.findIndex(m => m.id == model);\n } else {\n // Remove by model instance\n index = this.models.indexOf(model);\n }\n\n if (index !== -1) {\n const removedModel = this.models.splice(index, 1)[0];\n removedModels.push(removedModel);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && removedModels.length > 0) {\n this.emit('remove', { models: removedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return removedModels;\n }\n\n /**\n * Reset the collection (remove all models)\n * @param {array} models - Optional new models to set\n * @param {object} options - Options\n */\n reset(models = null, options = {}) {\n const previousModels = [...this.models];\n this.models = [];\n\n if (models) {\n this.add(models, { silent: true, ...options });\n }\n\n if (!options.silent) {\n this.emit('reset', {\n collection: this,\n previousModels\n });\n }\n\n return this;\n }\n\n /**\n * Get model by ID\n * @param {string|number} id - Model ID\n * @returns {Model|undefined} Model instance or undefined\n */\n get(id) {\n return this.models.find(model => model.id == id);\n }\n\n /**\n * Get model by index\n * @param {number} index - Model index\n * @returns {Model|undefined} Model instance or undefined\n */\n at(index) {\n return this.models[index];\n }\n\n /**\n * Get collection length\n * @returns {number} Number of models in collection\n */\n length() {\n return this.models.length;\n }\n\n /**\n * Check if collection is empty\n * @returns {boolean} True if collection has no models\n */\n isEmpty() {\n return this.models.length === 0;\n }\n\n /**\n * Find models matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {array} Array of matching models\n */\n where(criteria) {\n if (typeof criteria === 'function') {\n return this.models.filter(criteria);\n }\n\n if (typeof criteria === 'object') {\n return this.models.filter(model => {\n return Object.entries(criteria).every(([key, value]) => {\n return model.get(key) === value;\n });\n });\n }\n\n return [];\n }\n\n /**\n * Find first model matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {Model|undefined} First matching model or undefined\n */\n findWhere(criteria) {\n const results = this.where(criteria);\n return results.length > 0 ? results[0] : undefined;\n }\n\n /**\n * Iterate over each model in the collection\n * @param {function} callback - Function to execute for each model (model, index, collection)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {Collection} Returns the collection for chaining\n */\n forEach(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n this.models.forEach((model, index) => {\n callback.call(thisArg, model, index, this);\n });\n\n return this;\n }\n\n /**\n * Sort collection by comparator function\n * @param {function|string} comparator - Comparison function or attribute name\n * @param {object} options - Sort options\n */\n sort(comparator, options = {}) {\n if (typeof comparator === 'string') {\n const attr = comparator;\n comparator = (a, b) => {\n const aVal = a.get(attr);\n const bVal = b.get(attr);\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n };\n }\n\n this.models.sort(comparator);\n\n if (!options.silent) {\n this.trigger('sort', { collection: this });\n }\n\n return this;\n }\n\n /**\n * Convert collection to JSON array\n * @returns {array} Array of model JSON representations\n */\n toJSON() {\n return this.models.map(model => model.toJSON());\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Collection: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Check if collection has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n /**\n * Build URL for collection endpoint\n * @returns {string} Collection API URL\n */\n buildUrl() {\n return this.endpoint;\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Iterator support for for...of loops\n */\n *[Symbol.iterator]() {\n for (const model of this.models) {\n yield model;\n }\n }\n\n /**\n * Static method to create collection from array data\n * @param {function} ModelClass - Model class constructor\n * @param {array} data - Array of model data\n * @param {object} options - Collection options\n * @returns {Collection} New collection instance\n */\n static fromArray(ModelClass, data = [], options = {}) {\n const collection = new this(ModelClass, options);\n collection.add(data, { silent: true });\n return collection;\n }\n}\n\nObject.assign(Collection.prototype, EventEmitter);\n\nexport default Collection;\n"],"names":["Model","constructor","data","options","this","endpoint","id","attributes","_","originalAttributes","errors","loading","rest","idAttribute","timestamps","getContextValue","key","get","includes","MOJOUtils","getContextData","set","value","previousAttributes","JSON","parse","stringify","hasChanged","attrKey","attrValue","Object","entries","_setNestedAttribute","silent","emit","attr","val","finalValue","_getNestedValue","oldValue","keys","split","topLevelKey","attrTarget","instanceTarget","i","length","currentKey","finalKey","source","current","k","getData","getId","fetch","url","requiresId","Error","buildUrl","requestKey","params","debounceMs","_debouncedFetch","currentRequest","currentRequestKey","abortController","abort","now","Date","lastFetchTime","AbortController","_performFetch","error","name","debouncedFetchTimeout","clearTimeout","cancel","Promise","resolve","reject","setTimeout","async","result","graph","response","GET","signal","success","status","message","save","isNew","method","destroy","DELETE","isDirty","getChangedAttributes","changed","reset","endsWith","toJSON","validate","validations","field","rules","validateField","rulesArray","Array","isArray","rule","required","minLength","maxLength","pattern","test","find","model","create","isFetching","showError","Dialog","alert","size","class","assign","prototype","EventEmitter","Collection","ModelClass","models","meta","add","start","tmp","restEnabled","preloaded","getModelName","additionalParams","rateLimiting","fetchParams","updateParams","newParams","autoFetch","setParams","fetchOne","console","warn","collection","addToCollection","existingModel","merge","download","format","downloadParams","download_format","filename","toLowerCase","acceptHeader","json","csv","headers","Accept","count","modelsData","addedModels","modelData","existingIndex","findIndex","m","push","remove","modelsToRemove","removedModels","index","indexOf","removedModel","splice","previousModels","at","isEmpty","where","criteria","filter","every","findWhere","results","forEach","callback","thisArg","TypeError","call","sort","comparator","a","b","aVal","bVal","trigger","map","Symbol","iterator","fromArray"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Collection-BQWznmwY.js","sources":["../../src/core/Model.js","../../src/core/Collection.js"],"sourcesContent":["/**\n * Model - Base class for models with REST API support\n * Provides CRUD operations for API resources with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits 'change' events when data is modified via set()\n * Emits 'change:attributeName' for specific attribute changes\n *\n * Standard Events:\n * - 'change' - Emitted when any model data changes\n * - 'change:fieldName' - Emitted when specific field changes\n *\n * @example\n * const user = new User({ name: 'John', email: 'john@example.com' });\n *\n * // Listen for any changes\n * user.on('change', (model) => {\n * console.log('User model changed');\n * view.render();\n * });\n *\n * // Listen for specific field changes\n * user.on('change:name', (newName, model) => {\n * console.log('Name changed to:', newName);\n * });\n *\n * // Trigger events by changing data\n * user.set('name', 'Jane'); // Emits 'change' and 'change:name'\n * user.set({ name: 'Bob', email: 'bob@example.com' }); // Emits 'change' and individual field events\n */\n\nimport MOJOUtils from '@core/utils/MOJOUtils.js';\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport rest from '@core/Rest.js';\n\nclass Model {\n constructor(data = {}, options = {}) {\n this.endpoint = options.endpoint || this.constructor.endpoint || '';\n this.id = data.id || null;\n this.attributes = { ...data };\n this._ = this.attributes;\n this.originalAttributes = { ...data };\n this.errors = {};\n this.loading = false;\n this.rest = rest;\n\n // Event system via EventEmitter mixin (applied to prototype)\n\n // Configuration options\n this.options = {\n idAttribute: 'id',\n timestamps: true,\n ...options\n };\n }\n\n getContextValue(key) {\n return this.get(key);\n }\n\n /**\n * Get attribute value with support for dot notation and pipe formatting\n * @param {string} key - Attribute key with optional pipes (e.g., \"name|uppercase\")\n * @returns {*} Attribute value, possibly formatted\n */\n get(key) {\n // Check if key exists as an instance field first (for 'id', 'endpoint', etc.)\n if (!key.includes('.') && !key.includes('|') && this[key] !== undefined) {\n // If it's a function, call it and return the result\n if (typeof this[key] === 'function') {\n return this[key]();\n }\n return this[key];\n }\n\n // Use MOJOUtils for all attribute access with pipes and dot notation\n return MOJOUtils.getContextData(this.attributes, key);\n }\n\n /**\n * Set attribute value(s)\n * @param {string|object} key - Attribute key or object of key-value pairs\n * @param {*} value - Attribute value (if key is string)\n * @param {object} options - Options (silent: true to not trigger change event)\n */\n set(key, value, options = {}) {\n const previousAttributes = JSON.parse(JSON.stringify(this.attributes)); // Deep copy\n let hasChanged = false;\n if (key === undefined || key === null) return;\n\n if (typeof key === 'object') {\n // Set multiple attributes\n for (const [attrKey, attrValue] of Object.entries(key)) {\n hasChanged = this._setNestedAttribute(attrKey, attrValue) || hasChanged;\n }\n if (key.id !== undefined) {\n this.id = key.id;\n }\n } else {\n // Set single attribute\n if (key === 'id') {\n this.id = value;\n hasChanged = true;\n } else {\n hasChanged = this._setNestedAttribute(key, value);\n }\n }\n\n // Trigger change event if data changed and not silent\n if (hasChanged && !options.silent) {\n this.emit('change', this);\n\n // Trigger specific attribute change events\n if (typeof key === 'string') {\n this.emit(`change:${key}`, value, this);\n } else {\n for (const [attr, val] of Object.entries(key)) {\n // Get the final value that was actually set (after nested expansion)\n const finalValue = this._getNestedValue(attr);\n if (JSON.stringify(this._getNestedValue(attr, previousAttributes)) !== JSON.stringify(finalValue)) {\n this.emit(`change:${attr}`, finalValue, this);\n }\n }\n }\n }\n }\n\n /**\n * Set a nested attribute using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {*} value - Value to set\n * @returns {boolean} - Whether the value changed\n */\n _setNestedAttribute(key, value) {\n if (!key.includes('.')) {\n // Simple attribute\n const oldValue = this.attributes[key];\n this.attributes[key] = value;\n this[key] = value;\n return oldValue !== value;\n }\n\n // Nested attribute with dot notation\n const keys = key.split('.');\n const topLevelKey = keys[0];\n\n // Ensure the top-level object exists\n if (!this.attributes[topLevelKey] || typeof this.attributes[topLevelKey] !== 'object') {\n this.attributes[topLevelKey] = {};\n }\n if (!this[topLevelKey] || typeof this[topLevelKey] !== 'object') {\n this[topLevelKey] = {};\n }\n\n // Get the old value for comparison\n const oldValue = this._getNestedValue(key);\n\n // Navigate to the nested location and set the value\n let attrTarget = this.attributes[topLevelKey];\n let instanceTarget = this[topLevelKey];\n\n for (let i = 1; i < keys.length - 1; i++) {\n const currentKey = keys[i];\n\n if (!attrTarget[currentKey] || typeof attrTarget[currentKey] !== 'object') {\n attrTarget[currentKey] = {};\n }\n if (!instanceTarget[currentKey] || typeof instanceTarget[currentKey] !== 'object') {\n instanceTarget[currentKey] = {};\n }\n\n attrTarget = attrTarget[currentKey];\n instanceTarget = instanceTarget[currentKey];\n }\n\n // Set the final value\n const finalKey = keys[keys.length - 1];\n attrTarget[finalKey] = value;\n instanceTarget[finalKey] = value;\n\n return JSON.stringify(oldValue) !== JSON.stringify(value);\n }\n\n /**\n * Get a nested value using dot notation\n * @param {string} key - Attribute key (may contain dots)\n * @param {object} source - Source object (defaults to this.attributes)\n * @returns {*} - The nested value\n */\n _getNestedValue(key, source = this.attributes) {\n if (!key.includes('.')) {\n return source[key];\n }\n\n const keys = key.split('.');\n let current = source;\n\n for (const k of keys) {\n if (current == null || typeof current !== 'object') {\n return undefined;\n }\n current = current[k];\n }\n\n return current;\n }\n\n getData() {\n return this.attributes;\n }\n\n getId() {\n return this.id;\n }\n\n /**\n * Fetch model data from API with request deduplication and cancellation\n * @param {object} options - Request options\n * @param {number} options.debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(options = {}) {\n let url = options.url;\n if (!url) {\n const id = options.id || this.getId();\n if (!id && this.options.requiresId !== false) {\n throw new Error('Model: ID is required for fetching');\n }\n url = this.buildUrl(id);\n }\n const requestKey = JSON.stringify({url, params: options.params});\n\n // Handle debounced fetch\n if (options.debounceMs && options.debounceMs > 0) {\n return this._debouncedFetch(requestKey, options);\n }\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Model: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Model: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Model: Rate limited, skipping fetch');\n return this;\n }\n\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, options, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Model: Request was cancelled');\n return this;\n }\n throw error;\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Handle debounced fetch requests\n * @param {string} requestKey - Unique key for this request\n * @param {object} options - Fetch options\n * @returns {Promise} Promise that resolves with REST response\n */\n async _debouncedFetch(requestKey, options) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch({ ...options, debounceMs: 0 });\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, options.debounceMs);\n });\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} options - Request options\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, options, abortController) {\n try {\n if (options.graph && (!options.params || !options.params.graph)) {\n if (!options.params) options.params = {};\n options.params.graph = options.graph;\n }\n const response = await this.rest.GET(url, options.params, {\n signal: abortController.signal\n });\n\n if (response.success) {\n if (response.data.status) {\n this.originalAttributes = { ...this.attributes };\n if (response.data.data) this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Model: Fetch was cancelled');\n throw error;\n }\n\n this.errors = { fetch: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Save model to API (create or update)\n * @param {object} data - Data to save to the model\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async save(data, options = {}) {\n const isNew = !this.id;\n const method = isNew ? 'POST' : 'PUT';\n const url = isNew ? this.buildUrl() : this.buildUrl(this.id);\n\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest[method](url, data, options.params);\n\n if (response.success) {\n if (response.data.status) {\n // Update model on success\n this.originalAttributes = { ...this.attributes };\n this.set(response.data.data);\n this.errors = {};\n } else {\n this.errors = response.data;\n }\n } else {\n this.errors = response.errors || {};\n }\n\n return response; // Always return the full response\n\n } catch (error) {\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n\n /**\n * Delete model from API\n * @param {object} options - Request options\n * @returns {Promise} Promise that resolves with REST response\n */\n async destroy(options = {}) {\n if (!this.id) {\n this.errors = { destroy: 'Cannot destroy model without ID' };\n return {\n success: false,\n error: 'Cannot destroy model without ID',\n status: 400\n };\n }\n\n const url = this.buildUrl(this.id);\n this.loading = true;\n this.errors = {};\n\n try {\n const response = await this.rest.DELETE(url, options.params);\n\n if (response.success) {\n // Clear model data on success\n this.attributes = {};\n this.originalAttributes = {};\n this.id = null;\n this.errors = {};\n } else {\n this.errors = response.errors || {};\n }\n\n return response;\n\n } catch (error) {\n this.errors = { destroy: error.message };\n\n // Return error response for network/other errors\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n }\n }\n\n /**\n * Check if model has been modified\n * @returns {boolean} True if model has unsaved changes\n */\n isDirty() {\n return JSON.stringify(this.attributes) !== JSON.stringify(this.originalAttributes);\n }\n\n /**\n * Get attributes that have changed since last save\n * @returns {object} Object containing only changed attributes\n */\n getChangedAttributes() {\n const changed = {};\n\n for (const [key, value] of Object.entries(this.attributes)) {\n if (this.originalAttributes[key] !== value) {\n changed[key] = value;\n }\n }\n\n return changed;\n }\n\n /**\n * Reset model to original state\n */\n reset() {\n this.attributes = { ...this.originalAttributes };\n this._ = this.attributes;\n this.errors = {};\n }\n\n /**\n * Build URL for API requests\n * @param {string|number} id - Optional ID to append to URL\n * @returns {string} Complete API URL\n */\n buildUrl(id = null) {\n let url = this.endpoint;\n if (id) {\n url = url.endsWith('/') ? `${url}${id}` : `${url}/${id}`;\n }\n return url;\n }\n\n /**\n * Convert model to JSON\n * @returns {object} Model attributes as plain object\n */\n toJSON() {\n return {\n id: this.id,\n ...this.attributes\n };\n }\n\n /**\n * Validate model attributes\n * @returns {boolean} True if valid, false if validation errors exist\n */\n validate() {\n this.errors = {};\n\n // Override in subclasses for custom validation\n if (this.constructor.validations) {\n for (const [field, rules] of Object.entries(this.constructor.validations)) {\n this.validateField(field, rules);\n }\n }\n\n return Object.keys(this.errors).length === 0;\n }\n\n /**\n * Validate a single field\n * @param {string} field - Field name\n * @param {object|array} rules - Validation rules\n */\n validateField(field, rules) {\n const value = this.get(field);\n const rulesArray = Array.isArray(rules) ? rules : [rules];\n\n for (const rule of rulesArray) {\n if (typeof rule === 'function') {\n const result = rule(value, this);\n if (result !== true) {\n this.errors[field] = result || `${field} is invalid`;\n break;\n }\n } else if (typeof rule === 'object') {\n if (rule.required && (value === undefined || value === null || value === '')) {\n this.errors[field] = rule.message || `${field} is required`;\n break;\n }\n if (rule.minLength && value && value.length < rule.minLength) {\n this.errors[field] = rule.message || `${field} must be at least ${rule.minLength} characters`;\n break;\n }\n if (rule.maxLength && value && value.length > rule.maxLength) {\n this.errors[field] = rule.message || `${field} must be no more than ${rule.maxLength} characters`;\n break;\n }\n if (rule.pattern && value && !rule.pattern.test(value)) {\n this.errors[field] = rule.message || `${field} format is invalid`;\n break;\n }\n }\n }\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Static method to create and fetch a model by ID\n * @param {string|number} id - Model ID\n * @param {object} options - Options\n * @returns {Promise<RestModel>} Promise that resolves with fetched model\n */\n static async find(id, options = {}) {\n const model = new this({}, options);\n await model.fetch({ id, ...options });\n return model;\n }\n\n /**\n * Static method to create a new model with data\n * @param {object} data - Model data\n * @param {object} options - Options\n * @returns {RestModel} New model instance\n */\n static create(data = {}, options = {}) {\n return new this(data, options);\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Model: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n\n // Cancel debounced fetch if exists\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n this.debouncedFetchTimeout = null;\n return true;\n }\n\n return false;\n }\n\n /**\n * Check if model has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n async showError(message) {\n await Dialog.alert(message, 'Error', {\n size: 'md',\n class: 'text-danger'\n });\n }\n}\n\nObject.assign(Model.prototype, EventEmitter);\n\nexport default Model;\n","/**\n * Collection - Class for managing arrays of Model instances\n * Provides methods for fetching and managing collections of models with built-in event system\n *\n * Event System:\n * Uses EventEmitter mixin for instance-level events (emit, on, off, once)\n * Automatically emits events when collection is modified\n *\n * Standard Events:\n * - 'add' - Emitted when models are added to the collection\n * - 'remove' - Emitted when models are removed from the collection\n * - 'update' - Emitted when collection is modified (after add/remove)\n * - 'reset' - Emitted when collection is reset (all models replaced)\n *\n * @example\n * const users = new UserCollection();\n *\n * // Listen for collection changes\n * users.on('add', ({ models, collection }) => {\n * console.log('Added', models.length, 'users');\n * updateUI();\n * });\n *\n * users.on('remove', ({ models, collection }) => {\n * console.log('Removed', models.length, 'users');\n * updateUI();\n * });\n *\n * // Add models - triggers 'add' and 'update' events\n * users.add([\n * new User({ name: 'John' }),\n * new User({ name: 'Jane' })\n * ]);\n *\n * Usage Examples:\n *\n * // Preloaded Data (no REST fetching)\n * const collection = new MyCollection({ preloaded: true });\n * collection.add(new MyModel({...}));\n * // collection.fetch() will be skipped if data already exists\n *\n * // REST Data (fetch from API)\n * const collection = new MyCollection({ preloaded: false }); // default\n * await collection.fetch(); // Will make API call\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\nimport Model from '@core/Model.js';\nimport rest from '@core/Rest.js';\n\nclass Collection {\n constructor(options = {}, data = null) {\n // Handle case where first argument is data instead of ModelClass\n if (Array.isArray(options)) {\n // First argument is an array, treat it as data\n data = options;\n options = data || {};\n } else {\n data = data || options.data || [];\n }\n this.ModelClass = options.ModelClass || Model;\n this.models = [];\n this.loading = false;\n this.errors = {};\n this.meta = {};\n this.rest = rest;\n if (data) {\n this.add(data);\n }\n\n // Initialize params with defaults - single source of truth for query state\n this.params = {\n start: 0,\n size: options.size || 10,\n ...options.params\n };\n\n // Set up endpoint\n this.endpoint = options.endpoint || this.ModelClass.endpoint || '';\n if (!this.endpoint) {\n let tmp = new this.ModelClass();\n this.endpoint = tmp.endpoint;\n }\n\n // Automatic REST detection based on endpoint\n this.restEnabled = this.endpoint ? true : false;\n\n // Allow explicit override\n if (options.restEnabled !== undefined) {\n this.restEnabled = options.restEnabled;\n }\n\n // Configuration\n this.options = {\n parse: true,\n reset: true,\n preloaded: false,\n ...options\n };\n\n // Event system via EventEmitter mixin (applied to prototype)\n }\n\n getModelName() {\n return this.ModelClass.name;\n }\n\n /**\n * Fetch collection data from API\n * @param {object} additionalParams - Additional parameters to merge for this fetch only\n * @returns {Promise} Promise that resolves with REST response\n */\n async fetch(additionalParams = {}) {\n const requestKey = JSON.stringify({ ...this.params, ...additionalParams });\n\n // CANCEL PREVIOUS REQUEST if it's different from current request\n if (this.currentRequest && this.currentRequestKey !== requestKey) {\n console.info('Collection: Cancelling previous request for new parameters');\n this.abortController?.abort();\n this.currentRequest = null;\n }\n\n // REQUEST DEDUPLICATION - Return existing promise if identical request\n if (this.currentRequest && this.currentRequestKey === requestKey) {\n console.info('Collection: Duplicate request in progress, returning existing promise');\n return this.currentRequest;\n }\n\n // RATE LIMITING - Prevent requests within 100ms of last request\n const now = Date.now();\n const minInterval = 100; // ms\n\n if (this.options.rateLimiting && this.lastFetchTime && (now - this.lastFetchTime) < minInterval) {\n console.info('Collection: Rate limited, skipping fetch');\n return { success: true, message: 'Rate limited, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if not REST enabled\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, skipping fetch');\n return { success: true, message: 'REST disabled, skipping fetch', data: { data: this.toJSON() } };\n }\n\n // Skip fetching if preloaded is true and we already have data\n if (this.options.preloaded && this.models.length > 0) {\n console.info('Collection: Using preloaded data, skipping fetch');\n return { success: true, message: 'Using preloaded data, skipping fetch', data: { data: this.toJSON() } };\n }\n\n const url = this.buildUrl();\n this.loading = true;\n this.errors = {};\n this.lastFetchTime = now;\n this.currentRequestKey = requestKey;\n\n // Create new AbortController for this request\n this.abortController = new AbortController();\n\n // Store the promise for deduplication\n this.currentRequest = this._performFetch(url, additionalParams, this.abortController);\n\n try {\n const result = await this.currentRequest;\n return result;\n } catch (error) {\n // Don't throw if request was cancelled\n if (error.name === 'AbortError') {\n console.info('Collection: Request was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.currentRequest = null;\n this.currentRequestKey = null;\n this.abortController = null;\n }\n }\n\n /**\n * Internal method to perform the actual fetch\n * @param {string} url - API endpoint URL\n * @param {object} additionalParams - Additional parameters\n * @param {AbortController} abortController - Controller for request cancellation\n * @returns {Promise} Promise that resolves with REST response\n */\n async _performFetch(url, additionalParams, abortController) {\n const fetchParams = { ...this.params, ...additionalParams };\n console.log('Fetching collection data from', url, fetchParams);\n try {\n this.emit(\"fetch:start\");\n const response = await this.rest.GET(url, fetchParams, {\n signal: abortController.signal\n });\n\n if (response.success && response.data.status) {\n const data = this.options.parse ? this.parse(response) : response.data;\n\n if (this.options.reset || additionalParams.reset !== false) {\n this.reset();\n }\n\n this.add(data, { silent: additionalParams.silent });\n this.errors = {};\n this.emit(\"fetch:success\");\n } else {\n if (response.data && response.data.error) {\n this.errors = response.data;\n this.emit(\"fetch:error\", { message: response.data.error, error: response.data });\n } else {\n this.errors = response.errors || {};\n this.emit(\"fetch:error\", { error: response.errors });\n }\n }\n\n return response;\n } catch (error) {\n // Handle cancellation gracefully\n if (error.name === 'AbortError') {\n console.info('Collection: Fetch was cancelled');\n return { success: false, error: 'Request cancelled', status: 0 };\n }\n\n this.errors = { fetch: error.message };\n this.emit(\"fetch:error\", { message: error.message, error: error });\n\n return {\n success: false,\n error: error.message,\n status: error.status || 500\n };\n } finally {\n this.loading = false;\n this.emit(\"fetch:end\");\n }\n }\n\n /**\n * Update collection parameters and optionally fetch new data\n * @param {object} newParams - Parameters to update\n * @param {boolean} autoFetch - Whether to automatically fetch after updating params\n * @param {number} debounceMs - Optional debounce delay in milliseconds\n * @returns {Promise} Promise that resolves with REST response if autoFetch=true, or collection if autoFetch=false\n */\n async updateParams(newParams, autoFetch = false, debounceMs = 0) {\n return await this.setParams({ ...this.params, ...newParams }, autoFetch, debounceMs);\n }\n\n async setParams(newParams, autoFetch = false, debounceMs = 0) {\n this.params = newParams;\n if (autoFetch && this.restEnabled) {\n if (debounceMs > 0) {\n // Clear existing debounced fetch\n if (this.debouncedFetchTimeout) {\n clearTimeout(this.debouncedFetchTimeout);\n }\n\n // Cancel any active request since we're about to start a new one\n this.cancel();\n\n return new Promise((resolve, reject) => {\n this.debouncedFetchTimeout = setTimeout(async () => {\n try {\n const result = await this.fetch();\n resolve(result);\n } catch (error) {\n reject(error);\n }\n }, debounceMs);\n });\n } else {\n // For immediate fetches, the fetch method will handle cancellation\n return this.fetch();\n }\n }\n\n return Promise.resolve(this);\n }\n\n /**\n * Fetch a single model by ID\n * @param {string|number} id - Model ID to fetch\n * @param {object} options - Additional fetch options\n * @returns {Promise<Model|null>} Promise that resolves with model instance or null if not found\n */\n async fetchOne(id, options = {}) {\n if (!id) {\n console.warn('Collection: fetchOne requires an ID');\n return null;\n }\n\n if (!this.restEnabled) {\n console.info('Collection: REST disabled, cannot fetch single item');\n return null;\n }\n\n try {\n // Create model instance with the ID and use its fetch method\n const model = new this.ModelClass({ id }, {\n endpoint: this.endpoint,\n collection: this\n });\n\n const response = await model.fetch(options);\n\n if (response.success) {\n // Optionally add to collection if not already present\n if (options.addToCollection === true) {\n const existingModel = this.get(model.id);\n if (!existingModel) {\n this.add(model, { silent: options.silent });\n } else if (options.merge !== false) {\n existingModel.set(model.attributes);\n }\n }\n\n return model;\n } else {\n console.warn('Collection: fetchOne failed -', response.error || 'Unknown error');\n return null;\n }\n } catch (error) {\n console.error('Collection: fetchOne error -', error.message);\n return null;\n }\n }\n\n /**\n * Download collection data in a specified format\n * @param {string} format - The format for the download (e.g., 'csv', 'json')\n * @param {object} options - Download options\n * @returns {Promise} Promise that resolves with the download result\n */\n async download(format = 'json', options = {}) {\n if (!this.restEnabled) {\n console.warn('Collection: REST is not enabled, cannot download from remote.');\n // Here we could implement local data export in the future.\n return { success: false, message: 'Remote downloads are not enabled for this collection.' };\n }\n\n const url = this.buildUrl();\n const downloadParams = { ...this.params };\n\n // Remove pagination params for full export\n delete downloadParams.start;\n delete downloadParams.size;\n\n // Add format param\n downloadParams.download_format = format;\n\n // Provide a default filename and content type\n const baseName = `export-${this.getModelName().toLowerCase()}`;\n const rangeSuffix = this._buildDateRangeSuffix(downloadParams);\n const filename = `${baseName}${rangeSuffix}.${format}`;\n const contentTypes = {\n json: 'application/json',\n csv: 'text/csv'\n };\n const acceptHeader = contentTypes[format] || '*/*';\n downloadParams.filename = filename;\n\n return this.rest.download(url, downloadParams, {\n ...options,\n filename,\n headers: { 'Accept': acceptHeader }\n });\n }\n\n _buildDateRangeSuffix(params = {}) {\n const hasStart = params.dr_start;\n const hasEnd = params.dr_end;\n\n if (!hasStart && !hasEnd) {\n return '';\n }\n\n const sanitize = (value) => {\n if (!value) return '';\n return String(value).replace(/[^\\dA-Za-z_-]/g, '-');\n };\n\n const parts = [];\n const field = params.dr_field || 'daterange';\n parts.push(sanitize(field));\n\n if (hasStart) {\n parts.push(`from-${sanitize(params.dr_start)}`);\n }\n if (hasEnd) {\n parts.push(`to-${sanitize(params.dr_end)}`);\n }\n\n return `-${parts.filter(Boolean).join('-')}`;\n }\n\n /**\n * Parse response data - override in subclasses for custom parsing\n * @param {object} response - API response\n * @returns {array} Array of model data objects\n */\n parse(response) {\n // Handle standard paginated responses with size/start/count\n if (response.data && Array.isArray(response.data.data)) {\n this.meta = {\n size: response.data.size || 10,\n start: response.data.start || 0,\n count: response.data.count || 0,\n status: response.data.status,\n graph: response.data.graph,\n ...response.meta\n };\n return response.data.data;\n }\n\n // Handle direct array responses\n if (Array.isArray(response.data)) {\n return response.data;\n }\n\n // Fallback - assume response itself is the data array\n return Array.isArray(response) ? response : [response];\n }\n\n /**\n * Add model(s) to the collection\n * @param {object|array} data - Model data or array of model data\n * @param {object} options - Options for adding models\n */\n add(data, options = {}) {\n const modelsData = Array.isArray(data) ? data : [data];\n const addedModels = [];\n\n for (const modelData of modelsData) {\n let model;\n\n if (modelData instanceof this.ModelClass) {\n model = modelData;\n } else {\n model = new this.ModelClass(modelData, {\n endpoint: this.endpoint,\n collection: this\n });\n }\n\n // Check for duplicates\n const existingIndex = this.models.findIndex(m => m.id === model.id);\n if (existingIndex !== -1) {\n if (options.merge !== false) {\n // Update existing model\n this.models[existingIndex].set(model.attributes);\n }\n } else {\n // Add new model\n this.models.push(model);\n addedModels.push(model);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && addedModels.length > 0) {\n this.emit('add', { models: addedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return addedModels;\n }\n\n /**\n * Remove model(s) from the collection\n * @param {Model|array|string|number} models - Model(s) to remove or ID(s)\n * @param {object} options - Options\n */\n remove(models, options = {}) {\n const modelsToRemove = Array.isArray(models) ? models : [models];\n const removedModels = [];\n\n for (const model of modelsToRemove) {\n let index = -1;\n\n if (typeof model === 'string' || typeof model === 'number') {\n // Remove by ID\n index = this.models.findIndex(m => m.id == model);\n } else {\n // Remove by model instance\n index = this.models.indexOf(model);\n }\n\n if (index !== -1) {\n const removedModel = this.models.splice(index, 1)[0];\n removedModels.push(removedModel);\n }\n }\n\n // Emit events if not silent\n if (!options.silent && removedModels.length > 0) {\n this.emit('remove', { models: removedModels, collection: this });\n this.emit('update', { collection: this });\n }\n\n return removedModels;\n }\n\n /**\n * Reset the collection (remove all models)\n * @param {array} models - Optional new models to set\n * @param {object} options - Options\n */\n reset(models = null, options = {}) {\n const previousModels = [...this.models];\n this.models = [];\n\n if (models) {\n this.add(models, { silent: true, ...options });\n }\n\n if (!options.silent) {\n this.emit('reset', {\n collection: this,\n previousModels\n });\n }\n\n return this;\n }\n\n /**\n * Get model by ID\n * @param {string|number} id - Model ID\n * @returns {Model|undefined} Model instance or undefined\n */\n get(id) {\n return this.models.find(model => model.id == id);\n }\n\n /**\n * Get model by index\n * @param {number} index - Model index\n * @returns {Model|undefined} Model instance or undefined\n */\n at(index) {\n return this.models[index];\n }\n\n /**\n * Get collection length\n * @returns {number} Number of models in collection\n */\n length() {\n return this.models.length;\n }\n\n /**\n * Check if collection is empty\n * @returns {boolean} True if collection has no models\n */\n isEmpty() {\n return this.models.length === 0;\n }\n\n /**\n * Find models matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {array} Array of matching models\n */\n where(criteria) {\n if (typeof criteria === 'function') {\n return this.models.filter(criteria);\n }\n\n if (typeof criteria === 'object') {\n return this.models.filter(model => {\n return Object.entries(criteria).every(([key, value]) => {\n return model.get(key) === value;\n });\n });\n }\n\n return [];\n }\n\n /**\n * Find first model matching criteria\n * @param {function|object} criteria - Filter function or object with key-value pairs\n * @returns {Model|undefined} First matching model or undefined\n */\n findWhere(criteria) {\n const results = this.where(criteria);\n return results.length > 0 ? results[0] : undefined;\n }\n\n /**\n * Iterate over each model in the collection\n * @param {function} callback - Function to execute for each model (model, index, collection)\n * @param {object} thisArg - Optional value to use as this when executing callback\n * @returns {Collection} Returns the collection for chaining\n */\n forEach(callback, thisArg) {\n if (typeof callback !== 'function') {\n throw new TypeError('Callback must be a function');\n }\n\n this.models.forEach((model, index) => {\n callback.call(thisArg, model, index, this);\n });\n\n return this;\n }\n\n /**\n * Sort collection by comparator function\n * @param {function|string} comparator - Comparison function or attribute name\n * @param {object} options - Sort options\n */\n sort(comparator, options = {}) {\n if (typeof comparator === 'string') {\n const attr = comparator;\n comparator = (a, b) => {\n const aVal = a.get(attr);\n const bVal = b.get(attr);\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n };\n }\n\n this.models.sort(comparator);\n\n if (!options.silent) {\n this.trigger('sort', { collection: this });\n }\n\n return this;\n }\n\n /**\n * Convert collection to JSON array\n * @returns {array} Array of model JSON representations\n */\n toJSON() {\n return this.models.map(model => model.toJSON());\n }\n\n /**\n * Cancel any active fetch request\n * @returns {boolean} True if a request was cancelled, false if no active request\n */\n cancel() {\n if (this.currentRequest && this.abortController) {\n console.info('Collection: Manually cancelling active request');\n this.abortController.abort();\n return true;\n }\n return false;\n }\n\n /**\n * Check if collection has an active fetch request\n * @returns {boolean} True if fetch is in progress\n */\n isFetching() {\n return !!this.currentRequest;\n }\n\n /**\n * Build URL for collection endpoint\n * @returns {string} Collection API URL\n */\n buildUrl() {\n return this.endpoint;\n }\n\n // EventEmitter API: on, off, once, emit (from mixin).\n\n /**\n * Iterator support for for...of loops\n */\n *[Symbol.iterator]() {\n for (const model of this.models) {\n yield model;\n }\n }\n\n /**\n * Static method to create collection from array data\n * @param {function} ModelClass - Model class constructor\n * @param {array} data - Array of model data\n * @param {object} options - Collection options\n * @returns {Collection} New collection instance\n */\n static fromArray(ModelClass, data = [], options = {}) {\n const collection = new this(ModelClass, options);\n collection.add(data, { silent: true });\n return collection;\n }\n}\n\nObject.assign(Collection.prototype, EventEmitter);\n\nexport default Collection;\n"],"names":["Model","constructor","data","options","this","endpoint","id","attributes","_","originalAttributes","errors","loading","rest","idAttribute","timestamps","getContextValue","key","get","includes","MOJOUtils","getContextData","set","value","previousAttributes","JSON","parse","stringify","hasChanged","attrKey","attrValue","Object","entries","_setNestedAttribute","silent","emit","attr","val","finalValue","_getNestedValue","oldValue","keys","split","topLevelKey","attrTarget","instanceTarget","i","length","currentKey","finalKey","source","current","k","getData","getId","fetch","url","requiresId","Error","buildUrl","requestKey","params","debounceMs","_debouncedFetch","currentRequest","currentRequestKey","abortController","abort","now","Date","lastFetchTime","AbortController","_performFetch","error","name","debouncedFetchTimeout","clearTimeout","cancel","Promise","resolve","reject","setTimeout","async","result","graph","response","GET","signal","success","status","message","save","isNew","method","destroy","DELETE","isDirty","getChangedAttributes","changed","reset","endsWith","toJSON","validate","validations","field","rules","validateField","rulesArray","Array","isArray","rule","required","minLength","maxLength","pattern","test","find","model","create","isFetching","showError","Dialog","alert","size","class","assign","prototype","EventEmitter","Collection","ModelClass","models","meta","add","start","tmp","restEnabled","preloaded","getModelName","additionalParams","rateLimiting","fetchParams","updateParams","newParams","autoFetch","setParams","fetchOne","console","warn","collection","addToCollection","existingModel","merge","download","format","downloadParams","download_format","filename","toLowerCase","_buildDateRangeSuffix","acceptHeader","json","csv","headers","Accept","hasStart","dr_start","hasEnd","dr_end","sanitize","String","replace","parts","dr_field","push","filter","Boolean","join","count","modelsData","addedModels","modelData","existingIndex","findIndex","m","remove","modelsToRemove","removedModels","index","indexOf","removedModel","splice","previousModels","at","isEmpty","where","criteria","every","findWhere","results","forEach","callback","thisArg","TypeError","call","sort","comparator","a","b","aVal","bVal","trigger","map","Symbol","iterator","fromArray"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as rest, M as MOJOUtils, E as EventEmitter } from "./Rest-0oRgqNjX.js";
|
|
2
2
|
class Model {
|
|
3
3
|
constructor(data = {}, options = {}) {
|
|
4
4
|
this.endpoint = options.endpoint || this.constructor.endpoint || "";
|
|
@@ -720,18 +720,42 @@ class Collection {
|
|
|
720
720
|
delete downloadParams.start;
|
|
721
721
|
delete downloadParams.size;
|
|
722
722
|
downloadParams.download_format = format;
|
|
723
|
-
const
|
|
723
|
+
const baseName = `export-${this.getModelName().toLowerCase()}`;
|
|
724
|
+
const rangeSuffix = this._buildDateRangeSuffix(downloadParams);
|
|
725
|
+
const filename = `${baseName}${rangeSuffix}.${format}`;
|
|
724
726
|
const contentTypes = {
|
|
725
727
|
json: "application/json",
|
|
726
728
|
csv: "text/csv"
|
|
727
729
|
};
|
|
728
730
|
const acceptHeader = contentTypes[format] || "*/*";
|
|
731
|
+
downloadParams.filename = filename;
|
|
729
732
|
return this.rest.download(url, downloadParams, {
|
|
730
733
|
...options,
|
|
731
734
|
filename,
|
|
732
735
|
headers: { "Accept": acceptHeader }
|
|
733
736
|
});
|
|
734
737
|
}
|
|
738
|
+
_buildDateRangeSuffix(params = {}) {
|
|
739
|
+
const hasStart = params.dr_start;
|
|
740
|
+
const hasEnd = params.dr_end;
|
|
741
|
+
if (!hasStart && !hasEnd) {
|
|
742
|
+
return "";
|
|
743
|
+
}
|
|
744
|
+
const sanitize = (value) => {
|
|
745
|
+
if (!value) return "";
|
|
746
|
+
return String(value).replace(/[^\dA-Za-z_-]/g, "-");
|
|
747
|
+
};
|
|
748
|
+
const parts = [];
|
|
749
|
+
const field = params.dr_field || "daterange";
|
|
750
|
+
parts.push(sanitize(field));
|
|
751
|
+
if (hasStart) {
|
|
752
|
+
parts.push(`from-${sanitize(params.dr_start)}`);
|
|
753
|
+
}
|
|
754
|
+
if (hasEnd) {
|
|
755
|
+
parts.push(`to-${sanitize(params.dr_end)}`);
|
|
756
|
+
}
|
|
757
|
+
return `-${parts.filter(Boolean).join("-")}`;
|
|
758
|
+
}
|
|
735
759
|
/**
|
|
736
760
|
* Parse response data - override in subclasses for custom parsing
|
|
737
761
|
* @param {object} response - API response
|
|
@@ -987,4 +1011,4 @@ export {
|
|
|
987
1011
|
Collection as C,
|
|
988
1012
|
Model as M
|
|
989
1013
|
};
|
|
990
|
-
//# sourceMappingURL=Collection-
|
|
1014
|
+
//# sourceMappingURL=Collection-zmb3xHhH.js.map
|