alchemy-form 0.2.7 → 0.2.9
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/CHANGELOG.md +14 -0
- package/assets/stylesheets/form/elements/_apex_charts.scss +7 -0
- package/assets/stylesheets/form/elements/index.scss +2 -1
- package/config/routes.js +13 -0
- package/controller/form_api_controller.js +37 -1
- package/element/10_dataprovider.js +3 -3
- package/element/al_apex_chart.js +38 -0
- package/element/al_field.js +273 -21
- package/element/al_field_schema.js +2 -7
- package/element/al_form.js +164 -33
- package/element/al_table.js +4 -1
- package/element/al_toggle.js +9 -1
- package/helper/field_recompute_handler.js +50 -0
- package/helper/form_actions/00_form_action.js +0 -1
- package/helper/pathway/leaf.js +0 -5
- package/package.json +1 -1
- package/view/form/elements/alchemy_field.hwk +1 -1
- package/view/form/inputs/edit/belongs_to.hwk +2 -0
- package/view/form/inputs/edit/decimal.hwk +7 -0
- package/view/form/inputs/edit/fixed_decimal.hwk +15 -0
- package/view/form/inputs/edit/hidden.hwk +7 -0
- package/view/form/inputs/edit/integer.hwk +8 -0
- package/view/form/inputs/edit/local_date.hwk +13 -0
- package/view/form/inputs/edit/local_date_time.hwk +20 -0
- package/view/form/inputs/edit/local_time.hwk +14 -0
- package/view/form/inputs/hidden/hidden.hwk +7 -0
- package/view/form/inputs/view/hidden.hwk +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 0.2.9 (2023-11-27)
|
|
2
|
+
|
|
3
|
+
* Add support for new local Date/Time fields & Decimal fields
|
|
4
|
+
* Add "hidden" field templates
|
|
5
|
+
* Fix `al-toggle` not firing a change event
|
|
6
|
+
* Fixed `al-field` rendering to update content for non-value setter elements when a new value is assigned
|
|
7
|
+
* Use `constraints` data from sibling fields when loading associated field data
|
|
8
|
+
* Automatically update computed field values when a field it depends on changes
|
|
9
|
+
|
|
10
|
+
## 0.2.8 (2023-10-15)
|
|
11
|
+
|
|
12
|
+
* Add basic `al-apex-chart` element
|
|
13
|
+
* Fix `al-table` always turning `DocumentList` instances into dumb arrays
|
|
14
|
+
|
|
1
15
|
## 0.2.7 (2023-10-05)
|
|
2
16
|
|
|
3
17
|
* If a `al-field` element is assigned a `Field` instance to its `config` property without a parent `schema` property, it will be stored in its `assigned_data` property
|
package/config/routes.js
CHANGED
|
@@ -3,6 +3,8 @@ Router.add({
|
|
|
3
3
|
methods : 'post',
|
|
4
4
|
paths : '/api/form/data/related',
|
|
5
5
|
policy : 'logged_in',
|
|
6
|
+
cache : true,
|
|
7
|
+
is_system_route : true,
|
|
6
8
|
});
|
|
7
9
|
|
|
8
10
|
Router.add({
|
|
@@ -10,4 +12,15 @@ Router.add({
|
|
|
10
12
|
methods : 'post',
|
|
11
13
|
paths : '/api/form/data/qbdata',
|
|
12
14
|
policy : 'logged_in',
|
|
15
|
+
cache : true,
|
|
16
|
+
is_system_route : true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
Router.add({
|
|
20
|
+
name : 'FormApi#recompute',
|
|
21
|
+
methods : 'post',
|
|
22
|
+
paths : '/api/form/data/recompute/{model_name}/{field}',
|
|
23
|
+
policy : 'logged_in',
|
|
24
|
+
is_system_route : true,
|
|
25
|
+
permission : 'model.{model_name}.recompute.{field}',
|
|
13
26
|
});
|
|
@@ -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.2.
|
|
20
|
+
* @version 0.2.9
|
|
21
21
|
*
|
|
22
22
|
* @param {Conduit} conduit
|
|
23
23
|
*/
|
|
@@ -31,6 +31,12 @@ FormApi.setAction(async function related(conduit) {
|
|
|
31
31
|
crit.limit(50);
|
|
32
32
|
crit.setOption('scenario', 'related_data');
|
|
33
33
|
|
|
34
|
+
if (body.constraints) {
|
|
35
|
+
for (let key in body.constraints) {
|
|
36
|
+
crit.where(key).equals(body.constraints[key]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
if (config.value) {
|
|
35
41
|
crit.where(model.primary_key).equals(config.value);
|
|
36
42
|
} else if (config.search) {
|
|
@@ -84,6 +90,36 @@ FormApi.setAction(async function related(conduit) {
|
|
|
84
90
|
conduit.end(result);
|
|
85
91
|
});
|
|
86
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The action to recompute field values
|
|
95
|
+
*
|
|
96
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
97
|
+
* @since 0.2.9
|
|
98
|
+
* @version 0.2.9
|
|
99
|
+
*
|
|
100
|
+
* @param {Conduit} conduit
|
|
101
|
+
* @param {String} model_name
|
|
102
|
+
* @param {String} field
|
|
103
|
+
*/
|
|
104
|
+
FormApi.setAction(async function recompute(conduit, model_name, field) {
|
|
105
|
+
|
|
106
|
+
const body = conduit.body || {};
|
|
107
|
+
|
|
108
|
+
const model = this.getModel(model_name);
|
|
109
|
+
|
|
110
|
+
if (!model) {
|
|
111
|
+
return conduit.error(new Error('Model "' + model_name + '" not found'));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let doc = model.createDocument(body);
|
|
115
|
+
|
|
116
|
+
await doc.recomputeFieldIfNecessary(field, true);
|
|
117
|
+
|
|
118
|
+
conduit.end({
|
|
119
|
+
result : doc[field],
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
87
123
|
/**
|
|
88
124
|
* The related action
|
|
89
125
|
*
|
|
@@ -199,7 +199,7 @@ WithDataprovider.setMethod(function getWantedPage() {
|
|
|
199
199
|
*
|
|
200
200
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
201
201
|
* @since 0.2.0
|
|
202
|
-
* @version 0.2.
|
|
202
|
+
* @version 0.2.9
|
|
203
203
|
*
|
|
204
204
|
* @param {Object} fetch_config
|
|
205
205
|
*/
|
|
@@ -207,9 +207,9 @@ WithDataprovider.setMethod(function loadRemoteData(fetch_config) {
|
|
|
207
207
|
|
|
208
208
|
let pledge = new Classes.Pledge();
|
|
209
209
|
|
|
210
|
-
let config = this.getRemoteFetchConfig();
|
|
210
|
+
let config = this.getRemoteFetchConfig(fetch_config);
|
|
211
211
|
|
|
212
|
-
if (fetch_config) {
|
|
212
|
+
if (fetch_config && fetch_config != config) {
|
|
213
213
|
Object.assign(config, fetch_config);
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The apex-chart custom element
|
|
3
|
+
*
|
|
4
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
5
|
+
* @since 0.2.8
|
|
6
|
+
* @version 0.2.8
|
|
7
|
+
*/
|
|
8
|
+
const ApexChart = Function.inherits('Alchemy.Element.Form.Base', 'ApexChart');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The actual ApexChart config
|
|
12
|
+
*
|
|
13
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
14
|
+
* @since 0.2.8
|
|
15
|
+
* @version 0.2.8
|
|
16
|
+
*/
|
|
17
|
+
ApexChart.setAssignedProperty('apex_config');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The element has been added to the dom for the first time
|
|
21
|
+
*
|
|
22
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
23
|
+
* @since 0.2.8
|
|
24
|
+
* @version 0.2.8
|
|
25
|
+
*/
|
|
26
|
+
ApexChart.setMethod(async function introduced() {
|
|
27
|
+
|
|
28
|
+
await hawkejs.require('https://cdn.jsdelivr.net/npm/apexcharts');
|
|
29
|
+
|
|
30
|
+
let apex_config = this.apex_config;
|
|
31
|
+
|
|
32
|
+
let timeline = this.createElement('div');
|
|
33
|
+
timeline.classList.add('apex-chart-container');
|
|
34
|
+
this.append(timeline);
|
|
35
|
+
|
|
36
|
+
let instance = new ApexCharts(timeline, apex_config);
|
|
37
|
+
instance.render();
|
|
38
|
+
});
|
package/element/al_field.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const LAST_SET_VALUE = Symbol('last_set_value');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The al-field element
|
|
3
5
|
*
|
|
@@ -583,7 +585,7 @@ Field.setProperty(function value_element() {
|
|
|
583
585
|
*
|
|
584
586
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
585
587
|
* @since 0.1.0
|
|
586
|
-
* @version 0.
|
|
588
|
+
* @version 0.2.9
|
|
587
589
|
*/
|
|
588
590
|
Field.setProperty(function value() {
|
|
589
591
|
|
|
@@ -591,19 +593,47 @@ Field.setProperty(function value() {
|
|
|
591
593
|
|
|
592
594
|
if (element) {
|
|
593
595
|
return element.value;
|
|
594
|
-
} else {
|
|
595
|
-
return this.original_value;
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
+
return this.value_to_render;
|
|
599
|
+
|
|
598
600
|
}, function setValue(value) {
|
|
599
601
|
|
|
600
|
-
let
|
|
602
|
+
let has_changed = !Object.alike(this[LAST_SET_VALUE], value);
|
|
601
603
|
|
|
602
|
-
if (
|
|
603
|
-
element.value = value;
|
|
604
|
-
} else if (this.original_value == null) {
|
|
604
|
+
if (this.original_value == null) {
|
|
605
605
|
this.original_value = value;
|
|
606
606
|
}
|
|
607
|
+
|
|
608
|
+
this[LAST_SET_VALUE] = value;
|
|
609
|
+
|
|
610
|
+
if (!this.valueElementHasValuePropertySetter()) {
|
|
611
|
+
// @TODO: Rerendering during a render causes a deadlock
|
|
612
|
+
if (has_changed) {
|
|
613
|
+
this.rerender();
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
let element = this.value_element;
|
|
617
|
+
|
|
618
|
+
if (element) {
|
|
619
|
+
element.value = value;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Get value to use for rendering
|
|
626
|
+
*
|
|
627
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
628
|
+
* @since 0.2.9
|
|
629
|
+
* @version 0.2.9
|
|
630
|
+
*/
|
|
631
|
+
Field.setProperty(function value_to_render() {
|
|
632
|
+
if (this[LAST_SET_VALUE] != null) {
|
|
633
|
+
return this[LAST_SET_VALUE];
|
|
634
|
+
} else {
|
|
635
|
+
return this.original_value;
|
|
636
|
+
}
|
|
607
637
|
});
|
|
608
638
|
|
|
609
639
|
/**
|
|
@@ -827,7 +857,122 @@ Field.setMethod(function retained() {
|
|
|
827
857
|
label.setAttribute('for', v_id);
|
|
828
858
|
this.value_element.setAttribute('id', v_id);
|
|
829
859
|
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Is this field in the given list somehow?
|
|
864
|
+
*
|
|
865
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
866
|
+
* @since 0.2.9
|
|
867
|
+
* @version 0.2.9
|
|
868
|
+
*
|
|
869
|
+
* @param {string[]|Field[]} list
|
|
870
|
+
*
|
|
871
|
+
* @return boolean
|
|
872
|
+
*/
|
|
873
|
+
Field.setMethod(function isInList(list) {
|
|
874
|
+
|
|
875
|
+
if (!list?.length) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
for (let entry of list) {
|
|
880
|
+
|
|
881
|
+
if (typeof entry == 'string') {
|
|
882
|
+
if (entry == this.field_name) {
|
|
883
|
+
return true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
830
888
|
|
|
889
|
+
if (entry == this || entry == this.config) {
|
|
890
|
+
return true;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// @TODO
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return false;
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* The element has been introduced to the DOM for the first time
|
|
901
|
+
*
|
|
902
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
903
|
+
* @since 0.2.9
|
|
904
|
+
* @version 0.2.9
|
|
905
|
+
*/
|
|
906
|
+
Field.setMethod(function introduced() {
|
|
907
|
+
|
|
908
|
+
let dependencies = this.getDependencyFields();
|
|
909
|
+
|
|
910
|
+
if (!dependencies.length) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
let form = this.alchemy_form;
|
|
915
|
+
|
|
916
|
+
if (!form) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
form.addEventListener('change', e => {
|
|
921
|
+
|
|
922
|
+
let changed_field = e.target.closest('al-field');
|
|
923
|
+
|
|
924
|
+
console.log('Field change', changed_field, e, this)
|
|
925
|
+
|
|
926
|
+
// Rerender the entire field when a field we depend on changes
|
|
927
|
+
// (@TODO: use something less drastic than a rerender)
|
|
928
|
+
if (changed_field?.field_name && changed_field.isInList(dependencies)) {
|
|
929
|
+
this.handleDependentFieldValueChange(changed_field);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Refresh the value of this field somehow
|
|
936
|
+
*
|
|
937
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
938
|
+
* @since 0.2.9
|
|
939
|
+
* @version 0.2.9
|
|
940
|
+
*
|
|
941
|
+
* @param {Alchemy.Element.Form.Field} changed_field
|
|
942
|
+
*/
|
|
943
|
+
Field.decorateMethod(
|
|
944
|
+
Blast.Decorators.throttle({
|
|
945
|
+
minimum_wait: 150,
|
|
946
|
+
reset_on_call: true,
|
|
947
|
+
}),
|
|
948
|
+
async function handleDependentFieldValueChange(changed_field) {
|
|
949
|
+
|
|
950
|
+
const field = this.config;
|
|
951
|
+
|
|
952
|
+
// Unset the value
|
|
953
|
+
this.value = null;
|
|
954
|
+
|
|
955
|
+
if (field?.is_computed) {
|
|
956
|
+
let doc = this.alchemy_form.getUpdatedDocument();
|
|
957
|
+
|
|
958
|
+
if (doc) {
|
|
959
|
+
let value = await doc.recomputeFieldIfNecessary(field, true);
|
|
960
|
+
this.value = value;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Get any other field names this field depends on
|
|
967
|
+
*
|
|
968
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
969
|
+
* @since 0.2.9
|
|
970
|
+
* @version 0.2.9
|
|
971
|
+
*
|
|
972
|
+
* @return {string[]}
|
|
973
|
+
*/
|
|
974
|
+
Field.setMethod(function getDependencyFields() {
|
|
975
|
+
return this.config?.dependency_fields || [];
|
|
831
976
|
});
|
|
832
977
|
|
|
833
978
|
/**
|
|
@@ -835,7 +980,7 @@ Field.setMethod(function retained() {
|
|
|
835
980
|
*
|
|
836
981
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
837
982
|
* @since 0.1.0
|
|
838
|
-
* @version 0.
|
|
983
|
+
* @version 0.2.9
|
|
839
984
|
*
|
|
840
985
|
* @param {Object} config
|
|
841
986
|
* @param {HTMLElement} element
|
|
@@ -846,14 +991,10 @@ Field.setMethod(async function loadData(config, element) {
|
|
|
846
991
|
|
|
847
992
|
if (field) {
|
|
848
993
|
|
|
849
|
-
console.log('Loading data...', config, element);
|
|
850
|
-
|
|
851
994
|
let result;
|
|
852
995
|
|
|
853
996
|
if (typeof field.loadData == 'function') {
|
|
854
997
|
|
|
855
|
-
console.log(' -- Using loadData of', field);
|
|
856
|
-
|
|
857
998
|
try {
|
|
858
999
|
result = await field.loadData(config, element);
|
|
859
1000
|
} catch (err) {
|
|
@@ -866,27 +1007,138 @@ Field.setMethod(async function loadData(config, element) {
|
|
|
866
1007
|
}
|
|
867
1008
|
}
|
|
868
1009
|
|
|
1010
|
+
const field_options = field.options || {},
|
|
1011
|
+
assoc_options = field_options.options;
|
|
1012
|
+
|
|
869
1013
|
let model = field.parent_schema?.model_name,
|
|
870
|
-
assoc_model =
|
|
1014
|
+
assoc_model = field_options.model_name || field_options.modelName;
|
|
1015
|
+
|
|
1016
|
+
let body = {
|
|
1017
|
+
field : field.name,
|
|
1018
|
+
model : model,
|
|
1019
|
+
assoc_model : assoc_model,
|
|
1020
|
+
config : config,
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
if (assoc_options?.constraints) {
|
|
1024
|
+
body.constraints = this.resolveConstraintInstruction(assoc_options.constraints);
|
|
1025
|
+
}
|
|
871
1026
|
|
|
872
1027
|
let resource_options = {
|
|
873
1028
|
name : 'FormApi#related',
|
|
874
1029
|
post : true,
|
|
875
|
-
body :
|
|
876
|
-
field : field.name,
|
|
877
|
-
model : model,
|
|
878
|
-
assoc_model : assoc_model,
|
|
879
|
-
config : config,
|
|
880
|
-
}
|
|
1030
|
+
body : body,
|
|
881
1031
|
};
|
|
882
1032
|
|
|
883
|
-
console.log('Resource options:', resource_options)
|
|
884
|
-
|
|
885
1033
|
if (this.data_src) {
|
|
886
1034
|
resource_options.name = this.data_src;
|
|
887
1035
|
}
|
|
888
1036
|
|
|
889
1037
|
return this.hawkejs_helpers.Alchemy.getResource(resource_options);
|
|
890
1038
|
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Resolve a constraint instruction
|
|
1043
|
+
*
|
|
1044
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1045
|
+
* @since 0.2.9
|
|
1046
|
+
* @version 0.2.9
|
|
1047
|
+
*
|
|
1048
|
+
* @param {Object} constraints
|
|
1049
|
+
*/
|
|
1050
|
+
Field.setMethod(function resolveConstraintInstruction(constraints) {
|
|
1051
|
+
|
|
1052
|
+
let context,
|
|
1053
|
+
result = {},
|
|
1054
|
+
value,
|
|
1055
|
+
key;
|
|
1056
|
+
|
|
1057
|
+
for (key in constraints) {
|
|
1058
|
+
value = constraints[key];
|
|
1059
|
+
|
|
1060
|
+
if (value && typeof value == 'object') {
|
|
1061
|
+
if (value instanceof Classes.Alchemy.PathEvaluator) {
|
|
1062
|
+
if (!context && this.alchemy_form) {
|
|
1063
|
+
context = this.alchemy_form.getMainValue();
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
result[key] = value.getValue({$0: context}) ?? null;
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
result[key] = value;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
return result;
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Does this field require a re-render when a related field changes?
|
|
1078
|
+
*
|
|
1079
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1080
|
+
* @since 0.2.9
|
|
1081
|
+
* @version 0.2.9
|
|
1082
|
+
*/
|
|
1083
|
+
Field.setMethod(function requiresRerenderOnRelatedFieldChange() {
|
|
1084
|
+
|
|
1085
|
+
if (!this.valueElementHasValuePropertySetter()) {
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
return false;
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Can the value element of this field be updated by its value property?
|
|
1094
|
+
*
|
|
1095
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1096
|
+
* @since 0.2.9
|
|
1097
|
+
* @version 0.2.9
|
|
1098
|
+
*/
|
|
1099
|
+
Field.setMethod(function valueElementHasValuePropertySetter() {
|
|
1100
|
+
|
|
1101
|
+
let element = this.value_element;
|
|
1102
|
+
|
|
1103
|
+
if (!element) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
let descriptor = Object.getPropertyDescriptor(element, 'value');
|
|
1108
|
+
|
|
1109
|
+
if (typeof descriptor?.set == 'function') {
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return false;
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Get a certain field option.
|
|
1118
|
+
* This might be defined in the Field instance itself, or on this element.
|
|
1119
|
+
*
|
|
1120
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1121
|
+
* @since 0.2.9
|
|
1122
|
+
* @version 0.2.9
|
|
1123
|
+
*/
|
|
1124
|
+
Field.setMethod(function getFieldOption(name) {
|
|
1125
|
+
|
|
1126
|
+
if (!name) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
let dasherized = name.dasherize();
|
|
1131
|
+
let attribute_name = 'data-' + dasherized;
|
|
1132
|
+
|
|
1133
|
+
if (this.hasAttribute(attribute_name)) {
|
|
1134
|
+
let result = this.getAttribute(attribute_name);
|
|
1135
|
+
|
|
1136
|
+
if (result) {
|
|
1137
|
+
return result;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
let underscored = name.underscore();
|
|
891
1142
|
|
|
1143
|
+
return this.config?.options?.[underscored];
|
|
892
1144
|
});
|
|
@@ -21,7 +21,7 @@ FieldSchema.setTemplateFile('form/elements/alchemy_field_schema');
|
|
|
21
21
|
*
|
|
22
22
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
23
23
|
* @since 0.1.0
|
|
24
|
-
* @version 0.2.
|
|
24
|
+
* @version 0.2.9
|
|
25
25
|
*/
|
|
26
26
|
FieldSchema.setProperty(function schema() {
|
|
27
27
|
|
|
@@ -46,12 +46,7 @@ FieldSchema.setProperty(function schema() {
|
|
|
46
46
|
|
|
47
47
|
const form = field_element.alchemy_form;
|
|
48
48
|
|
|
49
|
-
let record_value = form.
|
|
50
|
-
|
|
51
|
-
if (form.model) {
|
|
52
|
-
record_value = record_value[form.model];
|
|
53
|
-
}
|
|
54
|
-
|
|
49
|
+
let record_value = form.getMainValue();
|
|
55
50
|
parent_schema_value = record_value;
|
|
56
51
|
}
|
|
57
52
|
|
package/element/al_form.js
CHANGED
|
@@ -34,6 +34,15 @@ Form.setAttribute('method');
|
|
|
34
34
|
*/
|
|
35
35
|
Form.setAttribute('model');
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Should the entire document be submitted?
|
|
39
|
+
*
|
|
40
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
41
|
+
* @since 0.2.9
|
|
42
|
+
* @version 0.2.9
|
|
43
|
+
*/
|
|
44
|
+
Form.setAttribute('serialize-entire-document', {type: 'boolean'});
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* The document that is being edited
|
|
39
48
|
*
|
|
@@ -88,34 +97,20 @@ Form.setProperty(function main_error_area() {
|
|
|
88
97
|
*
|
|
89
98
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
90
99
|
* @since 0.1.0
|
|
91
|
-
* @version 0.2.
|
|
100
|
+
* @version 0.2.9
|
|
92
101
|
*/
|
|
93
102
|
Form.setProperty(function value() {
|
|
94
103
|
|
|
95
|
-
let
|
|
96
|
-
result = {},
|
|
97
|
-
field,
|
|
98
|
-
key,
|
|
99
|
-
i;
|
|
100
|
-
|
|
101
|
-
if (this.document && this.document.$pk) {
|
|
102
|
-
result[this.document.$model.primary_key] = this.document.$pk;
|
|
103
|
-
}
|
|
104
|
+
let result = this.getMainValue();
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (this.model) {
|
|
107
|
+
let model = alchemy.getModel(this.model);
|
|
107
108
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
109
|
+
if (model) {
|
|
110
|
+
result = {
|
|
111
|
+
[model.model_name] : result
|
|
112
|
+
};
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
result[field.field_name] = field.value;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (this.model) {
|
|
116
|
-
result = {
|
|
117
|
-
[this.model] : result
|
|
118
|
-
};
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
return result;
|
|
@@ -148,6 +143,45 @@ Form.setMethod(Hawkejs.SERIALIZE_FORM, function serializeForm() {
|
|
|
148
143
|
return this.value;
|
|
149
144
|
});
|
|
150
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Get the non-wrapped value
|
|
148
|
+
*
|
|
149
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
150
|
+
* @since 0.2.9
|
|
151
|
+
* @version 0.2.9
|
|
152
|
+
*/
|
|
153
|
+
Form.setMethod(function getMainValue() {
|
|
154
|
+
|
|
155
|
+
let fields = this.queryAllNotNested('al-field'),
|
|
156
|
+
result = {},
|
|
157
|
+
field,
|
|
158
|
+
i;
|
|
159
|
+
|
|
160
|
+
if (this.document) {
|
|
161
|
+
|
|
162
|
+
if (this.serialize_entire_document) {
|
|
163
|
+
let value = this.document.$main;
|
|
164
|
+
Object.assign(result, value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (this.document.$pk) {
|
|
168
|
+
result[this.document.$model.primary_key] = this.document.$pk;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (i = 0; i < fields.length; i++) {
|
|
173
|
+
field = fields[i];
|
|
174
|
+
|
|
175
|
+
if (field && field.readonly) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
result[field.field_name] = field.value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
});
|
|
184
|
+
|
|
151
185
|
/**
|
|
152
186
|
* Submit this form
|
|
153
187
|
*
|
|
@@ -172,33 +206,130 @@ Form.setMethod(async function submit() {
|
|
|
172
206
|
}
|
|
173
207
|
}
|
|
174
208
|
|
|
209
|
+
if (result?.render == false && result.document) {
|
|
210
|
+
this.setDocument(result.document);
|
|
211
|
+
}
|
|
212
|
+
|
|
175
213
|
return result;
|
|
176
214
|
});
|
|
177
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Get a field by its name
|
|
218
|
+
*
|
|
219
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
220
|
+
* @since 0.2.9
|
|
221
|
+
* @version 0.2.9
|
|
222
|
+
*/
|
|
223
|
+
Form.setMethod(function getField(name) {
|
|
224
|
+
|
|
225
|
+
let fields = this.queryAllNotNested('al-field'),
|
|
226
|
+
field,
|
|
227
|
+
i;
|
|
228
|
+
|
|
229
|
+
for (i = 0; i < fields.length; i++) {
|
|
230
|
+
field = fields[i];
|
|
231
|
+
|
|
232
|
+
if (field.field_name == name) {
|
|
233
|
+
return field;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Set the document
|
|
240
|
+
*
|
|
241
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
242
|
+
* @since 0.2.9
|
|
243
|
+
* @version 0.2.9
|
|
244
|
+
*/
|
|
245
|
+
Form.setMethod(function setDocument(document) {
|
|
246
|
+
|
|
247
|
+
let current_value = this.getMainValue();
|
|
248
|
+
|
|
249
|
+
// Set the current document
|
|
250
|
+
this.document = document;
|
|
251
|
+
|
|
252
|
+
console.log('Setting document', document);
|
|
253
|
+
|
|
254
|
+
for (let key in current_value) {
|
|
255
|
+
let original_value = current_value[key],
|
|
256
|
+
new_value = document[key];
|
|
257
|
+
|
|
258
|
+
if (!Object.alike(original_value, new_value)) {
|
|
259
|
+
let field = this.getField(key);
|
|
260
|
+
|
|
261
|
+
if (field) {
|
|
262
|
+
field.value = new_value;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
});
|
|
268
|
+
|
|
178
269
|
/**
|
|
179
270
|
* Validate this form
|
|
180
271
|
*
|
|
181
272
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
182
273
|
* @since 0.2.2
|
|
183
|
-
* @version 0.2.
|
|
274
|
+
* @version 0.2.9
|
|
184
275
|
*/
|
|
185
276
|
Form.setMethod(async function validate() {
|
|
186
277
|
|
|
187
|
-
|
|
188
|
-
let model = alchemy.getModel(this.model);
|
|
278
|
+
let doc = this.getValueAsDocument();
|
|
189
279
|
|
|
190
|
-
|
|
191
|
-
let doc = model.createDocument(this.value);
|
|
280
|
+
let violations = await doc.getViolations();
|
|
192
281
|
|
|
193
|
-
|
|
282
|
+
if (violations) {
|
|
283
|
+
this.showError(violations);
|
|
284
|
+
throw violations;
|
|
285
|
+
}
|
|
194
286
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get the value as a document instance
|
|
291
|
+
*
|
|
292
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
293
|
+
* @since 0.2.9
|
|
294
|
+
* @version 0.2.9
|
|
295
|
+
*/
|
|
296
|
+
Form.setMethod(function getValueAsDocument() {
|
|
297
|
+
|
|
298
|
+
if (!this.model) {
|
|
299
|
+
return;
|
|
200
300
|
}
|
|
201
301
|
|
|
302
|
+
let model = alchemy.getModel(this.model);
|
|
303
|
+
|
|
304
|
+
if (model) {
|
|
305
|
+
return model.createDocument(this.getMainValue());
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get the updated document:
|
|
311
|
+
* The original document's values + the form's values
|
|
312
|
+
*
|
|
313
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
314
|
+
* @since 0.2.9
|
|
315
|
+
* @version 0.2.9
|
|
316
|
+
*/
|
|
317
|
+
Form.setMethod(function getUpdatedDocument() {
|
|
318
|
+
|
|
319
|
+
if (!this.model) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const model = alchemy.getModel(this.model);
|
|
324
|
+
const original_value = this.document;
|
|
325
|
+
|
|
326
|
+
if (!original_value) {
|
|
327
|
+
return this.getValueAsDocument();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let main_value = Object.assign({}, original_value.$main, this.getMainValue());
|
|
331
|
+
|
|
332
|
+
return model.createDocument(main_value);
|
|
202
333
|
});
|
|
203
334
|
|
|
204
335
|
/**
|
package/element/al_table.js
CHANGED
|
@@ -1121,7 +1121,7 @@ Table.setMethod(function getRemoteFetchConfig() {
|
|
|
1121
1121
|
*
|
|
1122
1122
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
1123
1123
|
* @since 0.2.0
|
|
1124
|
-
* @version 0.2.
|
|
1124
|
+
* @version 0.2.8
|
|
1125
1125
|
*/
|
|
1126
1126
|
Table.setMethod(function applyFetchedData(err, result, config) {
|
|
1127
1127
|
|
|
@@ -1134,6 +1134,9 @@ Table.setMethod(function applyFetchedData(err, result, config) {
|
|
|
1134
1134
|
|
|
1135
1135
|
if (Array.isArray(result)) {
|
|
1136
1136
|
records = result;
|
|
1137
|
+
} else if (result.length) {
|
|
1138
|
+
// This way we keep `DocumentList` instances as they are!
|
|
1139
|
+
records = result;
|
|
1137
1140
|
} else {
|
|
1138
1141
|
records = result.records;
|
|
1139
1142
|
}
|
package/element/al_toggle.js
CHANGED
|
@@ -42,12 +42,16 @@ AlchemyToggle.setAttribute('value', null, function setValue(value) {
|
|
|
42
42
|
*
|
|
43
43
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
44
44
|
* @since 0.1.0
|
|
45
|
-
* @version 0.
|
|
45
|
+
* @version 0.2.9
|
|
46
46
|
*/
|
|
47
47
|
AlchemyToggle.setMethod(function introduced() {
|
|
48
48
|
|
|
49
49
|
this.addEventListener('click', e => {
|
|
50
50
|
this.value = !this.value;
|
|
51
|
+
this.emit('change', {
|
|
52
|
+
'bubbles' : true,
|
|
53
|
+
'cancelable': true
|
|
54
|
+
});
|
|
51
55
|
});
|
|
52
56
|
|
|
53
57
|
let input = this.querySelector('input');
|
|
@@ -62,5 +66,9 @@ AlchemyToggle.setMethod(function introduced() {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
this.value = input.checked;
|
|
69
|
+
this.emit('change', {
|
|
70
|
+
'bubbles' : true,
|
|
71
|
+
'cancelable': true
|
|
72
|
+
});
|
|
65
73
|
});
|
|
66
74
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recompute fields in the browser that has its logic in the server
|
|
3
|
+
*
|
|
4
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
5
|
+
* @since 0.2.9
|
|
6
|
+
* @version 0.2.9
|
|
7
|
+
*
|
|
8
|
+
* @param {Alchemy.Client.Document} document
|
|
9
|
+
* @param {Alchemy.Client.Field} field
|
|
10
|
+
*/
|
|
11
|
+
alchemy.registerCustomHandler('recompute_field', async function fieldRecomputeHandler(document, field) {
|
|
12
|
+
|
|
13
|
+
let body = {
|
|
14
|
+
_id : document._id,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (let other_field of field.dependency_fields) {
|
|
18
|
+
let value = other_field.getRecordValue(document);
|
|
19
|
+
body[other_field.name] = value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let resource_options = {
|
|
23
|
+
name : 'FormApi#recompute',
|
|
24
|
+
post : true,
|
|
25
|
+
body : body,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let params = {
|
|
29
|
+
model_name : document.$model_name,
|
|
30
|
+
field : field.name,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
console.log(document)
|
|
34
|
+
|
|
35
|
+
let helpers;
|
|
36
|
+
|
|
37
|
+
if (Blast.isServer) {
|
|
38
|
+
helpers = document.conduit?.renderer?.helpers;
|
|
39
|
+
} else {
|
|
40
|
+
helpers = hawkejs.scene.helpers;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!helpers) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let response = await helpers.Alchemy.getResource(resource_options, params);
|
|
48
|
+
|
|
49
|
+
return response?.result;
|
|
50
|
+
});
|
package/helper/pathway/leaf.js
CHANGED
|
@@ -137,18 +137,13 @@ Leaf.setMethod(async function getPage(page_nr) {
|
|
|
137
137
|
|
|
138
138
|
let provider = this.getProvider();
|
|
139
139
|
|
|
140
|
-
console.log('Getting page...', page_nr, 'from', provider)
|
|
141
|
-
|
|
142
140
|
if (!provider) {
|
|
143
141
|
return;
|
|
144
142
|
}
|
|
145
143
|
|
|
146
144
|
let records = await provider.getAll();
|
|
147
145
|
|
|
148
|
-
console.log('Got records:', records)
|
|
149
|
-
|
|
150
146
|
return records;
|
|
151
|
-
|
|
152
147
|
});
|
|
153
148
|
|
|
154
149
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%
|
|
2
|
+
if (alchemy_field && alchemy_field.config && alchemy_field.config.options && alchemy_field.config.options.scale != null) {
|
|
3
|
+
step = 10 ** (-1 * alchemy_field.config.options.scale);
|
|
4
|
+
} else {
|
|
5
|
+
step = 1;
|
|
6
|
+
}
|
|
7
|
+
%>
|
|
8
|
+
<input
|
|
9
|
+
value=<% value %>
|
|
10
|
+
class="alchemy-field-value"
|
|
11
|
+
type="number"
|
|
12
|
+
form=<% form_id %>
|
|
13
|
+
name=<% path %>
|
|
14
|
+
step={% step %}
|
|
15
|
+
>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%
|
|
2
|
+
|
|
3
|
+
if (value) {
|
|
4
|
+
|
|
5
|
+
// Make sure it's a date
|
|
6
|
+
value = LocalDate.create(value);
|
|
7
|
+
|
|
8
|
+
// According to MDN `toISOString()` should work,
|
|
9
|
+
// but neither Chrome or Firefox allow that format (it still contains timezone info)
|
|
10
|
+
value = value.format('Y-m-d\\TH:i:s');
|
|
11
|
+
}
|
|
12
|
+
%>
|
|
13
|
+
<input
|
|
14
|
+
value=<% value %>
|
|
15
|
+
class="alchemy-field-value"
|
|
16
|
+
type="datetime-local"
|
|
17
|
+
form=<% form_id %>
|
|
18
|
+
name=<% path %>
|
|
19
|
+
pattern="\d{4}-\d{2}-\d{2} \d{1,2}:\d{1,2}(?::\d{1,2})?"
|
|
20
|
+
>
|