@livinglogic/livingapi 0.10.0 → 0.13.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/src/livingapi.js CHANGED
@@ -1,15 +1,50 @@
1
- // Version of this Javascipt package
1
+ /*!
2
+ * LivingAPI JavaScript Library
3
+ * https://my.living-apps.de/docs/LivingAPI.html
4
+ *
5
+ * Copyright 2017-2019 by LivingLogic AG, Bayreuth/Germany
6
+ * Copyright 2017-2019 by Walter Dörwald
7
+ *
8
+ * All Rights Reserved
9
+ *
10
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ * of this software and associated documentation files (the "Software"), to deal
12
+ * in the Software without restriction, including without limitation the rights
13
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ * copies of the Software, and to permit persons to whom the Software is
15
+ * furnished to do so, subject to the following conditions:
16
+ *
17
+ * The above copyright notice and this permission notice shall be included in
18
+ * all copies or substantial portions of the Software.
19
+ *
20
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26
+ * THE SOFTWARE.
27
+ */
28
+
29
+ // Version of this Javascript package
2
30
  import { version } from '../package.json';
3
31
 
4
32
  export { version };
5
33
 
6
34
  import * as ul4 from '@livinglogic/ul4';
7
35
 
36
+
8
37
  export class Base extends ul4.Proto
9
38
  {
10
- constructor()
39
+ constructor(id)
11
40
  {
12
41
  super();
42
+ this.id = id;
43
+ }
44
+
45
+ get ul4onid()
46
+ {
47
+ return this.id;
13
48
  }
14
49
 
15
50
  ul4ondump(encoder)
@@ -48,7 +83,7 @@ export class Base extends ul4.Proto
48
83
  this[name] = null;
49
84
  }
50
85
 
51
- __getattr__(name)
86
+ [ul4.symbols.getattr](name)
52
87
  {
53
88
  if (this._ul4attrs.has(name))
54
89
  {
@@ -67,12 +102,18 @@ export class Base extends ul4.Proto
67
102
  throw new ul4.AttributeError(this, name);
68
103
  }
69
104
 
70
- __repr__()
105
+ [ul4.symbols.repr]()
71
106
  {
72
107
  return "<" + this.constructor.name + ">";
73
108
  }
109
+
110
+ toString()
111
+ {
112
+ return this[ul4.symbols.repr]();
113
+ }
74
114
  };
75
115
 
116
+
76
117
  export class Handler
77
118
  {
78
119
  save(record)
@@ -84,14 +125,21 @@ export class Handler
84
125
  }
85
126
  };
86
127
 
128
+
87
129
  export class Globals extends Base
88
130
  {
89
- constructor()
131
+ static classdoc = "Global information";
132
+
133
+ constructor(id)
90
134
  {
91
- super();
135
+ super(id);
92
136
  this.version = null;
137
+ this.hostname = null;
93
138
  this.platform = null;
94
139
  this.user = null;
140
+ this.lang = null;
141
+ this.app = null;
142
+ this.record = null;
95
143
  this.maxdbactions = null;
96
144
  this.maxtemplateruntime = null;
97
145
  this.flashmessages = null;
@@ -125,67 +173,137 @@ export class Globals extends Base
125
173
  return s;
126
174
  }
127
175
 
128
- __repr__()
176
+ scaledURL(url, width, height, type="fill", enlarge=true, gravity="sm", quality=null, rotate=null, blur=null, sharpen=null, format=null, cache=true)
177
+ {
178
+ let filename = null;
179
+
180
+ if (url instanceof File)
181
+ {
182
+ url = "https://" + this.hostname + url.url;
183
+ filename = encodeURIComponent(url.filename);
184
+ if (filename != url.filename)
185
+ filename = null;
186
+ }
187
+
188
+ let result = "";
189
+
190
+ if (cache)
191
+ result += "/imgproxycache/insecure";
192
+ else
193
+ result += "/imgproxy/insecure";
194
+
195
+ if (type !== "fit")
196
+ result += "/rt:" + type;
197
+
198
+ if (width !== null && width !== undefined)
199
+ result += "/w:" + width;
200
+
201
+ if (height !== null && height !== undefined)
202
+ result += "/h:" + height;
203
+
204
+ if (enlarge)
205
+ result += "/el:1";
206
+
207
+ if (gravity !== null && gravity !== undefined)
208
+ result += "/g:" + gravity;
209
+
210
+ if (quality !== null && quality !== undefined)
211
+ result += "/q:" + quality;
212
+
213
+ if (rotate !== null && rotate % 360 != 0)
214
+ result += "/rot:" + rotatet;
215
+
216
+ if (blur !== null && blur !== undefined)
217
+ result += "/bl:" + blur;
218
+
219
+ if (sharpen !== null && sharpen !== undefined)
220
+ result += "/sh:" + sharpen;
221
+
222
+ if (format !== null && format !== undefined)
223
+ result += "/f:" + format;
224
+
225
+ if (filename !== null && filename !== undefined)
226
+ result += "/fn:" + filename;
227
+
228
+ result += "/plain/" + encodeURIComponent(url);
229
+
230
+ return result;
231
+ }
232
+
233
+ [ul4.symbols.repr]()
129
234
  {
130
235
  return "<Globals version=" + ul4._repr(this.version) + ">";
131
236
  }
132
237
  };
133
238
 
134
- Globals.prototype._ul4onattrs = ["version", "platform", "user", "maxdbactions", "maxtemplateruntime", "flashmessages"];
135
- Globals.prototype._ul4attrs = ul4._makeset("version", "platform", "user", "maxdbactions", "maxtemplateruntime", "flashmessages");
239
+ Globals.prototype._ul4onattrs = ["version", "platform", "user", "maxdbactions", "maxtemplateruntime", "flashmessages", "lang", "datasources", "hostname", "app", "record", "google_api_key", "mode", "view_template_id", "email_template_id", "view_id"];
240
+ Globals.prototype._ul4attrs = new Set(["version", "hostname", "platform", "user", "lang", "app", "record", "maxdbactions", "maxtemplateruntime", "flashmessages", "mode"]);
241
+
136
242
 
137
243
  export class FlashMessage extends Base
138
244
  {
139
- __repr__()
245
+ static classdoc = "A flash message in a web page";
246
+
247
+ [ul4.symbols.repr]()
140
248
  {
141
249
  return "<FlashMessage type=" + ul4._repr(this.type) + " title=" + ul4._repr(this.title) + ">";
142
250
  }
143
251
  };
144
252
 
145
253
  FlashMessage.prototype._ul4onattrs = ["timestamp", "type", "title", "message"];
146
- FlashMessage.prototype._ul4attrs = ul4._makeset("timestamp", "type", "title", "message");
254
+ FlashMessage.prototype._ul4attrs = new Set(["timestamp", "type", "title", "message"]);
255
+
147
256
 
148
257
  export class App extends Base
149
258
  {
150
- __repr__()
259
+ static classdoc = "A LivingApps application";
260
+
261
+ [ul4.symbols.repr]()
151
262
  {
152
263
  return "<App id=" + ul4._repr(this.id) + " name=" + ul4._repr(this.name) + ">";
153
264
  }
154
265
 
155
266
  insert(values={})
156
267
  {
157
- let record = this.__call__(values);
268
+ let record = this[ul4.symbols.call](values);
158
269
  this.globals.handler.save(this);
159
270
  return record;
160
271
  }
161
272
 
162
- __call__(values={})
273
+ get layout_controls()
274
+ {
275
+ if (this.active_view === null)
276
+ return new Map();
277
+ return this.active_view.layout_controls;
278
+ }
279
+
280
+ [ul4.symbols.call](values={})
163
281
  {
164
- let record = new Record(this);
282
+ let record = new Record(null, this);
165
283
  if (ul4._ismap(values))
166
284
  {
167
- for (let [key, value] of values.entries())
285
+ for (let identifier of values.keys())
168
286
  {
169
- if (!record.fields.has(key))
170
- throw new ul4.ArgumentError("update() get an unexpected keyword argument " + ul4._repr(key));
171
- record.fields.get(key).value = value;
287
+ if (!record.fields.has(identifier))
288
+ throw new ul4.ArgumentError(ul4._repr(this) + "() get an unexpected keyword argument " + ul4._repr(identifier));
172
289
  }
290
+ record._make_fields(true, values, null, null);
173
291
  }
174
292
  else if (ul4._isobject(values))
175
293
  {
176
- for (let key in values)
294
+ for (let identifier in values)
177
295
  {
178
- if (!record.fields.has(key))
179
- throw new ul4.ArgumentError("update() get an unexpected keyword argument " + ul4._repr(key));
180
- record.fields.get(key).value = values[key];
296
+ if (!record.fields.has(identifier))
297
+ throw new ul4.ArgumentError(ul4._repr(this) + "() get an unexpected keyword argument " + ul4._repr(identifier));
181
298
  }
299
+ record._make_fields(true, new Map(ul4._list(ul4.dicttype.items(values))), null, null);
182
300
  }
183
301
  else
184
302
  throw new ul4.TypeError("values must be an object or a Map");
185
303
  return record;
186
304
  }
187
305
 
188
- __getattr__(name)
306
+ [ul4.symbols.getattr](name)
189
307
  {
190
308
  if (name.startsWith("c_"))
191
309
  {
@@ -194,43 +312,83 @@ export class App extends Base
194
312
  return this.controls.get(name.substr(2));
195
313
  }
196
314
  else
197
- return super.__getattr__(name);
315
+ return super[ul4.symbols.getattr](name);
316
+ }
317
+
318
+ get active_view()
319
+ {
320
+ return this._active_view;
321
+ }
322
+
323
+ set active_view(view)
324
+ {
325
+ if (view === null)
326
+ ;
327
+ else if (typeof(view) === "string")
328
+ {
329
+ if (this.views !== null && this.views.has(view))
330
+ view = this.views.get(view);
331
+ else
332
+ throw new ul4.ValueError("View " + ul4._repr(view) + " not found!");
333
+ }
334
+ else if (view instanceof View)
335
+ {
336
+ if (view.app === this)
337
+ ;
338
+ else
339
+ throw new ul4.ValueError("View " + ul4._repr(view) + " belongs to the wrong app!");
340
+ }
341
+ else
342
+ {
343
+ throw new ul4.TypeError(ul4._repr(view) + " is not a view!");
344
+ }
345
+
346
+ this._active_view = view;
198
347
  }
199
348
  };
200
349
 
201
- App.prototype._ul4onattrs = ["id", "globals", "name", "description", "language", "startlink", "iconlarge", "iconsmall", "createdby", "controls", "records", "recordcount", "installation", "categories", "params", "views", "datamanagement_identifier", "basetable", "primarykey", "insertprocedure", "updateprocedure", "deleteprocedure", "templates", "createdat", "updatedat", "updatedby"];
202
- App.prototype._ul4attrs = ul4._makeset("id", "globals", "name", "description", "language", "startlink", "iconlarge", "iconsmall", "createdat", "createdby", "updatedat", "updatedby", "controls", "records", "recordcount", "installation", "categories", "params", "views", "datamanagement_identifier", "insert");
203
- ul4.expose(App.prototype.__call__, ["**values"], {"needsobject": true});
350
+
351
+ App.prototype._ul4onattrs = ["globals", "name", "description", "lang", "startlink", "iconlarge", "iconsmall", "createdby", "controls", "records", "recordcount", "installation", "categories", "params", "views", "datamanagement_identifier", "basetable", "primarykey", "insertprocedure", "updateprocedure", "deleteprocedure", "templates", "createdat", "updatedat", "updatedby", "superid", "favorite", "_active_view"];
352
+ App.prototype._ul4attrs = new Set(["id", "globals", "name", "description", "lang", "startlink", "iconlarge", "iconsmall", "createdat", "createdby", "updatedat", "updatedby", "controls", "records", "recordcount", "installation", "categories", "params", "views", "datamanagement_identifier", "insert", "favorite", "_active_view"]);
353
+ ul4.expose(App.prototype[ul4.symbols.call], ["**values"], {"needsobject": true});
204
354
  ul4.expose(App.prototype.insert, ["**values"], {"needsobject": true});
205
355
 
356
+
206
357
  export class View extends Base
207
358
  {
208
- __repr__()
359
+ static classdoc = "An input form for a LivingApps application";
360
+
361
+ [ul4.symbols.repr]()
209
362
  {
210
363
  return "<View id=" + ul4._repr(this.id) + " name=" + ul4._repr(this.name) + ">";
211
364
  }
212
365
  };
213
366
 
214
- View.prototype._ul4onattrs = ["id", "name", "app", "order", "width", "height", "start", "end"];
215
- View.prototype._ul4attrs = ul4._makeset("id", "name", "app", "order", "width", "height", "start", "end");
367
+ View.prototype._ul4onattrs = ["name", "combined_type", "app", "order", "width", "height", "start", "end", "controls", "layout_controls", "lang"];
368
+ View.prototype._ul4attrs = new Set(["id", "name", "combined_type", "app", "order", "width", "height", "start", "end", "controls", "layout_controls", "lang"]);
369
+
216
370
 
217
371
  export class DataSourceData extends Base
218
372
  {
219
- __repr__()
373
+ static classdoc = "The data resulting from a data source configuration";
374
+
375
+ [ul4.symbols.repr]()
220
376
  {
221
377
  return "<DataSource.Data id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
222
378
  }
223
379
  };
224
380
 
225
- DataSourceData.prototype._ul4onattrs = ["id", "identifier", "app", "apps"];
226
- DataSourceData.prototype._ul4attrs = ul4._makeset("id", "identifier", "app", "apps");
381
+ DataSourceData.prototype._ul4onattrs = ["identifier", "app", "apps"];
382
+ DataSourceData.prototype._ul4attrs = new Set(["id", "identifier", "app", "apps"]);
383
+
227
384
 
228
385
  export class Record extends Base
229
386
  {
230
- constructor(app)
387
+ static classdoc = "A record of a LivingApp application";
388
+
389
+ constructor(id, app)
231
390
  {
232
- super();
233
- this.id = null;
391
+ super(id);
234
392
  this.app = app;
235
393
  this.createdat = null;
236
394
  this.createdby = null;
@@ -243,15 +401,47 @@ export class Record extends Base
243
401
  this.children = new Map();
244
402
  this.attachments = null;
245
403
  this.errors = [];
404
+ this._sparsefielderrors = null;
405
+ this._sparsefieldlookupdata = null;
246
406
  this._is_deleted = false;
407
+ this._is_new = true;
408
+ }
409
+
410
+ _make_fields(defaults, values, errors, lookupdata)
411
+ {
412
+ let fields = new Map();
413
+
414
+ for (let control of this.app.controls.values())
415
+ {
416
+ let identifier = control.identifier;
417
+ let value = null;
418
+ if (values !== null)
419
+ {
420
+ if (defaults && !values.has(identifier))
421
+ value = control.default;
422
+ else if (values.has(identifier))
423
+ value = values.get(identifier);
424
+ }
425
+ let field = new Field(control, this, value);
426
+ fields.set(identifier, field);
427
+ if (errors !== null && errors.has(identifier))
428
+ field.errors = errors.get(identifier);
429
+ if (lookupdata !== null && lookupdata.has(identifier))
430
+ field.lookupdata = lookupdata.get(identifier);
431
+ }
432
+
433
+ this._fields = fields;
434
+ this._sparsevalues = null;
435
+ this._sparsefielderrors = null;
436
+ this._sparselookupdata = null;
247
437
  }
248
438
 
249
- __repr__()
439
+ [ul4.symbols.repr]()
250
440
  {
251
441
  let v = ["<Record id=", ul4._repr(this.id)];
252
442
  for (let field of this.fields.values())
253
443
  {
254
- if (field.control.priority)
444
+ if (field.control.priority && !field.is_empty())
255
445
  {
256
446
  v.push(" v_");
257
447
  v.push(field.control.identifier);
@@ -259,21 +449,32 @@ export class Record extends Base
259
449
  v.push(ul4._repr(field.value)); // FIXME: This might lead to infinite recursions
260
450
  }
261
451
  }
262
- v.push(">")
452
+ v.push(" state=");
453
+ v.push(ul4._repr(this.state));
454
+ v.push(">");
263
455
  return v.join("");
264
456
  }
265
457
 
458
+ get state()
459
+ {
460
+ if (this._is_deleted)
461
+ return "deleted";
462
+ else if (this._is_new)
463
+ return "new";
464
+ else if (this.is_dirty())
465
+ return "dirty";
466
+ else
467
+ return "saved";
468
+ }
469
+
266
470
  get values()
267
471
  {
268
472
  if (this._values === null)
269
473
  {
270
- this._values = ul4._havemap ? new Map() : {};
271
- for (let [identifier, control] of this.app.controls.entries())
474
+ this._values = new Map();
475
+ for (let field of this.fields.values())
272
476
  {
273
- let fieldvalue = this._sparsevalues.get(identifier);
274
- if (typeof(fieldvalue) === "undefined")
275
- fieldvalue = null;
276
- this._values.set(identifier, fieldvalue);
477
+ this._values.set(field.control.identifier, field.value);
277
478
  }
278
479
  }
279
480
  return this._values;
@@ -282,14 +483,7 @@ export class Record extends Base
282
483
  get fields()
283
484
  {
284
485
  if (this._fields === null)
285
- {
286
- this._fields = ul4._havemap ? new Map() : {};
287
- for (let [identifier, value] of this.values.entries())
288
- {
289
- let field = new Field(this.app.controls.get(identifier), this, value);
290
- this._fields.set(identifier, field);
291
- }
292
- }
486
+ this._make_fields(false, this._sparsevalues, this._sparsefielderrors, this._sparsefieldlookupdata)
293
487
  return this._fields;
294
488
  }
295
489
 
@@ -319,7 +513,9 @@ export class Record extends Base
319
513
 
320
514
  delete()
321
515
  {
322
- return this.app.globals.handler.delete(this);
516
+ let result = this.app.globals.handler.delete(this);
517
+ this._is_deleted = true;
518
+ return result;
323
519
  }
324
520
 
325
521
  save()
@@ -367,6 +563,12 @@ export class Record extends Base
367
563
  return true;
368
564
  }
369
565
 
566
+ ul4onload(decoder)
567
+ {
568
+ super.ul4onload(decoder);
569
+ this._is_new = false;
570
+ }
571
+
370
572
  _dumpUL4ONAttr(name)
371
573
  {
372
574
  if (name === "values")
@@ -375,54 +577,879 @@ export class Record extends Base
375
577
  return this[name];
376
578
  }
377
579
 
378
- _loadUL4ONAttr(name, value)
580
+ _loadUL4ONAttr(name, value)
581
+ {
582
+ if (name === "values")
583
+ {
584
+ this._sparsevalues = value;
585
+ this._values = null;
586
+ this._fields = null;
587
+ }
588
+ else
589
+ this[name] = value;
590
+ }
591
+
592
+ [ul4.symbols.getattr](name)
593
+ {
594
+ if (name.startsWith("c_"))
595
+ return this.children.get(name.substr(2))
596
+ else if (name.startsWith("f_"))
597
+ return this.fields.get(name.substr(2))
598
+ else if (name.startsWith("v_"))
599
+ return this.values.get(name.substr(2))
600
+ else
601
+ return this[name];
602
+ }
603
+
604
+ [ul4.symbols.setattr](name, value)
605
+ {
606
+ if (name.startsWith("c_"))
607
+ this.children[name.substr(2)] = value;
608
+ else if (name.startsWith("v_"))
609
+ this.fields.get(name.substr(2)).value = value;
610
+ else
611
+ throw new ul4.AttributeError(this, name);
612
+ }
613
+ };
614
+
615
+ Record.prototype._ul4onattrs = ["app", "createdat", "createdby", "updatedat", "updatedby", "updatecount", "_sparsevalues", "attachments", "children", "errors", "_sparsefielderrors", "_sparsefieldlookupdata"];
616
+ Record.prototype._ul4attrs = new Set(["id", "app", "createdat", "createdby", "updatedat", "updatedby", "updatecount", "values", "attachments", "children", "errors"]);
617
+ ul4.expose(Record.prototype.is_dirty, []);
618
+ ul4.expose(Record.prototype.has_errors, []);
619
+ ul4.expose(Record.prototype.delete, []);
620
+ ul4.expose(Record.prototype.save, []);
621
+ ul4.expose(Record.prototype.update, ["**values"], {"needsobject": true});
622
+
623
+
624
+ export class Field extends Base
625
+ {
626
+ static classdoc = "Holds the value of a field of a record (and related information)";
627
+
628
+ constructor(control, record, value)
629
+ {
630
+ super(null);
631
+ this.control = control;
632
+ this.record = record;
633
+ this._label = null;
634
+ this._value = value;
635
+ this._dirty = false;
636
+ this._lookupdate = null;
637
+ this.errors = [];
638
+ }
639
+
640
+ get value()
641
+ {
642
+ return this._value;
643
+ }
644
+
645
+ set value(value)
646
+ {
647
+ let oldvalue = this._value;
648
+
649
+ let change = {value: value, errors: []};
650
+ this._validate(change);
651
+
652
+ if (ul4._ne(oldvalue, change.value))
653
+ {
654
+ this.record.values.set(this.control.identifier, change.value);
655
+ this._value = change.value;
656
+ this._dirty = true;
657
+ }
658
+ this.errors = change.errors;
659
+ }
660
+
661
+ _validate(change)
662
+ {
663
+ // Do nothing, i.e. accept the value unchanged and without errors
664
+ }
665
+
666
+ get label()
667
+ {
668
+ if (this._label !== null)
669
+ return this._label;
670
+ return this.control.label;
671
+ }
672
+
673
+ set label(value)
674
+ {
675
+ this._label = value;
676
+ }
677
+
678
+ get globals()
679
+ {
680
+ return this.control.globals;
681
+ }
682
+
683
+ is_empty()
684
+ {
685
+ return this._value === null || (ul4._islist(this._value) && this._value.length === 0);
686
+ }
687
+
688
+ is_dirty()
689
+ {
690
+ return this._dirty;
691
+ }
692
+
693
+ has_errors()
694
+ {
695
+ return this.errors.length !== 0;
696
+ }
697
+
698
+ search(searchvalue)
699
+ {
700
+ return this.control.search(this.value, searchvalue);
701
+ }
702
+
703
+ msg_field_required(value)
704
+ {
705
+ switch (this.globals.lang)
706
+ {
707
+ case "de":
708
+ return `"${this.label}" wird benötigt!`;
709
+ default:
710
+ return `"${this.label}" is required!`;
711
+ }
712
+ }
713
+
714
+ msg_field_wrong_type(value)
715
+ {
716
+ return `"${this.label}" doesn't support the type ${ul4._type(value).name}!`;
717
+ }
718
+
719
+ msg_bool_truerequired(value)
720
+ {
721
+ return `"${this.label}" only accepts "Yes"!`;
722
+ }
723
+
724
+ msg_string_tooshort(value)
725
+ {
726
+ return `"${this.label}" is too short. Tou must use at least ${this.control.minlength} characters!`;
727
+ }
728
+
729
+ msg_string_toolong(value)
730
+ {
731
+ return `"${this.label}" is too long. You may use at most ${this.control.maxlength} characters!`;
732
+ }
733
+
734
+ msg_email_format(value)
735
+ {
736
+ return `"${this.label}" must be a valid email address!`;
737
+ }
738
+
739
+ msg_url_format(value)
740
+ {
741
+ return `"${this.label}" must be a valid URL!`;
742
+ }
743
+
744
+ msg_tel_format(value)
745
+ {
746
+ return `"${this.label}" must be a valid phone number!`;
747
+ }
748
+
749
+ msg_date_format(value)
750
+ {
751
+ return `"${this.label}" doesn't support this date format!`;
752
+ }
753
+
754
+ msg_lookup_wrongitem(value)
755
+ {
756
+ return `The option ${ul4._repr(value)} for "${this.label}" doesn't belong to this lookup!`;
757
+ }
758
+
759
+ msg_lookup_unknownkey(value)
760
+ {
761
+ return `The option ${ul4._repr(value)} for "${this.label}" is unknown!`;
762
+ }
763
+
764
+ msg_applookup_wrongrecord(value)
765
+ {
766
+ return `The record ${ul4._repr(value)} for "${this.label}" doesn't belong to the correct app!`;
767
+ }
768
+
769
+ msg_applookup_unknownkey(value)
770
+ {
771
+ return `The record ${ul4._repr(value)} for "${this.label}" can't be found!`;
772
+ }
773
+
774
+ [ul4.symbols.repr]()
775
+ {
776
+ let s = "<Field identifier=";
777
+ s += ul4._repr(this.control.identifier)
778
+ if (this._dirty)
779
+ s += " is_dirty()=True";
780
+ if (this.errors.length > 0)
781
+ s += " has_errors()=True";
782
+ s += ">"
783
+ return s;
784
+ }
785
+ };
786
+
787
+ Field.prototype._ul4onattrs = ["control", "record", "label", "value", "errors", "enabled", "writable", "visible"];
788
+ Field.prototype._ul4attrs = new Set(["control", "record", "label", "value", "errors", "enabled", "writable", "visible"]);
789
+
790
+
791
+ export class BoolField extends Field
792
+ {
793
+ static classdoc = "Holds the value of a bool field of a record (and related information)";
794
+
795
+ _validate(change)
796
+ {
797
+ if (change.value === null)
798
+ {
799
+ if (this.control.required)
800
+ change.errors.push(this.msg_field_required(change.value));
801
+ }
802
+ else if (typeof(change.value) === "boolean")
803
+ {
804
+ if (this.control.required && !change.value)
805
+ change.errors.push(this.msg_bool_truerequired(change.value));
806
+ }
807
+ else
808
+ {
809
+ change.errors.push(this.msg_field_wrong_type(change.value));
810
+ change.value = null;
811
+ }
812
+ }
813
+ };
814
+
815
+
816
+ export class IntField extends Field
817
+ {
818
+ static classdoc = "Holds the value of an int field of a record (and related information)";
819
+
820
+ _validate(change)
821
+ {
822
+ if (change.value === null)
823
+ {
824
+ if (this.control.required)
825
+ change.errors.push(this.msg_field_required(change.value));
826
+ }
827
+ else if (typeof(change.value) !== "number" || Math.round(change.value) !== change.value)
828
+ {
829
+ change.errors.push(this.msg_field_wrong_type(change.value));
830
+ }
831
+ }
832
+ };
833
+
834
+
835
+ export class NumberField extends Field
836
+ {
837
+ static classdoc = "Holds the value of a number field of a record (and related information)";
838
+
839
+ _validate(change)
840
+ {
841
+ if (change.value === null || change.value === "")
842
+ {
843
+ if (this.control.required)
844
+ change.errors.push(this.msg_field_required(change.value));
845
+ change.value = null;
846
+ }
847
+ else if (typeof(change.value) !== "number")
848
+ {
849
+ change.errors.push(this.msg_field_wrong_type(change.value));
850
+ }
851
+ }
852
+ };
853
+
854
+
855
+ export class StringField extends Field
856
+ {
857
+ static classdoc = "Holds the value of a string field of a record (and related information)";
858
+
859
+ _validate(change)
860
+ {
861
+ if (change.value === null || change.value === "")
862
+ {
863
+ if (this.control.required)
864
+ change.errors.push(this.msg_field_required(change.value));
865
+ change.value = null;
866
+ }
867
+ else if (typeof(change.value) === "string")
868
+ {
869
+ let minlength = this.control.minlength;
870
+ let maxlength = this.control.maxlength;
871
+ if (minlength !== null && change.value.length < minlength)
872
+ change.errors.push(this.msg_string_tooshort(change.value))
873
+ if (maxlength !== null && change.value.length > maxlength)
874
+ change.errors.push(this.msg_string_toolong(change.value))
875
+ }
876
+ else
877
+ {
878
+ change.errors.push(this.msg_field_wrong_type(change.value));
879
+ change.value = null;
880
+ }
881
+ }
882
+ };
883
+
884
+
885
+ export class EmailField extends StringField
886
+ {
887
+ static classdoc = "Holds the value of a string/email field of a record (and related information)";
888
+
889
+ _validate(change)
890
+ {
891
+ if (typeof(change.value) === "string" && change.value.length > 0)
892
+ {
893
+ const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i;
894
+ if (!re.test(change.value))
895
+ change.errors.push(this.msg_email_format(change.value));
896
+ }
897
+ super._validate(change);
898
+ }
899
+ };
900
+
901
+
902
+ export class URLField extends StringField
903
+ {
904
+ static classdoc = "Holds the value of a string/url field of a record (and related information)";
905
+
906
+ _validate(change)
907
+ {
908
+ if (typeof(change.value) === "string" && change.value.length > 0)
909
+ {
910
+ let url = null;
911
+ try
912
+ {
913
+ url = new URL(change.value);
914
+ }
915
+ catch (exc)
916
+ {
917
+ change.errors.push(this.msg_url_format(change.value));
918
+ }
919
+ if (url !== null && url.protocol !== "http:" && url.protocol !== "https:")
920
+ {
921
+ change.errors.push(this.msg_url_format(change.value));
922
+ }
923
+ }
924
+ super._validate(change);
925
+ }
926
+ };
927
+
928
+
929
+ export class TelField extends StringField
930
+ {
931
+ static classdoc = "Holds the value of a string/tel field of a record (and related information)";
932
+
933
+ _validate(change)
934
+ {
935
+ if (typeof(change.value) === "string" && change.value.length > 0)
936
+ {
937
+ const re = /^\\+?[0-9 /()-]+$/;
938
+ if (!re.test(change.value))
939
+ change.errors.push(this.msg_tel_format(change.value));
940
+ }
941
+ super._validate(change);
942
+ }
943
+ };
944
+
945
+
946
+ export class TextAreaField extends StringField
947
+ {
948
+ static classdoc = "Holds the value of a string/textarea field of a record (and related information)";
949
+ };
950
+
951
+
952
+ export class HTMLField extends StringField
953
+ {
954
+ static classdoc = "Holds the value of a string/html field of a record (and related information)";
955
+ };
956
+
957
+
958
+ export class GeoField extends Field
959
+ {
960
+ static classdoc = "Holds the value of a geo field of a record (and related information)";
961
+
962
+ _validate(change)
963
+ {
964
+ if (change.value === null || change.value === "")
965
+ {
966
+ if (this.control.required)
967
+ change.errors.push(this.msg_field_required(change.value));
968
+ change.value = null;
969
+ }
970
+ else if (!(change.value instanceof Geo))
971
+ {
972
+ change.errors.push(this.msg_field_wrong_type(change.value));
973
+ change.value = null;
974
+ }
975
+ }
976
+ };
977
+
978
+
979
+ export class FileField extends Field
980
+ {
981
+ static classdoc = "Holds the value of a file field of a record (and related information)";
982
+
983
+ _validate(change)
984
+ {
985
+ if (change.value === null || change.value === "")
986
+ {
987
+ if (this.control.required)
988
+ change.errors.push(this.msg_field_required(change.value));
989
+ change.value = null;
990
+ }
991
+ else if (!(change.value instanceof File))
992
+ {
993
+ change.errors.push(this.msg_field_wrong_type(change.value));
994
+ change.value = null;
995
+ }
996
+ }
997
+ };
998
+
999
+
1000
+ export class FileSignatureField extends FileField
1001
+ {
1002
+ static classdoc = "Holds the value of a file/signature field of a record (and related information)";
1003
+
1004
+ _validate(change)
1005
+ {
1006
+ // FIXME: Implement handling of data URLs.
1007
+ super._validate(change);
1008
+ }
1009
+ };
1010
+
1011
+
1012
+ export class DateFieldBase extends Field
1013
+ {
1014
+ static classdoc = "Holds the value of a date field of a record (and related information)";
1015
+
1016
+ _validate(change)
1017
+ {
1018
+ if (change.value === null || change.value === "")
1019
+ {
1020
+ if (this.control.required)
1021
+ change.errors.push(this.msg_field_required(change.value));
1022
+ change.value = null;
1023
+ }
1024
+ else if (change.value instanceof Date)
1025
+ {
1026
+ change.value = this._convert(new Date(change.value.getTime()));
1027
+ }
1028
+ else if (typeof(change.value) === "string")
1029
+ {
1030
+ let value = null;
1031
+ if (change.value.length === 10)
1032
+ value = new Date(change.value + "T00:00:00");
1033
+ else if (change.value.length === 16)
1034
+ value = new Date(change.value + ":00");
1035
+ else if (change.value.length === 19)
1036
+ value = new Date(change.value);
1037
+
1038
+ if (value === null || Number.isNaN(value.getYear()))
1039
+ {
1040
+ change.errors.push(this.msg_date_format(change.value));
1041
+ // Keep original invalid string
1042
+ }
1043
+ else
1044
+ {
1045
+ change.value = this._convert(value);
1046
+ }
1047
+ }
1048
+ else
1049
+ {
1050
+ change.errors.push(this.msg_field_wrong_type(change.value));
1051
+ change.value = null;
1052
+ }
1053
+ }
1054
+ };
1055
+
1056
+
1057
+ export class DateField extends DateFieldBase
1058
+ {
1059
+ static classdoc = "Holds the value of a date/date field of a record (and related information)";
1060
+
1061
+ _convert(date)
1062
+ {
1063
+ return new ul4.Date_(date.getFullYear(), date.getMonth()+1, date.getDate());
1064
+ }
1065
+ };
1066
+
1067
+
1068
+ export class DatetimeMinuteField extends DateFieldBase
1069
+ {
1070
+ static classdoc = "Holds the value of a date/datetimeminute field of a record (and related information)";
1071
+
1072
+ _convert(date)
1073
+ {
1074
+ // Get rid of seconds and milliseconds
1075
+ date.setSeconds(0);
1076
+ date.setMilliseconds(0);
1077
+ return date;
1078
+ }
1079
+ };
1080
+
1081
+
1082
+ export class DatetimeSecondField extends DateFieldBase
1083
+ {
1084
+ static classdoc = "Holds the value of a date/datetimesecond field of a record (and related information)";
1085
+
1086
+ _convert(date)
1087
+ {
1088
+ // Get rid of milliseconds
1089
+ date.setMilliseconds(0);
1090
+ return date;
1091
+ }
1092
+ };
1093
+
1094
+
1095
+ export class LookupFieldBase extends Field
1096
+ {
1097
+ static classdoc = "Base type of LookupField and MultipleLookupField";
1098
+
1099
+ get lookupdata()
1100
+ {
1101
+ if (this._lookupdate !== null)
1102
+ return this._lookupdate;
1103
+ else
1104
+ return this.control.lookupdata;
1105
+ }
1106
+
1107
+ set lookupdata(value)
1108
+ {
1109
+ this._lookupdate = value;
1110
+ }
1111
+
1112
+ _find_lookupitem(change)
1113
+ {
1114
+ let v = change.value;
1115
+ if (typeof(v) === "string")
1116
+ {
1117
+ // If `lookupdata`} is `null`, this is probably an interface,
1118
+ // so we can't validate anything (note that interfaces will however go
1119
+ // away in the future)
1120
+ if (this.lookupdata === null)
1121
+ return;
1122
+ let lookupitem = this.lookupdata.get(v);
1123
+ if (lookupitem === undefined)
1124
+ {
1125
+ // If the lookup control is auto expandable,
1126
+ // the user may have given us a label
1127
+ if (this.control.autoexpandable)
1128
+ {
1129
+ // Search for the LookupItem with that label
1130
+ for (let search_lookupitem of this.lookupdata.values())
1131
+ {
1132
+ if (v === search_lookupitem.label)
1133
+ {
1134
+ change.value = search_lookupitem;
1135
+ return;
1136
+ }
1137
+ }
1138
+ // If we couldn't find the label, keep the label as it is.
1139
+ }
1140
+ else
1141
+ {
1142
+ change.errors.push(this.msg_lookup_unknownkey(v))
1143
+ change.value = null;
1144
+ }
1145
+ }
1146
+ else
1147
+ {
1148
+ change.value = lookupitem;
1149
+ }
1150
+ }
1151
+ else if (v instanceof LookupItem)
1152
+ {
1153
+ let value = this.control.lookupdata.get(v.key);
1154
+ if (value !== v)
1155
+ {
1156
+ change.errors.push(this.msg_lookup_wrongitem(v));
1157
+ change.value = null;
1158
+ }
1159
+ }
1160
+ else
1161
+ {
1162
+ change.errors.push(this.msg_field_wrong_type(v));
1163
+ change.value = null;
1164
+ }
1165
+ }
1166
+ };
1167
+
1168
+
1169
+ export class LookupField extends LookupFieldBase
1170
+ {
1171
+ static classdoc = "Holds the value of a lookup field of a record (and related information)";
1172
+
1173
+ _validate(change)
1174
+ {
1175
+ let v = change.value;
1176
+ if (v === null || v === "" || this.control.nonekey === v)
1177
+ {
1178
+ if (this.control.required)
1179
+ change.errors.push(this.msg_field_required(v));
1180
+ change.value = null;
1181
+ }
1182
+ else
1183
+ {
1184
+ this._find_lookupitem(change);
1185
+ }
1186
+ }
1187
+ };
1188
+
1189
+
1190
+ export class MultipleLookupField extends LookupFieldBase
1191
+ {
1192
+ static classdoc = "Holds the value of a multiple lookup field of a record (and related information)";
1193
+
1194
+ _validate(change)
1195
+ {
1196
+ let v = change.value;
1197
+ let islist = (Object.prototype.toString.call(v) === "[object Array]");
1198
+
1199
+ if (v === null || v === "" || this.control.nonekey === v || (islist && v.length === 0))
1200
+ {
1201
+ if (this.control.required)
1202
+ change.errors.push(this.msg_field_required(v));
1203
+ change.value = [];
1204
+ }
1205
+ else if (islist)
1206
+ {
1207
+ let newvalue = [];
1208
+ for (let item of v)
1209
+ {
1210
+ let singlechange = {value: item, errors: []};
1211
+ this._find_lookupitem(singlechange);
1212
+ if (singlechange.errors.length > 0)
1213
+ change.errors = [...change.errors, ...singlechange.errors];
1214
+ else
1215
+ newvalue.push(singlechange.value);
1216
+ }
1217
+ change.value = newvalue;
1218
+ }
1219
+ else
1220
+ {
1221
+ this._find_lookupitem(change);
1222
+ if (change.errors.length > 0)
1223
+ change.value = [];
1224
+ else
1225
+ change.value = [change.value];
1226
+ }
1227
+ }
1228
+ };
1229
+
1230
+
1231
+ export class AppLookupFieldBase extends Field
1232
+ {
1233
+ static classdoc = "Base type of AppLookupField and MultipleAppLookupField";
1234
+
1235
+ get lookupdata()
1236
+ {
1237
+ if (this._lookupdate !== null)
1238
+ return this._lookupdate;
1239
+ else
1240
+ {
1241
+ if (this.control.lookup_app.records !== null)
1242
+ return this.control.lookup_app.records;
1243
+ }
1244
+ return new Map();
1245
+ }
1246
+
1247
+ set lookupdata(value)
1248
+ {
1249
+ this._lookupdate = value;
1250
+ }
1251
+
1252
+ _find_record(change)
1253
+ {
1254
+ let v = change.value;
1255
+ if (typeof(v) === "string")
1256
+ {
1257
+ let record = this.lookupdata.get(v);
1258
+ if (record === undefined)
1259
+ {
1260
+ change.errors.push(this.msg_applookup_unknownkey(v))
1261
+ change.value = null;
1262
+ }
1263
+ else
1264
+ {
1265
+ change.value = record;
1266
+ }
1267
+ }
1268
+ else if (v instanceof Record)
1269
+ {
1270
+ if (v.app !== this.control.lookup_app)
1271
+ {
1272
+ change.errors.push(this.msg_applookup_wrongrecord(v));
1273
+ change.value = null;
1274
+ }
1275
+ }
1276
+ else
1277
+ {
1278
+ change.errors.push(this.msg_field_wrong_type(v));
1279
+ change.value = null;
1280
+ }
1281
+ }
1282
+ };
1283
+
1284
+
1285
+ export class AppLookupField extends AppLookupFieldBase
1286
+ {
1287
+ static classdoc = "Holds the value of a applookup field of a record (and related information)";
1288
+
1289
+ _validate(change)
1290
+ {
1291
+ this._find_record(change);
1292
+ }
1293
+ };
1294
+
1295
+
1296
+ export class MultipleAppLookupField extends AppLookupFieldBase
1297
+ {
1298
+ static classdoc = "Holds the value of a multiple applookup field of a record (and related information)";
1299
+
1300
+ _validate(change)
1301
+ {
1302
+ let v = change.value;
1303
+ let islist = (Object.prototype.toString.call(v) === "[object Array]");
1304
+
1305
+ if (v === null || v === "" || this.control.nonekey === v || (islist && v.length === 0))
1306
+ {
1307
+ if (this.control.required)
1308
+ change.errors.push(this.msg_field_required(v));
1309
+ change.value = [];
1310
+ }
1311
+ else if (islist)
1312
+ {
1313
+ let newvalue = [];
1314
+ for (let item of v)
1315
+ {
1316
+ let singlechange = {value: item, errors: []};
1317
+ this._find_record(singlechange);
1318
+ if (singlechange.errors.length > 0)
1319
+ change.errors = [...change.errors, ...singlechange.errors];
1320
+ else
1321
+ newvalue.push(singlechange.value);
1322
+ }
1323
+ change.value = newvalue;
1324
+ }
1325
+ else
1326
+ {
1327
+ this._find_record(change);
1328
+ if (change.errors.length > 0)
1329
+ change.value = [];
1330
+ else
1331
+ change.value = [change.value];
1332
+ }
1333
+ }
1334
+ };
1335
+
1336
+
1337
+ export class Control extends Base
1338
+ {
1339
+ [ul4.symbols.repr]()
1340
+ {
1341
+ return "<" + this.constructor.name + " id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
1342
+ }
1343
+
1344
+ get globals()
1345
+ {
1346
+ return this.app.globals;
1347
+ }
1348
+
1349
+ get fulltype()
1350
+ {
1351
+ return this.subtype ? this.type + "/" + this.subtype : this.type;
1352
+ }
1353
+
1354
+ get label()
1355
+ {
1356
+ let view_control = this._view_control();
1357
+ if (view_control === null)
1358
+ return this._label;
1359
+ let label = view_control.label;
1360
+ if (label === null)
1361
+ return this._label;
1362
+ return label;
1363
+ }
1364
+
1365
+ set label(value)
1366
+ {
1367
+ this._label = value;
1368
+ }
1369
+
1370
+ get default()
1371
+ {
1372
+ return null;
1373
+ }
1374
+
1375
+ get top()
1376
+ {
1377
+ let view_control = this._view_control();
1378
+ return view_control !== null ? view_control.top : null;
1379
+ }
1380
+
1381
+ get left()
1382
+ {
1383
+ let view_control = this._view_control();
1384
+ return view_control !== null ? view_control.left : null;
1385
+ }
1386
+
1387
+ get width()
1388
+ {
1389
+ let view_control = this._view_control();
1390
+ return view_control !== null ? view_control.width : null;
1391
+ }
1392
+
1393
+ get height()
1394
+ {
1395
+ let view_control = this._view_control();
1396
+ return view_control !== null ? view_control.height : null;
1397
+ }
1398
+
1399
+ get liveupdate()
1400
+ {
1401
+ let view_control = this._view_control();
1402
+ return view_control !== null ? view_control.liveupdate : false;
1403
+ }
1404
+
1405
+ get tabindex()
1406
+ {
1407
+ let view_control = this._view_control();
1408
+ return view_control !== null ? view_control.tabindex : null;
1409
+ }
1410
+
1411
+ get required()
1412
+ {
1413
+ let view_control = this._view_control();
1414
+ return view_control !== null ? view_control.required : false;
1415
+ }
1416
+
1417
+ get mode()
379
1418
  {
380
- if (name === "values")
381
- {
382
- this._sparsevalues = value;
383
- this._values = null;
384
- this._fields = null;
385
- }
386
- else
387
- this[name] = value;
1419
+ let view_control = this._view_control();
1420
+ return view_control !== null ? view_control.mode : "edit";
388
1421
  }
389
1422
 
390
- __getattr__(name)
1423
+ get labelpos()
391
1424
  {
392
- if (name.startsWith("c_"))
393
- return this.children.get(name.substr(2))
394
- else if (name.startsWith("f_"))
395
- return this.fields.get(name.substr(2))
396
- else if (name.startsWith("v_"))
397
- return this.values.get(name.substr(2))
398
- else
399
- return this[name];
1425
+ let view_control = this._view_control();
1426
+ return view_control !== null ? view_control.labelpos : "left";
400
1427
  }
401
1428
 
402
- __setattr__(name, value)
1429
+ get labelwidth()
403
1430
  {
404
- if (name.startsWith("c_"))
405
- this.children[name.substr(2)] = value;
406
- else if (name.startsWith("v_"))
407
- this.fields.get(name.substr(2)).value = value;
408
- else
409
- throw new ul4.AttributeError(this, name);
1431
+ let view_control = this._view_control();
1432
+ return view_control !== null ? view_control.labelwidth : null;
410
1433
  }
411
- };
412
1434
 
413
- Record.prototype._ul4onattrs = ["id", "app", "createdat", "createdby", "updatedat", "updatedby", "updatecount", "values", "attachments", "children"];
414
- Record.prototype._ul4attrs = ul4._makeset("id", "app", "createdat", "createdby", "updatedat", "updatedby", "updatecount", "values", "attachments", "children");
415
- ul4.expose(Record.prototype.is_dirty, []);
416
- ul4.expose(Record.prototype.has_errors, []);
417
- ul4.expose(Record.prototype.delete, []);
418
- ul4.expose(Record.prototype.save, []);
419
- ul4.expose(Record.prototype.update, ["**values"], {"needsobject": true});
1435
+ get autoalign()
1436
+ {
1437
+ let view_control = this._view_control();
1438
+ return view_control !== null ? view_control.autoalign : true;
1439
+ }
420
1440
 
421
- export class Control extends Base
422
- {
423
- __repr__()
1441
+ get in_active_view()
1442
+ {
1443
+ let view_control = this._view_control();
1444
+ return view_control !== null;
1445
+ }
1446
+
1447
+ _view_control()
424
1448
  {
425
- return "<" + this.__type__ + " id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
1449
+ if (this.app._active_view === null)
1450
+ return null;
1451
+ let view_control = this.app._active_view.controls.get(this.identifier);
1452
+ return view_control ?? null;
426
1453
  }
427
1454
 
428
1455
  _logsearch(value, search)
@@ -442,11 +1469,14 @@ export class Control extends Base
442
1469
 
443
1470
  Control.prototype.type = null;
444
1471
  Control.prototype.subtype = null;
445
- Control.prototype._ul4onattrs = ["id", "identifier", "field", "app", "label", "priority", "order", "default", "ininsertprocedure", "inupdateprocedure"];
446
- Control.prototype._ul4attrs = ul4._makeset("id", "identifier", "field", "app", "label", "priority", "order", "default", "ininsertprocedure", "inupdateprocedure");
1472
+ Control.prototype._ul4onattrs = ["identifier", "fieldname", "app", "_label", "priority", "order", "ininsertprocedure", "inupdateprocedure"];
1473
+ Control.prototype._ul4attrs = new Set(["id", "identifier", "fieldname", "app", "priority", "order", "ininsertprocedure", "inupdateprocedure", "fulltype", "label", "top", "left", "width", "height", "liveupdate", "tabindex", "required", "mode", "labelpos", "autoalign", "in_active_view"]);
1474
+
447
1475
 
448
1476
  export class BoolControl extends Control
449
1477
  {
1478
+ static classdoc = "A LivingApps boolean field (type 'bool')";
1479
+
450
1480
  // ``search`` must by ``null``, ``false`` or ``true``
451
1481
  search(value, search)
452
1482
  {
@@ -459,9 +1489,13 @@ export class BoolControl extends Control
459
1489
  };
460
1490
 
461
1491
  BoolControl.prototype.type = "bool";
1492
+ BoolControl.prototype.fieldtype = BoolField;
1493
+
462
1494
 
463
1495
  export class IntControl extends Control
464
1496
  {
1497
+ static classdoc = "A LivingApps integer field (type 'int')";
1498
+
465
1499
  // ``search.value`` must by ``null`` or an integer
466
1500
  search(value, search)
467
1501
  {
@@ -474,9 +1508,29 @@ export class IntControl extends Control
474
1508
  };
475
1509
 
476
1510
  IntControl.prototype.type = "int";
1511
+ IntControl.prototype.fieldtype = IntField;
1512
+
1513
+
1514
+ class PlaceholderControl extends Control
1515
+ {
1516
+ static classdoc = "A LivingApps multiplelookup field (type 'multiplelookup/select')";
1517
+
1518
+ get placeholder()
1519
+ {
1520
+ let view_control = this._view_control();
1521
+ if (view_control === null)
1522
+ return null;
1523
+ return view_control.placeholder;
1524
+ }
1525
+ };
1526
+
1527
+ PlaceholderControl.prototype._ul4attrs = new Set([...Control.prototype._ul4attrs, "placeholder"]);
477
1528
 
478
- export class NumberControl extends Control
1529
+
1530
+ export class NumberControl extends PlaceholderControl
479
1531
  {
1532
+ static classdoc = "A LivingApps number field (type 'number')";
1533
+
480
1534
  // ``search.value`` must by ``null`` or an integer
481
1535
  search(value, search)
482
1536
  {
@@ -495,9 +1549,103 @@ export class NumberControl extends Control
495
1549
  };
496
1550
 
497
1551
  NumberControl.prototype.type = "number";
1552
+ NumberControl.prototype.fieldtype = NumberField;
498
1553
 
499
- export class StringControl extends Control
1554
+
1555
+ export class StringControl extends PlaceholderControl
500
1556
  {
1557
+ _user_placeholder(user, placeholder)
1558
+ {
1559
+ if (placeholder === null)
1560
+ return null;
1561
+ switch (placeholder)
1562
+ {
1563
+ case "{gender}":
1564
+ return user !== null ? user.gender : null;
1565
+ case "{title}":
1566
+ return user !== null ? user.title : null;
1567
+ case "{firstname}":
1568
+ return user !== null ? user.firstname : null;
1569
+ case "{surname}":
1570
+ return user !== null ? user.surname : null;
1571
+ case "{account}":
1572
+ return user !== null ? user.email : null;
1573
+ case "{streetname}":
1574
+ return user !== null ? user.streetname : null;
1575
+ case "{streetnumber}":
1576
+ return user !== null ? user.streetnumber : null;
1577
+ case "{street}":
1578
+ if (user === null)
1579
+ return null;
1580
+ else if (user.street === null)
1581
+ return user.streetnumber;
1582
+ else if (user.streetnumber === null)
1583
+ return user.street;
1584
+ else
1585
+ return user.street + " " + user.streetnumber;
1586
+ case "{zip}":
1587
+ return user !== null ? user.zip : null;
1588
+ case "{phone}":
1589
+ return user !== null ? user.phone : null;
1590
+ case "{fax}":
1591
+ return user !== null ? user.fax : null;
1592
+ case "{company}":
1593
+ return user !== null ? user.company : null;
1594
+ case "{city}":
1595
+ return user !== null ? user.city : null;
1596
+ case "{summary}":
1597
+ return user !== null ? user.summary : null;
1598
+ case "{interests}":
1599
+ return user !== null ? user.interests : null;
1600
+ case "{personal_website}":
1601
+ return user !== null ? user.personal_website : null;
1602
+ case "{company_website}":
1603
+ return user !== null ? user.company_website : null;
1604
+ case "{position}":
1605
+ return user !== null ? user.position : null;
1606
+ case "{department}":
1607
+ return user !== null ? user.department : null;
1608
+ case "{today}":
1609
+ return ul4.today();
1610
+ default:
1611
+ return placeholder;
1612
+ }
1613
+ }
1614
+
1615
+ get default()
1616
+ {
1617
+ let view_control = this._view_control();
1618
+
1619
+ if (view_control === null)
1620
+ return null;
1621
+
1622
+ return this._user_placeholder(this.app.globals.user, view_control.default);
1623
+ }
1624
+
1625
+ get minlength()
1626
+ {
1627
+ let view_control = this._view_control();
1628
+
1629
+ if (view_control === null)
1630
+ return 0;
1631
+ let minlength = view_control.minlength;
1632
+ if (minlength === null || minlength < 0)
1633
+ return 0;
1634
+ return minlength;
1635
+ }
1636
+
1637
+ get maxlength()
1638
+ {
1639
+ let view_control = this._view_control();
1640
+
1641
+ if (view_control === null)
1642
+ return 4000;
1643
+ let maxlength = view_control.maxlength;
1644
+ if (maxlength === null || maxlength < 0 || maxlength > 4000)
1645
+ return 4000;
1646
+ return maxlength;
1647
+ }
1648
+
501
1649
  search(value, search)
502
1650
  {
503
1651
  this._logsearch(value, search);
@@ -515,46 +1663,92 @@ export class StringControl extends Control
515
1663
 
516
1664
  StringControl.prototype.type = "string";
517
1665
 
1666
+
518
1667
  export class TextControl extends StringControl
519
1668
  {
1669
+ static classdoc = "A LivingApps text field (type 'string/text')";
520
1670
  };
521
1671
 
522
1672
  TextControl.prototype.subtype = "text";
1673
+ TextControl.prototype.fieldtype = StringField;
1674
+
523
1675
 
524
1676
  export class EmailControl extends StringControl
525
1677
  {
1678
+ static classdoc = "A LivingApps email field (type 'string/email')";
526
1679
  };
527
1680
 
528
1681
  EmailControl.prototype.subtype = "email";
1682
+ EmailControl.prototype.fieldtype = EmailField;
1683
+
529
1684
 
530
1685
  export class URLControl extends StringControl
531
1686
  {
1687
+ static classdoc = "A LivingApps URL field (type 'string/url')";
532
1688
  };
533
1689
 
534
1690
  URLControl.prototype.subtype = "url";
1691
+ URLControl.prototype.fieldtype = URLField;
1692
+
535
1693
 
536
1694
  export class TelControl extends StringControl
537
1695
  {
1696
+ static classdoc = "A LivingApps phone number field (type 'string/tel')";
538
1697
  };
539
1698
 
540
1699
  TelControl.prototype.subtype = "tel";
1700
+ TelControl.prototype.fieldtype = TelField;
1701
+
541
1702
 
542
- export class PasswordControl extends StringControl
1703
+ export class TextAreaControl extends StringControl
543
1704
  {
1705
+ static classdoc = "A LivingApps textarea field (type 'string/textarea')";
1706
+
1707
+ get maxlength()
1708
+ {
1709
+ let view_control = this._view_control();
1710
+
1711
+ if (view_control === null)
1712
+ return null;
1713
+ // We only get the encrypted text in encryption mode, so we can't check its length
1714
+ if (this.encrypted !== null)
1715
+ return null;
1716
+ let maxlength = view_control.maxlength;
1717
+ if (maxlength < 0)
1718
+ return null;
1719
+ return maxlength;
1720
+ }
544
1721
  };
545
1722
 
546
- PasswordControl.prototype.subtype = "password";
1723
+ TextAreaControl.prototype.subtype = "textarea";
1724
+ TextAreaControl.prototype.fieldtype = TextAreaField;
1725
+ TextAreaControl.prototype._ul4onattrs = [...StringControl.prototype._ul4onattrs, "encrypted"];
1726
+ TextAreaControl.prototype._ul4attrs = new Set([...StringControl.prototype._ul4attrs, "encrypted"]);
1727
+
547
1728
 
548
- export class TextAreaControl extends StringControl
1729
+ export class HTMLControl extends StringControl
549
1730
  {
1731
+ static classdoc = "A LivingApps HTML field (type 'string/html')";
550
1732
  };
551
1733
 
552
- TextAreaControl.prototype.subtype = "textarea";
553
- TextAreaControl.prototype._ul4onattrs = StringControl.prototype._ul4onattrs.concat(["encrypted"]);
554
- TextAreaControl.prototype._ul4attrs = ul4._makeset(...StringControl.prototype._ul4attrs, "encrypted");
1734
+ HTMLControl.prototype.subtype = "html";
1735
+ HTMLControl.prototype.fieldtype = HTMLField;
1736
+
555
1737
 
556
- export class DateControl extends Control
1738
+ export class DateControl extends PlaceholderControl
557
1739
  {
1740
+ static classdoc = "A LivingApps date field (type 'date/date')";
1741
+
1742
+ get default()
1743
+ {
1744
+ let view_control = this._view_control();
1745
+
1746
+ if (view_control === null)
1747
+ return null;
1748
+
1749
+ return view_control.default === "{today}" ? ul4.today() : null;
1750
+ }
1751
+
558
1752
  formatstring(language)
559
1753
  {
560
1754
  language = language || this.app.language;
@@ -592,9 +1786,31 @@ export class DateControl extends Control
592
1786
 
593
1787
  DateControl.prototype.type = "date";
594
1788
  DateControl.prototype.subtype = "date";
1789
+ DateControl.prototype.fieldtype = DateField;
1790
+
595
1791
 
596
1792
  export class DatetimeMinuteControl extends DateControl
597
1793
  {
1794
+ static classdoc = "A LivingApps date field (type 'date/datetimeminute')";
1795
+
1796
+ get default()
1797
+ {
1798
+ let view_control = this._view_control();
1799
+
1800
+ if (view_control === null)
1801
+ return null;
1802
+
1803
+ if (view_control.default === "{today}")
1804
+ {
1805
+ let now = new Date();
1806
+ now.setSeconds(0);
1807
+ now.setMilliseconds(0);
1808
+ return now;
1809
+ }
1810
+ else
1811
+ return null;
1812
+ }
1813
+
598
1814
  formatstring(language)
599
1815
  {
600
1816
  language = language || this.app.language;
@@ -607,9 +1823,30 @@ export class DatetimeMinuteControl extends DateControl
607
1823
  };
608
1824
 
609
1825
  DatetimeMinuteControl.prototype.subtype = "datetimeminute";
1826
+ DatetimeMinuteControl.prototype.fieldtype = DatetimeMinuteField;
1827
+
610
1828
 
611
1829
  export class DatetimeSecondControl extends DateControl
612
1830
  {
1831
+ static classdoc = "A LivingApps date field (type 'date/datetimesecond')";
1832
+
1833
+ get default()
1834
+ {
1835
+ let view_control = this._view_control();
1836
+
1837
+ if (view_control === null)
1838
+ return null;
1839
+
1840
+ if (view_control.default === "{today}")
1841
+ {
1842
+ let now = new Date();
1843
+ now.setMilliseconds(0);
1844
+ return now;
1845
+ }
1846
+ else
1847
+ return null;
1848
+ }
1849
+
613
1850
  formatstring(language)
614
1851
  {
615
1852
  language = language || this.app.language;
@@ -622,9 +1859,45 @@ export class DatetimeSecondControl extends DateControl
622
1859
  };
623
1860
 
624
1861
  DatetimeSecondControl.prototype.subtype = "datetimesecond";
1862
+ DatetimeSecondControl.prototype.fieldtype = DatetimeSecondField;
1863
+
625
1864
 
626
1865
  export class LookupControl extends Control
627
1866
  {
1867
+ get default()
1868
+ {
1869
+ let view_control = this._view_control();
1870
+
1871
+ if (view_control === null)
1872
+ return null;
1873
+
1874
+ return this.lookupdata.get(view_control) ?? null;
1875
+ }
1876
+
1877
+ get autoexpandable()
1878
+ {
1879
+ let view_control = this._view_control();
1880
+ if (view_control === null)
1881
+ return false;
1882
+ return view_control.autoexpandable;
1883
+ }
1884
+
1885
+ get nonekey()
1886
+ {
1887
+ let view_control = this._view_control();
1888
+ if (view_control === null)
1889
+ return null;
1890
+ return view_control.lookupnonekey;
1891
+ }
1892
+
1893
+ get nonelabel()
1894
+ {
1895
+ let view_control = this._view_control();
1896
+ if (view_control === null)
1897
+ return null;
1898
+ return view_control.lookupnonelabel;
1899
+ }
1900
+
628
1901
  // ``search.value`` must be ``null`` or a ``LookupItem`` key
629
1902
  search(value, search)
630
1903
  {
@@ -641,27 +1914,38 @@ export class LookupControl extends Control
641
1914
  };
642
1915
 
643
1916
  LookupControl.prototype.type = "lookup";
644
- LookupControl.prototype._ul4onattrs = Control.prototype._ul4onattrs.concat(["lookupdata"]);
645
- LookupControl.prototype._ul4attrs = ul4._makeset(...Control.prototype._ul4attrs, "lookupdata");
1917
+ LookupControl.prototype.fieldtype = LookupField;
1918
+ LookupControl.prototype._ul4onattrs = [...Control.prototype._ul4onattrs, "lookupdata"];
1919
+ LookupControl.prototype._ul4attrs = new Set([...Control.prototype._ul4attrs, "lookupdata"]);
1920
+
646
1921
 
647
1922
  export class LookupSelectControl extends LookupControl
648
1923
  {
1924
+ static classdoc = "A LivingApps lookup field (type 'lookup/select')";
1925
+
649
1926
  };
650
1927
 
651
1928
  LookupSelectControl.prototype.subtype = "select";
652
1929
 
1930
+
653
1931
  export class LookupRadioControl extends LookupControl
654
1932
  {
1933
+ static classdoc = "A LivingApps lookup field (type 'lookup/radio')";
1934
+
655
1935
  };
656
1936
 
657
1937
  LookupRadioControl.prototype.subtype = "radio";
658
1938
 
1939
+
659
1940
  export class LookupChoiceControl extends LookupControl
660
1941
  {
1942
+ static classdoc = "A LivingApps lookup field (type 'lookup/choice')";
1943
+
661
1944
  };
662
1945
 
663
1946
  LookupChoiceControl.prototype.subtype = "choice";
664
1947
 
1948
+
665
1949
  export class AppLookupControl extends Control
666
1950
  {
667
1951
  // ``search.value`` must be an object containing the search criteria for the referenced record
@@ -675,29 +1959,52 @@ export class AppLookupControl extends Control
675
1959
  };
676
1960
 
677
1961
  AppLookupControl.prototype.type = "applookup";
678
- AppLookupControl.prototype._ul4onattrs = Control.prototype._ul4onattrs.concat(["lookupapp", "lookupcontrols"]);
679
- AppLookupControl.prototype._ul4attrs = ul4._makeset(...Control.prototype._ul4attrs, "lookupapp", "lookupcontrols");
1962
+ AppLookupControl.prototype.fieldtype = AppLookupField;
1963
+ AppLookupControl.prototype._ul4onattrs = [...Control.prototype._ul4onattrs, "lookup_app", "lookup_controls", "local_master_control", "local_detail_controls", "remote_master_control"];
1964
+ AppLookupControl.prototype._ul4attrs = new Set([...Control.prototype._ul4attrs, "lookup_app", "lookup_controls", "local_master_control", "local_detail_controls", "remote_master_control"]);
1965
+
680
1966
 
681
1967
  export class AppLookupSelectControl extends AppLookupControl
682
1968
  {
1969
+ static classdoc = "A LivingApps applookup field (type 'applookup/select')";
1970
+
683
1971
  };
684
1972
 
685
1973
  AppLookupSelectControl.prototype.subtype = "select";
686
1974
 
1975
+
687
1976
  export class AppLookupRadioControl extends AppLookupControl
688
1977
  {
1978
+ static classdoc = "A LivingApps applookup field (type 'applookup/radio')";
1979
+
689
1980
  };
690
1981
 
691
1982
  AppLookupRadioControl.prototype.subtype = "radio";
692
1983
 
1984
+
693
1985
  export class AppLookupChoiceControl extends AppLookupControl
694
1986
  {
1987
+ static classdoc = "A LivingApps applookup field (type 'applookup/choice')";
1988
+
695
1989
  };
696
1990
 
697
1991
  AppLookupChoiceControl.prototype.subtype = "choice";
698
1992
 
1993
+
699
1994
  export class MultipleLookupControl extends LookupControl
700
1995
  {
1996
+ get default()
1997
+ {
1998
+ let view_control = this._view_control();
1999
+
2000
+ if (view_control === null)
2001
+ return null;
2002
+
2003
+ let defaultValue = this.lookupdata.get(view_control)
2004
+
2005
+ return (defaultValue !== undefined) ? [defaultValue] : [];
2006
+ }
2007
+
701
2008
  // search.value must be ``null`` or a ``LookupItem`` key
702
2009
  search(value, search)
703
2010
  {
@@ -716,25 +2023,36 @@ export class MultipleLookupControl extends LookupControl
716
2023
  };
717
2024
 
718
2025
  MultipleLookupControl.prototype.subtype = "multiplelookup";
2026
+ MultipleLookupControl.prototype.fieldtype = MultipleLookupField;
2027
+
719
2028
 
720
2029
  export class MultipleLookupSelectControl extends MultipleLookupControl
721
2030
  {
2031
+ static classdoc = "A LivingApps multiplelookup field (type 'multiplelookup/select')";
2032
+
722
2033
  };
723
2034
 
724
2035
  MultipleLookupSelectControl.prototype.subtype = "select";
725
2036
 
2037
+
726
2038
  export class MultipleLookupCheckboxControl extends MultipleLookupControl
727
2039
  {
2040
+ static classdoc = "A LivingApps multiplelookup field (type 'multiplelookup/checkbox')";
2041
+
728
2042
  };
729
2043
 
730
2044
  MultipleLookupCheckboxControl.prototype.subtype = "checkbox";
731
2045
 
2046
+
732
2047
  export class MultipleLookupChoiceControl extends MultipleLookupControl
733
2048
  {
2049
+ static classdoc = "A LivingApps multiplelookup field (type 'multiplelookup/choice')";
2050
+
734
2051
  };
735
2052
 
736
2053
  MultipleLookupChoiceControl.prototype.subtype = "choice";
737
2054
 
2055
+
738
2056
  export class MultipleAppLookupControl extends AppLookupControl
739
2057
  {
740
2058
  // ``search.value`` must be an object containing the search criteria for the referenced record
@@ -760,36 +2078,61 @@ export class MultipleAppLookupControl extends AppLookupControl
760
2078
  };
761
2079
 
762
2080
  MultipleAppLookupControl.prototype.type = "multipleapplookup";
2081
+ MultipleAppLookupControl.prototype.fieldtype = MultipleAppLookupField;
2082
+
763
2083
 
764
2084
  export class MultipleAppLookupSelectControl extends MultipleAppLookupControl
765
2085
  {
2086
+ static classdoc = "A LivingApps multiple applookup field (type 'multipleapplookup/select')";
2087
+
766
2088
  };
767
2089
 
768
2090
  MultipleAppLookupSelectControl.prototype.subtype = "select";
769
2091
 
2092
+
770
2093
  export class MultipleAppLookupCheckboxControl extends MultipleAppLookupControl
771
2094
  {
2095
+ static classdoc = "A LivingApps multiple applookup field (type 'multipleapplookup/checkbox')";
2096
+
772
2097
  };
773
2098
 
774
2099
  MultipleAppLookupCheckboxControl.prototype.subtype = "checkbox";
775
2100
 
2101
+
776
2102
  export class MultipleAppLookupChoiceControl extends MultipleAppLookupControl
777
2103
  {
2104
+ static classdoc = "A LivingApps multiple applookup field (type 'multipleapplookup/choice')";
778
2105
  };
779
2106
 
780
2107
  MultipleAppLookupChoiceControl.prototype.subtype = "choice";
781
2108
 
2109
+
782
2110
  export class GeoControl extends Control
783
2111
  {
2112
+ static classdoc = "A LivingApps geo field (type 'geo')";
784
2113
  };
785
2114
 
786
2115
  GeoControl.prototype.type = "geo";
2116
+ GeoControl.prototype.fieldtype = GeoField;
2117
+
787
2118
 
788
2119
  export class FileControl extends Control
789
2120
  {
2121
+ static classdoc = "A LivingApps upload field (type 'file')";
790
2122
  };
791
2123
 
792
2124
  FileControl.prototype.type = "file";
2125
+ FileControl.prototype.fieldtype = FileField;
2126
+
2127
+
2128
+ export class FileSignatureControl extends FileControl
2129
+ {
2130
+ static classdoc = "A LivingApps signature image field (type 'file/signature')";
2131
+ };
2132
+
2133
+ FileSignatureControl.prototype.subtype = "signature";
2134
+ FileSignatureControl.prototype.fieldtype = FileSignatureField;
2135
+
793
2136
 
794
2137
  export class ButtonControl extends Control
795
2138
  {
@@ -797,165 +2140,237 @@ export class ButtonControl extends Control
797
2140
 
798
2141
  ButtonControl.prototype.type = "button";
799
2142
 
800
- export class Field extends Base
2143
+
2144
+ export class LookupItem extends Base
801
2145
  {
802
- constructor(control, record, value)
2146
+ static classdoc = "An option in a lookup control/field";
2147
+
2148
+ get label()
803
2149
  {
804
- super();
805
- this.control = control;
806
- this.record = record;
807
- this._value = value;
808
- this._dirty = false;
809
- this.errors = [];
2150
+ if (this.control === null)
2151
+ return this._label;
2152
+ let view_lookupitem = this._view_lookupitem();
2153
+ if (view_lookupitem === null)
2154
+ return this._label;
2155
+ let label = view_lookupitem.label;
2156
+ if (label === null)
2157
+ return this._label;
2158
+ return label;
810
2159
  }
811
2160
 
812
- get value()
2161
+ get visible()
813
2162
  {
814
- return this._value;
2163
+ if (this.control === null)
2164
+ return true;
2165
+ let view_lookupitem = this._view_lookupitem();
2166
+ if (view_lookupitem === null)
2167
+ return true;
2168
+ return view_lookupitem.visible;
815
2169
  }
816
2170
 
817
- set value(value)
2171
+ _view_lookupitem()
818
2172
  {
819
- let oldvalue = this._value;
820
-
821
- if (ul4._ne(oldvalue, value))
2173
+ let identifier = this.control.identifier;
2174
+ let active_view = this.control.app.active_view;
2175
+ if (active_view !== null && active_view.controls !== null)
822
2176
  {
823
- this.record.values.set(this.control.identifier, value);
824
- this._value = value;
825
- this._dirty = true;
2177
+ let view_control = active_view.controls.get(identifier);
2178
+ if (view_control !== undefined && view_control.lookupdata !== undefined && view_control.lookupdata !== null)
2179
+ {
2180
+ let view_lookupitem = view_control.lookupdata.get(this.key);
2181
+ if (view_lookupitem !== undefined)
2182
+ return view_lookupitem;
2183
+ }
826
2184
  }
2185
+ return null;
827
2186
  }
828
2187
 
829
- is_empty()
2188
+ [ul4.symbols.repr]()
830
2189
  {
831
- return this._value === null || (ul4._islist(this._value) && this._value.length === 0);
2190
+ let repr = "<LookupItem key=" + ul4._repr(this.key) + " label=" + ul4._repr(this.label);
2191
+ if (!this.visible)
2192
+ repr += "visible=False"
2193
+ return repr + ">";
832
2194
  }
2195
+ };
833
2196
 
834
- is_dirty()
835
- {
836
- return this._dirty;
837
- }
2197
+ LookupItem.prototype._ul4onattrs = ["control", "key", "_label"];
2198
+ LookupItem.prototype._ul4attrs = new Set(["id", "control", "key", "label"]);
838
2199
 
839
- has_errors()
840
- {
841
- return this.errors.length !== 0;
842
- }
843
2200
 
844
- search(searchvalue)
2201
+ export class ViewLookupItem extends Base
2202
+ {
2203
+ static classdoc = "View specific information about a lookup item";
2204
+
2205
+ [ul4.symbols.repr]()
845
2206
  {
846
- return this.control.search(this.value, searchvalue);
2207
+ let repr = "<ViewLookupItem key=" + ul4._repr(this.key) + " label=" + ul4._repr(this.label);
2208
+ if (!this.visible)
2209
+ repr += " visible=False"
2210
+ return repr + ">";
847
2211
  }
2212
+ };
2213
+
2214
+ ViewLookupItem.prototype._ul4onattrs = ["key", "label", "visible"];
2215
+ ViewLookupItem.prototype._ul4attrs = new Set(["key", "label", "visible"]);
848
2216
 
849
- __repr__()
2217
+
2218
+ export class LayoutControl extends Base
2219
+ {
2220
+ static classdoc = "A decoration in an input form";
2221
+
2222
+ [ul4.symbols.repr]()
850
2223
  {
851
- let s = "<Field identifier=";
852
- s += ul4._repr(this.control.identifier)
853
- if (this._dirty)
854
- s += " is_dirty()=True";
855
- if (this.errors.length !== 0)
856
- s += " has_errors()=True";
857
- s += ">"
858
- return s;
2224
+ return "<" + this.constructor.name + " id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
859
2225
  }
860
2226
  };
861
2227
 
862
- export class LookupItem extends Base
2228
+ LayoutControl.prototype._ul4onattrs = ["label", "identifier", "view", "top", "left", "width", "height"];
2229
+ LayoutControl.prototype._ul4attrs = new Set(["id", "label", "identifier", "view", "top", "left", "width", "height"]);
2230
+
2231
+
2232
+ export class HTMLLayoutControl extends LayoutControl
2233
+ {
2234
+ static classdoc = "An HTML decoration in an input form";
2235
+ };
2236
+
2237
+ HTMLLayoutControl.prototype._ul4onattrs = [...LayoutControl.prototype._ul4onattrs, "value"];
2238
+ HTMLLayoutControl.prototype._ul4attrs = new Set([LayoutControl.prototype._ul4attrs, "value"]);
2239
+
2240
+
2241
+ export class ImageLayoutControl extends LayoutControl
2242
+ {
2243
+ static classdoc = "An image decoration in an input form";
2244
+ };
2245
+
2246
+ ImageLayoutControl.prototype._ul4onattrs = [...LayoutControl.prototype._ul4onattrs, "original", "scaled"];
2247
+ ImageLayoutControl.prototype._ul4attrs = new Set([LayoutControl.prototype._ul4attrs, "original", "scaled"]);
2248
+
2249
+
2250
+ export class ViewControl extends Base
863
2251
  {
864
- __repr__()
2252
+ static classdoc = "Contains view specific information aboutn a control";
2253
+
2254
+ get mode()
865
2255
  {
866
- return "<LookupItem key=" + ul4._repr(this.key) + " label=" + ul4._repr(this.label) + ">";
2256
+ return this._mode ? "display" : "edit";
867
2257
  }
868
2258
  };
869
2259
 
870
- LookupItem.prototype._ul4onattrs = ["key", "label"];
871
- LookupItem.prototype._ul4attrs = ul4._makeset("key", "label");
2260
+ ViewControl.prototype._ul4onattrs = ["view", "control", "top", "left", "width", "height", "liveupdate", "default", "tabindex", "minlength", "maxlength", "required", "placeholder", "_mode", "labelpos", "lookupnonekey", "lookupnonelabel", "label", "autoalign", "labelwidth", "lookupdata", "autoexpandable"];
2261
+ ViewControl.prototype._ul4attrs = new Set(["id", "view", "control", "top", "left", "width", "height", "liveupdate", "default", "tabindex", "minlength", "maxlength", "required", "placeholder", "mode", "labelpos", "lookupnonekey", "lookupnonelabel", "label", "autoalign", "labelwidth", "lookupdata", "autoexpandable"]);
2262
+
872
2263
 
873
2264
  export class User extends Base
874
2265
  {
875
- __repr__()
2266
+ static classdoc = "A LivingApps user/account";
2267
+
2268
+ [ul4.symbols.repr]()
876
2269
  {
877
2270
  return "<User id=" + ul4._repr(this.id) + " firstname=" + ul4._repr(this.firstname) + " surname=" + ul4._repr(this.surname) + " email=" + ul4._repr(this.email) + ">";
878
2271
  }
879
2272
  };
880
2273
 
881
- User.prototype._ul4onattrs = ["_id", "id", "gender", "firstname", "surname", "initials", "email", "language", "avatarsmall", "avatarlarge", "keyviews"];
882
- User.prototype._ul4attrs = ul4._makeset("_id", "id", "gender", "firstname", "surname", "initials", "email", "language", "avatarsmall", "avatarlarge", "keyviews");
2274
+ User.prototype._ul4onattrs = ["_id", "gender", "title", "firstname", "surname", "initials", "email", "streetname", "streetnumber", "zip", "city", "phone", "fax", "lang", "avatarsmall", "avatarlarge", "summary", "interests", "personalwebsite", "companywebsite", "company", "position", "department", "keyviews"];
2275
+ User.prototype._ul4attrs = new Set(["id", "_id", "gender", "title", "firstname", "surname", "initials", "email", "streetname", "streetnumber", "zip", "city", "phone", "fax", "lang", "avatarsmall", "avatarlarge", "summary", "interests", "personalwebsite", "companywebsite", "company", "position", "department", "keyviews"]);
2276
+
883
2277
 
884
2278
  export class File extends Base
885
2279
  {
886
- __repr__()
2280
+ static classdoc = "An uploaded file";
2281
+
2282
+ [ul4.symbols.repr]()
887
2283
  {
888
2284
  return "<File id=" + ul4._repr(this.id) + " url=" + ul4._repr(this.url) + " filename=" + ul4._repr(this.filename) + ">";
889
2285
  }
890
2286
  };
891
2287
 
892
- File.prototype._ul4onattrs = ["id", "url", "filename", "mimetype", "width", "height", "internalid", "createdat", "size"];
893
- File.prototype._ul4attrs = ul4._makeset("id", "url", "filename", "mimetype", "width", "height", "size", "createdat");
2288
+ File.prototype._ul4onattrs = ["url", "filename", "mimetype", "width", "height", "internalid", "createdat", "size"];
2289
+ File.prototype._ul4attrs = new Set(["id", "url", "filename", "mimetype", "width", "height", "size", "createdat"]);
2290
+
894
2291
 
895
2292
  export class Geo extends Base
896
2293
  {
2294
+ static classdoc = "Geographical coordinates and location information";
2295
+
897
2296
  constructor(lat, long, info)
898
2297
  {
899
- super();
2298
+ super(null);
900
2299
  this.lat = lat;
901
2300
  this.long = long;
902
2301
  this.info = info;
903
2302
  }
904
2303
 
905
- __repr__()
2304
+ [ul4.symbols.repr]()
906
2305
  {
907
2306
  return "<Geo lat=" + ul4._repr(this.lat) + " long=" + ul4._repr(this.long) + " info=" + ul4._repr(this.info) + ">";
908
2307
  }
909
2308
  };
910
2309
 
911
2310
  Geo.prototype._ul4onattrs = ["lat", "long", "info"];
912
- Geo.prototype._ul4attrs = ul4._makeset("lat", "long", "info");
2311
+ Geo.prototype._ul4attrs = new Set(["lat", "long", "info"]);
2312
+
913
2313
 
914
2314
  export class Attachment extends Base
915
2315
  {
916
- __repr__()
2316
+ [ul4.symbols.repr]()
917
2317
  {
918
- return "<" + this.__type__ + " id=" + ul4._repr(this.id) + " label=" + ul4._repr(this.label) + ">";
2318
+ return "<" + this.constructor.name + " id=" + ul4._repr(this.id) + " label=" + ul4._repr(this.label) + ">";
919
2319
  }
920
2320
  };
921
2321
 
922
- Attachment.prototype._ul4onattrs = ["id", "record", "label", "active"];
923
- Attachment.prototype._ul4attrs = ul4._makeset("id", "record", "label", "active");
2322
+ Attachment.prototype._ul4onattrs = ["record", "label", "active"];
2323
+ Attachment.prototype._ul4attrs = new Set(["id", "record", "label", "active"]);
2324
+
924
2325
 
925
2326
  export class NoteAttachment extends Attachment
926
2327
  {
2328
+ static classdoc = "A note attachment of a record";
2329
+
927
2330
  };
928
2331
 
929
2332
  NoteAttachment.prototype.type = "noteattachment";
930
- NoteAttachment.prototype._ul4onattrs = Attachment.prototype._ul4onattrs.concat(["value"]);
931
- NoteAttachment.prototype._ul4attrs = ul4._makeset(...Attachment.prototype._ul4onattrs, "value");
2333
+ NoteAttachment.prototype._ul4onattrs = [...Attachment.prototype._ul4onattrs, "value"];
2334
+ NoteAttachment.prototype._ul4attrs = new Set([...Attachment.prototype._ul4onattrs, "value"]);
2335
+
932
2336
 
933
2337
  export class URLAttachment extends Attachment
934
2338
  {
2339
+ static classdoc = "A URL attachment of a record";
2340
+
935
2341
  };
936
2342
 
937
2343
  URLAttachment.prototype.type = "urlattachment";
938
- URLAttachment.prototype._ul4onattrs = Attachment.prototype._ul4onattrs.concat(["value"]);
939
- URLAttachment.prototype._ul4attrs = ul4._makeset(...Attachment.prototype._ul4onattrs, "value");
2344
+ URLAttachment.prototype._ul4onattrs = [...Attachment.prototype._ul4onattrs, "value"];
2345
+ URLAttachment.prototype._ul4attrs = new Set([...Attachment.prototype._ul4onattrs, "value"]);
2346
+
940
2347
 
941
2348
  export class FileAttachment extends Attachment
942
2349
  {
2350
+ static classdoc = "A file attachment of a record";
2351
+
943
2352
  };
944
2353
 
945
2354
  FileAttachment.prototype.type = "fileattachment";
946
- FileAttachment.prototype._ul4onattrs = Attachment.prototype._ul4onattrs.concat(["value"]);
947
- FileAttachment.prototype._ul4attrs = ul4._makeset(...Attachment.prototype._ul4onattrs, "value");
2355
+ FileAttachment.prototype._ul4onattrs = [...Attachment.prototype._ul4onattrs, "value"];
2356
+ FileAttachment.prototype._ul4attrs = new Set([...Attachment.prototype._ul4onattrs, "value"]);
2357
+
948
2358
 
949
2359
  export class ImageAttachment extends Attachment
950
2360
  {
2361
+ static classdoc = "An image attachment of a record";
2362
+
951
2363
  };
952
2364
 
953
2365
  ImageAttachment.prototype.type = "imageattachment";
954
- ImageAttachment.prototype._ul4onattrs = Attachment.prototype._ul4onattrs.concat(["original", "thumb", "small", "medium", "large"]);
955
- ImageAttachment.prototype._ul4attrs = ul4._makeset(...Attachment.prototype._ul4onattrs, "original", "thumb", "small", "medium", "large");
2366
+ ImageAttachment.prototype._ul4onattrs = [...Attachment.prototype._ul4onattrs, "original", "thumb", "small", "medium","large"];
2367
+ ImageAttachment.prototype._ul4attrs = new Set([...Attachment.prototype._ul4onattrs, "original", "thumb", "small", "medium", "large"]);
2368
+
956
2369
 
957
2370
  export class JSONAttachment extends Attachment
958
2371
  {
2372
+ static classdoc = "A JSON attachment of a record";
2373
+
959
2374
  _dumpUL4ONAttr(name)
960
2375
  {
961
2376
  if (name === "value")
@@ -974,23 +2389,29 @@ export class JSONAttachment extends Attachment
974
2389
  };
975
2390
 
976
2391
  JSONAttachment.prototype.type = "jsonattachment";
977
- JSONAttachment.prototype._ul4onattrs = Attachment.prototype._ul4onattrs.concat(["value"]);
978
- JSONAttachment.prototype._ul4attrs = ul4._makeset(...Attachment.prototype._ul4onattrs, "value");
2392
+ JSONAttachment.prototype._ul4onattrs = [...Attachment.prototype._ul4onattrs, "value"];
2393
+ JSONAttachment.prototype._ul4attrs = new Set([...Attachment.prototype._ul4onattrs, "value"]);
2394
+
979
2395
 
980
2396
  export class Installation extends Base
981
2397
  {
982
- __repr__()
2398
+ static classdoc = "The installation that created an app";
2399
+
2400
+ [ul4.symbols.repr]()
983
2401
  {
984
2402
  return "<Installation id=" + ul4._repr(this.id) + " name=" + ul4._repr(this.name) + ">";
985
2403
  }
986
2404
  };
987
2405
 
988
- Installation.prototype._ul4onattrs = ["id", "name"];
989
- Installation.prototype._ul4attrs = ul4._makeset("id", "name");
2406
+ Installation.prototype._ul4onattrs = ["name"];
2407
+ Installation.prototype._ul4attrs = new Set(["id", "name"]);
2408
+
990
2409
 
991
2410
  export class Category extends Base
992
2411
  {
993
- __repr__()
2412
+ static classdoc = "A navigation category";
2413
+
2414
+ [ul4.symbols.repr]()
994
2415
  {
995
2416
  let v = [];
996
2417
  let category = this;
@@ -1003,30 +2424,37 @@ export class Category extends Base
1003
2424
  }
1004
2425
  };
1005
2426
 
1006
- Category.prototype._ul4onattrs = ["id", "identifier", "name", "order", "parent", "children", "apps"];
1007
- Category.prototype._ul4attrs = ul4._makeset("id", "identifier", "name", "order", "parent", "children", "apps");
2427
+ Category.prototype._ul4onattrs = ["identifier", "name", "order", "parent", "children", "apps"];
2428
+ Category.prototype._ul4attrs = new Set(["id", "identifier", "name", "order", "parent", "children", "apps"]);
2429
+
1008
2430
 
1009
2431
  export class KeyView extends Base
1010
2432
  {
1011
- __repr__()
2433
+ static classdoc = "Object granting access to a view template";
2434
+
2435
+ [ul4.symbols.repr]()
1012
2436
  {
1013
2437
  return "<KeyView id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
1014
2438
  }
1015
2439
  };
1016
2440
 
1017
- KeyView.prototype._ul4onattrs = ["id", "identifier", "name", "key", "user"];
1018
- KeyView.prototype._ul4attrs = ul4._makeset("id", "identifier", "name", "key", "user");
2441
+ KeyView.prototype._ul4onattrs = ["identifier", "name", "key", "user"];
2442
+ KeyView.prototype._ul4attrs = new Set(["id", "identifier", "name", "key", "user"]);
2443
+
1019
2444
 
1020
2445
  export class AppParameter extends Base
1021
2446
  {
1022
- __repr__()
2447
+ static classdoc = "A parameter of a LivingApps application";
2448
+
2449
+ [ul4.symbols.repr]()
1023
2450
  {
1024
2451
  return "<AppParameter id=" + ul4._repr(this.id) + " identifier=" + ul4._repr(this.identifier) + ">";
1025
2452
  }
1026
2453
  };
1027
2454
 
1028
- AppParameter.prototype._ul4onattrs = ["id", "app", "identifier", "description", "value"];
1029
- AppParameter.prototype._ul4attrs = ul4._makeset("id", "app", "identifier", "description", "value");
2455
+ AppParameter.prototype._ul4onattrs = ["app", "identifier", "description", "value"];
2456
+ AppParameter.prototype._ul4attrs = new Set(["id", "app", "identifier", "description", "value"]);
2457
+
1030
2458
 
1031
2459
  let classes = [
1032
2460
  Globals,
@@ -1042,7 +2470,6 @@ let classes = [
1042
2470
  EmailControl,
1043
2471
  URLControl,
1044
2472
  TelControl,
1045
- PasswordControl,
1046
2473
  TextAreaControl,
1047
2474
  DateControl,
1048
2475
  DatetimeMinuteControl,
@@ -1065,9 +2492,37 @@ let classes = [
1065
2492
  MultipleAppLookupChoiceControl,
1066
2493
  GeoControl,
1067
2494
  FileControl,
2495
+ FileSignatureControl,
1068
2496
  ButtonControl,
1069
2497
  Field,
2498
+ BoolField,
2499
+ StringField,
2500
+ EmailField,
2501
+ URLField,
2502
+ TelField,
2503
+ TextAreaField,
2504
+ HTMLField,
2505
+ IntField,
2506
+ NumberField,
2507
+ GeoField,
2508
+ FileField,
2509
+ FileSignatureField,
2510
+ DateFieldBase,
2511
+ DateField,
2512
+ DatetimeMinuteField,
2513
+ DatetimeSecondField,
2514
+ LookupFieldBase,
2515
+ LookupField,
2516
+ MultipleLookupField,
2517
+ AppLookupFieldBase,
2518
+ AppLookupField,
2519
+ MultipleAppLookupField,
1070
2520
  LookupItem,
2521
+ ViewLookupItem,
2522
+ LayoutControl,
2523
+ HTMLLayoutControl,
2524
+ ImageLayoutControl,
2525
+ ViewControl,
1071
2526
  User,
1072
2527
  File,
1073
2528
  Geo,