alchemy-form 0.1.10 → 0.1.12

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.
Files changed (31) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/assets/stylesheets/form/alchemy_field.scss +33 -0
  3. package/controller/form_api_controller.js +0 -1
  4. package/element/00_form_base.js +48 -9
  5. package/element/alchemy_field.js +143 -51
  6. package/element/alchemy_field_schema.js +20 -8
  7. package/element/alchemy_pager.js +5 -5
  8. package/element/alchemy_select_item.js +18 -4
  9. package/element/alchemy_table.js +123 -38
  10. package/element/query_builder_entry.js +6 -5
  11. package/element/query_builder_value.js +1 -1
  12. package/helper/form_actions/url_action.js +2 -2
  13. package/helper/query_builder_ns.js +108 -0
  14. package/helper/query_builder_variable_definition/00_variable_definition.js +52 -1
  15. package/helper/widgets/alchemy_field_widget.js +6 -23
  16. package/helper/widgets/alchemy_form_widget.js +26 -2
  17. package/helper/widgets/alchemy_table_widget.js +18 -1
  18. package/helper_field/query_builder_field.js +49 -28
  19. package/helper_field/query_builder_value.js +0 -45
  20. package/helper_field/query_builder_variable.js +0 -45
  21. package/package.json +3 -2
  22. package/view/form/elements/alchemy_select_item.hwk +1 -3
  23. package/view/form/inputs/edit_inline/boolean.hwk +4 -0
  24. package/view/form/inputs/view_inline/boolean.hwk +19 -0
  25. package/view/form/inputs/view_inline/date.hwk +4 -0
  26. package/view/form/inputs/view_inline/enum.hwk +1 -0
  27. package/view/form/inputs/view_inline/objectid.hwk +1 -0
  28. package/view/form/select/qb_item.hwk +1 -0
  29. package/view/form/wrappers/edit_inline/default.hwk +1 -0
  30. package/view/form/wrappers/view_inline/default.hwk +1 -1
  31. package/view/form/inputs/view_inline/file.hwk +0 -6
package/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.1.12 (2022-10-12)
2
+
3
+ * Don't let users manually add form widgets
4
+ * Allow overriding alchemy-field title & description in extra widget settings
5
+ * Allow overriding an alchemy-field's used wrapper-view
6
+ * Add `AlchemyField#applyOptions(options)` method
7
+ * Allow an alchemy-field's field_title property to be set
8
+ * Allow turning off specific column filters in alchemy-table
9
+ * Add inline enum field view
10
+ * Fix alchemy-pager icons
11
+ * Use `Model#getDisplayTitle()` to get the display title of records for alchemy-select-items
12
+ * Make the QueryBuilder field's source data configurable
13
+ * Add `VariableDefinition.fromMany()` method
14
+ * `VariableDefinition.cast()` now also accepts `Field` instances
15
+ * Add `QueryBuilder.applyToCriteria()` method
16
+ * Fix schema fields sometimes getting the wrong schema from an Enum value
17
+
18
+ ## 0.1.11 (2022-07-23)
19
+
20
+ * Upgrade to `alchemy-media` v0.6.3
21
+ * Add `purpose` and `mode` attributes to AlchemyForm-based elements for improved field-template getting
22
+ * Add first few `edit_inline` and `view_inline` field templates
23
+
1
24
  ## 0.1.10 (2022-07-14)
2
25
 
3
26
  * Hide `code-input` contents until it's fully loaded
@@ -1,3 +1,36 @@
1
1
  alchemy-field {
2
2
  display: block;
3
+
4
+ &[field-type="boolean"] {
5
+
6
+ .field {
7
+ display: flex;
8
+ justify-content: center;
9
+ }
10
+
11
+ .boolean-wrapper {
12
+ &.boolean-true {
13
+ --boolean-color: green;
14
+ --text-color: white;
15
+ }
16
+
17
+ &.boolean-false {
18
+ --boolean-color: red;
19
+ --text-color: white;
20
+ }
21
+
22
+ &.boolean-null {
23
+ --boolean-color: #f3f3f3;
24
+ --text-color: #666;
25
+ font-style: italic;
26
+ }
27
+
28
+ background-color: var(--boolean-color);
29
+ border-radius: 5px;
30
+ padding: 4px 8px;
31
+ min-width: 1rem;
32
+ color: var(--text-color, white);
33
+ display: inline-flex;
34
+ }
35
+ }
3
36
  }
@@ -62,7 +62,6 @@ FormApi.setAction(async function related(conduit) {
62
62
  conduit.end(result);
63
63
  });
64
64
 
65
-
66
65
  /**
67
66
  * The related action
68
67
  *
@@ -5,9 +5,7 @@
5
5
  * @since 0.1.0
6
6
  * @version 0.1.0
7
7
  */
8
- var Base = Function.inherits('Alchemy.Element', 'Alchemy.Element.Form', function Base() {
9
- Base.super.call(this);
10
- });
8
+ var Base = Function.inherits('Alchemy.Element', 'Alchemy.Element.Form', 'Base');
11
9
 
12
10
  /**
13
11
  * Set the custom element prefix
@@ -59,27 +57,67 @@ Base.setStatic(function addParentTypeGetter(name, type) {
59
57
  });
60
58
 
61
59
  /**
62
- * The view-type determines which type of wrapper/field to use,
63
- * e.g.: view, list, edit, ...
60
+ * The purpose determines what the goal of the field is.
61
+ * Is it for editing or viewing?
64
62
  *
65
63
  * @author Jelle De Loecker <jelle@elevenways.be>
66
- * @since 0.1.0
67
- * @version 0.1.0
64
+ * @since 0.1.11
65
+ * @version 0.1.11
68
66
  */
69
- Base.setProperty(function view_type() {
67
+ Base.setAttribute('purpose', function getPurpose(value) {
68
+
69
+ if (value) {
70
+ return value;
71
+ }
70
72
 
71
- var value = this.getAttribute('view-type');
73
+ value = this.getAttribute('view-type');
72
74
 
73
75
  if (!value && this.alchemy_form) {
74
76
  value = this.alchemy_form.view_type;
75
77
  }
76
78
 
79
+ // Fallback to the "edit" type
77
80
  if (!value) {
78
81
  value = 'edit';
79
82
  }
80
83
 
81
84
  return value;
85
+ });
86
+
87
+ /**
88
+ * The mode is used as a hint to how this element should be rendered.
89
+ * Could be "inline", "standalone", ...
90
+ *
91
+ * @author Jelle De Loecker <jelle@elevenways.be>
92
+ * @since 0.1.11
93
+ * @version 0.1.11
94
+ */
95
+ Base.setAttribute('mode');
82
96
 
97
+ /**
98
+ * The view-type determines which type of wrapper/field to use,
99
+ * e.g.: view, list, edit, ...
100
+ *
101
+ * @deprecated You should use `purpose` for this
102
+ *
103
+ * @author Jelle De Loecker <jelle@elevenways.be>
104
+ * @since 0.1.0
105
+ * @version 0.1.11
106
+ */
107
+ Base.setProperty(function view_type() {
108
+
109
+ if (this.hasAttribute('view-type')) {
110
+ return this.getAttribute('view-type');
111
+ }
112
+
113
+ let purpose = this.purpose,
114
+ mode = this.mode;
115
+
116
+ if (mode) {
117
+ purpose += '_' + mode;
118
+ }
119
+
120
+ return purpose;
83
121
  }, function setViewType(value) {
84
122
 
85
123
  if (value == null) {
@@ -88,6 +126,7 @@ Base.setProperty(function view_type() {
88
126
  this.setAttribute('view-type', value);
89
127
  }
90
128
 
129
+ return value;
91
130
  });
92
131
 
93
132
  /**
@@ -70,6 +70,15 @@ Field.setAttribute('field-type');
70
70
  */
71
71
  Field.setAttribute('field-view');
72
72
 
73
+ /**
74
+ * The wrapper view override
75
+ *
76
+ * @author Jelle De Loecker <jelle@elevenways.be>
77
+ * @since 0.1.12
78
+ * @version 0.1.12
79
+ */
80
+ Field.setAttribute('wrapper-view');
81
+
73
82
  /**
74
83
  * Is this a read only field?
75
84
  *
@@ -260,22 +269,19 @@ Field.setProperty(function is_translatable() {
260
269
  *
261
270
  * @author Jelle De Loecker <jelle@elevenways.be>
262
271
  * @since 0.1.0
263
- * @version 0.1.0
272
+ * @version 0.1.12
264
273
  */
265
274
  Field.setProperty(function field_title() {
266
275
 
267
- let config = this.config,
268
- result;
269
-
270
- if (config) {
271
- result = config.title;
272
- }
276
+ let result = this._title || this.widget_settings?.title || this.config?.title;
273
277
 
274
278
  if (!result && this.field_name) {
275
279
  result = this.field_name.titleize();
276
280
  }
277
281
 
278
282
  return result;
283
+ }, function setTitle(value) {
284
+ this._title = value;
279
285
  });
280
286
 
281
287
  /**
@@ -283,16 +289,11 @@ Field.setProperty(function field_title() {
283
289
  *
284
290
  * @author Jelle De Loecker <jelle@elevenways.be>
285
291
  * @since 0.1.0
286
- * @version 0.1.0
292
+ * @version 0.1.12
287
293
  */
288
294
  Field.setProperty(function field_description() {
289
295
 
290
- let config = this.config,
291
- result;
292
-
293
- if (config && config.options) {
294
- result = config.options.description;
295
- }
296
+ let result = this.widget_settings?.description || this.config?.description;
296
297
 
297
298
  return result;
298
299
  });
@@ -349,60 +350,55 @@ Field.setProperty(function model() {
349
350
  });
350
351
 
351
352
  /**
352
- * Get the view to use for this field
353
+ * Get the preferred view file to use for this field
353
354
  *
354
355
  * @author Jelle De Loecker <jelle@elevenways.be>
355
356
  * @since 0.1.0
356
- * @version 0.1.0
357
+ * @version 0.1.11
357
358
  */
358
359
  Field.enforceProperty(function view_file(new_value, old_value) {
359
360
 
360
361
  if (!new_value) {
361
362
 
362
- let config = this.config,
363
- view_type = this.view_type;
364
-
365
- if (config) {
366
-
367
- let field_view;
363
+ let view_type = this.view_type,
364
+ field_view = this.field_view || this.field_type;
365
+
366
+ if (!field_view) {
367
+ let config = this.config;
368
368
 
369
- if (this.field_view) {
370
- field_view = this.field_view;
371
- } else {
369
+ if (config) {
372
370
  field_view = config.constructor.type_name;
373
371
  }
374
-
375
- new_value = 'form/inputs/' + view_type + '/' + field_view;
376
372
  }
373
+
374
+ new_value = this.generateTemplatePath('inputs', view_type, field_view);
377
375
  }
378
376
 
379
377
  return new_value;
380
378
  });
381
379
 
382
-
383
380
  /**
384
- * Get the wrapper to use for this field
381
+ * Get the preferred wrapper to use for this field
385
382
  *
386
383
  * @author Jelle De Loecker <jelle@elevenways.be>
387
384
  * @since 0.1.0
388
- * @version 0.1.0
385
+ * @version 0.1.11
389
386
  */
390
387
  Field.enforceProperty(function wrapper_file(new_value, old_value) {
391
388
 
392
389
  if (!new_value) {
393
390
 
394
- let config = this.config,
395
- view_type = this.view_type;
396
-
397
391
  let wrapper_type = this.wrapper_type;
398
392
 
399
393
  if (wrapper_type === false) {
400
394
  return false;
401
395
  }
402
396
 
403
- if (config) {
404
- let field_type = config.constructor.type_name;
405
- return 'form/wrappers/' + view_type + '/' + field_type;
397
+ let wrapper_view = this.wrapper_view || this.getFieldType(),
398
+ view_type = this.view_type;
399
+
400
+ if (wrapper_view) {
401
+ return this.generateTemplatePath('wrappers', view_type, wrapper_view);
406
402
  }
407
403
  }
408
404
 
@@ -414,7 +410,7 @@ Field.enforceProperty(function wrapper_file(new_value, old_value) {
414
410
  *
415
411
  * @author Jelle De Loecker <jelle@elevenways.be>
416
412
  * @since 0.1.0
417
- * @version 0.1.0
413
+ * @version 0.1.11
418
414
  */
419
415
  Field.setProperty(function view_files() {
420
416
 
@@ -425,14 +421,20 @@ Field.setProperty(function view_files() {
425
421
  result.push(view_file);
426
422
  }
427
423
 
428
- let config = this.config,
424
+ let field_type = this.getFieldType(),
429
425
  view_type = this.view_type;
430
426
 
431
- if (config) {
432
- let field_type = config.constructor.type_name,
433
- view = 'form/inputs/' + view_type + '/' + field_type;
434
-
427
+ if (field_type) {
428
+ let view = this.generateTemplatePath('inputs', view_type, field_type);
429
+
435
430
  result.push(view);
431
+
432
+ let purpose = this.purpose;
433
+
434
+ if (purpose) {
435
+ view = this.generateTemplatePath('inputs', purpose, field_type);
436
+ result.push(view);
437
+ }
436
438
  }
437
439
 
438
440
  if (result.length == 0) {
@@ -440,7 +442,7 @@ Field.setProperty(function view_files() {
440
442
  }
441
443
 
442
444
  // Fallback to a simple string input
443
- result.push('form/inputs/' + view_type + '/string');
445
+ result.push(this.generateTemplatePath('inputs', view_type, 'string'));
444
446
 
445
447
  return result;
446
448
  });
@@ -450,7 +452,7 @@ Field.setProperty(function view_files() {
450
452
  *
451
453
  * @author Jelle De Loecker <jelle@elevenways.be>
452
454
  * @since 0.1.0
453
- * @version 0.1.0
455
+ * @version 0.1.11
454
456
  */
455
457
  Field.setProperty(function wrapper_files() {
456
458
 
@@ -466,22 +468,21 @@ Field.setProperty(function wrapper_files() {
466
468
  result.push(wrapper_file);
467
469
  }
468
470
 
469
- let config = this.config,
471
+ let field_type = this.getFieldType(),
470
472
  view_type = this.view_type;
471
473
 
472
- if (config) {
473
- let field_type = config.constructor.type_name,
474
- view = 'form/wrappers/' + view_type + '/' + field_type;
474
+ if (field_type) {
475
+ let view = this.generateTemplatePath('wrappers', view_type, field_type);
475
476
 
476
477
  result.push(view);
477
478
 
478
- view = 'form/wrappers/' + view_type + '/default';
479
+ view = this.generateTemplatePath('wrappers', view_type, 'default');
479
480
  result.push(view);
480
481
 
481
- view = 'form/wrappers/default/' + field_type;
482
+ view = this.generateTemplatePath('wrappers', 'default', field_type);
482
483
  result.push(view);
483
484
 
484
- view = 'form/wrappers/default/default';
485
+ view = this.generateTemplatePath('wrappers', 'default', 'default');
485
486
  result.push(view);
486
487
  }
487
488
 
@@ -584,6 +585,97 @@ Field.setProperty(function value() {
584
585
  }
585
586
  });
586
587
 
588
+ /**
589
+ * Apply options
590
+ *
591
+ * @author Jelle De Loecker <jelle@elevenways.be>
592
+ * @since 0.1.12
593
+ * @version 0.1.12
594
+ *
595
+ * @param {Object} options
596
+ */
597
+ Field.setMethod(function applyOptions(options) {
598
+
599
+ if (!options || typeof options != 'object') {
600
+ return;
601
+ }
602
+
603
+ if (options.purpose) {
604
+ this.purpose = options.purpose;
605
+ }
606
+
607
+ if (options.mode) {
608
+ this.mode = options.mode;
609
+ }
610
+
611
+ if (options.view) {
612
+ this.field_view = options.view;
613
+ }
614
+
615
+ if (options.wrapper) {
616
+ this.wrapper_view = options.wrapper;
617
+ }
618
+
619
+ if (options.readonly) {
620
+ this.readonly = true;
621
+ }
622
+
623
+ if (options.widget_settings) {
624
+ this.widget_settings = options.widget_settings;
625
+ }
626
+
627
+ if (options.data_src) {
628
+ this.data_src = options.data_src;
629
+ }
630
+
631
+ if (options.title) {
632
+ this.field_title = options.title;
633
+ }
634
+
635
+ });
636
+
637
+ /**
638
+ * Get the field type
639
+ *
640
+ * @author Jelle De Loecker <jelle@elevenways.be>
641
+ * @since 0.1.11
642
+ * @version 0.1.11
643
+ *
644
+ * @return {String}
645
+ */
646
+ Field.setMethod(function getFieldType() {
647
+
648
+ let result = this.field_type;
649
+
650
+ if (!result) {
651
+ let config = this.config;
652
+
653
+ if (config) {
654
+ result = config.constructor.type_name;
655
+ }
656
+ }
657
+
658
+ return result;
659
+ });
660
+
661
+ /**
662
+ * Generate a template path
663
+ *
664
+ * @author Jelle De Loecker <jelle@elevenways.be>
665
+ * @since 0.1.11
666
+ * @version 0.1.11
667
+ *
668
+ * @param {String} container_type The container (inputs/wrappers)
669
+ * @param {String} view_type The view (edit/view/edit_inline/...)
670
+ * @param {String} field_type The name of the field (and thus the view)
671
+ *
672
+ * @return {String}
673
+ */
674
+ Field.setMethod(function generateTemplatePath(container_type, view_type, field_type) {
675
+ let result = 'form/' + container_type + '/' + view_type + '/' + field_type;
676
+ return result;
677
+ });
678
+
587
679
  /**
588
680
  * Get the rule violations for this field
589
681
  *
@@ -5,9 +5,7 @@
5
5
  * @since 0.1.0
6
6
  * @version 0.1.0
7
7
  */
8
- var FieldSchema = Function.inherits('Alchemy.Element.Form.FieldCustom', function FieldSchema() {
9
- FieldSchema.super.call(this);
10
- });
8
+ var FieldSchema = Function.inherits('Alchemy.Element.Form.FieldCustom', 'FieldSchema');
11
9
 
12
10
  /**
13
11
  * The template to use for the content of this element
@@ -23,7 +21,7 @@ FieldSchema.setTemplateFile('form/elements/alchemy_field_schema');
23
21
  *
24
22
  * @author Jelle De Loecker <jelle@elevenways.be>
25
23
  * @since 0.1.0
26
- * @version 0.1.4
24
+ * @version 0.1.12
27
25
  */
28
26
  FieldSchema.setProperty(function schema() {
29
27
 
@@ -38,15 +36,29 @@ FieldSchema.setProperty(function schema() {
38
36
  let values = other_field.config.options.values;
39
37
 
40
38
  if (values) {
39
+ let value;
41
40
 
42
41
  if (values instanceof Classes.Alchemy.Map.Backed) {
43
- schema = values.get(other_field.value);
42
+ value = values.get(other_field.value);
44
43
  } else {
45
- schema = values[other_field.value];
44
+ value = values[other_field.value];
45
+ }
46
+
47
+ if (value) {
48
+ if (value.schema) {
49
+ schema = value.schema;
50
+ } else if (value.value) {
51
+ // Enumified values can be wrapped on the server-side
52
+ value = value.value;
53
+
54
+ if (value.schema) {
55
+ schema = value.schema;
56
+ }
57
+ }
46
58
  }
47
59
 
48
- if (schema && schema.schema) {
49
- schema = schema.schema;
60
+ if (!schema) {
61
+ schema = value;
50
62
  }
51
63
  }
52
64
  }
@@ -12,28 +12,28 @@ const Pager = Function.inherits('Alchemy.Element.Form.Base', 'Pager');
12
12
  *
13
13
  * @author Jelle De Loecker <jelle@elevenways.be>
14
14
  * @since 0.1.0
15
- * @version 0.1.0
15
+ * @version 0.1.12
16
16
  */
17
17
  Pager.setTemplate(`<ul>
18
18
  <li class="afp-first">
19
19
  <a href="#" aria-label="First page">
20
- <al-ico type="arrow-left-double"></al-ico>
20
+ <al-ico icon-name="left-to-line"></al-ico>
21
21
  </a>
22
22
  </li>
23
23
  <li class="afp-prev">
24
24
  <a href="#" aria-label="Previous page">
25
- <al-ico type="arrow-left"></al-ico>
25
+ <al-ico icon-name="left"></al-ico>
26
26
  </a>
27
27
  </li>
28
28
 
29
29
  <li class="afp-next">
30
30
  <a href="#" aria-label="Next page">
31
- <al-ico type="arrow-right"></al-ico>
31
+ <al-ico icon-name="right"></al-ico>
32
32
  </a>
33
33
  </li>
34
34
  <li class="afp-last">
35
35
  <a href="#" aria-label="Last page">
36
- <al-ico type="arrow-right-double"></al-ico>
36
+ <al-ico icon-name="right-to-line"></al-ico>
37
37
  </a>
38
38
  </li>
39
39
  </ul>`, true);
@@ -85,13 +85,27 @@ Item.enforceProperty(function alchemy_select(new_value) {
85
85
  *
86
86
  * @author Jelle De Loecker <jelle@elevenways.be>
87
87
  * @since 0.1.5
88
- * @version 0.1.5
88
+ * @version 0.1.12
89
89
  */
90
90
  Item.setProperty(function display_title() {
91
91
 
92
- if (!this.data) {
93
- return '';
92
+ let result;
93
+
94
+ if (this.data) {
95
+ const model = this.data.$model;
96
+
97
+ if (model) {
98
+ result = model.getDisplayTitle(this.data, ['title', 'name']);
99
+ }
100
+ }
101
+
102
+ if (!result) {
103
+ if (this.data) {
104
+ result = this.data.title || this.data.name || this.data.$pk || this.value;
105
+ } else {
106
+ result = this.value;
107
+ }
94
108
  }
95
109
 
96
- return this.data.title || this.data.name || this.data.username || this.data.$pk || this.value;
110
+ return result || '';
97
111
  });