alchemy-form 0.1.3 → 0.1.6

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 (42) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/assets/stylesheets/form/alchemy_field_array.scss +4 -0
  3. package/assets/stylesheets/form/alchemy_toggle.scss +2 -0
  4. package/assets/stylesheets/form/query_builder.scss +185 -0
  5. package/config/routes.js +8 -0
  6. package/controller/form_api_controller.js +51 -2
  7. package/element/00_form_base.js +37 -1
  8. package/element/20_query_builder_base.js +82 -0
  9. package/element/alchemy_field.js +65 -15
  10. package/element/alchemy_field_schema.js +45 -11
  11. package/element/alchemy_form.js +1 -12
  12. package/element/alchemy_select.js +68 -13
  13. package/element/alchemy_select_item.js +42 -1
  14. package/element/alchemy_table.js +123 -21
  15. package/element/query_builder.js +90 -0
  16. package/element/query_builder_entry.js +388 -0
  17. package/element/query_builder_group.js +221 -0
  18. package/element/query_builder_value.js +435 -0
  19. package/helper/form_actions/00_form_action.js +328 -0
  20. package/helper/form_actions/url_action.js +69 -0
  21. package/helper/query_builder_variable_definition/00_variable_definition.js +351 -0
  22. package/helper/query_builder_variable_definition/boolean_variable_definition.js +24 -0
  23. package/helper/query_builder_variable_definition/number_variable_definition.js +106 -0
  24. package/helper/query_builder_variable_definition/string_variable_definition.js +46 -0
  25. package/helper/widgets/alchemy_field_widget.js +48 -1
  26. package/helper/widgets/alchemy_form_widget.js +17 -2
  27. package/helper_field/query_builder_assignment.js +11 -0
  28. package/helper_field/query_builder_field.js +91 -0
  29. package/helper_field/query_builder_value.js +56 -0
  30. package/package.json +2 -2
  31. package/view/form/elements/alchemy_field_array.hwk +3 -1
  32. package/view/form/elements/alchemy_field_array_entry.hwk +3 -1
  33. package/view/form/elements/alchemy_field_schema.hwk +2 -4
  34. package/view/form/elements/alchemy_select_item.hwk +6 -1
  35. package/view/form/elements/query_builder.hwk +1 -0
  36. package/view/form/elements/query_builder_entry.hwk +33 -0
  37. package/view/form/elements/query_builder_group.hwk +64 -0
  38. package/view/form/elements/query_builder_value.hwk +10 -0
  39. package/view/form/inputs/edit/query_builder.hwk +5 -0
  40. package/view/form/inputs/edit/query_builder_assignment.hwk +6 -0
  41. package/view/form/inputs/edit/query_builder_value.hwk +11 -0
  42. package/view/form/select/qb_item.hwk +7 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 0.1.6 (2022-05-31)
2
+
3
+ * Fix `FormApi#related` action searching through multiple fields in an `and` group
4
+ * Use microcopy translations in buttons
5
+ * Add `Action` class for user interaction
6
+ * Add context-menu support to `alchemy-table`
7
+ * Add support for using custom templates for `alchemy-select` options
8
+ * Add QueryBuilder
9
+
10
+ ## 0.1.5 (2022-03-21)
11
+
12
+ * Print `alchemy-select-item` contents as text, not html
13
+ * Only get a maximum of 50 related items
14
+ * Set the parent `alchemy-select` element when creating `alchemy-select-item` element
15
+
16
+ ## 0.1.4 (2022-03-16)
17
+
18
+ * Make fields work with the new `Alchemy.Map.Backed` and `Alchemy.Map.Enum` class
19
+ * Add the `field_path_in_current_schema` property
20
+ * Make sure `alchemy-fields` know which `alchemy-form` they belong to
21
+ * Improve the `original_value` getters
22
+ * Set the `field-type` attribute of `alchemy-field` elements
23
+ * Fix `alchemy-field` elements sometimes not getting their value elements
24
+
1
25
  ## 0.1.3 (2022-02-20)
2
26
 
3
27
  * Fix datetime fields never showing their value
@@ -10,4 +10,8 @@ alchemy-field-array-entry {
10
10
  align-content: center;
11
11
  padding-left: 0.5rem;
12
12
  }
13
+
14
+ .button .remove > * {
15
+ writing-mode: vertical-rl;
16
+ }
13
17
  }
@@ -34,6 +34,7 @@ alchemy-toggle {
34
34
  height: 36px;
35
35
  flex: 0 0 134px;
36
36
  border-radius: 4px;
37
+ min-width: 134px;
37
38
 
38
39
  transition: background-color 0.3s cubic-bezier(0, 1, 0.5, 1);
39
40
  background: #848484;
@@ -43,6 +44,7 @@ alchemy-toggle {
43
44
  &:after {
44
45
  text-transform: uppercase;
45
46
  text-align: center;
47
+ box-sizing: inherit;
46
48
  }
47
49
 
48
50
  &:before {
@@ -0,0 +1,185 @@
1
+ :root {
2
+ --qb-border-color: #d3d3d3;
3
+ --qb-btn-background-color: #fafafa;
4
+ --qb-text-color: #3e3e3e;
5
+
6
+ --qb-btn-primary: #286090;
7
+ --qb-btn-primary-border: #204d74;
8
+ --qb-btn-primary-text-color: #fafafa;
9
+
10
+ --qb-btn-active: #3da50e;
11
+ --qb-btn-active-text: #fff;
12
+ --qb-btn-border-color: #d3d3d3;
13
+ }
14
+
15
+ alchemy-query-builder {
16
+ display: block;
17
+ border-color: var(--qb-border-color);
18
+ padding: 1rem;
19
+ position: relative;
20
+ }
21
+
22
+ alchemy-query-builder-group {
23
+ display: block;
24
+ padding: 10px;
25
+ padding-bottom: 6px;
26
+ background-color: rgba(250,240,210,.5);
27
+ border: 1px solid #dcc896;
28
+ position: relative;
29
+
30
+
31
+ margin: 4px 0;
32
+ padding: 5px;
33
+ border: 1px solid #eee;
34
+ border-radius: 3px;
35
+
36
+ .qb-group-header {
37
+ margin-bottom: 10px;
38
+ }
39
+
40
+ .qb-group-actions {
41
+ float: right;
42
+ }
43
+
44
+ .qb-group-body {
45
+ .qb-rules-list {
46
+ display: flex;
47
+ flex-flow: column;
48
+ gap: 1rem;
49
+ padding: 0 0 0 15px;
50
+
51
+ & > :first-child::before {
52
+ top: -11px;
53
+ height: calc(50% + 14px);
54
+ }
55
+
56
+ & > :last-child::before {
57
+ border-radius: 0 0 0 4px;
58
+ }
59
+
60
+ & > ::after {
61
+ top: 50%;
62
+ border-width: 0 0 0 2px;
63
+ }
64
+
65
+ & >:last-child::after {
66
+ display: none;
67
+ }
68
+
69
+ > ::before {
70
+ top: -2px;
71
+ border-width: 0 0 2px 2px;
72
+ }
73
+
74
+ > ::before,
75
+ > ::after {
76
+ content: '';
77
+ position: absolute;
78
+ left: -10px;
79
+ width: 10px;
80
+ height: calc(50% + 4px);
81
+ border-color: #ccc;
82
+ border-style: solid;
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ alchemy-query-builder-value {
89
+ min-width: 10rem;
90
+ }
91
+
92
+ alchemy-query-builder-entry {
93
+ min-width: 40rem;
94
+ }
95
+
96
+ alchemy-query-builder-value,
97
+ alchemy-query-builder-entry {
98
+ display: flex;
99
+ gap: 0.7rem;
100
+
101
+ alchemy-select {
102
+ min-width: 11rem;
103
+ }
104
+
105
+ .qb-delete-wrapper {
106
+ flex: 1;
107
+ text-align: right;
108
+ }
109
+
110
+ .qb-value-wrapper {
111
+ display: flex;
112
+ gap: 0.3rem;
113
+ }
114
+
115
+ .qb-value-input {
116
+ height: 100%;
117
+ color: black;
118
+ padding: 0 0.5rem;
119
+ }
120
+ }
121
+
122
+ .qb-group-invert,
123
+ .qb-button-group,
124
+ .qb-group-type {
125
+ display: inline-flex;
126
+ box-shadow: 0 1px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);
127
+ user-select: none;
128
+
129
+ > input {
130
+ height: 1px;
131
+ width: 1px;
132
+ opacity: 0;
133
+ position: absolute;
134
+ margin: 0 0 0 -1px;
135
+ }
136
+
137
+ > input + label {
138
+ opacity: 0.7;
139
+ }
140
+
141
+ > input:checked + label {
142
+ background-color: var(--qb-btn-active);
143
+ border-color: var(--qb-btn-active);
144
+ color: var(--qb-btn-active-text);
145
+ opacity: 1;
146
+ }
147
+ }
148
+
149
+ .qb-primary {
150
+ --qb-btn-background-color: var(--qb-btn-primary);
151
+ --qb-text-color: var(--qb-btn-primary-text-color);
152
+ --qb-btn-border-color: var(--qb-btn-primary-border);
153
+ }
154
+
155
+ button.qb-btn {
156
+ border-color: var(--qb-btn-border-color);
157
+ }
158
+
159
+ .qb-btn {
160
+ background-color: var(--qb-btn-background-color);
161
+ color: var(--qb-text-color);
162
+ transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
163
+ border-radius: 2px;
164
+ cursor: pointer;
165
+ display: inline-block;
166
+ font-weight: bold;
167
+ text-align: center;
168
+ padding: 2px 12px 1px;
169
+ }
170
+
171
+ .qb-select-item {
172
+ display: flex;
173
+ flex-flow: column;
174
+ padding-right: 1rem;
175
+
176
+ .option-title {
177
+ font-weight: bold;
178
+ font-size: 1rem;
179
+ white-space: nowrap;
180
+ }
181
+
182
+ .option-description {
183
+ min-width: 10rem;
184
+ }
185
+ }
package/config/routes.js CHANGED
@@ -2,4 +2,12 @@ Router.add({
2
2
  name : 'FormApi#related',
3
3
  methods : 'post',
4
4
  paths : '/api/form/data/related',
5
+ policy : 'logged_in',
6
+ });
7
+
8
+ Router.add({
9
+ name : 'FormApi#queryBuilderData',
10
+ methods : 'post',
11
+ paths : '/api/form/data/qbdata',
12
+ policy : 'logged_in',
5
13
  });
@@ -17,7 +17,7 @@ const FormApi = Function.inherits('Alchemy.Controller', 'FormApi');
17
17
  *
18
18
  * @author Jelle De Loecker <jelle@elevenways.be>
19
19
  * @since 0.1.0
20
- * @version 0.1.0
20
+ * @version 0.1.6
21
21
  *
22
22
  * @param {Conduit} conduit
23
23
  */
@@ -26,8 +26,26 @@ FormApi.setAction(async function related(conduit) {
26
26
  const body = conduit.body;
27
27
 
28
28
  const model = this.getModel(body.assoc_model);
29
+ let crit = model.find();
30
+ crit.limit(50);
31
+ crit.setOption('scenario', 'related_data');
32
+
33
+ if (body.config && body.config.search) {
34
+ let display_fields = Array.cast(model.displayField);
35
+
36
+ let or = crit.or();
37
+ let rx = RegExp.interpretWildcard('*' + body.config.search + '*', 'i');
29
38
 
30
- let records = await model.find('all');
39
+ for (let field of display_fields) {
40
+ if (!field) {
41
+ continue;
42
+ }
43
+
44
+ or.where(field).equals(rx);
45
+ }
46
+ }
47
+
48
+ let records = await model.find('all', crit);
31
49
 
32
50
  let result = {
33
51
  available : records.available,
@@ -36,3 +54,34 @@ FormApi.setAction(async function related(conduit) {
36
54
 
37
55
  conduit.end(result);
38
56
  });
57
+
58
+
59
+ /**
60
+ * The related action
61
+ *
62
+ * @author Jelle De Loecker <jelle@elevenways.be>
63
+ * @since 0.1.6
64
+ * @version 0.1.6
65
+ *
66
+ * @param {Conduit} conduit
67
+ */
68
+ FormApi.setAction(async function queryBuilderData(conduit) {
69
+
70
+ const body = conduit.body;
71
+ const config = body.config || {};
72
+ let result;
73
+
74
+ if (body && body.model) {
75
+ const model = this.getModel(body.model);
76
+
77
+ if (body.$pk) {
78
+ const doc = await model.findByPk(body.$pk);
79
+
80
+ if (doc) {
81
+ result = await doc.loadQueryBuilderData(config);
82
+ }
83
+ }
84
+ }
85
+
86
+ conduit.end(result);
87
+ });
@@ -125,6 +125,38 @@ Base.setProperty(function wrapper_type() {
125
125
 
126
126
  });
127
127
 
128
+ /**
129
+ * Get the path of this field value in the current (sub)schema
130
+ *
131
+ * @author Jelle De Loecker <jelle@elevenways.be>
132
+ * @since 0.1.4
133
+ * @version 0.1.4
134
+ */
135
+ Base.setProperty(function field_path_in_current_schema() {
136
+
137
+ let result = [],
138
+ parent = this.getParentField(),
139
+ name;
140
+
141
+ name = this.getPathEntryName();
142
+
143
+ if (name) {
144
+ result.push(name);
145
+ }
146
+
147
+ while (parent && !(parent instanceof Classes.Alchemy.Element.Form.FieldSchema)) {
148
+ name = parent.getPathEntryName();
149
+
150
+ if (name) {
151
+ result.unshift(name);
152
+ }
153
+
154
+ parent = parent.getParentField();
155
+ }
156
+
157
+ return result.join('.');
158
+ });
159
+
128
160
  /**
129
161
  * Get the path of this field value in the current record
130
162
  *
@@ -162,7 +194,7 @@ Base.setProperty(function field_path_in_record() {
162
194
  *
163
195
  * @author Jelle De Loecker <jelle@elevenways.be>
164
196
  * @since 0.1.3
165
- * @version 0.1.3
197
+ * @version 0.1.4
166
198
  *
167
199
  * @return {Alchemy.Element.Form.Base}
168
200
  */
@@ -179,6 +211,10 @@ Base.setMethod(function getParentField() {
179
211
  parent = parent.parentElement;
180
212
  }
181
213
 
214
+ if (this.field_context) {
215
+ return this.field_context;
216
+ }
217
+
182
218
  return false;
183
219
  });
184
220
 
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The base abstract query builder custom element
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.1.6
6
+ * @version 0.1.6
7
+ */
8
+ const QueryBuilderBase = Function.inherits('Alchemy.Element.Form.Base', 'QueryBuilderBase');
9
+
10
+ /**
11
+ * Don't register this as a custom element,
12
+ * but don't let child classes inherit this
13
+ *
14
+ * @author Jelle De Loecker <jelle@elevenways.be>
15
+ * @since 0.1.6
16
+ * @version 0.1.6
17
+ */
18
+ QueryBuilderBase.setStatic('is_abstract_class', true, false);
19
+
20
+ /**
21
+ * The stylesheet to load for this element
22
+ *
23
+ * @author Jelle De Loecker <jelle@elevenways.be>
24
+ * @since 0.1.6
25
+ * @version 0.1.6
26
+ */
27
+ QueryBuilderBase.setStylesheetFile('form/query_builder');
28
+
29
+ /**
30
+ * Get the dataprovider
31
+ *
32
+ * @author Jelle De Loecker <jelle@elevenways.be>
33
+ * @since 0.1.6
34
+ * @version 0.1.6
35
+ */
36
+ QueryBuilderBase.enforceProperty(function dataprovider(new_value) {
37
+
38
+ if (new_value == null) {
39
+ if (this.assigned_data.dataprovider) {
40
+ new_value = this.assigned_data.dataprovider;
41
+ } else if (this.root_query_builder && this.root_query_builder.dataprovider) {
42
+ new_value = this.root_query_builder.dataprovider;
43
+ }
44
+ } else {
45
+ this.assigned_data.dataprovider = new_value;
46
+ }
47
+
48
+ return new_value;
49
+ });
50
+
51
+ /**
52
+ * Getter for the rules list element
53
+ *
54
+ * @author Jelle De Loecker <jelle@elevenways.be>
55
+ * @since 0.1.6
56
+ * @version 0.1.6
57
+ */
58
+ QueryBuilderBase.setProperty(function root_query_builder() {
59
+ return this.queryParents('alchemy-query-builder');
60
+ });
61
+
62
+ /**
63
+ * Get the value type of the given input
64
+ *
65
+ * @author Jelle De Loecker <jelle@elevenways.be>
66
+ * @since 0.1.6
67
+ * @version 0.1.6
68
+ *
69
+ * @param {HTMLElement}
70
+ *
71
+ * @return {String}
72
+ */
73
+ QueryBuilderBase.setMethod(function getValueType(element) {
74
+
75
+ if (!element) {
76
+ return null;
77
+ }
78
+
79
+ let result = element.value_type || element.type;
80
+
81
+ return result;
82
+ });
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.1.0
6
- * @version 0.1.0
6
+ * @version 0.1.4
7
7
  */
8
- var Field = Function.inherits('Alchemy.Element.Form.Base', function Field() {
9
- Field.super.call(this);
10
- });
8
+ const Field = Function.inherits('Alchemy.Element.Form.Base', 'Field');
11
9
 
12
10
  /**
13
11
  * The template to use for the content of this element
@@ -45,6 +43,15 @@ Field.setStatic('use_new_renderer_scope', true);
45
43
  */
46
44
  Field.setAttribute('field-name');
47
45
 
46
+ /**
47
+ * The type of the field
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.1.4
51
+ * @version 0.1.4
52
+ */
53
+ Field.setAttribute('field-type');
54
+
48
55
  /**
49
56
  * The view override
50
57
  *
@@ -77,9 +84,24 @@ Field.setAssignedProperty('widget_settings');
77
84
  *
78
85
  * @author Jelle De Loecker <jelle@elevenways.be>
79
86
  * @since 0.1.0
80
- * @version 0.1.0
87
+ * @version 0.1.4
81
88
  */
82
- Field.addParentTypeGetter('alchemy_form', 'alchemy-form');
89
+ Field.enforceProperty(function alchemy_form(new_value) {
90
+
91
+ if (new_value == null) {
92
+ new_value = this.queryUp('alchemy-form');
93
+
94
+ if (!new_value && this.field_context) {
95
+ new_value = this.field_context.queryUp('alchemy-form');
96
+ }
97
+
98
+ if (!new_value && this.alchemy_field_schema && this.alchemy_field_schema.alchemy_field) {
99
+ new_value = this.alchemy_field_schema.alchemy_field.alchemy_form;
100
+ }
101
+ }
102
+
103
+ return new_value;
104
+ });
83
105
 
84
106
  /**
85
107
  * Get the error area
@@ -111,7 +133,7 @@ Field.enforceProperty(function alchemy_field_schema(new_value, old_value) {
111
133
  *
112
134
  * @author Jelle De Loecker <jelle@elevenways.be>
113
135
  * @since 0.1.0
114
- * @version 0.1.0
136
+ * @version 0.1.4
115
137
  */
116
138
  Field.enforceProperty(function config(new_value, old_value) {
117
139
 
@@ -124,6 +146,12 @@ Field.enforceProperty(function config(new_value, old_value) {
124
146
  }
125
147
  }
126
148
 
149
+ if (new_value && new_value.constructor && new_value.constructor.type_name) {
150
+ this.field_type = new_value.constructor.type_name;
151
+ } else {
152
+ this.field_type = null;
153
+ }
154
+
127
155
  return new_value;
128
156
  });
129
157
 
@@ -465,17 +493,20 @@ Field.setProperty(function wrapper_files() {
465
493
  *
466
494
  * @author Jelle De Loecker <jelle@elevenways.be>
467
495
  * @since 0.1.0
468
- * @version 0.1.0
496
+ * @version 0.1.4
469
497
  */
470
498
  Field.setProperty(function original_value() {
471
499
 
472
- let alchemy_field_schema = this.alchemy_field_schema;
500
+ let alchemy_field_schema = this.alchemy_field_schema,
501
+ path = this.field_path_in_current_schema;
473
502
 
474
503
  if (alchemy_field_schema) {
475
504
  let original_schema_value = alchemy_field_schema.original_value;
476
505
 
477
506
  if (original_schema_value) {
478
- return original_schema_value[this.field_name];
507
+ if (path) {
508
+ return Object.path(original_schema_value, path);
509
+ }
479
510
  }
480
511
 
481
512
  return;
@@ -484,7 +515,7 @@ Field.setProperty(function original_value() {
484
515
  let form = this.alchemy_form;
485
516
 
486
517
  if (form && form.document) {
487
- return form.document[this.field_name];
518
+ return Object.path(form.document, path);
488
519
  }
489
520
  });
490
521
 
@@ -493,7 +524,7 @@ Field.setProperty(function original_value() {
493
524
  *
494
525
  * @author Jelle De Loecker <jelle@elevenways.be>
495
526
  * @since 0.1.0
496
- * @version 0.1.3
527
+ * @version 0.1.4
497
528
  */
498
529
  Field.setProperty(function value_element() {
499
530
 
@@ -506,7 +537,9 @@ Field.setProperty(function value_element() {
506
537
  input = this.querySelector('alchemy-field-array');
507
538
  } else if (this.contains_schema) {
508
539
  input = this.querySelector('alchemy-field-schema');
509
- } else {
540
+ }
541
+
542
+ if (!input) {
510
543
  input = this.querySelector('.alchemy-field-value');
511
544
  }
512
545
 
@@ -675,16 +708,33 @@ Field.setMethod(function retained() {
675
708
  *
676
709
  * @author Jelle De Loecker <jelle@elevenways.be>
677
710
  * @since 0.1.0
678
- * @version 0.1.0
711
+ * @version 0.1.6
679
712
  *
680
713
  * @param {Object} config
681
714
  * @param {HTMLElement} element
682
715
  */
683
- Field.setMethod(function loadData(config, element) {
716
+ Field.setMethod(async function loadData(config, element) {
684
717
 
685
718
  let field = this.config;
686
719
 
687
720
  if (field) {
721
+
722
+ let result;
723
+
724
+ if (typeof field.loadData == 'function') {
725
+
726
+ try {
727
+ result = await field.loadData(config, element);
728
+ } catch (err) {
729
+ // Ignore
730
+ console.error('Error loading field data:', err);
731
+ }
732
+
733
+ if (result) {
734
+ return result;
735
+ }
736
+ }
737
+
688
738
  return this.hawkejs_helpers.Alchemy.getResource({
689
739
  name : 'FormApi#related',
690
740
  post : true,