sdc_client 0.58.6 → 0.158.0
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/.github/workflows/node.js.yml +2 -2
- package/.idea/inspectionProfiles/Project_Default.xml +17 -0
- package/.idea/workspace.xml +122 -23
- package/.readthedocs.yaml +13 -0
- package/dist/index.js +70 -51
- package/dist/ugly.index.js +1 -1
- package/docs/api-reference.rst +225 -0
- package/docs/conf.py +11 -0
- package/docs/controllers.rst +228 -0
- package/docs/events-and-dom.rst +120 -0
- package/docs/getting-started.rst +143 -0
- package/docs/index.rst +22 -0
- package/docs/models.rst +351 -0
- package/docs/overview.rst +66 -0
- package/docs/requirements.txt +1 -0
- package/eslint.config.js +60 -0
- package/gulp/gulp.jsx +15 -13
- package/package.json +13 -3
- package/src/index.js +44 -11
- package/src/simpleDomControl/AbstractSDC.js +287 -286
- package/src/simpleDomControl/sdc_controller.js +157 -132
- package/src/simpleDomControl/sdc_dom_events.js +99 -88
- package/src/simpleDomControl/sdc_events.js +39 -40
- package/src/simpleDomControl/sdc_main.js +88 -67
- package/src/simpleDomControl/sdc_model.js +1510 -0
- package/src/simpleDomControl/sdc_params.js +44 -29
- package/src/simpleDomControl/sdc_server_call.js +153 -154
- package/src/simpleDomControl/sdc_socket.js +8 -829
- package/src/simpleDomControl/sdc_test_utils.js +77 -80
- package/src/simpleDomControl/sdc_utils.js +295 -176
- package/src/simpleDomControl/sdc_view.js +245 -177
- package/test/model.test.js +332 -0
- package/test/models/Author.js +145 -0
- package/test/models/Book.js +112 -0
- package/test/models/BookContent.js +114 -0
- package/test/models/SdcUser.js +75 -0
- package/test/models/src.js +8 -0
- package/test/sdc_model_dates.ai.test.js +57 -0
- package/test/sdc_server_call.ai.test.js +267 -0
package/dist/ugly.index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e={};let t;e.d=(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);const n=/(?<=^|,\s?)[^=\s,]+/g,r=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;function o(){return new Promise(function(e){e()})}function s(e){return(e=(e=e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)).replace(/[0-9]+/g,e=>`-${e}`)).replace(/^[-]/g,"")}function i(e){return e.replace(/-./g,e=>`${e[1].toUpperCase()}`)}const l=(e,t)=>{let n=t,r=Object.getOwnPropertyNames(e.prototype).concat(Object.getOwnPropertySymbols(e.prototype));for(;""!==n.name;)Object.getOwnPropertyNames(n.prototype).concat(Object.getOwnPropertySymbols(n.prototype)).forEach(t=>{t.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)||r.includes(t)||(r.push(t),Object.defineProperty(e.prototype,t,Object.getOwnPropertyDescriptor(n.prototype,t)))}),n=Object.getPrototypeOf(n)};function a(e,t,n){return $.ajax({url:t,type:n||"POST",xhr:function(){var e=$.ajaxSettings.xhr();return e.upload&&e.upload.addEventListener("progress",c,!1),e},data:e,cache:!1,contentType:!1,processData:!1,beforeSend:function(e,t){(function(e){return/^(GET|HEAD|OPTIONS|TRACE)$/.test(e)})(t.type)||this.crossDomain||e.setRequestHeader("X-CSRFToken",window.CSRF_TOKEN)}})}function c(e){if(e.lengthComputable){var t=Math.round(e.loaded/e.total*100),n=$(".progress-container");100===t?n.hide():n.show(),t+="%",n.find(".progress-bar").css({width:t}).text(t)}}function u(e,t=null){return"string"!=typeof e?e:t&&void 0!==t[e]?"function"==typeof t[e]?t[e].bind(t):t[e]:e.match(/^(true|false)$/)?"true"===e:"undefined"!==e?"none"===e.toLowerCase()?null:e.match(/^-?\d+$/)?parseInt(e):e.match(/^-?\d+\.?\d+$/)?parseFloat(e):e.match(/^(['][^']*['])|(["][^"]*["])$/)?e.substr(1,e.length-2):e:void 0}function d(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16))}function h(e){e.find(".has-error").removeClass("has-error").find(".alert-danger").remove(),e.find(".non-field-errors").remove()}function p(e,t){t=$("<div>").append(t),e.find(".has-error").removeClass("has-error").find(".alert-danger").safeRemove(),e.find(".non-field-errors").safeRemove();let n=t.find("input[type=file]").parent();e.find("input[type=file]").parent().each(function(e){$(this).replaceWith(n[e])});let r=0===t.find(".non-field-errors").insertAfter(e.find(".hidden-form-fields")).length;return t.find(".has-error").each(function(){r=!1;let t=$(this),n=t.data("auto-id"),o=e.find(".form-group."+n);o.addClass("has-error"),o.find(".form-input-container").append(t.find(".alert-danger"))}),r}function f(e,t=[]){return Object.fromEntries(Object.entries(e).filter(([e,n])=>e!==L&&!t.includes(e)).map(([e,t])=>{if("string"==typeof t&&t.startsWith("SDC_JSON_MODEL=["))try{const{pk:n,fields:r}=JSON.parse(t.slice(16,-1));return[e,{...r,id:n,pk:n}]}catch{}return[e,t]}))}function m(e,t,n=null){let r=function(e,t){let n;e||(e=[]);let r=t.data(),o=f(r,e);n=[];for(let t=0;t<e.length;t++){let o=e[t];r.hasOwnProperty(o)?n.push(r[o]):n.push("undefined")}return n.push(o),n}(e,t);return function(e,t=null){let n=[];for(let r=0;r<e.length;r++){let o=u(e[r],t);n.push(o)}return n}(r,n)}function g(e,t,o){if(!e)return!1;if("function"!=typeof e.onInit)return!1;let s;var i;"function"==typeof e._on_init_params?s=e._on_init_params():s=(i=e.onInit.toString().replace(r,"")).slice(i.indexOf("(")+1,i.indexOf(")")).match(n)||[];let l=m(s,t,o._parentController);if(e.onInit.apply(o,l),o===e)for(let n in e._mixins)g(e._mixins[n],t,o)}const y=["onbeforeunload","onunload"],v=Object.keys(window).filter(e=>/^on/.test(e)&&!y.includes(e)).map(e=>e.slice(2));function w(e,t,n){let r;switch(e){case"mouseleave":case"mouseout":case"mouseenter":case"mousein":return r=document.elementFromPoint(t.clientX,t.clientY),n!==r&&!n.contains(r);default:return!0}}function O(e){let t=e.type;e.hasOwnProperty("namespace")&&e.namespace&&e.namespace.length&&(t+=`.${e.namespace}`);let n=$(e.target),r=null,o=!1,s=!1;for(e.stopImmediatePropagation=()=>s=!0,e.stopPropagation=()=>s=o=!0;n.length;){let i=n.attr(`sdc_${t}`);if(i&&w(t,e,n[0])){if(!r&&(r=W(n),!r))return;for(;r;){if(i.split(" ").forEach(s=>{if(o)return;let i=null;if("function"==typeof s)i=s;else if("function"==typeof r[s])i=r[s];else if("string"==typeof s&&s.startsWith("this.event_")){if(i=r.getEvents()[t],!i)return;if(i=i[s.slice(11)],!i)return}i&&i.call(r,n,e)}),s)return;r=r._parentController}}if(o)return;n=n.parent()}return{res:!0}}function C(e){if(e._isEventsSet)return;const t=e.getEvents();for(let n in t)if(t.hasOwnProperty(n)){let r=t[n];for(let t in r)r.hasOwnProperty(t)&&e.find(t).each(function(){let e=$(this),r=e.attr(`sdc_${n}`)||null;r=r?r.split(" "):[];const o=`this.event_${t}`;-1===r.indexOf(o)&&(r.push(o),e.attr(`sdc_${n}`,r.join(" ")))})}}let b=[],k={};function N(e,t){if(e){let n=i(t._tagName);e._childController[n]||(e._childController[n]=[]),e._childController[n].push(t)}return t._parentController=e}function P(e,t,n,r){let o=[];r=function(e,t){e=(e=e.concat(k[t][1])).filter((e,t,n)=>n.indexOf(e)===t);let n=!0;for(;n;){n=!1;for(let t of e)for(let r of k[t][1])e.includes(r)||(e.push(r),n=!0)}return e}(r,n);for(let e of r)o.push(k[e][0]);let s=new(function(e,...t){let n={[e.name]:class{constructor(...n){let r={};t.forEach(e=>{let t;Object.assign(this,t=new e),t._tagName=e.prototype._tagName,t._isMixin=!0,r[e.name]=t}),Object.assign(this,new e),this._mixins=r}static get name(){return e.name}static className(){return this.name}get mixins(){return this._mixins}}}[e.name];return l(n,e),t.forEach(e=>{l(n,e)}),n}(k[n][0],...o));return s._tagName=n,N(e,s),s.$container=t,function(e,t){g(t,e,t)}(t,s),s}function S(e,t,n,r){if(b.includes(n)){let o=i(n);return window[o]||(window[o]=P(e,t,n,r)),window[o].$container=t,window[o]}return P(e,t,n,r)}function E(e,t){const n=function(e){return function(e){let t={args:{}};return e.contentUrl&&(t=B(e),e.contentUrl=t.url),Promise.all([D(e,t.args)]).then(function(e){let t=e[0];if(t)try{return $(t)}catch{return $("<div></div>").append(t)}return null})}(e).then(t=>!e.onLoad||e._onLoadDone?t:(e._onLoadDone=!0,(e.onLoad(t)||o()).then(()=>t)))}(e).then(function(t){return function(e,t){return K(e,t).then(function(t){if(t=t||!0,e.willShow){let n=e.willShow();if(n instanceof Promise)return n.then(function(){return t})}return t})}(e,t)}).then(()=>function(e,t){return Z(null,e,t)}(e,t)).catch(function(t){return K(e,t)});return e.load_async?Promise.resolve():n}function R(e){const t=function(e){let t=[];for(;e;)e._isEventsSet=!1,t.unshift(e),e=e._parentController;return t}(e.controller[0]),n=t.concat(e.controller.slice(1));for(let t of n)C(t),t.onRefresh(e.controller[0])}function x(e,t){let n=Boolean(e);return n?e.uuids.has(t._uuid)||(e.uuids.add(t._uuid),e.controller.push(t)):e={uuids:new Set([t._uuid]),controller:[t]},{isRunningProcess:n,refreshProcess:e}}let q={},F={};function j(e,t){return T(e),F.hasOwnProperty(e)?t[F[e]]?void q[e].push(t):console.log("No event handler: "+e,t):console.log("No event: "+e,t)}function T(e,t){t||(t=e),F[e]||(F[e]=t,q[e]=[])}function M(e){for(let t in q)if(q.hasOwnProperty(t))for(let n=q[t].length;n>=0;n--)e===q[t][n]&&q[t].splice(n,1)}function A(e){let t=Array.apply(null,arguments);if(e=t.shift(),!q.hasOwnProperty(e)||!F.hasOwnProperty(e))return o();let n=q[e],r=F[e],s=[];for(let e=0;e<n.length;e++){let o=n[e][r].apply(n[e],t);void 0!==o&&s.push(o)}return Promise.all(s)}let I={};const L="_controller_",J="_sdc_controller_";function U(e,t,n){if(!e)return[];let r=e.children(),o=[];return r.each(function(e,r){let s=$(r),i=s.prop("tagName").toLowerCase().split("_");$.inArray(i[0],t)>=0?o.push({tag:i[0],super:i.splice(1)||[],dom:s}):i[0].startsWith("this.")?s.addClass(`_bind_to_update_handler sdc_uuid_${n._uuid}`):o=o.concat(U(s,t,n))}),o}function D(e,t){const{contentUrl:n,_tagName:r,contentReload:o}=e;return n?I[r]?Promise.resolve(I[r]):(t.VERSION=_e.VERSION,t._method="content",$.get(n,t).then(function(e){return o||(I[r]=e),e}).catch(function(t){throw 301===t.status&&A("_RedirectOnView",t.responseJSON["url-link"]),"function"==typeof e.confirmPageLoaded&&A("navLoaded",{controller_name:()=>t.status}),`<sdc-error data-code="${t.status}">${t.responseText}</sdc-error>`})):Promise.resolve(!1)}function V(e,t,n=null){return t=t||e.data(L),H(_e.tagNames,e,t,n)}function B(e){let t=e.contentUrl;if(e&&0===e._urlParams.length){let n,r=/%\(([^)]+)\)\w/gm;for(e._urlParams=[];n=r.exec(t);)e._urlParams.push(n[1]),e.contentReload=!0}let n=function(e,t){return m(e._urlParams,t)}(e,e.$container);return e._urlParams.length&&(t=function(e,t,n){for(let r in e._urlParams)if(e._urlParams.hasOwnProperty(r)){let o=e._urlParams[r],s=RegExp("%\\("+o+"\\)\\w","gm");t=t.replace(s,""+n.shift())}return t}(e,t,n)),e.parsedContentUrl=t,{url:t,args:n[n.length-1]}}function W(e){return e.hasClass(J)?e.data(`${L}`):e.closest(`.${J}`).data(`${L}`)}function G(e,t,n,r,o){let s=e.data(L);return s?V(e,s,o):(s=S(r,e,t,n),e.data(L,s),e.addClass(J),E(s,o))}function K(e,t,n=null){if(t&&t.length>0){e.$container.empty(),e.$container.attr(e._tagName,"");for(let t in e._mixins)e.$container.attr(e._mixins[t]._tagName,"");e.$container.append(t)}return V(e.$container,e,n)}function H(e,t,n,r){return new Promise(o=>{let s=U(t,e,n),i=s.length;if(0===i)return o();for(let e=0;e<s.length;e++)G(s[e].dom,s[e].tag,s[e].super,n,r).then(()=>{if(i--,0===i)return o()})})}function X({$element:e,id:t=[],parent:n=null}){t.push(function(e){if(3===e[0].nodeType)return`TEXT__${e[0].nodeValue}`;const t=[e[0].tagName];return"INPUT"===e[0].nodeName&&[["name",""],["type","text"],["id",""]].forEach(([n,r])=>{const o=e.attr(n)??r;o&&t.push(o)}),t.join("__")}(e));const r={$element:e,id:t.join("::"),depth:t.length,idx:0,getRealParent:()=>n,getIdx:function(){return this.idx=Math.max(0,(this.getRealParent()?.getIdx()??-1)+e.index()+1),this.idx},op:null,parent:n};return r.getIdx.bind(r),[r].concat(e.contents().toArray().map(e=>X({$element:$(e),id:t.slice(),parent:r})).flat())}function z(e,t){const n=X({$element:t}),r=X({$element:e});n.map((e,t)=>e.idx=t),r.map((e,t)=>e.idx=t);const o=Math.max(...r.concat(n).map(e=>e.depth)),s=Y(n,r,o);let i=[];window.MAIN=t,window.OPS=s,s.forEach((e,t)=>{const{op:n,$element:r,idx:o}=e;if("keep_counterpart"===n.type){if(n.counterpart.getIdx()!==o){const t=e.getBefore();t?n.counterpart.$element.insertAfter(t.$element):e.getRealParent().$element.prepend(n.counterpart.$element)}!function(e,t){const n=e[0].attributes??[],r=t[0].attributes??[];[...n].forEach(n=>{t.is(`[${n.name}]`)||e.removeAttr(n.name)}),[...r].forEach(t=>{t.name.startsWith("data")||e.attr(t.name)===t.value||e.attr(t.name,t.value)}),e.removeData(),Object.entries(t.data()).forEach(([t,n])=>{e.data(t,n)})}(n.counterpart.$element,r),r.hasClass(J)&&(r.data(L).$container=n.counterpart.$element,r.data(L,null)),i.push(r)}else if("delete"===n.type)r.safeRemove();else if("insert"===n.type){const{after:t,target:n}=e.op;t?r.insertAfter(t.$element):n&&n.$element.prepend(r)}}),i.forEach(e=>e.safeRemove())}function Y(e,t,n){return t.filter(e=>e.depth===n&&!e.op).forEach(t=>{const n=e.find(e=>!e.op&&e.id===t.id);if(n){const e=(t,n)=>{t.op={type:"keep",idx:n.idx},n.op={type:"keep_counterpart",counterpart:t},t=t.parent,n=n.parent,!t||t.op||n?.op||e(t,n)};e(n,t)}}),n>1?Y(e,t,n-1):(e.forEach((t,n)=>{if(!t.op){const r=(e[n-1]?.op.idx??-1)+1;t.op={type:"delete",idx:r}}}),t.forEach((e,n)=>{if(e.getBefore=()=>function(e,n){const r=e.depth;for(;n>=1&&e.depth>=r;)if((e=t[n-=1]).depth===r)return"keep_counterpart"===e.op.type?e.op.counterpart:e;return null}(e,n),e.getRealParent=()=>{return(t=e).parent?"keep_counterpart"===t.parent.op.type?t.parent.op.counterpart:t.parent:null;var t},!e.op){const t=e.getRealParent(),n="insert"===t?.op.type?"insert_ignore":"insert";e.op={type:n,target:t,after:e.getBefore()}}}),[...e,...t].sort((e,t)=>(e.op?.idx??e.idx)-(t.op?.idx??t.idx)))}function Z(e,t,n=null){if(t||(t=W(e)),!t)return Promise.resolve();const{refreshProcess:r,isRunningProcess:o}=x(n,t);return e??=t.$container,H(_e.tagNames,e,t,n).then(()=>function(e,t,n){return function(e,t,n){const r=[];return t.find(`._bind_to_update_handler.sdc_uuid_${e._uuid}`).each(function(){const t=$(this);let o;if(t.hasClass("_with_handler"))o=t.data("handler");else{let t=this.tagName.toLowerCase().replace(/^this./,"");e[t]&&(o=e[t])}if("function"==typeof o){const n=f(t.data());o=o.bind(e)(n)}o&&r.push(Promise.resolve(o).then(r=>{let o=$("<div></div>");return o.append(r),""===t.html()&&t.append("<div></div>"),_e.reconcile(e,o,t.children(),n)}))}),Promise.all(r)}(e,t??e.$container,n)}(t,e,r).then(()=>{o||R(r)}))}const Q=25e3;class ee{constructor(e,t){this.pk=e,this._model=t}set model(e){this._model=e}get model(){return this._model}load(e){if(!this._model)throw new TypeError("Model is not set!!");return this.pk instanceof Array?e.newModel(this._model,{pk__in:this.pk}):e.newModel(this._model,{pk:this.pk})}}const te={get(e,t){const n=e[t]??void 0;if(n instanceof ee){if(!n.pk&&0!==n.pk)return null;let e;return e=n.pk instanceof Array?n.pk.slice():new Number(n.pk),e.load=n.load.bind(n),e}return n},set(e,t,n){if(t in e){const r=e[t];if(r instanceof ee)if(n.hasOwnProperty("pk"))r.pk=n.pk;else try{r.pk=JSON.parse(n)}catch{r.pk=n}else e[t]=n}else e[t]=n;return!0}};function ne(e){return e.toLowerCase().match(/^(true|false)$/)?"true"===e.toLowerCase():"undefined"!==e?"none"===e.toLowerCase()?null:e.match(/^-?\d+$/)?parseInt(e):e.match(/^-?\d+\.?\d+$/)?parseFloat(e):e.match(/^(['][^']*['])|(["][^"]*["])$/)?e.substring(1,e.length-1):e:void 0}class re{constructor(e,t={}){this._onNoOpenRequests=[],this.values_list=[],this.values={},this.model_name=e,this.model_query=t,this._is_connected=!1,this._is_conneting_process=!1,this._auto_reconnect=!0,this.socket=null,this.open_request={},this.on_update=()=>{},this.on_create=()=>{},this.form_id=d()}[Symbol.iterator](){let e=-1;return{next:()=>(++e,e<this.values_list.length?{value:this.values_list[e],done:!1}:{value:null,done:!0})}}length(){return this.values_list.length}byPk(e){if(null!==e){e=parseInt(e),isNaN(e)&&(e=-1);let t=this.values_list.find(t=>t.pk===e);return t||(t=new Proxy({pk:e},te),this.values_list.push(t)),t}return{pk:e}}filter(e){return this.model_query=Object.assign({},this.model_query,e),this}load(){return this.isConnected().then(()=>{const e=d();return new Promise((t,n)=>{this.socket.send(JSON.stringify({event:"model",event_type:"load",event_id:e,args:{model_name:this.model_name,model_query:this.model_query}})),this.open_request[e]=[t,n]})})}listView(e={},t=null,n=null,r={}){return this.view({filter:e,cbResolve:t,cbReject:n,templateContext:r,event_type:"list_view"})}view({viewName:e="html_list_template",filter:t={},cbResolve:n=null,cbReject:r=null,templateContext:o={},event_type:s="named_view"}){let i=$('<div class="container-fluid">');return this.isConnected().then(()=>{const l=d();this.socket.send(JSON.stringify({event:"model",event_type:s,event_id:l,args:{view_name:e,model_name:this.model_name,model_query:this.model_query,filter:t,template_context:o}})),this.open_request[l]=[e=>{i.append(e.html),_e.refresh(i),n&&n(e)},e=>{r&&r(e)}]}),i}detailView(e=null,t=null,n=null,r={}){e=e??-1,e=parseInt(e),isNaN(e)&&(e=-1);let o,s=$('<div class="container-fluid">');return o=0!==this.values_list.length?this.isConnected():this.load(),o.then(()=>{-1===e&&(e=this.values_list[0].pk);const o=d();this.socket.send(JSON.stringify({event:"model",event_type:"detail_view",event_id:o,args:{model_name:this.model_name,model_query:this.model_query,pk:e,template_context:r}})),this.open_request[o]=[e=>{s.append(e.html),_e.refresh(s),t&&t(e)},e=>{n&&n(e)}]}),s}syncFormToModel(e){return this.syncForm(e)}syncModelToForm(e){e&&e.hasClass(this.form_id)||(e=$(`.${this.form_id}`));let t=this;e.each(function(){let e=$(this).data("model_pk"),n=null;if(-1!==e||void 0!==t.values.pk&&-1!==t.values.pk?null!=e&&(n=t.byPk(e)):n=t.values,n)for(let e of this.elements){let t=e.name;if(t&&""!==t)if("checkbox"===e.type)e.checked=n[t];else if("file"===e.type){if(n[t]instanceof File){let r=new DataTransfer;r.items.add(n[t]),e.files=r}}else $(e).val(n[t])}})}syncForm(e){e&&e.hasClass(this.form_id)||(e=$(`.${this.form_id}`));const t=this;let n=[];return e.each(function(){let e=$(this).data("model_pk"),r=t.byPk(e);for(let e of this.elements){let t=e.name;t&&""!==t&&("hidden"===e.type?r[t]=ne($(e).val()):"checkbox"===e.type?r[t]=e.checked:"file"===e.type?r[t]=e.files[0]:r[t]=$(e).val())}return n.push(r),r}),this.values_list.length<=1&&n.length>0&&(this.values=n.at(-1)),n}createForm(e=null,t=null){let n=$("<div>");return this.isConnected().then(()=>{this._getForm(null,"create_form",null,n,e,t)}),n}editForm(e=-1,t=null,n=null){let r;e=parseInt(e),isNaN(e)&&(e=-1),r=0!==this.values_list.length?this.isConnected():this.load();let o=$("<div>");return r.then(()=>{e<=-1&&(e=this.values_list.at(e).pk),this._getForm(e,"edit_form",null,o,t,n)}),o}namedForm(e=-1,t,n=null,r=null){let o;e=parseInt(e),isNaN(e)&&(e=-1),o=0!==this.values_list.length?this.isConnected():this.load();let s=$('<div class="container-fluid">');return o.then(()=>{e<=-1&&(e=this.values_list.at(e).pk),this._getForm(e,"named_form",t,s,n,r)}),s}_getForm(e,t,n,r,o,s){e=parseInt(e),isNaN(e)&&(e=-1);const i=d();this.socket.send(JSON.stringify({event:"model",event_type:t,event_id:i,args:{model_name:this.model_name,model_query:this.model_query,pk:e,form_name:n}}));const l=null===e||-1===e?"create":"edit";this.open_request[i]=[t=>{r.append(t.html);let s=r.closest("form").addClass(`sdc-model-${l}-form sdc-model-form ${this.form_id}`).data("model",this).data("model_pk",e).data("form_name",n);s.length>0&&!s[0].hasAttribute("sdc_submit")&&s.attr("sdc_submit","submitModelFormDistributor"),_e.refresh(r),o&&o(t)},e=>{s&&s(e)}]}new(){return new Promise((e,t)=>{const n=$("<form>").append(this.createForm(()=>{this.syncFormToModel(n),e()},t))})}save(e=-1,t="edit_form"){return e=parseInt(e),isNaN(e)&&(e=-1),this.isConnected().then(()=>{let n;n=e>-1?[this.byPk(e)]:this.values_list;let r=[];return n.forEach(e=>{const n=d();r.push(new Promise((r,o)=>{this._readFiles(e).then(s=>{this.socket.send(JSON.stringify({event:"model",event_type:"save",event_id:n,args:{form_name:t,model_name:this.model_name,model_query:this.model_query,data:e,files:s}})),this.open_request[n]=[e=>{let t="string"==typeof e.data.instance?JSON.parse(e.data.instance):e.data.instance;e.data.instance=this._parseServerRes(t),r(e)},o]})}))}),Promise.all(r)})}create(e=this.values){const t=d();return this.isConnected().then(()=>new Promise((n,r)=>{this._readFiles(e).then(o=>{this.socket.send(JSON.stringify({event:"model",event_type:"create",event_id:t,args:{model_name:this.model_name,model_query:this.model_query,data:e,files:o}})),this.open_request[t]=[e=>{let t="string"==typeof e.data.instance?JSON.parse(e.data.instance):e.data.instance;-1===this.values?.pk&&(this.values_list=[],this.values=null),e.data.instance=this._parseServerRes(t)[0],n(e)},r]})}))}delete(e=-1){e=parseInt(e),isNaN(e)&&(e=-1),-1===e&&(e=this.values?.pk);const t=d();return this.isConnected().then(()=>new Promise((n,r)=>{this.socket.send(JSON.stringify({event:"model",event_type:"delete",event_id:t,args:{model_name:this.model_name,model_query:this.model_query,pk:e}})),this.open_request[t]=[n,r]}))}isConnected(){return new Promise((e,t)=>{if(this._is_connected)e();else if(this._is_conneting_process&&this.open_request._connecting_process){const[n,r]=this.open_request._connecting_process;this.open_request._connecting_process=[()=>{n(),e()},()=>{r(),t()}]}else this._is_conneting_process=!0,this.open_request._connecting_process=[()=>{},()=>{}],this._connectToServer().then(()=>{e(this._checkConnection())})})}close(){this.socket&&(this._auto_reconnect=!1,this.socket.onclose=()=>{},this.socket.close(),delete this.socket)}clean(){return this.values_list=[],this.values={},this}_readFiles(e){let t=[],n={};for(const[r,o]of Object.entries(e))o instanceof File&&t.push(new Promise((e,t)=>{((r,o)=>{let s=new FileReader;s.onload=s=>{const i=d();this.open_request[i]=[e,t];let l=s.target.result,a=parseInt(Math.ceil(l.length/Q));n[r]={id:i,file_name:o.name,field_name:r,content_length:o.size};for(let e=0;e<a;++e)this.socket.send(JSON.stringify({event:"model",event_type:"upload",event_id:i,args:{chunk:l.slice(Q*e,Q*(e+1)),idx:e,number_of_chunks:a,file_name:o.name,field_name:r,content_length:o.size,content_type:o.type,model_name:this.model_name,model_query:this.model_query}}))},s.onerror=()=>{t()},s.readAsBinaryString(o)})(r,o)}));return Promise.all(t).then(()=>n)}_onMessage(e){let t=JSON.parse(e.data);if(t.is_error)this.open_request.hasOwnProperty(t.event_id)&&(this.open_request[t.event_id][1](t),this._closeOpenRequest(t.event_id)),(t.msg||t.header)&&A("pushErrorMsg",t.header||"",t.msg||""),"connect"===t.type&&(this.open_request._connecting_process[1](t),this._closeOpenRequest("_connecting_process"),this._auto_reconnect=!1,this.socket.close());else{if((t.msg||t.header)&&A("pushMsg",t.header||"",t.msg||""),"connect"===t.type)this._is_connected=!0,this._is_conneting_process=!1,this.open_request._connecting_process[0](t),this._closeOpenRequest("_connecting_process");else if(["load","named_view","detail_view"].includes(t.type)){const e=JSON.parse(t.args.data);this.values_list=[],t.args.data=this._parseServerRes(e)}else if("on_update"===t.type||"on_create"===t.type){const e=JSON.parse(t.args.data);let n,r=this._parseServerRes(e);n="on_create"===t.type?this.on_create:this.on_update,n(r),t.args.data=r}let e=t.data?.instance;e&&(t.data.instance=JSON.parse(t.data.instance)),this.open_request.hasOwnProperty(t.event_id)&&(this.open_request[t.event_id][0](t),this._closeOpenRequest(t.event_id))}}noOpenRequests(){return new Promise(e=>{if(0===Object.keys(this.open_request).length)return e();this._onNoOpenRequests.push(e)})}_closeOpenRequest(e){delete this.open_request[e],0===Object.keys(this.open_request).length&&(this._onNoOpenRequests.forEach(e=>e()),this._onNoOpenRequests=[])}_connectToServer(){return new Promise(e=>{const t=`${this.model_name}`+(this.model_id>0?`/${this.model_id}`:"");"https:"===window.location.protocol?this.socket=new WebSocket(`wss://${window.location.host}/sdc_ws/model/${t}`):this.socket=new WebSocket(`ws://${window.location.host}/sdc_ws/model/${t}`),this.socket.onmessage=this._onMessage.bind(this),this.socket.onclose=e=>{console.error(`SDC Model (${this.model_name}, ${this.model_id}) Socket closed unexpectedly`),this._is_connected=!1;for(const[t,n]of Object.entries(this.open_request))n[1](e);this.open_request={},setTimeout(()=>{this._auto_reconnect&&this._connectToServer().then(()=>{})},1e3)},this.socket.onerror=e=>{if(console.error(`Model Socket encountered error: ${e} Closing socket`),this._is_connected)try{this.socket.close()}catch(e){}},this.socket.onopen=()=>{e()}})}_checkConnection(){const e=d();return new Promise((t,n)=>{this.socket.send(JSON.stringify({event:"model",event_type:"connect",event_id:e,args:{model_name:this.model_name,model_query:this.model_query}})),this.open_request[e]=[t,n]})}_parseServerRes(e){let t=[];for(let n of e){const e=n.pk,r=this.byPk(e);for(const[e,t]of Object.entries(n.fields))t&&"object"==typeof t&&t.__is_sdc_model__?r[e]=new ee(t.pk,t.model):r[e]=t;t.push(r)}return 1===this.values_list.length?this.values=this.values_list.at(-1):this.values={},t}}let oe=!1,se=!1,ie=null,le={};function ae(){return window.SERVER_CALL_VIA_WEB_SOCKET?new Promise(e=>oe?e():se?void setTimeout(()=>{ae().then(()=>{e()})},200):e(ue())):Promise.resolve(!0)}function ce(){window.SERVER_CALL_VIA_WEB_SOCKET&&function(){if(oe){oe=!1;try{ie.close()}catch(e){}}}()}function ue(){return se=!0,new Promise(e=>{ie="https:"===window.location.protocol?new WebSocket(`wss://${window.location.host}/sdc_ws/ws/`):new WebSocket(`ws://${window.location.host}/sdc_ws/ws/`),ie.onmessage=function(e){de(JSON.parse(e.data))},ie.onclose=function(){oe&&console.error("SDC Socket closed unexpectedly"),oe=!1;for(const[e,t]of Object.entries(le))t[1]({}),delete le[e];setTimeout(()=>{ue()},1e3)},ie.onerror=function(e){if(console.error("Socket encountered error: ",e.message,"Closing socket"),oe)try{ie.close()}catch(e){}},ie.onopen=function(){oe=!0,se=!1,e()}})}function de(e){if(e||(e={}),e.is_error)(e.msg||e.header)&&A("pushErrorMsg",e.header||"",e.msg||""),e.id&&le[e.id]&&(le[e.id][1](e.data||null),delete le[e.id]);else if((e.msg||e.header)&&A("pushMsg",e.header||"",e.msg||""),e.type&&"sdc_recall"===e.type)e.id&&le[e.id]&&(le[e.id][0](e.data),delete le[e.id]);else if(e.type&&"sdc_event"===e.type){let t=e.event;t&&A(t,e.payload)}else e.type&&"sdc_redirect"===e.type&&A("onNavLink",e.link)}class he{constructor(){this._uuid=d(),this.contentUrl="",this.contentReload=!1,this.parsedContentUrl=null,this.events=[],this.load_async=!1,this._isEventsSet=!1,this._allEvents=null,this._urlParams=[],this._models=[],this._cssUrls=[],this.afterShow=()=>{console.warn("afterShow is deprecated!!")},this._mixins={},this._tagName=Object.getPrototypeOf(this)._tagName??"",this._childController={},this._parentController=null,this._onLoadDone=!1,this.$container=null,this._isMixin=!1}_runLifecycle(e,t){_e.DEBUG&&!this._isMixin&&console.debug(e,this._tagName);let n=[];if(!this._isMixin){this._isMixin=!0;for(let r in this._mixins){let o=this._mixins[r];"function"==typeof o[e]&&n.push(o[e].apply(this,t))}return Promise.all(n).then(()=>{this._isMixin=!1})}}onInit(){_e.DEBUG&&!this._isMixin&&console.DEBUG(Array.apply(null,arguments),this._tagName)}get parentController(){return this._parentController}get childController(){return this._childController}onLoad(){return this._runLifecycle("onLoad",arguments)}willShow(){return this._runLifecycle("willShow",arguments)}onRefresh(){return this._runLifecycle("onRefresh",arguments)}onRemove(){return this._runLifecycle("onRemove",arguments),!0}noOpenModelRequests(){return Promise.all(this._models.map(e=>e.noOpenRequests()))}remove(){for(const e of this._models)e.close();let e=this._childController;for(let t in e)if(e.hasOwnProperty(t))for(let n of e[t])if(!n.remove())return!1;if(!this.onRemove||this.onRemove()){M(this);const e=i(this._tagName);if(this._parentController._childController[e]){let t=this._parentController._childController[e];for(let e=0;e<t.length;e++)t[e]===this&&t.splice(e,1)}return this.$container.remove(),!0}return!1}controller_name(){return this._tagName.replace(/-./g,e=>` ${e[1].toUpperCase()}`).replace(/^./g,e=>`${e.toUpperCase()}`)}addEvent(e,t,n){this.getEvents(),this._allEvents[e]=this._allEvents[e]||{},this._allEvents[e][t]=n}getEvents(){if(this._allEvents)return this._allEvents;let e=[];e=e.concat(this.events);for(let t in this._mixins){let n=this._mixins[t];Array.isArray(n.events)&&(e=e.concat(n.events))}return this._allEvents=_.merge(...e)}post(e,t){return _e.post(this,e,t)}get(e,t){return _e.get(this,e,t)}submitForm(e,t,n){return _e.submitFormAndUpdateView(this,e,t,n)}serverCall(e,t){let n=this.contentUrl.match(/sdc_view\/([^/]+)/i);if(n&&!(n.length<2))return function(e,t,n,r,o){return window.SERVER_CALL_VIA_WEB_SOCKET?function(e,t,n,r){let o=d();return ae().then(()=>{ie.send(JSON.stringify({event:"sdc_call",id:o,controller:t,app:e,function:n,args:r}))}),new Promise((e,t)=>{le[o]=[e,t]})}(e,t,r,o):function(e,t,n){return"object"!=typeof n&&Array.isArray(n)&&null===n&&(n={arg0:n}),n={data:JSON.stringify(n),_sdc_func_name:t,_method:"sdc_server_call"},$.post({url:e,data:n,beforeSend:function(e,t){e.setRequestHeader("X-CSRFToken",window.CSRF_TOKEN)}}).then(e=>{const t=e._return_data;return de(t),t}).catch(e=>{const t=e.responseJSON;throw t.is_error=!0,de(t),e})}(n,r,o)}(n[1],this._tagName,this.parsedContentUrl??this.contentUrl,e,t);console.error("To use the serverCall function the contentUrl must be set: "+this.name)}newModel(e,t={}){if(e instanceof Number&&e.hasOwnProperty("load"))return e.load(this);if(e instanceof re)return e;const n=new re(e,t);return this._models.push(n),n}updateModel(e,t={},n){let r=new re(e,t);return r.load().then(()=>{r.values|=n,r.save().then(()=>(r.close(),r.values))})}find(e){return this.$container.find(e)}refresh(){return _e.refresh(this.$container,this)}reload(){return _e.reloadController(this)}reconcile(e,t=null){return _e.reconcile(this,e,t)}submitModelFormDistributor(e,t){return"function"==typeof this._submitModelForm?this._submitModelForm(e,t):"function"==typeof this.submitModelForm?this.submitModelForm(e,t):this.defaultSubmitModelForm(e,t)}iterateAllChildren(){let e=this._childController,t=[];for(let n in e)if(e.hasOwnProperty(n))for(let r of e[n])t.push(r),t.push(...r.iterateAllChildren());return t}defaultSubmitModelForm(e,t){let n=[];if(!this._isMixin){t.stopPropagation(),t.preventDefault();let{model:r,form_name:o}=e.data();const s=r.syncForm(e);for(let t of s)n.push(new Promise((n,s)=>{let i;i=null!==t.pk&&t.pk>=0?r.save(t.pk,o):r.create(t),i.then(t=>{h(e),this.submit_model_form_success&&this.submit_model_form_success(t[0]);for(const e of this.iterateAllChildren())e.submit_model_form_success&&e.submit_model_form_success(t[0]);n(t)}).catch(t=>{this.reconcile($(`<div class="container-fluid">${t.html}</div>`),e.find(".container-fluid").first()),this.submit_model_form_error&&this.submit_model_form_error(t);for(const e of this.iterateAllChildren())e.submit_model_form_error&&e.submit_model_form_error(t);s(t)})}))}return Promise.all(n).then(e=>Object.assign({},...e.flat()))}}const pe={classname:"class"};window.sdcDom=function(e,t,...n){if(!e)return"";const r=function(e,t){let n,r=!1;if("string"==typeof e)n=$(document.createElement(e));else{const t=`this.${e.name}`;n=$(document.createElement(t)),n.data("handler",e),r=!0}return t&&Object.entries(t).forEach(([e,t])=>{e.startsWith("on")?n[0].addEventListener(e.substring(2).toLowerCase(),t):(pe[e.toLowerCase()]&&(e=pe[e.toLowerCase()]),n[0].setAttribute(e,t))}),r&&n.addClass("_bind_to_update_handler _with_handler"),n}(e,t);for(const e of n)r.append(e);return r};let fe,_e={CSRF_TOKEN:window.CSRF_TOKEN||"",LANGUAGE_CODE:window.LANGUAGE_CODE||"en",DEBUG:window.DEBUG||!1,VERSION:window.VERSION||"0.0",tagNames:[],Global:b,rootController:null,globalRootController:null,_isInit:!1,_origin_trigger:null,_globalControllerClasses:[],init_sdc:()=>{_e._isInit||(_e._isInit=!0,_e._origin_trigger?ce():(_e._origin_trigger=$.fn.trigger,$.fn.trigger=function(e){const t={}.hasOwnProperty.call(e,"type")?e.type:e;return v.includes(t)||(v.push(t),$(window).on(t,O)),_e._origin_trigger.call(this,e)},_e.updateJquery()),ae(),function(){const e=$(window);v.forEach(t=>{e.off(t).on(t,O)})}(),_e.rootController=_e.rootController||new he,_e.globalRootController=_e.globalRootController||new he),_e.tagNames=Object.keys(k);const e=$("<div></div>");b.forEach(t=>{const n=k[t][0];e.append(`<${n.prototype._tagName}></${n.prototype._tagName}>`)});const{refreshProcess:n}=x(null,_e.rootController);return H(_e.tagNames,e,_e.globalRootController,n).then(()=>H(_e.tagNames,(t||(t=$("body")),t),_e.rootController,n).then(e=>(R(n),e)))},updateJquery:()=>{$.fn.safeReplace=function(e){return _e.safeReplace($(this),e)},$.fn.safeEmpty=function(){return _e.safeEmpty($(this))},$.fn.safeRemove=function(){return _e.safeRemove($(this))}},controllerToTag:e=>s(e.name).replace(/-controller$/,""),registerGlobal:e=>{_e.register(e);let t=e.prototype._tagName;b.push(t)},cleanCache:()=>{I={}},register:(e,t=!1)=>{let n=_e.controllerToTag(e);return!k[n]||t?(k[n]=[e,[]],e.prototype._tagName=n,{addMixin:(...e)=>{for(let t of e){let e;"string"==typeof t?e=s(t):t&&(e=_e.controllerToTag(t)),k[n][1].push(e)}}}):{addMixin:(...e)=>{}}},post:(e,t,n)=>(n||(n={}),n.CSRF_TOKEN=_e.CSRF_TOKEN,_e.ajax(e,t,params,$.post)),get:(e,t,n)=>_e.ajax(e,t,n,$.get),ajax:(e,t,n,r)=>{n||(n={}),n.VERSION=_e.VERSION,n._method=n._method||"api";const o=new Promise((s,i)=>r(t,n).then((t,n,r)=>{s(t,n,r),"redirect"===t.status?A("onNavLink",t["url-link"]):o.then(()=>{_e.refresh(e.$container)})}).catch(i));return o},submitFormAndUpdateView:(e,t,n,r)=>{let o=new FormData(t);const s=e=>{e["url-link"]?A("onNavLink",e["url-link"]):window.location.href=e.url},i=new Promise((l,c)=>{a(o,n||t.action,r||t.method).then((t,n,r)=>{l(t,n,r),"redirect"===t.status?s(t):i.then(()=>{_e.refresh(e.$container,e)})}).catch((e,t,n)=>{301===e.status?(e=e.responseJSON,s(e),l(e,t,n)):c(e,t,n)})});return i},submitForm:(e,t,n)=>{let r=new FormData(e);return new Promise((o,s)=>{a(r,t||e.action,n||e.method).then(o).catch(s)})},getController:e=>W(e),safeEmpty:e=>(e.children().each(function(e,t){let n=$(t);_e.safeRemove(n)}),e),safeReplace:(e,t)=>(t.insertBefore(e),_e.safeRemove(e)),safeRemove:e=>(e.each(function(){let e=$(this);e.data(`${L}`)&&e.data(`${L}`).remove()}),e.find(`.${J}`).each(function(){const e=$(this).data(`${L}`);e&&e.remove()}),e.remove()),reloadController:e=>function(e){if(e.contentUrl){let t=B(e);return e.contentUrl=t.url,D(e,t.args)}return new Promise(e=>{e($())})}(e).then(t=>{let n=$(t);return _e.reconcile(e,n)}),reconcile:(e,t,n=null,r=null)=>{if(!n){let n=e.$container.clone().empty();n.data(L,null),n.removeClass(J),n.append(t),t=n}n=n??e.$container;const{refreshProcess:o,isRunningProcess:s}=x(r,e);return Z(t,e,o).then(()=>{var r;return z(t,n),(r=e)._childController={},r.find(`.${J}`).each(function(){const e=W($(this));e===r&&N(r,e)}),s||R(o),e})},refresh:(e,t)=>Z(e,t)},me=[];function ge(){if(!jest)throw new Error("JEST is not defined");0===me.length&&(fe=$.ajax.bind($),me.push(jest.spyOn($,"ajax")),me[0].mockImplementation(function(e){return fe(e).then(e=>e).catch(e=>e)}))}const ye=ce,ve={get_controller:async function(e,t={},n=""){ge();const r=$("body");_e.updateJquery(),r.safeEmpty();const o=$(`<${e}>${n}</${e}>`);for(const[e,n]of Object.entries(t))o.data(e,n);const s=$("<div></div>").append(o);return r.append(s),_e._isInit=!1,_e.cleanCache(),await _e.init_sdc(),_e.getController(o)},getCsrfToken:function(){return function(e){if(document.cookie&&""!==document.cookie){const t=document.cookie.split(";");for(let n=0;n<t.length;n++){const r=t[n].trim();if(r.substring(0,10)===e+"=")return decodeURIComponent(r.substring(10))}}return""}("csrftoken")},controllerFromTestHtml:async function(e,t=null){ge();const n=$("body");_e.updateJquery(),n.safeEmpty().append(e),_e._isInit=!1,_e.cleanCache(),await _e.init_sdc();let r=_e.rootController.iterateAllChildren();if(!t)return r;const o=r[0].onRefresh,s=jest.spyOn(r[0],"onRefresh");return new Promise(e=>{s.mockImplementation(function(){s.mockRestore();const t=o.apply(r[0],arguments);return e(r),t})})}};export{he as AbstractSDC,M as allOff,_e as app,u as checkIfParamNumberBoolOrString,h as clearErrorsInForm,S as controllerFactory,j as on,E as runControlFlowFunctions,p as setErrorsInForm,T as setEvent,ye as socketReconnect,ve as test_utils,A as trigger};
|
|
1
|
+
var e={};let t;e.d=(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);const n=/(?<=^|,\s?)[^=\s,]+/g,r=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;function s(){return new Promise(function(e){e()})}function o(e){return(e=(e=e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)).replace(/[0-9]+/g,e=>`-${e}`)).replace(/^[-]/g,"")}function i(e){return e.replace(/-./g,e=>`${e[1].toUpperCase()}`)}const l=(e,t)=>{let n=t,r=Object.getOwnPropertyNames(e.prototype).concat(Object.getOwnPropertySymbols(e.prototype));for(;""!==n.name;)Object.getOwnPropertyNames(n.prototype).concat(Object.getOwnPropertySymbols(n.prototype)).forEach(t=>{t.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)||r.includes(t)||(r.push(t),Object.defineProperty(e.prototype,t,Object.getOwnPropertyDescriptor(n.prototype,t)))}),n=Object.getPrototypeOf(n)};function a(e,t,n){return $.ajax({url:t,type:n||"POST",xhr:function(){var e=$.ajaxSettings.xhr();return e.upload&&e.upload.addEventListener("progress",c,!1),e},data:e,cache:!1,contentType:!1,processData:!1,beforeSend:function(e,t){(function(e){return/^(GET|HEAD|OPTIONS|TRACE)$/.test(e)})(t.type)||this.crossDomain||e.setRequestHeader("X-CSRFToken",window.CSRF_TOKEN)}})}function c(e){if(e.lengthComputable){var t=Math.round(e.loaded/e.total*100),n=$(".progress-container");100===t?n.hide():n.show(),t+="%",n.find(".progress-bar").css({width:t}).text(t)}}function u(e,t=null){return"string"!=typeof e?e:t&&void 0!==t[e]?"function"==typeof t[e]?t[e].bind(t):t[e]:e.match(/^(true|false)$/)?"true"===e:"undefined"!==e?"none"===e.toLowerCase()?null:e.match(/^-?\d+$/)?parseInt(e):e.match(/^-?\d+\.?\d+$/)?parseFloat(e):e.match(/^(['][^']*['])|(["][^"]*["])$/)?e.substr(1,e.length-2):e:void 0}function d(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16))}function h(e){e.find(".has-error").removeClass("has-error").find(".alert-danger").remove(),e.find(".non-field-errors").remove()}function f(e,t){t=$("<div>").append(t),e.find(".has-error").removeClass("has-error").find(".alert-danger").safeRemove(),e.find(".non-field-errors").safeRemove();let n=t.find("input[type=file]").parent();e.find("input[type=file]").parent().each(function(e){$(this).replaceWith(n[e])});let r=0===t.find(".non-field-errors").insertAfter(e.find(".hidden-form-fields")).length;return t.find(".has-error").each(function(){r=!1;let t=$(this),n=t.data("auto-id"),s=e.find(".form-group."+n);s.addClass("has-error"),s.find(".form-input-container").append(t.find(".alert-danger"))}),r}function m(e){let{type:t,name:n}=e;return n&&""!==n?"hidden"===t?(r=$(e).val()).toLowerCase().match(/^(true|false)$/)?"true"===r.toLowerCase():"undefined"!==r?"none"===r.toLowerCase()?null:r.match(/^-?\d+$/)?parseInt(r,10):r.match(/^-?\d+\.?\d+$/)?parseFloat(r):r.match(/^(['][^']*['])|(["][^"]*["])$/)?r.substring(1,r.length-1):r:void 0:"checkbox"===t?e.checked:"file"===t?e.files[0]:$(e).val():null;var r}function p(e,t){let{type:n,name:r}=e;if(r&&""!==r)if("checkbox"===n)e.checked=t;else if("file"===n){if(t instanceof File&&"undefined"!=typeof DataTransfer){let n=new DataTransfer;n.items.add(t),e.files=n}}else t instanceof ue?$(e).val(t.id):t instanceof ce?$(e).val(`[${t.getIds().join(",")}]`):$(e).val(t);return null}function y(e,t=[]){return Object.fromEntries(Object.entries(e).filter(([e,n])=>e!==D&&!t.includes(e)).map(([e,t])=>{if("string"==typeof t&&t.startsWith("SDC_JSON_MODEL=["))try{const{pk:n,fields:r}=JSON.parse(t.slice(16,-1));return[e,{...r,id:n,pk:n}]}catch{}return[e,t]}))}function g(e,t,n=null){let r=function(e,t){let n;e||(e=[]);let r=t.data(),s=y(r,e);n=[];for(let t=0;t<e.length;t++){let s=e[t];r.hasOwnProperty(s)?n.push(r[s]):n.push("undefined")}return n.push(s),n}(e,t);return function(e,t=null){let n=[];for(let r=0;r<e.length;r++){let s=u(e[r],t);n.push(s)}return n}(r,n)}function v(e,t,s){if(!e)return!1;if("function"!=typeof e.onInit)return!1;let o;var i;"function"==typeof e._on_init_params?o=e._on_init_params():o=(i=e.onInit.toString().replace(r,"")).slice(i.indexOf("(")+1,i.indexOf(")")).match(n)||[];let l=g(o,t,s._parentController);if(e.onInit.apply(s,l),s===e)for(let n in e._mixins)v(e._mixins[n],t,s)}const w=["onbeforeunload","onunload"],b=Object.keys(window).filter(e=>/^on/.test(e)&&!w.includes(e)).map(e=>e.slice(2));function R(e,t,n){let r;switch(e){case"mouseleave":case"mouseout":case"mouseenter":case"mousein":return r=document.elementFromPoint(t.clientX,t.clientY),n!==r&&!n.contains(r);default:return!0}}function C(e){let t=e.type;e.hasOwnProperty("namespace")&&e.namespace&&e.namespace.length&&(t+=`.${e.namespace}`);let n=$(e.target),r=null,s=!1,o=!1;for(e.stopImmediatePropagation=()=>o=!0,e.stopPropagation=()=>o=s=!0;n.length;){let i=n.attr(`sdc_${t}`);if(i&&R(t,e,n[0])){if(!r&&(r=G(n),!r))return;for(;r;){if(i.split(" ").forEach(o=>{if(s)return;let i=null;if("function"==typeof o)i=o;else if("function"==typeof r[o])i=r[o];else if("string"==typeof o&&o.startsWith("this.event_")){if(i=r.getEvents()[t],!i)return;if(i=i[o.slice(11)],!i)return}i&&i.call(r,n,e)}),o)return;r=r._parentController}}if(s)return;n=n.parent()}return{res:!0}}function N(e){if(e._isEventsSet)return;const t=e.getEvents();for(let n in t)if(t.hasOwnProperty(n)){let r=t[n];for(let t in r)r.hasOwnProperty(t)&&e.find(t).each(function(){let e=$(this),r=e.attr(`sdc_${n}`)||null;r=r?r.split(" "):[];const s=`this.event_${t}`;-1===r.indexOf(s)&&(r.push(s),e.attr(`sdc_${n}`,r.join(" ")))})}}let O=[],S={};function k(e,t){if(e){let n=i(t._tagName);e._childController[n]||(e._childController[n]=[]),e._childController[n].push(t)}return t._parentController=e}function E(e,t,n,r){let s=[];r=function(e,t){e=(e=e.concat(S[t][1])).filter((e,t,n)=>n.indexOf(e)===t);let n=!0;for(;n;){n=!1;for(let t of e)for(let r of S[t][1])e.includes(r)||(e.push(r),n=!0)}return e}(r,n);for(let e of r)s.push(S[e][0]);let o=new(function(e,...t){let n={[e.name]:class{constructor(...n){let r={};t.forEach(e=>{let t;Object.assign(this,t=new e),t._tagName=e.prototype._tagName,t._isMixin=!0,r[e.name]=t}),Object.assign(this,new e),this._mixins=r}static get name(){return e.name}static className(){return this.name}get mixins(){return this._mixins}}}[e.name];return l(n,e),t.forEach(e=>{l(n,e)}),n}(S[n][0],...s));return o._tagName=n,k(e,o),o.$container=t,function(e,t){v(t,e,t)}(t,o),o}function F(e,t,n,r){if(O.includes(n)){let s=i(n);return window[s]||(window[s]=E(e,t,n,r)),window[s].$container=t,window[s]}return E(e,t,n,r)}function P(e,t){const n=function(e){return function(e){let t={args:{}};return e.contentUrl&&(t=W(e),e.contentUrl=t.url),Promise.all([Q(e,t.args)]).then(function(e){let t=e[0];if(t)try{return $(t)}catch{return $("<div></div>").append(t)}return null})}(e).then(t=>!e.onLoad||e._onLoadDone?t:(e._onLoadDone=!0,(e.onLoad(t)||s()).then(()=>t)))}(e).then(function(t){return function(e,t){return K(e,t).then(function(t){if(t=t||!0,e.willShow){let n=e.willShow();if(n instanceof Promise)return n.then(function(){return t})}return t})}(e,t)}).then(()=>function(e,t){return ee(null,e,t)}(e,t)).catch(function(t){return K(e,t)});return e.load_async?Promise.resolve():n}function x(e){const t=function(e){let t=[];for(;e;)e._isEventsSet=!1,t.unshift(e),e=e._parentController;return t}(e.controller[0]),n=t.concat(e.controller.slice(1));for(let t of n)N(t),t.onRefresh(e.controller[0])}function I(e,t){let n=Boolean(e);return n?e.uuids.has(t._uuid)||(e.uuids.add(t._uuid),e.controller.push(t)):e={uuids:new Set([t._uuid]),controller:[t]},{isRunningProcess:n,refreshProcess:e}}let L={},j={};function q(e,t){return T(e),j.hasOwnProperty(e)?t[j[e]]?void L[e].push(t):console.log("No event handler: "+e,t):console.log("No event: "+e,t)}function T(e,t){t||(t=e),j[e]||(j[e]=t,L[e]=[])}function M(e){for(let t in L)if(L.hasOwnProperty(t))for(let n=L[t].length-1;n>=0;n--)e===L[t][n]&&L[t].splice(n,1)}function A(e){let t=Array.apply(null,arguments);if(e=t.shift(),!L.hasOwnProperty(e)||!j.hasOwnProperty(e))return s();let n=L[e],r=j[e],o=[];for(let e=0;e<n.length;e++){let s=n[e][r].apply(n[e],t);void 0!==s&&o.push(s)}return Promise.all(o)}let U={};const D="_controller_",J="_sdc_controller_";function V(e,t,n){if(!e)return[];let r=e.children(),s=[];return r.each(function(e,r){let o=$(r),i=o.prop("tagName").toLowerCase().split("_");$.inArray(i[0],t)>=0?s.push({tag:i[0],super:i.splice(1)||[],dom:o}):i[0].startsWith("this.")?o.addClass(`_bind_to_update_handler sdc_uuid_${n._uuid}`):s=s.concat(V(o,t,n))}),s}function Q(e,t){const{contentUrl:n,_tagName:r,contentReload:s}=e;return n?U[r]?Promise.resolve(U[r]):(t.VERSION=$e.VERSION,t._method="content",$.get(n,t).then(function(e){return s||(U[r]=e),e}).catch(function(t){throw 301===t.status&&A("_RedirectOnView",t.responseJSON["url-link"]),"function"==typeof e.confirmPageLoaded&&A("navLoaded",{controller_name:()=>t.status}),`<sdc-error data-code="${t.status}">${t.responseText}</sdc-error>`})):Promise.resolve(!1)}function B(e,t,n=null){return t=t||e.data(D),H($e.tagNames,e,t,n)}function W(e){let t=e.contentUrl;if(e&&0===e._urlParams.length){let n,r=/%\(([^)]+)\)\w/gm;for(e._urlParams=[];n=r.exec(t);)e._urlParams.push(n[1]),e.contentReload=!0}let n=function(e,t){return g(e._urlParams,t)}(e,e.$container);return e._urlParams.length&&(t=function(e,t,n){for(let r in e._urlParams)if(e._urlParams.hasOwnProperty(r)){let s=e._urlParams[r],o=RegExp("%\\("+s+"\\)\\w","gm");t=t.replace(o,""+n.shift())}return t}(e,t,n)),e.parsedContentUrl=t,{url:t,args:n[n.length-1]}}function G(e){return e.hasClass(J)?e.data(`${D}`):e.closest(`.${J}`).data(`${D}`)}function z(e,t,n,r,s){let o=e.data(D);return o?B(e,o,s):(o=F(r,e,t,n),e.data(D,o),e.addClass(J),P(o,s))}function K(e,t,n=null){if(t&&t.length>0){e.$container.empty(),e.$container.attr(e._tagName,"");for(let t in e._mixins)e.$container.attr(e._mixins[t]._tagName,"");e.$container.append(t)}return B(e.$container,e,n)}function H(e,t,n,r){return new Promise(s=>{let o=V(t,e,n),i=o.length;if(0===i)return s();for(let e=0;e<o.length;e++)z(o[e].dom,o[e].tag,o[e].super,n,r).then(()=>{if(i--,0===i)return s()})})}function X({$element:e,id:t=[],parent:n=null}){t.push(function(e){if(3===e[0].nodeType)return`TEXT__${e[0].nodeValue}`;const t=[e[0].tagName];return"INPUT"===e[0].nodeName&&[["name",""],["type","text"],["id",""]].forEach(([n,r])=>{const s=e.attr(n)??r;s&&t.push(s)}),t.join("__")}(e));const r={$element:e,id:t.join("::"),depth:t.length,idx:0,getRealParent:()=>n,getIdx:function(){return this.idx=Math.max(0,(this.getRealParent()?.getIdx()??-1)+e.index()+1),this.idx},op:null,parent:n};return r.getIdx.bind(r),[r].concat(e.contents().toArray().map(e=>X({$element:$(e),id:t.slice(),parent:r})).flat())}function Y(e,t){const n=X({$element:t}),r=X({$element:e});n.map((e,t)=>e.idx=t),r.map((e,t)=>e.idx=t);const s=Math.max(...r.concat(n).map(e=>e.depth)),o=Z(n,r,s);let i=[];window.MAIN=t,window.OPS=o,o.forEach((e,t)=>{const{op:n,$element:r,idx:s}=e;if("keep_counterpart"===n.type){if(n.counterpart.getIdx()!==s){const t=e.getBefore();t?n.counterpart.$element.insertAfter(t.$element):e.getRealParent().$element.prepend(n.counterpart.$element)}!function(e,t){const n=e[0].attributes??[],r=t[0].attributes??[];[...n].forEach(n=>{t.is(`[${n.name}]`)||e.removeAttr(n.name)}),[...r].forEach(t=>{t.name.startsWith("data")||e.attr(t.name)===t.value||e.attr(t.name,t.value)}),e.removeData(),Object.entries(t.data()).forEach(([t,n])=>{e.data(t,n)})}(n.counterpart.$element,r),r.hasClass(J)&&(r.data(D).$container=n.counterpart.$element,r.data(D,null)),i.push(r)}else if("delete"===n.type)r.safeRemove();else if("insert"===n.type){const{after:t,target:n}=e.op;t?r.insertAfter(t.$element):n&&n.$element.prepend(r)}}),i.forEach(e=>e.safeRemove())}function Z(e,t,n){return t.filter(e=>e.depth===n&&!e.op).forEach(t=>{const n=e.find(e=>!e.op&&e.id===t.id);if(n){const e=(t,n)=>{t.op={type:"keep",idx:n.idx},n.op={type:"keep_counterpart",counterpart:t},t=t.parent,n=n.parent,!t||t.op||n?.op||e(t,n)};e(n,t)}}),n>1?Z(e,t,n-1):(e.forEach((t,n)=>{if(!t.op){const r=(e[n-1]?.op.idx??-1)+1;t.op={type:"delete",idx:r}}}),t.forEach((e,n)=>{if(e.getBefore=()=>function(e,n){const r=e.depth;for(;n>=1&&e.depth>=r;)if((e=t[n-=1]).depth===r)return"keep_counterpart"===e.op.type?e.op.counterpart:e;return null}(e,n),e.getRealParent=()=>{return(t=e).parent?"keep_counterpart"===t.parent.op.type?t.parent.op.counterpart:t.parent:null;var t},!e.op){const t=e.getRealParent(),n="insert"===t?.op.type?"insert_ignore":"insert";e.op={type:n,target:t,after:e.getBefore()}}}),[...e,...t].sort((e,t)=>(e.op?.idx??e.idx)-(t.op?.idx??t.idx)))}function ee(e,t,n=null){if(t||(t=G(e)),!t)return Promise.resolve();const{refreshProcess:r,isRunningProcess:s}=I(n,t);return e??=t.$container,H($e.tagNames,e,t,n).then(()=>function(e,t,n){return function(e,t,n){const r=[];return t.find(`._bind_to_update_handler.sdc_uuid_${e._uuid}`).each(function(){const t=$(this);let s;if(t.hasClass("_with_handler"))s=t.data("handler");else{let t=this.tagName.toLowerCase().replace(/^this./,"");e[t]&&(s=e[t])}if("function"==typeof s){const n=y(t.data());s=s.bind(e)(n)}s&&r.push(Promise.resolve(s).then(r=>{let s=$("<div></div>");return s.append(r),""===t.html()&&t.append("<div></div>"),$e.reconcile(e,s,t.children(),n)}))}),Promise.all(r)}(e,t??e.$container,n)}(t,e,r).then(()=>{s||x(r)}))}const te={};function ne(e,t){te[e]=t}function re(e){return te[e]}const se=25e3,oe="_connecting_process";class ie extends Error{constructor(e){"string"==typeof e&&(e={msg:e,is_error:!0,header:"Error"}),super(e.msg),this.msg=e.msg,this.header=e.header,this.is_error=e.is_error,this.html=e.html}}class le{constructor({name:e,url:t}){this.name=e,this.url=t,this.content=null}async load(e=!1){return!e&&this.content||(this.content=await new Promise((e,t)=>{$.get(this.url).then(e).catch(()=>{e("")})})),this.content}async text(){return this.load()}static isValid(e){return!(null===e||"object"!=typeof e||Array.isArray(e)||e instanceof File||"string"!=typeof e.url||"string"!=typeof e.name)}}function ae(e){const t=parseInt(e??-1,10);return Number.isNaN(t)?-1:t}class ce{constructor(e,t={}){return this.valuesList=[],this.loaded=!1,this.modelName=e,this.modelQuery=t??{},this._onNoOpenRequests=[],this._isConnected=!1,this._isConnectingProcess=!1,this._autoReconnect=!0,this.socket=null,this.openRequest={},this.modelId=0,this.onUpdate=()=>{},this.onCreate=()=>{},new Proxy(this,{get:(e,t)=>"symbol"==typeof t||isNaN(t)?e[t]:e.valuesList[t],set:(e,t,n)=>isNaN(t)?(e[t]=n,!0):(e.valuesList[t]=n,!0)})}[Symbol.iterator](){let e=-1;return{next:()=>(++e,e<this.valuesList.length?{value:this.valuesList[e],done:!1}:{value:null,done:!0})}}set on_create(e){this.onCreate=e}set on_update(e){this.onUpdate=e}setIds(e){if(null===e||Array.isArray(e)&&0===e.length)return this.valuesList=[],this.valuesList;if(e instanceof ce)return this.valuesList=structuredClone(e.valuesList),this.valuesList.forEach(e=>e._setQuerySet(this,!0)),this.valuesList;if(e instanceof ue)return this.valuesList=[structuredClone(e)],this.valuesList.forEach(e=>e._setQuerySet(this,!0)),this.valuesList;let t,n=Number.NaN;if(Number.isInteger(e))n=e;else if(e instanceof Array){const n=e.map(e=>parseInt(e));n.some(Number.isNaN)||(t=n)}else if(e instanceof String){const r=e.split(",").map(e=>parseInt(e));r.some(Number.isNaN)?n=parseInt(e):t=r}if(Number.isNaN(n)){if(t){this.valuesList=this.valuesList.filter(e=>t.includes(e.id));const e=this.getIds();t.filter(t=>!e.includes(t)).forEach(e=>{this.valuesList.push(new(re(this.modelName))({id:e}))})}}else this.new().id=n;return this.valuesList}getIds(){return this.valuesList.map(e=>e.id)}get length(){return this.valuesList.length}byId(e){if(null!==e){const t=ae(e);return this.valuesList.find(e=>e.id===t)??null}return null}setFilter(e){return this.modelQuery=e,this}addFilter(e){return this.modelQuery=Object.assign({},this.modelQuery,e),this}new(e={}){const t=new(re(this.modelName))(e);return t._setQuerySet(this,!1),this.valuesList.push(t),t}load(e=null){return this.valuesList=[],this.modelQuery=e??this.modelQuery,this._sendLoad()}update({modelQuery:e=null,item:t=null}){this.modelQuery=e??this.modelQuery;let n=t?{pk:t.id}:this.modelQuery;return this._sendLoad(n)}delete({pk:e=null,elem:t=null}){if(null===(e=t?t.id:e))throw new Error("pk or elem must be set");const n=d();return this.isConnected().then(()=>new Promise((t,r)=>{this.socket.send(JSON.stringify({event:"model",event_type:"delete",event_id:n,args:{model_name:this.modelName,model_query:this.modelQuery,pk:e}})),this.openRequest[n]=[t,r]}))}_sendLoad(e=null){return this.isConnected().then(()=>{const t=d();return new Promise((n,r)=>{this.socket.send(JSON.stringify({event:"model",event_type:"load",event_id:t,args:{model_name:this.modelName,model_query:e??this.modelQuery}})),this.openRequest[t]=[e=>{this.loaded=!0,n(e)},r]})})}_sendListView({modelQuery:e={},cbResolve:t=null,cbReject:n=null,templateContext:r={}}){return this.view({modelQuery:e,cbResolve:t,cbReject:n,templateContext:r,eventType:"list_view"})}view({viewName:e="html_list_template",modelQuery:t={},cbResolve:n=null,cbReject:r=null,templateContext:s={},eventType:o="named_view"}){let i=$('<div class="container-fluid">');return this.isConnected().then(()=>{const l=d();this.socket.send(JSON.stringify({event:"model",event_type:o,event_id:l,args:{view_name:e,model_name:this.modelName,model_query:t,template_context:s}})),this.openRequest[l]=[e=>{i.append(e.html),$e.refresh(i),n&&n(e)},e=>{r&&r(e)}]}),i}_sendDetailView({pk:e=null,cbResolve:t=null,cbReject:n=null,templateContext:r={}}){e=ae(e);let s=$('<div class="container-fluid">');return this.isConnected().then(()=>{const o=d();this.socket.send(JSON.stringify({event:"model",event_type:"detail_view",event_id:o,args:{model_name:this.modelName,model_query:this.modelQuery,pk:e,template_context:r}})),this.openRequest[o]=[e=>{s.append(e.html),$e.refresh(s),t&&t(e)},e=>{n&&n(e)}]}),s}getForm({modelObj:e,eventType:t,formName:n,$divForm:r,cbResolve:s,cbReject:o,formId:i}){const l=d(),a=e.id??-1;this.isConnected().then(()=>{this.socket.send(JSON.stringify({event:"model",event_type:t,event_id:l,args:{model_name:this.modelName,pk:a,form_name:n}}))});const c=null===a||-1===a?"create":"edit";this.openRequest[l]=[t=>{r.append(t.html);let o=r.closest("form").addClass(`sdc-model-${c}-form sdc-model-form ${i}`).data("model",e).data("model_pk",a).data("form_name",n);e.addForm(o),o.length>0&&!o[0].hasAttribute("sdc_submit")&&o.attr("sdc_submit","submitModelFormDistributor"),$e.refresh(r).then(()=>null),s&&s(t)},e=>{o&&o(e)}]}async get(e=null,t=!1){if(t){const t=e?.id??e?.pk;return this.byId(t)??this.new(e||this.modelQuery)}if(await this.load(e),1!==this.length)throw new Error(`model query returns ${this.length} but only 1 expected.`);return this.valuesList[0]}detailView({pk:e,cbResolve:t=null,cbReject:n=null,templateContext:r={}}){return this._sendDetailView({pk:e,cbResolve:t,cbReject:n,templateContext:r})}listView({modelQuery:e={},searchValues:t={},cbResolve:n=null,cbReject:r=null,templateContext:s={}}){return this._sendListView({modelQuery:Object.assign({},this.modelQuery,e,{__search_values:t}),cbResolve:n,cbReject:r,templateContext:s})}save({pk:e=null,formName:t="edit_form",data:n=null}={}){const r=ae(e);return this.isConnected().then(()=>{let e;if(r>-1){const t=this.byId(r);if(!t)return Promise.reject(new ie(`Element not found with ID: ${r}`));e=[t]}else e=this.valuesList;let s=[];return e.forEach(e=>{const r=d();s.push(new Promise((s,o)=>{this._readFiles(e).then(i=>{const l=n?{...n}:e.serialize();l.pk=e.id,this.socket.send(JSON.stringify({event:"model",event_type:"save",event_id:r,args:{form_name:t,model_name:this.modelName,model_query:this.modelQuery,data:l,pk:l.pk,files:i}})),this.openRequest[r]=[e=>{let t="string"==typeof e.data.instance?JSON.parse(e.data.instance):e.data.instance;e.data.instance=this._parseServerRes(t),s(e)},o]})}))}),Promise.all(s)})}create({elem:e=null,data:t=null}={}){const n=d();return e||(e=this.new(t)),this.isConnected().then(()=>new Promise((r,s)=>{this._readFiles(e).then(o=>{this.socket.send(JSON.stringify({event:"model",event_type:"create",event_id:n,args:{model_name:this.modelName,model_query:this.modelQuery,data:t??e?.serialize()??{},files:o}})),this.openRequest[n]=[t=>{let n="string"==typeof t.data.instance?JSON.parse(t.data.instance):t.data.instance;e&&(e.id=n[0]?.pk||n[0]?.id),t.data.instance=this._parseServerRes(n)[0],r(t)},s]})}))}isConnected(){return new Promise((e,t)=>{if(this._isConnected)e();else if(this._isConnectingProcess&&this.openRequest[oe]){const[n,r]=this.openRequest[oe];this.openRequest[oe]=[()=>{n(),e()},()=>{r(),t()}]}else this._isConnectingProcess=!0,this.openRequest[oe]=[()=>{},()=>{}],this._connectToServer().then(()=>{e(this._checkConnection())})})}close(){this.socket&&(this._autoReconnect=!1,this.socket.onclose=()=>{},this.socket.close(),delete this.socket),this.valuesList.forEach(e=>{e._onClose()})}_readFiles(e){let t=[],n={};return e?(Object.keys(e.constructor.fields).forEach(r=>{const s=e[r];s instanceof File&&t.push(new Promise(async(e,t)=>{const o=d();this.openRequest[o]=[e,t];const i=await s.arrayBuffer();let l=new Uint8Array(i),a=Math.ceil(l.length/se);n[r]={id:o,file_name:s.name,field_name:r,content_length:s.size};for(let e=0;e<a;++e){const t=Array.from(l.slice(se*e,se*(e+1)));this.socket.send(JSON.stringify({event:"model",event_type:"upload",event_id:o,args:{chunk:t,idx:e,number_of_chunks:a,file_name:s.name,field_name:r,content_length:s.size,content_type:s.type,model_name:this.modelName,model_query:this.modelQuery}}))}}))}),Promise.all(t).then(()=>n)):Promise.resolve(n)}async _onMessage(e){let t=JSON.parse(e.data);if(t.is_error)this.openRequest.hasOwnProperty(t.event_id)&&(this.openRequest[t.event_id][1](new ie(t)),this._closeOpenRequest(t.event_id)),(t.msg||t.header)&&A("pushErrorMsg",t.header||"",t.msg||""),"connect"===t.type&&(this.openRequest[oe][1](new ie(t)),this._closeOpenRequest(oe),this._autoReconnect=!1,this.socket.close());else{if((t.msg||t.header)&&A("pushMsg",t.header||"",t.msg||""),"connect"===t.type)this._isConnected=!0,this._isConnectingProcess=!1,this.openRequest[oe][0](t),this._closeOpenRequest(oe);else if(["load","named_view","detail_view"].includes(t.type)){const e=JSON.parse(t.args.data);t.args.data=await this._parseServerRes(e)}else if("on_update"===t.type||"on_create"===t.type){const e=JSON.parse(t.args.data);let n,r=await this._parseServerRes(e);n="on_create"===t.type?this.onCreate:this.onUpdate,n(r),t.args.data=r}let e=t.data?.instance;e&&(t.data.instance=JSON.parse(t.data.instance)),this.openRequest.hasOwnProperty(t.event_id)&&(this.openRequest[t.event_id][0](t),this._closeOpenRequest(t.event_id))}}noOpenRequests(){return new Promise(e=>{if(0===Object.keys(this.openRequest).length)return e();this._onNoOpenRequests.push(e)})}_closeOpenRequest(e){delete this.openRequest[e],0===Object.keys(this.openRequest).length&&(this._onNoOpenRequests.forEach(e=>e()),this._onNoOpenRequests=[])}_connectToServer(){return new Promise(e=>{const t=`${this.modelName}`+(this.modelId>0?`/${this.modelId}`:"");"https:"===window.location.protocol?this.socket=new WebSocket(`wss://${window.location.host}/sdc_ws/model/${t}`):this.socket=new WebSocket(`ws://${window.location.host}/sdc_ws/model/${t}`),this.socket.onmessage=this._onMessage.bind(this),this.socket.onclose=e=>{console.error(`SDC Model (${this.modelName}, ${this.modelId}) Socket closed unexpectedly`),this._isConnected=!1;for(const[t,n]of Object.entries(this.openRequest))n[1](e);this.openRequest={},setTimeout(()=>{this._autoReconnect&&this._connectToServer().then(()=>{})},1e3)},this.socket.onerror=e=>{if(console.error(`Model Socket encountered error: ${e} Closing socket`),this._isConnected)try{this.socket.close()}catch(e){}},this.socket.onopen=()=>{e()}})}_checkConnection(){const e=d();return new Promise((t,n)=>{this.socket.send(JSON.stringify({event:"model",event_type:"connect",event_id:e,args:{model_name:this.modelName,model_query:this.modelQuery}})),this.openRequest[e]=[t,n]})}_parseServerRes(e){const t=[];for(const n of e){const e=new(re(this.modelName))({id:n.pk??n.id,...n.fields}),r=this.byId(e.id);r?(r.setValues(e),t.push(r)):(this.valuesList.push(e),e._setQuerySet(this,!0),t.push(e))}return this.valuesList.sort((e,t)=>e._id-t._id),t}}class ue{static fields={};constructor(e){this._id=null,this._forms=[],this.loaded=!1,this.formId=d(),this.modelName=e}static querySet(e=null,t=null){return t?t.querySet(this.modeName,e):new ce(this.modeName,e)}addForm(e){const t=this._onChange.bind(this);this._forms.push(e),e&&e.on("input.formWatcher change.formWatcher","input, select, textarea",t)}_onChange(e){const{name:t}=e.target;this.constructor.fields[t]&&this[`set${t}`](m(e.target))}_updateForm(e){const t=this;this._forms.forEach(n=>{n.find(`[name="${e}"]`).each(function(){p(this,t[e])})})}setValues(e={}){throw new Error("setValues() must be implemented by subclass")}_setQuerySet(e,t){this._querySet=new WeakRef(e),this.loaded=t}save({formName:e="edit_form",data:t=null}={}){return this._querySet.deref().save({pk:this.id,formName:e,data:t})}create({data:e=null}={}){return this._querySet.deref().create({elem:this,data:e})}delete(){return this._querySet.deref().delete({elem:this})}load(){return this._querySet.deref().update({item:this})}get id(){return this._id}set pk(e){this._id=e}get pk(){return this._id}close(){return this._querySet.deref().close()}_onClose(){Object.keys(this.constructor.fields).filter(e=>this[`_${e}`]instanceof ce).forEach(e=>this[`_${e}`].close())}detailView({cbResolve:e=null,cbReject:t=null,templateContext:n={}}){return this._querySet.deref().detailView({pk:this.id,cbResolve:e,cbReject:t,templateContext:n})}serialize(){return Object.entries(this.constructor.fields).reduce((e,[t,n])=>{const r=this[t];if(r instanceof ce)if(n.many_to_many||n.one_to_many)e[t]=r.getIds();else{const n=r.getIds();n.length>0?e[t]=n[0]:e[t]=null}else n.is_relation&&r instanceof ue?e[t]=r.id??null:e[t]=r;return e},{})}toJson(){const e={};for(let t in this.constructor.fields){const n=this[t];n instanceof File?e[t]=n.name:e[t]=n instanceof ue?n.id:n instanceof ce?n.getIds():n}return e}syncFormToModel(e){return this.syncForm(e)}_resolveForms(e){return e&&e.hasClass(this.formId)?e:$(`.${this.formId}`)}syncModelToForm(e){e=this._resolveForms(e);const t=this,n=this.constructor.fields;e.each(function(){const e=ae($(this).data("model_pk"));if(t.id===e)for(let e of this.elements){let r=e.name;r&&""!==r&&n[r]&&p(e,t[r])}})}syncForm(e){e=this._resolveForms(e);const t=this,n=this.constructor.fields,r={};function s(e,s){n[e]&&(t[e]=s),r[e]=s}return e.each(function(){const e=ae($(this).data("model_pk"));if(t.id===e||null===t.id&&-1===e){for(let e of this.elements)s(e.name,m(e));return t}}),r}form({cbResolve:e=null,cbReject:t=null}){return null===this.id||-1===this.id?this._createForm({cbReject:t,cbResolve:e}):this._editForm({cbReject:t,cbResolve:e})}_createForm({cbResolve:e=null,cbReject:t=null}){let n=$("<div>");return this._querySet.deref().getForm({modelObj:this,eventType:"create_form",formName:null,$divForm:n,cbResolve:e,cbReject:t,formId:this.formId}),n}_editForm({cbResolve:e=null,cbReject:t=null}){let n=$("<div>");return this._querySet.deref().getForm({modelObj:this,eventType:"edit_form",formName:null,$divForm:n,cbResolve:e,cbReject:t,formId:this.formId}),n}namedForm({formName:e,cbResolve:t=null,cbReject:n=null}){let r=$('<div class="container-fluid">');return this._querySet.deref().getForm({modelObj:this,eventType:"named_form",formName:e,$divForm:r,cbResolve:t,cbReject:n,formId:this.formId}),r}validate(e,t){const n=function(e,t){const{type:n,required:r,max_length:s,is_relation:o,many_to_many:i,one_to_many:l,many_to_one:a,one_to_one:c,related_model:u}=t;if(r&&(null==e||""===e))return"This field is required";if(null==e)return null;if((i||l||a||c)&&(!(e instanceof ce)||e.modelName!==u))return"object"!=typeof e&&Number.isNaN(parseInt(e))?"Must be object or ID":null;switch(n){case"CharField":case"TextField":if("string"!=typeof e)return"Must be a string";if(s&&e.length>s)return`Max length is ${s}`;break;case"IntegerField":case"AutoField":case"BigIntegerField":if(!Number.isInteger(e))return"Must be an integer";break;case"FloatField":case"DecimalField":if("number"!=typeof e)return"Must be a number";break;case"BooleanField":if("boolean"!=typeof e)return"Must be a boolean";break;case"DateField":case"DateTimeField":if(isNaN(Date.parse(e)))return"Must be a valid date";break;case"EmailField":if("string"!=typeof e||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e))return"Invalid email";break;case"URLField":try{new URL(e)}catch{return"Invalid URL"}break;case"UUIDField":if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(e))return"Invalid UUID";break;case"JSONField":if("object"!=typeof e)return"Must be JSON object";break;case"FileField":if(!(le.isValid(e)||e instanceof File||e instanceof le))return"Must be a valid file"}return null}(e,t);if(n)throw new Error(n)}parseValue(e,t){const{type:n}=t;switch(n){case"CharField":case"TeextField":case"UUIDField":case"EmailField":return`${e}`;case"IntegerField":case"AutoField":case"BigIntegerField":return parseInt(e,10);case"FloatField":case"DecimalField":if("number"!=typeof e)return"Must be a number";break;case"BooleanField":return!!e;case"DateField":case"DateTimeField":return Date.parse(e);case"URLField":return new URL(e);case"FileField":if(null==e)return null;if("string"==typeof e)return null;if(le.isValid(e))return new le(e);if(e instanceof le)return new le(e);if("undefined"!=typeof File&&e instanceof File)return t.max_size&&e.size>t.max_size?`File too large (max ${t.max_size} bytes)`:t.allowed_types&&!t.allowed_types.includes(e.type)?`Invalid file type (${e.type})`:e;break;case"JSONField":if("object"==typeof e)return e;if("string"==typeof e)return JSON.parse(e)}return e}}let de=!1,he=!1,fe=null,me={};function pe(){return window.SERVER_CALL_VIA_WEB_SOCKET?new Promise(e=>de?e():he?void setTimeout(()=>{pe().then(()=>{e()})},200):e(ye())):Promise.resolve(!0)}function _e(){window.SERVER_CALL_VIA_WEB_SOCKET&&function(){if(de){de=!1;try{fe.close()}catch(e){}}}()}function ye(){return he=!0,new Promise(e=>{fe="https:"===window.location.protocol?new WebSocket(`wss://${window.location.host}/sdc_ws/ws/`):new WebSocket(`ws://${window.location.host}/sdc_ws/ws/`),fe.onmessage=function(e){ge(JSON.parse(e.data))},fe.onclose=function(){de&&console.error("SDC Socket closed unexpectedly"),de=!1;for(const[e,t]of Object.entries(me))t[1]({}),delete me[e];setTimeout(()=>{ye()},1e3)},fe.onerror=function(e){if(console.error("Socket encountered error: ",e.message,"Closing socket"),de)try{fe.close()}catch(e){}},fe.onopen=function(){de=!0,he=!1,e()}})}function ge(e){if(e||(e={}),e.is_error)(e.msg||e.header)&&A("pushErrorMsg",e.header||"",e.msg||""),e.id&&me[e.id]&&(me[e.id][1](e.data||null),delete me[e.id]);else if((e.msg||e.header)&&A("pushMsg",e.header||"",e.msg||""),e.type&&"sdc_recall"===e.type)e.id&&me[e.id]&&(me[e.id][0](e.data),delete me[e.id]);else if(e.type&&"sdc_event"===e.type){let t=e.event;t&&A(t,e.payload)}else e.type&&"sdc_redirect"===e.type&&A("onNavLink",e.link)}class ve{constructor(){this._uuid=d(),this.contentUrl="",this.contentReload=!1,this.parsedContentUrl=null,this.events=[],this.load_async=!1,this._isEventsSet=!1,this._allEvents=null,this._urlParams=[],this._models=[],this._cssUrls=[],this.afterShow=()=>{console.warn("afterShow is deprecated!!")},this._mixins={},this._tagName=Object.getPrototypeOf(this)._tagName??"",this._childController={},this._parentController=null,this._onLoadDone=!1,this.$container=null,this._isMixin=!1}_runLifecycle(e,t){$e.DEBUG&&!this._isMixin&&console.debug(e,this._tagName);let n=[];if(!this._isMixin){this._isMixin=!0;for(let r in this._mixins){let s=this._mixins[r];"function"==typeof s[e]&&n.push(s[e].apply(this,t))}return Promise.all(n).then(()=>{this._isMixin=!1})}}onInit(){$e.DEBUG&&!this._isMixin&&console.debug(Array.apply(null,arguments),this._tagName)}get parentController(){return this._parentController}get childController(){return this._childController}onLoad(){return this._runLifecycle("onLoad",arguments)}willShow(){return this._runLifecycle("willShow",arguments)}onRefresh(){return this._runLifecycle("onRefresh",arguments)}onRemove(){return this._runLifecycle("onRemove",arguments),!0}noOpenModelRequests(){return Promise.all(this._models.map(e=>e.noOpenRequests()))}remove(){for(const e of this._models)e.close();let e=this._childController;for(let t in e)if(e.hasOwnProperty(t))for(let n of e[t])if(!n.remove())return!1;if(!this.onRemove||this.onRemove()){M(this);const e=i(this._tagName);if(this._parentController._childController[e]){let t=this._parentController._childController[e];for(let e=0;e<t.length;e++)t[e]===this&&t.splice(e,1)}return this.$container.remove(),!0}return!1}controller_name(){return this._tagName.replace(/-./g,e=>` ${e[1].toUpperCase()}`).replace(/^./g,e=>`${e.toUpperCase()}`)}addEvent(e,t,n){this.getEvents(),this._allEvents[e]=this._allEvents[e]||{},this._allEvents[e][t]=n}getEvents(){if(this._allEvents)return this._allEvents;let e=[];e=e.concat(this.events);for(let t in this._mixins){let n=this._mixins[t];Array.isArray(n.events)&&(e=e.concat(n.events))}return this._allEvents=_.merge(...e)}post(e,t){return $e.post(this,e,t)}get(e,t){return $e.get(this,e,t)}submitForm(e,t,n){return $e.submitFormAndUpdateView(this,e,t,n)}serverCall(e,t){let n=this.contentUrl.match(/sdc_view\/([^/]+)/i);if(n&&!(n.length<2))return function(e,t,n,r,s){return window.SERVER_CALL_VIA_WEB_SOCKET?function(e,t,n,r){let s=d();return pe().then(()=>{fe.send(JSON.stringify({event:"sdc_call",id:s,controller:t,app:e,function:n,args:r}))}),new Promise((e,t)=>{me[s]=[e,t]})}(e,t,r,s):function(e,t,n){return"object"!=typeof n&&Array.isArray(n)&&null===n&&(n={arg0:n}),n={data:JSON.stringify(n),_sdc_func_name:t,_method:"sdc_server_call"},$.post({url:e,data:n,beforeSend:function(e,t){e.setRequestHeader("X-CSRFToken",window.CSRF_TOKEN)}}).then(e=>{const t=e._return_data;return ge(t),t}).catch(e=>{const t=e.responseJSON;throw t.is_error=!0,ge(t),e})}(n,r,s)}(n[1],this._tagName,this.parsedContentUrl??this.contentUrl,e,t);console.error("To use the serverCall function the contentUrl must be set: "+this.name)}querySet(e,t={}){const n=new ce(e,t);return this._models.push(n),n}newModel(e,t={}){return console.warn("newModel is deprecated and will soon be removed"),this.querySet(e,t)}find(e){return this.$container.find(e)}refresh(){return $e.refresh(this.$container,this)}reload(){return $e.reloadController(this)}reconcile(e,t=null){return $e.reconcile(this,e,t)}submitModelFormDistributor(e,t){return"function"==typeof this._submitModelForm?this._submitModelForm(e,t):"function"==typeof this.submitModelForm?this.submitModelForm(e,t):this.defaultSubmitModelForm(e,t)}iterateAllChildren(){let e=this._childController,t=[];for(let n in e)if(e.hasOwnProperty(n))for(let r of e[n])t.push(r),t.push(...r.iterateAllChildren());return t}defaultSubmitModelForm(e,t){if(!this._isMixin){t.stopPropagation(),t.preventDefault();let{model:n,form_name:r}=e.data(),s=n.syncForm(e);return new Promise((t,o)=>{let i;i=null!==n.id&&n.id>=0?n.save({formName:r,data:s}):n.create({data:s}),i.then(n=>{h(e),this.submit_model_form_success&&this.submit_model_form_success(n[0]);for(const e of this.iterateAllChildren())e.submit_model_form_success&&e.submit_model_form_success(n[0]);t(n)}).catch(t=>{this.reconcile($(`<div class="container-fluid">${t.html}</div>`),e.find(".container-fluid").first()),this.submit_model_form_error&&this.submit_model_form_error(t);for(const e of this.iterateAllChildren())e.submit_model_form_error&&e.submit_model_form_error(t);o(t)})})}return Promise.resolve()}}const we={classname:"class"};window.sdcDom=function(e,t,...n){if(!e)return"";const r=function(e,t){let n,r=!1;if("string"==typeof e)n=$(document.createElement(e));else{const t=`this.${e.name}`;n=$(document.createElement(t)),n.data("handler",e),r=!0}return t&&Object.entries(t).forEach(([e,t])=>{e.startsWith("on")?n[0].addEventListener(e.substring(2).toLowerCase(),t):(we[e.toLowerCase()]&&(e=we[e.toLowerCase()]),n[0].setAttribute(e,t))}),r&&n.addClass("_bind_to_update_handler _with_handler"),n}(e,t);for(const e of n)r.append(e);return r};let be,$e={CSRF_TOKEN:window.CSRF_TOKEN||"",LANGUAGE_CODE:window.LANGUAGE_CODE||"en",DEBUG:window.DEBUG||!1,VERSION:window.VERSION||"0.0",tagNames:[],Global:O,rootController:null,globalRootController:null,_isInit:!1,_origin_trigger:null,_globalControllerClasses:[],init_sdc:()=>{$e._isInit||($e._isInit=!0,$e._origin_trigger?_e():($e._origin_trigger=$.fn.trigger,$.fn.trigger=function(e){const t={}.hasOwnProperty.call(e,"type")?e.type:e;return b.includes(t)||(b.push(t),$(window).on(t,C)),$e._origin_trigger.call(this,e)},$e.updateJquery()),pe(),function(){const e=$(window);b.forEach(t=>{e.off(t).on(t,C)})}(),$e.rootController=$e.rootController||new ve,$e.globalRootController=$e.globalRootController||new ve),$e.tagNames=Object.keys(S);const e=$("<div></div>");O.forEach(t=>{const n=S[t][0];e.append(`<${n.prototype._tagName}></${n.prototype._tagName}>`)});const{refreshProcess:n}=I(null,$e.rootController);return H($e.tagNames,e,$e.globalRootController,n).then(()=>H($e.tagNames,(t||(t=$("body")),t),$e.rootController,n).then(e=>(x(n),e)))},updateJquery:()=>{$.fn.safeReplace=function(e){return $e.safeReplace($(this),e)},$.fn.safeEmpty=function(){return $e.safeEmpty($(this))},$.fn.safeRemove=function(){return $e.safeRemove($(this))}},controllerToTag:e=>o(e.name).replace(/-controller$/,""),registerGlobal:e=>{$e.register(e);let t=e.prototype._tagName;O.push(t)},cleanCache:()=>{U={}},register:(e,t=!1)=>{let n=$e.controllerToTag(e);return!S[n]||t?(S[n]=[e,[]],e.prototype._tagName=n,{addMixin:(...e)=>{for(let t of e){let e;"string"==typeof t?e=o(t):t&&(e=$e.controllerToTag(t)),S[n][1].push(e)}}}):{addMixin:(...e)=>{}}},post:(e,t,n)=>(n||(n={}),n.CSRF_TOKEN=$e.CSRF_TOKEN,$e.ajax(e,t,n,$.post)),get:(e,t,n)=>$e.ajax(e,t,n,$.get),ajax:(e,t,n,r)=>{n||(n={}),n.VERSION=$e.VERSION,n._method=n._method||"api";const s=new Promise((o,i)=>r(t,n).then((t,n,r)=>{o(t,n,r),"redirect"===t.status?A("onNavLink",t["url-link"]):s.then(()=>{$e.refresh(e.$container)})}).catch(i));return s},submitFormAndUpdateView:(e,t,n,r)=>{let s=new FormData(t);const o=e=>{e["url-link"]?A("onNavLink",e["url-link"]):window.location.href=e.url},i=new Promise((l,c)=>{a(s,n||t.action,r||t.method).then((t,n,r)=>{l(t,n,r),"redirect"===t.status?o(t):i.then(()=>{$e.refresh(e.$container,e)})}).catch((e,t,n)=>{301===e.status?(e=e.responseJSON,o(e),l(e,t,n)):c(e,t,n)})});return i},submitForm:(e,t,n)=>{let r=new FormData(e);return new Promise((s,o)=>{a(r,t||e.action,n||e.method).then(s).catch(o)})},getController:e=>G(e),safeEmpty:e=>(e.children().each(function(e,t){let n=$(t);$e.safeRemove(n)}),e),safeReplace:(e,t)=>(t.insertBefore(e),$e.safeRemove(e)),safeRemove:e=>(e.each(function(){let e=$(this);e.data(`${D}`)&&e.data(`${D}`).remove()}),e.find(`.${J}`).each(function(){const e=$(this).data(`${D}`);e&&e.remove()}),e.remove()),reloadController:e=>function(e){if(e.contentUrl){let t=W(e);return e.contentUrl=t.url,Q(e,t.args)}return new Promise(e=>{e($())})}(e).then(t=>{let n=$(t);return $e.reconcile(e,n)}),reconcile:(e,t,n=null,r=null)=>{if(!n){let n=e.$container.clone().empty();n.data(D,null),n.removeClass(J),n.append(t),t=n}n=n??e.$container;const{refreshProcess:s,isRunningProcess:o}=I(r,e);return ee(t,e,s).then(()=>{var r;return Y(t,n),(r=e)._childController={},r.find(`.${J}`).each(function(){const e=G($(this));e===r&&k(r,e)}),o||x(s),e})},refresh:(e,t=null)=>ee(e,t)},Re=[];function Ce(){if(!jest)throw new Error("JEST is not defined");0===Re.length&&(be=$.ajax.bind($),Re.push(jest.spyOn($,"ajax")),Re[0].mockImplementation(function(e){return be(e).then(e=>e).catch(e=>e)}))}const Ne=_e,Oe={get_controller:async function(e,t={},n=""){Ce();const r=$("body");$e.updateJquery(),r.safeEmpty();const s=$(`<${e}>${n}</${e}>`);for(const[e,n]of Object.entries(t))s.data(e,n);const o=$("<div></div>").append(s);return r.append(o),$e._isInit=!1,$e.cleanCache(),await $e.init_sdc(),$e.getController(s)},getCsrfToken:function(){return function(e){if(document.cookie&&""!==document.cookie){const t=document.cookie.split(";");for(let n=0;n<t.length;n++){const r=t[n].trim();if(r.substring(0,10)===e+"=")return decodeURIComponent(r.substring(10))}}return""}("csrftoken")},controllerFromTestHtml:async function(e,t=null){Ce();const n=$("body");$e.updateJquery(),n.safeEmpty().append(e),$e._isInit=!1,$e.cleanCache(),await $e.init_sdc();let r=$e.rootController.iterateAllChildren();if(!t)return r;const s=r[0].onRefresh,o=jest.spyOn(r[0],"onRefresh");return new Promise(e=>{o.mockImplementation(function(){o.mockRestore();const t=s.apply(r[0],arguments);return e(r),t})})}};export{ve as AbstractSDC,ue as SdcModel,ce as SdcQuerySet,M as allOff,$e as app,u as checkIfParamNumberBoolOrString,h as clearErrorsInForm,F as controllerFactory,q as on,ne as registerModel,P as runControlFlowFunctions,f as setErrorsInForm,T as setEvent,Ne as socketReconnect,Oe as test_utils,A as trigger};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
API Reference
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
Top-level exports
|
|
5
|
+
-----------------
|
|
6
|
+
|
|
7
|
+
The package entry point exports the following public API:
|
|
8
|
+
|
|
9
|
+
``app``
|
|
10
|
+
Global runtime object.
|
|
11
|
+
|
|
12
|
+
``AbstractSDC``
|
|
13
|
+
Base controller class.
|
|
14
|
+
|
|
15
|
+
``SdcModel``
|
|
16
|
+
Model base export from ``sdc_model.js``.
|
|
17
|
+
|
|
18
|
+
``SdcQuerySet``
|
|
19
|
+
QuerySet collection class for backend-backed models.
|
|
20
|
+
|
|
21
|
+
``registerModel(name, classObj)``
|
|
22
|
+
Registers a model class in the global model registry.
|
|
23
|
+
|
|
24
|
+
``on(name, controller)``
|
|
25
|
+
Registers a controller or object as a listener for an application event.
|
|
26
|
+
|
|
27
|
+
``trigger(name, ...args)``
|
|
28
|
+
Emits an application event and returns a promise of listener results.
|
|
29
|
+
|
|
30
|
+
``allOff(controller)``
|
|
31
|
+
Removes the controller from all application event subscriptions.
|
|
32
|
+
|
|
33
|
+
``setEvent(name, functionName = name)``
|
|
34
|
+
Maps an application event name to a handler method name.
|
|
35
|
+
|
|
36
|
+
``clearErrorsInForm($form)``
|
|
37
|
+
Removes form-level and field-level validation error markup.
|
|
38
|
+
|
|
39
|
+
``setErrorsInForm($form, $resForm)``
|
|
40
|
+
Applies validation error markup from a response form fragment.
|
|
41
|
+
|
|
42
|
+
``checkIfParamNumberBoolOrString(value, controller = null)``
|
|
43
|
+
Converts serialized DOM values into typed JavaScript values.
|
|
44
|
+
|
|
45
|
+
``controllerFactory(parentController, $element, tagName, superTagNameList)``
|
|
46
|
+
Creates a controller instance for a registered tag.
|
|
47
|
+
|
|
48
|
+
``runControlFlowFunctions(controller, process)``
|
|
49
|
+
Runs ``onLoad()``, child rendering, ``willShow()``, and refresh.
|
|
50
|
+
|
|
51
|
+
``socketReconnect``
|
|
52
|
+
Alias of the server-call ``close()`` function.
|
|
53
|
+
|
|
54
|
+
``test_utils``
|
|
55
|
+
Test helpers: ``get_controller()``, ``getCsrfToken()``,
|
|
56
|
+
``controllerFromTestHtml()``.
|
|
57
|
+
|
|
58
|
+
``app`` reference
|
|
59
|
+
-----------------
|
|
60
|
+
|
|
61
|
+
Bootstrap and registration
|
|
62
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
63
|
+
|
|
64
|
+
``app.init_sdc()``
|
|
65
|
+
Initializes the runtime, scans the DOM, and instantiates registered
|
|
66
|
+
controllers.
|
|
67
|
+
|
|
68
|
+
``app.register(Controller, overwrite = false)``
|
|
69
|
+
Registers a controller class and returns an object with ``addMixin(...)``.
|
|
70
|
+
|
|
71
|
+
``app.registerGlobal(Controller)``
|
|
72
|
+
Registers a controller and marks it as global.
|
|
73
|
+
|
|
74
|
+
``app.controllerToTag(Controller)``
|
|
75
|
+
Converts a controller class name to a tag name.
|
|
76
|
+
|
|
77
|
+
DOM and controller helpers
|
|
78
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
79
|
+
|
|
80
|
+
``app.getController($elem)``
|
|
81
|
+
Returns the controller bound to a DOM node or its closest controller parent.
|
|
82
|
+
|
|
83
|
+
``app.refresh($dom, leafController = null)``
|
|
84
|
+
Refreshes the controller subtree rooted at the given node.
|
|
85
|
+
|
|
86
|
+
``app.reloadController(controller)``
|
|
87
|
+
Reloads controller HTML from ``contentUrl`` and reconciles it into place.
|
|
88
|
+
|
|
89
|
+
``app.reconcile(controller, $virtualNode, $realNode = null, process = null)``
|
|
90
|
+
Runs refresh and DOM reconciliation for a controller.
|
|
91
|
+
|
|
92
|
+
``app.cleanCache()``
|
|
93
|
+
Clears cached HTML fragments loaded from controller ``contentUrl`` values.
|
|
94
|
+
|
|
95
|
+
``app.updateJquery()``
|
|
96
|
+
Installs ``safeReplace()``, ``safeEmpty()``, and ``safeRemove()`` on jQuery.
|
|
97
|
+
|
|
98
|
+
Safe DOM operations
|
|
99
|
+
^^^^^^^^^^^^^^^^^^^
|
|
100
|
+
|
|
101
|
+
``app.safeRemove($elem)``
|
|
102
|
+
Removes DOM nodes after cleanly removing any controllers contained in them.
|
|
103
|
+
|
|
104
|
+
``app.safeEmpty($elem)``
|
|
105
|
+
Safely removes all children of a container.
|
|
106
|
+
|
|
107
|
+
``app.safeReplace($elem, $new)``
|
|
108
|
+
Inserts ``$new`` before ``$elem`` and safely removes ``$elem``.
|
|
109
|
+
|
|
110
|
+
HTTP and form helpers
|
|
111
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
112
|
+
|
|
113
|
+
``app.get(controller, url, args)``
|
|
114
|
+
GET request wrapper that refreshes the controller on success.
|
|
115
|
+
|
|
116
|
+
``app.post(controller, url, args)``
|
|
117
|
+
POST request wrapper that refreshes the controller on success.
|
|
118
|
+
|
|
119
|
+
``app.ajax(controller, url, args, method)``
|
|
120
|
+
Shared transport wrapper for ``get`` and ``post``.
|
|
121
|
+
|
|
122
|
+
``app.submitFormAndUpdateView(controller, form, url, method)``
|
|
123
|
+
Submits a form, then refreshes the controller when successful.
|
|
124
|
+
|
|
125
|
+
``app.submitForm(form, url, method)``
|
|
126
|
+
Submits a form without a controller refresh step.
|
|
127
|
+
|
|
128
|
+
``AbstractSDC`` reference
|
|
129
|
+
-------------------------
|
|
130
|
+
|
|
131
|
+
Tree and DOM
|
|
132
|
+
^^^^^^^^^^^^
|
|
133
|
+
|
|
134
|
+
``find(selector)``
|
|
135
|
+
Shortcut for ``this.$container.find(selector)``.
|
|
136
|
+
|
|
137
|
+
``refresh()``
|
|
138
|
+
Refreshes the controller subtree.
|
|
139
|
+
|
|
140
|
+
``reload()``
|
|
141
|
+
Reloads controller HTML from the backend.
|
|
142
|
+
|
|
143
|
+
``reconcile($virtualNode, $realNode = null)``
|
|
144
|
+
Reconciles virtual HTML into the controller's real DOM.
|
|
145
|
+
|
|
146
|
+
``iterateAllChildren()``
|
|
147
|
+
Recursively returns all descendant controllers.
|
|
148
|
+
|
|
149
|
+
Lifecycle
|
|
150
|
+
^^^^^^^^^
|
|
151
|
+
|
|
152
|
+
``onInit(...args)``
|
|
153
|
+
``onLoad(html)``
|
|
154
|
+
``willShow()``
|
|
155
|
+
``onRefresh(originController)``
|
|
156
|
+
``onRemove()``
|
|
157
|
+
|
|
158
|
+
Networking and models
|
|
159
|
+
^^^^^^^^^^^^^^^^^^^^^
|
|
160
|
+
|
|
161
|
+
``get(url, args)``
|
|
162
|
+
``post(url, args)``
|
|
163
|
+
``submitForm(form, url, method)``
|
|
164
|
+
``serverCall(methodName, args)``
|
|
165
|
+
``querySet(modelName, modelQuery = {})``
|
|
166
|
+
``noOpenModelRequests()``
|
|
167
|
+
|
|
168
|
+
Forms
|
|
169
|
+
^^^^^
|
|
170
|
+
|
|
171
|
+
``submitModelFormDistributor($form, event)``
|
|
172
|
+
``defaultSubmitModelForm($form, event)``
|
|
173
|
+
|
|
174
|
+
``SdcQuerySet`` reference
|
|
175
|
+
-------------------------
|
|
176
|
+
|
|
177
|
+
Collection API
|
|
178
|
+
^^^^^^^^^^^^^^
|
|
179
|
+
|
|
180
|
+
``length``
|
|
181
|
+
``[index]``
|
|
182
|
+
``[Symbol.iterator]()``
|
|
183
|
+
``getIds()``
|
|
184
|
+
``setIds(ids)``
|
|
185
|
+
``byId(id)``
|
|
186
|
+
``new()``
|
|
187
|
+
|
|
188
|
+
Query and transport
|
|
189
|
+
^^^^^^^^^^^^^^^^^^^
|
|
190
|
+
|
|
191
|
+
``setFilter(modelQuery)``
|
|
192
|
+
``addFilter(modelQuery)``
|
|
193
|
+
``load(modelQuery = null)``
|
|
194
|
+
``update({ modelQuery = null, item = null })``
|
|
195
|
+
``get(modelQuery = null)``
|
|
196
|
+
``delete({ pk = null, elem = null })``
|
|
197
|
+
``save({ pk = null, formName = "edit_form", data = null })``
|
|
198
|
+
``create({ elem, data = null })``
|
|
199
|
+
``isConnected()``
|
|
200
|
+
``close()``
|
|
201
|
+
``noOpenRequests()``
|
|
202
|
+
|
|
203
|
+
View rendering
|
|
204
|
+
^^^^^^^^^^^^^^
|
|
205
|
+
|
|
206
|
+
``view({ viewName = "html_list_template", modelQuery = {}, cbResolve = null,
|
|
207
|
+
cbReject = null, templateContext = {}, eventType = "named_view" })``
|
|
208
|
+
|
|
209
|
+
``listView({ modelQuery = {}, cbResolve = null, cbReject = null,
|
|
210
|
+
templateContext = {} })``
|
|
211
|
+
|
|
212
|
+
``detailView({ pk, cbResolve = null, cbReject = null, templateContext = {} })``
|
|
213
|
+
|
|
214
|
+
Test utilities
|
|
215
|
+
--------------
|
|
216
|
+
|
|
217
|
+
``getCsrfToken()``
|
|
218
|
+
Reads the ``csrftoken`` cookie in test environments.
|
|
219
|
+
|
|
220
|
+
``get_controller(tag_name, init_arguments = {}, origen_html = "")``
|
|
221
|
+
Creates a controller through the normal SDC lifecycle in a JSDOM test.
|
|
222
|
+
|
|
223
|
+
``controllerFromTestHtml(html, afterLifecycle = null)``
|
|
224
|
+
Bootstraps controllers from arbitrary test HTML and returns the resulting
|
|
225
|
+
controller list.
|
package/docs/conf.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
project = "SimpleDomControlClient"
|
|
2
|
+
author = "Martin Starman"
|
|
3
|
+
copyright = "2026, Martin Starman"
|
|
4
|
+
release = "0.58.6"
|
|
5
|
+
|
|
6
|
+
extensions = []
|
|
7
|
+
templates_path = ["_templates"]
|
|
8
|
+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|
9
|
+
|
|
10
|
+
html_theme = "alabaster"
|
|
11
|
+
html_static_path = ["_static"]
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Controllers
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Base class
|
|
5
|
+
----------
|
|
6
|
+
|
|
7
|
+
All controllers extend ``AbstractSDC``.
|
|
8
|
+
|
|
9
|
+
Common properties
|
|
10
|
+
-----------------
|
|
11
|
+
|
|
12
|
+
``contentUrl``
|
|
13
|
+
Backend URL used to load the controller's HTML fragment.
|
|
14
|
+
|
|
15
|
+
``contentReload``
|
|
16
|
+
If ``true``, the content is always reloaded instead of using the cached
|
|
17
|
+
HTML fragment.
|
|
18
|
+
|
|
19
|
+
``events``
|
|
20
|
+
List of event definition objects merged into one event map.
|
|
21
|
+
|
|
22
|
+
``load_async``
|
|
23
|
+
If ``true``, the controller lifecycle does not block parent initialization.
|
|
24
|
+
|
|
25
|
+
``$container``
|
|
26
|
+
jQuery wrapper for the DOM element associated with the controller.
|
|
27
|
+
|
|
28
|
+
``parentController`` / ``childController``
|
|
29
|
+
Accessors for the controller tree.
|
|
30
|
+
|
|
31
|
+
Lifecycle
|
|
32
|
+
---------
|
|
33
|
+
|
|
34
|
+
``onInit(...args)``
|
|
35
|
+
Runs immediately after the controller is created. Arguments are resolved
|
|
36
|
+
from DOM ``data-*`` attributes.
|
|
37
|
+
|
|
38
|
+
``onLoad(html)``
|
|
39
|
+
Runs after the controller HTML has been fetched but before child controller
|
|
40
|
+
tags are replaced. This is a common place to mutate the loaded fragment.
|
|
41
|
+
|
|
42
|
+
``willShow()``
|
|
43
|
+
Runs after child controller content has been prepared and before refresh.
|
|
44
|
+
|
|
45
|
+
``onRefresh(originController)``
|
|
46
|
+
Runs after DOM updates and event wiring are complete.
|
|
47
|
+
|
|
48
|
+
``onRemove()``
|
|
49
|
+
Runs when the controller is being removed. Return ``false`` to block
|
|
50
|
+
removal.
|
|
51
|
+
|
|
52
|
+
Example lifecycle
|
|
53
|
+
-----------------
|
|
54
|
+
|
|
55
|
+
.. code-block:: javascript
|
|
56
|
+
|
|
57
|
+
class ProductList extends AbstractSDC {
|
|
58
|
+
constructor() {
|
|
59
|
+
super();
|
|
60
|
+
this.contentUrl = "/sdc_view/shop/product-list/";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onInit(categoryId) {
|
|
64
|
+
this.categoryId = categoryId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onLoad(html) {
|
|
68
|
+
$(html).find(".title").text(`Category ${this.categoryId}`);
|
|
69
|
+
return super.onLoad(html);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
willShow() {
|
|
73
|
+
this.loadedAt = Date.now();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
onRefresh() {
|
|
77
|
+
console.log("ProductList refreshed");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
DOM events
|
|
82
|
+
----------
|
|
83
|
+
|
|
84
|
+
Controller events are delegated through the document window and stored as
|
|
85
|
+
``sdc_<event-name>`` attributes on matching elements.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
.. code-block:: javascript
|
|
90
|
+
|
|
91
|
+
class TodoList extends AbstractSDC {
|
|
92
|
+
constructor() {
|
|
93
|
+
super();
|
|
94
|
+
this.events.unshift({
|
|
95
|
+
click: {
|
|
96
|
+
".add-button": "addItem",
|
|
97
|
+
".delete-button": "deleteItem",
|
|
98
|
+
},
|
|
99
|
+
submit: {
|
|
100
|
+
"form": "saveForm",
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
addItem($element, event) {}
|
|
106
|
+
deleteItem($element, event) {}
|
|
107
|
+
saveForm($element, event) {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
The handler receives:
|
|
111
|
+
|
|
112
|
+
- the matched jQuery element
|
|
113
|
+
- the original browser event
|
|
114
|
+
|
|
115
|
+
You can also register events at runtime with ``addEvent(event, selector,
|
|
116
|
+
handler)``.
|
|
117
|
+
|
|
118
|
+
Mixins
|
|
119
|
+
------
|
|
120
|
+
|
|
121
|
+
Controllers can aggregate other registered controller classes as mixins.
|
|
122
|
+
|
|
123
|
+
.. code-block:: javascript
|
|
124
|
+
|
|
125
|
+
app.register(BaseList);
|
|
126
|
+
app.register(Sortable);
|
|
127
|
+
app.register(Filterable);
|
|
128
|
+
|
|
129
|
+
app.register(ProductList)
|
|
130
|
+
.addMixin(Sortable, Filterable);
|
|
131
|
+
|
|
132
|
+
Mixin methods and event definitions are merged into the controller instance.
|
|
133
|
+
Lifecycle methods on mixins are invoked through ``AbstractSDC._runLifecycle()``.
|
|
134
|
+
|
|
135
|
+
Refresh, reload, and reconciliation
|
|
136
|
+
-----------------------------------
|
|
137
|
+
|
|
138
|
+
``refresh()``
|
|
139
|
+
Re-runs nested controller replacement and handler-bound ``<this.*>`` DOM
|
|
140
|
+
fragments inside the controller container.
|
|
141
|
+
|
|
142
|
+
``reload()``
|
|
143
|
+
Reloads the controller HTML from ``contentUrl`` and reconciles it into the
|
|
144
|
+
existing DOM tree.
|
|
145
|
+
|
|
146
|
+
``reconcile($virtualNode, $realNode = null)``
|
|
147
|
+
Diffs the virtual and real DOM trees and preserves matching elements where
|
|
148
|
+
possible. This is used to keep elements such as form inputs stable across
|
|
149
|
+
updates.
|
|
150
|
+
|
|
151
|
+
Safe DOM helpers
|
|
152
|
+
----------------
|
|
153
|
+
|
|
154
|
+
The runtime installs jQuery helpers:
|
|
155
|
+
|
|
156
|
+
- ``$elem.safeReplace($new)``
|
|
157
|
+
- ``$elem.safeEmpty()``
|
|
158
|
+
- ``$elem.safeRemove()``
|
|
159
|
+
|
|
160
|
+
These helpers call controller removal logic before modifying the DOM so child
|
|
161
|
+
controllers can clean up sockets, events, and internal state.
|
|
162
|
+
|
|
163
|
+
Rendering dynamic fragments with ``sdcDom`` and ``<this.*>``
|
|
164
|
+
------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
The global ``window.sdcDom()`` helper creates DOM fragments compatible with
|
|
167
|
+
the controller refresh pipeline.
|
|
168
|
+
|
|
169
|
+
When the tag name is a function, the runtime creates a ``<this.functionName>``
|
|
170
|
+
placeholder that is rebound during refresh.
|
|
171
|
+
|
|
172
|
+
This enables patterns like:
|
|
173
|
+
|
|
174
|
+
.. code-block:: javascript
|
|
175
|
+
|
|
176
|
+
class Demo extends AbstractSDC {
|
|
177
|
+
onLoad(html) {
|
|
178
|
+
$(html).append("<this.listview></this.listview>");
|
|
179
|
+
return super.onLoad(html);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
listview() {
|
|
183
|
+
return "<div>Dynamic content</div>";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
During ``refresh()``, the runtime resolves these placeholders by calling the
|
|
188
|
+
matching controller method and reconciling the result into the current DOM.
|
|
189
|
+
|
|
190
|
+
Backend method calls
|
|
191
|
+
--------------------
|
|
192
|
+
|
|
193
|
+
Use ``serverCall(methodName, args)`` to invoke a backend controller method.
|
|
194
|
+
|
|
195
|
+
.. code-block:: javascript
|
|
196
|
+
|
|
197
|
+
class InvoiceEditor extends AbstractSDC {
|
|
198
|
+
approve() {
|
|
199
|
+
return this.serverCall("approve", { approved: true });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
``serverCall()`` uses:
|
|
204
|
+
|
|
205
|
+
- AJAX if ``window.SERVER_CALL_VIA_WEB_SOCKET`` is falsy
|
|
206
|
+
- WebSockets if it is truthy
|
|
207
|
+
|
|
208
|
+
For ``serverCall()`` to work, ``contentUrl`` must contain an ``sdc_view/<app>/``
|
|
209
|
+
segment so the client can infer the backend app name.
|
|
210
|
+
|
|
211
|
+
Model-aware forms
|
|
212
|
+
-----------------
|
|
213
|
+
|
|
214
|
+
``AbstractSDC`` includes a default model form submit pipeline.
|
|
215
|
+
|
|
216
|
+
``submitModelFormDistributor($form, event)``
|
|
217
|
+
Dispatches to ``_submitModelForm()``, ``submitModelForm()``, or
|
|
218
|
+
``defaultSubmitModelForm()``.
|
|
219
|
+
|
|
220
|
+
``defaultSubmitModelForm($form, event)``
|
|
221
|
+
Reads the model from form metadata, submits ``save()`` or ``create()``,
|
|
222
|
+
clears validation errors on success, and reconciles returned error HTML on
|
|
223
|
+
failure.
|
|
224
|
+
|
|
225
|
+
Controllers and their descendants can react to the result through:
|
|
226
|
+
|
|
227
|
+
- ``submit_model_form_success(result)``
|
|
228
|
+
- ``submit_model_form_error(errorData)``
|