@dogiloki/artha-js 1.0.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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/artha.min.css +1 -0
- package/dist/artha.min.css.map +1 -0
- package/dist/artha.min.js +32 -0
- package/package.json +31 -0
- package/src/components/artha-container.js +402 -0
- package/src/components/artha-form.js +146 -0
- package/src/components/artha-message.js +61 -0
- package/src/core/Config.js +47 -0
- package/src/core/EventBus.js +33 -0
- package/src/core/TaskQueue.js +171 -0
- package/src/core/Util.js +86 -0
- package/src/core/XHR.js +116 -0
- package/src/scss/colors.scss +9 -0
- package/src/scss/main.scss +1 -0
- package/src/scss/message.scss +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Julio Villanueva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# artha-js
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
artha-message{width:100%;padding:10px;text-align:left;font-size:1em;cursor:default}artha-message[type=error]{background-color:#f2dede;color:#b94a48}artha-message[type=success]{background-color:#dff0d8;color:#468847}artha-message[type=warning]{background-color:#fcf8e3;color:#c09853}artha-message[type=info]{background-color:#d9edf7;color:#3a87ad}/*# sourceMappingURL=artha.min.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../src/scss/message.scss","../src/scss/colors.scss"],"names":[],"mappings":"AAEA,cACI,WACA,aACA,gBACA,cACA,eAEA,0BACI,iBCLqB,QDMrB,MCVe,QDYnB,4BACI,iBCRuB,QDSvB,MCbiB,QDerB,4BACI,iBCXuB,QDYvB,MChBiB,QDkBrB,yBACI,iBCdoB,QDepB,MCnBc","file":"artha.min.css"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Config from '../src/core/Config.js';
|
|
2
|
+
import Util from '../src/core/Util.js';
|
|
3
|
+
import EventBus from '../src/core/EventBus.js';
|
|
4
|
+
import TaskQueue from '../src/core/TaskQueue.js';
|
|
5
|
+
import XHR from '../src/core/XHR.js';
|
|
6
|
+
import ArthaMessage from '../src/components/artha-message.js';
|
|
7
|
+
import ArthaContainer from '../src/components/artha-container.js';
|
|
8
|
+
import ArthaForm from '../src/components/artha-form.js';
|
|
9
|
+
|
|
10
|
+
if(!customElements.get('artha-container')){
|
|
11
|
+
customElements.define('artha-container',ArthaContainer);
|
|
12
|
+
}
|
|
13
|
+
if(!customElements.get('artha-form')){
|
|
14
|
+
customElements.define('artha-form',ArthaForm);
|
|
15
|
+
}
|
|
16
|
+
if(!customElements.get('artha-message')){
|
|
17
|
+
customElements.define('artha-message',ArthaMessage);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const Artha={
|
|
21
|
+
version:"1.0.0",
|
|
22
|
+
config(options){
|
|
23
|
+
Config.set(options);
|
|
24
|
+
},
|
|
25
|
+
get(path,def=null){
|
|
26
|
+
return Config.get(path,def);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Window.Artha=Artha;
|
|
31
|
+
|
|
32
|
+
export default Artha;
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dogiloki/artha-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/artha.min.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "sass --watch src/scss/main.scss:dist/artha.css",
|
|
13
|
+
"build": "sass src/scss/main.scss dist/artha.min.css --style=compressed"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"task-queue",
|
|
17
|
+
"javascript",
|
|
18
|
+
"notifications",
|
|
19
|
+
"ui"
|
|
20
|
+
],
|
|
21
|
+
"author": "dogiloki",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/dogiloki/artha-js.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/dogiloki/artha-js/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/dogiloki/artha-js#readme"
|
|
31
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import Util from '../core/Util.js';
|
|
2
|
+
import EventBus from '../core/EventBus.js';
|
|
3
|
+
import XHR from '../core/XHR.js';
|
|
4
|
+
import TaskQueue from '../core/TaskQueue.js';
|
|
5
|
+
import ArthaMessage from './artha-message.js';
|
|
6
|
+
|
|
7
|
+
export default class ArthaContainer extends HTMLElement{
|
|
8
|
+
|
|
9
|
+
constructor(){
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
this.onRenderItem=(element,data)=>{};
|
|
13
|
+
this.onRenderItemFill=(element,data,fill_element,fill_data)=>{};
|
|
14
|
+
this.onRenderItemIter=(element,data,iter_element,iter_data)=>{};
|
|
15
|
+
|
|
16
|
+
this.task_queue=TaskQueue.singleton();
|
|
17
|
+
this.items={};
|
|
18
|
+
this.selection_store=new SelectionStore();
|
|
19
|
+
this.response_type='json';
|
|
20
|
+
this._elements={};
|
|
21
|
+
this._props=[
|
|
22
|
+
"template","action","action_router","method",
|
|
23
|
+
"pagination","message","searcher","selectable","multiple"];
|
|
24
|
+
this._props.forEach((prop)=>{
|
|
25
|
+
Object.defineProperty(this,prop,{
|
|
26
|
+
get:()=>{
|
|
27
|
+
switch(prop){
|
|
28
|
+
case 'template':
|
|
29
|
+
if(!this._elements[prop] || this._elements[prop].id!==this.getAttribute(prop)){
|
|
30
|
+
this._elements[prop]=document.getElementById(this.getAttribute(prop));
|
|
31
|
+
}
|
|
32
|
+
return this._elements[prop];
|
|
33
|
+
case 'method': return this.getAttribute(prop)??'GET';
|
|
34
|
+
default: return this.getAttribute(prop);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
set:(value)=>{
|
|
38
|
+
if(this.getAttribute(prop)!==value){
|
|
39
|
+
this.setAttribute(prop,value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const attr_val=this.getAttribute(prop);
|
|
44
|
+
if(attr_val!==null) this[prop]=attr_val;
|
|
45
|
+
});
|
|
46
|
+
this.pagination=this.getAttribute("pagination");
|
|
47
|
+
this.searcher=this.hasAttribute("searcher");
|
|
48
|
+
this.selectable=this.hasAttribute("selectable");
|
|
49
|
+
this.multiple=this.hasAttribute("multiple");
|
|
50
|
+
this.message=this.querySelector('artha-message')??this.querySelector(this.getAttribute('message-target'))??null;
|
|
51
|
+
|
|
52
|
+
// Loader
|
|
53
|
+
this.loader_container=this._createLoader();
|
|
54
|
+
|
|
55
|
+
// Contenido
|
|
56
|
+
this.content=this.querySelector(':scope > dynamic-content') || this.appendChild(document.createElement('dynamic-content'));
|
|
57
|
+
this._content=this.content.children[0];
|
|
58
|
+
|
|
59
|
+
// Input de búsqueda
|
|
60
|
+
this.input_search=this.querySelector("input-search");
|
|
61
|
+
if(this.input_search){
|
|
62
|
+
this.input_search.addEventListener('search',(evt)=>this._handleSearch(evt));
|
|
63
|
+
this.refresh(this.input_search.value);
|
|
64
|
+
}else{
|
|
65
|
+
this.refresh();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
_createLoader(){
|
|
70
|
+
return Util.createElement('div',(div)=>{
|
|
71
|
+
div.classList.add('loader-container');
|
|
72
|
+
div.setAttribute('title','Procesando...');
|
|
73
|
+
div.appendChild(Util.createElement('div',(div2)=>{
|
|
74
|
+
div2.classList.add('background-overlay');
|
|
75
|
+
}));
|
|
76
|
+
div.appendChild(Util.createElement('div',(div3)=>{
|
|
77
|
+
div3.classList.add('loader','active');
|
|
78
|
+
}));
|
|
79
|
+
const img_container=div.appendChild(Util.createElement('div',(div4)=>{
|
|
80
|
+
div4.classList.add('img-container');
|
|
81
|
+
}));
|
|
82
|
+
img_container.appendChild(Util.createElement('img',(img)=>{
|
|
83
|
+
img.src='/assets/logo.png';
|
|
84
|
+
}));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_handleSearch(evt){
|
|
89
|
+
if(this.action){
|
|
90
|
+
this.refresh(evt.detail.query);
|
|
91
|
+
}else{
|
|
92
|
+
const search=evt.detail.query?.toLowerCase()??'';
|
|
93
|
+
for(const item of this.items??[]){
|
|
94
|
+
Util.modal(item,item.textContent.toLowerCase().includes(search));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// value - getter/setter
|
|
100
|
+
get value(){
|
|
101
|
+
if(!this.selectable) return null;
|
|
102
|
+
return this.multiple?this.selection_store.toValues():this.selection_store.toValues()[0]??null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set value(values){
|
|
106
|
+
if(!this.selectable) return;
|
|
107
|
+
if(!Array.isArray(values)) values=[values];
|
|
108
|
+
|
|
109
|
+
this.selection_store.clear();
|
|
110
|
+
let count=0;
|
|
111
|
+
for(const item of this.items){
|
|
112
|
+
const id=item.dataset.id;
|
|
113
|
+
if(values.includes(id)){
|
|
114
|
+
this.selection_store.add(id,item,item.data);
|
|
115
|
+
this.classList.add('selected');
|
|
116
|
+
if(!this.multiple && ++count>=1) break;
|
|
117
|
+
}else{
|
|
118
|
+
item.classList.remove('selected');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
connectedCallback(){
|
|
124
|
+
const channels=(this.getAttribute('refresh-on')||'').split(',').map(c=>c.trim()).filter(c=>c.length>0);
|
|
125
|
+
this._refresh_listeners=[];
|
|
126
|
+
for(const channel of channels){
|
|
127
|
+
const listener=(evt)=>evt?.detail?this.refreshWithData(evt.detail):this.refresh();
|
|
128
|
+
EventBus.on(channel,listener);
|
|
129
|
+
this._refresh_listeners.push({channel,listener});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
disconnectedCallback(){
|
|
134
|
+
for(const {channel,listener} of this._refresh_listeners??[]){
|
|
135
|
+
EventBus.off(channel,listener);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getData(search=null){
|
|
140
|
+
if(!this.action) return;
|
|
141
|
+
const query=search?{search}:{};
|
|
142
|
+
const id=this.getAttribute("id")??Util.numberRandom(10000,99999);
|
|
143
|
+
this.task_queue.loadTask(`container-${id}`,null,(task)=>{
|
|
144
|
+
XHR.request({
|
|
145
|
+
url:this.action,
|
|
146
|
+
method:this.method,
|
|
147
|
+
headers:{
|
|
148
|
+
'Accept':'application/json'
|
|
149
|
+
},
|
|
150
|
+
response_type:this.response_type,
|
|
151
|
+
query:Object.keys(query).length?query:{},
|
|
152
|
+
onLoad:(xhr)=>{
|
|
153
|
+
this.dispatchEvent(new CustomEvent('load',{detail:xhr}));
|
|
154
|
+
},
|
|
155
|
+
onData:(xhr,data)=>{
|
|
156
|
+
// Respuesta procesada en en formato json
|
|
157
|
+
task.resolve(xhr,(json)=>{
|
|
158
|
+
this.dispatchEvent(new CustomEvent('resolve',{detail:json}));
|
|
159
|
+
if(json.message){
|
|
160
|
+
this.message?.show(json.message,json.status);
|
|
161
|
+
}
|
|
162
|
+
this.render(json.data);
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
onError:(err)=>{
|
|
166
|
+
this.message?.error(err??"Error de conexión");
|
|
167
|
+
task.onFinalize();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
},{
|
|
171
|
+
message:this.message
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
refresh(search=null){
|
|
176
|
+
this.loader_container.remove();
|
|
177
|
+
if(this.template){
|
|
178
|
+
this.content.innerHTML="";
|
|
179
|
+
if(this._content) this.content.appendChild(this._content);
|
|
180
|
+
this.content.appendChild(this.loader_container);
|
|
181
|
+
}
|
|
182
|
+
this.getData(search);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
refreshWithData(data){
|
|
186
|
+
for(const child of this.content.querySelectorAll('[data-id]')){
|
|
187
|
+
if(child.dataset.id==data.id) this.renderItem(data,true,child);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
render(results,refresh=false,refresh_children=true){
|
|
192
|
+
if(refresh) this.refresh();
|
|
193
|
+
results=Array.isArray(results)?results:[results];
|
|
194
|
+
this.data=results;
|
|
195
|
+
this.items=[];
|
|
196
|
+
this.loader_container.remove();
|
|
197
|
+
for(const data of results) this.renderItem(data,refresh_children);
|
|
198
|
+
this.dispatchEvent(new CustomEvent('dynamic-content-loaded',{detail:results}));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
renderItem(data,refresh_children=true,update=null){
|
|
202
|
+
const template=this.template?(this.template.tagName==='TEMPLATE'?this.template.content.cloneNode(true):this.template.cloneNode(true)):this;
|
|
203
|
+
const element=update?update:(this.template?template.children[0]:this);
|
|
204
|
+
|
|
205
|
+
const items=this._findWires(update?element:template);
|
|
206
|
+
let index=0;
|
|
207
|
+
|
|
208
|
+
for(const item of items){
|
|
209
|
+
const wires=item.getAttribute("data-wire").split(",");
|
|
210
|
+
for(let wire of wires){
|
|
211
|
+
const [attrib_json,attrib_element,attrib_action]=wire.split(",");
|
|
212
|
+
let value=attrib_json?Util.getValueByPath(data,attrib_json.replaceAll("[]","")):data[index]??"";
|
|
213
|
+
const append=attrib_action==="append";
|
|
214
|
+
const chooser=attrib_action==="chooser";
|
|
215
|
+
const is_value_array=attrib_json?.endsWith("[]");
|
|
216
|
+
|
|
217
|
+
if(item instanceof ArthaContainer) item.render(value,refresh_children);
|
|
218
|
+
else if(is_value_array) this._fillArray(item,append,chooser);
|
|
219
|
+
else this._setValue(item,attrib_element,value,append,chooser);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if(!update){
|
|
224
|
+
this.items.push(element);
|
|
225
|
+
element.data=data;
|
|
226
|
+
element.dataset.id=data.id;
|
|
227
|
+
if(this.selectable) this._bindSelectable(element,data);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.onRenderItem(element,data);
|
|
231
|
+
if(this.template && !update) this.content.appendChild(element);
|
|
232
|
+
this.dispatchEvent(new CustomEvent('item-rendered',{detail:{item:element,data,index}}));
|
|
233
|
+
index++;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_fillArray(item,data,value){
|
|
237
|
+
const fill_template=item.children[0].cloneNode(true);
|
|
238
|
+
item.innerHTML="";
|
|
239
|
+
for(const fill of value??[]){
|
|
240
|
+
const node=fill_template.cloneNode(true);
|
|
241
|
+
const fill_elements=node.querySelectorAll("[fillable]");
|
|
242
|
+
const iter_elements=node.querySelectorAll("[iterable]");
|
|
243
|
+
fill_elements.forEach((element)=>{
|
|
244
|
+
this.onRenderItemFill(item,data,element,fill);
|
|
245
|
+
element.textContent=fill;
|
|
246
|
+
element.value=fill;
|
|
247
|
+
});
|
|
248
|
+
iter_elements.forEach((element)=>{
|
|
249
|
+
this.onRenderItemIter(item,data,element,fill);
|
|
250
|
+
});
|
|
251
|
+
item.appendChild(node);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_setValue(item,attrib_element,value,append,chooser){
|
|
256
|
+
if(attrib_element){
|
|
257
|
+
if(append){
|
|
258
|
+
switch(attrib_element){
|
|
259
|
+
case 'textcontent': value=item.textContent+value; break;
|
|
260
|
+
case 'innerhtml': value=item.innerhtml+value; break;
|
|
261
|
+
default: value=item.getAttribute(attrib_element)+value;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
switch(attrib_element.toLowerCase()){
|
|
265
|
+
case 'textcontent': item.textContent=value; break;
|
|
266
|
+
case 'innerhtml': item.innerhtml=value; break;
|
|
267
|
+
case 'boolean':{
|
|
268
|
+
if(chooser){
|
|
269
|
+
let applied=false;
|
|
270
|
+
const templates=item.querySelectorAll('template');
|
|
271
|
+
for(const template of templates){
|
|
272
|
+
if(template.getAttribute('data-chooser-value')==value){
|
|
273
|
+
item.innerHTML="";
|
|
274
|
+
item.appendChild(template.content.cloneNode(true));
|
|
275
|
+
applied=true;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if(!applied){
|
|
280
|
+
const template=item.querySelector('template[data-chooser-default]')?.content?.cloneNode(true)??document.createElement('span');
|
|
281
|
+
item.innerHTML="";
|
|
282
|
+
item.appendChild(template);
|
|
283
|
+
}
|
|
284
|
+
}else{
|
|
285
|
+
item.innerHTML="";
|
|
286
|
+
item.appendChild(Util.createElement('span',(span)=>{
|
|
287
|
+
span.classList.add('check-cross');
|
|
288
|
+
if(value){
|
|
289
|
+
span.classList.add('check-cross-yes');
|
|
290
|
+
span.textContent="✔";
|
|
291
|
+
}else{
|
|
292
|
+
span.classList.add('check-cross-no');
|
|
293
|
+
span.textContent="✘";
|
|
294
|
+
}
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
default: this.setAttribute(attrib_element,value);
|
|
300
|
+
}
|
|
301
|
+
}else{
|
|
302
|
+
if(append) value=item.textContent+value;
|
|
303
|
+
item.textContent=value;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
_bindSelectable(element,data){
|
|
308
|
+
const id=element.dataset.id;
|
|
309
|
+
element.addEventListener('click',(evt)=>{
|
|
310
|
+
if(this.selection_store.has(id)){
|
|
311
|
+
const selection=this.selection_store.remove(id);
|
|
312
|
+
this.dispatchEvent(new CustomEvent('item-deselected',{detail:selection}));
|
|
313
|
+
}else{
|
|
314
|
+
if(!this.multiple) this.reset();
|
|
315
|
+
const selection=this.selection_store.add(id,element,data);
|
|
316
|
+
this.dispatchEvent(new CustomEvent('item-selected',{detail:selection}));
|
|
317
|
+
}
|
|
318
|
+
element.classList.toggle('selected');
|
|
319
|
+
});
|
|
320
|
+
if(this.selection_store.has(id)) element.classList.add('selected');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_findWires(root){
|
|
324
|
+
const result=[];
|
|
325
|
+
for(const child of root.children){
|
|
326
|
+
if(child instanceof ArthaContainer && child.hasAttribute('data-ignore-wire')) continue;
|
|
327
|
+
if(child.hasAttribute('data-wire')) result.push(child);
|
|
328
|
+
result.push(...this._findWires(child));
|
|
329
|
+
}
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
selection(){
|
|
334
|
+
return this.selection_store;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
reset(){
|
|
338
|
+
this.selection_store.clear();
|
|
339
|
+
for(const item of this.items){
|
|
340
|
+
item.classList.remove('selected');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class SelectionStore{
|
|
347
|
+
|
|
348
|
+
constructor(){
|
|
349
|
+
this.values=new Set();
|
|
350
|
+
this.elements=new Map();
|
|
351
|
+
this.data=new Map();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
add(value,element,data){
|
|
355
|
+
this.values.add(value);
|
|
356
|
+
this.elements.set(value,element);
|
|
357
|
+
this.data.set(value,data);
|
|
358
|
+
return {
|
|
359
|
+
value,element,data
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
remove(value){
|
|
364
|
+
const sel={value,element:this.elements.get(value),data:this.data.get(value)};
|
|
365
|
+
this.values.delete(value);
|
|
366
|
+
this.elements.delete(value);
|
|
367
|
+
this.data.delete(value);
|
|
368
|
+
return sel;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
clear(){
|
|
372
|
+
this.values.clear();
|
|
373
|
+
this.elements.clear();
|
|
374
|
+
this.data.clear();
|
|
375
|
+
return this;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
has(value){
|
|
379
|
+
return this.values.has(value);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
toValues(){
|
|
383
|
+
return Array.from(this.values);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
toElements(){
|
|
387
|
+
return Array.from(this.elements);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
toData(){
|
|
391
|
+
return Array.from(this.data);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
toArray(){
|
|
395
|
+
return Array.from(this.values).map(v=>({
|
|
396
|
+
value:v,
|
|
397
|
+
element:this.elements.get(v),
|
|
398
|
+
data:this.data.get(v)
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import Util from '../core/Util.js';
|
|
2
|
+
import XHR from '../core/XHR.js';
|
|
3
|
+
import TaskQueue from '../core/TaskQueue.js';
|
|
4
|
+
import ArthaMessage from './artha-message.js';
|
|
5
|
+
|
|
6
|
+
export default class ArthaForm extends HTMLElement{
|
|
7
|
+
|
|
8
|
+
constructor(){
|
|
9
|
+
super();
|
|
10
|
+
this.task_queue=TaskQueue.singleton();
|
|
11
|
+
this.response_type=this.getAttribute('response-type')??'json';
|
|
12
|
+
this.disable_submit=this.hasAttribute('disable-submit');
|
|
13
|
+
this.message=this.querySelector('artha-message')??this.querySelector(this.getAttribute('message-target'))??null;
|
|
14
|
+
if(!this.message){
|
|
15
|
+
this.message=Util.createElement('artha-message');
|
|
16
|
+
this.appendChild(this.message);
|
|
17
|
+
}
|
|
18
|
+
this.element_inputs=[];
|
|
19
|
+
this.ignored_input=[];
|
|
20
|
+
|
|
21
|
+
// Cargar inputs iniciales
|
|
22
|
+
this.loadInputs();
|
|
23
|
+
|
|
24
|
+
// Interceptar submit
|
|
25
|
+
this.addEventListener('submit',(evt)=>{
|
|
26
|
+
evt.preventDefault();
|
|
27
|
+
if(!this.disable_submit) this.submit();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Tecla enter
|
|
31
|
+
this.addEventListener('keydown',(evt)=>{
|
|
32
|
+
if(this.disable_submit && evt.key==='Enter' && evt.target instanceof HTMLInputElement){
|
|
33
|
+
evt.preventDefault();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this._bindEvents();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_bindEvents(){
|
|
41
|
+
// Botones
|
|
42
|
+
this.querySelectorAll('button').forEach((btn)=>{
|
|
43
|
+
switch(btn.getAttribute('type')){
|
|
44
|
+
case 'submit': btn.addEventListener('click',(evt)=>this.submit()); break;
|
|
45
|
+
case 'reset': btn.addEventListener('click',(evt)=>this.reset()); break;
|
|
46
|
+
default: btn.addEventListener('click',(evt)=>this.submit());
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Cargar inputs dinámicos
|
|
52
|
+
loadInputs(selector="input,select,textarea"){
|
|
53
|
+
this.element_inputs=[];
|
|
54
|
+
this.querySelectorAll(selector).forEach((element)=>{
|
|
55
|
+
const name=element.getAttribute('name');
|
|
56
|
+
if(name){
|
|
57
|
+
this[name]=element;
|
|
58
|
+
this.element_inputs.push(element);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Obtener valor de un input por el atributo name
|
|
64
|
+
getValue(name){
|
|
65
|
+
const element=this[name]??this.querySelector(`[name="${name}"]`);
|
|
66
|
+
return element?(element.type==='checkbox'?(element.checked?1:0):element.value):null;
|
|
67
|
+
}
|
|
68
|
+
input(name){
|
|
69
|
+
const element=this.querySelector(`[name="${name}"]`);
|
|
70
|
+
if(element!=null && !(name in this)){
|
|
71
|
+
this[name]=element;
|
|
72
|
+
}
|
|
73
|
+
return element;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Reset general del formulario
|
|
77
|
+
reset(reset_message=true){
|
|
78
|
+
this.element_inputs.forEach((element)=>{
|
|
79
|
+
if(element.type==='checkbox') element.checked=false;
|
|
80
|
+
else element.value='';
|
|
81
|
+
});
|
|
82
|
+
if(reset_message) this.resetMessage();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Reset al mensaje (ocultar)
|
|
86
|
+
resetMessage(){
|
|
87
|
+
if(this.message) this.message.hidden();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validar formulario
|
|
91
|
+
checkValidity(){
|
|
92
|
+
let valid=true;
|
|
93
|
+
for(const element of this.element_inputs){
|
|
94
|
+
if(typeof element.checkValidity==='function' && !element.checkValidity()){
|
|
95
|
+
valid=false;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return valid;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Enviar formulario
|
|
103
|
+
submit(){
|
|
104
|
+
const form_data={};
|
|
105
|
+
this.element_inputs.forEach((element)=>{
|
|
106
|
+
form_data[element.name]=element.type==='checkbox'?(element.checked?1:0):element.value;
|
|
107
|
+
})
|
|
108
|
+
const action=this.getAttribute('action')??'';
|
|
109
|
+
const method=this.getAttribute('method')??'GET';
|
|
110
|
+
const id=this.getAttribute('id');
|
|
111
|
+
this.task_queue.loadTask(`form-${id}`,null,(task)=>{
|
|
112
|
+
XHR.request({
|
|
113
|
+
url:action,
|
|
114
|
+
method:method,
|
|
115
|
+
data:form_data,
|
|
116
|
+
response_type:this.response_type,
|
|
117
|
+
onLoad:(xhr)=>{
|
|
118
|
+
this.dispatchEvent(new CustomEvent('load',{detail:xhr}));
|
|
119
|
+
},
|
|
120
|
+
onData:(xhr,data)=>{
|
|
121
|
+
// Respuesta procesada en formato json
|
|
122
|
+
task.resolve(xhr,(json)=>{
|
|
123
|
+
this.dispatchEvent(new CustomEvent('resolve',{detail:json}));
|
|
124
|
+
this.fillFromJson(json.data??{},false);
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
onError:(err)=>{
|
|
128
|
+
this.message.error(err??"Error de conexión");
|
|
129
|
+
task.onFinalize();
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
},{
|
|
133
|
+
message:this.message
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Llenar inputs desde un JSON
|
|
138
|
+
fillFromJson(json,reset=true){
|
|
139
|
+
if(reset) this.reset(false);
|
|
140
|
+
for(const key in json){
|
|
141
|
+
const element=this[key]??this.querySelector(`[name="${key}"]`);
|
|
142
|
+
if(element) element.value=json[key];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Config from "../core/Config.js";
|
|
2
|
+
import Util from "../core/Util.js";
|
|
3
|
+
|
|
4
|
+
export default class ArthaMessage extends HTMLElement{
|
|
5
|
+
|
|
6
|
+
static TYPE=Object.freeze({
|
|
7
|
+
ERROR:{
|
|
8
|
+
code:-1,
|
|
9
|
+
name:"error"
|
|
10
|
+
},
|
|
11
|
+
INFO:{
|
|
12
|
+
code:0,
|
|
13
|
+
name:"info"
|
|
14
|
+
},
|
|
15
|
+
SUCCESS:{
|
|
16
|
+
code:1,
|
|
17
|
+
name:"suscess"
|
|
18
|
+
},
|
|
19
|
+
WARNING:{
|
|
20
|
+
code:2,
|
|
21
|
+
name:"warning"
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
constructor(){
|
|
27
|
+
super();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
connectedCallback(){
|
|
31
|
+
this.type=this.getAttribute("type")||"info";
|
|
32
|
+
this.hidden();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
error(message=null){
|
|
36
|
+
this.show(message,ArthaMessage.TYPE.ERROR);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
info(message=null){
|
|
40
|
+
this.show(message,ArthaMessage.TYPE.INFO);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
success(message=null){
|
|
44
|
+
this.show(message,ArthaMessage.TYPE.SUCCESS);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
warning(message=null){
|
|
48
|
+
this.show(message,ArthaMessage.TYPE.WARNING);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
show(message=null,type=null){
|
|
52
|
+
if(type) this.setAttribute("type",(typeof type==="string"?type:type.name)||"info");
|
|
53
|
+
if(message) this.innerHTML=message;
|
|
54
|
+
Util.modal(this,true);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
hidden(){
|
|
58
|
+
Util.modal(this,false);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const DEFAULT_CONFIG={
|
|
2
|
+
locale:"es-MX",
|
|
3
|
+
currency:"MXN",
|
|
4
|
+
money:{
|
|
5
|
+
digits:2
|
|
6
|
+
},
|
|
7
|
+
xhr:{
|
|
8
|
+
method:"GET",
|
|
9
|
+
url:null,
|
|
10
|
+
uri:"",
|
|
11
|
+
headers:{},
|
|
12
|
+
data:{},
|
|
13
|
+
query:{},
|
|
14
|
+
files:{},
|
|
15
|
+
response_type:"json",
|
|
16
|
+
with_credentials:false,
|
|
17
|
+
timeout:0,
|
|
18
|
+
retry:false,
|
|
19
|
+
retry_delay:5000,
|
|
20
|
+
onLoad:()=>{},
|
|
21
|
+
onData:()=>{},
|
|
22
|
+
onError:()=>{},
|
|
23
|
+
onTimeout:()=>{},
|
|
24
|
+
onProgress:()=>{},
|
|
25
|
+
onAbort:()=>{},
|
|
26
|
+
onAction:()=>{},
|
|
27
|
+
},
|
|
28
|
+
task_queue:{
|
|
29
|
+
title:"Petición en proceso...",
|
|
30
|
+
close:false,
|
|
31
|
+
message:null
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default class Config{
|
|
36
|
+
|
|
37
|
+
static SETTINGS={...DEFAULT_CONFIG};
|
|
38
|
+
|
|
39
|
+
static set(options){
|
|
40
|
+
this.SETTINGS={...this.SETTINGS,...options};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static get(path,def=null){
|
|
44
|
+
return path.split(".").reduce((o,p)=>o?o[p]:def,this.SETTINGS);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const EVENT_BUS=new EventTarget();
|
|
2
|
+
|
|
3
|
+
export default class EventBus{
|
|
4
|
+
|
|
5
|
+
// Emitir evento
|
|
6
|
+
static emit(name,data){
|
|
7
|
+
EVENT_BUS.dispatchEvent(new CustomEvent(name,{detail:data}));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Escuchar evento
|
|
11
|
+
static on(name,callback){
|
|
12
|
+
const handler=(evt)=>{
|
|
13
|
+
callback(evt.detail);
|
|
14
|
+
};
|
|
15
|
+
EVENT_BUS.addEventListener(name,callback);
|
|
16
|
+
return ()=>EVENT_BUS.removeEventListener(name,handler);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Escucha una sola vez
|
|
20
|
+
static once(name,callback){
|
|
21
|
+
const handler=(evt)=>{
|
|
22
|
+
callback(evt.detail);
|
|
23
|
+
EVENT_BUS.removeEventListener(name,handler);
|
|
24
|
+
};
|
|
25
|
+
EVENT_BUS.addEventListener(name,callback);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Remover listener manualmente
|
|
29
|
+
static off(name,callback){
|
|
30
|
+
EVENT_BUS.removeEventListener(name,callback);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import Config from "./Config.js";
|
|
2
|
+
import Util from "./Util.js";
|
|
3
|
+
import ArthaMessage from "../components/artha-message.js";
|
|
4
|
+
|
|
5
|
+
export default class TaskQueue{
|
|
6
|
+
|
|
7
|
+
static INSTNACE=null;
|
|
8
|
+
|
|
9
|
+
static singleton(){
|
|
10
|
+
if(!this.INSTNACE){
|
|
11
|
+
this.INSTNACE=new TaskQueue();
|
|
12
|
+
}
|
|
13
|
+
return this.INSTNACE;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(){
|
|
17
|
+
this.queues=new Map();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Crear una nueva tarea
|
|
21
|
+
loadTask(id,title,callback,options={}){
|
|
22
|
+
if(typeof options!=='object'){
|
|
23
|
+
options={close:options};
|
|
24
|
+
}
|
|
25
|
+
if(this.queues.has(id)){
|
|
26
|
+
alert("La petición ya está en proceso... Por favor espere.");
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if(title) options.title=title;
|
|
30
|
+
const task=new TaskQueueItem(id,callback,options);
|
|
31
|
+
task.onFinalize=(remove=false)=>{
|
|
32
|
+
if(remove && !task.finalized){
|
|
33
|
+
task.message_element.warning(task.options.title);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
task.finalized=true;
|
|
37
|
+
this.queues.delete(id);
|
|
38
|
+
if(task.options.close || remove){
|
|
39
|
+
setTimeout(()=>task.removeElement(),task.options.close?2500:0);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
this.queues.set(id,task);
|
|
43
|
+
return task;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class TaskQueueItem{
|
|
49
|
+
|
|
50
|
+
constructor(id,callback,options){
|
|
51
|
+
this.id=id;
|
|
52
|
+
this.callback=callback;
|
|
53
|
+
options={...Config.get("task_queue"),...options};
|
|
54
|
+
const {
|
|
55
|
+
title,
|
|
56
|
+
close,
|
|
57
|
+
message
|
|
58
|
+
}=options;
|
|
59
|
+
this.options=options;
|
|
60
|
+
this.message_element=options.message instanceof ArthaMessage?options.message:document.querySelector("#"+options.message)??null;
|
|
61
|
+
this.resolve_callback=null;
|
|
62
|
+
this.reject_callback=null;
|
|
63
|
+
this.finalized=false;
|
|
64
|
+
this.status="pending";
|
|
65
|
+
this.message_element?.warning(options.title);
|
|
66
|
+
this.onFinalize=()=>{};
|
|
67
|
+
|
|
68
|
+
// Promesa
|
|
69
|
+
this.promise=new Promise((resolve,reject)=>{
|
|
70
|
+
this._resolve=resolve;
|
|
71
|
+
this._reject=reject;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Ejecutar callback
|
|
75
|
+
callback(this);
|
|
76
|
+
|
|
77
|
+
// Resolver
|
|
78
|
+
this.promise.then((data)=>{
|
|
79
|
+
this.handleResponse(data);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Error
|
|
83
|
+
this.promise.catch((error)=>{
|
|
84
|
+
this.message_element?.error(error?.message||String(error));
|
|
85
|
+
this.status="error";
|
|
86
|
+
this.reject_callback?.(error);
|
|
87
|
+
this.onFinalize();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Procesar respuesta
|
|
92
|
+
handleResponse(data){
|
|
93
|
+
if(!data){
|
|
94
|
+
this.message_element?.error("Error en la respuesta del servidor");
|
|
95
|
+
this.status="error";
|
|
96
|
+
this.onFinalize();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let response=data?.response??data;
|
|
100
|
+
|
|
101
|
+
// Blob para descargar
|
|
102
|
+
if(response instanceof Blob){
|
|
103
|
+
this.status="success";
|
|
104
|
+
this.resolve_callback?.(response);
|
|
105
|
+
this.onFinalize();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let json;
|
|
109
|
+
try{
|
|
110
|
+
json=(typeof response==='string')?JSON.parse(response):response;
|
|
111
|
+
if(!json || typeof json!=='object'){
|
|
112
|
+
throw new Error("Respuesta inválida del servidor");
|
|
113
|
+
}
|
|
114
|
+
}catch(ex){
|
|
115
|
+
this.message_element?.error(ex.message||String(ex));
|
|
116
|
+
this.status="error";
|
|
117
|
+
this.onFinalize();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Obtener mensaje
|
|
121
|
+
let message=null;
|
|
122
|
+
if(json.errors && typeof json.errors==='object'){
|
|
123
|
+
const values=Object.values(json.errors);
|
|
124
|
+
if(values.length>0){
|
|
125
|
+
const first=values[0];
|
|
126
|
+
message=Array.isArray(first)?first[0]:first;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
message=message||json.message||"Operación completada";
|
|
130
|
+
if(message){
|
|
131
|
+
this.message_element?.show(message,json?.status??null);
|
|
132
|
+
}
|
|
133
|
+
// Validar respuesta http
|
|
134
|
+
if(Util.withinRange(data.status,200,299)){
|
|
135
|
+
if(!message){
|
|
136
|
+
this.message_element?.success("Operación completada");
|
|
137
|
+
}
|
|
138
|
+
this.status="success";
|
|
139
|
+
}else{
|
|
140
|
+
if(!message){
|
|
141
|
+
this.message_element?.error("Error en la respuesta del servidor");
|
|
142
|
+
}
|
|
143
|
+
this.status="error";
|
|
144
|
+
this.onFinalize();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.resolve_callback?.(json);
|
|
148
|
+
this.onFinalize();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Cancelar si usa XHR
|
|
152
|
+
cancel(){
|
|
153
|
+
if(this.xhr) this.xhr.abort();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Resolver manualmente
|
|
157
|
+
resolve(data,callback){
|
|
158
|
+
this.resolve_callback=callback;
|
|
159
|
+
this._resolve(data);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Recahzar manualmente
|
|
163
|
+
reject(error){
|
|
164
|
+
this._reject(error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
removeElement(){
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
}
|
package/src/core/Util.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Config from "./Config.js";
|
|
2
|
+
|
|
3
|
+
export default class Util{
|
|
4
|
+
|
|
5
|
+
static getMeta(name){
|
|
6
|
+
const meta=document.querySelector(`meta[name="${name}"]`);
|
|
7
|
+
return meta?meta.getAttribute("content"):null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static getValueByPath(obj,path,default_value=null){
|
|
11
|
+
return path.split(".").reduce((o,p)=>o?o[p]:default_value,obj);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static modal(content,visible=-1){
|
|
15
|
+
content.style.display=(visible==-1)?
|
|
16
|
+
(content.style.display=="none"?"block":"none")
|
|
17
|
+
:(visible?"block":"none");
|
|
18
|
+
if(visible==-1){
|
|
19
|
+
content.classList.toggle("hidden");
|
|
20
|
+
if(content.hasAttribute("hidden")){
|
|
21
|
+
content.removeAttribute("hidden");
|
|
22
|
+
}
|
|
23
|
+
}else{
|
|
24
|
+
if(visible){
|
|
25
|
+
content.classList.remove("hidden");
|
|
26
|
+
content.removeAttribute("hidden");
|
|
27
|
+
}else{
|
|
28
|
+
content.classList.add("hidden");
|
|
29
|
+
content.setAttribute("hidden","");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static modalById(id,visible=-1){
|
|
35
|
+
Util.modal(document.getElementById(id),visible);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static formatMoney(value,options={}){
|
|
39
|
+
const {
|
|
40
|
+
locale=Config.get("locale"),
|
|
41
|
+
currency=Config.get("currency"),
|
|
42
|
+
digits=Config.get("money.digits")
|
|
43
|
+
}=options;
|
|
44
|
+
value=value.toString();
|
|
45
|
+
if(value.endsWith(".") && !add_decimals){
|
|
46
|
+
add_decimals=true;
|
|
47
|
+
}
|
|
48
|
+
let amount=Number(value.replace(/[^0-9.]/g,""));
|
|
49
|
+
if(isNaN(amount)){
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
let minimum=0;
|
|
53
|
+
return new Intl.NumberFormat(locale,{
|
|
54
|
+
style:"currency",
|
|
55
|
+
currency,
|
|
56
|
+
minimum,
|
|
57
|
+
digits
|
|
58
|
+
}).format(amount);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static numberRandom(min,max){
|
|
62
|
+
return Math.floor(Math.random()*(max-min+1))+min;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static withinRange(value,min,max){
|
|
66
|
+
return value>=min && value<=max;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static createElement(type,value=null,options={}){
|
|
70
|
+
const el=document.createElement(type,options);
|
|
71
|
+
if(value!==null){
|
|
72
|
+
return el;
|
|
73
|
+
}
|
|
74
|
+
if(Array.isArray(value)){
|
|
75
|
+
value.forEach((item)=>{
|
|
76
|
+
el.appendChild(item);
|
|
77
|
+
});
|
|
78
|
+
}else if(typeof value==="function"){
|
|
79
|
+
value(el);
|
|
80
|
+
}else{
|
|
81
|
+
el.textContent=value;
|
|
82
|
+
}
|
|
83
|
+
return el;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
package/src/core/XHR.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Config from './Config.js';
|
|
2
|
+
import Util from './Util.js';
|
|
3
|
+
|
|
4
|
+
export default class XHR{
|
|
5
|
+
|
|
6
|
+
static request(options){
|
|
7
|
+
options={...Config.get("xhr"),...options};
|
|
8
|
+
const {
|
|
9
|
+
method,
|
|
10
|
+
url,
|
|
11
|
+
uri,
|
|
12
|
+
headers,
|
|
13
|
+
data,
|
|
14
|
+
query,
|
|
15
|
+
files,
|
|
16
|
+
response_type,
|
|
17
|
+
with_credentials,
|
|
18
|
+
timeout,
|
|
19
|
+
retry,
|
|
20
|
+
retry_delay,
|
|
21
|
+
onLoad,
|
|
22
|
+
onData,
|
|
23
|
+
onError,
|
|
24
|
+
onTimeout,
|
|
25
|
+
onProgress,
|
|
26
|
+
onAbort,
|
|
27
|
+
onAction
|
|
28
|
+
}=options;
|
|
29
|
+
url??="/"+uri;
|
|
30
|
+
const xhr=new XMLHttpRequest();
|
|
31
|
+
const query_string=Object.keys(query).length?"?"+Object.entries(query)
|
|
32
|
+
.filter(([_,v])=>v!=null)
|
|
33
|
+
.map(([k,v])=>`${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&"):"";
|
|
34
|
+
xhr.open(method,url+query_string,true);
|
|
35
|
+
xhr.responseType=response_type;
|
|
36
|
+
xhr.withCredentials=with_credentials;
|
|
37
|
+
xhr.timeout=timeout;
|
|
38
|
+
|
|
39
|
+
// Encabezados
|
|
40
|
+
const token=Util.getMeta("csrf-token")??Util.getMeta("csrf_token");
|
|
41
|
+
if(token){
|
|
42
|
+
xhr.setRequestHeader("X-CSRF-Token",token);
|
|
43
|
+
}
|
|
44
|
+
for(let key in headers){
|
|
45
|
+
xhr.setRequestHeader(key,headers[key]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Cuerpo
|
|
49
|
+
let body=null;
|
|
50
|
+
if(method!=='GET'){
|
|
51
|
+
const form_data=new FormData();
|
|
52
|
+
if(token) form_data.append("csrf_token",token);
|
|
53
|
+
form_data.append("_method",method);
|
|
54
|
+
for(let key in data){
|
|
55
|
+
form_data.append(key,data[key]);
|
|
56
|
+
}
|
|
57
|
+
for(let key in files){
|
|
58
|
+
const value=files[key];
|
|
59
|
+
if(Array.isArray(value) || value instanceof FileList){
|
|
60
|
+
for(let index=0; index<value.length; index++){
|
|
61
|
+
form_data.append(`${key}[]`,value[index]);
|
|
62
|
+
}
|
|
63
|
+
}else{
|
|
64
|
+
form_data.append(key,value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
body=form_data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Carga con datos según la respuesta
|
|
71
|
+
xhr.addEventListener("load",()=>{
|
|
72
|
+
onLoad(xhr);
|
|
73
|
+
if(Util.withinRange(xhr.status,200,299)){
|
|
74
|
+
onData(xhr,xhr.response);
|
|
75
|
+
}else{
|
|
76
|
+
onError(xhr.response);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Error
|
|
81
|
+
xhr.addEventListener("error",()=>{
|
|
82
|
+
if(retry){
|
|
83
|
+
setTimeout(()=>{
|
|
84
|
+
XHR.request(options);
|
|
85
|
+
},retry_delay);
|
|
86
|
+
}
|
|
87
|
+
onError(xhr.response);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Aborto
|
|
91
|
+
xhr.addEventListener("abort",()=>{
|
|
92
|
+
onAbort(xhr.response);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Tiempo de espera
|
|
96
|
+
xhr.addEventListener("timeout",()=>{
|
|
97
|
+
if(retry){
|
|
98
|
+
setTimeout(()=>{
|
|
99
|
+
XHR.request(options);
|
|
100
|
+
},retry_delay);
|
|
101
|
+
}
|
|
102
|
+
onTimeout(xhr.response);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Progreso
|
|
106
|
+
xhr.addEventListener("progress",(evt)=>{
|
|
107
|
+
onProgress(evt,evt.loaded,evt.total);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Enviar
|
|
111
|
+
onAction(xhr);
|
|
112
|
+
xhr.send(body);
|
|
113
|
+
return xhr;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Colores de ventanas de mensajes
|
|
2
|
+
$msg-text-error-color: #b94a48;
|
|
3
|
+
$msg-text-success-color: #468847;
|
|
4
|
+
$msg-text-warning-color: #c09853;
|
|
5
|
+
$msg-text-info-color: #3a87ad;
|
|
6
|
+
$msg-background-error-color: #f2dede;
|
|
7
|
+
$msg-background-success-color: #dff0d8;
|
|
8
|
+
$msg-background-warning-color: #fcf8e3;
|
|
9
|
+
$msg-background-info-color: #d9edf7;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use './message.scss' as *;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@use './colors.scss' as *;
|
|
2
|
+
|
|
3
|
+
artha-message{
|
|
4
|
+
width: 100%;
|
|
5
|
+
padding: 10px;
|
|
6
|
+
text-align: left;
|
|
7
|
+
font-size: 1em;
|
|
8
|
+
cursor: default;
|
|
9
|
+
|
|
10
|
+
&[type="error"]{
|
|
11
|
+
background-color: $msg_background_error_color;
|
|
12
|
+
color: $msg_text_error_color;
|
|
13
|
+
}
|
|
14
|
+
&[type="success"]{
|
|
15
|
+
background-color: $msg_background_success_color;
|
|
16
|
+
color: $msg_text_success_color;
|
|
17
|
+
}
|
|
18
|
+
&[type="warning"]{
|
|
19
|
+
background-color: $msg_background_warning_color;
|
|
20
|
+
color: $msg_text_warning_color;
|
|
21
|
+
}
|
|
22
|
+
&[type="info"]{
|
|
23
|
+
background-color: $msg_background_info_color;
|
|
24
|
+
color: $msg_text_info_color;
|
|
25
|
+
}
|
|
26
|
+
}
|