dars-framework 1.2.3__py3-none-any.whl
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.
- dars/__init__.py +0 -0
- dars/all.py +69 -0
- dars/cli/__init__.py +0 -0
- dars/cli/doctor/__init__.py +1 -0
- dars/cli/doctor/detect.py +154 -0
- dars/cli/doctor/doctor.py +176 -0
- dars/cli/doctor/installers.py +100 -0
- dars/cli/doctor/persist.py +62 -0
- dars/cli/doctor/preflight.py +33 -0
- dars/cli/doctor/ui.py +54 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +1107 -0
- dars/cli/preview.py +448 -0
- dars/cli/translations.py +531 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +26 -0
- dars/components/advanced/card.py +33 -0
- dars/components/advanced/modal.py +45 -0
- dars/components/advanced/navbar.py +44 -0
- dars/components/advanced/table.py +25 -0
- dars/components/advanced/tabs.py +31 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +55 -0
- dars/components/basic/checkbox.py +35 -0
- dars/components/basic/container.py +29 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +57 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/markdown.py +86 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +18 -0
- dars/components/basic/radiobutton.py +35 -0
- dars/components/basic/select.py +82 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +12 -0
- dars/components/basic/text.py +23 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +19 -0
- dars/components/layout/__init__.py +0 -0
- dars/components/layout/anchor.py +13 -0
- dars/components/layout/flex.py +26 -0
- dars/components/layout/grid.py +45 -0
- dars/config.py +134 -0
- dars/core/__init__.py +0 -0
- dars/core/app.py +957 -0
- dars/core/component.py +284 -0
- dars/core/events.py +102 -0
- dars/core/js_bridge.py +99 -0
- dars/core/properties.py +127 -0
- dars/core/state.py +309 -0
- dars/dars_tests/apps_test/health_check.py +56 -0
- dars/dars_tests/run_tests.py +275 -0
- dars/dars_tests/tests/test_advanced_components.py +69 -0
- dars/dars_tests/tests/test_basic_components.py +88 -0
- dars/dars_tests/tests/test_core_and_cli.py +17 -0
- dars/dars_tests/tests/test_layout_components.py +58 -0
- dars/dars_tests/tests/test_version_check.py +21 -0
- dars/docs/__init__.py +0 -0
- dars/docs/app.md +290 -0
- dars/docs/cli.md +80 -0
- dars/docs/components.md +1679 -0
- dars/docs/custom_components.md +30 -0
- dars/docs/events.md +45 -0
- dars/docs/exporters.md +162 -0
- dars/docs/getting_started.md +79 -0
- dars/docs/index.md +18 -0
- dars/docs/scripts.md +593 -0
- dars/docs/state_management.md +57 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +96 -0
- dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
- dars/exporters/web/OLD/html_css_js_old.py +1406 -0
- dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +2675 -0
- dars/exporters/web/vdom.py +251 -0
- dars/js_lib.py +206 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/dscript.py +26 -0
- dars/scripts/script.py +39 -0
- dars/security.py +195 -0
- dars/templates/__init__.py +0 -0
- dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- dars/templates/examples/README.md +4 -0
- dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
- dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
- dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
- dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
- dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
- dars/templates/examples/advanced/__init__.py +0 -0
- dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
- dars/templates/examples/basic/Forms/form_components.py +516 -0
- dars/templates/examples/basic/Forms/simple_form.py +379 -0
- dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
- dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
- dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
- dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
- dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
- dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
- dars/templates/examples/basic/__init__.py +0 -0
- dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
- dars/templates/examples/demo/complete_app.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
- dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
- dars/templates/examples/markdown/__init__.py +0 -0
- dars/templates/html/__init__.py +0 -0
- dars/version.py +2 -0
- dars_framework-1.2.3.dist-info/METADATA +15 -0
- dars_framework-1.2.3.dist-info/RECORD +118 -0
- dars_framework-1.2.3.dist-info/WHEEL +5 -0
- dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
- dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
- dars_framework-1.2.3.dist-info/top_level.txt +1 -0
dars/docs/scripts.md
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
# Dars - Script System
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
The script system of Dars allows adding interactive logic and dynamic behaviors to applications. Scripts are written in JavaScript and seamlessly integrate with UI components.
|
|
6
|
+
|
|
7
|
+
## Fundamentals
|
|
8
|
+
|
|
9
|
+
### What are Scripts?
|
|
10
|
+
|
|
11
|
+
Scripts in Dars are fragments of JavaScript code that:
|
|
12
|
+
|
|
13
|
+
- Handle user interface events
|
|
14
|
+
- Implement client-side business logic
|
|
15
|
+
- Provide advanced interactivity
|
|
16
|
+
- Run in the context of the exported application
|
|
17
|
+
|
|
18
|
+
### Types of Scripts
|
|
19
|
+
|
|
20
|
+
Dars supports three main types of scripts:
|
|
21
|
+
|
|
22
|
+
1. **InlineScript**: Code defined directly in Python
|
|
23
|
+
2. **FileScript**: Code loaded from external files
|
|
24
|
+
3. **dScript**: Flexible script that can be defined either inline (as a string) or as a reference to an external file. Only one mode is allowed at a time.
|
|
25
|
+
|
|
26
|
+
## Base Script Class
|
|
27
|
+
|
|
28
|
+
All scripts inherit from the base `Script` class:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from abc import ABC, abstractmethod
|
|
32
|
+
|
|
33
|
+
class Script(ABC):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def get_code(self) -> str:
|
|
39
|
+
"""Retorna el código del script"""
|
|
40
|
+
pass
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## dScript
|
|
44
|
+
|
|
45
|
+
### When to use dScript
|
|
46
|
+
|
|
47
|
+
dScript is a flexible class that allows you to define a script as either:
|
|
48
|
+
- Inline JavaScript (via the `code` argument)
|
|
49
|
+
- Or as a reference to an external file (via the `file_path` argument)
|
|
50
|
+
|
|
51
|
+
But **never both at the same time**. This is useful for presets, user-editable actions, and advanced integrations.
|
|
52
|
+
|
|
53
|
+
### Basic Syntax
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from dars.scripts.dscript import dScript
|
|
57
|
+
|
|
58
|
+
# Inline JS
|
|
59
|
+
script_inline = dScript(code="""
|
|
60
|
+
function hello() { alert('Hello from dScript!'); }
|
|
61
|
+
document.addEventListener('DOMContentLoaded', hello);
|
|
62
|
+
""")
|
|
63
|
+
|
|
64
|
+
# External file
|
|
65
|
+
script_file = dScript(file_path="./scripts/my_script.js")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Example: Editable JS preset from Python
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from dars.scripts.dscript import dScript
|
|
72
|
+
|
|
73
|
+
custom_action = dScript(code="""
|
|
74
|
+
function customClick() {
|
|
75
|
+
alert('Custom action from preset!');
|
|
76
|
+
}
|
|
77
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
78
|
+
var btn = document.getElementById('my-btn');
|
|
79
|
+
if (btn) btn.onclick = customClick;
|
|
80
|
+
});
|
|
81
|
+
""")
|
|
82
|
+
|
|
83
|
+
app.add_script(custom_action)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## InlineScript
|
|
87
|
+
|
|
88
|
+
### Basic Syntax
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from dars.scripts.script import InlineScript
|
|
92
|
+
|
|
93
|
+
script = InlineScript("""
|
|
94
|
+
function saludar() {
|
|
95
|
+
alert('¡Hola desde Dars!');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
99
|
+
console.log('Aplicación cargada');
|
|
100
|
+
});
|
|
101
|
+
""")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Practical Examples
|
|
105
|
+
|
|
106
|
+
#### Button Event Handling
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
script_botones = InlineScript("""
|
|
110
|
+
// Function to handle button clicks
|
|
111
|
+
function manejarClickBoton(evento) {
|
|
112
|
+
const boton = evento.target;
|
|
113
|
+
const texto = boton.textContent;
|
|
114
|
+
|
|
115
|
+
console.log(`Button pressed: ${texto}`);
|
|
116
|
+
|
|
117
|
+
// Change text temporarily
|
|
118
|
+
const textoOriginal = boton.textContent;
|
|
119
|
+
boton.textContent = '¡Presionado!';
|
|
120
|
+
boton.disabled = true;
|
|
121
|
+
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
boton.textContent = textoOriginal;
|
|
124
|
+
boton.disabled = false;
|
|
125
|
+
}, 1000);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add events to all buttons
|
|
129
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
130
|
+
const botones = document.querySelectorAll('button');
|
|
131
|
+
botones.forEach(boton => {
|
|
132
|
+
boton.addEventListener('click', manejarClickBoton);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
""")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Form Validation
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
script_validacion = InlineScript("""
|
|
142
|
+
// Form validation
|
|
143
|
+
function validarFormulario() {
|
|
144
|
+
const inputs = document.querySelectorAll('input[required]');
|
|
145
|
+
let esValido = true;
|
|
146
|
+
|
|
147
|
+
inputs.forEach(input => {
|
|
148
|
+
if (!input.value.trim()) {
|
|
149
|
+
mostrarError(input, 'This field is required');
|
|
150
|
+
esValido = false;
|
|
151
|
+
} else {
|
|
152
|
+
limpiarError(input);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Specific type validation
|
|
156
|
+
if (input.type === 'email' && input.value) {
|
|
157
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
158
|
+
if (!emailRegex.test(input.value)) {
|
|
159
|
+
mostrarError(input, 'Email is invalid');
|
|
160
|
+
esValido = false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return esValido;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function mostrarError(input, mensaje) {
|
|
169
|
+
// Remove previous error
|
|
170
|
+
limpiarError(input);
|
|
171
|
+
|
|
172
|
+
// Create error element
|
|
173
|
+
const error = document.createElement('div');
|
|
174
|
+
error.className = 'error-mensaje';
|
|
175
|
+
error.textContent = mensaje;
|
|
176
|
+
error.style.color = '#dc3545';
|
|
177
|
+
error.style.fontSize = '12px';
|
|
178
|
+
error.style.marginTop = '5px';
|
|
179
|
+
|
|
180
|
+
// Add after the input
|
|
181
|
+
input.parentNode.insertBefore(error, input.nextSibling);
|
|
182
|
+
|
|
183
|
+
// Change input style
|
|
184
|
+
input.style.borderColor = '#dc3545';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function limpiarError(input) {
|
|
188
|
+
const error = input.parentNode.querySelector('.error-mensaje');
|
|
189
|
+
if (error) {
|
|
190
|
+
error.remove();
|
|
191
|
+
}
|
|
192
|
+
input.style.borderColor = '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Configure real-time validation
|
|
196
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
197
|
+
const inputs = document.querySelectorAll('input');
|
|
198
|
+
inputs.forEach(input => {
|
|
199
|
+
input.addEventListener('blur', function() {
|
|
200
|
+
if (this.hasAttribute('required') && !this.value.trim()) {
|
|
201
|
+
mostrarError(this, 'This field is required');
|
|
202
|
+
} else {
|
|
203
|
+
limpiarError(this);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
input.addEventListener('input', function() {
|
|
208
|
+
limpiarError(this);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
""")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Visual Effects and Animations
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
script_animaciones = InlineScript("""
|
|
219
|
+
// Fade in effect for elements
|
|
220
|
+
function fadeIn(elemento, duracion = 500) {
|
|
221
|
+
elemento.style.opacity = '0';
|
|
222
|
+
elemento.style.display = 'block';
|
|
223
|
+
|
|
224
|
+
const inicio = performance.now();
|
|
225
|
+
|
|
226
|
+
function animar(tiempo) {
|
|
227
|
+
const progreso = (tiempo - inicio) / duracion;
|
|
228
|
+
|
|
229
|
+
if (progreso < 1) {
|
|
230
|
+
elemento.style.opacity = progreso;
|
|
231
|
+
requestAnimationFrame(animar);
|
|
232
|
+
} else {
|
|
233
|
+
elemento.style.opacity = '1';
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
requestAnimationFrame(animar);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Typing effect for text
|
|
241
|
+
function efectoTyping(elemento, texto, velocidad = 50) {
|
|
242
|
+
elemento.textContent = '';
|
|
243
|
+
let i = 0;
|
|
244
|
+
|
|
245
|
+
function escribir() {
|
|
246
|
+
if (i < texto.length) {
|
|
247
|
+
elemento.textContent += texto.charAt(i);
|
|
248
|
+
i++;
|
|
249
|
+
setTimeout(escribir, velocidad);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
escribir();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Parallax simple
|
|
257
|
+
function iniciarParallax() {
|
|
258
|
+
window.addEventListener('scroll', function() {
|
|
259
|
+
const scrolled = window.pageYOffset;
|
|
260
|
+
const elementos = document.querySelectorAll('.parallax');
|
|
261
|
+
|
|
262
|
+
elementos.forEach(elemento => {
|
|
263
|
+
const velocidad = elemento.dataset.velocidad || 0.5;
|
|
264
|
+
const yPos = -(scrolled * velocidad);
|
|
265
|
+
elemento.style.transform = `translateY(${yPos}px)`;
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Inicializar efectos
|
|
271
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
272
|
+
// Fade in para todos los elementos con clase 'fade-in'
|
|
273
|
+
const elementosFadeIn = document.querySelectorAll('.fade-in');
|
|
274
|
+
elementosFadeIn.forEach((elemento, index) => {
|
|
275
|
+
setTimeout(() => fadeIn(elemento), index * 200);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Efecto typing para títulos
|
|
279
|
+
const titulos = document.querySelectorAll('.typing-effect');
|
|
280
|
+
titulos.forEach(titulo => {
|
|
281
|
+
const texto = titulo.textContent;
|
|
282
|
+
efectoTyping(titulo, texto);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Inicializar parallax
|
|
286
|
+
iniciarParallax();
|
|
287
|
+
});
|
|
288
|
+
""")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Integration with Exporter
|
|
292
|
+
|
|
293
|
+
The exporter (`html_css_js.py`) automatically detects and exports all scripts of type `dScript`, `InlineScript`, and `FileScript`. You can safely mix and match them in your app, and all will be included in the generated JS.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## FileScript
|
|
298
|
+
|
|
299
|
+
### Basic Syntax
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from dars.scripts.script import FileScript
|
|
303
|
+
|
|
304
|
+
# Load script from file
|
|
305
|
+
script = FileScript("./scripts/mi_script.js")
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### File Organization
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
mi_proyecto/
|
|
312
|
+
├── app.py
|
|
313
|
+
├── scripts/
|
|
314
|
+
│ ├── utils.js
|
|
315
|
+
│ ├── validaciones.js
|
|
316
|
+
│ └── animaciones.js
|
|
317
|
+
└────── api.js
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Example: utils.js
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
// scripts/utils.js
|
|
325
|
+
|
|
326
|
+
// General utilities
|
|
327
|
+
const Utils = {
|
|
328
|
+
// Date formatting
|
|
329
|
+
formatearFecha: function(fecha) {
|
|
330
|
+
return new Intl.DateTimeFormat('es-ES', {
|
|
331
|
+
year: 'numeric',
|
|
332
|
+
month: 'long',
|
|
333
|
+
day: 'numeric'
|
|
334
|
+
}).format(fecha);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// Debounce for event optimization
|
|
338
|
+
debounce: function(func, wait) {
|
|
339
|
+
let timeout;
|
|
340
|
+
return function executedFunction(...args) {
|
|
341
|
+
const later = () => {
|
|
342
|
+
clearTimeout(timeout);
|
|
343
|
+
func(...args);
|
|
344
|
+
};
|
|
345
|
+
clearTimeout(timeout);
|
|
346
|
+
timeout = setTimeout(later, wait);
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
// Email validation
|
|
351
|
+
esEmailValido: function(email) {
|
|
352
|
+
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
353
|
+
return regex.test(email);
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
// Generate unique ID
|
|
357
|
+
generarId: function() {
|
|
358
|
+
return '_'+ Math.random().toString(36).substr(2, 9);
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
// Local storage
|
|
362
|
+
guardarEnLocal: function(clave, valor) {
|
|
363
|
+
try {
|
|
364
|
+
localStorage.setItem(clave, JSON.stringify(valor));
|
|
365
|
+
return true;
|
|
366
|
+
} catch (e) {
|
|
367
|
+
console.error('Error al guardar en localStorage:', e);
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
obtenerDeLocal: function(clave) {
|
|
373
|
+
try {
|
|
374
|
+
const item = localStorage.getItem(clave);
|
|
375
|
+
return item ? JSON.parse(item) : null;
|
|
376
|
+
} catch (e) {
|
|
377
|
+
console.error('Error al leer de localStorage:', e);
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Make available globally
|
|
384
|
+
window.Utils = Utils;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Example: api.js
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
// scripts/api.js
|
|
391
|
+
|
|
392
|
+
// API client
|
|
393
|
+
class ApiClient {
|
|
394
|
+
constructor(baseUrl) {
|
|
395
|
+
this.baseUrl = baseUrl;
|
|
396
|
+
this.headers = {
|
|
397
|
+
'Content-Type': 'application/json'
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async request(endpoint, options = {}) {
|
|
402
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
403
|
+
const config = {
|
|
404
|
+
headers: this.headers,
|
|
405
|
+
...options
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const response = await fetch(url, config);
|
|
410
|
+
|
|
411
|
+
if (!response.ok) {
|
|
412
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return await response.json();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
console.error('Error en la petición:', error);
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async get(endpoint) {
|
|
423
|
+
return this.request(endpoint, { method: 'GET' });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async post(endpoint, data) {
|
|
427
|
+
return this.request(endpoint, {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
body: JSON.stringify(data)
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async put(endpoint, data) {
|
|
434
|
+
return this.request(endpoint, {
|
|
435
|
+
method: 'PUT',
|
|
436
|
+
body: JSON.stringify(data)
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async delete(endpoint) {
|
|
441
|
+
return this.request(endpoint, { method: 'DELETE' });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Global instance
|
|
446
|
+
window.api = new ApiClient('https://api.ejemplo.com');
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Usage in the Application
|
|
450
|
+
|
|
451
|
+
#### Global and Page-specific Scripts (multipage)
|
|
452
|
+
|
|
453
|
+
In multipage applications, you can add global scripts to the App and page-specific scripts to each Page:
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
from dars.scripts.script import InlineScript
|
|
457
|
+
from dars.components.basic import Page, Button, Text
|
|
458
|
+
|
|
459
|
+
home = Page(
|
|
460
|
+
Text("Inicio"),
|
|
461
|
+
Button("Ir a About", id="btn-about")
|
|
462
|
+
)
|
|
463
|
+
home.add_script(InlineScript("""
|
|
464
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
465
|
+
var btn = document.getElementById('btn-about');
|
|
466
|
+
if (btn) btn.onclick = () => window.location.href = 'about.html';
|
|
467
|
+
});
|
|
468
|
+
"""))
|
|
469
|
+
|
|
470
|
+
about = Page(
|
|
471
|
+
Text("Sobre Nosotros"),
|
|
472
|
+
Button("Volver", id="btn-home")
|
|
473
|
+
)
|
|
474
|
+
about.add_script(InlineScript("""
|
|
475
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
476
|
+
var btn = document.getElementById('btn-home');
|
|
477
|
+
if (btn) btn.onclick = () => window.location.href = 'index.html';
|
|
478
|
+
});
|
|
479
|
+
"""))
|
|
480
|
+
|
|
481
|
+
# Global script
|
|
482
|
+
app.add_script(InlineScript("console.log('Script global para todas las páginas');"))
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
When exporting, each page will have its own JS file combining global scripts and page-specific scripts.
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
from dars.scripts.script import FileScript
|
|
489
|
+
|
|
490
|
+
# Load multiple scripts
|
|
491
|
+
app.add_script(FileScript("./scripts/utils.js"))
|
|
492
|
+
app.add_script(FileScript("./scripts/api.js"))
|
|
493
|
+
app.add_script(FileScript("./scripts/validaciones.js"))
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
## Component Integration
|
|
497
|
+
|
|
498
|
+
### Connecting Scripts to Components
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
from dars.core.app import App
|
|
502
|
+
from dars.components.basic.button import Button
|
|
503
|
+
from dars.components.basic.input import Input
|
|
504
|
+
from dars.components.basic.container import Container
|
|
505
|
+
from dars.scripts.script import InlineScript
|
|
506
|
+
|
|
507
|
+
# Create components with specific IDs
|
|
508
|
+
formulario = Container(
|
|
509
|
+
id="formulario-contacto",
|
|
510
|
+
children=[
|
|
511
|
+
Input(
|
|
512
|
+
id="campo-nombre",
|
|
513
|
+
placeholder="Nombre",
|
|
514
|
+
required=True
|
|
515
|
+
),
|
|
516
|
+
Input(
|
|
517
|
+
id="campo-email",
|
|
518
|
+
placeholder="Email",
|
|
519
|
+
input_type="email",
|
|
520
|
+
required=True
|
|
521
|
+
),
|
|
522
|
+
Button(
|
|
523
|
+
id="boton-enviar",
|
|
524
|
+
text="Enviar"
|
|
525
|
+
)
|
|
526
|
+
]
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
# Script that interacts with components
|
|
530
|
+
script_formulario = InlineScript("""
|
|
531
|
+
document.addEventListener(\'DOMContentLoaded\', function() {
|
|
532
|
+
const formulario = document.getElementById(\'formulario-contacto\');
|
|
533
|
+
const campoNombre = document.getElementById(\'campo-nombre\');
|
|
534
|
+
const campoEmail = document.getElementById(\'campo-email\');
|
|
535
|
+
const botonEnviar = document.getElementById(\'boton-enviar\');
|
|
536
|
+
|
|
537
|
+
// Real-time validation
|
|
538
|
+
campoNombre.addEventListener(\'input\', function() {
|
|
539
|
+
validarNombre(this.value);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
campoEmail.addEventListener(\'input\', function() {
|
|
543
|
+
validarEmail(this.value);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Handle form submission
|
|
547
|
+
botonEnviar.addEventListener(\'click\', function(e) {
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
enviarFormulario();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
function validarNombre(nombre) {
|
|
553
|
+
const esValido = nombre.length >= 2;
|
|
554
|
+
campoNombre.style.borderColor = esValido ? \'#28a745\' : \'#dc3545\';
|
|
555
|
+
return esValido;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function validarEmail(email) {
|
|
559
|
+
const regex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
|
|
560
|
+
const esValido = regex.test(email);
|
|
561
|
+
campoEmail.style.borderColor = esValido ? \'#28a745\' : \'#dc3545\';
|
|
562
|
+
return esValido;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function enviarFormulario() {
|
|
566
|
+
const nombre = campoNombre.value;
|
|
567
|
+
const email = campoEmail.value;
|
|
568
|
+
|
|
569
|
+
if (validarNombre(nombre) && validarEmail(email)) {
|
|
570
|
+
// Simular envío
|
|
571
|
+
botonEnviar.textContent = \'Enviando...\';
|
|
572
|
+
botonEnviar.disabled = true;
|
|
573
|
+
|
|
574
|
+
setTimeout(() => {
|
|
575
|
+
alert(\'Formulario enviado correctamente\');
|
|
576
|
+
campoNombre.value = \'\';
|
|
577
|
+
campoEmail.value = \'\';
|
|
578
|
+
botonEnviar.textContent = \'Enviar\';
|
|
579
|
+
botonEnviar.disabled = false;
|
|
580
|
+
}, 2000);
|
|
581
|
+
} else {
|
|
582
|
+
alert(\'Por favor, corrige los errores en el formulario\');
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
""")
|
|
587
|
+
|
|
588
|
+
# Add to the application
|
|
589
|
+
app = App(title="Form with Script")
|
|
590
|
+
app.set_root(form)
|
|
591
|
+
app.add_script(form_script)
|
|
592
|
+
|
|
593
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# State management in Dars (dState, cState, goto, mods)
|
|
2
|
+
|
|
3
|
+
This document describes the new state system available in Dars 1.1.9.
|
|
4
|
+
|
|
5
|
+
- dState(name, component|id, states): declares a state tied to a DOM target (component id).
|
|
6
|
+
- state(idx=None, goto=None, cComp=False, render=None): triggers a state change from Python by producing a JS inline script.
|
|
7
|
+
- cState(idx, mods=[...]): declares rules to execute when entering a state.
|
|
8
|
+
- Mod helpers: inc, dec, set, toggle_class, append_text, prepend_text.
|
|
9
|
+
- goto: absolute (e.g. 2) or relative ("+1", "-1") state jumps.
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
```python
|
|
13
|
+
from dars.all import *
|
|
14
|
+
from dars.core.state import dState, Mod
|
|
15
|
+
|
|
16
|
+
app = App(title="State Demo")
|
|
17
|
+
label = Text("0", id="Counter")
|
|
18
|
+
st = dState("counter", component=label, states=[0,1,2,3])
|
|
19
|
+
|
|
20
|
+
# Rules on state entry
|
|
21
|
+
st.cState(1, mods=[Mod.inc(label, prop='text', by=1)])
|
|
22
|
+
st.cState(2, mods=[Mod.dec(label, prop='text', by=1)])
|
|
23
|
+
st.cState(3, mods=[Mod.toggle_class(label, name='highlight', on=None)])
|
|
24
|
+
|
|
25
|
+
# Buttons to navigate
|
|
26
|
+
next_btn = Button("Next", on_click=st.state(goto='+1'))
|
|
27
|
+
prev_btn = Button("Prev", on_click=st.state(goto='-1'))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Full HTML replacement (custom components)
|
|
31
|
+
If you need full HTML replacement on state change:
|
|
32
|
+
```python
|
|
33
|
+
swap_btn = Button(
|
|
34
|
+
"Swap",
|
|
35
|
+
on_click=st.state(2, cComp=True, render=label.mod(text="SWAPPED"))
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
`render` accepts:
|
|
39
|
+
- A DeferredAttr produced by `component.mod(...)` or `component.attr(..., defer=True)`.
|
|
40
|
+
- A Component instance (will be rendered to HTML at event time).
|
|
41
|
+
- A raw HTML string.
|
|
42
|
+
|
|
43
|
+
## Runtime behavior
|
|
44
|
+
- At export time, state declarations are embedded in the page as a bootstrap JSON.
|
|
45
|
+
- The runtime (dars.min.js) registers states with: id, states, current index, and optional rules.
|
|
46
|
+
- `change({...})` resolves `goto`, updates `current`, applies `rules[<state>].mods` and optional `rules[<state>].goto` (single hop), then dispatches a `CustomEvent('dars:state', ...)`.
|
|
47
|
+
|
|
48
|
+
## Mod operations
|
|
49
|
+
- inc/dec(target, prop='text', by=1): increments or decrements a numeric value (textContent by default).
|
|
50
|
+
- set(target, **attrs): sets attributes; `text` sets textContent; `html` sets innerHTML; other keys map to element attributes.
|
|
51
|
+
- toggle_class(target, name, on=None): toggles a class; when `on` is True/False, forces add/remove.
|
|
52
|
+
- append_text / prepend_text: concatenates to textContent.
|
|
53
|
+
|
|
54
|
+
## Best practices
|
|
55
|
+
- Keep the label text purely numeric if you plan to use `inc/dec` on `text`.
|
|
56
|
+
- Use `goto` in rules to avoid infinite accumulation when staying at the same state.
|
|
57
|
+
- Prefer `mods` for small changes; use `cComp=True` only when you need full HTML replacement.
|
|
File without changes
|